65.9K
CodeProject 正在变化。 阅读更多。
Home

MVVM - 关注属性更改事件的示例应用程序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.82/5 (10投票s)

2017年6月22日

CPOL

5分钟阅读

viewsIcon

26552

downloadIcon

514

对属性更改接口的解释以及在哪里/何时实现它。

引言

关于 Model 是否应该实现属性更改接口,存在许多讨论和争论。理论上,在 Model 中实现属性更改接口 (INotifyPropertyChanged) 会违反 MVVM 模式。本文将介绍这种情况通常何时发生,并尝试解决它,以保持 MVVM 模式的有效性和可靠性。

背景

首先,简要概述 MVVM 模式。我不会在这里详细介绍,因为网上和书本上已经有很多关于它的内容。本文的重点是澄清属性更改接口,所以如果你完全不熟悉 MVVM 模式,我建议你先从基础开始,然后再继续阅读。我的信息来源是 Robert McCarter 的文章,设计模式 - Model-View-ViewModel 的问题与解决方案

Model - 简单的类对象,用于保存数据,有时包含逻辑。Model 不直接引用 View,也无法通过其他方式引用。Model 对 View 的实现方式没有依赖。从技术上讲,Model 类与封装数据访问的服务或存储库结合使用

ViewModel - ViewModel 类主要目的是将数据公开给 View。它包含表示逻辑,并且可以独立于 Model 进行测试。与 Model 类似,ViewModel 从不引用 View,但它公开属性和命令来绑定 View 数据。本质上,ViewModel 在 View 和 Model 之间充当协调者

View - View 类中不应包含任何逻辑代码。View 只用于 UI 可视化行为。

挑战

MVVM 模式建议不要混合 UI 代码(表示逻辑)和数据代码(业务逻辑),目的是将这两个领域分开。标准的 MVVM 方法是只在 ViewModel 中实现属性更改接口。因此,如果在 Model 中实现了该接口,则会违反模式并使 Model 变得臃肿。

但是,如果一个 Model 类有 20 个属性需要公开给 View,ViewModel 通常会包含 20 个相同的属性,这些属性只是将调用代理到基础 Model 实例。这些代理属性通常会在设置时引发属性更改事件,以指示 View 属性已更改。因此,每个需要公开给 View 的 Model 属性都应该有一个代理属性。但是,假设一个专业的应用程序可能有多个 Model 类需要通过 ViewModel 公开给 View,这使得实现成为一场噩梦。在这种情况下,许多开发人员最终会在 Model 类中添加属性更改事件。采用这种方法,Model 会变得复杂,并降低了你以后引入新的 ViewModel 特定功能的能力。

使用代码

该应用程序的想法非常简单。只有一个窗口,分为两个部分,第一部分和第二部分。第一部分包含 16 个控件,其中一半是文本,其余是充当绿色和红色指示灯的圆圈。最后,“Generate”按钮会生成一些值。这些值是来自模拟服务生成的数字,范围在 -100 到 100 之间。如果生成的随机数为正数,则对应的指示灯变为绿色。相应地,如果数字为负数,指示灯变为红色。

第二部分只是一个简单的加法计算器。用户输入数字,“Sum”按钮返回结果。这里没有复杂的逻辑,只是一个求和函数。

当前示例使用了 MVVM Light Toolkit,这是一个轻量级的工具包,可以加速 MVVM 应用程序的开发。

现在,让我们看看在构建第一部分时常见的错误。许多开发人员过去常常在 Model 中实现 RaisePropertyChanged。当 View 直接绑定到 Model 时,你混合了 UI 代码和数据代码。结果如下:

// Model 
public class Section1Model : ViewModelBase
{
	private int _value1;
	private SolidColorBrush _lightIndicator1;
	// ...
		
	public int Value1
	{
		get
		{
			return _value1;
		}
		set
		{
			_value1 = value;
			RaisePropertyChanged("Value1");
		}
	}

	// ... more proxy-proterties here

	public SolidColorBrush LightIndicator1
	{
		get
		{
			return _lightIndicator1;
		}
		set
		{
			_lightIndicator1 = value;
			RaisePropertyChanged("LightIndicator1");
		}
	}

	// ... more proxy-proterties here
}

