Попытка понять DependencyProperty
быть новым для WPF и его, по-видимому, удивительная способность изменять, связывать, включать и иным образом манипулировать. Я пытаюсь получить мысленный обзор того, что происходит, и надеюсь, что некоторые могут подтвердить или исправить мои показания.
перед WPF у вас есть делегаты и события. У вас может быть дюжина элементов управления прослушиванием (через регистрацию события), поэтому, когда событие срабатывает, все другие элементы управления будут уведомлены автоматически и могут действовать, однако они были так закодированы. Такие как...
из кода позади, вы бы сделали что-то вроде
GotFocus += MyMethodToDoSomething;
тогда метод подписи
private void MyMethodToDoSomething(object sender, RoutedEventArgs e)
{
.. do whatever
}
кроме того, используя стандартный геттер / сеттер, сеттер может вызывать свои собственные методы в своем собственном классе, чтобы делать что-то каждый раз, когда кто-то пытается получить или установить значение
private int someValue;
public int SomeValue
{
get { this.DoSomeOtherThing();
return someValue;
}
set { this.DoAnotherThing();
someValue = value;
}
Теперь есть свойства зависимостей, и одно/двухстороннюю привязку. Я понимаю (я думаю) об одном способе моделирования больше только для чтения операция.
во всяком случае, с двухсторонней привязкой зависимости автоматически уведомляют кого-либо "в зависимости" от изменения источника или цели соответственно, без явной проверки, если что-то подписалось на событие, платформа автоматически обрабатывает объявление об изменении соответствующих элементов управления (цель или источник).
Итак, позвольте мне через этот сценарий со старой формой добавления/редактирования сохранения/отмены обслуживания. В более старой структуре, если кто-то нажал при нажатии кнопки "Добавить" или "изменить" все поля ввода данных будут "включены" с пустыми данными для новой записи или редактирования существующих данных. В то же время кнопки добавления/редактирования будут отключены, но кнопки сохранения/отмены теперь будут включены.
аналогично, когда закончите с помощью Save/Cancel, он отключит все поля ввода, сохранит/отменит и повторно включит кнопки добавления / редактирования.
Я не совсем понимаю, как такой сценарий будет обрабатываться в этом сценарии свойств зависимостей (пока), но я близок? Я также понимаю, что вы можете привязываться практически ко всему, включая цветовые схемы, Показать/Скрыть, шрифты и т. д... Но я делаю небольшие шаги, пытаясь по-настоящему понять это.
спасибо.
3 ответов
плакат попросил, чтобы я перепечатал свой комментарий в качестве ответа. Рад помочь :-)
- видео презентация я говорил: http://blog.lab49.com/archives/2650
- бонусная ссылка: удивительная статья WPF в MSDN: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 так много вещей, что очень трудно держать все это в голове, когда вы его изучаете.
материал геттера / сеттера является особенностью обычных свойств C#. Он не уникален для WPF.
этот односторонний / двусторонний материал говорит о привязке данных WPF, которая не требует создания свойств зависимостей - просто использовать их.
свойства зависимостей встроены в сами управления. Они позволяют напрямую ссылаться на эти свойства при добавлении экземпляров элемента управления в форме. Они позволяют вашему custom control чувствовать себя немного больше "родной."
вообще они привыкли к реализовать свойство, которое может использовать привязку данных. В ваших приложениях вы будете в основном просто использовать привязка данных, а не реализация новых крючков для него.
... если кто-то нажмет на кнопку Добавить или изменить, все поля ввода данных будут "включены" с пустыми данными для новой записи или редактирования существующих данных. В то же время кнопки добавления/редактирования будут отключены, но Кнопки Сохранить/Отменить теперь будут включены.
аналогично, когда закончите с помощью Save/Cancel, он отключит все поля ввода, сохранит/отменит и повторно включит кнопки добавления / редактирования.
я бы выполнил то, что вы хотите выполнить с:
- модели
- привязка данных представления к этой модели представления
- выставление ICommand на этой модели представления (для кнопок)
- INotifyPropertyChanged для модель представления (для всех свойств)
для этого сценария не требуется создавать новые свойства зависимостей. Вы просто будете использовать существующие для привязки данных.
вот пример кода / учебник по выполнению WPF с привязкой данных и стилем MVVM.
настройка проекта
я создал приложение WPF в Мастере нового проекта и назвал его MyProject
.
я настроил имя проекта и пространства имен на соответствует общепринятой схеме вещей. Вы должны установить эти свойства в обозревателе решений - > проект - > щелкните правой кнопкой мыши - > Свойства.
у меня также есть пользовательская схема папок, которую я люблю использовать для проектов WPF:
я вставил представление в свою собственную папку" View " для организационных целей. Это также отражается в пространстве имен, так как ваши пространства имен должны соответствовать вашим папкам (namespace MyCompany.MyProject.View
).
I также отредактировано 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 и не использовать любой код. Я позволю дизайнеру делать то, что он хочет, но я не буду касаться этого кода.
создание модели вида
Далее I сделаю свой вид модели. Это должно быть разработано таким образом, чтобы оно обслуживало представление, но в идеале могло быть независимым от представления. Я не буду беспокоиться об этом слишком много, но дело в том, что ты не есть иметь четность 1-к-1 элементов управления видом и объектов модели вида.
я стараюсь, чтобы мои представления/модели представлений имели смысл в большем контексте приложения, поэтому я начну использовать модель представления здесь. Мы сделаем эту "редактируемую форму" записью rolodex.
мы создадим вспомогательный класс, который нам нужен...
ViewModel / 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;
}
}
}
ViewModel / 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
}
}
как обычно для этого типа рабочего процесса, есть некоторые требования, которые я пропустил при первоначальном создании представления. Например, я понял, что имеет смысл иметь функцию "revert", которая отменяет изменения, но сохраняет диалог открытым. Я также понял, что могу повторно использовать редактирование кнопка для этой цели. Поэтому я сделал свойство, которое я буду читать, чтобы получить имя кнопки редактирования.
модель представления содержит много кода, чтобы сделать что-то простое, но большая часть его является шаблоном для подключения свойств. Однако этот шаблон дает вам некоторую силу. Это помогает изолировать вас от вашего представления, поэтому ваше представление может радикально измениться без изменений или только незначительных изменений в модели представления.
если модель представления становится слишком большой, вы можете начать толкать ее в дополнительные модели sub view. Создайте их там, где это имеет наибольший смысл, и верните их как свойства в этой модели представления. Механизм привязки данных 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>
я здесь много работал. Во-первых, статический материал:
- я изменил название формы в соответствии с идеей Rolodex
- я добавил метки для полей, так как теперь я знаю, что они применяются к
- я изменил минимальную ширину/высоту, так как я заметил, что элементы управления были отрезаны
далее привязка данных:
- я привязал все текстовые поля к соответствующие свойства модели представления
- я сделал текстовые поля обновить вид модели на каждое нажатие (
UpdateSourceTrigger=PropertyChanged
). Это не обязательно для этого приложения, но может быть полезно в будущем. Я добавил его, чтобы избавить вас от поиска, когда вам это нужно:) - я связал
IsEnabled
поле каждого текстового поля вIsInEditMode
свойства - я привязал кнопки к соответствующим командам
- я связал имя кнопки редактирования (
Content
свойство) к соответствующему свойству на модели вида
> вот и результат
теперь работает вся логика пользовательского интерфейса, кроме тех, которые мы оставили Todo
комментировать. Я оставил их нереализованными, потому что они имеют отношение к определенной архитектуре приложения, и я не хотел вдаваться в это для этой демонстрации.
кроме того, vanilla WPF не имеет очень чистого способа MVVM закрыть форму это я знаю. Для этого можно использовать code-behind или одну из десятков библиотек надстроек WPF, которые предоставляют свой собственный более чистый способ.
Свойства Зависимостей
возможно, вы заметили, что я не создал ни одного пользовательского свойства зависимостей в своем коде. Свойства зависимостей, которые я использовал, были все на существующих элементах управления (например,Text
, Content
и Command
). Так это обычно работает в WPF, потому что привязка данных и стиль (в который я не попал) дает вам много вариантов. Это позволяет полностью настроить внешний вид, ощущение и действия встроенных элементов управления.
в предыдущей версии Windows GUI-инфраструктур, вам часто придется подкласс существующие элементы или создать пользовательские элементы управления, чтобы получить индивидуальный внешний вид. Единственные причины создания пользовательских элементов управления в WPF-это объединение шаблонов нескольких элементов управления повторно или создание совершенно нового элемента управления с нуля.
Е. Г. если вы создавали текстовое поле автозаполнения, которое сопряжено с всплывающим элементом управления для отображения значений, из которых оно автоматически заполняется. В этом случае может потребоваться создать пользовательский элемент управления со свойствами пользовательских зависимостей (например, источник автоматического завершения). Таким образом, вы можете повторно использовать элемент управления во всем приложении и других приложениях.
если вы не создаете пользовательские элементы управления или не создаете специальные классы без пользовательского интерфейса, которые могут непосредственно создавать экземпляры и использовать в XAML и привязке данных с, вам, вероятно, не нужно будет создавать свойства зависимостей.
простой способ взглянуть на них заключается в том, что они являются свойствами, которые указывают на другое свойство.
на самом деле это определение свойства, которое определяет имя свойства, тип, значение по умолчанию и т. д., но фактическое значение свойства не сохраняется с определением свойства.
таким образом, вы можете сказать, что свойство кнопки 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" />