2016年4月18日 星期一

從解決 dotNet ComboBox 文字無法置中問題領略自繪元件的威力

作業系統歷經不斷的發展,從以前古老的 OS/2、DOS、Win3.0 到現在的 Win7、Win10、Linux 3.x系統在視窗子系統的部分越來越強大,像微軟最新的 WPF 框架其 UI 繪圖引擎甚至已經以
DirectXGraphics(前身為 DirectDraw 與 Direct3D)為基礎可以使用 GPU 硬體達到高速繪製 UI
的能力,從作業系統不斷演進與增強 GUI 子系統就可以知道,現代化的程式設計 GUI 的程式
設計是相當重要的一部分,也是複雜度很高的部分,一位工程師有可能完全掌握某些演算法程式的撰寫,可是基本上不可能完全掌握 OS 所有 UI 繪圖相關的 API,常常遇到的窘境正是
演算法有了,可是對應的 UI 卻不能很好的做出視覺化的呈現,導致客戶放棄功能強大卻難用
的操作介面,轉而選擇能夠簡單直覺操作的良好 UI,也許功能比原先同性質的軟體弱。

因此對於大部分非設計GUI 程式庫為職業的工程師而言,要用系統原始的 Toolkit 搞一個 UI 程式而言(有沒有用 Xt 寫過 Unix 下 X Window 程式,自己用 API 一筆一畫刻出 Chart 元件?讀者就會知道有多痛苦),往往 UI 控制的複雜度反而比軟體主要核心的演算法還複雜,所以不少公司都已經致力於簡化 GUI 程式設計的難度,讓非 GUI 設計專家的工程師,也能在預設的 Widget 或 Control 寫出標準的UI 介面,像是微軟的 dotNet Framework 就是一個龐大的框架型的程式庫,其中 WindowsForms 命名空間裡面就是針對一般 GUI 設計會用到的標準元件。

今天要討論的問題就是,當你用這些 UI Toolkit 可能會遇到某個你要用的 Control 裡面可能你
預期會有的屬性竟然找不到,而且該元件類別的聚合關係很詭異,這個時候你就需要能夠有自繪該元件外觀的能力,跳脫原本 Toolkit 對該元件繪製的限制,我注意到 dotNet Framework
裡面有一個元件就很詭異,這個元件就是 ComboBox,它詭異在甚麼地方呢,幾乎所有可以
顯示文字的 Control 在 dotNet Framework 裡面都可以控制顯示文字的對齊方式,在屬性->外觀
裡面的 TextAlign,你可以很輕易的控制所有 dotNet UI Control 裡面文字對齊的方式。

TextAlign 有三種對齊方式:Left Right Center

偏偏這個 dotNet 的 ComboBox 裡面就是沒有,非常的不討喜,當你很高興的在視窗設計師
(就是可以所見即得拖拉UI的編輯器)裡面精心弄了一個參數面板視窗,當所有的 Control
都調整成文字置中的時候,你會一整個一頭霧水,怎麼 ComboBox 裡面就是找不到 TextAlign
,這時候你可能只能含淚通通又改為文字靠左去配合那個無法更動文字對齊的 ComboBox
想解決這個問題,就不是拖拉跟點點屬性可以解決問題。

圖一、NumericUpDown 有 TextAlign 可以調整,能夠有這個屬性的原因就是該元件內的編輯
元件用的是真正 dotNet 環境的 WindowsForms EDIT Class。

圖二、ComboBox 沒有 TextAlign 可以調整,沒有這個屬性的原因就是該元件內的編輯
元件用的竟然是 Windows 系統底層原始的 Edit Class,這個當然不是 dotNet 的東西。

圖一與圖二解說應該很清楚了,dotNet ComboBox 內的編輯元件非常不合常理,裡面用的竟然
不是原生 dotNet 的元件去聚合,而是直接使用 Windows 系統的 Edit 元件,這點相當的詭異
我猜這可能跟微軟早期在實現這些 WindowsForms 裡面的 UI 元件有關,有可能 Windows
Forms ComboBox Class 這個元件比 WindowsForms EDIT Class 更早就被創造出來,所以才會去
呼叫到系統原生的 Edit Class,就 UI 程式庫的一致性來說這並不是一個好的現象,因為對
使用者而言,看到具有顯示文字的 dotNet UI 竟然在屬性找不到 TextAlign 看起來就非常詭異。

要解決這個問題就要動用到自繪元件的技巧,雖然該 dotNet ComboBox 沒有提供 TextAlign
但是因為它有用到傳統的 Edit 控制項,所以讀者可以看到微軟其實提供了 DrawMode 與
DropDownStyle,這個是系統原生的 Edit 與 ComboBox 封裝到 dotNet 環境中的屬性。

圖三、DrawMode 設定為 OwnerDrawFixed 與 DropDownStyle 設定為 DropDownList

如圖三,我把 DrawMode 設定為 OwnerDrawFixed 與 DropDownStyle 設定為 DropDownList
OwnerDrawFixed 可以觸發 DrawItem 的事件,這個寫過 Win32 原生視窗程式的人就會知道
WM_DRAWITEM 這個事件,事件對應的參數 DRAWITEM STRUCTURE 就可以讓程式設計師
進入繪圖的方式自繪 UI 元件,跳脫原本的外觀,在 dotNet 裡面就是觸發 DrawItem 事件
一旦進入事件裡面就可以取得元件的 dotNet Graphics 物件,就可以控制元件的外觀,而
改為 DropDownList 的目的是要讓 ComboBox 選定的 Item 其風格可以與 ComboBox 顯示的風格
一致,也就是說假如選擇 Item 的文字靠左,ComboBox Text 文字也會靠左。

下面我直接給出我已經寫好的 DrawItem 事件內的程式碼:


結果呈現:

圖四、利用 DrawItem 事件硬是搞出 ComboBox 文字置中效果

由圖四的結果可知,在 DrawItem 事件裡面可以直接操作 UI 物件底層,也就是 Graphics 物件
一旦取得繪圖物件,其實你要怎麼搞都可以,要做出每個 Item 顏色背景不一樣,文字對齊
不一樣其實都可以辦到,不過說真的,在自定控制項技術的領域,攔截 WM_DRAWITEM
重新繪製元件的方法算是過時的方法,不過既然 ComboBox 有提供這樣的事件,那就直接
抓來用比較簡單,這篇的目的讓對自訂 GUI 行為與外觀有興趣的讀者提供一個簡單的入門。

圖五、利用 DrawItem 事件做出只有偶數 Item 置中

讀者可以想一想我圖五這種效果的 ComboBox 是怎麼做到的呢?
這個就留給讀者自己去想,應該很簡單吧 : )

沒有留言:

張貼留言