Pergunta

Quando quero vincular um controle a uma propriedade do meu objeto, eu tenho que fornecer o nome da propriedade como uma string. Isso não é muito bom porque:

  1. Se a propriedade for removido ou renomeado, eu não obter um compilador Atenção.
  2. Se uma renomeação da propriedade com uma ferramenta de refatoração, é provavelmente os dados de ligação não será Atualizada.
  3. Eu não recebo um erro até tempo de execução se o tipo da propriedade é errada, por exemplo ligação a um número inteiro um seletor de data.

Existe a-padrão de design que fica em volta deste, mas ainda tem a facilidade de utilização dos dados de ligação?

(Este é um problema no WinForm, Asp.net e WPF e a maioria dos lotes prováveis ??de outros sistemas)

Eu já encontrado " soluções alternativas para nameof () operador em C # : typesafe databinding ", que também tem um bom ponto de partida para uma solução

.

Se você estiver disposto a usar um processador de pós depois de compilar seu código, notifypropertyweaver é bem a pena olhar a.


Qualquer um sabe de uma solução boa para WPF quando as ligações são feitas em XML em vez de C #?

Foi útil?

Solução

Graças ao Oliver por me começou agora tenho uma solução que ambos os suportes refatoração e é o tipo de seguro. Também deixe-me implementar INotifyPropertyChanged para que ele lida com propriedades que estão sendo renomeado.

É de uso parece com:

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

Os shows de classe pessoa Como implementado INotifyPropertyChanged de uma forma segura tipo (ou ver esta resposta para uma outra bastante agradável maneira de implementar INotifyPropertyChanged, ActiveSharp - Automatic INotifyPropertyChanged também parece bom):

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

Os WinForms ligação classe auxiliar tem a carne em que faz todo o trabalho:

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

Este faz uso de um monte de coisas novas em C # 3.5 e mostra apenas o que é possível. Agora, se tivéssemos higiênico macros lisp programador pode parar de chamar-nos cidadãos de segunda classe)

Outras dicas

O operador nameof foi implementado em C # 6.0 com .NET 4.6 e VS2015 em julho de 2015. O seguinte ainda é válida para C # <6.0

Para evitar cordas que contêm nomes de propriedade, eu escrevi uma classe simples usando árvores de expressão para retornar o nome do membro:

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

Você pode usar essa classe como segue. Mesmo que você pode usá-lo somente no código (assim não em XAML), é bastante útil (pelo menos para mim), mas o seu código ainda não é typesafe. Você poderia estender o método de nome com um segundo argumento de tipo que define o valor de retorno da função, o que restringe o tipo da propriedade.

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

Até agora eu não encontrei nada que resolve o problema de ligação de dados typesafety.

Atenciosamente

O Framework 4.5 fornece-nos com a CallerMemberNameAttribute , que faz passar o nome da propriedade como uma seqüência desnecessária:

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

Se você está trabalhando em Framework 4.0 com KB2468871 instalado, você pode instalar o Microsoft BCL Compatibility pack via NuGet , que também fornece este atributo.

Este blog href="http://blog.quantumbitdesigns.com/2010/01/26/mvvm-lambda-vs-inotifypropertychanged-vs-dependencyobject/" levanta algumas boas perguntas sobre o desempenho desta abordagem . Você poderia melhorar essas deficiências, convertendo a expressão para uma string como parte de algum tipo de inicialização estática.

Os mecanismos reais pode ser um pouco sem graça, mas ainda assim seria tipo seguro, e aproximadamente o mesmo desempenho para o INotifyPropertyChanged matéria.

Algo tipo de como isto:

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

Uma maneira de obter feedback se suas ligações são quebradas, é criar um DataTemplate e declarar seu tipo de dados a ser o tipo de ViewModel que se liga a, por exemplo, se você tem um PersonView e uma PersonViewModel você faria o seguinte:

  1. declarar um DataTemplate com tipo de dados = PersonViewModel e uma chave (por exemplo PersonTemplate)

  2. Cut todos PersonView xaml e colá-lo no modelo de dados (que, idealmente, pode apenas estar no topo da PersonView.

3a. Criar um ContentControl e definir o ContentTemplate = PersonTemplate e ligam o seu conteúdo para o PersonViewModel.

3b. Outra opção é não dar uma chave para o DataTemplate e não definir o ContentTemplate do ContentControl. Neste caso WPF vai descobrir o que DataTemplate de usar, uma vez que sabe que tipo de objeto que você está ligado. Ele irá procurar a árvore e encontrar o seu DataTemplate e, uma vez que corresponde ao tipo da ligação, ele irá aplicá-lo automaticamente como o ContentTemplate.

Você acaba com essencialmente a mesma vista como antes, mas desde que você mapeou o DataTemplate para um tipo de dados subjacente, ferramentas como ReSharper pode lhe dar feedback (via identificadores de cores - ReSharper-Opções-Configurações-Cor Identifiers) a wether seu ligações são quebradas ou não.

Você ainda não terá avisos do compilador, mas pode visualmente verificar se há ligações quebradas, o que é melhor do que ter que verificar e para trás entre seu ponto de vista e viewmodel.

Outra vantagem desta informação adicional que você dá, é que ele também pode ser usado em renomear refatorações. Tanto quanto eu me lembro ReSharper é capaz de mudar o nome automaticamente ligações no DataTemplates digitadas quando o subjacente nome da propriedade de ViewModel é alterada e vice-versa.

1. Se a propriedade for removido ou renomeado, eu não receber um aviso do compilador.

2. Se uma renomeação a propriedade com uma ferramenta de refatoração, é provável ligação de dados não será atualizado.

3. Eu não receber um erro até a execução se o tipo da propriedade é errado, por exemplo, vinculativo um inteiro para um seletor de data.

Sim, Ian, que são exatamente os problemas com a ligação de dados nome-string conduzido. Você pediu a-padrão de design. Eu projetei o padrão de tipo seguro Model View (TVM), que é uma concretização da parte Ver Modelo do padrão Model-View-ViewModel (MVVM). Ele é baseado em uma ligação de tipo seguro, semelhante a sua própria resposta. Eu só postou uma solução para WPF:

http: // www.codeproject.com/Articles/450688/Enhanced-MVVM-Design-w-Type-Safe-View-Models-TVM

x: bind (também chamado de "ligações de dados compilados") para XAML (app universal) no Windows 10 e Windows Phone 10 pode resolver este problema, consulte https://channel9.msdn.com/Events/Build/2015/3-635

Não consigo encontrar o em docs linha para ele, mas não colocar muito esforço em, pois é algo que não vai usar por algum tempo. No entanto, esta resposta deve ser um indicador útil para outras pessoas.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top