web-dev-qa-db-fra.com

Contrôle de l'onglet WPF et sélection MVVM

J'ai un TabControl dans une application MVVM WPF . Il est défini comme suit.

<TabControl Style="{StaticResource PortfolioSelectionTabControl}" SelectedItem="{Binding SelectedParameterTab}" >
    <TabItem Header="Trades" Style="{StaticResource PortfolioSelectionTabItem}">
        <ContentControl Margin="0,10,0,5" Name="NSDetailTradeRegion" cal:RegionManager.RegionName="NSDetailTradeRegion" />
    </TabItem>
    <TabItem Header="Ccy Rates" Style="{StaticResource PortfolioSelectionTabItem}">
        <ContentControl Margin="0,10,0,5" Name="NSDetailCcyRegion" cal:RegionManager.RegionName="NSDetailCcyRegion" />
    </TabItem>
    <TabItem Header="Correlations / Shocks" Style="{StaticResource PortfolioSelectionTabItem}">
        <ContentControl Name="NSDetailCorrelationRegion" cal:RegionManager.RegionName="NSDetailCorrelationRegion" />
    </TabItem>
    <TabItem Header="Facility Overrides" Style="{StaticResource PortfolioSelectionTabItem}" IsEnabled="False">
        <ContentControl Name="NSDetailFacilityOverrides" cal:RegionManager.RegionName="NSDetailFacilityOverrides" />
    </TabItem>
</TabControl>

Ainsi, chaque contenu d'élément d'onglet a sa propre vue qui lui est associée. Chacune de ces vues a le MEF [Export] attribut et est associé à la région concernée via la découverte de vues, donc le code ci-dessus est tout ce dont j'ai besoin pour que le contrôle de l'onglet soit chargé et basculé entre eux. Ils font tous référence au même objet ViewModel partagé derrière eux et interagissent donc de manière transparente.

Mon problème est que lorsque l'utilisateur accède à la fenêtre parent, je veux que le contrôle onglet par défaut sur le deuxième élément de l'onglet. C'est assez facile à faire lors du premier chargement de la fenêtre, en spécifiant dans XAML IsSelected="True" dans TabItem numéro 2. C'est moins facile à faire lorsque l'utilisateur s'éloigne de l'écran puis y revient.

J'ai pensé à avoir un SelectedItem={Binding SelectedTabItem} propriété sur le contrôle onglet, donc je pourrais définir par programme l'onglet sélectionné dans le ViewModel, mais le problème est que je n'ai aucune connaissance des objets TabItem dans le ViewModel car ils sont déclarés ci-dessus dans le XAML uniquement, donc je n'ai pas TabItem objet à passer à la propriété setter.

Une idée que j'ai eue était de faire en sorte que les vues enfants (qui forment le contenu de chacun des éléments de l'onglet ci-dessus) aient un style au niveau UserControl de leur XAML, quelque chose comme ceci.

<Style TargetType={x:Type UserControl}>
    <Style.Triggers>
        <DataTrigger Property="IsSelected" Value="True">
             <Setter Property="{ElementName={FindAncestor, Parent, typeof(TabItem)}, Path=IsSelected", Value="True" />
        </DataTrigger>
    </Style.Triggers>
</Style>

Je sais que le bit findancestor n'est pas correct; Je viens de le mettre là pour spécifier mon intention, mais je ne suis pas sûr de la syntaxe exacte. Fondamentalement, pour chaque UserControl d'avoir un déclencheur qui écoute une propriété sur le ViewModel (je ne sais pas comment je distinguerais chaque UserControl différent car, évidemment, ils ne peuvent pas tous écouter la même propriété ou ils sélectionneraient tous simultanément lorsque la propriété est définie sur True, mais avoir une propriété pour chaque contrôle utilisateur semble moche), puis trouve son conteneur TabItem parent et définit la valeur IsSelected sur true.

Suis-je sur la bonne voie avec une solution ici? Est-il possible de faire ce que je pense? Existe-t-il une solution plus ordonnée?

19
NZJames

Si vous regardez la page TabControl Class sur MSDN, vous trouverez une propriété appelée SelectedIndex qui est un int. Par conséquent, ajoutez simplement une propriété int dans votre modèle de vue et Bind au TabControl.SelectedIndex, puis vous pouvez sélectionner à tout moment l'onglet de votre choix dans le modèle de vue:

<TabControl SelectedIndex="{Binding SelectedIndex}">
    ...
</TabControl>

MISE À JOUR >>>

La définition d'un onglet de démarrage est encore plus simple en utilisant cette méthode:

En vue modèle:

private int selectedIndex = 2; // Set the field to whichever tab you want to start on

public int SelectedIndex { get; set; } // Implement INotifyPropertyChanged here
42
Sheridan

L'exemple de code ci-dessous créera un onglet dynamique à l'aide de MVVM.

XAML

<TabControl Margin="20" x:Name="tabCategory"
                ItemsSource="{Binding tabCategory}"
                SelectedItem="{Binding SelectedCategory}">

    <TabControl.ItemTemplate>
        <DataTemplate>
            <HeaderedContentControl Header="{Binding TabHeader}"/>
        </DataTemplate>
    </TabControl.ItemTemplate>

    <TabControl.ContentTemplate>
        <DataTemplate>
            <ContentControl Content="{Binding TabContent}" />
        </DataTemplate>
    </TabControl.ContentTemplate>
</TabControl>

Classe modale

TabCategoryItem représente chaque élément de l'onglet. Sur deux propriétés, TabHeader affichera une légende de tabulation et TabContent contient le contenu/contrôle pour remplir chaque onglet.

Public Class TabCategoryItem

    Public Property TabHeader As String
    Public Property TabContent As UIElement
End Class

Classe VM

Public Class vmClass

    Public Property tabCategory As ObjectModel.ObservableCollection(Of TabCategoryItem)
    Public Property SelectedCategory As TabCategoryItem
End Class

Le code ci-dessous remplira et liera le contenu. Je crée deux onglets, tab1 et tab2. Les deux onglets contiendront des zones de texte. Vous pouvez utiliser n'importe quel élément UI au lieu de zones de texte.

Dim vm As New vmClass

vm.tabCategory = New ObjectModel.ObservableCollection(Of TabCategoryItem)

'VM.tabCategory colection will create all tabs

vm.tabCategory.Add(New TabCategoryItem() With {.TabHeader = "Tab1", .TabContent = new TextBlock().Text = "My first Tab control1"})
vm.tabCategory.Add(New TabCategoryItem() With {.TabHeader = "Tab2", .TabContent = new TextBlock().Text = "My first Tab control2"})

mywindow.DataContent = vm
10
Rosh

Juste pour info, je suis passé par le même problème où j'ajoute des onglets dynamiquement en utilisant la source ObservableCollection mais le dernier onglet ajouté n'est pas sélectionné. J'ai fait les mêmes changements que Sheridan a dit de sélectionner Tab selon SelectedIndex. Maintenant, le dernier onglet ajouté est sélectionné mais il ne se focalisait pas. Donc, pour concentrer l'onglet, nous devons ajouter la propriété Set Binding IsAsync True.

<TabControl ItemsSource="{Binding Workspaces}" Margin="5" SelectedIndex="{Binding TabIndex, Mode=OneWay,UpdateSourceTrigger=PropertyChanged, IsAsync=True}">
9
Sudhir