DataGrid中的级联ComboBox与MVVM

时间:2016-04-04 20:19:20

标签: c# wpf mvvm datagrid cascadingdropdown

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

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

所以我在DataGrid中有一个所选员工的Times列表。 DataGrid中的每一行都是一个Time对象。时间包括一些字段InTime,OutTime,Date,Hours ...等。 A Time还有一个部门和一个工作。

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

以下是我的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):

如何使用Converter返回一个Object,以便我可以使用该Converter将不同的东西绑定到该Object的字段。

说这是我的转换器

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>

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

1 个答案:

答案 0 :(得分: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; }
}

这产生了这个:

enter image description here

这可能是您已经完成的最小变化。如果你想要单独的视图模型路由,这可能是有利的(你已经有一个“显示”属性,这是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...}
}

这两者都有相同的结果,你最终会将你的第二个ComboBox绑定到你以某种方式存储的List。逻辑可以根据你的应用程序而改变,我只是用一本字典作为例子。

编辑:回复编辑

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

<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;
}