轉(zhuǎn)帖|其它|編輯:郝浩|2011-03-30 13:54:07.000|閱讀 752 次
概述:在上篇中我們實(shí)現(xiàn)了DataPager的擴(kuò)展,本文我們的目標(biāo)則是ComboBox,標(biāo)題的“擴(kuò)展”兩個(gè)字在本文稍有不適,因?yàn)閷?duì)DataPager我們確實(shí)是擴(kuò)展了它的外觀和功能,而對(duì)于ComboBox,我們要做的事情可能用“改變”這個(gè)詞更加恰當(dāng)。
# 界面/圖表報(bào)表/文檔/IDE等千款熱門軟控件火熱銷售中 >>
在上篇中我們實(shí)現(xiàn)了DataPager的擴(kuò)展,本文我們的目標(biāo)則是ComboBox,標(biāo)題的“擴(kuò)展”兩個(gè)字在本文稍有不適,因?yàn)閷?duì)DataPager我們確實(shí)是擴(kuò)展了它的外觀和功能,而對(duì)于ComboBox,我們要做的事情可能用“改變”這個(gè)詞更加恰當(dāng)。
好了,來看看我們準(zhǔn)備對(duì)ComboBox做些什么。Microsoft為我們提供的ComboBox簡(jiǎn)單好用,當(dāng)然簡(jiǎn)單好用的另外一個(gè)意思就是在有些場(chǎng)合它就會(huì)顯得很笨,在網(wǎng)上搜索一下ComboBox,比較常見的問題都是ComboBox下拉框帶自定義控件有關(guān)的,尤其是帶TreeView的,有一些不錯(cuò)的解決方案,今天我們完成擴(kuò)展的第二個(gè)實(shí)例,換一個(gè)角度實(shí)現(xiàn)一個(gè)樹狀下拉框的ComboBox,不改動(dòng)ContrlTemplate也不使用UserControl拼裝,就使用DependencyPropertyWatcher。
ComboBox有方便快捷的SelectedValuePath和DisplayMemberPath,也有靈活的ItemTemplate,ItemTemplate幾乎無所不能,但是之所有有幾乎兩字,一旦我們?yōu)镃omboBox設(shè)置了ItemTemplate,那么SelectedItem也套用了ItemTemplate。然而在非常多的場(chǎng)合,我們希望在下拉框中顯示對(duì)象的各種詳細(xì)信息,而在被選擇之后只顯示關(guān)鍵信息,比如一個(gè)ComboBox中如果填充了一張客戶列表,那么下拉框展開之時(shí)能除了看到客戶名稱還有聯(lián)系人和聯(lián)系方式會(huì)讓我們感覺使用得舒服,但是如果選擇了一個(gè)客戶之后,我們一般希望僅僅顯示客戶的名稱就足夠了,或者說我們希望呈現(xiàn)的是另外一種合理的重新被組織過的信息,簡(jiǎn)而言之,在DisplayMemberPath之外,我們需要一個(gè)獨(dú)立的SelectionDisplayMemberPath,在ItemTemplate之外,我們需要一個(gè)獨(dú)立的SelectionItemTemplate,當(dāng)然SelectionDisplayMemberPath的適用性非常小,因?yàn)樗鼉H僅是一個(gè)簡(jiǎn)單的Path設(shè)置,而SelectionItemTemplate卻真正能為我們解決問題的方案,接下來我們就來實(shí)現(xiàn)它。
和DataPager一樣,我們還是先看看ComboBox的ControlTemplate定義:
比DataPager幸運(yùn)的是,我們可以直接取得ContentPresenter的引用了,由于和DataPager單純的添加元素不同,為ComboBox添加一個(gè)SelectionItemTemplate之前,我們還至少要看看為什么ItemTemplate會(huì)作用到SelectedItem上的,打開Reflector查看ComboBox的相關(guān)代碼,毫無疑問直奔主題找到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的呈現(xiàn)者就是ContentPresenter,基本可以判定SetContentPresenter就是關(guān)鍵所在:
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: }
(這里的最后兩行按照字面意思應(yīng)該就是與我們所要實(shí)現(xiàn)的SelectionItemTemplate一樣的效果,不過我沒有發(fā)現(xiàn)其他地方有這2個(gè)變量的地方,而且它們也被簡(jiǎn)單設(shè)置成了obj3和contentTemplate)
代碼比較長(zhǎng),不過大部分可以不管,僅看46行開始的代碼。obj3表示我們綁定到ComboBox的ItemsSource中的數(shù)據(jù)項(xiàng),contentTemplate表示了我們?cè)O(shè)置的ComboBox的ItemTemplate(假如有的話),如果我們直接提供了ComboBoxItem的派生類作為數(shù)據(jù)源則參考46行代碼以前的處理,ElementContentPresenter表示我們?cè)谝婚_始處ComboBox的ControlTemplate中找到的ContentPresenter,即選中項(xiàng)的呈現(xiàn)者。相關(guān)代碼可在OnApplyTemplate中找到。
1: this.ElementContentPresenter = base.GetTemplateChild("ContentPresenter") as ContentPresenter;
至此,所有的準(zhǔn)備工作都已經(jīng)完成,為了實(shí)現(xiàn)SelectionItemTemplate,我們只要阻止57行代碼的執(zhí)行,或者說是使之無效。阻止執(zhí)行顯然是不科學(xué)的,因此我們的辦法就是監(jiān)視ElementContentPresenter的ContentTemplate屬性,但它發(fā)生改變的時(shí)候,馬上強(qiáng)制替換成我們的SelectionItemTemplate,從而達(dá)到與ItemTemplate不一致的效果。有了前面的準(zhǔn)備,實(shí)現(xiàn)此效果出乎意料的簡(jiǎn)單,首先定義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中獲得選中項(xiàng)的呈現(xiàn)者,并檢測(cè)它的ContentTemplate變化:
1: public override void OnApplyTemplate()
2: {
3: base.OnApplyTemplate();
4:
5: _ContentPresenter = (ContentPresenter)GetTemplateChild( "ContentPresenter");
6:
7: _Watcher.Attach(_ContentPresenter, "ContentTemplate", SelectionContentTemplateChanged);
8: }
當(dāng)ContentTemplate發(fā)生變化時(shí),強(qiáng)制設(shè)置為我們的SelectionItemTemplate:
1: private void SelectionContentTemplateChanged(object value)
2: {
3: if (value != SelectionItemTemplate && value != null)
4: {
5: _ContentPresenter.ContentTemplate = SelectionItemTemplate;
6: }
7: }
這里有個(gè)需要注意的地方,value != null這個(gè)條件很容易被忽視,當(dāng)ComboBox沒有選中任何項(xiàng)時(shí),_ContentPresenter的ContentTemplate應(yīng)該讓其保持為null,因?yàn)榇丝蘝ContentPresenter的Content屬性也為null,事實(shí)上這個(gè)時(shí)候_ContentPresenter會(huì)把它自己的DataContext作為數(shù)據(jù)源,如果這個(gè)時(shí)候也強(qiáng)制把模版設(shè)置為SelectionItemTemplate,則可能會(huì)出現(xiàn)一些意外的效果,比如我們使用了這樣一個(gè)SelectionItemTemplate:
1: <DataTemplate>
2: <TextBlock Text={Binding Name} />
3: </DataTemplate>
而正好ComboBox的父控件層級(jí)中有一個(gè)設(shè)置了DataContext,于是ComboBox的DataContext也使用了這一值,而DataContext對(duì)象正好有一個(gè)Name的屬性,那么在ComboBox沒有選中任何項(xiàng)時(shí),會(huì)看到SelectionItemTemplate呈現(xiàn)出DataContext的Name。
在有SelectionItemTemplate之后我們來試著用它實(shí)現(xiàn)一個(gè)簡(jiǎn)單的樹狀結(jié)構(gòu)ComboBox,先定義一個(gè)類,常見的如產(chǎn)品分類:
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 = "聯(lián)想", Level = 2 });
26: Add(new ProductCategory{ Name = "惠普", Level = 2 });
27: Add(new ProductCategory{ Name = "打印機(jī)", Level = 1 });
28: Add(new ProductCategory{ Name = "兄弟", Level = 2 });
29: Add(new ProductCategory{ Name = "佳能", Level = 2 });
30: }
31: }
樹狀的排序規(guī)則這里略過,硬編碼合理的順序。然后在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中呈現(xiàn)不一樣的模版,“|--”的符號(hào)比較丑陋,事實(shí)上ItemTemplate中應(yīng)該使用Path對(duì)象畫出比較好的節(jié)點(diǎn)效果,不過如前面多次提到的,這不是重點(diǎn),而且SelectionItemTemplate更適用的場(chǎng)合應(yīng)該不是為了做樹結(jié)構(gòu)效果,這個(gè)實(shí)例只是為了展示SelectionItemTemplate的應(yīng)用。
本站文章除注明轉(zhuǎn)載外,均為本站原創(chuàng)或翻譯。歡迎任何形式的轉(zhuǎn)載,但請(qǐng)務(wù)必注明出處、不得修改原文相關(guān)鏈接,如果存在內(nèi)容上的異議請(qǐng)郵件反饋至chenjj@fc6vip.cn
文章轉(zhuǎn)載自:網(wǎng)絡(luò)轉(zhuǎn)載