为了账号安全,请及时绑定邮箱和手机立即绑定

如何使数据绑定类型安全并支持重构

如何使数据绑定类型安全并支持重构

RISEBY 2019-10-05 13:47:16
当我希望将控件绑定到对象的属性时,必须提供属性的名称作为字符串。这不是很好,因为:如果该属性被删除或重命名,则不会收到编译器警告。如果使用重构工具重命名该属性,则很可能不会更新数据绑定。如果属性的类型错误,直到运行时我都不会出错,例如,将整数绑定到日期选择器。是否有一种设计模式可以解决此问题,但仍易于使用数据绑定?(这是WinForm,Asp.net和WPF以及大多数其他系统中的问题)现在,我发现“ C#中的nameof()运算符的变通办法:typesafe数据绑定 ”也为解决方案提供了良好的起点。如果您愿意在编译代码后使用后处理器,那么notifypropertyweaver非常值得研究。当用XML而不是C#进行绑定时,有人知道WPF的一个好的解决方案吗?
查看完整描述

3 回答

?
largeQ

TA贡献2039条经验 获得超7个赞

感谢Oliver让我入门,现在我有了一个既支持重构又是类型安全的解决方案。它也让我实现INotifyPropertyChanged,以便处理重命名的属性。


它的用法如下:


checkBoxCanEdit.Bind(c => c.Checked, person, p => p.UserCanEdit);

textBoxName.BindEnabled(person, p => p.UserCanEdit);

checkBoxEmployed.BindEnabled(person, p => p.UserCanEdit);

trackBarAge.BindEnabled(person, p => p.UserCanEdit);


textBoxName.Bind(c => c.Text, person, d => d.Name);

checkBoxEmployed.Bind(c => c.Checked, person, d => d.Employed);

trackBarAge.Bind(c => c.Value, person, d => d.Age);


labelName.BindLabelText(person, p => p.Name);

labelEmployed.BindLabelText(person, p => p.Employed);

labelAge.BindLabelText(person, p => p.Age);

person类显示了如何以一种类型安全的方式实现INotifyPropertyChanged(或参见以下答案以实现实现INotifyPropertyChanged的另一种相当不错的方法,ActiveSharp-自动INotifyPropertyChanged也看起来不错):


public class Person : INotifyPropertyChanged

{

   private bool _employed;

   public bool Employed

   {

      get { return _employed; }

      set

      {

         _employed = value;

         OnPropertyChanged(() => c.Employed);

      }

   }


   // etc


   private void OnPropertyChanged(Expression<Func<object>> property)

   {

      if (PropertyChanged != null)

      {

         PropertyChanged(this, 

             new PropertyChangedEventArgs(BindingHelper.Name(property)));

      }

   }


   public event PropertyChangedEventHandler PropertyChanged;

}

WinForms绑定帮助程序类包含了使它们全部工作的基础:


namespace TypeSafeBinding

{

    public static class BindingHelper

    {

        private static string GetMemberName(Expression expression)

        {

            // The nameof operator was implemented in C# 6.0 with .NET 4.6

            // and VS2015 in July 2015. 

            // The following is still valid for C# < 6.0


            switch (expression.NodeType)

            {

                case ExpressionType.MemberAccess:

                    var memberExpression = (MemberExpression) expression;

                    var supername = GetMemberName(memberExpression.Expression);

                    if (String.IsNullOrEmpty(supername)) return memberExpression.Member.Name;

                    return String.Concat(supername, '.', memberExpression.Member.Name);

                case ExpressionType.Call:

                    var callExpression = (MethodCallExpression) expression;

                    return callExpression.Method.Name;

                case ExpressionType.Convert:

                    var unaryExpression = (UnaryExpression) expression;

                    return GetMemberName(unaryExpression.Operand);

                case ExpressionType.Parameter:

                case ExpressionType.Constant: //Change

                    return String.Empty;

                default:

                    throw new ArgumentException("The expression is not a member access or method call expression");

            }

        }


        public static string Name<T, T2>(Expression<Func<T, T2>> expression)

        {

            return GetMemberName(expression.Body);

        }


        //NEW

        public static string Name<T>(Expression<Func<T>> expression)

        {

           return GetMemberName(expression.Body);

        }


        public static void Bind<TC, TD, TP>(this TC control, Expression<Func<TC, TP>> controlProperty, TD dataSource, Expression<Func<TD, TP>> dataMember) where TC : Control

        {

            control.DataBindings.Add(Name(controlProperty), dataSource, Name(dataMember));

        }


        public static void BindLabelText<T>(this Label control, T dataObject, Expression<Func<T, object>> dataMember)

        {

            // as this is way one any type of property is ok

            control.DataBindings.Add("Text", dataObject, Name(dataMember));

        }


        public static void BindEnabled<T>(this Control control, T dataObject, Expression<Func<T, bool>> dataMember)

        {       

           control.Bind(c => c.Enabled, dataObject, dataMember);

        }

    }

}

这利用了C#3.5中的许多新内容,并显示了可能的结果。现在,如果只有卫生宏,那么 Lisp程序员可能会停止称我们为二等公民)


查看完整回答
反对 回复 2019-10-05
?
天涯尽头无女友

TA贡献1831条经验 获得超9个赞

Framework 4.5为我们提供了CallerMemberNameAttribute,从而无需将属性名称作为字符串传递:


private string m_myProperty;

public string MyProperty

{

    get { return m_myProperty; }

    set

    {

        m_myProperty = value;

        OnPropertyChanged();

    }

}


private void OnPropertyChanged([CallerMemberName] string propertyName = "none passed")

{

    // ... do stuff here ...

}

如果您正在使用安装了KB2468871的 Framework 4.0进行操作,则可以通过nuget来安装Microsoft BCL兼容性包,该包也提供了此属性。


查看完整回答
反对 回复 2019-10-05
  • 3 回答
  • 0 关注
  • 380 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信