WPF快速入门。 第1部分。绑定,INotifyPropertyChanged和MVVM

大家好!


由于各种原因,我们大多数人使用桌面应用程序,至少使用浏览器:)并且我们中有些人需要编写自己的应用程序。 在本文中,我想介绍一个使用Windows Presentation Foundation(WPF)技术并应用MVVM模式的简单桌面应用程序的开发过程。 那些希望继续阅读的人,请注意。


我认为不必说WPF是Microsoft开发的:)此技术旨在从Windows XP开始为Windows开发桌面应用程序。 为什么这样 这是由于WPF在.NET平台上运行,该平台的最低要求是Windows XP和更高版本。 不幸的是,WPF无法在其他平台上运行,尽管有可能在不久的将来发生变化:基于WPF的Avalonia框架正在开发中


WPF有什么特别之处?


WPF与其他桌面构建工具之间的两个主要区别是:


  • XAML标记语言,用于标记窗口界面本身。
  • 通过DirectX渲染,硬件图形加速。

我不会详细介绍,因为 这不是本文的主题。 如果有兴趣,然后谷歌的XAML,WPF渲染,milcore.dll和DirectX :)


这篇文章是关于什么的?


本文包含基于WPF技术构建的示例应用程序:



我将尝试以“跟着我重复”的方式将文章的内容定位为实用的方向,并附有解释。


我们需要重复什么文章?


很少的C#开发经验:)至少,您需要很好地理解语言语法。 您还需要一台装有Visual Studio的Windows机器(在示例中为Win 10)(在示例中为2017,有免费的Community版本)。 安装VS时,您需要启用对.NET平台的桌面开发的支持。


图片


同样在本节中,我将描述项目的创建。


我们启动VS,创建一个新项目,选择应用程序类型WPF App(.NET Framework)(您可以在右上角的搜索栏中输入它),然后随意命名。


图片


创建新项目后,界面编辑器窗口将打开,对我来说看起来像这样


图片


底部是布局编辑器,顶部是窗口界面的预览,但是您可以使用以下按钮更改代码编辑器和界面预览的相对位置,以使其位于水平位置(在右侧两个区域的边框上):


图片


开始之前


窗口元素(在Control一词中也称为控件)应放置在容器内或ContentControl类型的另一个元素内。 容器是一种特殊的控件,允许您在内部放置几个子控件并组织它们的相互安排。 容器示例:


  • 网格 -允许您按列和行组织元素,每列或每行的宽度均单独配置。
  • StackPanel-允许您将子级排列在一行或一列中。

还有其他容器。 由于容器也是控件,因此在容器内部可能存在包含嵌套容器等的嵌套容器。 这使您可以灵活地相对于彼此布置控件。 同样,借助容器,我们在调整窗口大小时同样可以灵活地控制嵌套控件的行为。


MVVM和INotifyPropertyChanged接口。 文本副本。


本示例的结果将是一个具有两个控件的应用程序,其中一个可以编辑文本,而另一个仅用于查看。 从一个到另一个的更改将同步传输,而无需使用绑定显式复制文本。


因此,我们有一个新创建的项目(我将其命名为Ex1 ),转到布局编辑器,首先用<StackPanel> </ StackPanel>替换默认容器( <Grid> </ Grid> )。 这个容器就足够了,因为 我们将只需要在两个控件之间放置一个控件即可。 我们通过添加属性Orientation =“ Vertical”明确指定组件的排列方式。 在面板集内添加两个元素:一个用于输入文本的字段和一个用于显示文本的字段。 由于这些控件将不包含嵌入式代码,因此您可以使用自动关闭标记对其进行描述(请参见下面的代码)。 完成上述所有过程之后,容器描述代码和嵌套控件应采用以下形式:


<StackPanel Orientation="Vertical"> <TextBox /> <TextBlock /> </StackPanel> 

现在,让我们集中讨论此示例的目的。 我们希望在文本框中键入内容时,在文本块中同步显示相同的文本,同时避免显式复制操作。 我们需要某种连接实体,在这里,我们谈到了上面提到的诸如binding之类的东西。 WPF术语中的绑定是一种机制,它允许您将控件的某些属性与C#类对象的某些属性相关联,并在捆绑包的其中一部分发生更改时相互更新这些属性(这可以同时在一个方向,另一个方向或两个方向上起作用)。 对于那些熟悉Qt的人来说,您可以对时隙和信号进行类比。 为了不浪费时间,让我们继续进行代码。


因此,要组织绑定,您需要控件的属性和某些C#类的某些属性。 首先,让我们弄清楚XAML代码。 两个控件的文本都存储在Text属性中,因此请为这些属性添加一个绑定。 这样做是这样的:


 <TextBox Text="{Binding}"/> <TextBlock Text="{Binding}"/> 

我们进行了绑定,但目前尚不清楚为什么:)我们需要将要进行绑定的某个类的对象和该对象中的某些属性(正如他们所说,您需要绑定到该对象)。


