与嵌套 ViewModel 的双向绑定

Two-way binding with nested ViewModels

使用 Catel 3.9 和 DevExpress 15.x

我的客户要求我对应用程序进行一些 UI 更改,但我不确定是否可以轻松完成。

架构:

旧的 UI 在每个 TabItem 上都有按钮,允许客户加载、保存、显示、过滤等。这些按钮的 commands/properties 直接绑定到该选项卡的 ViewModel并且工作正常。

客户宁愿有一个顶级(在主窗口上)菜单,从该菜单中的选择会影响当前焦点所在的选项卡。

我可以将命令(使用 Messaging 或 Catel 的 InterestedIn 属性)传递给正确的 ViewModel,但我希望与顶级菜单和适当的 ViewModel 有更直接的绑定,这样我就可以 enable/disable菜单项甚至修改文本以适应打开的任何选项卡。

我主要在寻找 XAML and/or Catel 解决方案。如果您需要更多信息,请告诉我。

建议?

谢谢, 兰迪

编辑:抱歉,我没有包括额外的研究。如果您认识我,您就会知道我会花费 hours/days 寻找问题的解决方案,只有在我遇到困难时才会寻求帮助。不包括更多是我的错。

这个问题最难的部分是定义好的搜索参数。大多数建议类似于:将所有内容都放入 MainWindow ViewModel 中(对我而言)这不是一个好的设计选择,因为选项卡上显示的内容不同并且应该分开。

其他解决方案是让 MainWindow ViewModel 构造每个内部 ViewModel,然后管理它们。使用我使用的 Catel 框架,该框架会在加载 View 时自动构建 VM,并将任何必需的参数注入构造函数。请参见下文——您只需引用 View,Catel 就会将其与其 ViewModel 匹配并为您创建。不幸的是,如果不采取其他步骤,您实际上没有对创建的 VM 的引用。

MainWindow.xaml:

<dx:DXTabControl x:Name="MainTabControl" 
                         Grid.Row="1"
                         Margin="10" 
                         BorderThickness="0" 
                         SelectedIndex="{Binding SelectedTabIndex}"
                         >
            <dx:DXTabItem Header="Getting Started" IsEnabled="True">
                <views:GetStarted />
            </dx:DXTabItem>
            <dx:DXTabItem Header="Validate Student Records" Background="Ivory">
                <views:StudentValidation />
            </dx:DXTabItem>
            <dx:DXTabItem Header="Validate Teacher Records" Background="Ivory">
                <views:TeacherValidation />
            </dx:DXTabItem>
            <dx:DXTabItem Header="Validate Admin Records" Background="Ivory">
                <views:AdminRecordValidation />
            </dx:DXTabItem>
        </dx:DXTabControl>

我正在查看的一些可能的解决方案示例:

编辑 #2: 有一个关于使用服务的建议,因此不允许您添加详细的评论(并且它会在您的评论上设置一个 TIMER -- SHEEESH !),所以我会把我的回复放在这里。

考虑使用服务的这种情况:客户启动应用程序并单击选项卡 (StudentValidation)。 MainWindowViewModel(通过 属性)检测选定的选项卡并通过更新调用服务(我不确定更新了什么;可能是某种状态)。 "nested" ViewModels 被通知(通过事件)服务的变化。我假设 StudentValidationViewModel 是唯一一个实际响应事件并与服务交互的人,检索 "data".

所以,现在我们显示了 StudentValidation 选项卡,并且客户转到了应用程序的主菜单。主菜单仍然绑定到 MainWindow,每个命令都绑定到 MainWindowViewModel。该服务如何将主菜单绑定到当前所选选项卡的 ViewModel,以便 StudentValidationViewModel 处理命令?我可能遗漏了什么。

使用保存共享数据的单例模型,这样您就可以从任何地方获取实例。

服务就是解决之道。创建一个注入所有视图模型的解决方案。然后顶级vm可以更新服务,所有vm都可以通过事件响应更新。

请记住,虚拟机仅代表内存中的实时视图,因此您可以与它们进行交互。

感谢您提出的所有建议。我试着给每一个投票,但作为 "newbie" 我不能。当我浏览每一个时,我意识到我想做的就是将主菜单项的特定子集绑定到主 Window 上当前聚焦选项卡的 View/ViewModel。看起来就像更改菜单项的 DataContext 一样简单。

这是主菜单。 FileSubMenu 是我需要绑定到当前聚焦的 ViewModel 的那个。其他菜单项可以由 MainWindowViewModel 处理。

MainWindow.xaml:

<dxb:MainMenuControl Grid.Row="0" 
                     HorizontalAlignment="Left"
                     HorizontalContentAlignment="Stretch"
                     VerticalAlignment="Top"
                     BarItemDisplayMode="ContentAndGlyph"
                     >
    <dxb:BarStaticItem Content="Validator" Glyph="pack://application:,,,/LexValidator;component/Images/lex-logo.png" />
    <dxb:BarSubItem x:Name="FileSubMenu" Content="File">
        <dxb:BarButtonItem Content="{Binding LoadRecordsText}" Glyph="{dx:DXImage Image=LoadFrom_16x16.png}" Command="{Binding LoadRecordsFile}"/>
        <dxb:BarButtonItem Content="Clear Display" Glyph="{dx:DXImageOffice2013 Image=Clear_16x16.png}" Command="{Binding ClearDisplay}"/>
        <dxb:BarButtonItem Content="FTE Counts..." Glyph="{dx:DXImage Image=TextBox_16x16.png}" Command="{Binding ShowFTECounts}"/>
        <dxb:BarButtonItem Content="Show Pay Grid..." Glyph="{dx:DXImage Image=Financial_16x16.png}" Command="{Binding ShowPayGrid}"/>
        <dxb:BarItemLinkSeparator />
        <dxb:BarCheckItem Content="Show Ignored Issues" Glyph="{dx:DXImage Image=ClearFilter_16x16.png}" IsChecked="{Binding ShowIgnoredIssues}" IsEnabled="{Binding IsShowIgnoredIssuesEnabled}" />
    </dxb:BarSubItem>
    <dxb:BarSubItem Content="Exit">
        <dxb:BarButtonItem Content="Exit" Glyph="{dx:DXImage Image=Close_16x16.png}" Command="{Binding ExitApplication}"/>
    </dxb:BarSubItem>
    <dxb:BarSubItem Content="Help">
        <dxb:BarButtonItem Content="About..." Glyph="{dx:DXImageGrayscale Image=Index_16x16.png}" Command="{Binding ShowAboutBox}"/>
    </dxb:BarSubItem>
</dxb:MainMenuControl>

然后在 TabControl 上,我在选择新选项卡时处理事件:

MainWindow.xaml.cs

private void MainTabControl_SelectionChanged(object sender, TabControlSelectionChangedEventArgs e)
{
    // Turn it off and see if it needs to be enabled.
    this.FileSubMenu.IsEnabled = false;

    var newTabItem = e.NewSelectedItem as DXTabItem;
    if (newTabItem != null)
    {
        var tabView = newTabItem.Content as UserControl;
        if (tabView != null)
        {
            var tabViewModel = tabView.ViewModel;
            if (tabViewModel != null)
            {
                this.FileSubMenu.DataContext = tabViewModel;
                this.FileSubMenu.IsEnabled = true;
            }
        }
    }
}

我意识到这可能不是很 "MVVM",但它运作良好并且 "Boss" 表示 "Move on to something else"。如果上面的代码可以完全在 XAML 中处理,我会更高兴——也许是某种资源?

同样,如果有什么我遗漏的或更好的(更多 MVVM)解决方案,请告诉我。