getter/setter的東西是常規C#屬性的一個特性。這不是WPF獨有的。
這種單向/雙向的東西是在談論WPF數據綁定,它不需要您創建依賴屬性 - 只是爲了使用它們。
依存屬性內置於控件本身。當您將控件的實例添加到窗體時,它們讓您直接引用這些屬性。他們讓您的自定義控件感覺更加「原生」。
通常它們用於器具可以使用數據綁定的屬性。在你的應用程序中,你將主要使用使用數據綁定,而不是實現新的掛鉤。
...如果有人點擊添加或編輯按鈕,所有數據輸入字段將變爲「啓用」,或者爲新記錄留空數據或編輯現有數據。同時,添加/編輯按鈕將被禁用,但保存/取消按鈕現在變爲啓用。
同樣,當通過保存/取消完成時,它將禁用所有輸入字段,保存/取消並重新啓用添加/編輯按鈕。
我會完成你想要實現的目標:
- 視圖模型
- 數據的視圖到視圖模型
- 在該視圖模型暴露的ICommand綁定(對於按鈕)
- INotifyPropertyChanged的上視圖模型(對於所有屬性)
不需要爲此方案創建新的依賴項屬性。您只需使用現有的數據進行數據綁定。
下面是使用數據綁定和MVVM樣式進行WPF的代碼示例/教程。
建立項目
我創建了新建項目嚮導WPF應用程序,並把它命名爲MyProject
。
我設置了我的項目名稱和命名空間以匹配事物的普遍接受的方案。您應該在解決方案資源管理器中設置這些屬性 - >項目 - >右鍵單擊 - >屬性。
我也有我喜歡使用WPF項目自定義文件夾方案:
我堅持認爲在自己的「查看」文件夾,用於組織的目的。這也反映在名稱空間中,因爲您的名稱空間應該與您的文件夾匹配(namespace MyCompany.MyProject.View
)。
我還編輯AssemblyInfo.cs中,盪滌我的程序集引用和應用程序配置,但也只是一些單調乏味,我將離開作爲一個練習留給讀者:)
創建一個視圖
從設計師開始,讓一切看起來不錯。不要添加任何代碼,或者做其他任何工作。只需在設計師身邊玩耍,直到事情看起來正確(尤其是當您調整大小時)。這是我結束了:
查看/ 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 keypress(
UpdateSourceTrigger=PropertyChanged
)。這不是這個應用程序所必需的,但可能對未來有所幫助。我添加它來尋找它饒了你,當你需要它:)
- 我必將每個文本框的
IsEnabled
領域的IsInEditMode
財產
- 我綁定的按鍵各自命令
- 我綁定的編輯按鈕的名稱(
Content
屬性)對視圖模型
這裏對應屬性的結果
現在所有的UI邏輯工作,除了那些我們留下了Todo
評論。我沒有實現這些功能,因爲他們必須處理特定的應用程序體系結構,而我不想爲此演示考慮。
而且,香草WPF沒有一個很乾淨的MVVM方式來關閉一個形式,我知道的。您可以使用代碼隱藏功能來執行此操作,也可以使用數十個WPF附加程序庫中的一個,這些附加程序庫提供了自己更乾淨的方法。
依賴屬性
您可能已經注意到,我沒有在我的代碼創建一個自定義的依賴項屬性。我使用的依賴項屬性都在現有控件上(例如Text
,Content
和Command
)。這是它通常在WPF中的工作原理,因爲數據綁定和樣式(我沒有涉及)給了你很多選擇。它可以讓您完全自定義內置控件的外觀,感覺和操作。
在以前的Windows GUI框架,你經常要繼承現有的控件或創建自定義的控制來獲得定製的外觀和感覺。在WPF中製作自定義控件的唯一原因是以可重用的方式組合多個控件的模式,或者從頭創建一個全新的控件。
E.g.如果您正在創建一個與彈出控件配對的自動完成文本框來顯示它自動完成的值。在這種情況下,您可能想要使用自定義依賴項屬性(例如自動完成源)來創建自定義控件。這樣,您可以在整個應用程序和其他應用程序中重複使用該控件。
如果你沒有做自定義控件,或使得可以直接實例化和XAML和數據綁定使用專用的非UI類,你可能不會需要創建依賴屬性。
因爲WPF是一個很大的話題,而且你的問題比較寬泛,所以很難回答。我很樂意爲您提供我最喜歡的WPF資源的鏈接。你有沒有例如閱讀Model-View-ViewModel模式?這是一個很好的演示:http://blog.lab49.com/archives/2650 –
你的問題看起來像是一篇關於依賴屬性的文章。我甚至沒有讀過它。 –
@Corey Kosak,如果您發表您的評論作爲答案,我會檢查解決方案,因爲它提供了最好的一步一步的理解,而無需購買一本書。 – DRapp