Привязать кнопку к 3 разным сеткам данных, которые находятся в TabControl каждой

#c# #wpf #xaml #mvvm

#c# #wpf #xaml #mvvm

Вопрос:

Я работаю над проектом, который содержит TabControl . В каждой из TabItem них есть DataGrid , которая привязана к ObservableCollection модели моего представления.

Мне нужно привязать редактирование Button к тому, DataGrid которое сфокусировано в данный момент ( TabItem сфокусировано). Возможно ли выполнить это довольно просто, не имея нескольких кнопок, которые «жестко» закодированы в одну DataGrid / TabItem и используя шаблон MVVM?

Итак, в основном в таком порядке: TabControl -> Выбранный TabItem -> DataGrid -> SelectedItem

Tabcontrol с сетками данных и кнопкой

Пример XAMLcode (в основном реальный формат без стилизации и т.д.):

 <Button Content="Edit"
        Command="{Binding ExecuteEditMessmittelCommand}"
        CommandParameter="{Binding ElementName=Messmittel_DataGrid, Path=SelectedItem}">
</Button>

<TabControl>

   <TabItem Header="Messmittel">
             <DataGrid x:Name="Messmittel_Datagrid"
                       ItemsSource="{Binding MessmittelDisplayCollection}">
                <DataGrid.Columns>
                   <DataGridTextColumn Header="ID"
                        Binding="{Binding Path=Benutzer_ID}"

                   <DataGridTextColumn Header="Seriennummer"
                        Binding="{Binding Path=Seriennummer}"

                   <DataGridTextColumn Header="MessmittelArt"
                        Binding="{Binding Path=Vorname}"
                </DataGrid.Columns>
             </DataGrid>
   </TabItem>

   <TabItem Header="Mechanik">
             <DataGrid x:Name="Mechanik_Datagrid"
                       ItemsSource="{Binding MechanikDisplayCollection}">
                //here is the datagrid content
             </DataGrid>
   </TabItem>

   <TabItem Header="Prüfhilfsmittel">
             <DataGrid x:Name="Pruefhilfsmittel_Datagrid"
                       ItemsSource="{Binding PruefhilfsmittelDisplayCollection}">
                //here is the datagrid content
             </DataGrid>
   </TabItem>

</TabControl>
  

Просмотр модели ( SetProperty просто запускает INotifyPropertyChanged и устанавливает значение):

 public ObservableCollection<MessmittelModel> MessmittelDisplayCollection
{
        get { return DatabaseDisplayModel.MessmittelCollection; }
        set { SetProperty(ref DatabaseDisplayModel.MessmittelCollection, value);}
}

public ObservableCollection<MessmittelModel> MechanikDisplayCollection
{
        get { return DatabaseDisplayModel.MechanischeMessmittelCollection; }
        set { SetProperty(ref DatabaseDisplayModel.MechanischeMessmittelCollection, value); }
}
public ObservableCollection<MessmittelModel> PruefhilfsmittelDisplayCollection
{
        get { return DatabaseDisplayModel.PruefhilfsmittelCollection; }
        set { SetProperty(ref DatabaseDisplayModel.PruefhilfsmittelCollection, value); }
}
  

Я не думаю, что моя модель представления важна в этом случае, но если вам нужна дополнительная информация, просто скажите мне, и я ее предоставлю.

Комментарии:

1. У вас есть модели просмотра для ваших элементов вкладки или только одна модель просмотра для всего представления? К чему привязаны ItemsSource s из DataGrid s? Они не привязаны к XAML, который вы предоставили.

2. Предоставленный xaml — это просто пример, который должен показать, как это работает. Каждая сетка данных привязана к списку внутри Viewmodel всего окна, поэтому есть только 1 Viewmodel, который я отредактирую itemsource, чтобы его лучше понять

Ответ №1:

Статический вариант MVVM

Я думаю, вам нужно разложить вашу основную модель представления. Ваша основная модель представления содержит списки, которые связаны сетками данных внутри TabItems . Эта модель представления быстро раздувается и плохо разделяет представления и проблемы. Вместо этого у вас должна быть модель основного представления для представления, содержащего TabControl , и отдельная модель представления для каждого уникального представления в элементе управления tab.