如前所述,这违反了 MVVM 模式。我知道有时这可能很有用,但在类似的情况下,我们可以找到(稍后会介绍)一种更符合 MVVM 模式的方法。下图显示了该模式的架构。

此外,我想提到另一个违背 MVVM 最佳实践的错误。这就是 SolidColorBrush 的使用。再次强调,这是一个与 UI 相关的职责(可视化行为),它应该保留在 View 中。

现在,为了跳过在 Model 中实现属性更改事件,我更倾向于在 ViewModel 中创建一个 Model 的包装器,并在 XAML 中绑定其属性。下面是一个例子,从更新后的 Model 开始:

// Model 
public class Section1Model
{
	private int _value1;
	private SolidColorBrush _lightIndicator1;
	// ...
		
	public int Value1
	{
		get
		{
			return _value1;
		}
		set
		{
			_value1 = value;
		}
	}

	// ... more proxy-proterties here

	public SolidColorBrush LightIndicator1
	{
		get
		{
			return _lightIndicator1;
		}
		set
		{
			_lightIndicator1 = value;
		}
	}

	// ... more proxy-proterties here
}

现在,ViewModel 实现属性更改事件。

// ViewModel
public class MainViewModel : ViewModelBase
{
	private Section1Model _section1Model;
	
	public Section1Model Section1Model
	{
		get
		{
			return _section1Model;
		}
		set
		{
			_section1Model = value;
			RaisePropertyChanged("Section1Model");
		}
	}
}

最后,XAML 中的绑定变为:

// View
<TextBlock Text="{Binding Path=Section1Model.Value1, Mode=OneWay, FallbackValue=value1}">/>
<Ellipse Fill="{Binding Path=Section1Model.LightIndicator1, FallbackValue=Gray}">/>

现在,为了使 Model 清除与 UI 相关的代码,我们删除 SolidColorBrush。我们可以创建自己的类型来表示 Model 中的颜色(例如,布尔值)。然后,我们可以编写自定义的 ValueConverterModel 的颜色类型转换为依赖于表示框架的颜色表示。Converter 应与绑定表达式一起使用。可以在此处找到一个示例。

// Custom ValueConverter
class BoolToColorConverter : IValueConverter
{
	public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
	{
		if (value == null)
		{
			return new SolidColorBrush(Colors.Gray);
		}

		return System.Convert.ToBoolean(value) ? new SolidColorBrush(Colors.Green) : new SolidColorBrush(Colors.Red);
	}

	public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
	{
		throw new NotImplementedException();
	}
}

布尔值充当绿色/红色指示灯(真/假)。干净的 Model 如下所示:

// Model 
public class Section1Model
{
	private int _value1;
	private bool _lightIndicator1;
	// ...
		
	public int Value1
	{
		get
		{
			return _value1;
		}
		set
		{
			_value1 = value;
		}
	}

	// ... more proxy-proterties here

	public bool LightIndicator1
	{
		get
		{
			return _lightIndicator1;
		}
		set
		{
			_lightIndicator1 = value;
		}
	}

	// ... more proxy-proterties here
}

XAML 中的绑定变为:

// View
<src:BoolToColorConverter x:Key="boolToColorConverter"/> // adding the resource
<Ellipse Fill="{Binding Path=Section1Model.LightIndicator1, Converter={StaticResource boolToColorConverter}, FallbackValue=Gray}">/>

现在,应用程序清晰地将业务逻辑和表示逻辑与其用户界面分开了。

第二部分不一定需要创建 Model。它在此示例中的目的是仅用于与第一部分进行比较。显然,如果你需要制作一个真正的计算器,你可能需要一个 Model 类,或者一个计算所有功能的 Service。这取决于你。

结论

再次强调,在 Model 中实现属性更改事件并非总是坏事。但是,如果你需要一个干净的 MVVM 模式,就必须将它们分开。干净的 MVVM 模式的好处是:

  • 它提供了关注点分离。应用程序逻辑和 UI 之间的清晰分离将使应用程序更易于测试、维护和演进。
  • 它是 XAML 平台的自然模式。
  • 它实现了开发者-设计师工作流程。当 UI XAML 不与代码隐藏紧密耦合时,设计师很容易获得他们所需的自由度,从而发挥创意并打造出色的产品。
  • 它提高了应用程序的可测试性。
© . All rights reserved.