在MVVM(Model-View-ViewModel)模式中,ViewModel扮演着至关重要的角色,它负责将UI逻辑与业务逻辑分离。CodeBehind与ViewModel在功能上非常相似,但ViewModel作为一个独立的类,有助于更好地分离逻辑和UI。在实现MVVM时,Visual Studio并不了解View和ViewModel之间的联系,因此不能像在XAML和CodeBehind之间那样轻松地通过按F7键进行切换。此外,如果ViewModel文件能够在解决方案资源管理器中像CodeBehind那样位于View文件下,那将非常有用。
实际上,甚至不需要CodeBehind文件!如果一开始没有CodeBehind文件,那么团队成员就不会被诱惑去使用它。如果没有CodeBehind文件,就无法编写CodeBehind代码,这是一件好事。
将CodeBehind转换为ViewModel的想法已经考虑了很长时间,并且认为找到了一个巧妙的方法来实现这一点。将CodeBehind文件转换为ViewModel,并且能够同时抓住两端!
例如,假设有一个名为FirstView的视图。原始的XAML看起来像这样:
这是有趣的一行:
x:Class="ViewModelAsCodeBehindTrick.Views.FirstView"
它使XAML的解析器创建一个名为FirstView的类,该类继承自UserControl。这个类将通过使用'partial'与CodeBehind类连接。
切换到CodeBehind,将看到以下代码,正如之前所说——实际上并不需要:
这里有FirstView类的第二部分,它连接到XAML创建的第一部分。所要做的就是:
1. 将类名从FirstView更改为FirstViewModel。
2. 将命名空间更改为ViewModels。
3. 移除调用InitializeComponent的构造函数(这个方法显然在ViewModel中没有位置)。
4. 实现INotifyPropertyChanged,以便ViewModel稍后可以准备进行数据绑定。
最终,文件将看起来像这样:
为了演示,将向ViewModel添加一个属性,以便稍后可以从视图中绑定到它,以便可以看到一切是否正常工作。以下属性只是一个文本属性,其值为"Hello MVVM"。
现在只剩下将视图连接到ViewModel了。这可以通过所有标准方式完成(通常使用ViewModelLocator),但在这个例子中使用了最简单的方式:
此外,添加了一个TextBlock,它绑定到"SomeText",以便可以看到一切是否正常工作。
如果现在查看设计器预览,将看到它显然有效:
但是不要那么快...
这就变得复杂了。如果没有太多注意,已经创建了一个只有在运行时才会看到的讨厌的错误。如果将视图("FirstView")放在主窗口上:
然后运行应用程序,将看到……什么都没有。出于某种原因它不起作用…
尽管在设计时它*确实*有效:
所以这是什么?为什么它在运行时不起作用,尽管在设计时*确实*有效?!
每个资深WPF程序员的第一反应是立即检查输出窗口是否有任何绑定错误,但这在这里不会有帮助。
没有绑定错误,输出窗口将是干净的。这是一个完全不同的问题。 (建议花几分钟时间自己尝试解决。也花了很长时间……:-)
理解XAML解析过程 在编译期间,从XAML文件创建了两个文件: 1. FirstView.g.cs - FirstView类所在的文件。这个类加载第二个文件 - 2. FirstView.Baml 这是XAML经过某种编译后的结果(实际上是预标记化 - 提前解析文件,以便在运行时加载比加载未解析的XML文件更快) 这两个文件的加载和连接是在FirstView.g.cs中InitializeComponent方法执行的,只是……现在已经摆脱了CodeBehind,没有人调用这个方法。发生的事情是没有任何东西加载BAML文件,因此一切都完全为空。看不到绑定错误,因为甚至绑定也没有加载。 有趣的是,在设计时,Visual Studio会自动加载并运行BAML文件,这就是为什么在设计时它有效的原因。 所以需要做的是确保这个方法在运行时确实被调用。但是怎么做呢?
创建一个自动调用InitializeComponent的UserControl
发现的解决方案是一个不错的技巧。与其让视图实现UserControl,不如让它实现一个名为ViewBase的新类。
让创建一个名为ViewBase的新类,它继承自UserControl:
与其让视图直接继承UserControl,它现在将继承ViewBase:
(当将UserControl更改为v:ViewBase时,Visual Studio最初不会喜欢它,它不会为几秒钟提供智能感知。没关系。)