【问题标题】:C# WPF Best Practice to share a model across viewmodels跨视图模型共享模型的 C# WPF 最佳实践
【发布时间】:2020-09-19 05:28:39
【问题描述】:
  1. 我正在编写一个小型科学程序。它有很多变量作为输入。
  2. 用户必须在窗口(或用户界面)中输入所有输入变量。但是,我无法将所有输入都放在一个窗口 (XAML) 中。因此,我创建了几个视图,用户只需按下 NEXT 按钮即可在下一个视图中输入数据。
  3. 所有这些视图都有关联的视图模型。它们都继承自基础 ViewModel。
  4. 所以,我的问题是:我是否在基本 ViewModel 中编写所有变量的属性?像这样:
namespace ScienceProgram
{
    public abstract class BaseViewModel : INotifyPropertyChanged
    {

        #region Usual Boiler-plate stuff for BindableBase

        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged([CallerMemberName] string name = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }       
        #endregion

        #region Properties (All scientific parameters and calculations )

        public double ParamA { get; set; }
        public double ParamB { get; set;  }
        // ...
        // Lots of parameters //
        // ...
        public double ParamA23 { get; set;  }


        public double TotalLength()
        {
            return ParamA + ParamB + ParamA23;                
        }

        // ...
        // Lots of other methods 
        // ...

        #endregion 

    }
}
  1. 或者我是否为所有输入参数创建一个单独的类(例如 ScienceParameters.cs)并执行以下操作(这就是我正在做的):
namespace ScienceProgram
{
    public abstract class BaseViewModel : INotifyPropertyChanged
    {

        // Global Parameter // Shared across ViewModels //
        public static ScienceParameters scienceParameters = new ScienceParameters ();

        #region Usual Boiler-plate stuff for BindableBase

        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged([CallerMemberName] string name = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }       
        #endregion

    }
}
  1. 我使用的是后一种方法。当用户在文本框中输入数据时,我只是将其存储为:
namespace ScienceProgram
{
    public class UserInputView1ViewModel : BaseViewModel
    {
        #region User Input 
        private double _paramA;
        public double ParamA
        {
            get { return _paramA; }
            set
            {                
                _paramA = value;                
                OnPropertyChanged("ParamA");

                // Store Value // 
                scienceParameters.ParamA = value;
            }
        }
    }
}
        #endregion
  1. 正确的方法是什么?或者,还有更好的方法?
  2. 我一直在寻找一些最佳实践,但我一直被定向到 EventAggregator 并使用单例。我不认为我需要那些。
  3. 我认为解决方案是“将模型传递给视图模型的构造函数。”正如许多人所说。但我对如何做到这一点有点困惑。这不会在每个视图模型中创建一个新实例吗?
  4. 对不起,如果这听起来像一个愚蠢的问题,但过去一周我一直在寻找一个直接的答案,但还没有找到解决方案。

提前非常感谢。

【问题讨论】:

  • 您使用的是静态科学参数。如果您确保将相同的 Viewmodel 实例传递给所有 3 个向导页面(可能是“单例”的意思),则不需要这样做。让每个对话框的 XAML 决定填写 Viewmodel 实例的哪些属性。旁注:我 不是 MVVM 专家,这是评论,而不是答案。但看起来你的 3 个向导页面实际上形成了一个输入对话框,最后只有一个提交。你也可以使用你的 Viewmodel 的一个实例?
  • 嗨@Goodies,非常感谢您的回复。对于迟到的回复,我深表歉意。我没有注意到这个评论。事实上,您建议使用使用 3 个不同向导页面的单个 viewModel 真的很棒。这就是我今天正在实施的。我每天都在学习越来越多的 MVVM 模式,我愚蠢地认为“每个视图都需要自己的视图模式”。但是,现在当我阅读您的建议时,我认为它将通过对所有向导页面使用单个视图模型来帮助我构建一个更简洁的程序。这很棒 !我从来没想过这点 !再次感谢!
  • 成功moostang..至于术语,也许您甚至可以将由多个页面组成的“向导”视为一个视图!只有 XAML 不支持,它是面向单个窗口的。 ASP.NET 也有类似的约束,它的 .cshtml 文件只定义一页 HTML。但是向导使用超过 1 页 (=window) 的唯一原因是用户清楚。从 MVVM 程序的角度来看,向导是具有单一结果的单一对话框:用户指定的所有参数都在执行中。

标签: wpf viewmodel


【解决方案1】:

你已经接近了。我看到您正在复制可能导致维护问题的数据存储的区域。根据您所说的,我认为您使用 BaseViewModel 的方法是不必要的(但如果您按照我在此处的回答,我将向您展示如何使 BaseViewModel 比您使用它的用途更有用)。

