乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      一步一步玩控件:自定義TabControl

       鴻楓nh 2016-08-03


      作者:野比 (conmajia@gmail.com

      時間:May, 2012

      封面圖片為野比原創(chuàng),請勿未經(jīng)允許私自引用


      #1-1

      嗯,各位,又是我,生物鐘顛倒的家伙。

      今天我要山寨的是大名鼎鼎的Apple,傳說中的「被山寨之王」。

      沒錯,都被我山寨好幾次了。

      說起Apple,相信大家對他家的各種產(chǎn)品,不管他軟還是硬,都有相當?shù)暮酶小?/p>

      最近Apple把自家的Web瀏覽器Safari升級到了第5版,并同步推出了Windows版,支持WinXP開始的全部Windows版本。

      不得不說,這是一個很給力的瀏覽器,它看起來就像這樣。

      Icon for Safari

      其實我并不是蘋果控,我控紅富士要多點。客觀的評價Safari,這個軟件界面華麗,速度快,但在Windows平臺上,TopSites首頁資源消耗巨大,操作習慣和常規(guī)Win瀏覽器有一定區(qū)別,部分網(wǎng)頁不支持或不兼容(WebKit引擎)。

      不多說了,這不是重點。重點在于它的「偏好設置(Preference)」界面,就是這個:



      看到這個,你肯定會覺得怎么蘋果的東西會變得這么一般呢?不過就是TabControl上面增加了幾個圖標嘛。

      嗯,朋友,你說的似乎沒錯。但是,我曾經(jīng)也算中肯的評價過蘋果的東西,拋開外觀,蘋果的特點之一就是「悶騷」,還有「OCD」,也就是強迫癥。

      聽我這么說顯得很干癟,那么就讓我順著導航標簽,一路點擊過去,看看會發(fā)生什么事。




      沒錯,這窗口會自動伸縮,而且是動畫的!這就是apple悶騷的地方!

      為了不讓他一家獨騷,為了不辜負他被山寨之王的名頭,我只好勉為其難的山寨一番了。


      山寨前的準備

      山寨其實沒啥好準備的,但還是需要幾樣重要的東西:

      • 原裝貨:Apple Safari 5
      • 照相機:Snagit 10
      • 生產(chǎn)線:Visual Studio 2005
      • 手冊:MSDN
      • 苦力:野比

      分析,分析

      山寨的靈魂在于分析,首先把剛才拍的高清果照扯過來分解了。



      所以,我把他分解成這幾個部分:

      1. 根據(jù)標簽不同修改窗體標題
      2. 導航標簽
      3. 標簽面板
      4. 自動縮放

      組件設計

      分析了其中的功能,那么就要想想怎么來實現(xiàn)。

      從功能來看,這個窗口實際上是由多個子面板切換來實現(xiàn)的,最多他加了點自動縮放。所以從本質來說,還是一個標簽切換的窗口。

      我最早想到的就是大名鼎鼎卻又丑得無以復加的TabControl。



      按照標簽切換這個思想,TabControl完全可以勝任這次的山寨需求。但是TabControl這么丑,必須要給它整整容才行。想不到我竟然有整容的才華。


      下手吧,年輕人!

      因為要改動的地方會很多,所以還是完全自己來繪制標簽好了。為了完全自定義TabControl,同時方便循環(huán)利用,從TabControl派生一個我們自己的標簽控件TabControlEx。

      1. public class TabControlEx : System.Windows.Forms.TabControl  



      這就是我們的TabControlEx,看起來和TabControl沒什么兩樣(那是當然的)。

      為了讓他看起來不太一樣,在構造函數(shù)里加上下面的代碼。

      1. base.SetStyle(  
      2.      ControlStyles.UserPaint |                      // 控件將自行繪制,而不是通過操作系統(tǒng)來繪制  
      3.      ControlStyles.OptimizedDoubleBuffer |          // 該控件首先在緩沖區(qū)中繪制,而不是直接繪制到屏幕上,這樣可以減少閃爍  
      4.      ControlStyles.AllPaintingInWmPaint |           // 控件將忽略 WM_ERASEBKGND 窗口消息以減少閃爍  
      5.      ControlStyles.ResizeRedraw |                   // 在調整控件大小時重繪控件  
      6.      ControlStyles.SupportsTransparentBackColor,    // 控件接受 alpha 組件小于 255 的 BackColor 以模擬透明  
      7.      true);                                         // 設置以上值為 true  
      8. base.UpdateStyles();  

      這段代碼的意思就像注釋里說的,注意ControlStyles這個枚舉是可以按位組合的,所以上面要用「或(|)」來進行連接,這樣系統(tǒng)就會完全忽視TabControl這個基類的界面顯示,而使用我們自己的方式來呈現(xiàn)UI。

      現(xiàn)在TabControlEx看起來是這樣的。


      啥米???!OMG!東西哪去了??

      嗯,當我第一次玩UserPaint的時候,也被嚇了一跳。其實這就是上面我們設置的那句ControlStyles.UserPaint,于是系統(tǒng)就不幫我們畫任何東西了。

      所以從現(xiàn)在開始,一切都要靠自己了。下面所有的繪制都在OnPaint()方法中繪制。

      為了先讓我們找到方向,在OnPaint()方法中,我們先把Tab的位置找到,為此我們給每個Tab的邊框都畫出來。

      1. protected override void OnPaint(PaintEventArgs e)  
      2. {  
      3.     for (int i = 0; i < this.TabCount; i++)  
      4.     {  
      5.         e.Graphics.DrawRectangle(Pens.Red, this.GetTabRect(i));  
      6.     }  
      7. }  

      TabControl.GetTabRect(int)的功能是獲得指定index的標簽的矩形位置。畫完后,我們的TabControlEx看起來不那么迷糊了。


      可是,標簽的大小還是不對,我們要的不是普通的那種長條,而是悶騷的蘋果的瘦高型,要像這樣。


      嗯,好吧,我們回到構造函數(shù),用下面的語句來設置大小。

      1. this.SizeMode = TabSizeMode.Fixed;  // 大小模式為固定  
      2. this.ItemSize = new Size(44, 55);   // 設定每個標簽的尺寸  

      上面設置44x55其實只是因為蘋果原版剛好是這么大,先這么著,后面如果不合適了,回頭再來改。現(xiàn)在標簽是這樣的了。


      Apple標簽的選中狀態(tài)是帶陰影的,看起來很酷,可是如果我用GDI+來畫的話,什么漸變什么變換,煩都煩死了。怎么辦呢?

      請記住,我們正在山寨。所謂山寨的精神,就是不問方法、不擇手段,只要最后「看起來一樣」就行了。所以,我決定用上摳圖大法,把apple的背景圖摳出來。



      把這個背景保存為TabBackground.bmp文件,然后添加到項目中,把它做成「嵌入的資源」,就像這樣。




      然后我們用一個變量來保存背景圖。因為這張圖隨時會用到,所以還是做成全局變量(類級別),在構造函數(shù)里讀取圖片。

      1. Image backImage;  
      2. public TabControlEx()  
      3. {  
      4.     // (略)  
      5.     backImage = new Bitmap(this.GetType(), "TabButtonBackground.bmp");   // 從資源文件(嵌入到程序集)里讀取圖片  
      6. }  

      現(xiàn)在有了圖標,加上去看看吧。在OnPaint()里這樣寫。

      1. if (this.SelectedIndex == i)  
      2. {  
      3.     e.Graphics.DrawImage(backImage, this.GetTabRect(i));  
      4. }  

      只有被選中的標簽才會出現(xiàn)這種背景。于是,標簽變成這樣了。



      繪制文字

      這會看著還挺單調的,所以我們來加點料。下面來畫文字。說起文字,我想你應該注意到了,Safari的標簽文字,都是帶有陰影的(準確的說是高光)。


      所以,在繪制文字時,先用高光色繪制第一遍,再用普通文字色(黑)繪制第二遍。

      1. protected override void OnPaint(PaintEventArgs e)  
      2. {  
      3.     for (int i = 0; i < this.TabCount; i++)  
      4.     {  
      5.         // (略)  
      6.   
      7.         // Calculate text position  
      8.         Rectangle bounds = this.GetTabRect(i);  
      9.         PointF textPoint = new PointF();  
      10.         SizeF textSize = TextRenderer.MeasureText(this.TabPages[i].Text, this.Font);  
      11.   
      12.         // 注意要加上每個標簽的左偏移量X  
      13.         textPoint.X  
      14.             = bounds.X + (bounds.Width - textSize.Width) / 2;  
      15.         textPoint.Y  
      16.             = bounds.Bottom - textSize.Height - this.Padding.Y;  
      17.   
      18.         // Draw highlights  
      19.         e.Graphics.DrawString(  
      20.             this.TabPages[i].Text,  
      21.             this.Font,  
      22.             SystemBrushes.ControlLightLight,    // 高光顏色  
      23.             textPoint.X,  
      24.             textPoint.Y);  
      25.   
      26.         // 繪制正常文字  
      27.         textPoint.Y--;  
      28.         e.Graphics.DrawString(  
      29.             this.TabPages[i].Text,  
      30.             this.Font,  
      31.             SystemBrushes.ControlText,    // 正常顏色  
      32.             textPoint.X,  
      33.             textPoint.Y);  
      34.   
      35.     }  
      36. }  


      繽紛色彩的源泉:圖標

      文字也有了,那么接下來就輪到圖標了。TabControl是用ImageList控件來存儲自己使用的圖標的,那么添加一個ImageList,然后加入圖標。注意這里都要32x32的圖標,所以應該設置ImageList.ImageSize為32x32。

      1. // 繪制圖標  
      2. if (this.ImageList != null)  
      3. {  
      4.     int index = this.TabPages[i].ImageIndex;  
      5.     string key = this.TabPages[i].ImageKey;  
      6.     Image icon = new Bitmap(1, 1);  
      7.   
      8.     if (index > -1)  
      9.     {  
      10.         icon = this.ImageList.Images[index];  
      11.     }  
      12.     if (!string.IsNullOrEmpty(key))  
      13.     {  
      14.         icon = this.ImageList.Images[key];  
      15.     }  
      16.     e.Graphics.DrawImage(  
      17.         icon,  
      18.         bounds.X + (bounds.Width - icon.Width) / 2,  
      19.         bounds.Top + this.Padding.Y);  
      20. }  


      嗯,現(xiàn)在我們的標簽看起來像那么回事了,接下來就該難看的紅線條退休了。再完善一下,我們的標簽就OK了。

        


      同步滾動演示。上面是山寨,下面是正品,正品打開了文字抗鋸齒,我們也可以,在OnPaint()事件開始加入這樣的代碼。

      1. e.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;  

      到此,標簽導航部分已經(jīng)完成,剩下的,就是窗體的自動縮放功能了。

      作者:野比 (conmajia@gmail.com

      時間:May, 2012


      #1-2


      嗯,還是我。

      現(xiàn)在繼續(xù)昨天的山寨。昨天我們分析得到了4條需要山寨的部分,如下。

      1. 根據(jù)標簽不同修改窗體標題
      2. 導航標簽
      3. 標簽面板
      4. 自動縮放
      通過昨天的努力,我們已經(jīng)搞定了第2、3條,所以,今天的任務,就只剩下兩條

      1. 根據(jù)標簽不同修改窗體標題
      2. 導航標簽
      3. 標簽面板
      4. 自動縮放


      修改窗體標題

      我們參考下圖,



      我們制作的TabControlEx是作為它所在窗體的子控件存在的,為了獲得包含TabControlEx的窗體(的引用),可以調用TabControlEx的FindForm()方法(從Control繼承)。FindForm()可以獲取容納該控件的頂層窗體,在我們的例子里,就是我們的山寨Safari窗體。

      為了在TabControlEx剛剛加入父控件的時候(也就是窗體初始化的時候)就能夠順利「劫持」到窗體的引用,并修改它的標題(否則顯示Tab0的時候會發(fā)現(xiàn)窗體的標題還未改變),我們重寫一下TabControlEx的ParentChanged事件。

      1. // 對父窗體的引用  
      2. Form oldman;  
      3. protected override void OnParentChanged(EventArgs e)  
      4. {  
      5.     // 如果沒有劫持到,則搜索  
      6.     if (oldman == null)  
      7.         oldman = this.FindForm();  
      8.   
      9.     oldman.Text = this.TabPages[0].Text;  
      10. }  

      這樣,我們就可以在啟動時就修改父窗體標題了。我們最終的目的是每次切換標簽時都改變父窗體標題,現(xiàn)在我們拿到了窗體的引用,只需要重寫TabControlEx的Selected事件。

      1. protected override void OnSelected(TabControlEventArgs e)  
      2. {  
      3.     parent.Text = e.TabPage.Text;  
      4. }  

      下面是完成之后的效果



      自動調整窗體大小

      完成了雜項工作,現(xiàn)在要進入今天的重點:自動調整大小。在開始之前,先來回顧一下這個悶騷的功能。



      下面來好好分析一下到底發(fā)生了什么事。

      注意,大家發(fā)現(xiàn)右下角那個問號沒有?根據(jù)觀察,那個問號始終是保持在窗體右下角的,這就好辦了,直接Anchor到Right和Bottom就行。因此下面的分析中直接無視它了。

      從本質上來看,因為切換的標簽內容高度不同,所以窗體高度也發(fā)生了改變。但不管怎么變,窗體的底部到最下面一個控件的距離Δ沒有變化,參考分析圖。





      所以,動畫就是在H1-H2這段距離內發(fā)生的。另外,值得注意的是,Safari是在窗體動畫完成,調整大小到位以后,才顯示新標簽的控件,這樣做可以顯得很有動感,而且留下了足夠的時間加載控件。所以,動畫應該在標簽的Selecting事件里解決,而顯示控件留到Selected事件。

      下面來分析大小調整的算法。


      山寨算法:從不追求精確還原

      通過慢鏡頭分析,可以看到在相同時間差內窗體大小的運動距離是不同的,這說明窗體大小不是勻速改變的。



      為了不讓算法影響我們的設計進度,將算法寫在單獨的方法里(最正規(guī)的應該是寫成委托,直接傳遞方法,但你認為一個山寨貨有必要嗎)。

      1. private double getHeight(double time)  
      2. {  
      3.     // (略)  
      4. }  

      既然這樣,那么算法的問題我們稍后再來討論,現(xiàn)在研究怎樣讓窗體動起來。

      由于動畫過程較長,將近1秒,那么我們實現(xiàn)的時候應當盡量以不影響主線程為前提。除了動不動就多線程這種有點大炮打蚊子太2的方法外,我們還可以用系統(tǒng)自帶的Timer。在每個Timer.Tick事件里挪一步,合起來就成了動畫。

      1. // Δ常量  
      2. int FORM_DELTA = 20;  
      3. // 動畫用Timer  
      4. Timer timer;  
      5. // 經(jīng)歷時間計數(shù)器  
      6. int elapsed = 0;  
      7.   
      8. // 構造函數(shù)  
      9. public TabControlEx()  
      10. {  
      11.     // (略)  
      12.   
      13.     // 初始化Timer  
      14.     timer = new Timer();  
      15.     timer.Interval = 100;  
      16.     timer.Enabled = false;  
      17.     timer.Tick += new EventHandler(timer_Tick);  
      18. }  
      19.   
      20. // Timer tickle  
      21. void timer_Tick(object sender, EventArgs e)  
      22. {  
      23.     if (parent == null)  
      24.         return;  
      25.   
      26.     elapsed++;  
      27.     parent.Height = getHeight(elapsed, FORM_DELTA);  
      28. }  


      現(xiàn)在我們可以填寫剛才分析的Selecting和Selected事件了。



      算法嘗試

      目前流行的加減速函數(shù)有很多,最簡單的從1次函數(shù)(勻速)、2次函數(shù)(勻加速)到3、4甚至5次函數(shù)都有人在用。這類指數(shù)型的加速函數(shù)使用簡單方便,用得很多。下面是在Mahematica里繪制的幾種函數(shù)曲線,從上倒下分別為:g=10的自由落體函數(shù),y=x^2,y=x^3,y=x^4和y=x直線(注意:為了讓大家看清函數(shù)細節(jié),x和y軸不是1:1的)。

      看起來要實現(xiàn)又加速又減速還真是麻煩,看來只有去掉減速了。反正山寨嘛,只要「看起來像」就行了。沒辦法,我們是搞山寨的,手藝當然不行了,所以到底用那種,還真的不知道。山寨大法告訴我們,不知道的東西,「試,就對了」。那么就選3個版本的getHeight()來試試。

      作者:野比 (conmajia@gmail.com

      時間:May, 2012



      (未完待續(xù))

        本站是提供個人知識管理的網(wǎng)絡存儲空間,所有內容均由用戶發(fā)布,不代表本站觀點。請注意甄別內容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權內容,請點擊一鍵舉報。
        轉藏 分享 獻花(0

        0條評論

        發(fā)表

        請遵守用戶 評論公約

        類似文章 更多