2011-09-29 67 views
9

对WPF来说是新手,它显然是非常了不起的改变,绑定,启用和其他操作的能力。我试图对发生的事情进行精神概述,并希望有些人能够确认或纠正我的读数。试图理解DependencyProperty

在WPF之前,你有代表和事件。你可以有十几个控件都在监听(通过注册到事件),所以当事件触发时,所有其他控件都会自动通知,并可以采取行动,但是他们是这样编码的。如...

从代码的背后,你会做这样的事情

GotFocus += MyMethodToDoSomething; 

然后,签名方法

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

此外,通过使用标准的getter/setter,二传手可以调用它自己的方法在自己的班级做每次有人试图获得或设置一个值的东西

private int someValue; 
public int SomeValue 
{ 
    get { this.DoSomeOtherThing(); 
     return someValue; 
     } 

    set { this.DoAnotherThing(); 
     someValue = value; 
} 

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

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

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

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

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

谢谢。

+1

因为WPF是一个很大的话题,而且你的问题比较宽泛,所以很难回答。我很乐意为您提供我最喜欢的WPF资源的链接。你有没有例如阅读Model-View-ViewModel模式?这是一个很好的演示:http://blog.lab49.com/archives/2650 –

+0

你的问题看起来像是一篇关于依赖属性的文章。我甚至没有读过它。 –

+0

@Corey Kosak,如果您发表您的评论作为答案,我会检查解决方案,因为它提供了最好的一步一步的理解,而无需购买一本书。 – DRapp

回答

2

海报要求我转发我的评论作为答案。乐意帮忙:-)

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

我自己的EXP与WPF合作的过程涉及在我尝试让我的程序运行时在一堆不同的资源之间进行回溯。WPF中有这么多东西,当你正在学习时,很难将它全部放在你的脑海中。

6

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

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

依存属性内置于控件本身。当您将控件的实例添加到窗体时,它们让您直接引用这些属性。他们让您的自定义控件感觉更加“原生”。

通常它们用于器具可以使用数据绑定的属性。在你的应用程序中,你将主要使用使用数据绑定,而不是实现新的挂钩。

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

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

我会完成你想要实现的目标:

  • 视图模型
  • 数据的视图到视图模型
  • 在该视图模型暴露的ICommand绑定(对于按钮)
  • INotifyPropertyChanged的上视图模型(对于所有属性)

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

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

建立项目

我创建了新建项目向导WPF应用程序,并把它命名为MyProject

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

Project settings to set the correct namespaces

我也有我喜欢使用WPF项目自定义文件夹方案:

enter image description here

我坚持认为在自己的“查看”文件夹,用于组织的目的。这也反映在名称空间中,因为您的名称空间应该与您的文件夹匹配(namespace MyCompany.MyProject.View)。

我还编辑AssemblyInfo.cs中,荡涤我的程序集引用和应用程序配置,但也只是一些单调乏味,我将离开作为一个练习留给读者:)

创建一个视图

从设计师开始,让一切看起来不错。不要添加任何代码,或者做其他任何工作。只需在设计师身边玩耍,直到事情看起来正确(尤其是当您调整大小时)。这是我结束了:

The view I ended up with

查看/ EntryView.xaml:

<Window x:Class="MyCompany.MyProject.View.EntryView" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     Title="Entry View" Height="350" Width="525"> 
    <Grid> 
     <Grid.RowDefinitions> 
      <RowDefinition Height="*" /> 
      <RowDefinition Height="Auto" /> 
     </Grid.RowDefinitions> 
     <Grid Grid.Row="0"> 
      <Grid.RowDefinitions> 
       <RowDefinition Height="Auto" /> 
       <RowDefinition Height="Auto" /> 
       <RowDefinition Height="Auto" /> 
       <RowDefinition Height="Auto" /> 
      </Grid.RowDefinitions> 
      <TextBox Text="Test 1" Grid.Row="0" /> 
      <TextBox Text="Test 2" Grid.Row="1" Margin="0,6,0,0" /> 
      <TextBox Text="Test 3" Grid.Row="2" Margin="0,6,0,0" /> 
      <TextBox Text="Test 4" Grid.Row="3" Margin="0,6,0,0" /> 
     </Grid> 
     <Grid Grid.Row="1"> 
      <Grid.ColumnDefinitions> 
       <ColumnDefinition Width="*" /> 
       <ColumnDefinition Width="Auto" /> 
       <ColumnDefinition Width="Auto" /> 
      </Grid.ColumnDefinitions> 
      <Button Content="Edit" IsEnabled="True" Grid.Column="0" 
       HorizontalAlignment="Left" Width="75" /> 
      <Button Content="Save" IsEnabled="False" Grid.Column="1" 
       Width="75" /> 
      <Button Content="Cancel" IsEnabled="False" Grid.Column="2" 
       Width="75" Margin="6,0,0,0" /> 
     </Grid> 
    </Grid> 
