Да, это возможно!
Сценарий
- Имеют два отдельных поля
DateTimePicker
в пользовательском интерфейсе, одно из которых представляет дату, а другое представляет время.Пример:
DateTimePicker dtpDate;
DateTimePicker dtpTime;
Имейте модель с единственным свойством
DateTime
.Пример:
public class MyModel
{
public DateTime? When;
}
Что мы пытаемся сделать?
Изменить свойство модели When
автоматически при изменении любого изупомянутые элементы управления пользовательского интерфейса и наоборот;
Решение
Сделайте свой класс модели привязываемым и имеют 2 дополнительных свойства, которые действуют как мост (прокси) междусвойство When
и поля, представляющие частичное DateTime
значение (например: один DateTimePicker
для даты и другой для времени).Эти прокси необходимы, потому что при изменении любого из элементов управления пользовательского интерфейса нам нужно знать, какие компоненты / части DateTime
необходимо будет изменить.Это дата или время?
Теперь следующий код делает совсем немного.Во-первых, вы хотите реагировать на любые изменения любого из прокси (ProxyWhenDate
и ProxyWhenTime
), отражая это изменение в результирующем DateTime
(When
), и наоборот.Чтобы это работало, вам нужно сделать этот класс привязываемым.Это можно сделать, внедрив INotifyPropertyChanged
и реализовав пользовательские установщики свойств, которые вызывают событие PropertyChanged
с соответствующим именем свойства.
Пример:
public class MyModel : INotifyPropertyChanged
{
#region Bindable properties
private DateTime? _When;
public virtual DateTime? When
{
get { return _When; }
set
{
SetField(ref _When, value);
// When a change to `When` occurs, it means `ProxyWhenDate` and `ProxyWhenTime` have been changed as well,
// so we need to raise a `PropertyChanged` notification for both of them.
NotifyPropertyChanged(this.GetPropertyName((MyModel x) => x.ProxyWhenDate));
NotifyPropertyChanged(this.GetPropertyName((MyModel x) => x.ProxyWhenTime));
}
}
#endregion
#region Proxies
public virtual DateTime ProxyWhenDate
{
get
{
return When.HasValue ? When.Value : DateTime.UtcNow;
}
set
{
DateTime v = When.HasValue ? When.Value : DateTime.UtcNow;
// Change only Year + Month + Day, keeping the rest as it is.
When = new DateTime(value.Year, value.Month, value.Day, v.Hour, v.Minute, v.Second);
}
}
public virtual DateTime ProxyWhenTime
{
get
{
return When.HasValue ? When.Value : DateTime.UtcNow;
}
set
{
DateTime v = When.HasValue ? When.Value : DateTime.UtcNow;
// Change only Hour + Minute + Second, keeping the rest as it is.
When = new DateTime(v.Year, v.Month, v.Day, value.Hour, value.Minute, value.Second);
}
}
#endregion
#region Object extensions
public static string GetPropertyName<TSource, TField>(this Object obj, Expression<Func<TSource, TField>> Field)
{
return (Field.Body as MemberExpression ?? ((UnaryExpression)Field.Body).Operand as MemberExpression).Member.Name;
}
#endregion
#region Support for bindings
public virtual event PropertyChangedEventHandler PropertyChanged;
// NotifyPropertyChanged will raise the PropertyChanged event passing the
// source property that is being updated.
public virtual void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public virtual void NotifyPropertyChanged<TProperty>(Expression<Func<TProperty>> property)
{
var lambda = (LambdaExpression)property;
MemberExpression memberExpression;
if (lambda.Body is UnaryExpression)
{
var unaryExpression = (UnaryExpression)lambda.Body;
memberExpression = (MemberExpression)unaryExpression.Operand;
}
else
{
memberExpression = (MemberExpression)lambda.Body;
}
NotifyPropertyChanged(memberExpression.Member.Name);
}
public virtual bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value))
return false;
field = value;
NotifyPropertyChanged(propertyName);
return true;
}
#endregion
}
Сейчасвсе, что вам нужно сделать, это создать привязки между свойствами элементов управления пользовательского интерфейса и свойствами вашей модели.Пример:
public partial class MyForm : Form
{
private MyModel Model;
public MyForm(MyModel model)
{
InitializeComponent();
Model = model;
// Create our bindings
dtpDate.DataBindings.Add(new Binding("Value", Model,
this.GetPropertyName((MyModel x) => x.ProxyWhenDate)));
dtpTime.DataBindings.Add(new Binding("Value", Model,
this.GetPropertyName((MyModel x) => x.ProxyWhenTime)));
}
}
Готово!Наслаждайтесь волшебством и извините за опоздание на ~ 6 лет; -)
ПРИМЕЧАНИЕ. Атрибут CallerMemberName
был введен в .NET Framework 4.5, но вы можете жить без него, изменив еговсего несколько строк кода.