MVVM 导航父视图和子视图

MVVM Navigation Parent and Child Views

我有一个 GeneralView,它是一个父视图,当打开它时,它会打开一个父视图,然后是一个子视图。我想实现导航并将按钮保留在侧面(如 UserPage)。让我们转到所需的行为,然后是我现在拥有的代码。

我实现 ChildView 的方式没有改变,保留在 HomeView aka FriendsView 中。

So description 登录 > GeneralView(在主页中立即打开)> 点击关于,childView 更改为 AboutView,在主页中点击 HomeView 再次显示。

我有:

GeneralView

<UserControl x:Class="WpfWHERE.View.GeneralView"
         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:local="clr-namespace:WpfWHERE.View"
         xmlns:ViewModel="clr-namespace:WpfWHERE.ViewModel"
         mc:Ignorable="d" 
         d:DesignHeight="600" d:DesignWidth="800">
<UserControl.DataContext>
    <ViewModel:GeneralViewModel/>
</UserControl.DataContext>
<UserControl.Resources>
    <DataTemplate DataType="{x:Type ViewModel:FriendsViewModel}">
        <local:FriendsView />
    </DataTemplate>
    <DataTemplate DataType="{x:Type ViewModel:AboutViewModel}">
        <local:AboutView />
    </DataTemplate>
</UserControl.Resources>
<DockPanel Margin="0,0,0,0">
    <StackPanel Orientation="Horizontal">
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto" MaxWidth="200"/>
            </Grid.ColumnDefinitions>
            <Image Grid.Column="1" x:Name="userImage" Source="/Resources/Images/profileImage.png" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Height="161" Width="180" />
            <Label Grid.Column="1" x:Name="labelName" Content="NameHere" HorizontalAlignment="Left" Margin="10.4,171,0,0" VerticalAlignment="Top" RenderTransformOrigin="0.536,1.344" Height="26" Width="67"/>
            <StackPanel Grid.Column="1" Margin="0.4,202,-1.2,0" VerticalAlignment="Top" Height="200">
                <MenuItem Style="{DynamicResource MyMenuItem}" Command="{Binding DataContext.SelectViewCommand, ElementName=GeneralView}" CommandParameter="FriendsViewModel" Header="Home" x:Name="Home"/>
                <MenuItem Style="{DynamicResource MyMenuItem}" Header="Overview" x:Name="Overview"/>
                <MenuItem Style="{DynamicResource MyMenuItem}" Header="Settings" x:Name="Settings"/>
                <MenuItem Style="{DynamicResource MyMenuItem}" Header="About" Command="{Binding DataContext.SelectViewCommand, ElementName=GeneralView}" CommandParameter="AboutViewModel" x:Name="About"/>
            </StackPanel>
        </Grid>
        <ContentControl Content="{Binding Current_ViewModel}" Height="600" Width="600"/>
    </StackPanel>
</DockPanel>

GeneralViewModel

 class GeneralViewModel:AViewModel
{
    public GeneralViewModel()
    {
        this.AddViewModel(new FriendsViewModel() { DisplayName = "Friends", InternalName = "FriendsViewModel" });
        this.AddViewModel(new AboutViewModel() { DisplayName = "About", InternalName = "AboutViewModel" });
        this.Current_ViewModel = this.GetViewModel("FriendsViewModel");
    }
}

AViewModel 接口

 public abstract class AViewModel : ViewModelBase
{
    public string Name { get; set; }
    public RelayCommand<string> SelectViewCommand { get; set; }

    public AViewModel()
    {
        SelectViewCommand = new RelayCommand<string>(OnSelectViewCommand);
    }

    private static ObservableCollection<ViewModelBase> _ViewModels;
    public static ObservableCollection<ViewModelBase> ViewModels
    {
        get { return _ViewModels; }
        set { _ViewModels = value; }
    }

    public void AddViewModel(ViewModelBase viewmodel)
    {
        if (ViewModels == null)
            ViewModels = new ObservableCollection<ViewModelBase>();

        if (!ViewModels.Contains(viewmodel))
            ViewModels.Add(viewmodel);
    }

