功能描述: ComboBox+TreeView实现的用户控件,设置数据源后,可以递归加载数据,支持双向绑定 控件截图: XAML界面设计: <UserControl x:Class="ChuanyeOA.CustomControls.ComboBoxTree" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignHeight="30" d:DesignWidth="300" xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"> <Grid x:Name="LayoutRoot"> <ComboBox Height="23" Name="cbMain" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" DropDownOpened="cbMain_DropDownOpened" DropDownClosed="cbMain_DropDownClosed"> <ComboBoxItem x:Name="cbItemTreeView"> <ComboBoxItem.Content> <sdk:TreeView Name="tvList" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" SelectedItemChanged="tvList_SelectedItemChanged" /> </ComboBoxItem.Content> </ComboBoxItem> <ComboBoxItem x:Name="cbItemDisplay" > </ComboBoxItem> </ComboBox> </Grid> </UserControl> 后台CS代码: using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes; using System.Windows.Data; using System.Collections; using System.Reflection; /******************************** * 创建人:刘跃飞 * 创建时间:2010-09-21 * 功能描述:ComboBox+TreeView实现的用户控件,主要实现选择TreeView中的项做为ComboBox的项的功能 * 属性描述:此控件包含以下几个属性 * 1.ItemsSource:控件要绑定的数据源 * 2.SelectedValuePath:选中的值的属性 * 3.DisplayMemberPath:获取或设置为每个数据项要显示的属性的名称 * 4.SelectedItem 当前选择的项 * 5.SelectedValue 当前选择的值 * 6.IsRecursionEnabled 设置是否启用递归加载数据源中的项 。需要设置ChildName和ParentName两个属性 * 7.ChildMemberPath 递归加载数据源时,实体中的子级属性项 * 8. ParentMemberPath 递归加载数据源时,实体中的父级属性项 * 9.IsExpandAll 设置TreeView打开时是全部打开状态还是收缩状态 默认为True打开状态 * * 使用方法: * 注意事项:本控件设计尚不完善,【ItemsSource】属性需要在最后设置,否则,将提示错误 * 1.界面上直接设置,例如: * <myControl:ComboBoxTree SelectedValue="{Binding DepartmentID, Mode=TwoWay}" DisplayMemberPath="DepartName" SelectedValuePath="ID" IsRecursionEnabled="True" ChildMemberPath="ID" ParentMemberPath="ParentID" ItemsSource="{Binding Data, Source={StaticResource a_DepartmentDomainDataSource}}"> * 2.在后台代码中直接设置,例如: * comboBoxTree1.DisplayMemberPath = "Name"; * comboBoxTree1.SelectedValuePath = "ID"; * comboBoxTree1.ChildMemberPath = "ID"; * comboBoxTree1.ParentMemberPath = "ParentID"; * comboBoxTree1.IsRecursionEnabled = true; * comboBoxTree1.ItemSource = MyControls.DepartmentInfo.GetDepartment().AsEnumerable(); * 3.如果启用递归加载,父级ID为【0】的项,将作为根节点加载,如果没有父级ID为【0】的项,将无法进行加载 * 4.此控件用到了TreeView的ExpandAll扩展方法,需要添加【System.Windows.Controls.Toolkit】类库的引用,否则将提示找不到此方法 * ***********************************/ namespace ChuanyeOA.CustomControls { public partial class ComboBoxTree : UserControl { public ComboBoxTree() { InitializeComponent(); this.LayoutUpdated += new EventHandler(ComboBoxTree_LayoutUpdated); } void ComboBoxTree_LayoutUpdated(object sender, EventArgs e) { if (this.IsExpandAll) { //--展开所有节点 tvList.ExpandAll(); } } #region ItemSource 设置或获得绑定的数据源(属性) public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(ComboBoxTree), new PropertyMetadata(new PropertyChangedCallback(ItemsSourcePropertyChangedCallBack))); /// <summary> /// 数据源 /// </summary> public IEnumerable ItemsSource { get { return (IEnumerable)this.GetValue(ItemsSourceProperty); } set { this.SetValue(ItemsSourceProperty, value); } } //--属性更改的回调事件 public static void ItemsSourcePropertyChangedCallBack(DependencyObject sender, DependencyPropertyChangedEventArgs args) { if (sender != null) { ComboBoxTree comboBoxTree = sender as ComboBoxTree; if (comboBoxTree.IsRecursionEnabled == false) { // 以列表的方式加载数据 comboBoxTree.LoadTreeViewDataWithList(); } else { //以递归的方式加载数据 comboBoxTree.LoadTreeViewDataWithRecursion(); } } } #endregion #region SelectedValuePath 选中的值的属性 public static readonly DependencyProperty SelectedValuePathProperty = DependencyProperty.Register("SelectedValuePath", typeof(string), typeof(ComboBoxTree), null); /// <summary> /// 获取或设置每个属性项要绑定的属性的名称 /// </summary> public string SelectedValuePath { get { return (string)this.GetValue(SelectedValuePathProperty); } set { this.SetValue(SelectedValuePathProperty, value); } } #endregion #region DisplayMemberPath 获取或设置为每个数据项要显示的属性的名称 public static readonly DependencyProperty DisplayMemberPathProperty = DependencyProperty.Register("DisplayMemberPath", typeof(string), typeof(ComboBoxTree), null); /// <summary> /// 获取或设置为每个数据项要显示的属性的名称 /// </summary> public string DisplayMemberPath { get { return (string)this.GetValue(DisplayMemberPathProperty); } set { this.SetValue(DisplayMemberPathProperty, value); } } #endregion #region SelectedItem 当前选择的项 public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.Register("SelectedItem", typeof(object), typeof(ComboBoxTree), null); /// <summary> /// 当前选择的项 /// </summary> public object SelectedItem { get { return this.GetValue(SelectedItemProperty); } set { this.SetValue(SelectedItemProperty, value); } } #endregion #region SelectedValue 当前选择的值 public static readonly DependencyProperty SelectedValueProperty = DependencyProperty.Register("SelectedValue", typeof(object), typeof(ComboBoxTree), null); /// <summary> /// 当前选择的值 /// </summary> public object SelectedValue { get { return this.GetValue(SelectedValueProperty); } set { this.SetValue(SelectedValueProperty, value); } } #endregion #region IsRecursionEnabled 设置是否启用递归加载数据源中的项 。需要设置ChildName和ParentName两个属性 public static readonly DependencyProperty IsRecursionEnabledProperty = DependencyProperty.Register("IsRecursionEnabled", typeof(bool), typeof(ComboBoxTree), new PropertyMetadata(new PropertyChangedCallback(IsRecursionEnabledCallBack))); /// <summary> /// 设置是否启用递归加载数据源中的项。 /// 需要设置ChildMemberPath和ParentMemberPath两个属性,如果这两个属性没有设置,则不启用递归加载。 /// </summary> public bool IsRecursionEnabled { get { return (bool)this.GetValue(IsRecursionEnabledProperty); } set { //if (value == true) //{ // //---如果子级和父级属性设置不为空 则开始递归加载数据 // if (!string.IsNullOrEmpty(this.ChildMemberPath) && !string.IsNullOrEmpty(this.ParentMemberPath)) // { // if (this.ItemSource != null) // { // this.LoadTreeViewDataWithRecursion(); // } // } //} this.SetValue(IsRecursionEnabledProperty, value); } } public static void IsRecursionEnabledCallBack(DependencyObject sender, DependencyPropertyChangedEventArgs args) { if (sender != null) { ComboBoxTree comboBoxTree = sender as ComboBoxTree; comboBoxTree.IsRecursionEnabled = (bool)args.NewValue; } } #endregion #region ChildMemberPath 递归加载数据源时,实体中的子级属性项 public static readonly DependencyProperty ChildMemberPathProperty = DependencyProperty.Register("ChildMemberPath", typeof(string), typeof(ComboBoxTree), null); /// <summary> /// 递归加载数据源时,实体中的子级属性项 /// </summary> public string ChildMemberPath { get { return (string)this.GetValue(ChildMemberPathProperty); } set { this.SetValue(ChildMemberPathProperty, value); } } #endregion #region ParentMemberPath 递归加载数据源时,实体中的父级属性项 public static readonly DependencyProperty ParentMemberPathProperty = DependencyProperty.Register("ParentMemberPath", typeof(string), typeof(ComboBoxTree), null); /// <summary> /// 递归加载数据源时,实体中的父级属性项 /// </summary> public string ParentMemberPath { get { return (string)this.GetValue(ParentMemberPathProperty); } set { this.SetValue(ParentMemberPathProperty, value); } } #endregion #region IsExpandAll 设置TreeView打开时是全部打开状态还是收缩状态 默认为True打开状态 /// <summary> /// 设置TreeView打开时是全部打开状态还是收缩状态 默认为True打开状态 /// </summary> public static readonly DependencyProperty IsExpandAllProperty = DependencyProperty.Register("IsExpandAll", typeof(bool), typeof(ComboBoxTree), new PropertyMetadata(true)); /// <summary> /// 设置TreeView打开时是全部打开状态还是收缩状态 默认为True打开状态 /// </summary> public bool IsExpandAll { get { return (bool)this.GetValue(IsExpandAllProperty); } set { this.SetValue(IsExpandAllProperty, value); } } #endregion #region 加载数据源方法 /// <summary> /// 用列表的方式加载数据源 /// </summary> private void LoadTreeViewDataWithList() { tvList.Items.Clear();//先清空所有的数据 foreach (var item in ItemsSource) { Type objType = item.GetType(); //--用反射的方式 获得属性对应的值 PropertyInfo displayMemberInfo = objType.GetProperty(this.DisplayMemberPath); PropertyInfo selectedValueInfo = objType.GetProperty(this.SelectedValuePath); //--当前要显示的文本 string displayText = displayMemberInfo.GetValue(item, null).ToString(); //--当前选择的值 string selectedValue = selectedValueInfo.GetValue(item, null).ToString(); TreeViewItem myitem = new TreeViewItem(); myitem.Header = displayText; myitem.Tag = selectedValue; tvList.Items.Add(myitem); } } /// <summary> /// 用递归加载数据源 /// </summary> private void LoadTreeViewDataWithRecursion() { tvList.Items.Clear();//先清空所有的数据 if (ItemsSource != null) { //---如果子级和父级属性设置不为空 则开始递归加载数据 if (string.IsNullOrEmpty(this.ChildMemberPath) || string.IsNullOrEmpty(this.ParentMemberPath)) { MessageBox.Show("在【ComboBoxTree】控件中,如果启用了递归方式加载数据,必须设置【ChildMemberPath】和【ParentMemberPath】属性的值。"); } foreach (var item in ItemsSource) { Type objType = item.GetType(); //--用反射的方式 获得属性对应的值 PropertyInfo displayMemberInfo = objType.GetProperty(this.DisplayMemberPath); PropertyInfo selectedValueInfo = objType.GetProperty(this.SelectedValuePath); PropertyInfo childValueInfo = objType.GetProperty(this.ChildMemberPath); PropertyInfo parentValueInfo = objType.GetProperty(this.ParentMemberPath); //--当前要显示的文本 string displayText = displayMemberInfo.GetValue(item, null).ToString(); //--当前选择的值 string selectedValue = selectedValueInfo.GetValue(item, null).ToString(); //---子级绑定的值 string childValue = childValueInfo.GetValue(item, null).ToString(); //--父级绑定的值 string parentValue = parentValueInfo.GetValue(item, null).ToString(); if (string.IsNullOrEmpty(childValue) || string.IsNullOrEmpty(parentValue)) { return; } if (parentValue == "0")//--所有的父级的值为【0】的数据默认为添加到根节点。 { TreeViewItem myitem = new TreeViewItem(); myitem.Header = displayText; myitem.Tag = selectedValue; this.LoadItems(myitem, ItemsSource); tvList.Items.Add(myitem); } } } } /// <summary> /// 递归加载数据源 /// </summary> /// <param name="treeviewitem"></param> /// <param name="mylist"></param> private void LoadItems(TreeViewItem treeviewitem, IEnumerable mylist) { if (mylist != null) { foreach (var item in ItemsSource) { Type objType = item.GetType(); //--用反射的方式 获得属性对应的值 PropertyInfo displayMemberInfo = objType.GetProperty(this.DisplayMemberPath); PropertyInfo selectedValueInfo = objType.GetProperty(this.SelectedValuePath); PropertyInfo childValueInfo = objType.GetProperty(this.ChildMemberPath); PropertyInfo parentValueInfo = objType.GetProperty(this.ParentMemberPath); //--当前要显示的文本 string displayText = displayMemberInfo.GetValue(item, null).ToString(); //--当前选择的值 string selectedValue = selectedValueInfo.GetValue(item, null).ToString(); //---子级绑定的值 string childValue = childValueInfo.GetValue(item, null).ToString(); //--父级绑定的值 string parentValue = parentValueInfo.GetValue(item, null).ToString(); if (string.IsNullOrEmpty(childValue) || string.IsNullOrEmpty(parentValue)) { return; } if (treeviewitem.Tag.ToString() == parentValue) { TreeViewItem myitem = new TreeViewItem(); myitem.Header = displayText; myitem.Tag = selectedValue; treeviewitem.Items.Add(myitem); this.LoadItems(myitem, ItemsSource); } } } } #endregion /// <summary> /// TreeView的选择事件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void tvList_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e) { if (tvList.Items.Count > 0) { this.SelectedItem = tvList.SelectedItem; if (string.IsNullOrEmpty(this.DisplayMemberPath) || string.IsNullOrEmpty(this.SelectedValuePath)) { MessageBox.Show("此控件必须设置【DisplayMemberPath】和【SelectedValuePath】属性的值"); return; } Type objType = null; PropertyInfo displayMemberInfo = null; PropertyInfo selectedValueInfo = null; foreach (var item in ItemsSource) { //获取实体的类型 objType = item.GetType(); //--用反射的方式 获得属性对应的值 displayMemberInfo = objType.GetProperty(this.DisplayMemberPath); selectedValueInfo = objType.GetProperty(this.SelectedValuePath); break; } TreeViewItem treeviewitem = tvList.SelectedItem as TreeViewItem; //--当前要显示的文本 string displayText = treeviewitem.Header.ToString(); //--当前选择的值 this.SelectedValue = treeviewitem.Tag.ToString(); cbItemDisplay.Content = displayText;//设置ComboBox要显示的文本 //设置当前选择的项 foreach (var item in ItemsSource) { string selvalue = selectedValueInfo.GetValue(item, null).ToString(); if (!string.IsNullOrEmpty(selvalue)) { if (treeviewitem.Tag.ToString() == selvalue) { this.SelectedItem = item; ; } } } } } private void cbMain_DropDownOpened(object sender, EventArgs e) { //--打开时 隐藏要显示的项 cbItemDisplay.Visibility = System.Windows.Visibility.Collapsed; cbItemTreeView.Visibility = System.Windows.Visibility.Visible; } private void cbMain_DropDownClosed(object sender, EventArgs e) { //--关闭时,使显示的项可见 cbItemDisplay.Visibility = System.Windows.Visibility.Visible; cbItemTreeView.Visibility = System.Windows.Visibility.Collapsed; cbMain.SelectedItem = cbItemDisplay;//设置当前的选中项为显示项 } } }