WPF 选项卡控件和 MVVM 选择

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/19857184/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 23:59:50  来源:igfitidea点击:

WPF tab control and MVVM selection

wpfmvvmprismtabcontrol

提问by NZJames

I have a TabControl in an MVVMWPFapplication. It is defined as follows.

我在MVVM WPF应用程序中有一个 TabControl 。它的定义如下。

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

So each tab item content has its own view associated with it. Each of those views has the MEF[Export]attribute and is associated with the relevant region through view discovery, so the above code is all I need to have the tab control load and switch between them. They all reference the same shared ViewModel object behind them and so all interact seamlessly.

因此,每个选项卡项内容都有自己的关联视图。这些视图中的每一个都具有MEF[Export]属性,并通过视图发现与相关区域相关联,因此上面的代码是我需要加载选项卡控件并在它们之间切换的全部代码。它们都引用了它们背后的相同共享 ViewModel 对象,因此都可以无缝交互。

My problem is that when the user navigates to the parent window, I want the tab control to default to the second tab item. That is easy enough to do when the window is first loaded, by specifying in XAML IsSelected="True"in TabItem number 2. It is less easy to do when the user navigates away from the screen and then comes back to it.

我的问题是,当用户导航到父窗口时,我希望选项卡控件默认为第二个选项卡项。通过在IsSelected="True"第 2 号 TabItem 中指定 XAML,在首次加载窗口时很容易做到这一点。当用户离开屏幕然后又回到屏幕时,这样做就不那么容易了。

I thought about having a SelectedItem={Binding SelectedTabItem}property on the tab control, so I could programmatically set the selected tab in the ViewModel, but the problem is I have no knowledge of the TabItem objects in the ViewModel as they are declared above in the XAML only, so I have no TabItem object to pass to the setter property.

我想过SelectedItem={Binding SelectedTabItem}在选项卡控件上有一个属性,所以我可以在 ViewModel 中以编程方式设置选定的选项卡,但问题是我不知道 ViewModel 中的 TabItem 对象,因为它们仅在 XAML 中声明,所以我没有要传递给 setter 属性的 TabItem 对象。

One idea I had was to make the child Views (that form the content of each of the tab items above) have a style on the UserControl level of their XAML, something along the following.

我的一个想法是让子视图(形成上面每个选项卡项的内容)在其 XAML 的 UserControl 级别上具有样式,如下所示。

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

