2016-04-04 7 views
1

Моя цель состоит в том, чтобы иметь набор каскадных comboboxes в WPF. Я пытаюсь использовать модель MVVM, но все еще учась.Каскадный ComboBox в DataGrid с MVVM

Некоторая справочная информация о проекте. Я пытаюсь изменить время для сотрудников.

Итак, у меня есть список Times для выбранного сотрудника в DataGrid. Каждая строка в DataGrid является объектом Time. Время состоит из некоторых полей InTime, OutTime, Date, Hours ... ect. Время также имеет отдел и работу.

В настоящее время у меня есть отдел ComboBox, подключенный и работающий, но я не уверен, как создать поле со списком Job на основе того, что выбрано в поле Department.

Вот как мой 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> 

Так как же я могу осуществить свою работу выпадающего для заполнения его элементов на основе независимо выбран для отдела в этой строке?

Я предполагаю, что хочу поместить код для этого в мою модель просмотра.

Любая помощь приветствуется, спасибо!

EDIT (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. Должен ли я создать новый конвертер для возврата каждого поля, которое я хочу использовать в разных элементах управления? Или есть способ достичь того, что я хочу использовать с помощью этого метода?

ответ

0

я бы выбрал один из двух способов сделать это:

1. Используйте многопрофильным связывания преобразователя. Ваш первый элемент списка combobox будет связан с его коллекцией вещей. Второе может использовать многозадачный конвертер на первом SelectedItem и некоторую коллекцию доступных наборов элементов для второго combobox, чтобы вернуть коллекцию для Seconds 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...} 
} 

Оба они имеют одинаковый результат, вы в конечном итоге привяжете свои второй ComboBoxes к списку, который вы каким-то образом сохранили. Логика может меняться в зависимости от вашего приложения, я просто использовал словарь для примера.

Edit: ответ редактировать

Вы могли бы связать с контекстом данных в родительской панели, и доступ к свойствам в дочерних элементах:

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

Или вы могли бы добавить третий MultiBinding чтобы передать вашу собственность и использовать отражение, чтобы вернуть то, что вам нужно:

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

Спасибо @ Joe, я здесь немного смущен. Довольно новый на этом. Так что каждая строка в моем DataGrid должна быть привязана к ее собственному экземпляру модели представления? –

+0

Извините, должен был уточнить, да, каждая строка будет иметь модель обзора. Вот как я это сделаю. Если бы это была простая таблица данных, я не думаю, что это оправдывает это, но у вас явно есть более сложные функции. Вы могли бы использовать, вероятно, использовать конвертеры с несколькими связями без моделей просмотра строк. Сколько строк вы ожидаете? – Joe

+0

Рассматривая, как вы его реализовали, привязка multI должна отлично работать без моделей времени. Завтра я получу более подробный пример (9 часов или около того).Что определяет, какой сбор gos во второй блок со списком (используя код отдела из первого, который я предполагаю)? – Joe

Смежные вопросы