在 MVVM 中创建静态 viewModel 是正确的方法吗?

vol*_*tac -3 c# wpf mvvm

也许这是一个简单的问题,但不幸的是我找不到明确的答案。创建静态 viewModel 或在静态类中创建 ViewModel 并在不同的地方使用它是正确的方法吗?

例如,我想在我的一个屏幕上显示串行通信的数据。

为了避免复杂性,我创建了一个类似于我自己的项目的简单示例。串行通信开始,我创建了一个场景,就好像有连续的数据流一样。

以下数据绑定正确吗?如果不是,正确的做法应该是怎样的?

预先感谢您的帮助。

主窗口.xaml

<Grid>
    <StackPanel>
        <Button Command="{Binding ButtonCommandEvent}" Content="Click"/>
        <TextBox Text="{Binding Counter}"/>
    </StackPanel>
</Grid>
Run Code Online (Sandbox Code Playgroud)

主窗口.cs

public MainWindow()
{
    InitializeComponent();
    DataContext = Globals.mainWindowViewModel;
}
Run Code Online (Sandbox Code Playgroud)

主窗口视图模型

public class MainWindowViewModel : ViewModelBase
{
    private int _counter;

    public int Counter
    {
        get => _counter;
        set => SetProperty(ref _counter, value);
    }

    Communication communication = new Communication();

    public RelayCommand ButtonCommandEvent { get; set; }

    public MainWindowViewModel()
    {
        ButtonCommandEvent = new RelayCommand(ButtonEventClick);
    }

    private void ButtonEventClick(object param)
    {
        communication.Serial_Connect();
    }
}
Run Code Online (Sandbox Code Playgroud)

通讯.cs

class Communication
{
    DispatcherTimer dispatcherTimer = new DispatcherTimer();
    public void Serial_Connect()
    {
        dispatcherTimer.Interval = TimeSpan.FromSeconds(1);
        dispatcherTimer.Tick += DispatcherTimer_Tick;
        dispatcherTimer.Start();
    }

    private void DispatcherTimer_Tick(object sender, EventArgs e)
    {
        Globals.mainWindowViewModel.Counter = Globals.mainWindowViewModel.Counter + 1;
    }
}
Run Code Online (Sandbox Code Playgroud)

全局变量

public class Globals
{
    public static MainWindowViewModel mainWindowViewModel { get; } = new MainWindowViewModel();
}
Run Code Online (Sandbox Code Playgroud)

Eld*_*asp 7

您的解释不足以给出准确的答案。
\n为此,我们需要知道为什么需要对 ViewModel 实例的全局引用。

\n

因此,我将描述几个选项。

\n

1)如果:

\n
    \n
  • 一般来说,原则上,在任何情况下都不会假设 ViewModel 在创建它的程序集级别可以有多个实例;
  • \n
  • 如果这不会造成任何安全问题,因为每个人都可以访问静态实例;
  • \n
  • 如果静态值足以创建单个实例。在大多数情况下,这意味着 ViewModel 只有一个非参数化构造函数。
  • \n
\n

那么在这种情况下就值得使用 Singleton。

\n

例子:

\n
public class MainWindowViewModel : ViewModelBase\n{\n    // The only instance available outside of this class.\n    public static MainWindowViewModel Instanse { get; }\n        = new MainWindowViewModel();\n\n    // All constructors must be MANDATORY HIDDEN.\n    private MainWindowViewModel()\n    {\n        // Some code\n    }\n\n    // Some code\n}\n
Run Code Online (Sandbox Code Playgroud)\n

要在 XAML 中获取此实例,请使用 x: Static。
\n您可以获取整个实例,或创建到单独属性的绑定。

\n
<SomeElement\n    DataContext="{x:Static vm:MainWindowViewModel.Instance}"/>\n<SomeElement\n    Command="{Binding ButtonCommandEvent,\n                      Source={x:Static vm:MainWindowViewModel.Instance}}"/>\n
Run Code Online (Sandbox Code Playgroud)\n

2)如果:

\n
    \n
  • ViewModel 在另一个程序集中,它有开放的构造函数,但当前程序集只需要它的一个实例;
  • \n
  • 如果这不会造成任何安全问题,因为每个人都可以访问静态实例;
  • \n
  • 如果静态值 \xe2\x80\x8b\xe2\x80\x8bar 足以创建单个实例。在大多数情况下,这意味着 ViewModel 只有一个非参数化构造函数。
  • \n
\n

在这种情况下,您应该使用具有单个实例的静态类。
\n此静态类是在当前程序集中创建的。
\n通常它是一个 View 项目。
\n您的“全局类”是此类实现的一个示例。

\n

XAML 中的用法示例:

\n
<SomeElement\n    DataContext="{x:Static local:Globals.MainWindowViewModel}"/>\n<SomeElement\n    Command="{Binding ButtonCommandEvent,\n                      Source={x:Static local:Clobals.MainWindowViewModel}}"/>\n
Run Code Online (Sandbox Code Playgroud)\n

3)如果:

\n
    \n
  • ViewModel的实例可以互相替换,但一次只能使用一个实例;
  • \n
  • 如果这不会造成任何安全问题,因为每个人都可以访问静态实例。
  • \n
\n

在这种情况下,值得将静态类与 ViewModel 的当前实例一起使用,并提供此实例替换的通知。

\n

这种实现的一个例子:

\n
public static class Globals\n{\n    private static MainWindowViewModel _mainWindowViewModel = new MainWindowViewModel();\n\n    public static MainWindowViewModel MainWindowViewModel\n    {\n        get => _mainWindowViewModel;\n        set => Set(ref _mainWindowViewModel, value);\n    }\n    public static event EventHandler<PropertyChangedEventArgs> StaticPropertyChanged;\n    private static void RaisePropertyChanged([CallerMemberName] string propertyName = null)\n    {\n        StaticPropertyChanged?.Invoke(null, new PropertyChangedEventArgs(propertyName));\n    }\n\n    private static void Set<T>(ref T propertyFiled, in T newValue, [CallerMemberName] in string propertyName = null)\n    {\n        if (!Equals(propertyFiled, newValue))\n        {\n            propertyFiled = newValue;\n            RaisePropertyChanged(propertyName);\n        }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

XAML 中的用法示例:

\n
<SomeElement\n    DataContext="{Binding Path=(local:Globals.MainWindowViewModel)}"/>\n<SomeElement\n    Command="{Binding Path=(local:Globals.MainWindowViewModel).ButtonCommandEvent}"/>\n
Run Code Online (Sandbox Code Playgroud)\n

4) 如果仅需要应用程序中的所有窗口,那么最好在应用程序资源中实例化 ViewModel。

\n

这种实现的一个例子:

\n
<Application x:Class="***.App"\n             ---------------------\n             --------------------->\n    <Application.Resources>\n        <local:MainWindowViewModel x:Key="mainViewModel"/>\n    </Application.Resources>\n</Application>\n
Run Code Online (Sandbox Code Playgroud)\n

XAML 中的用法示例:

\n
<SomeElement\n    DataContext="{StaticResource mainViewModel}"/>\n<SomeElement\n    Command="{Binding ButtonCommandEvent,\n                      Source={StaticResource mainViewModel}}"/>\n
Run Code Online (Sandbox Code Playgroud)\n

5) 如果应用程序中的所有窗口都需要此功能,但您需要能够替换实例,或者创建实例时您需要一个带有应用程序启动后计算的参数的构造函数,那么最好创建一个额外的实例类将提供此实例以及所有 Windows 所需的其他数据。

\n

这种实现的一个例子:

\n
public class Locator : INotifyPropertyChanged\n{\n    private MainWindowViewModel _mainWindowViewModel = new MainWindowViewModel();\n\n    public MainWindowViewModel MainWindowViewModel\n    {\n        get => _mainWindowViewModel;\n        set => Set(ref _mainWindowViewModel, value);\n    }\n\n    #region INotifyPropertyChanged\n    public event PropertyChangedEventHandler PropertyChanged;\n    private void RaisePropertyChanged([CallerMemberName] string propertyName = null)\n    {\n        PropertyChanged?.Invoke(null, new PropertyChangedEventArgs(propertyName));\n    }\n\n    private void Set<T>(ref T propertyFiled, in T newValue, [CallerMemberName] in string propertyName = null)\n    {\n        if (!Equals(propertyFiled, newValue))\n        {\n            propertyFiled = newValue;\n            RaisePropertyChanged(propertyName);\n        }\n    }\n    #endregion\n}\n
Run Code Online (Sandbox Code Playgroud)\n
<Application x:Class="***.App"\n             ---------------------\n             --------------------->\n    <Application.Resources>\n        <local:Locator x:Key="locator">\n            <local:Locator.MainWindowViewModel>\n                <local:MainWindowViewModel/>\n            </local:Locator.MainWindowViewModel>\n        </local:Locator>\n    </Application.Resources>\n</Application>\n
Run Code Online (Sandbox Code Playgroud)\n

或者:

\n
<Application x:Class="***.App"\n             ---------------------\n             ---------------------\n             Startup="OnStartup">\n    <Application.Resources>\n        <local:Locator x:Key="locator"/>\n    </Application.Resources>\n</Application>\n
Run Code Online (Sandbox Code Playgroud)\n
public partial class App : Application\n{\n\n    private void OnStartup(object sender, StartupEventArgs e)\n    {\n        Locator locator = (Locator)Resources["locator"];\n\n        // Some Code\n\n        locator.MainWindowViewModel = new MainWindowViewModel(/* Some Parameters*/);\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

XAML 中的用法示例:

\n
<SomeElement\n    DataContext="{Binding MainWindowViewModel,\n                          Source={StaticResource locator}}"/>\n<SomeElement\n    Command="{Binding MainWindowViewModel.ButtonCommandEvent,\n                      Source={StaticResource locator}"/>\n
Run Code Online (Sandbox Code Playgroud)\n

关于“通讯”课。\n它不能直接与 UI 元素一起工作,因此不需要在其中使用“DispatcherTimer”。
\n主线程(即 DispatcherTimer 使用的)执行许多任务,并且不需要加载您自己的任务。
\n将 DispatcherTimer 替换为任何异步计时器:\nSystem.Timers.Timer、System.Threading.Timer 等。

\n