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



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

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

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

下面的数据绑定是否正确?如果不是,正确的做法应该是什么?

提前感谢您的帮助。

MainWindow.xaml

<Grid>
<StackPanel>
<Button Command="{Binding ButtonCommandEvent}" Content="Click"/>
<TextBox Text="{Binding Counter}"/>
</StackPanel>
</Grid>

MainWindow.cs

public MainWindow()
{
InitializeComponent();
DataContext = Globals.mainWindowViewModel;
}

MainWindowViewModel

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();
}
}

Communication.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;
}
}

全局

public class Globals
{
public static MainWindowViewModel mainWindowViewModel { get; } = new MainWindowViewModel();
}

你的解释不足以给出一个确切的答案。
为此,我们需要知道为什么需要对ViewModel实例的全局引用。

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

1)如果:

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

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

的例子:

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

要在XAML中获得这个实例,使用x: Static。
您可以获取整个实例,也可以创建到单独属性的绑定。

<SomeElement
DataContext="{x:Static vm:MainWindowViewModel.Instance}"/>
<SomeElement
Command="{Binding ButtonCommandEvent,
Source={x:Static vm:MainWindowViewModel.Instance}}"/>

2)如果:

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

在这种情况下,您应该使用具有单个实例的静态类。
此静态类是在当前程序集中创建的。
通常它是一个视图项目。
你的"类global "是这种实现的一个示例。

在XAML中的示例用法:

<SomeElement
DataContext="{x:Static local:Globals.MainWindowViewModel}"/>
<SomeElement
Command="{Binding ButtonCommandEvent,
Source={x:Static local:Clobals.MainWindowViewModel}}"/>

3)如果:

  • ViewModel的实例可以相互替换,但是一次只能使用一个实例;
  • 如果这不会产生任何安全问题,因为静态实例可以被任何人访问。

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

这样一个实现的例子:

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

在XAML中的示例用法:

<SomeElement
DataContext="{Binding Path=(local:Globals.MainWindowViewModel)}"/>
<SomeElement
Command="{Binding Path=(local:Globals.MainWindowViewModel).ButtonCommandEvent}"/>

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

这样一个实现的例子:

<Application x:Class="***.App"
---------------------
--------------------->
<Application.Resources>
<local:MainWindowViewModel x:Key="mainViewModel"/>
</Application.Resources>
</Application>

在XAML中的示例用法:

<SomeElement
DataContext="{StaticResource mainViewModel}"/>
<SomeElement
Command="{Binding ButtonCommandEvent,
Source={StaticResource mainViewModel}}"/>

5)如果这是应用程序中所有窗口都需要的,但是你需要替换实例的能力,或者创建一个实例,你需要一个带有参数的构造函数,这些参数是在应用程序启动后计算的,那么最好创建一个额外的类来提供这个实例和所有窗口所需的其他数据。

这样一个实现的例子:

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

或:

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

在XAML中的示例用法:

<SomeElement
DataContext="{Binding MainWindowViewModel,
Source={StaticResource locator}}"/>
<SomeElement
Command="{Binding MainWindowViewModel.ButtonCommandEvent,
Source={StaticResource locator}"/>

关于"通信";类。它不直接与UI元素一起工作,所以不需要使用dispatchertimer。在里面。主线程(即DispatcherTimer使用的主线程)执行它的许多任务,不需要不必要地用你自己的任务来加载它。
用任意异步定时器替换DispatcherTimer:System.Timers。计时器,System.Threading。计时器等。

你必须在Communication类中有一个事件,并从MainWindowViewModel订阅该事件构造函数。然后调用DispatcherTimer_Tick中的事件方法。

或者简单地使用Prism的PubSubEvent

通信类-

class Communication
{
public event EventHandler OnTickEvent;
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)
{
OnTickEvent?.Invoke(this, new EventArgs());
}
}

MainWindowViewModel类-

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);
communication.OnTickEvent += Communication_OnTickEvent;
}
private void Communication_OnTickEvent(object sender, EventArgs e)
{
Counter += 1;
}
private void ButtonEventClick(object param)
{
communication.Serial_Connect();
}
}

相关内容

最新更新