一、前言
先看看WPF自带的 ComboBox 在非编辑状态,自定义 ItemTemplate 的情况下,效果如下图所示:

其当前选中的项(红框内)与自定义的 ItemTemplate 一样;
但是 C1ComboBox 的非编辑状态(IsEditable="False"):

总感觉它的非编辑状态并没有完成,虽然数字和英文无法输入,但在红框内依旧可以输入中文文本(QQ拼音输入法的中文输入状态)
;并且在非编辑状态下并非像 Combobox 的非编辑状态可以显示自定义的 ItemTemplate 效果;这篇文章就介绍如何使用 C1ComboBox 模仿 ComboBox 的非编辑状态效果。
二、解决方案
先分析C1ComboBox的控件结构:

其中 ComboHeader 部分是由两个控件来回切换显示的,
internal C1TextBoxBase _elementEditControl;
internal ContentPresenter _elementContentControl;
_elementEditControl 则是编辑状态下显示的控件,_elementContentControl 则是非编辑状态下显示的控件(可显示自定义的 ItemTemplate);
而这两个控件转换显示的方法如下(C1TextEditableContentControl):
1 protected internal void UpdateVisualState()
2 {
3 if (this.EditControl == null || this.ContentControl == null)
4 {
5 return;
6 }
7 if (this.IsInEditMode || this.IsDropDownOpen)
8 {
9 this.EditControl.Opacity = 1.0;
10 this.EditControl.IsTabStop = true;
11 this.EditControl.IsHitTestVisible = true;
12 this.ContentControl.Visibility = Visibility.Collapsed;
13 this.ContentControl.Content = null;
14 }
15 else
16 {
17 this.EditControl.Opacity = 1.4012984643248171E-45;
18 this.EditControl.IsTabStop = false;
19 this.EditControl.IsHitTestVisible = false;
20 this.ContentControl.Visibility = Visibility.Visible;
21 this.ContentControl.Content = this.ActualContent;
22 }
23 base.Cursor = (this.IsEditable ? Cursors.IBeam : Cursors.Arrow);
24 }
25 // 注:EditControl对应_elementEditControl,ContentControl对应_elementContentControl;
即,当 this.IsInEditMode || this.IsDropDownOpen 为 false 时,方可显示自定义的 Itemplate ;
所以,当 ScrollViewer 收缩时(IsDropDownOpen 为 false),设置 ComboHeader 的 IsInEditMode 为 false, 即可保证下拉选择项后,在 ComboHeader 显示自定义的 ItemTemplate;
Loaded += (sender, e) =>
{
C1TextEditableContentControl editBox = GetChildObjects<C1TextEditableContentControl>(cmb, typeof(C1TextEditableContentControl))[0];
cmb.IsDropDownOpenChanged += (sender2, e2) =>
{
editBox.IsInEditMode = false;
};
};

1 public List<T> GetChildObjects<T>(DependencyObject obj, Type typename) where T : FrameworkElement
2 {
3 DependencyObject child = null;
4 List<T> childList = new List<T>();
5
6 for (int i = 0; i <= VisualTreeHelper.GetChildrenCount(obj) - 1; i++)
7 {
8 child = VisualTreeHelper.GetChild(obj, i);
9
10 if (child is T && (((T)child).GetType() == typename))
11 {
12 childList.Add((T)child);
13 }
14 childList.AddRange(GetChildObjects<T>(child, typename));
15 }
16 return childList;
17 }
但是,当 ComboHeader 获取焦点,仍然会显示 EditControl ,而不是 ContentControl,所以添加如下代码:
editBox.IsEditable = false;
editBox.GotFocus += (sender2, e2) =>
{
editBox.IsInEditMode = false;
};
需要注意的是,如果不设置 editBox.IsEditable = false; ,点击两次 ComboHeader 还是会进入编辑状态,显示 EditControl 的……
继续但是,当点击 ArrowToggle 按钮展开 ScrollViewer 时,仍旧会显示编辑状态,这个就麻烦了,查看源码可知,该操作时会触发 UpdateSwappedOut 方法以修改 _elementComboHeader 的 ActualContent ,进而触发上面的 UpdateVisualState 方法,而此时 ScrollViewer 的 IsDropDownOpen 属性为 true,导致显示编辑状态,而非自定义的 ItemTemplate;