В вашем примере достаточно одного типа модели представления, поскольку все три вкладки содержат одинаковые элементы управления и отображают только список в DataGrid . Эта модель представления предоставила бы коллекцию, которая будет привязана к DataGrid и свойству для текущего выбора. Вы могли бы назначить коллекцию многими способами, установить ее извне, например, через модель основного представления, передать коллекцию через конструктор или передать для этого сервис.

 public class MessmittelViewModel : BindableBase
{
   private MessmittelModel _selected;
   private ObservableCollection<MessmittelViewModel> _messmittelModels;

   // ...constructor, initialization of properties, other properties.

   public MessmittelModel Selected
   {
      get => _selected;
      set => SetProperty(ref _selected, value);
   }

   public ObservableCollection<MessmittelModel> MessmittelDisplayCollection
   {
      get => _messmittelModels;
      set { SetProperty(ref _messmittelModels, value);
   }
}
  

В вашей основной модели представления вы могли бы предоставить свойство модели представления для каждой вкладки.

 public class MainViewModel: BindableBase
{
   private MessmittelViewModel _selectedViewModel;
   private MechanischeMessmittel _mechanischeMessmittelViewModel;

   // ...contructor, initialize properties, other code.

   public MessmittelViewModel SelectedViewModel
   {
      get => _selectedViewModel;
      set => SetProperty(ref _selectedViewModel, value);
   }

   public MechanischeMessmittelViewModel
   {
      get => _mechanischeMessmittelViewModel;
      private set => SetProperty(ref _mechanischeMessmittelViewModel, value);
   }
}
  

Затем в вашем XAML привяжите SelectedItem из TabControl и DataContext s для вкладок.

 <TabControl SelectedItem="{Binding SelectedViewModel}">
   <!-- ...other content. -->
   <TabItem Header="Mechanik"
            DataContext={Binding MechanischeMessmittelViewModel}">
      <DataGrid ItemsSource="{Binding MessmittelDisplayCollection}">
         <!-- ...data grid content. -->
      </DataGrid>
   </TabItem>
</TabControl>
  

Теперь вы можете либо привязать параметр команды к Selected свойству…

 <Button Content="Edit"
        Command="{Binding ExecuteEditMessmittelCommand}"
        CommandParameter="{Binding SelectedViewModel.Selected}"/>
  

… или получить доступ к выбранной модели представления в вашем MainViewModel и получить ее Selected элемент списка.

 var parameter = SelectedViewModel.Selected;
  

Динамический вариант MVVM

Предоставление трех статических свойств не очень расширяемо, когда вы думаете о создании дополнительных вкладок, которые могут содержать разные представления, поэтому я покажу вам, как сделать это более динамичным способом. Давайте предположим, что вы создали MessmittelViewModel как указано выше, и FoobarMessmittelViewModel . Создайте ObservableCollection базовый тип, например MessmittelViewModelBase , и выбранное свойство, как и раньше.

 public class MainViewModel: BindableBase
{
   private MessmittelViewModelBase _selectedViewModel;
   private ObservableCollection<MessmittelViewModelBase> _messmittelModels;

   public MainViewModel()
   {
      // ...other code.

      MessmittelViewModels = new ObservableCollection<MessmittelViewModelBase>();
      MessmittelViewModels.Add(new MessmittelViewModel(DatabaseDisplayModel.MessmittelCollection));
      // ...add view models for the other tabs.
   }

   // ...other code.

   public MessmittelViewModelBase SelectedViewModel
   {
      get => _selectedViewModel;
      set => SetProperty(ref _selectedViewModel, value);
   }

   public ObservableCollection<MessmittelViewModelBase> MessmittelViewModels
   {
      get => _messmittelModels;
      set { SetProperty(ref _messmittelModels, value);
   }
}
  

Привяжите ее ItemsSource к MessmittelViewModels коллекции и создайте DataTemplate , которая представляет ее представление для каждого конкретного типа модели представления.

