1
votes

Liez un bouton à 3 DataGrids différents qui sont chacun dans un TabControl

Je travaille sur un projet qui contient un TabControl . Dans chacun des TabItem il y a un DataGrid qui est lié à un ObservableCollection dans mon modèle de vue.

J'ai besoin de lier le Button Edit au DataGrid qui est focalisé pour le moment ( TabItem est concentré). Est-il possible d'accomplir cela assez facilement sans avoir plusieurs boutons codés "en dur" sur un DataGrid / TabItem et en utilisant le modèle MVVM?

Donc, fondamentalement, cet ordre: TabControl -> Selected TabItem -> DataGrid -> SelectedItem

Tabcontrol avec Datagrids et Button

Exemple XAMLcode (essentiellement un format réel sans style, etc.):

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

Afficher le modèle ( SetProperty déclenche simplement INotifyPropertyChanged et définit la valeur):

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

Je ne pense pas que mon modèle de vue soit important dans ce cas, mais si vous avez besoin de plus d'informations, dites-le-moi et je vous le fournirai.


2 commentaires

Avez-vous des modèles de vue pour vos éléments d'onglets ou un seul modèle de vue pour la vue entière? À quoi sont liés les ItemsSource des DataGrid ? Ils ne sont pas liés dans le XAML que vous avez fourni.


Le xaml fourni est juste un exemple qui devrait montrer comment cela fonctionne. Chaque Datagrid est lié à une liste à l'intérieur du Viewmodel de la fenêtre entière, il n'y a donc qu'un seul Viewmodel que je modifierai la source d'éléments, il est donc préférable de comprendre


3 Réponses :


0
votes

Plusieurs façons de le faire sont faciles et rapides

"Changer" avec cheeking quel onglet est sélectionné

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
   }

d'autres façons de le faire seraient, l'événement onTabChanged puis le référencement du DataGrid sur le nouvel onglet ou une méthode qui vous donne le DataGrid focalisé actuel (comme mon exemple) et ainsi de suite. J'espère que je pourrais vous aider .. bonne chance!


6 commentaires

Merci pour votre réponse. Le problème est que c'est dans le code derrière et pour l'utiliser dans MVVM, j'ai besoin d'obtenir l'objet de l'élément sélectionné et donc sélectionné datagrid comme CommandParameter Je suppose que cela ne fonctionnera pas avec votre solution, n'est-ce pas?


Je ne vois pas réellement le problème, mais vous pouvez créer une méthode renvoyant une référence au DataGrid, si vous n'avez aucun moyen de communiquer entre vos travaux de code, le code ne sera évidemment pas utile. Mais vous avez parlé comme votre bouton fait référence à un DataGrid et il vous suffit de le changer.


Le problème, à mon avis, c'est que même si je sais techniquement quelle grille de données est au centre, je dois définir le paramètre de commande sur cela. Je veux dire que je pourrais définir une propriété à l'intérieur du Viewmodel en tant qu'objet de l'élément sélectionné du tabitem focalisé parce que l'objet du viewmodel est accessible par le code derrière, mais je suppose que ce serait contre le modèle MVVM n'est-ce pas?


Cela dépend, de la façon dont vous le voyez, vous pourriez le voir comme une interprétation de rendre le backend plus facile à développer, si c'est "juste" la cible d'un bouton, il y a beaucoup plus à faire pour qu'il soit mvvm. Alors c'est à vous comment vous voulez le faire. Ces «modèles» sont également juste une ligne directrice pour ce qui est efficace et facile à gérer pour les grandes équipes (qui séparent l'arrière et l'avant), notre équipe va également après le modèle MVVM, mais comme nous le faisons tous avant et arrière et, quelques petits ajustements qui nous donner une qualité de vie sont autorisés.


Oui, c'est vrai, je suppose que j'ai besoin d'un mélange des deux de toute façon, car cela ne fonctionnera probablement pas comme je le souhaite. Mais merci quand même pour votre aide et votre temps. Mon entreprise fait les deux aussi, mais le code derrière n'est pas très bien accueilli: /


lien Je connais votre lutte, mais cet article explique même qu'il y aura toujours un besoin de contrôleurs, parfois plus que de demander si quelque chose va bien n'est pas nécessaire pour trouver un moyen de le faire / de le faire autoriser



0
votes

Une solution possible pour MVVM à ce problème consiste à utiliser la propriété IsSelected de TabItems:

// 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
    }
}
<!-- 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>


2 commentaires

