【问题标题】:WPF mvvm navigation another wayWPF mvvm 导航另一种方式
【发布时间】:2017-10-28 20:20:38
【问题描述】:

我不确定如何使用 mvvm 进行导航。我是一个初学者,所以我没有使用任何像 mvvm light 这样的框架。

我找到了很好的例子https://rachel53461.wordpress.com/2011/12/18/navigation-with-mvvm-2/。但这并不是我想要的,因为在我的应用程序中,每个视图都将覆盖所有窗口。因此,当我更改页面时,我将无法从主视图访问控件。

所以我决定制作一个 MainViewModel 来更改 ViewModel(如 Rachel 博客中所述),但每个 ViewModel 都应该知道 MainViewModel 以执行更改视图。因此,当我创建 PageViewModel 时,我将使用公共方法(例如 changeview())传入构造函数 MainViewModel。

这是一个好方法吗?或者,也许有更好的方法来实现这一点?

【问题讨论】:

    标签: c# wpf mvvm


    【解决方案1】:

    子视图模型不应该知道主视图模型。

    相反,他们应该引发诸如 Forward 或 Back 等名称的事件。 ChangeView 是您提供的唯一示例,因此我们将继续使用它。

    我们将让子视图模型公开导致引发事件的命令。子视图的 XAML 中的按钮或 MenuItem 可以绑定到命令以让用户调用它们。您也可以通过 Click 事件处理程序在后面的子视图代码中调用 viewmodel 方法来做到这一点,但是命令更“正确”,因为以在 viewmodel 中进行更多工作为代价,它们使视图创建者的生活变得更加简单.

    主视图模型处理这些事件并相应地更改活动页面视图模型。因此,子进程不会调用 _mainVM.ChangeView(),而是会引发自己的 ChangeView 事件,并且子进程上的主 VM 处理该事件的处理程序会调用自己的方法 this.ChangeView()。主虚拟机是组织者虚拟机,所以它拥有导航。

    让代码尽可能不知道它的使用方式和使用地点是一个很好的规则。这适用于控件和视图模型。想象一下,如果 ListBox 类要求父类是某个特定的类;这将是令人沮丧的,也是不必要的。事件帮助我们编写有用的子类,不需要知道或关心哪个父类使用它们。即使不可能重用,这种方法也可以帮助您编写干净、分离良好的类,这些类易于编写和维护。

    如果您在细节方面需要帮助,请提供更多代码,我们可以将这个设计应用到您的项目中。


    示例

    public class MainViewModel : ViewModelBase
    {
        public MainViewModel()
        {
            FooViewModel = new FooViewModel();
    
            FooViewModel.Back += (object sender, EventArgs e) => Back();
        }
    
        public FooViewModel FooViewModel { get; private set; }
    
        public void Back()
        {
            //  Change selected page property
        }
    }
    
    public class FooViewModel : ViewModelBase
    {
        public event EventHandler Back;
    
        private ICommand _backCommand;
        public ICommand BackCommand {
            get {
                if (_backCommand == null)
                {
                    //  It has to give us a parameter, but we don't have to use it. 
                    _backCommand = new DelegateCommand(parameter => OnBack());
                }
                return _backCommand;
            }
        }
    
        //  C#7 version
        public void OnBack() => Back?.Invoke(this, EventArgs.Empty);
    
        //  C# <= 5
        //protected void OnBack()
        //{
        //    var handler = Back;
        //    if (handler != null)
        //    {
        //        handler(this, EventArgs.Empty);
        //    }
        //}
    }
    
    //  I don't know if you already have a DelegateCommand or RelayCommand class. 
    //  Whatever you call it, if you don't have it, here's a quick and dirty one.
    public class DelegateCommand : ICommand
    {
        public DelegateCommand(Action<object> exec, Func<object, bool> canExec = null)
        {
            _exec = exec;
            _canExec = canExec;
        }
    
        Action<object> _exec;
        Func<object, bool> _canExec;
    
        public event EventHandler CanExecuteChanged;
    
        public bool CanExecute(object parameter)
        {
            return _canExec == null || _canExec(parameter);
        }
    
        public void Execute(object parameter)
        {
            if (_exec != null)
            {
                _exec(parameter);
            }
        }
    }
    

    如何从子 XAML 调用 BackCommand

    <Button Content="Back" Command="{Binding BackCommand}" />
    

    【讨论】:

    • 感谢您的回复。好像没问题。但我不知道如何通过事件来实现。你能给我看一个简单的例子吗 ChildViewModel (引发事件)和 MainViewModel (处理这个事件)。 ?
    • 应该这样吗?在 ChildViewModel 我有字段类型事件 Back 并在 MainViewModel 中向该事件添加操作?
    • 我早上在写评论,没有太多时间,所以我没有写任何代码。在这种情况下,我不确定如何使用事件。我使用带有控件的事件,但不知道它可以以这种方式使用。所以非常感谢你的例子。我想现在一切都清楚了。
    • @GrzegorzSzydło 酷。如果您在自己的代码中遇到任何障碍,请给我一个@。如果你确实得到它的工作,你可以通过检查它的复选标记将答案标记为正确。
    猜你喜欢
    • 2023-03-07
    • 1970-01-01
    • 2011-03-09
    • 2015-06-04
    • 2016-07-29
    • 2013-11-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多