繁体   English   中英

使用 MVVM 在 DataGrid 中级联组合框

[英]Cascading ComboBox in DataGrid with MVVM

我的目标是在 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 该怎么办。 我是否必须创建一个新的转换器来返回我想在不同控件中使用的每个字段? 或者有没有办法使用这种方法实现我想要的?

我会选择两种方法之一来做到这一点:

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

它们都有相同的结果,您最终会将您的第二个 ComboBox 绑定到您以某种方式存储的列表。 逻辑可以根据您的应用程序而改变,我只是以字典为例。

编辑:响应编辑

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM