2011-09-29 58 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" />