1 private void UpdateSwappedOut()
2 {
3 if (this._elementComboHeader == null)
4 {
5 return;
6 }
7 this._elementComboHeader.IsDropDownOpen = this.IsDropDownOpen;
8 C1ComboBoxItem c1ComboBoxItem = null;
9 this._isHeaderUpdate = true;
10 this._elementComboHeader.ActualContent = null;
11 this._isHeaderUpdate = false;
12 this._elementComboHeader.UpdateIsWatermarked();
13 this._elementComboHeader.UpdateVisualState();
14 if (this.SwappedOutItem != null)
15 {
16 this.SwappedOutItem.SwappedOut = false;
17 this.SwappedOutItem = null;
18 }
19 if (this.SelectedItem == null)
20 {
21 return;
22 }
23 if (this.SelectedIndex != -1)
24 {
25 c1ComboBoxItem = (C1ComboBoxItem)base.ItemContainerGenerator.ContainerFromIndex(this.SelectedIndex);
26 if (c1ComboBoxItem != null && !this.IsDropDownOpen)
27 {
28 this.SwappedOutItem = c1ComboBoxItem;
29 c1ComboBoxItem.SwappedOut = true;
30 }
31 }
32 if (c1ComboBoxItem == null)
33 {
34 c1ComboBoxItem = (this.SelectedItem as C1ComboBoxItem);
35 }
36 this._isHeaderUpdate = true;
37 if (c1ComboBoxItem == null)
38 {
39 if (base.ItemStringFormat != null && !this.IsEditable && base.ItemTemplate == null)
40 {
41 this._elementComboHeader.ActualContent = this.FormattedString(base.ItemStringFormat, this.SelectedItem);
42 }
43 else
44 {
45 this._elementComboHeader.ActualContent = this.SelectedItem;
46 }
47 }
48 else if (base.ItemStringFormat != null && !this.IsEditable && base.ItemTemplate == null)
49 {
50 this._elementComboHeader.ActualContent = this.FormattedString(base.ItemStringFormat, c1ComboBoxItem.Content);
51 }
52 else
53 {
54 this._elementComboHeader.ActualContent = c1ComboBoxItem.Content;
55 }
56 this._isHeaderUpdate = false;
57 this._elementComboHeader.IsDirty = false;
58 }
没辙,没找到如何禁止触发 UpdateSwappedOut 方法,或者触发后如何设置 IsDropDownOpen 为 false,所以就把 EditControl 和 ContentControl 两个控件拿出来,再自己写一个 UpdateVisualState 来更新两个状态的转换;
1 private void UpdateVisualState()
2 {
3 if (this.EditControl == null || this.ContentControl == null)
4 {
5 return;
6 }
7 this.EditControl.Opacity = 1.4012984643248171E-45;
8 this.EditControl.IsTabStop = false;
9 this.EditControl.IsHitTestVisible = false;
10 this.ContentControl.Visibility = Visibility.Visible;
11 C1ComboBoxItem cmbi = ((C1ComboBoxItem)cmb.ItemContainerGenerator.ContainerFromIndex(cmb.SelectedIndex));
12 this.ContentControl.Content = cmbi.Content;
13 base.Cursor = (EditBox.IsEditable ? Cursors.IBeam : Cursors.Arrow);
14 }
1 EditControl = GetChildObjects<Control>(cmb, "EditControl")[0];
2 ContentControl = GetChildObjects<ContentPresenter>(cmb, "ContentControl")[0];
3 cmb.IsDropDownOpenChanged += (sender2, e2) =>
4 {
5 // EditBox.IsInEditMode = false;
6 UpdateVisualState();
7 };
1 public List<T> GetChildObjects<T>(DependencyObject obj, string name) where T : FrameworkElement
2 {
3 DependencyObject child = null;
4 List<T> childList = new List<T>();
5
6 for (int i = 0; i <= VisualTreeHelper.GetChildrenCount(obj) - 1; i++)
7 {
8 child = VisualTreeHelper.GetChild(obj, i);
9
10 if (child is T && (((T)child).Name == name | string.IsNullOrEmpty(name)))
11 {
12 childList.Add((T)child);
13 }
14 childList.AddRange(GetChildObjects<T>(child, name));
15 }
16 return childList;
17 }
一切Over,效果如下:
初始状态:
展开状态:

选择项改变后状态:

三、资源下载
1、Demo工程下载:http://files.cnblogs.com/files/memento/C1ComboBoxSample.7z

本文链接:[WPF] C1ComboBox的非编辑状态,转载请注明。