【问题标题】:Cascading ComboBox in DataGrid with MVVM使用 MVVM 在 DataGrid 中级联组合框
【发布时间】:2016-04-04 20:19:20
【问题描述】:

我的目标是在 WPF 中拥有一组级联组合框。我正在尝试使用 MVVM 模型,但仍在学习。

项目的一些背景信息。我正在尝试为员工编辑时间。

所以我有一个 DataGrid 中选定员工的时间列表。 DataGrid 中的每一行都是一个 Time 对象。时间由一些字段 InTime、OutTime、Date、Hours... 等组成。一个时间也有一个部门和一个工作。

目前我的部门组合框已连接并正常工作,但我不确定如何根据部门字段中选择的内容构建工作组合框。

这是我的 ViewModel 的设置方式

public ObservableCollection<Time> Times { get; set; }
public ObservableCollection<Department> Departments { get; set; }

public TimeSheetsViewModel()
{
   Times = new ObservableCollection<Time>();
   Departments = new ObservableCollection<Departments>();
   GetDepartments();
}

 private void GetDepartments()
{
    /*
     This section contains code to connect to my SQL Database and fills a DataTable dt
    */

    if (Departments != null)
        Departments.Clear();


    for (int i = 0; i < dt.Rows.Count; i++)
    {
        Department d = new Department() { Display = dt.Rows[i]["DISPLAY"].ToString(), DepartmentCode = dt.Rows[i]["DEPARTMENT_CODE"].ToString(), CompanyCode = dt.Rows[i]["COMPANY_CODE"].ToString() };
            Departments.Add(d);
    }
}

这是我的 DataGrid 上的绑定

<DataGrid Grid.Row="1" Margin="15,0,15,15" Visibility="Visible"  FontSize="14" HorizontalGridLinesBrush="{StaticResource Nelson2}" VerticalGridLinesBrush="{StaticResource Nelson2}" ItemsSource="{Binding Times}" SelectionMode="Single" CellEditEnding="DataGrid_CellEditEnding" RowEditEnding="DataGrid_RowEditEnding" AutoGenerateColumns="False">
    <DataGridTemplateColumn Header="Department Code">
          <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                     <TextBlock Text="{Binding Path= Department.Display}"/>
                </DataTemplate>
          </DataGridTemplateColumn.CellTemplate>
          <DataGridTemplateColumn.CellEditingTemplate>
                <DataTemplate>
                      <ComboBox ItemsSource="{Binding RelativeSource={RelativeSource Findancestor, AncestorType={x:Type UserControl}}, Path=DataContext.Departments}" DisplayMemberPath="Display" SelectedValuePath="DepartmentCode" SelectedValue="{Binding Department.DepartmentCode}" />
                 </DataTemplate>
           </DataGridTemplateColumn.CellEditingTemplate>
    </DataGridTemplateColumn>
</DataGrid>

那么我如何实现我的工作组合框以根据为该行中的部门选择的任何内容填充其项目?

我假设我想将代码放在我的同一个视图模型中。

感谢任何帮助,谢谢!

编辑(04/05/16):

如何返回一个带有转换器的对象,以便我可以使用该转换器将不同的东西绑定到该对象的字段。

说这是我的转换器

public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
    string departmentCode = values[0].ToString();
    ObservableCollection<Department> Departments = values[1] as ObservableCollection<Department>;

    return Departments.FirstOrDefault(Department => Department.DepartmentCode == departmentCode);
}

这是我的绑定

<TextBlock >
    <TextBlock.Text>
        <MultiBinding Converter="{StaticResource DeptCodeToDeptConverter}" >
            <Binding Path="DepartmentCode" />
            <Binding Path="DataContext.Departments" RelativeSource="{RelativeSource Findancestor, AncestorType={x:Type UserControl}}"/>
         </MultiBinding>
     </TextBlock.Text>
</TextBlock>

该转换器将返回一个部门对象,但如果我希望 TextBlock 的文本为 Department.Name 或 Department.Location 怎么办。我是否必须创建一个新的转换器来返回我想在不同控件中使用的每个字段?或者有没有办法使用这种方法来实现我想要的?

