试图了解DependencyProperty

成为WPF的新手,以及它显然改变,绑定,启用和操作的惊人能力。 我正在努力对所发生的事情进行精神概述,并希望有些人可以确认或纠正我的读数。

在WPF之前,您有代理和事件。 你可以有十几个控件全部监听(通过注册到事件),所以当事件触发时,所有其他控件将自动通知并且可以采取行动,但是它们是如此编码的。 如…

从Code Behind,你会做类似的事情

GotFocus += MyMethodToDoSomething; 

然后,签名方法

 private void MyMethodToDoSomething(object sender, RoutedEventArgs e) { .. do whatever } 

另外,通过使用标准的getter / setter,setter可以在自己的类中调用自己的方法,以便每当有人试图获取或设置值时执行某些操作

 private int someValue; public int SomeValue { get { this.DoSomeOtherThing(); return someValue; } set { this.DoAnotherThing(); someValue = value; } 

现在,有依赖属性和单/双向绑定。 我理解(我认为)关于模拟更多只读操作的单向方法。

无论如何,通过双向绑定,依赖关系会自动通知任何人“依赖”源或目标中的更改,而无需显式检查某些事件是否已订阅事件,框架会自动处理对各自的更改的通知控制(目标或来源)。

因此,让我通过旧的添加/编辑保存/取消维护表单完成此方案。 在较旧的框架中,如果有人单击了添加或编辑按钮,则所有数据输入字段将“启用”,其中包含新记录的空白数据或编辑现有数据。 同时,添加/编辑按钮将被禁用,但现在将启用“保存/取消”按钮。

同样,通过“保存/取消”完成后,它将禁用所有输入字段,保存/取消,并重新启用“添加/编辑”按钮。

我不太明白在这种依赖属性方案(但是)下如何处理这种类型的场景,但是我关闭了吗? 我也明白你几乎可以绑定任何东西,包括配色方案,显示/隐藏,字体等等……但是我在尝试真正掌握这些东西时采取了一些小步骤。

谢谢。

海报要求我重新发布我的评论作为答案。 很高兴:-)

  • 我提到的video演示: http : //blog.lab49.com/archives/2650
  • 奖金链接:MSDN中的WPF文章: http : //msdn.microsoft.com/en-us/magazine/dd419663.aspx
  • 如果你不知道它,在线文档中有一章: http : //msdn.microsoft.com/en-us/library/ms752914.aspx

此外,我发现这本书非常有用: http : //www.amazon.com/WPF-4-Unleashed-Adam-Nathan/dp/0672331195

我自己使用WPF的经验涉及到在我试图使我的程序工作时回到一堆不同的资源之间。 在WPF中有这么多东西,当你正在学习它时,很难将它全部保留在脑海中。

getter / setter东西是常规C#属性的一个特性。 它不是WPF独有的。

这种单向/双向的东西是讨论WPF数据绑定,它不需要你创建依赖属性 – 只是为了使用它们。

依赖属性内置于控件本身。 在将控件实例添加到表单时,它们允许您直接引用这些属性。 它们允许您的自定义控件感觉更“原生”。

通常,它们用于实现可以使用数据绑定的属性。 在您的应用程序中,您通常只使用数据绑定,而不是为它实现新的挂钩。

…如果有人点击添加或编辑按钮,所有数据输入字段将变为“启用”,其中包含新记录的空白数据或编辑现有数据。 同时,添加/编辑按钮将被禁用,但现在将启用“保存/取消”按钮。

同样,通过“保存/取消”完成后,它将禁用所有输入字段,保存/取消,并重新启用“添加/编辑”按钮。

我会完成你想要完成的任务:

  • 视图模型
  • 视图上的数据绑定到该视图模型
  • 在该视图模型上显示ICommand(用于按钮)
  • 视图模型上的INotifyPropertyChanged(适用于所有属性)

不需要为此方案创建新的依赖项属性。 您只需使用现有的数据绑定即可。

这是使用数据绑定和MVVM样式执行WPF的代码示例/教程。

设置项目

我在New Project向导中创建了一个WPF应用程序,并将其命名为MyProject

我设置了我的项目名称和名称空间以匹配普遍接受的方案。 您应该在解决方案资源管理器 – >项目 – >右键单击 – >属性中设置这些属性。

用于设置正确名称空间的项目设置

我还有一个我喜欢用于WPF项目的自定义文件夹方案:

在此处输入图像描述

为了组织目的,我将视图放在自己的“View”文件夹中。 这也反映在命名空间中,因为您的命名空间应与您的文件夹匹配( namespace MyCompany.MyProject.View )。

我还编辑了AssemblyInfo.cs,并清理了我的程序集参考和应用程序配置,但这只是一些单调乏味,我将作为练习留给读者:)

创建视图

从设计师开始,让一切看起来都不错。 不要添加任何代码,或做任何其他工作。 只需在设计师中玩耍,直到事情看起来正确(特别是当您resize时)。 这就是我最终得到的结果:

我最终得到的观点

查看/ EntryView.xaml:

                               

查看/ EntryView.xaml.cs:

 using System.Windows; namespace MyCompany.MyProject.View { public partial class EntryView : Window { public EntryView() { InitializeComponent(); } } } 

我没有在这些控件上创建任何Name属性。 这是故意的。 我将使用MVVM,并且不会使用任何代码。 我会让设计师做他们想做的事,但我不会触及任何代码。

创建视图模型

接下来我将制作我的视图模型。 这应该以为视图提供服务的方式设计,但理想情况下可以是视图独立的。 我不会太担心这一点,但关键是你不必拥有1对1的视图控件和视图模型对象。

我尝试在更大的应用程序上下文中使我的视图/视图模型有意义,因此我将开始在此处使用视图模型。 我们将这个“可编辑的forms”作为rolodex条目。

我们将首先创建一个我们需要的辅助类……

视图模型/ DelegateCommand.cs:

 using System; using System.Windows.Input; namespace MyCompany.MyProject.ViewModel { public class DelegateCommand : ICommand { private readonly Action _execute; private readonly Func _canExecute; public DelegateCommand(Action execute) : this(execute, CanAlwaysExecute) { } public DelegateCommand(Action execute, Func canExecute) { if (execute == null) throw new ArgumentNullException("execute"); if (canExecute == null) throw new ArgumentNullException("canExecute"); _execute = o => execute(); _canExecute = o => canExecute(); } public bool CanExecute(object parameter) { return _canExecute(parameter); } public void Execute(object parameter) { _execute(parameter); } public event EventHandler CanExecuteChanged; public void RaiseCanExecuteChanged() { if (CanExecuteChanged != null) CanExecuteChanged(this, new EventArgs()); } private static bool CanAlwaysExecute() { return true; } } } 

