轉帖|其它|編輯:郝浩|2011-03-30 13:54:07.000|閱讀 752 次
概述:在上篇中我們實現了DataPager的擴展,本文我們的目標則是ComboBox,標題的“擴展”兩個字在本文稍有不適,因為對DataPager我們確實是擴展了它的外觀和功能,而對于ComboBox,我們要做的事情可能用“改變”這個詞更加恰當。
# 界面/圖表報表/文檔/IDE等千款熱門軟控件火熱銷售中 >>
在上篇中我們實現了DataPager的擴展,本文我們的目標則是ComboBox,標題的“擴展”兩個字在本文稍有不適,因為對DataPager我們確實是擴展了它的外觀和功能,而對于ComboBox,我們要做的事情可能用“改變”這個詞更加恰當。
好了,來看看我們準備對ComboBox做些什么。Microsoft為我們提供的ComboBox簡單好用,當然簡單好用的另外一個意思就是在有些場合它就會顯得很笨,在網上搜索一下ComboBox,比較常見的問題都是ComboBox下拉框帶自定義控件有關的,尤其是帶TreeView的,有一些不錯的解決方案,今天我們完成擴展的第二個實例,換一個角度實現一個樹狀下拉框的ComboBox,不改動ContrlTemplate也不使用UserControl拼裝,就使用DependencyPropertyWatcher。
ComboBox有方便快捷的SelectedValuePath和DisplayMemberPath,也有靈活的ItemTemplate,ItemTemplate幾乎無所不能,但是之所有有幾乎兩字,一旦我們為ComboBox設置了ItemTemplate,那么SelectedItem也套用了ItemTemplate。然而在非常多的場合,我們希望在下拉框中顯示對象的各種詳細信息,而在被選擇之后只顯示關鍵信息,比如一個ComboBox中如果填充了一張客戶列表,那么下拉框展開之時能除了看到客戶名稱還有聯系人和聯系方式會讓我們感覺使用得舒服,但是如果選擇了一個客戶之后,我們一般希望僅僅顯示客戶的名稱就足夠了,或者說我們希望呈現的是另外一種合理的重新被組織過的信息,簡而言之,在DisplayMemberPath之外,我們需要一個獨立的SelectionDisplayMemberPath,在ItemTemplate之外,我們需要一個獨立的SelectionItemTemplate,當然SelectionDisplayMemberPath的適用性非常小,因為它僅僅是一個簡單的Path設置,而SelectionItemTemplate卻真正能為我們解決問題的方案,接下來我們就來實現它。
和DataPager一樣,我們還是先看看ComboBox的ControlTemplate定義:
比DataPager幸運的是,我們可以直接取得ContentPresenter的引用了,由于和DataPager單純的添加元素不同,為ComboBox添加一個SelectionItemTemplate之前,我們還至少要看看為什么ItemTemplate會作用到SelectedItem上的,打開Reflector查看ComboBox的相關代碼,毫無疑問直奔主題找到ComboBox的OnSelectionChanged:
1: internal override void OnSelectionChanged
(int oldIndex, int newIndex, object oldValue, object newValue)
2: {
3: if (this.IsDropDownOpen)
4: {
5: if (newIndex != -1)
6: {
7: base.SetFocusedItem(newIndex, true);
8: }
9: }
10: else if (this.ElementContentPresenter != null)
11: {
12: this.SetContentPresenter(newIndex);
13: }
14: }
順藤摸瓜,找到SetContentPresenter,由于在ControlTemplate中我們看到Selection的呈現者就是ContentPresenter,基本可以判定SetContentPresenter就是關鍵所在:
1: private void SetContentPresenter(int index)
2: {
3: if (this._swappedOutComboBoxItem != null)
4: {
5: object content = null;
6: if (this.ElementContentPresenter != null)
7: {
8: content = this.ElementContentPresenter.Content;
9: this.ElementContentPresenter.Content = null;
10: }
11: this._swappedOutComboBoxItem.Content = content;
12: this._swappedOutComboBoxItem = null;
13: }
14: if (index == -1)
15: {
16: if (this.ElementContentPresenter != null)
17: {
18: this.ElementContentPresenter.Content = this._emptyContent;
19: this.ElementContentPresenter.ContentTemplate = null;
20: }
21: this.SelectionBoxItem = null;
22: this.SelectionBoxItemTemplate = null;
23: }
24: else
25: {
26: if (this.ElementContentPresenter != null)
27: {
28: this.ElementContentPresenter.Content = null;
29: }
30: bool isNewlyRealized = false;
31: ComboBoxItem container = (ComboBoxItem)
base.ItemContainerGenerator.ContainerFromIndex(index);
32: if (container == null)
33: {
34: GeneratorPosition position =
base.ItemContainerGenerator.GeneratorPositionFromIndex(index);
35: using (base.IItemContainerGenerator.StartAt(position,
GeneratorDirection.Forward, true))
36: {
37: container = (ComboBoxItem) base.IItemContainerGenerator.
GenerateNext(out isNewlyRealized);
38: }
39: }
40: if (isNewlyRealized)
41: {
42: this._preparingContentPresentersElement = true;
43: base.IItemContainerGenerator.PrepareItemContainer(container);
44: this._preparingContentPresentersElement = false;
45: }
46: object obj3 = container.Content;
47: if (obj3 is UIElement)
48: {
49: container.Content = null;
50: this._swappedOutComboBoxItem = container;
51: }
52: container.IsMouseOver = false;
53: container.ChangeVisualState();
54: DataTemplate contentTemplate = container.ContentTemplate;
55: if (this.ElementContentPresenter != null)
56: {
57: this.ElementContentPresenter.ContentTemplate = contentTemplate;
58: this.ElementContentPresenter.Content = obj3;
59: }
60: this.SelectionBoxItem = obj3;
61: this.SelectionBoxItemTemplate = contentTemplate;
62: }
63: }
(這里的最后兩行按照字面意思應該就是與我們所要實現的SelectionItemTemplate一樣的效果,不過我沒有發現其他地方有這2個變量的地方,而且它們也被簡單設置成了obj3和contentTemplate)
代碼比較長,不過大部分可以不管,僅看46行開始的代碼。obj3表示我們綁定到ComboBox的ItemsSource中的數據項,contentTemplate表示了我們設置的ComboBox的ItemTemplate(假如有的話),如果我們直接提供了ComboBoxItem的派生類作為數據源則參考46行代碼以前的處理,ElementContentPresenter表示我們在一開始處ComboBox的ControlTemplate中找到的ContentPresenter,即選中項的呈現者。相關代碼可在OnApplyTemplate中找到。
1: this.ElementContentPresenter = base.GetTemplateChild("ContentPresenter") as ContentPresenter;
至此,所有的準備工作都已經完成,為了實現SelectionItemTemplate,我們只要阻止57行代碼的執行,或者說是使之無效。阻止執行顯然是不科學的,因此我們的辦法就是監視ElementContentPresenter的ContentTemplate屬性,但它發生改變的時候,馬上強制替換成我們的SelectionItemTemplate,從而達到與ItemTemplate不一致的效果。有了前面的準備,實現此效果出乎意料的簡單,首先定義SelectionItemTemplate屬性:
1: private static DependencyProperty SelectionItemTemplateProperty =
DependencyProperty.Register("SelectionItemTemplate", typeof(DataTemplate),
typeof(SpecialSelectionComboBox), null);
2:
3: public DataTemplate SelectionItemTemplate
4: {
5: get
6: {
7: return (DataTemplate)base.GetValue(SelectionItemTemplateProperty);
8: }
9: set
10: {
11: base.SetValue(SelectionItemTemplateProperty, value);
12: }
13: }
然后在OnApplyTemplate中獲得選中項的呈現者,并檢測它的ContentTemplate變化:
1: public override void OnApplyTemplate()
2: {
3: base.OnApplyTemplate();
4:
5: _ContentPresenter = (ContentPresenter)GetTemplateChild( "ContentPresenter");
6:
7: _Watcher.Attach(_ContentPresenter, "ContentTemplate", SelectionContentTemplateChanged);
8: }
當ContentTemplate發生變化時,強制設置為我們的SelectionItemTemplate:
1: private void SelectionContentTemplateChanged(object value)
2: {
3: if (value != SelectionItemTemplate && value != null)
4: {
5: _ContentPresenter.ContentTemplate = SelectionItemTemplate;
6: }
7: }
這里有個需要注意的地方,value != null這個條件很容易被忽視,當ComboBox沒有選中任何項時,_ContentPresenter的ContentTemplate應該讓其保持為null,因為此刻_ContentPresenter的Content屬性也為null,事實上這個時候_ContentPresenter會把它自己的DataContext作為數據源,如果這個時候也強制把模版設置為SelectionItemTemplate,則可能會出現一些意外的效果,比如我們使用了這樣一個SelectionItemTemplate:
1: <DataTemplate>
2: <TextBlock Text={Binding Name} />
3: </DataTemplate>
而正好ComboBox的父控件層級中有一個設置了DataContext,于是ComboBox的DataContext也使用了這一值,而DataContext對象正好有一個Name的屬性,那么在ComboBox沒有選中任何項時,會看到SelectionItemTemplate呈現出DataContext的Name。
在有SelectionItemTemplate之后我們來試著用它實現一個簡單的樹狀結構ComboBox,先定義一個類,常見的如產品分類:
1: public class ProductCategory
2: {
3: public string Name { get; set; }
4: public int Level { get; set; }
5: public string NameWithPathSymbol
6: {
7: get
8: {
9: string path = "|--";
10: for (int i = 0; i < Level - 1; ++i)
11: {
12: path = " " + path;
13: }
14:
15: return path + Name;
16: }
17: }
18: }
19:
20: public class ProductCategoryCollection : List <ProductCategory>
21: {
22: public ProductCategoryCollection()
23: {
24: Add(new ProductCategory{ Name = "電腦", Level = 1 });
25: Add(new ProductCategory{ Name = "聯想", Level = 2 });
26: Add(new ProductCategory{ Name = "惠普", Level = 2 });
27: Add(new ProductCategory{ Name = "打印機", Level = 1 });
28: Add(new ProductCategory{ Name = "兄弟", Level = 2 });
29: Add(new ProductCategory{ Name = "佳能", Level = 2 });
30: }
31: }
樹狀的排序規則這里略過,硬編碼合理的順序。然后在xaml中使用我們剛剛完成ComboBox:
1: <Grid>
2: <Grid.Resources>
3: <local:ProductCategoryCollection x:Key="ProductCategories" />
4: </Grid.Resources>
5: <local:MyComboBox ItemsSource="{StaticResource ProductCategories}">
6: <local:MyComboBox.ItemTemplate>
7: <DataTemplate>
8: <TextBlock Text="{Binding NameWithPathSymbol}" />
9: </DataTemplate>
10: </local:MyComboBox.ItemTemplate>
11: <local:MyComobBox.SelectionItemTemplate>
12: <DataTemplate>
13: <TextBlock Text="{Binding Name}" />
14: </DataTemplate>
15: </local:MyComobBox.SelectionItemTemplate>
16: </local:MyComboBox>
17: </Grid>
給上兩張效果圖,在Items列表和SelectedItem中呈現不一樣的模版,“|--”的符號比較丑陋,事實上ItemTemplate中應該使用Path對象畫出比較好的節點效果,不過如前面多次提到的,這不是重點,而且SelectionItemTemplate更適用的場合應該不是為了做樹結構效果,這個實例只是為了展示SelectionItemTemplate的應用。
本站文章除注明轉載外,均為本站原創或翻譯。歡迎任何形式的轉載,但請務必注明出處、不得修改原文相關鏈接,如果存在內容上的異議請郵件反饋至chenjj@fc6vip.cn
文章轉載自:網絡轉載