    public ViewModelBase GetViewModel(string viewmodel)
    {
        return ViewModels.FirstOrDefault(item => item.InternalName == viewmodel);
    }

    private void OnSelectViewCommand(string obj)
    {
        switch (obj)
        {
            case "ExitCommand":
                Application.Current.Shutdown();
                break;
            default:
                this.Current_ViewModel = this.GetViewModel(obj);
                break;
        }
    }

    private ViewModelBase _Current_ViewModel;
    public ViewModelBase Current_ViewModel
    {
        get { return _Current_ViewModel; }
        set { _Current_ViewModel = value; OnPropertyChanged("Current_ViewModel"); }
    }
}

试试这个....

改变这个...

        <StackPanel Grid.Column="1" Margin="0.4,202,-1.2,0" VerticalAlignment="Top" Height="200">
            <MenuItem Style="{DynamicResource MyMenuItem}" Command="{Binding DataContext.SelectViewCommand, ElementName=GeneralView}" CommandParameter="FriendsViewModel" Header="Home" x:Name="Home"/>
            <MenuItem Style="{DynamicResource MyMenuItem}" Header="Overview" x:Name="Overview"/>
            <MenuItem Style="{DynamicResource MyMenuItem}" Header="Settings" x:Name="Settings"/>
            <MenuItem Style="{DynamicResource MyMenuItem}" Header="About" Command="{Binding DataContext.SelectViewCommand, ElementName=GeneralView}" CommandParameter="AboutViewModel" x:Name="About"/>
        </StackPanel>

为此...

        <StackPanel Grid.Column="1" Margin="0.4,202,-1.2,0" VerticalAlignment="Top" Height="200">
            <MenuItem Style="{DynamicResource MyMenuItem}" Command="{Binding SelectViewCommand}" CommandParameter="FriendsViewModel" Header="Home" x:Name="Home"/>
            <MenuItem Style="{DynamicResource MyMenuItem}" Header="Overview" x:Name="Overview"/>
            <MenuItem Style="{DynamicResource MyMenuItem}" Header="Settings" x:Name="Settings"/>
            <MenuItem Style="{DynamicResource MyMenuItem}" Header="About" Command="{Binding SelectViewCommand}" CommandParameter="AboutViewModel" x:Name="About"/>
        </StackPanel>

请注意,我已从您的 MenuItems

中删除 'DataContext' 和 'ElementName'

INotifyProperty 已在 ViewModelBase 中实现

更新 1

问题出在 ElementName=GeneralView...具有该名称的元素不存在。您可以将 x:Name=”GeneralView” 添加到 Base_View XAML 的顶部,但是没有必要,因为您的 ContentControl 已绑定到 Base_View 中的 Current_ViewModel ]模型无论如何....

当您按下按钮 'change Views' 时,您实际上是在更改您的 ContentControl 绑定到的 属性 的值,因此您必须在相同的实例中调用正确的 SelectViewCommand 函数您的 ContentControl 也绑定了 class....

在演示中你会看到 'LogOn_View' 我调用

Command="{Binding DataContext.SelectViewCommand, ElementName=Base_V}", CommandParameter="Main_ViewModel"

这里我调用了Base_V中的SelectViewCommand,那是因为我想改变显示在Base_V的ContentControl

中的视图

在Main_View我叫

Command="{Binding SelectViewCommand}", CommandParameter="MainV1_ViewModel"

这里我调用了Main_ViewModel中的SelectViewCommand,那是因为我想改变显示在ManiView的ContentControl中的View

对于任何想要我上面所说的演示代码的人,您可以在这里找到它...

http://www.mediafire.com/download/3bubiq7s6xw7i73/Navigation1.rar

此外,对代码进行了一些更新...用这个替换 AviewModel 中的 AddViewModel 函数......

    public void AddViewModel(ViewModelBase viewmodel)
    {
        if (ViewModels == null)
            ViewModels = new ObservableCollection<ViewModelBase>();

        var currentVNs = (from vms in ViewModels where vms.InternalName == viewmodel.InternalName select vms).FirstOrDefault();
        if (currentVNs == null)
            ViewModels.Add(viewmodel);
    }

