使用嵌套ViewModels进行双向绑定

时间:2015-07-31 13:54:06

标签: c# xaml mvvm devexpress catel

使用Catel 3.9和DevExpress 15.x

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

架构:

  • MainWindow具有关联的View和ViewModel。
  • MainWindow拥有一个TabControl,其中每个标签的内容都是一个单独的View / ViewModel。 MainWindow ViewModel不拥有任何嵌套的VM;它们由Catel在运行时由View自动构建。

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

客户宁愿只有一个顶级(在MainWindow上)菜单,该菜单中的选择会影响当前焦点的任何一个选项卡。

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

我正在寻找主要的XAML和/或Catel解决方案。如果您需要其他信息,请告诉我们。

建议?

感谢, 好色

修改:很抱歉,我没有进行其他研究。如果你认识我,你就会知道我会花几个小时/几天寻找问题的解决方案,只有在我被困难的时候才能寻求帮助。我不喜欢不包括更多。

关于这个问题最难的部分是定义好的搜索参数。大多数建议都类似于:只需将所有内容放入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(通过属性)检测选定的选项卡并使用更新调用服务(我不确定更新的内容;可能是某种状态)。 &#34;嵌套&#34; ViewModel将通过(通过事件)通知服务中的更改。我假设StudentValidationViewModel是唯一一个实际响应事件并与服务交互,检索&#34;数据&#34;。

因此,现在我们显示了StudentValidation选项卡,Customer将转到应用程序的主菜单。主菜单仍然连接到MainWindow,每个命令都绑定到MainWindowViewModel。 Service如何将主菜单绑定到当前所选选项卡的ViewModel,以便命令将由StudentValidationViewModel处理?我可能错过了一些东西。

3 个答案:

答案 0 :(得分:0)

使用包含共享数据的单例模型,以便从任何地方获取实例。

答案 1 :(得分:0)

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

请记住,vm只是代表内存中的实时视图,因此您可以与它们进行交互。

答案 2 :(得分:0)

感谢您提出的所有建议。我试图向每个人投票,但作为一个新手&#34;我不能。当我通过每一个工作时,我意识到我想要做的就是将主菜单项的特定子集绑定到主窗口上当前聚焦选项卡的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;
            }
        }
    }
}

我意识到这可能不是非常&#34; MVVM&#34;,但它运作良好并且&#34; Boss&#34;说&#34;继续做别的事情&#34;。如果上面的代码可以在XAML中完全处理,我会更高兴 - 某种资源可能吗?

同样,如果我遗漏了某些内容或更好的(更多MVVM)解决方案,请告知我们。