</Window> 

查看/ EntryView.xaml.cs:

using System.Windows; 

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

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

创建一个视图模型

接下来我会让我的视图模型。这应该以它为视图提供服务的方式进行设计,但理想情况下可以独立观看。我不会太担心这一点,但重要的是你不要有一对一的视图控件和视图模型对象。

我尝试使我的视图/视图模型在更大的应用程序环境中有意义,因此我将开始在此处使用视图模型。我们将使这个“可编辑表单”成为一个rolodex条目。

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

视图模型/ DelegateCommand.cs:

using System; 
using System.Windows.Input; 

namespace MyCompany.MyProject.ViewModel 
{ 
    public class DelegateCommand : ICommand 
    { 
     private readonly Action<object> _execute; 
     private readonly Func<object, bool> _canExecute; 

     public DelegateCommand(Action execute) 
      : this(execute, CanAlwaysExecute) 
     { 
     } 

     public DelegateCommand(Action execute, Func<bool> 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 
    } 
} 

正如通常的这种类型的工作流程,有一些要求,我错过了最初的肌酸g视图。例如,我发现有一个“恢复”功能可以解除更改,但保持对话框处于打开状态。我也发现我可以重新使用编辑按钮来达到这个目的。所以我创建了一个属性,我将读取它来获取编辑按钮的名称。

视图模型包含很多代码来做一些简单的事情,但其中大部分代码是用于连接属性的样板。虽然这样的样板给了你一些权力。它有助于将您从视图中隔离出来,因此您的视图可以在没有变化的情况下发生剧烈变化,或者只对视图模型进行微小更改。

如果视图模型变得太大,您可以开始将它推入附加的子视图模型中。在最有意义的地方创建它们,并将它们返回为此视图模型的属性。 WPF数据绑定机制支持链接数据上下文。当我们把事情搞定的时候,你会在稍后发现这个数据上下文。

挂钩的观点对我们的视图模型

要将视图挂钩到一个视图模型,你必须设置上来看DataContext属性指向您的视图模型。

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

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

的App.xaml:

<Application x:Class="MyCompany.MyProject.App" 
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
      Startup="ApplicationStartup"> 
    <Application.Resources> 

    </Application.Resources> 
</Application> 

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

您现在可以运行你的项目,虽然逻辑我们建立韩元什么都不做。这是因为我们的初始视图是创建的,但它实际上并没有进行任何数据绑定。

设置数据绑定

让我们回去和编辑来完成挂钩这一切的观点。

编辑视图/ EntryView.xaml:

<Window x:Class="MyCompany.MyProject.View.EntryView" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     Title="Rolodex Entry" 
     Height="350" Width="525" 
     MinWidth="300" MinHeight="200"> 
    <Grid Margin="12"> 
     <Grid.RowDefinitions> 
      <RowDefinition Height="*" /> 
      <RowDefinition Height="Auto" /> 
     </Grid.RowDefinitions> 
     <Grid Grid.Row="0"> 
      <Grid.RowDefinitions> 
       <RowDefinition Height="Auto" /> 
       <RowDefinition Height="Auto" /> 
       <RowDefinition Height="Auto" /> 
       <RowDefinition Height="Auto" /> 
      </Grid.RowDefinitions> 
      <Grid.ColumnDefinitions> 
       <ColumnDefinition Width="Auto" /> 
       <ColumnDefinition Width="*" /> 
      </Grid.ColumnDefinitions> 
      <TextBlock Text="Name:" Grid.Column="0" Grid.Row="0" /> 
      <TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" 
        IsEnabled="{Binding IsInEditMode}" Grid.Column="1" 
        Grid.Row="0" Margin="6,0,0,0" /> 
      <TextBlock Text="E-mail:" Grid.Column="0" Grid.Row="1" 
         Margin="0,6,0,0" /> 
      <TextBox Text="{Binding Email, UpdateSourceTrigger=PropertyChanged}" 
        IsEnabled="{Binding IsInEditMode}" Grid.Column="1" 
        Grid.Row="1" Margin="6,6,0,0" /> 
      <TextBlock Text="Phone Number:" Grid.Column="0" Grid.Row="2" 
         Margin="0,6,0,0" /> 
      <TextBox Text="{Binding PhoneNumber, UpdateSourceTrigger=PropertyChanged}" 
        IsEnabled="{Binding IsInEditMode}" Grid.Column="1" Grid.Row="2" 
        Margin="6,6,0,0" /> 
      <TextBlock Text="Relationship:" Grid.Column="0" Grid.Row="3" 
         Margin="0,6,0,0" /> 
      <TextBox Text="{Binding Relationship, UpdateSourceTrigger=PropertyChanged}" 
        IsEnabled="{Binding IsInEditMode}" Grid.Column="1" Grid.Row="3" 
        Margin="6,6,0,0" /> 
     </Grid> 
     <Grid Grid.Row="1"> 
      <Grid.ColumnDefinitions> 
       <ColumnDefinition Width="*" /> 
       <ColumnDefinition Width="Auto" /> 
       <ColumnDefinition Width="Auto" /> 
      </Grid.ColumnDefinitions> 
      <Button Content="{Binding CurrentEditModeName}" 
        Command="{Binding MakeEditableOrRevertCommand}" 
        Grid.Column="0" HorizontalAlignment="Left" 
        Width="75" /> 
      <Button Content="Save" Command="{Binding SaveCommand}" 
        Grid.Column="1" Width="75" /> 
      <Button Content="Cancel" Command="{Binding CancelCommand}" 
        Grid.Column="2" Width="75" Margin="6,0,0,0" /> 
     </Grid> 
    </Grid> 
</Window> 

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