好吧……再想一想……网上有很多教程代码的问题是它太基础了。 Data (Model) <=> View之间关系的实现,似乎是一个品味问题。然而,我只见过它做得不好。就个人而言,我开发的大多数 WPF 应用程序都是绝对垃圾,无法维护和无法管理的意大利面条代码....您可以清楚地看到原始开发人员已经 'Learnt' 随着他们的发展,一切都无处不在没有一致性....因为我通常会半途而废一个项目,虽然我别无选择,只能继续它开始的方式,所以我从来没有考虑过,直到现在!!.

我自己的理解是 'Model' 定义数据结构,'View' 向用户显示该数据,而 ViewModel 是将数据(模型)转换为中间的位可以显示给用户的东西。

在 ViewModel 中,您将只拥有 ObservableCollections(数据列表(模型))和单个数据实例(模型的单个实例)。 'View' 然后绑定到 ObservableCollections(和单个模型实例)以显示该数据(使用 XAML 和模板等的魔力)......

至于将对象 (Class) 传递给 ViewModel,我认为您实际上不需要这样做。您只需在 ViewModel 中创建一个 属性 来表示您要显示的数据,然后在需要时从 'Source' 中检索数据(通常是在显示视图时,但也可以是周期性的)一个 timer\thread 之类的)...

我的演示代码(下载)的主要问题是 ViewModels 的构造函数在应用程序启动后从未被调用,因此无法从数据源刷新 ViewModel 中的属性。 ...

可能有更好的方法来做到这一点,但我已经通过在 'ViewModelBase'

中引入一个名为 Initialize 的事件解决了这个问题
    public delegate void MyEventHadler();
    public event MyEventHadler Initialize;

    public  void InitializeFunction()
    {
        if (Initialize != null)
            Initialize.Invoke();
    }

然后可以在每个 ViewModel 的构造函数中订阅该事件

    public MainV2_ViewModel()
    {
        this.Initialize += MainV2_ViewModel_Initialize; // our new Event
    }

以及当我们从其他地方导航到此 ViewModel\View 时调用的事件存根....

    private void MainV2_ViewModel_Initialize()
    {
        // So here we are retrieving a List of All users from the WCF Service
        this.AllUsers = new ObservableCollection<ServiceReference1.User>( DataAccessLevel.sr1.GetAllUsers());
        // Now our AllUsers property has been updated and the View will display the new data
    }

现在,当您从一个 ViewModel\View 切换到另一个时,初始化事件是 called\raised in AviewModel\Current_ViewModel 属性 Setter

    private ViewModelBase _Current_ViewModel;
    public ViewModelBase Current_ViewModel
    {
        get { return _Current_ViewModel; }
        set {
            _Current_ViewModel = value;
            // the Constructor of the ViewModel never gets called more that once on App Start 
            // so we have to implement/raise our own event when changing from one View to another.
            if (Current_ViewModel != null)
                Current_ViewModel.InitializeFunction(); // InitializeFunction will fire the event in this ViewModel, we can now initialise the properties.
            OnPropertyChanged("Current_ViewModel"); }
    }

这将在正在切换的 ViewModel 中触发 'Initialize' 事件,让我们有机会刷新数据......

演示代码现在是一个功能齐全的应用程序(当然还需要改进)。它提供以下功能...

注册新用户 :: 使用用户名和密码创建新用户

登录用户 :: 使用用户名和密码登录现有用户

恢复用户(忘记密码):: 重置注册用户的密码(使用现有用户名和新密码)

此外,当出现错误(密码不正确等)时,错误消息会从 WCF 服务返回,然后显示在错误视图中(参见 LogOnError_ViewModel 和 LogOnError_View)

在这里找到演示代码....

http://www.mediafire.com/download/881yo6reo55tm8l/Navigation1_WCF_EF_05052016.rar

Demo Code自带一个WCF服务和一个WPF应用程序,因为它使用了一个WCF服务所以它只能是运行来自Visual StudioIDE(除非你部署IIS 的 WCF 服务)。当您首次注册新用户时,WCF 服务使用 Entity Framework 和(应该)create\attach 数据库来存储用户数据...

也许您可以改进代码或获得一些想法......