ComboBox,是否可以将ItemsSource和SelectedValue绑定到不同的源?

时间:2016-10-27 19:51:36

标签: c# wpf combobox

我有两个不同的对象指向对方。第一个对象代表公司的一个部门。该对象有两个集合:Employees,即在该部门工作的所有员工和Project,这是该部门内正在进行的所有特殊项目。所以第一个对象看起来像这样:

public class Division : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged = delegate { };

    ObservableCollection<Employee> _employees;
    ObservableCollection<Project> _projects;

    public Division()
    {
        Employees = new ObservableCollection<Employee>();
        Projects = new ObservableCollection<Project>();
    }

    public ObservableCollection<Employee> Employees
    {
        get { return _employees; }
        set
        {
            if (_employees != value)
            {
                _employees = value;
                PropertyChanged(this, new PropertyChangedEventArgs("Employees"));
            }
        }
    }

    public ObservableCollection<Project> Projects
    {
        get { return _projects; }
        set
        {
            if (_projects != value)
            {
                _projects = value;
                PropertyChanged(this, new PropertyChangedEventArgs("Projects"));
            }
        }
    }

    public void AddNewProject()
    {
        this.Projects.Add(new Project(this));
    }
}

请注意,在向部门添加新项目时,我将对该部门的引用传递给该项目,如下所示:

public class Project : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged = delegate { };

    string _projectName;
    DateTime _deadline = DateTime.Now;
    Division _division;
    ObservableCollection<Employee> _members;

    public Project()
    {
        Members = new ObservableCollection<Employee>();
    }

    public Project(Division div)
    {
        Members = new ObservableCollection<Employee>();
        Division = div;
    }

    public string ProjectName
    {
        get { return _projectName; }
        set
        {
            if (_projectName != value)
            {
                _projectName = value;
                PropertyChanged(this, new PropertyChangedEventArgs("ProjectName"));
            }
        }
    }

    public DateTime Deadline
    {
        get { return _deadline; }
        set
        {
            if (_deadline != value)
            {
                _deadline = value;
                PropertyChanged(this, new PropertyChangedEventArgs("Deadline"));
            }
        }
    }

    public Division Division
    {
        get { return _division; }
        set
        {
            if (_division != value)
            {
                if (_division != null)
                {
                    _division.Employees.CollectionChanged -= members_CollectionChanged;
                }
                _division = value;
                if (_division != null)
                {
                    _division.Employees.CollectionChanged += members_CollectionChanged;
                }
                PropertyChanged(this, new PropertyChangedEventArgs("Division"));
            }
        }
    }

    public ObservableCollection<Employee> Members
    {
        get { return _members; }
        set
        {
            if (_members != value)
            {
                if (_members != null)
                {
                    _members.CollectionChanged -= members_CollectionChanged;
                }
                _members = value;
                if (_members != null)
                {
                    _members.CollectionChanged += members_CollectionChanged;
                }
                PropertyChanged(this, new PropertyChangedEventArgs("Members"));
            }
        }
    }

    public ObservableCollection<Employee> AvailableEmployees
    {
        get
        {
            if (Division != null){
                IEnumerable<Employee> availables =
                from s in Division.Employees
                where !Members.Contains(s)
                select s;

                return new ObservableCollection<Employee>(availables);
            }

            return new ObservableCollection<Employee>();
        }
    }

    void members_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        PropertyChanged(this, new PropertyChangedEventArgs("AvailableEmployees"));
    }
}

我这样做的原因是,该项目可以让任何类型的团队参与其中,但仅限于该部门内部。因此,在为部门构建仪表板时,经理可以选择该项目的任何员工,但不需要已经分配给该员工的员工。因此,项目对象中的AvailableEmployees属性始终跟踪尚未分配给该项目的人员。

我遇到的问题是如何将其转换为UI。到目前为止,我所做的实验看起来像这样:

<UserControl x:Class="Test.Views.TestView"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         mc:Ignorable="d" 
         xmlns:local="clr-namespace:Test.Views"
         d:DesignHeight="300" d:DesignWidth="300">

<StackPanel>
    <ListBox ItemsSource="{Binding Div.Projects}">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <Border Background="Transparent"
                        BorderThickness="0, 0, 0, 2"
                        BorderBrush="Black"
                        Margin="0, 0, 0, 5"
                        Padding="0, 0, 0, 5">
                    <StackPanel>
                        <TextBox Text="{Binding ProjectName}"/>
                        <ListBox ItemsSource="{Binding Members}">
                            <ListBox.ItemTemplate>
                                <DataTemplate>
                                    <ComboBox ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=local:TestView}, Path=DataContext.AvailableEmployees}"
                                              DisplayMemberPath="FirstName"
                                              Text="{Binding FirstName}"/>
                                </DataTemplate>
                            </ListBox.ItemTemplate>
                        </ListBox>
                        <Button Content="Add Employee to Project"
                                Command="{Binding RelativeSource={RelativeSource AncestorType=local:TestView}, Path=DataContext.AddEmployeeToProject}"
                                CommandParameter="{Binding}"/>
                    </StackPanel>
                </Border>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
    <Button Content="Add New Project"
            Command="{Binding AddNewProject}" />
</StackPanel>

与此视图关联的视图模型如下:

public class TestViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged = delegate { };

    private Division _div;

    public TestViewModel(Division div)
    {
        Div = div;

        AddNewProject = new DelegateCommand(OnAddNewProject);
        AddEmployeeToProject = new DelegateCommand<Project>(OnAddEmployeeToProject);
    }

    public DelegateCommand AddNewProject { get; set; }
    public DelegateCommand<Project> AddEmployeeToProject { get; set; }

    public Division Div
    {
        get { return _div; }
        set
        {
            if (_div != value)
            {
                _div = value;
                PropertyChanged(this, new PropertyChangedEventArgs("Div"));
            }
        }
    }

    private void OnAddNewProject()
    {
        Div.AddNewProject();
    }

    private void OnAddEmployeeToProject(Project proj)
    {
        var availables = proj.AvailableEmployees;

        if (availables.Count > 0)
        {
            proj.Members.Add(availables[0]);
        }
    }
}

但是,我无法为每个项目中的每个员工提供组合框。看起来所选的项目/值被绑定到itemssource,并且每次组合框都变成空白。我也尝试使用组合框的SelectedValue和SelectedItem属性来做这个,但没有一个工作。

如何将这两者区分开来。我还有什么别的吗?

1 个答案:

答案 0 :(得分:0)

行。经过这么多实验,我提出的最佳解决方案是创建我自己的用户控件,该控件由一个按钮和一个组合框组成,模仿我对组合框本身的预期行为。

首先,我在模型中犯了一个非常愚蠢的错误,其中成员Project和Division的两个成员包含相同的Employee实例,这使得AvailableEmployees属性有问题。我真正需要做的是在项目中创建员工副本列表,而不仅仅是参考。

无论如何,我创建了一个新的用户控件并将其命名为DynamicSourceComboBox。此控件的XAML如下所示:

<Grid>
    <Button x:Name="selected"
            Content="{Binding RelativeSource={RelativeSource AncestorType=local:DynamicSourceComboBox}, Path=SelectedValue}"
            Click="selected_Click"/>
    <ComboBox x:Name="selections"
              ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=local:DynamicSourceComboBox}, Path=ItemsSource}"
              DisplayMemberPath="{Binding RelativeSource={RelativeSource AncestorType=local:DynamicSourceComboBox}, Path=DisplayMemberPath}"
              Visibility="Collapsed"
              SelectionChanged="selections_SelectionChanged"
              MouseLeave="selections_MouseLeave"/>
</Grid>

我在这里有一些从按钮和组合框到我的用户控件中的属性的绑定。这些实际上是依赖属性。我的用户控件的代码隐藏如下:

public partial class DynamicSourceComboBox : UserControl
{
    public DynamicSourceComboBox()
    {
        InitializeComponent();
    }

    public object SelectedValue
    {
        get { return (object)GetValue(SelectedValueProperty); }
        set { SetValue(SelectedValueProperty, value); }
    }
    public static readonly DependencyProperty SelectedValueProperty =
        DependencyProperty.Register("SelectedValue", typeof(object), typeof(DynamicSourceComboBox), new PropertyMetadata(null));

    public IEnumerable ItemsSource
    {
        get { return (IEnumerable)GetValue(ItemsSourceProperty); }
        set { SetValue(ItemsSourceProperty, value); }
    }
    public static readonly DependencyProperty ItemsSourceProperty =
        ComboBox.ItemsSourceProperty.AddOwner(typeof(DynamicSourceComboBox));

    public string DisplayMemberPath
    {
        get { return (string)GetValue(DisplayMemberPathProperty); }
        set { SetValue(DisplayMemberPathProperty, value); }
    }
    public static readonly DependencyProperty DisplayMemberPathProperty =
        ComboBox.DisplayMemberPathProperty.AddOwner(typeof(DynamicSourceComboBox));

    private void selected_Click(object sender, RoutedEventArgs e)
    {
        selected.Visibility = Visibility.Hidden;
        selections.Visibility = Visibility.Visible;
        selections.IsDropDownOpen = true;
    }

    private void selections_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        selections.Visibility = Visibility.Collapsed;
        selected.Visibility = Visibility.Visible;
        selections.IsDropDownOpen = false;

        if (e.AddedItems.Count == 1)
        {
            var item = e.AddedItems[0];

            Type itemType = item.GetType();
            var itemTypeProps = itemType.GetProperties();

            var realValue = (from prop in itemTypeProps
                            where prop.Name == DisplayMemberPath
                            select prop.GetValue(selections.SelectedValue)).First();

            SelectedValue = realValue;
        }  
    }

    private void selections_MouseLeave(object sender, MouseEventArgs e)
    {
        selections.Visibility = Visibility.Collapsed;
        selected.Visibility = Visibility.Visible;
        selections.IsDropDownOpen = false;
    }
}

这些依赖项属性模仿ComboBox中具有相似名称的属性,但它们以一种使它们作为单个复杂组合框一起运行的方式连接到内部组合框和按钮。

按钮中的Click事件隐藏它并显示组合框以产生仅打开的框的效果。然后我在组合框触发中有一个SelectionChanged事件来更新所有需要的信息和一个MouseLeave事件,以防万一用户没有做出任何真正的选择更改。

当我需要使用新的用户控件时,我将其设置为:

<local:DynamicSourceComboBox ItemsSource="{Binding RelativeSource={RelativeSource AncestorLevel=1, AncestorType=ListBox}, Path=DataContext.AvailableEmployees}"
                                                                 DisplayMemberPath="FirstName"
                                                                 SelectedValue="{Binding FirstName, Mode=TwoWay}"/>

当然,为了使所有这些工作正常,我必须在模型中使用PropertyChanged事件进行大量连接,因此Projects实例将知道在任何时候进行更改时为AvailableEmployees引发PropertyChanged事件,但是这样并不是这个用户控件本身的关注点。

这是一个非常笨重的解决方案,有很多额外的代码有点难以理解,但它确实是我能够解决的问题的最佳(实际上唯一)解决方案。