  • 我改变了标题形式的匹配关系网的想法
  • 我加标签的领域,因为我现在知道它们适用于
  • 我改变了最小宽度/身高,因为我注意到对照被截断

下一页数据绑定:

  • 我绑定的所有文本字段ŧ o视图模型上的相应属性
  • 我使文本字段为update the view model on every keypressUpdateSourceTrigger=PropertyChanged)。这不是这个应用程序所必需的,但可能对未来有所帮助。我添加它来寻找它饶了你,当你需要它:)
  • 我必将每个文本框的IsEnabled领域的IsInEditMode财产
  • 我绑定的按键各自命令
  • 我绑定的编辑按钮的名称(Content属性)对视图模型

这里对应属性的结果

Read-only mode Edit mode

现在所有的UI逻辑工作,除了那些我们留下了Todo评论。我没有实现这些功能,因为他们必须处理特定的应用程序体系结构,而我不想为此演示考虑。

而且,香草WPF没有一个很干净的MVVM方式来关闭一个形式,我知道的。您可以使用代码隐藏功能来执行此操作,也可以使用数十个WPF附加程序库中的一个,这些附加程序库提供了自己更干净的方法。

依赖属性

您可能已经注意到,我没有在我的代码创建一个自定义的依赖项属性。我使用的依赖项属性都在现有控件上(例如TextContentCommand)。这是它通常在WPF中的工作原理,因为数据绑定和样式(我没有涉及)给了你很多选择。它可以让您完全自定义内置控件的外观,感觉和操作。

在以前的Windows GUI框架,你经常要继承现有的控件或创建自定义的控制来获得定制的外观和感觉。在WPF中制作自定义控件的唯一原因是以可重用的方式组合多个控件的模式,或者从头创建一个全新的控件。

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

如果你没有做自定义控件,或使得可以直接实例化和XAML和数据绑定使用专用的非UI类,你可能不会需要创建依赖属性。

+0

伟大的分步解决方案,并衷心感谢时间/精力。在这种发展观念中,我有很多东西要学习/理解。 – DRapp

+0

@DRapp:如果你想我可以写一个依赖属性的东西的例子:)也许会花更长的时间,因为我不经常使用它们。 –

+0

谢谢,但没有必要。我需要消化一些基本知识,用我的环境(思维模式)对它们进行抽样,并尝试爬行,走路,然后跑步。 – DRapp

1

的看着他们一个简单的方法是,他们是指向另一个属性的属性。

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

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

// Value points to the current DataContext object's CanSaveObject property 
<Button IsEnabled="{Binding CanSaveObject}" /> 

// Value points to the IsChecked property of CheckBoxA 
<Button IsEnabled="{Binding ElementName=CheckBoxA, Path=IsChecked}" /> 

// Value points to the value False 
<Button IsEnabled="False" />