MVVM Light - 用户控件作为视图
Posted
技术标签:
【中文标题】MVVM Light - 用户控件作为视图【英文标题】:MVVM Light - User Controls as Views 【发布时间】:2014-12-03 13:55:29 【问题描述】:我决定使用 MVVM Light 库来帮助设计 UI。经过大量的研究和反复试验,我还没有找到我正在寻找的答案。我已经用谷歌搜索并阅读了我能找到的每个 *** 问题,但是,我的问题在 SO 上似乎是独一无二的。
我希望设计一个带有单个窗口的 UI,并使用不同的视图/用户控件填充它。我不希望用户控件之间共享导航栏,也不希望弹出多个窗口。每个 View/UserControl 都应该绑定到自己的 ViewModel,而 MainWindow 将绑定到 MainViewModel。
示例场景 - 带有 3 个用户控件的主窗口
1. MainWindow populates with first UserControl which has a listbox and 3 buttons, the first button is enabled.
2. User clicks the first button.
3. MainWindow populates with second UserControl.
或者,另外
2. User selects choice from a listbox, button two and three become available.
3. User clicks second/third button.
4. MainWindow populates with second/third UserControl.
等等等等
也许我的方法不现实,但我觉得这必须是可能的。我不明白如何让所有这些作品在概念上起作用。我的欲望不可能是独一无二的。如果您觉得这是重复的问题,请重定向。干杯。
为了让事情更容易理解,我在下面添加了一些类。首先,我的 App.xaml。
<Application x:Class="Bobcat_BETA.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:views="clr-namespace:Bobcat_BETA.UserControls"
xmlns:vm="clr-namespace:Bobcat_BETA.ViewModels"
StartupUri="MainWindow.xaml"
mc:Ignorable="d">
<Application.Resources>
<vm:ViewModelLocator x:Key="Locator" d:IsDataSource="True" />
<DataTemplate DataType="x:Type vm:SavedScenariosViewModel">
<views:SavedScenariosUserControl />
</DataTemplate>
<DataTemplate DataType="x:Type vm:ScenarioEditorViewModel">
<views:ScenarioEditorUserControl />
</DataTemplate>
<DataTemplate DataType="x:Type vm:SimulatorViewModel">
<views:SimulatorUserControl />
</DataTemplate>
</Application.Resources>
</Application>
MainWindow.xaml
<Window x:Class="Bobcat_BETA.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Bobcat - Version:0.00"
DataContext="Binding Main, Source=StaticResource Locator">
<Grid>
<ContentControl Content="Binding CurrentView"/>
</Grid>
ViewModelLocator.cs
namespace Bobcat_BETA.ViewModels
public class ViewModelLocator
private static MainViewModel _main;
public ViewModelLocator()
_main = new MainViewModel();
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance",
"CA1822:MarkMembersAsStatic",
Justification = "This non-static member is needed for data binding purposes.")]
public MainViewModel Main
get
return _main;
MainViewModel.cs
namespace Bobcat_BETA.ViewModels
public class MainViewModel : ViewModelBase
private ViewModelBase _currentViewModel;
readonly static SavedScenariosViewModel _savedScenarioViewModel = new SavedScenariosViewModel();
readonly static ScenarioEditorViewModel _scenarioEditorViewModel = new ScenarioEditorViewModel();
readonly static SimulatorViewModel _simulatorViewModel = new SimulatorViewModel();
public ViewModelBase CurrentViewModel
get
return _currentViewModel;
set
if (_currentViewModel == value)
return;
_currentViewModel = value;
RaisePropertyChanged("CurrentViewModel");
public MainViewModel()
CurrentViewModel = MainViewModel._savedScenarioViewModel;
SavedScenarioViewCommand = new RelayCommand(() => ExecuteSavedScenarioViewCommand());
ScenarioEditorViewCommand = new RelayCommand(() => ExecuteScenarioEidtorViewCommand());
SimulatorViewCommand = new RelayCommand(() => ExecuteSimulatorViewCommand());
public ICommand SavedScenarioViewCommand get; private set;
public ICommand ScenarioEditorViewCommand get; private set;
public ICommand SimulatorViewCommand get; private set;
private void ExecuteSavedScenarioViewCommand()
CurrentViewModel = MainViewModel._savedScenarioViewModel;
private void ExecuteScenarioEidtorViewCommand()
CurrentViewModel = MainViewModel._scenarioEditorViewModel;
private void ExecuteSimulatorViewCommand()
CurrentViewModel = MainViewModel._simulatorViewModel;
SavedScenariosViewModel.cs
namespace Bobcat_BETA.ViewModels
public class SavedScenariosViewModel : ViewModelBase
public SavedScenariosViewModel()
ObservableCollection<ScenarioModel> _scenarioModels = new ObservableCollection<ScenarioModel>()
new ScenarioModel() Name = "Scenario 0", ID = 000, Desc = "This will describe the Scenario Model.",
new ScenarioModel() Name = "Scenario 1", ID = 001, Desc = "This will describe the Scenario Model.",
new ScenarioModel() Name = "Scenario 2", ID = 002, Desc = "This will describe the Scenario Model.",
new ScenarioModel() Name = "Scenario 3", ID = 003, Desc = "This will describe the Scenario Model.",
new ScenarioModel() Name = "Scenario 4", ID = 004, Desc = "This will describe the Scenario Model.",
new ScenarioModel() Name = "Scenario 5", ID = 005, Desc = "This will describe the Scenario Model.",
new ScenarioModel() Name = "Scenario 6", ID = 006, Desc = "This will describe the Scenario Model.",
new ScenarioModel() Name = "Scenario 7", ID = 007, Desc = "This will describe the Scenario Model.",
new ScenarioModel() Name = "Scenario 8", ID = 008, Desc = "This will describe the Scenario Model.",
new ScenarioModel() Name = "Scenario 9", ID = 009, Desc = "This will describe the Scenario Model."
;
public ObservableCollection<ScenarioModel> ScenarioModels
get return _scenarioModels;
SavedScenariosUserControl.xaml
<UserControl x:Class="Bobcat_BETA.UserControls.SavedScenariosUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:vm="clr-namespace:Bobcat_BETA.ViewModels"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/Dictionaries/MasterDictionary.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<UserControl.Style>
<DynamicResource ResourceKey="GeneralUserControl"/>
</UserControl.Style>
<Grid>
<Label Content="Saved Scenario Selection"
Style="StaticResource GeneralLabel" HorizontalAlignment="Left" Margin="26,30,0,0" VerticalAlignment="Top" Height="62" Width="345"/>
<Label Content="Chose Flight Model:"
Style="StaticResource GeneralLabel2"
HorizontalAlignment="Left" Margin="27,111,0,0" VerticalAlignment="Top" Height="43" Width="345"/>
<ListBox Style="StaticResource GeneralListBox"
HorizontalAlignment="Left" Height="509" Margin="27,154,0,0" VerticalAlignment="Top" Width="345"
ItemsSource="Binding ScenarioModels"/>
<Button Content="New"
Style="StaticResource TransitionButton"
HorizontalAlignment="Left" Margin="948,601,0,0" VerticalAlignment="Top" MinHeight="62" MinWidth="150" IsEnabled="True"
Command="Binding ScenarioEditorViewCommand"/>
<Button Content="Edit"
Style="StaticResource TransitionButton"
HorizontalAlignment="Left" Margin="401,519,0,0" VerticalAlignment="Top" MinHeight="62" MinWidth="150"
Command="Binding SaveScenariosViewCommand"/>
<Button Content="Load"
Style="StaticResource TransitionButton"
HorizontalAlignment="Left" Margin="401,601,0,0" VerticalAlignment="Top" MinHeight="62" MinWidth="150"
Command="Binding SimulatorViewCommand"/>
</Grid>
</UserControl>
如果有什么不清楚的地方,我也可以添加 Model 类,但我假设您可以根据正在发生的事情做出推断。谢谢。
【问题讨论】:
【参考方案1】:所以你的方法很合理。为了获得该功能,您必须使用某些复杂的东西。我必须使用包含所有视图模型的“MainViewModel”。这些视图模型的行为使得当数据上下文切换到不同的视图模型时,相应的用户控件将更改为适当的视图。 Sheridan here 回答了我遵循的一个很好的例子。使用适当的数据模板与您的 app.xaml 挂钩,数据上下文切换将像魔术一样处理:D
在我与 Sheridan 的示例不同的地方(因为我不想创建单独的中继命令类/对象),我实际上使用 mvvm light (galasoft) 从我的视图模型发送消息以将消息发送回“MainViewModel”以切换其数据上下文。可以在here 找到使用 MVVM 轻消息传递的一个很好的示例。从“子”视图模型发送消息并将其注册到“主视图模型”中。
祝你好运!
【讨论】:
实际上我的 App.xaml 的布局与您提到的第一个示例完全相同。我没有从 BaseViewModel 派生我的 ViewModel,而是从 MVVM Light 的 ViewModelBase 派生。不过,我对 Messenger 有点迷失了。明天我会把我所有的课程都发给大家看看。 好的!您对如何从 MVVM Light 派生 ViewModelBase 有点迷失了。但是,是的,发布您的代码,我会看看。 所以我无法真正深入研究你的代码,但我从来没有让我的 ViewModelLocator 工作。所以我只是删掉了我的那部分代码。相反,我将主窗口的数据上下文设置为您设置的 mainviewmodel。然后你的主窗口中的绑定可以是你的“currentviewmodel”。现在您缺少的是消息传递系统。我使用 NuGet 来获取用于 MVVM 轻型消息传递的 galasoft 包。安装后,按照 MVVM 轻消息模式的示例进行操作。 仅供参考:该模式是使用您的数据、消息等创建一个通用的“消息”类。将您要发送的消息封装在该类中。然后,创建解析该消息的函数,然后通过设置数据上下文切换到适当的视图模型。然后,仍然在您的 mainviewmodel 中,使用您刚刚创建的方法注册以接收消息。设置好之后,您可以在 SavedScenariosViewModel 中“发送”消息,方法是将发送消息挂钩到按钮操作(或任何您尝试实现的作为切换视图的触发器)。 这篇文章启发了一个突破。谢谢Stunna,你是个stunna。我有足够的继续!【参考方案2】:你不能使用接口来举例 IChildLayout 吗? 每个 ViewModel 都继承了这个接口...
public interface IChildLayout:INotifyPropertyChanged
public MainWindows_ViewModel Parent;
在你的 MainWindows ViewModel 中你可以有这样的东西......
一个属性 IChildLayout,当你点击你的按钮时它会改变...
public class MainWindows_ViewModel:INotifyPropertyChanged
public MainWindows_ViewModel()
//Here i set the default ViewModel
this.Child=new First_ViewModel()Parent=this;
private IChildLayout _child;
public IChildLayout Child
get
return _child;
set
_child=value;
_child.Parent=this;
NotifyPropertyChanged("Child");
#region INotifyPropertyChangedMembers...
对于每个布局,您都可以检索父窗口 ViewModel(通过编辑其自己的 ViewModel 的“Child”属性来切换布局很重要......)
您在主窗口中(在 xaml 中)放置一个 UserControl,内容绑定到您的 Child 属性,然后每次更改 Child 属性时都会刷新。
<Windows>
<Windows.DataContext>
<local:MainWindows_ViewModel/>
</Windows.DataContext>
<UserControl Content=Binding Child>
<UserControl.Resources>
<DataTemplate DataType="x:Type ViewModels:First_ViewModel">
<Controls:First_View DataContext="Binding"/>
</DataTemplate>
<DataTemplate DataType="x:Type ViewModels:Second_ViewModel">
<Controls:Second_View DataContext="Binding" />
</DataTemplate>
</UserControl.Resources>
</UserControl>
</Windows>
在这种情况下,您的 First_ViewModel 可以是:(在此示例中,我使用 prism DelegateCommand 来绑定按钮操作...
public class First_ViewModel:IChildLayout
public MainWindows_ViewModel Parent get;set;
public ICommand cmdBtn1clickget;set;
private Pass_to_second_ViewModel()
//Here i change the Parent Child Property, it will switch to Second_View.xaml...
this.Parent.Child=new Second_ViewModel();
public First_ViewModel()
// Here i connect the button to the command with Prism...
this.cmdBtn1click=new DelegateCommand(()=>Pass_to_second_ViewModel());
#region INotifyPropertyChangedMembers...
我希望这会对你有所帮助,我做了这样的事情来管理 WPF 应用程序中的不同选项卡。
【讨论】:
这看起来合乎逻辑,所以我会尽我最大的努力让它工作。欢迎任何其他代码示例。谢谢。以上是关于MVVM Light - 用户控件作为视图的主要内容,如果未能解决你的问题,请参考以下文章
如何从作为wpf mvvm模式中的窗口打开的视图模型中关闭用户控件?