I know the findancestor bit isn't correct; I've just put it there to specify my intent, but I am not sure of the exact syntax. Basically for each UserControl to have a trigger that listens to a property on the ViewModel (not sure how I would distinguish each different UserControl as obviously they can't all listen to the same property or they would all select simultaneously when the property is set to True, but having a property for each usercontrol seems ugly) and then finds its parent TabItem container and sets the IsSelected value to true.

我知道 findancestor 位不正确;我只是把它放在那里来说明我的意图,但我不确定确切的语法。基本上每个 UserControl 都有一个触发器来监听 ViewModel 上的一个属性(不知道我将如何区分每个不同的 UserControl,因为显然它们不能都监听相同的属性,或者当属性设置为时,它们都会同时选择是的,但为每个用户控件设置一个属性似乎很难看),然后找到其父 TabItem 容器并将 IsSelected 值设置为 true。

Am I on the right track with a solution here? Is it possible to do what I am pondering? Is there a tidier solution?

我在正确的轨道上有解决方案吗?有没有可能做我正在思考的事情?有更简洁的解决方案吗?

回答by Sheridan

If you look at the TabControlClasspage on MSDN, you'll find a property called SelectedIndexwhich is an int. Therefore, simply add an intproperty into your view model and Bindit to the TabControl.SelectedIndexproperty and then you can select whichever tab you like at any time from the view model:

如果您查看MSDN上的TabControlClass页面,您会发现名为SelectedIndexwhich的属性是int. 因此,只需将一个int属性添加到您的视图模型中,Bind然后将其添加到该TabControl.SelectedIndex属性中,然后您可以随时从视图模型中选择您喜欢的任何选项卡:

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


UPDATE >>>

更新 >>>

Setting a 'startup' tab is even easier using this method:

使用此方法设置“启动”选项卡更容易:

In view model:

在视图模型中:

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

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

回答by Rosh

The below code sample will create a dynamic tab using MVVM.

下面的代码示例将使用 MVVM 创建一个动态选项卡。

XAML

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>

Modal Class

模态类

TabCategoryItemrepresents each tab item. On two properties, TabHeaderwill display a tab caption and TabContentcontains the content/control to fill in each tab.

TabCategoryItem代表每个选项卡项。在两个属性上, TabHeader将显示选项卡标题,而TabContent包含要填充每个选项卡的内容/控件。

Public Class TabCategoryItem

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

VM Class

虚拟机类

Public Class vmClass

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

The below code will fill and bind the content. I am creating two tabs, tab1 and tab2. Both tabs will contain text boxes. You can use any UIelement instead of text boxes.

下面的代码将填充和绑定内容。我正在创建两个选项卡,tab1 和 tab2。两个选项卡都将包含文本框。您可以使用任何 UIelement 而不是文本框。

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

回答by Sudhir

Just FYI, I gone through the same issue where I add tabs dynamically using ObservableCollection source but last added Tab do not get selected. I have done same changes what Sheridan said to select Tab as per SelectedIndex. Now last added Tab gets selected but it was not getting focused. So to focus the Tab we have to add set Binding IsAsync property True.

仅供参考,我遇到了同样的问题,我使用 ObservableCollection 源动态添加选项卡,但最后添加的选项卡没有被选中。我对 Sheridan 所说的根据 SelectedIndex 选择 Tab 所做的更改进行了相同的更改。现在最后添加的 Tab 被选中,但它没有得到关注。因此,要聚焦 Tab,我们必须添加 set Binding IsAsync 属性 True。

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

回答by Pat M

The accepted answer is not working with DependencyObject on your ViewModel .

接受的答案不适用于您的 ViewModel 上的 DependencyObject 。

I'm using MVVM with DependencyObject and Just setting the TabControl didn't work for me.The problem I had was the the property was not getting update on the View when I was setting the tab selectedIndex from the ViewModel.

我正在使用带有 DependencyObject 的 MVVM,只是设置 TabControl 对我不起作用。我遇到的问题是当我从 ViewModel 设置选项卡 selectedIndex 时,该属性没有在视图上更新。

I did set the Mode to be two ways but nothing was working.

我确实将模式设置为两种方式,但没有任何效果。

<TabControl  SelectedIndex="{Binding SelectedTab,Mode=TwoWay}" >
    ...
</TabControl>

The ViewModel property "SelectedTab" was getting updated all the time when I navigated between tabs. This was confirming my binding was working properly. Each time I would navigate the tabs both the Get and Set would get called in my ViewModel. But if I try to set the SelectedIndex in the ViewModel it would not update the view. ie: SelectedTab=0 or SelectedTab=1 etc... When doing the set from the ViewModel the SelectedTab 'set' method would be called, but the view would never do the 'get'.

当我在选项卡之间导航时,ViewModel 属性“SelectedTab”一直在更新。这证实了我的绑定工作正常。每次我导航选项卡时,都会在我的 ViewModel 中调用 Get 和 Set。但是,如果我尝试在 ViewModel 中设置 SelectedIndex,它不会更新视图。即: SelectedTab=0 或 SelectedTab=1 等等...当从 ViewModel 中进行设置时,将调用 SelectedTab 'set' 方法,但视图永远不会执行 'get'。

All I could find online was example using INotifyPropertyChanged but I do not wish to use that with my ViewModel.

我在网上能找到的只是使用 INotifyPropertyChanged 的​​例子,但我不想在我的 ViewModel 中使用它。

I found the solutions in this page: http://blog.lexique-du-net.com/index.php?post/2010/02/24/DependencyProperties-or-INotifyPropertyChanged

我在这个页面找到了解决方案:http: //blog.lexique-du-net.com/index.php?post/2010/02/24/DependencyProperties-or-INotifyPropertyChanged

With DependencyObject, you need to register the DependencyProperties. Not for all properties but I guess for a tabcontrol property you need to.

使用 DependencyObject,您需要注册 DependencyProperties。不适用于所有属性,但我想对于您需要的 tabcontrol 属性。

Below my code:

在我的代码下面:

view.xaml

查看.xaml

//Not sure below if I need to mention the TwoWay mode
<TabControl  SelectedIndex="{Binding SelectedTab,Mode=TwoWay}" >
        ...
</TabControl>

ViewModel.cs

视图模型.cs

public class ViewModel : DependencyObject
{
       public static readonly DependencyProperty SelectedTabDP =  DependencyProperty.Register("SelectedTab", typeof(int), typeof(ViewModel));

       public int SelectedTab
       {
          get { return (int)GetValue(SelectedTabDP); }
          set { SetValue(SelectedTabDP, value); }
       }
}

Basically all I had to do was to actually register the dependency property (DependencyProperty) as you can see above.

基本上我所要做的就是实际注册依赖属性 (DependencyProperty),如上所示。

What made this hard to figure out was that I have a bunch of other Properties on that view and I didn't need to register them like that to make it work two ways. For some reason on the TabControl I had to register the property like I did above.

很难弄清楚的是,我在该视图上有很多其他属性,我不需要像这样注册它们以使其以两种方式工作。出于某种原因,在 TabControl 上,我必须像上面那样注册该属性。

Hope this help someone else.

希望这对其他人有帮助。

Turns out my problem were because my components have names:

原来我的问题是因为我的组件有名称:

x:Name="xxxxxxxx"

Giving names to components at the same time of biding them with DependencyObject seems to be the main cause of all my issues.

在使用 DependencyObject 投标的同时为组件命名似乎是我所有问题的主要原因。