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模式中的窗口打开的视图模型中关闭用户控件?

WPF MVVM 将用户控件绑定到主窗口视图模型

在 WPF MVVM 中添加多个用户控件的最佳控件?

遵循MVVM模式,如何创建“设置”功能,在其他用户控件中设置数据绑定项值?

使用 MVVM 和视图模型通信的 WPF 窗口模式对话框

MVVM - 用户控件相互交谈的理想方式是啥