 <TabControl ItemsSource="{Binding MessmittelViewModels}">
   <TabControl.Resources>
      <DataTemplate DataType="{x:Type MessmittelViewModel}">
         <DataGrid ItemsSource="{Binding MessmittelDisplayCollection}">
            <DataGrid.Columns>
               <DataGridTextColumn Header="ID"
                                   Binding="{Binding Path=Benutzer_ID}"
                <DataGridTextColumn Header="Seriennummer"
                                    Binding="{Binding Path=Seriennummer}"
                <DataGridTextColumn Header="MessmittelArt"
                                    Binding="{Binding Path=Vorname}"
            </DataGrid.Columns>
         </DataGrid>
      </DataTemplate>
      <!-- ...other data templates -->
   </TabControl.Resources>
</TabControl>
  

Вот и все, TabControl теперь будет создано соответствующее представление для каждого элемента в коллекции моделей просмотра на основе его типа. Поскольку сейчас существуют разные типы, вы должны выбрать свой параметр для команды (и, возможно, даже если он включен для этой вкладки) в коде, проверив тип.

 if (SelectedViewModel is MessmittelViewModel messmittelViewModel)
{
   var parameter = messmittelViewModel.Selected;
}
  

Вариант MVVM с использованием конвертера

Для вашего текущего подхода вы также могли бы решить проблему только в XAML, используя многозначный конвертер. Однако это также статический вариант. Этот конвертер вернет привязанное значение на основе выбранного индекса в элементе управления tab.

 public class SelectedIndexBindingConverter : IMultiValueConverter
{
   public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
   {
      return values[(int)values[0]   1];
   }

   public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
   {
      throw new InvalidOperationException();
   }
}
  

В XAML вы должны привязать CommandParameter вот так.

 <Window.Resources>
      <local:SelectedIndexBindingConverter x:Key="SelectedIndexBindingConverter"/>
</Window.Resources>

<Button Content="Edit"
        Command="{Binding CommandTest}">
   <Button.CommandParameter>
      <MultiBinding Converter="{StaticResource SelectedIndexBindingConverter}">
         <Binding Path="SelectedIndex" ElementName="MyTabControl"/>
         <Binding Path="SelectedItem" ElementName="Messmittel_Datagrid"/>
         <Binding Path="SelectedItem" ElementName="Mechanik_Datagrid"/>
         <Binding Path="SelectedItem" ElementName="Pruefhilfsmittel_Datagrid"/>
      </MultiBinding>
   </Button.CommandParameter>
</Button>

<TabControl x:Name="MyTabControl">
   <!-- ...tab items as before. -->
</TabControl>
  

Вы также могли бы создать конвертер для обхода визуального дерева и получения выбранного элемента соответствующего DataGrid , не привязывая его, но это предполагало бы визуальную структуру, и это решение более надежное, поскольку вы указываете элементы явно. На самом деле этот конвертер более гибкий, поскольку позволяет привязать параметр command к любому элементу управления с любым свойством в элементе вкладки.

Просто в качестве примечания, вы могли бы добиться того же в XAML и с помощью триггеров, но я думаю, что это слишком сильно повлияло бы на стиль элементов управления и могло бы быть сложнее повторно использовать.

Комментарии:

1. Я добавил еще один вариант с использованием многозначного преобразователя, который не требует от вас изменения модели представления, а также совместим с MVVM и может использоваться повторно.

2. Вау, вы вложили в это много работы, большое вам за это спасибо. Итак, я использовал опцию convert на данный момент, пока мне не понадобится, чтобы она была нестатичной, но поскольку я не планирую добавлять больше элементов в этот TabControl, мне не обязательно делать это динамически: все же я буду помнить о них для будущих проектов 🙂

Ответ №2:

Несколько способов сделать это, это простой и быстрый

«Переключите», указав, какая вкладка выбрана

