Pregunta

Cuando deseo vincular un control a una propiedad de mi objeto, debo proporcionar el nombre de la propiedad como una cadena.Esto no es muy bueno porque:

  1. Si la propiedad se elimina o se renombra, no recibo una advertencia de compilador.
  2. Si un cambio de nombre de la propiedad con una herramienta de refactorización, es probable que no se actualice el enlace de datos.
  3. No recibo un error hasta el tiempo de ejecución si el tipo de propiedad está mal, por ejemplo,vinculando a un entero a un selector de citas.

¿Existe algún patrón de diseño que solucione esto, pero que aún tenga la facilidad de uso del enlace de datos?

(Este es un problema en WinForm, Asp.net y WPF y probablemente en muchos otros sistemas)

Ahora he encontrado "Soluciones alternativas para el operador nameof() en C#:enlace de datos con seguridad de tipos"Esto también es un buen punto de partida para una solución.

Si está dispuesto a utilizar un posprocesador después de compilar su código, notificarpropertyweaver Vale la pena mirarlo.


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

¿Fue útil?

Solución

Gracias a Oliver por ayudarme a empezar, ahora tengo una solución que admite la refactorización y es segura para escribir.También me permitió implementar INotifyPropertyChanged para que pueda hacer frente al cambio de nombre de las propiedades.

Su uso es el siguiente:

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 muestra cómo implementar INotifyPropertyChanged de una manera segura (o ver esta respuesta para conocer otra forma bastante agradable de implementar INotifyPropertyChanged, ActiveSharp: INotifyPropertyChanged automático 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;
}

La clase auxiliar de enlace de WinForms tiene la esencia que hace que todo funcione:

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

Esto hace uso de muchas de las novedades de C# 3.5 y muestra lo que es posible.Ahora si tan solo tuviéramos macros higiénicas el programador lisp puede dejar de llamarnos ciudadanos de segunda clase)

Otros consejos

El operador nombredel fue implementado en C # 6.0 con .NET 4.6 y VS2015 en julio de 2015. El siguiente es todavía válida para C # <6,0

Para evitar cadenas que contienen los nombres de propiedades, he escrito una clase simple usando los á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. A pesar de que se puede usar solamente en el código (por lo que no en XAML), es bastante útil (al menos para mí), pero el código aún no está typesafe. Se podría extender el método Nombre con un segundo argumento que define el tipo de valor de retorno de la función, lo que limitaría el tipo de la propiedad.

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

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

Saludos

El Framework 4.5 nos proporciona la CallerMemberNameAttribute , que hace pasar el nombre de la propiedad como una cadena innecesaria:

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

Si usted está trabajando en Framework 4.0 con KB2468871 instalado, puede instalar el Microsoft BCL Paquete de compatibilidad a través de Nuget , que también proporciona este atributo.

Una forma de obtener retroalimentación si sus enlaces se rompen, es crear un DataTemplate y declarar su tipo de datos a ser el tipo de modelo de vista que se une a, por ejemplo si tiene un PersonView y una PersonViewModel que haría lo siguiente:

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

  2. Cortar todas xaml PersonView y pegarlo en la plantilla de datos (que idealmente solo puede ser en la parte superior de la PersonView.

3a. Crear un ContentControl y establecer el ContentTemplate = PersonTemplate y unirse a su contenido a la PersonViewModel.

3b. Otra opción es no dar una clave para la DataTemplate y no establecer el ContentTemplate del ContentControl. En este caso WPF se darán cuenta de lo que DataTemplate de usar, ya que sabe qué tipo de objeto que realizar la vinculación. Se buscará el árbol y encontrar su DataTemplate y ya que coincide con el tipo de la unión, se aplicará automáticamente como ContentTemplate.

Se termina con esencialmente la misma vista que antes, pero desde que ha correlacionado la DataTemplate a un tipo de datos subyacente, herramientas como ReSharper puede dar información (a través de identificadores de color - ReSharper-Opciones-Configuración-color Identificadores) como a wether su enlaces se rompen o no.

Todavía no recibirá advertencias del compilador, pero puede comprobar visualmente para los enlaces rotos, lo cual es mejor que tener que comprobar ida y vuelta entre la vista y el modelo de vista.

Otra de las ventajas de esta información adicional se da, es decir, que también puede ser utilizado en el cambio de nombre refactorizaciones. Por lo que yo recuerdo ReSharper es capaz de nombre fijaciones en DataTemplates mecanografiadas cuando el nombre de la propiedad del modelo de vista subyacente se cambia y viceversa.

x: bind (también llamados "enlaces de datos compilados") para XAML (aplicación universal) en Windows 10 y Windows Phone 10 puede resolver este problema, consulte https://channel9.msdn.com/Events/Build/2015/3-635

No puedo encontrar la documentación en línea para ella, pero no he puesto mucho esfuerzo en el, ya que es algo que no va a utilizar durante algún tiempo. Sin embargo, esta respuesta debe ser un indicador útil para otras personas.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top