是的,有可能!
场景
- 在 UI 中有两个不同的
DateTimePicker 字段,一个代表日期,另一个代表时间。示例:
DateTimePicker dtpDate;
DateTimePicker dtpTime;
- 拥有一个具有单个
DateTime 属性的模型。示例:
public class MyModel
{
public DateTime? When;
}
我们想要做什么?
当任何提到的 UI 控件发生更改时,自动更改模型的 When 属性,反之亦然;
解决方案
使您的模型类可绑定并有两个额外的属性,它们就像When 属性和代表部分DateTime 值的字段之间的桥梁(代理)(例如:一个DateTimePicker 用于日期,另一个用于时间)。这些代理是必需的,因为当任何 UI 控件发生更改时,我们需要知道 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
}
现在您所要做的就是在 UI 控件的属性和模型属性之间创建绑定。示例:
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 中引入的,但您只需更改几行代码即可不使用它。