Merci pour votre réponse. Par conséquent, je dois également utiliser la propriété selectedItem des datagrids, ce qui n'est pas ce que je voulais vraiment dans mon code, mais c'est bien. Même si ce n'est pas aussi "joli" que d'utiliser CommandParameter, je suppose que c'est l'une des meilleures options que j'aurai donc je vais l'utiliser pour le moment :) Alors merci pour votre aide, je ne le marquerai pas comme la réponse pour le moment car peut-être quelqu'un qui est vraiment bon dans ce domaine connaît une réponse, mais je le ferai plus tard s'il n'y a pas d'autre réponse :)


Je vous en prie! Oui, je suis d'accord que ce n'est pas trop joli - voyons si quelqu'un propose une réponse plus élégante.



2
votes

Une variante MVVM statique

Je pense que vous devez décomposer votre modèle de vue principale. Votre modèle de vue principal contient des listes liées par des grilles de données à l'intérieur de TabItems . Ce modèle de vue se gonfle rapidement et ne sépare pas bien les vues et les préoccupations. Au lieu de cela, vous devez avoir un modèle de vue principale pour la vue contenant le TabControl et un modèle de vue distinct pour chaque vue unique dans le contrôle onglet.

Dans votre exemple, un type de modèle de vue suffit, car les trois onglets contiennent les mêmes contrôles et affichent juste une liste dans un DataGrid . Ce modèle de vue exposerait la collection à être liée par le DataGrid et une propriété pour la sélection actuelle. Vous pouvez attribuer la collection de plusieurs manières, la définir de l'extérieur, par exemple via le modèle de vue principale, passer la collection via le constructeur ou passer un service pour cela.

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

Dans votre modèle de vue principal, vous pouvez exposer une propriété de modèle de vue pour chaque onglet.

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

Ensuite, dans votre XAML, liez le SelectedItem du TabControl et le DataContext s pour les onglets.

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

Vous pouvez maintenant lier le paramètre de commande à la propriété Selected ...

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

... ou accédez au modèle de vue sélectionné dans votre MainViewModel et obtenez son élément de liste Selected .

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

Une variante MVVM dynamique

L'exposition de trois propriétés statiques n'est pas très extensible lorsque vous pensez à créer des onglets supplémentaires pouvant contenir des vues différentes, je vous montre donc comment le faire de manière plus dynamique. Supposons que vous ayez créé le MessmittelViewModel comme ci-dessus et un FoobarMessmittelViewModel . Créez une ObservableCollection d'un type de base, par exemple MessmittelViewModelBase et une propriété sélectionnée comme précédemment.

var parameter = SelectedViewModel.Selected;

Liez le ItemsSource à la collection MessmittelViewModels et créez un DataTemplate qui représente sa vue pour chaque type de modèle de vue concrète.

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

Voilà, le TabControl va maintenant créer une vue appropriée pour chaque élément de la collection de modèles de vue en fonction de son type. Puisqu'il existe différents types maintenant, vous choisirez votre paramètre pour la commande (et peut-être même s'il est activé pour cet onglet) dans le code en vérifiant le type.

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

Une variante MVVM utilisant un convertisseur

Pour votre approche actuelle, vous pouvez également résoudre le problème en XAML uniquement, à l'aide d'un convertisseur à valeurs multiples. Cependant, il s'agit également d'une variante statique. Ce convertisseur renverra une valeur liée basée sur l'index sélectionné dans le contrôle onglet.

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

En XAML, vous devez lier le CommandParameter comme ceci.

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

Vous pouvez également créer un convertisseur pour parcourir l'arborescence visuelle et obtenir l'élément sélectionné du DataGrid correspondant, sans le lier, mais cela supposerait une structure visuelle et cette solution est plus robuste, car vous spécifiez les éléments explicitement. En fait, ce convertisseur est plus flexible en ce qu'il vous permet de lier le paramètre de commande à n'importe quel contrôle avec n'importe quelle propriété dans un élément d'onglet.

Pour rappel, vous pouvez également obtenir la même chose en XAML via des déclencheurs, mais je pense que cela interférerait trop avec le style des contrôles et pourrait être plus difficile à réutiliser.


2 commentaires

J'ai ajouté une autre variante utilisant un convertisseur à valeurs multiples qui ne vous oblige pas à modifier votre modèle de vue et est également compatible MVVM et réutilisable.


Wow, c'est beaucoup de travail que vous avez mis dans ce domaine, merci beaucoup pour cela. J'ai donc utilisé l'option de conversion pour le moment jusqu'à ce que j'en ai besoin pour qu'elle ne soit pas statique, mais comme je ne prévois pas d'ajouter plus d'éléments à ce TabControl, il n'est pas nécessaire que je le fasse de manière dynamique: je les garderai toujours esprit pour les projets futurs :)