63 votos

Cómo hacer que el Enlace de datos tipo de seguro y el apoyo de refactorización

Cuando quiero enlazar un control a una propiedad de mi objeto, me tiene que proporcionar el nombre de la propiedad como una cadena. Esto no es muy bueno porque:

  1. Si la propiedad se quita o cambia el nombre, no tengo un compilador aviso.
  2. Si un cambio de nombre de la propiedad con una herramienta de refactorización, es probablemente, el enlace de datos no será actualizado.
  3. No me sale un error que hasta tiempo de ejecución si el tipo de la propiedad está mal, por ejemplo, de unión de un número entero un selector de fecha.

Hay un diseño de patrón que se obtiene de la ronda este, pero todavía tiene la facilidad de uso de enlace de datos?

(Este es un problema en WinForm, Asp.net y WPF y lo más probable es que muchos de los otros sistemas)

Ahora he encontrado "soluciones para nameof operador() en C#: typesafe de enlace de datos", que también tiene un buen punto de partida para una solución.

Si usted está dispuesto a usar un post procesador después de compilar el código, notifypropertyweaver es bien vale la pena mirar.


Alguien sabe de una buena solución para WPF cuando los enlaces se realizan en XML en lugar de C#?

48voto

Ian Ringrose Puntos 19115

Gracias a Oliver para hacer que me inició, ahora tengo una solución que ambos soportes de refactorización y es el tipo de seguro. También permítanme implementar INotifyPropertyChanged así se arregla con las propiedades de ser rebautizado.

Su uso se ve así:

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);

La clase de persona se muestra cómo se implementa INotifyPropertyChanged en un tipo de manera segura (o ver esta respuesta para otro bastante agradable forma de aplicación de INotifyPropertyChanged, ActiveSharp - Automático INotifyPropertyChanged también se ve bien ):

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;
}

El WinForms unión de ayudante de clase de la carne en lo que hace todo el trabajo:

namespace TypeSafeBinding
{
    public static class BindingHelper
    {
        private static string GetMemberName(Expression expression)
        {
            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);
        }
    }
}

Esto hace que el uso de un montón de cosas nuevas en C# 3.5 y muestra sólo lo que es posible. Ahora, si tan sólo tuviéramos higiene macros de lisp programador puede dejar de llamarnos ciudadanos de segunda clase)

26voto

Oliver Hanappi Puntos 5141

Para evitar que las cadenas que contienen los nombres de propiedad, he escrito una clase simple de usar árboles de expresión para devolver el nombre del miembro:

using System;
using System.Linq.Expressions;
using System.Reflection;

public static class Member
{
    private static string GetMemberName(Expression expression)
    {
        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:
                return String.Empty;

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

    public static string Name<T>(Expression<Func<T, object>> expression)
    {
        return GetMemberName(expression.Body);
    }

    public static string Name<T>(Expression<Action<T>> expression)
    {
        return GetMemberName(expression.Body);
    }
}

Puede utilizar esta clase de la siguiente manera. Aunque sólo se puede usar en el código (por lo que no en XAML), es muy útil (al menos para mí), pero su código no es todavía typesafe. Se podría extender el Nombre del método con un segundo tipo de argumento que define el valor de retorno de la función, lo que podría limitar el tipo de la propiedad.

var name = Member.Name<MyClass>(x => x.MyProperty); // name == "MyProperty"

Hasta ahora no he encontrado nada que se soluciona el enlace de datos typesafety problema.

Saludos

21voto

takrl Puntos 3285

El framework 4.5 nos ofrece la CallerMemberNameAttribute, lo que hace que pasar el nombre de la propiedad como una cadena innecesarios:

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 ...
}

5voto

nedruod Puntos 750

Este blog el artículo plantea algunos interrogantes sobre el rendimiento de este enfoque. Puede mejorar esas deficiencias mediante la conversión de la expresión a una cadena como parte de algún tipo de inicialización estática.

La mecánica real podría ser un poco feo, pero no por ello deja de ser de tipo seguro, y aproximadamente el mismo rendimiento a la cruda INotifyPropertyChanged.

Algo como esto:

public class DummyViewModel : ViewModelBase
{
    private class DummyViewModelPropertyInfo
    {
        internal readonly string Dummy;

        internal DummyViewModelPropertyInfo(DummyViewModel model)
        {
            Dummy = BindingHelper.Name(() => model.Dummy);
        }
    }

    private static DummyViewModelPropertyInfo _propertyInfo;
    private DummyViewModelPropertyInfo PropertyInfo
    {
        get { return _propertyInfo ?? (_propertyInfo = new DummyViewModelPropertyInfo(this)); }
    }

    private string _dummyProperty;
    public string Dummy
    {
        get
        {
            return this._dummyProperty;
        }
        set
        {
            this._dummyProperty = value;
            OnPropertyChanged(PropertyInfo.Dummy);
        }
    }
}

3voto

Thorsten Lorenz Puntos 4419

Una manera de obtener retroalimentación si los enlaces están rotos, es crear una plantilla de datos y declarar su Tipo de datos a ser el tipo de la Perspective que se une, por ejemplo, si usted tiene un PersonView y un PersonViewModel, debe hacer lo siguiente:

  1. Declarar un DataTemplate con DataType = PersonViewModel y una clave (por ejemplo, PersonTemplate)

  2. Cortar todos los PersonView xaml y pegarlo en la plantilla de datos (que, idealmente, sólo puede ser en la parte superior de la PersonView.

3a. Crear un ContentControl y establecer el ContentTemplate = PersonTemplate y enlazar su Contenido a la PersonViewModel.

3b. Otra opción es no dar la clave a la plantilla de datos y no se establece la ContentTemplate de la ContentControl. En este caso WPF va a averiguar lo que DataTemplate para utilizar, ya que sabe qué tipo de objeto que son vinculantes. Buscar en el árbol y encontrar su plantilla de datos y ya que coincide con el tipo de la unión, se aplicará de forma automática como el ContentTemplate.

Se termina con esencialmente la misma vista que antes, pero desde que asigna la plantilla de datos a un subyacente Tipo de datos, herramientas como Resharper puede dar retroalimentación (a través de identificadores de Color - Resharper-Opciones-Configuración del Color de los Identificadores), como si los enlaces están rotos o no.

Todavía no consigue las advertencias del compilador, pero puede comprobar visualmente para enlaces rotos, que es mejor que tener que comprobar de ida y vuelta entre la vista y el viewmodel.

Otra ventaja de esta información adicional que le dan, es decir, que también puede ser utilizado en el cambio de nombre de refactorings. Que yo recuerde Resharper es capaz de cambiar automáticamente el nombre de los enlaces escrito DataTemplates cuando el subyacente ViewModel la propiedad de que se cambie el nombre y viceversa.

Iteramos.com

Iteramos es una comunidad de desarrolladores que busca expandir el conocimiento de la programación mas allá del inglés.
Tenemos una gran cantidad de contenido, y también puedes hacer tus propias preguntas o resolver las de los demás.

Powered by:

X