 Button_OnButtonClicked(//eventargs){
   DataGrid dgridToEdit = null;
   if(TabControl.SelectedTabIndex == 0){
      dgridToEdit = firstDGrid;
   }
   else if(TabControl.SelectedTabIndex == 1){
      dgridToEdit = secondDGrid;
   }
   else if(TabControl.SelectedTabIndex == 2){
      dgridToEdit = thirdDGrid;
   }


   //... then do your things with the dGrid
   }
  

другими способами сделать это было бы, если бы событие TABCHANGED затем ссылалось на сетку данных на новой вкладке или метод, который дает вам текущую сфокусированную сетку данных (как в моем примере) и так далее. Я надеюсь, что смогу вам помочь .. удачи!

Комментарии:

1. Спасибо за ответ. Проблема в том, что это в коде, и для того, чтобы использовать его в MVVM, мне нужно получить объект выбранного элемента и, следовательно, выбранную сетку данных в качестве CommandParameter, я думаю, это не сработает с вашим решением, не так ли?

2. На самом деле я не вижу проблемы, но вы могли бы создать метод, возвращающий ссылку на сетку данных, если у вас нет способа связи между вашими codeworks, чем, очевидно, не поможет код. Но вы говорили, что ваша кнопка ссылается на сетку данных, и вам просто нужно ее изменить.

3. Проблема, на мой взгляд, в том, что, хотя я технически знаю, какая сетка данных находится в фокусе, мне нужно установить commandparameter на это. Я имею в виду, что я мог бы установить свойство внутри Viewmodel в качестве объекта выбранного элемента сфокусированного tabitem, потому что к объекту viewmodel можно получить доступ с помощью кода, но я думаю, что это противоречило бы шаблону MVVM, не так ли?

4. Зависит от того, как вы это видите, вы могли бы рассматривать это как интерпретацию упрощения разработки серверной части, если это «просто» цель для кнопки, вам нужно сделать гораздо больше, чтобы она была mvvm. Так что вам решать, как вы хотите это сделать. Эти «шаблоны» также являются лишь руководством для того, что эффективно и легко обрабатывать для более крупных команд (которые разделяют серверную часть и интерфейс), наша команда также использует шаблон MVVM, но, поскольку мы все делаем front и back, допускаются некоторые небольшие изменения, которые обеспечивают нам качество жизни.

5. Да, верно, я думаю, мне в любом случае нужна смесь обоих, поскольку, вероятно, это будет работать не так, как я хочу. Но в любом случае спасибо за вашу помощь и время. Моя компания также делает и то, и другое, но код за этим не приветствуется так сильно :/

Ответ №3:

Одним из возможных MVVM-дружественных решений этой проблемы было бы использование свойства IsSelected TabItems:

 <!-- language: lang-xml -->
<Button Content="Edit"
        Command="{Binding ExecuteEditCommand}">
</Button>

<TabControl>

   <TabItem Header="Messmittel"
            IsSelected={Binding IsMessmittelSelected}>
             <DataGrid x:Name="Messmittel_Datagrid"
                       ItemsSource="{Binding MessmittelDisplayCollection}">
                <DataGrid.Columns>
                   <DataGridTextColumn Header="ID"
                        Binding="{Binding Path=Benutzer_ID}"

                   <DataGridTextColumn Header="Seriennummer"
                        Binding="{Binding Path=Seriennummer}"

                   <DataGridTextColumn Header="MessmittelArt"
                        Binding="{Binding Path=Vorname}"
                </DataGrid.Columns>
             </DataGrid>
   </TabItem>

   <TabItem Header="Mechanik"
            IsSelected={Binding IsMechanikSelected}>
             <DataGrid x:Name="Mechanik_Datagrid"
                       ItemsSource="{Binding MechanikDisplayCollection}">
                //here is the datagrid content
             </DataGrid>
   </TabItem>

   <TabItem Header="Prüfhilfsmittel"
            IsSelected={Binding IsPrüfhilfsmittelSelected}>
             <DataGrid x:Name="Pruefhilfsmittel_Datagrid"
                       ItemsSource="{Binding PruefhilfsmittelDisplayCollection}">
                //here is the datagrid content
             </DataGrid>
   </TabItem>

</TabControl>
  
 // DataContext above control is bound to:
// This method would be used by ExecuteEditCommand
private void OnExecuteEdit()
{
    if (IsMessmittelSelected)
    {
        // IsMessmittelSelected logic
    }
    else if (IsMechanikSelected)
    {
        // IsMechanikSelected logic
    }
    else if (IsPrüfhilfsmittelSelected)
    {
        // IsPrüfhilfsmittelSelected logic
    }
}
  

Комментарии:

1. Спасибо за ваш ответ. Поэтому мне также нужно использовать свойство SelectedItem для сеток данных, чего я на самом деле не хотел в своем коде, но все в порядке. Даже если это не так «красиво», как использование CommandParameter, я думаю, это один из лучших вариантов, которые я получу, поэтому я буду использовать его сейчас 🙂 Так что спасибо вам за помощь, я пока не буду отмечать это как ответ, потому что, возможно, кто-то, кто действительно хорош в этом, знает ответ, но я сделаю это позже, если другого ответа не будет 🙂

2. Добро пожаловать! Да, я согласен, это не слишком красиво — давайте посмотрим, придумает ли кто-нибудь более элегантный ответ.