视图模型/ EntryViewModel.cs:

 using System; using System.ComponentModel; using System.Windows.Input; namespace MyCompany.MyProject.ViewModel { public class EntryViewModel : INotifyPropertyChanged { private readonly string _initialName; private readonly string _initialEmail; private readonly string _initialPhoneNumber; private readonly string _initialRelationship; private string _name; private string _email; private string _phoneNumber; private string _relationship; private bool _isInEditMode; private readonly DelegateCommand _makeEditableOrRevertCommand; private readonly DelegateCommand _saveCommand; private readonly DelegateCommand _cancelCommand; public EntryViewModel(string initialNamename, string email, string phoneNumber, string relationship) { _isInEditMode = false; _name = _initialName = initialNamename; _email = _initialEmail = email; _phoneNumber = _initialPhoneNumber = phoneNumber; _relationship = _initialRelationship = relationship; MakeEditableOrRevertCommand = _makeEditableOrRevertCommand = new DelegateCommand(MakeEditableOrRevert, CanEditOrRevert); SaveCommand = _saveCommand = new DelegateCommand(Save, CanSave); CancelCommand = _cancelCommand = new DelegateCommand(Cancel, CanCancel); } public string Name { get { return _name; } set { _name = value; RaisePropertyChanged("Name"); } } public string Email { get { return _email; } set { _email = value; RaisePropertyChanged("Email"); } } public string PhoneNumber { get { return _phoneNumber; } set { _phoneNumber = value; RaisePropertyChanged("PhoneNumber"); } } public string Relationship { get { return _relationship; } set { _relationship = value; RaisePropertyChanged("Relationship"); } } public bool IsInEditMode { get { return _isInEditMode; } private set { _isInEditMode = value; RaisePropertyChanged("IsInEditMode"); RaisePropertyChanged("CurrentEditModeName"); _makeEditableOrRevertCommand.RaiseCanExecuteChanged(); _saveCommand.RaiseCanExecuteChanged(); _cancelCommand.RaiseCanExecuteChanged(); } } public string CurrentEditModeName { get { return IsInEditMode ? "Revert" : "Edit"; } } public ICommand MakeEditableOrRevertCommand { get; private set; } public ICommand SaveCommand { get; private set; } public ICommand CancelCommand { get; private set; } private void MakeEditableOrRevert() { if (IsInEditMode) { // Revert Name = _initialName; Email = _initialEmail; PhoneNumber = _initialPhoneNumber; Relationship = _initialRelationship; } IsInEditMode = !IsInEditMode; // Toggle the setting } private bool CanEditOrRevert() { return true; } private void Save() { AssertEditMode(isInEditMode: true); IsInEditMode = false; // Todo: Save to file here, and trigger close... } private bool CanSave() { return IsInEditMode; } private void Cancel() { AssertEditMode(isInEditMode: true); IsInEditMode = false; // Todo: Trigger close form... } private bool CanCancel() { return IsInEditMode; } private void AssertEditMode(bool isInEditMode) { if (isInEditMode != IsInEditMode) throw new InvalidOperationException(); } #region INotifyPropertyChanged Members public event PropertyChangedEventHandler PropertyChanged; private void RaisePropertyChanged(string propertyName) { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } #endregion INotifyPropertyChanged Members } } 

与此类工作流程一样,最初创建视图时我会遗漏一些要求。 例如,我发现有一个“恢复”function可以撤消更改,但保持对话框打开是有意义的。 我还想到我可以为此目的重复使用编辑按钮。 所以我创建了一个属性,我将阅读以获取编辑按钮的名称。

视图模型包含许多代码来执行简单的操作,但大多数代码都是用于连接属性的样板。 不过,这个样板可以给你一些力量。 它有助于将您与视图隔离开来,因此您的视图可以在不进行任何更改或仅对视图模型进行微小更改的情况下进行大幅更改。

如果视图模型太大,您可以开始将其推送到其他子视图模型中。 在最有意义的地方创建它们,并将它们作为此视图模型的属性返回。 WPF数据绑定机制支持链接数据上下文。 当我们联系起来时,你会稍后发现这个数据上下文。

将视图连接到我们的视图模型

要将视图连接到视图模型,必须在视图上设置DataContext属性以指向视图模型。

有些人喜欢在XAML代码中实例化和指定视图模型。 虽然这可以工作,但我喜欢保持视图和视图模型彼此独立,所以我确保使用一些第三类来连接两者。

通常我会使用dependency injection容器来连接我的所有代码,这是很多工作,但保持所有部分独立。 但对于这个简单的应用程序,我喜欢使用App类将我的东西绑定在一起。 我们去编辑它:

App.xaml中:

     

App.xaml.cs:

 using System.Windows; namespace MyCompany.MyProject { public partial class App : Application { private void ApplicationStartup(object sender, StartupEventArgs e) { // Todo: Somehow load initial data... var viewModel = new ViewModel.EntryViewModel( "some name", "some email", "some phone number", "some relationship" ); var view = new View.EntryView() { DataContext = viewModel }; view.Show(); } } } 

您现在可以运行您的项目,但我们构建的逻辑不会执行任何操作。 这是因为我们创建了初始视图,但它实际上并没有进行任何数据绑定。

设置数据绑定

让我们回过头来编辑视图,完成所有操作。

编辑View / EntryView.xaml:

                                       

我在这里做了很多工作。 首先,静态的东西:

  • 我更改了表单的标题以匹配Rolodex的想法
  • 我为这些字段添加了标签,因为我现在知道它们适用于什么
  • 我改变了最小宽度/高度,因为我注意到控件被切断了

接下来的数据绑定:

  • 我将所有文本字段绑定到视图模型上的相应属性
  • 我让文本字段在每个按键上更新视图模型 ( UpdateSourceTrigger=PropertyChanged )。 这个应用程序不是必需的,但将来可能会有所帮助。 我添加它以免你在需要时查找它:)
  • 我将每个文本框的IsEnabled字段绑定到IsInEditMode属性
  • 我将按钮绑定到各自的命令
  • 我将编辑按钮的名称( Content属性)绑定到视图模型上的相应属性

这是结果

只读模式编辑模式

现在所有的UI逻辑都有效,除了我们留下Todo评论的那些。 我留下那些未实现的,因为它们与特定的应用程序架构有关,我不想进入这个演示。

此外,vanilla WPF没有非常干净的MVVM方式来关闭我所知道的表单。 您可以使用代码隐藏来执行此操作,或者您可以使用几十个WPF附加库中的一个,这些库提供了更清晰的方法。

依赖属性

您可能已经注意到我没有在我的代码中创建一个自定义依赖项属性。 我使用的依赖属性都在现有控件上(例如TextContentCommand )。 这就是它在WPF中的常用方式,因为数据绑定和样式(我没有进入)为您提供了很多选择。 它允许您完全自定义内置控件的外观,感觉和操作。

在以前的Windows GUI框架中,您通常必须子类化现有控件或创建自定义控件以获得自定义外观。 在WPF中进行自定义控件的唯一原因是以可重用的方式组合多个控件的模式,或者从头开始创建一个全新的控件。

例如,如果您正在创建一个与弹出控件配对的自动完成文本框,以显示它自动完成的值。 在这种情况下,您可能希望使用自定义依赖项属性(例如自动完成源)进行自定义控件。 这样,您可以在整个应用程序和其他应用程序中重用控件。

如果您没有进行自定义控件,或者制作可以在XAML中直接实例化和使用以及与数据绑定的特殊非UI类,则可能不需要创建依赖项属性。

查看它们的一种简单方法是它们是指向另一个属性的属性。

它们实际上是属性的定义,它定义属性名称,类型,默认值等,但属性的实际值不与属性定义一起存储。

所以你可以说Button的Enabled属性将指向特定类的属性,或者它将指向CheckBoxA.IsChecked属性,或者你甚至可以说它只是指向一个布尔值假。

 // Value points to the current DataContext object's CanSaveObject property  // Value points to the IsChecked property of CheckBoxA  // Value points to the value False