与嵌套 ViewModel 的双向绑定
Two-way binding with nested ViewModels
使用 Catel 3.9 和 DevExpress 15.x
我的客户要求我对应用程序进行一些 UI 更改,但我不确定是否可以轻松完成。
架构:
- 有一个带有关联视图和视图模型的主窗口。
- MainWindow 包含一个 TabControl,其中每个 Tab 的内容都是一个单独的 View/ViewModel。 MainWindow ViewModel 不拥有任何嵌套的 VM;它们由 Catel 在运行时由 View 自动构建。
旧的 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>
我正在查看的一些可能的解决方案示例:
- One ViewModel Member of Another
- Get a property of a tabitem's ViewModel when it's selected
编辑 #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)解决方案,请告诉我。
使用 Catel 3.9 和 DevExpress 15.x
我的客户要求我对应用程序进行一些 UI 更改,但我不确定是否可以轻松完成。
架构:
- 有一个带有关联视图和视图模型的主窗口。
- MainWindow 包含一个 TabControl,其中每个 Tab 的内容都是一个单独的 View/ViewModel。 MainWindow ViewModel 不拥有任何嵌套的 VM;它们由 Catel 在运行时由 View 自动构建。
旧的 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>
我正在查看的一些可能的解决方案示例:
- One ViewModel Member of Another
- Get a property of a tabitem's ViewModel when it's selected
编辑 #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)解决方案,请告诉我。