如果我理解正确,您有多个视图,因为您希望用户按“NEXT”进入下一个导航视图。每个 View 都有一个关联的 ViewModel。但在底层,您希望将所有属性收集到一个类实例中。

对我来说,这里的最佳实践是学习 MVVM 架构。来自 MVVM 的关键学习是理解“关注点分离”。主要是,您的数据(即所有用户的输入值,也就是“模型”)与您在 UI 中呈现给他们的视图和视图模型无关。阅读此内容,您将更好地掌握这一良好做法。

我会这样做。

1) 创建一个模型类 (ScienceParameters.cs),它将包含您希望用户输入的所有属性。示例:

public class ScienceParameters
{
        public double ParamA { get; set; }
        public double ParamB { get; set;  }
        // ...
        // Lots of parameters //
        // ...
        public double ParamA23 { get; set;  }


        public double TotalLength()
        {
            return ParamA + ParamB + ParamA23;                
        }  
}

然后在您呈现给用户的每个 UI 上,您将向他们展示一个视图和一个关联的 ViewModel,让他们可以访问“查看/获取”或“存储/设置”部分数据。示例:

public class UserInput1ViewModel: INotifyPropertyChanged
{
        public UserInput1ViewModel(ScienceParameters model)
        {
            this.Model = model;
        }

        public ScienceParameters Model { get; private set; }

        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged([CallerMemberName] string name = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }       

        public double ParamA
        {
            get { return this.Model.ParamA; }
            set
            {   
                // Store the user's data directly into the Model, not the ViewModel!
                this.Model.ParamA = value;                
                OnPropertyChanged(nameof(this.ParamA));  // <-- avoid magic words like "ParamA" in quotes, this is bad coding and can cause maintenance issues.
            }
        }

}
public class UserInput2ViewModel: INotifyPropertyChanged
{
        public UserInput2ViewModel(ScienceParameters model)
        {
            this.Model = model;
        }

        public ScienceParameters Model { get; private set; }

        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged([CallerMemberName] string name = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }       

        public double ParamB
        {
            get { return this.Model.ParamB; }
            set
            {   
                this.Model.ParamB = value;                
                OnPropertyChanged(nameof(this.ParamB));
            }
        }

}

额外积分:

如果你想消除在每个 ViewModel 中编写 PropertyChanged 垃圾的重复性,那么将其放入 BaseViewModel 中。

public class BaseViewModel : INotifyPropertyChanged
{
        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged([CallerMemberName] string name = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }       
}

然后从您的 ViewModel 中删除所有那些 PropertyChanged 行,而是使用基础定义每个 ViewModel:

public class UserInput1ViewModel: BaseViewModel
{
   // ...
}

【讨论】:

  • 您好 Tam,非常感谢您,感谢您的精彩回答。这正是我一直在寻找的。是的,我一直在阅读有关 MVVM 的内容,但还不够!您刚刚提升了我对 MVVM 的理解。我认为 MVVM 模式涉及不编写那些代码隐藏,以及旧 Windows 窗体的常见“button_click”内容。我一直在使用 ICommand、RelayCommand 并绑定我的操作。但是,在这里您扩展了我对 MVVM 的了解。那太棒了!我会听从你的建议,是的,我应该花更多时间学习更多!非常感谢谭​​。
  • 不编写代码隐藏(如果可能)是 MVVM 优势的一部分。是的,如果可以避免,请使用 ICommand 和 RelayCommands 以及数据绑定。我不想参与其中,因为它会混淆您想要实现的目标的信息。
  • 感谢您向我展示了如何在构造函数中传递模型。 ``` public UserInput2ViewModel(ScienceParameters model) { this.Model = model; } ```
  • 再次感谢谭。不要担心数据绑定。经过几次尝试,我对 Icommands 和 RelayCommands 有了一些基本的了解。
  • 如果您希望用户能够清除模型并重新开始,请在 UI 上向他们显示一个“清除”按钮,该按钮绑定到 BaseViewModel 中的 RelayCommand(例如,称为 ClearCommand) (这样,他们可以从您的任何视图中清除它)。在 ClearCommand 中,只需重置您的模型。您可以逐个清除每个属性,或者只设置 this.Model = new ScienceParameters(); 不要忘记在公共 ViewModel 属性上调用 PropertyChanged,否则这些字段将在 UI 中过时并且仍然显示旧的数据。
猜你喜欢
  • 1970-01-01
  • 2011-09-16
  • 2011-04-08
  • 2011-03-06
  • 2014-11-27
  • 1970-01-01
  • 2016-02-27
  • 2011-06-21
  • 1970-01-01
相关资源
最近更新 更多