【问题讨论】:

    标签: c# wpf mvvm datagrid cascadingdropdown


    【解决方案1】:

    我会选择以下两种方法之一:

    1.使用多绑定转换器。您的第一个组合框的 ItemsSource 将绑定到它的集合。第二个可以在第一个的 SelectedItem 上使用多重绑定转换器,并为第二个组合框使用一些可用项集的集合,以返回第二个的 ItemsSource 的集合。

    当第一个组合框更改它的选定项时,绑定将更新:

    public class DepartmentJobComboboValueConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            Department department = values[0] as Department;
            ObservableCollection<string> jobCodes = values[1] as ObservableCollection<string>;
    
            //do our logic to filter the job codes by department
            return jobCodes.Where(jobCode => jobCode.StartsWith(department.DepartmentCode)).ToList();
        }
    
        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
    

    然后您可以将 SelectedItems 绑定为第一个值,并将集合字典绑定为您的第二个值:

        <DataGrid ItemsSource="{Binding Times}"
                  SelectionMode="Single"
                  AutoGenerateColumns="False">
            <DataGrid.Columns>
                <DataGridTemplateColumn Header="Department">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <TextBlock Text="{Binding Path= Department.DepartmentCode}"/>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                    <DataGridTemplateColumn.CellEditingTemplate>
                        <DataTemplate>
                            <ComboBox ItemsSource="{Binding RelativeSource={RelativeSource Findancestor, AncestorType={x:Type UserControl}}, Path=DataContext.Departments, UpdateSourceTrigger=PropertyChanged}"
                                      DisplayMemberPath="DepartmentCode"
                                      SelectedValuePath="DepartmentCode"
                                      SelectedValue="{Binding Department.DepartmentCode}" />
                        </DataTemplate>
                    </DataGridTemplateColumn.CellEditingTemplate>
                </DataGridTemplateColumn>
                <DataGridTemplateColumn Header="Job code">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <TextBlock Text="{Binding Path=Job}"/>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                    <DataGridTemplateColumn.CellEditingTemplate>
                        <DataTemplate>
                            <ComboBox SelectedValue="{Binding Job}">
                                <ComboBox.ItemsSource>
                                    <MultiBinding Converter="{StaticResource DepartmentJobComboboValueConverter}">
                                        <Binding Path="Department" />
                                        <Binding Path="DataContext.JobCodes"
                                                 RelativeSource="{RelativeSource Findancestor, AncestorType={x:Type UserControl}}"/>
                                    </MultiBinding>
                                </ComboBox.ItemsSource>
                            </ComboBox>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellEditingTemplate>
                </DataGridTemplateColumn>
            </DataGrid.Columns>
        </DataGrid>
    

    使用多绑定转换器作为静态资源:

    这是视图模型:

    public class TimeSheetsViewModel
    {
        public ObservableCollection<Time> Times { get; set; }
        public ObservableCollection<Department> Departments { get; set; }
        public ObservableCollection<string> JobCodes { get; set; }
    
        public TimeSheetsViewModel()
        {
            Times = new ObservableCollection<Time>();
            Departments = new ObservableCollection<Department>();
            GetDepartments();
            JobCodes = new ObservableCollection<string>();
            GetJobCodes();
        }
    
        private void GetJobCodes()
        {
            JobCodes = new ObservableCollection<string> { "01-A", "01-B", "02-A", "02-B", "03-A", "03-B" };
        }
    
        private void GetDepartments()
        {
            Departments = new ObservableCollection<Department> {
                new Department("01"),
                new Department("02"),
                new Department("03")
            };
        }
    }
    
    public class Department
    {
        public String DepartmentCode { get; set; }
        public Department(string departmentCode) { DepartmentCode = departmentCode; }
    }
    
    public class Time
    {
        //time in etc etc
        public Department Department { get; set; }
        public string Job { get; set; }
    }
    

    这会产生这个:

    这可能是对您已有的最少的更改。如果你想走单独的视图模型路线,这可能是有利的(你已经有一个“显示”属性,它在 ViewModel 行为的领域中,因为它不是数据,不应该在你的模型中。

    同样,您可能希望在用户更改部门代码时采取措施,例如清除/清空职位代码(否则,他们可以设置职位代码,然后更改部门代码并进行无效配置)。逻辑越来越复杂,可能更适合 TimesViewModel。

    2。您也可以使用中间属性来做到这一点您不必直接绑定到所有内容,您可以像这样在 ViewModel 中创建一个属性:

    public class TimesViewModel: INotifyPropertyChanged
    {
        //notifying property that is bound to ItemsSource in the first Combobox
        public ObservableCollection<Department> Departments{ get... }
    
        //total list of job codes
        public List<string> JobCodes{ get...}
    
        //This is the Department that's bound to SelectedItem in the first ComboBox
        public Department Department
        {
            get
            {
                return department;
            }
            set
            {
                //standard notify like all your other bound properties
                if (department!= value)
                {
                    department= value;
                    //when this changes, our selection has changed, so update the second list's ItemsSource
                    DepartmentOnlyJobCodes = JobCodes.Where(jobCode => jobCode.StartsWith(Department.DepartmentCode)).ToList();
                    //we can also do more complex operations for example, lets clear the JobCode!
                    JobCode = "";
                    NotifyPropertyChanged("SelectedKey");
                }
            }
        }
    
        //an "intermediatary" Property that's bound to the second Combobox, changes with the first's selection
        public ObservableCollection<string> DepartmentOnlyJobCodes{ get ... }
    
        public string JobCode {get...}
    }
    

    这些都有相同的结果,您最终会将您的第二个 ComboBoxes 绑定到您以某种方式存储的列表。逻辑可以根据您的应用程序而改变,我刚刚使用了一个字典作为示例。

    编辑:对编辑的响应

    您可以绑定到父面板中的数据上下文,并访问子元素中的属性:

    <StackPanel>
        <StackPanel.DataContext>
            <MultiBinding Converter="{StaticResource DeptCodeToDeptConverter}" >
                <Binding Path="DepartmentCode" />
                <Binding Path="DataContext.Departments" RelativeSource="{RelativeSource Findancestor, AncestorType={x:Type UserControl}}"/>
             </MultiBinding>
         </StackPanel.DataContext>
        <TextBlock Text="{Binding DepartmentCode>"/>
        <TextBlock Text="{Binding DepartmentName>"/>
    </StackPanel>
    

    或者您可以添加第三个多重绑定来传递您的属性并使用反射返回您需要的内容:

    <TextBlock >
        <TextBlock.Text>
            <MultiBinding Converter="{StaticResource DeptCodeToDeptConverter}" >
                <Binding Path="DepartmentCode" />
                <Binding Path="DataContext.Departments" RelativeSource="{RelativeSource Findancestor, AncestorType={x:Type UserControl}}"/>
                <Binding>
                    <Binding.Source>
                        <sys:String>DepartmentCode</sys:String>
                    </Binding.Source>
                </Binding>
             </MultiBinding>
         </TextBlock.Text>
    </TextBlock>
    

    然后您可以将其标记到转换器逻辑的末尾:

    if (values.Length > 2 && values[2] != null)
    {
        return typeof(Department).GetProperty(values[2] as string).GetValue(department, null);
    }
    else 
    {
        //no property string passed - assume they just want the Department object
        return department;
    }
    

    【讨论】:

    • 谢谢@Joe,不过我有点困惑。这东西很新。那么我的 DataGrid 中的每一行是否需要绑定到它自己的视图模型实例?
    • 对不起,应该澄清一下,是的,每一行都会有一个视图模型。我就是这样处理的。如果它是一个简单的数据表,我认为它不值得,但你显然有更复杂的功能。不过,您可以使用没有行视图模型的多绑定转换器。您期望有多少行?
    • 看看你是如何实现它的,如果没有时间视图模型,多重绑定应该可以正常工作。明天我会敲出一个更详细的例子(9小时左右)。是什么定义了哪个集合进入第二个组合框(使用第一个我假设的部门代码)?
    • 好吧,我不反对每条线都使用视图模型,但根据我的经验,我什至不知道这是一种可能性。我预计大约有 5-7 行(他们在一周内工作的每一天),但可能会超过 20-30 行(不太可能)。是的,例如,部门代码实际上附加在工作代码的开头; 003 是我的部门代码 工作代码可以是 003-00001 到 003-00025。因此,使用部门代码是我为该部门制作工作列表的方式。谢谢你的帮助!我明天某个时候回来看看
    • @CR_eeper 主要编辑更详细地展示了如何进行多重绑定。
    猜你喜欢
    • 2023-03-14
    • 2017-09-02
    • 2012-02-02
    • 2022-01-06
    • 1970-01-01
    • 2023-03-18
    • 1970-01-01
    • 1970-01-01
    • 2012-04-05
    相关资源
    最近更新 更多