那这堂课是什么? 此类称为视图模型,并用作视图(接口或其部分)与模型(模型,即代码中负责应用程序逻辑的那些部分)之间的链接。这使您可以分开(在某种程度上) )来自接口(视图,视图)的应用程序逻辑称为Model-View-ViewModel(MVVM)模式 。在WPF中,此类也称为DataContext


但是,仅编写视图模型类是不够的。 必须以某种方式通知绑定机制,视图模型属性或视图属性已更改。 为此,有一个特殊的接口INotifyPropertyChanged ,其中包含PropertyChanged事件。 我们在基类BaseViewModel的框架中实现此接口。 将来,我们将从该基类继承所有视图模型,以免重复该接口的实现。 因此,将ViewModels目录添加到项目中,并将BaseViewModel.cs文件添加到该目录中。 我们得到以下项目结构:


图片


基本视图模型的实现代码:


 using System.ComponentModel; namespace Ex1.ViewModels { public class BaseViewModel : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged(string propertyName = "") { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } } 

让我们为MainWindow类创建我们的视图模型,该模型继承自基本模型。 为此,请在同一ViewModels目录中创建MainWindowViewModel.cs文件,该文件中将包含以下代码:


 namespace Ex1.ViewModels { public class MainWindowViewModel : BaseViewModel { } } 

太好了! 现在,我们需要在该视图模型中添加一个属性,将控件文本绑定到该属性上。 由于这是文本,因此此属性的类型必须为string


 public string SynchronizedText { get; set; } 

结果,我们得到了这样的代码


 namespace Ex1.ViewModels { public class MainWindowViewModel : BaseViewModel { public string SynchronizedText { get; set; } } } 

因此,似乎他们做到了。 它仍然可以从视图绑定到此属性,并准备就绪。 现在就开始吧:


 <TextBox Text="{Binding Path=SynchronizedText}"/> <TextBlock Text="{Binding Path=SynchronizedText}"/> 

Nishtyak,我们开始项目,在文本框iiiii中键入……什么也没发生)))好的,事实上,我们的方法正确,只是没有到达正确的位置。


我建议停一会儿,想一想我们所缺少的。 我们有一个看法。 视图模型也是如此。 像zabindili这样的属性。 所需的接口已实现。 我们做了很多工作来复制可悲的文本,为什么我们需要这个???!?!


好吧,开个玩笑。 我们忘记了创建一个视图模型对象和其他东西(稍后会详细介绍)。 我们描述了类本身,但这没有任何意义,因为我们没有此类的对象。 好的,您需要在哪里存储指向该对象的链接? 在该示例的开头,我提到了WPF中使用的某个DataContext 。 因此,任何视图都具有DataContext属性,我们可以向其分配指向视图模型的链接。 来吧 为此,请打开MainWindow.xaml文件,然后按F7键打开此视图的代码。 它几乎是空的,它只有一个窗口类构造函数。 将创建的视图模型添加到其中,并将其放置在窗口的DataContext中 (不要忘记添加使用所需的命名空间):


 public MainWindow() { InitializeComponent(); this.DataContext = new MainWindowViewModel(); } 

这很简单,但还不够。 尽管如此,当应用程序启动时,不会发生文本同步。 还有什么需要做的?


SynchronizedText属性更改时,您需要引发PropertyChanged事件,并通知视图它应该监视此事件。 因此,要触发事件,请修改视图模型代码:


 public class MainWindowViewModel : BaseViewModel { private string _synchronizedText; public string SynchronizedText { get => _synchronizedText; set { _synchronizedText = value; OnPropertyChanged(nameof(SynchronizedText)); } } } 

我们在这里做了什么? 我们添加了一个用于存储文本的隐藏字段,将其包装在现有属性中,当更改此属性时,我们不仅更改了该隐藏字段,还调用了基础视图模型中定义的OnPropertyChanged方法,并引发了在INotifyPropertyChanged接口中声明的PropertyChanged事件,该事件也在基础中实现查看模型。 事实证明,每次更改文本时,都会发生PropertyChanged事件,将已更改的视图模型的属性名称传递给该事件。


好吧,几乎所有东西,终点线! 仍然可以指定它应侦听PropertyChanged事件的视图:


 <TextBox Text="{Binding Path=SynchronizedText, UpdateSourceTrigger=PropertyChanged, Mode=OneWayToSource}"/> <TextBlock Text="{Binding Path=SynchronizedText, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}"/> 

除了我们指出应该由哪个触发器进行更新这一事实外,我们还指出了跟踪更新的方向:从视图到视图模型,反之亦然。 由于我们在文本框中输入文本,因此我们仅对视图中的更改感兴趣,因此我们选择OneWayToSource模式。 对于文本块,一切都恰好相反:我们对视图模型中的更改感兴趣,以便在视图中显示它们,因此我们选择OneWay模式。 如果我们希望在两个方向上都可以跟踪更改,则根本不能指定Mode ,也不能明确指定TwoWay


因此,运行该程序,键入文本并输入voi-la! 文本同步更改,我们没有在任何地方复制任何内容!


图片


感谢您的关注,请继续。 我们将处理DataTemplate和Command模式。

Source: https://habr.com/ru/post/zh-CN427325/


All Articles