Os conversores de valor são mais problemáticos do que valem?


20

Estou trabalhando em um aplicativo WPF com visualizações que exigem inúmeras conversões de valor. Inicialmente, minha filosofia (inspirada em parte por este animado debate sobre os discípulos XAML ) era que eu deveria fazer o modelo de exibição estritamente sobre o suporte aos requisitos de dados da exibição. Isso significava que quaisquer conversões de valor necessárias para transformar dados em coisas como visibilidades, pincéis, tamanhos etc. seriam tratadas com conversores de valor e conversores de vários valores. Conceitualmente, isso parecia bastante elegante. O modelo de visão e a visão teriam um propósito distinto e seriam bem dissociados. Uma linha clara seria traçada entre "dados" e "aparência".

Bem, depois de dar a essa estratégia "a velha faculdade tentar", estou com algumas dúvidas se quero continuar desenvolvendo dessa maneira. Na verdade, estou pensando fortemente em descartar os conversores de valor e colocar a responsabilidade por (quase) toda a conversão de valor diretamente nas mãos do modelo de exibição.

A realidade do uso de conversores de valor simplesmente não parece estar medindo o valor aparente de preocupações limpas. Meu maior problema com os conversores de valor é que eles são tediosos de usar. É necessário criar uma nova classe, implementar IValueConverterou IMultiValueConverterconverter os valores objectpara o tipo correto, testar DependencyProperty.Unset(pelo menos para conversores de vários valores), escrever a lógica de conversão, registrar o conversor em um dicionário de recursos [ver atualização abaixo ] e, finalmente, conecte o conversor usando XAML bastante detalhado (que requer o uso de seqüências de caracteres mágicas para a (s) ligação (ões) e o nome do conversor[veja atualização abaixo]). O processo de depuração também não é um piquenique, pois as mensagens de erro geralmente são enigmáticas, especialmente no modo de design do Visual Studio / Expression Blend.

Isso não quer dizer que a alternativa - tornar o modelo de visualização responsável por toda a conversão de valor - seja uma melhoria. Isso poderia muito bem ser uma questão de a grama ser mais verde do outro lado. Além de perder a elegante separação de preocupações, você deve escrever várias propriedades derivadas e ligar conscientemente RaisePropertyChanged(() => DerivedProperty)ao definir propriedades básicas, o que pode ser um problema desagradável de manutenção.

A seguir, é apresentada uma lista inicial que reuni os prós e os contras de permitir que os modelos de exibição manejem a lógica de conversão e acabem com os conversores de valor:

  • Prós:
    • Menos ligações totais desde que os multi-conversores são eliminados
    • Menos seqüências de caracteres mágicas (caminhos de ligação + nomes de recursos do conversor )
    • Não é mais necessário registrar cada conversor (além de manter esta lista)
    • Menos trabalho para escrever cada conversor (não são necessárias interfaces de implementação ou conversão)
    • Pode injetar facilmente dependências para ajudar nas conversões (por exemplo, tabelas de cores)
    • A marcação XAML é menos detalhada e mais fácil de ler
    • A reutilização do conversor ainda é possível (embora seja necessário algum planejamento)
    • Sem problemas misteriosos com DependencyProperty.Unset (um problema que notei com conversores de vários valores)

* As rasuras indicam os benefícios que desaparecem se você usar extensões de marcação (consulte a atualização abaixo)

  • Contras:
    • Acoplamento mais forte entre o modelo de vista e a vista (por exemplo, as propriedades devem lidar com conceitos como visibilidade e pincéis)
    • Mais propriedades totais para permitir o mapeamento direto para todas as ligações vistas
    • RaisePropertyChangeddeve ser chamado para cada propriedade derivada (consulte a Atualização 2 abaixo)
    • Ainda deve contar com conversores se a conversão for baseada na propriedade de um elemento da interface do usuário

Então, como você provavelmente pode dizer, eu tenho um pouco de azia sobre esse problema. Estou muito hesitante em seguir o caminho da refatoração apenas para perceber que o processo de codificação é tão ineficiente e tedioso se eu uso conversores de valor ou expondo inúmeras propriedades de conversão de valor no meu modelo de exibição.

Estou faltando algum prós / contras? Para aqueles que tentaram os dois meios de conversão de valor, qual você achou que funcionou melhor para você e por quê? Há alguma outra alternativa? (Os discípulos mencionaram algo sobre os provedores de descritores de tipo, mas eu não conseguia entender o que eles estavam falando. Qualquer visão sobre isso seria apreciada.)


Atualizar

Descobri hoje que é possível usar algo chamado "extensão de marcação" para eliminar a necessidade de registrar conversores de valor. De fato, ele não apenas elimina a necessidade de registrá-los, mas também fornece inteligência para selecionar um conversor quando você digita Converter=. Aqui está o artigo que me iniciou: http://www.wpftutorial.net/ValueConverters.html .

A capacidade de usar uma extensão de marcação altera um pouco o equilíbrio na minha lista de prós e contras e na discussão acima (consulte tacadas).

Como resultado dessa revelação, estou experimentando um sistema híbrido no qual uso conversores BoolToVisibilitye o que chamo de MatchToVisibilitymodelo de exibição para todas as outras conversões. MatchToVisibility é basicamente um conversor que permite verificar se o valor associado (geralmente uma enumeração) corresponde a um ou mais valores especificados em XAML.

Exemplo:

Visibility="{Binding Status, Converter={vc:MatchToVisibility
            IfTrue=Visible, IfFalse=Hidden, Value1=Finished, Value2=Canceled}}"

Basicamente, o que isso faz é verificar se o status é Concluído ou Cancelado. Se for, a visibilidade será definida como "Visível". Caso contrário, ele obtém conjuntos para "Oculto". Isso acabou sendo um cenário muito comum, e ter esse conversor me salvou cerca de 15 propriedades no meu modelo de exibição (mais as instruções RaisePropertyChanged associadas). Observe que, quando você digita Converter={vc:, "MatchToVisibility" aparece em um menu intellisense. Isso reduz visivelmente a chance de erros e torna o uso de conversores de valor menos tedioso (você não precisa se lembrar ou procurar o nome do conversor de valor que deseja).

Caso você esteja curioso, colarei o código abaixo. Uma característica importante deste implementação MatchToVisibilityé que ele verifica para ver se o valor limite é um enum, e se for, ele verifica para certificar-se Value1, Value2etc. também são enums do mesmo tipo. Isso fornece uma verificação em tempo de design e tempo de execução se algum dos valores da enumeração está digitado incorretamente. Para melhorar isso para uma verificação em tempo de compilação, você pode usar o seguinte (digitei manualmente, por favor, perdoe-me se cometi algum erro):

Visibility="{Binding Status, Converter={vc:MatchToVisibility
            IfTrue={x:Type {win:Visibility.Visible}},
            IfFalse={x:Type {win:Visibility.Hidden}},
            Value1={x:Type {enum:Status.Finished}},
            Value2={x:Type {enum:Status.Canceled}}"

Embora isso seja mais seguro, é muito detalhado para valer a pena para mim. É melhor usar apenas uma propriedade no modelo de exibição se eu fizer isso. De qualquer forma, estou descobrindo que a verificação do tempo de design é perfeitamente adequada para os cenários que tentei até agora.

Aqui está o código para MatchToVisibility

[ValueConversion(typeof(object), typeof(Visibility))]
public class MatchToVisibility : BaseValueConverter
{
    [ConstructorArgument("ifTrue")]
    public object IfTrue { get; set; }

    [ConstructorArgument("ifFalse")]
    public object IfFalse { get; set; }

    [ConstructorArgument("value1")]
    public object Value1 { get; set; }

    [ConstructorArgument("value2")]
    public object Value2 { get; set; }

    [ConstructorArgument("value3")]
    public object Value3 { get; set; }

    [ConstructorArgument("value4")]
    public object Value4 { get; set; }

    [ConstructorArgument("value5")]
    public object Value5 { get; set; }

    public MatchToVisibility() { }

    public MatchToVisibility(
        object ifTrue, object ifFalse,
        object value1, object value2 = null, object value3 = null,
        object value4 = null, object value5 = null)
    {
        IfTrue = ifTrue;
        IfFalse = ifFalse;
        Value1 = value1;
        Value2 = value2;
        Value3 = value3;
        Value4 = value4;
        Value5 = value5;
    }

    public override object Convert(
        object value, Type targetType, object parameter, CultureInfo culture)
    {
        var ifTrue = IfTrue.ToString().ToEnum<Visibility>();
        var ifFalse = IfFalse.ToString().ToEnum<Visibility>();
        var values = new[] { Value1, Value2, Value3, Value4, Value5 };
        var valueStrings = values.Cast<string>();
        bool isMatch;
        if (Enum.IsDefined(value.GetType(), value))
        {
            var valueEnums = valueStrings.Select(vs => vs == null ? null : Enum.Parse(value.GetType(), vs));
            isMatch = valueEnums.ToList().Contains(value);
        }
        else
            isMatch = valueStrings.Contains(value.ToString());
        return isMatch ? ifTrue : ifFalse;
    }
}

Aqui está o código para BaseValueConverter

// this is how the markup extension capability gets wired up
public abstract class BaseValueConverter : MarkupExtension, IValueConverter
{
    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        return this;
    }

    public abstract object Convert(
        object value, Type targetType, object parameter, CultureInfo culture);

    public virtual object ConvertBack(
        object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

Aqui está o método de extensão ToEnum

public static TEnum ToEnum<TEnum>(this string text)
{
    return (TEnum)Enum.Parse(typeof(TEnum), text);
}

Atualização 2

Desde que publiquei essa pergunta, deparei-me com um projeto de código aberto que usa "IL weaving" para injetar o código NotifyPropertyChanged para propriedades e propriedades dependentes. Isso torna a implementação da visão de Josh Smith do modelo de visualização como um "conversor de valor em esteróides" uma brisa absoluta. Você pode simplesmente usar "Propriedades Auto-Implementadas", e o tecelão fará o resto.

Exemplo:

Se eu inserir este código:

public string GivenName { get; set; }
public string FamilyName { get; set; }

public string FullName
{
    get
    {
        return string.Format("{0} {1}", GivenName, FamilyName);
    }
}

... isto é o que é compilado:

string givenNames;
public string GivenNames
{
    get { return givenName; }
    set
    {
        if (value != givenName)
        {
            givenNames = value;
            OnPropertyChanged("GivenName");
            OnPropertyChanged("FullName");
        }
    }
}

string familyName;
public string FamilyName
{
    get { return familyName; }
    set 
    {
        if (value != familyName)
        {
            familyName = value;
            OnPropertyChanged("FamilyName");
            OnPropertyChanged("FullName");
        }
    }
}

public string FullName
{
    get
    {
        return string.Format("{0} {1}", GivenName, FamilyName);
    }
}

Isso representa uma enorme economia na quantidade de código que você precisa digitar, ler, rolar, etc. Mais importante, porém, evita que você precise descobrir quais são suas dependências. Você pode adicionar novas "propriedades obtidas", FullNamesem precisar subir meticulosamente a cadeia de dependências para adicionar RaisePropertyChanged()chamadas.

Como é chamado esse projeto de código aberto? A versão original é chamada "NotifyPropertyWeaver", mas o proprietário (Simon Potter) criou uma plataforma chamada "Fody" para hospedar toda uma série de tecelões de IL. O equivalente a NotifyPropertyWeaver nesta nova plataforma é chamado PropertyChanged.Fody.

Se você preferir usar o NotifyPropertyWeaver (que é um pouco mais simples de instalar, mas não será necessariamente atualizado no futuro além das correções de bugs), aqui está o site do projeto: http://code.google.com/p/ notifypropertyweaver /

De qualquer maneira, essas soluções de tecelagem de IL alteram completamente o cálculo no debate entre o modelo de exibição em esteróides versus conversores de valor.


Apenas uma observação: BooleanToVisibilitypega um valor relacionado à visibilidade (verdadeiro / falso) e o converte em outro. Este parece ser o uso ideal de a ValueConverter. Por outro lado, MatchToVisibilityestá codificando a lógica de negócios no View(que tipos de itens devem estar visíveis). Na minha opinião, essa lógica deve ser empurrada para o ViewModel, ou ainda mais, para o que eu chamo de EditModel. O que o usuário pode ver deve ser algo em teste.
Scott Whitlock

@ Scott, esse é um bom argumento. O aplicativo em que estou trabalhando no momento não é realmente um aplicativo "comercial", onde existem diferentes níveis de permissão para os usuários, então eu não estava pensando nesse sentido. MatchToVisibilityparecia ser uma maneira conveniente de ativar alguns comutadores de modo simples (eu tenho uma visualização em particular com várias partes que podem ser ativadas e desativadas. Na maioria dos casos, as seções da visualização são até rotuladas (com x:Name) para corresponder ao modo eles correspondem.) Realmente não me ocorreu que isso seja "lógica de negócios", mas vou pensar um pouco no seu comentário.
Devuxer

Exemplo: digamos que você tenha um sistema estéreo que possa estar no modo rádio, CD ou MP3. Suponha que haja recursos visuais correspondentes a cada modo em diferentes partes da interface do usuário. Você pode (1) deixar a visualização decidir quais gráficos correspondem a qual modo e ativá-los / desativá-los de acordo, (2) expor propriedades no modelo de visualização para cada valor de modo (por exemplo, IsModeRadio, IsModeCD) ou (3) expor propriedades no modelo de visualização para cada elemento / grupo gráfico (por exemplo, IsRadioLightOn, IsCDButtonGroupOn). (1) pareceu um ajuste natural para a minha opinião, porque já possui reconhecimento de modo. O que você acha nesse caso?
devuxer

Esta é a pergunta mais longa que eu já vi em todo o SE! :]
Trejder

Respostas:


10

Eu usei ValueConvertersem alguns casos e coloquei a lógica ViewModelem outros. Meu sentimento é que um ValueConverterse torna parte da Viewcamada; portanto, se a lógica é realmente parte do View, coloque-o lá, caso contrário, coloque-o no ViewModel.

Pessoalmente, não vejo problema em ViewModellidar com Viewconceitos específicos, como Brushes, porque, em meus aplicativos, ViewModelsó existe uma superfície testável e vinculável para o View. No entanto, algumas pessoas colocam muita lógica de negócios no ViewModel(eu não) e, nesse caso, ViewModelé mais como parte de sua camada de negócios; portanto, nesse caso, eu não gostaria de coisas específicas do WPF lá.

Eu prefiro uma separação diferente:

  • View- Material WPF, às vezes não testável (como XAML e code-behind), mas também ValueConverters
  • ViewModel - classe testável e vinculável que também é específica do WPF
  • EditModel - parte da camada de negócios que representa meu modelo durante a manipulação
  • EntityModel - parte da camada de negócios que representa meu modelo como persistente
  • Repository- responsável pela persistência do EntityModelpara o banco de dados

Então, do jeito que eu faço, tenho pouco uso para ValueConverters

A maneira como me afastei de alguns dos seus "golpes" é tornar os meus ViewModelmuito genéricos. Por exemplo, um que ViewModeleu chamei ChangeValueViewModelimplementa uma propriedade Label e uma propriedade Value. No Viewexiste um Labelque se liga à propriedade Label e um TextBoxque se liga à propriedade Value.

Então, eu tenho um ChangeValueViewque é uma DataTemplatechave do ChangeValueViewModeltipo. Sempre que o WPF vê que ViewModelisso se aplica View. O construtor do meu ChangeValueViewModelpega a lógica de interação necessária para atualizar seu estado de EditModel(geralmente passando apenas a Func<string>) e a ação que precisa executar quando o usuário edita o Valor (apenas um Actionque executa alguma lógica no EditModel).

O pai ViewModel(para a tela) pega um EditModelem seu construtor e apenas instancia os elementos elementares apropriados ViewModel, como ChangeValueViewModel. Como o pai ViewModelestá injetando a ação a ser executada quando o usuário faz alguma alteração, ele pode interceptar todas essas ações e executar outras ações. Portanto, a ação de edição injetada para um ChangeValueViewModelpode se parecer com:

(string newValue) =>
{
    editModel.SomeField = newValue;
    foreach(var childViewModel in this.childViewModels)
    {
        childViewModel.RefreshStateFromEditModel();
    }
}

Obviamente, o foreachloop pode ser refatorado em outro lugar, mas o que isso faz é executar a ação, aplicá-lo ao modelo e, em seguida (supondo que o modelo tenha atualizado seu estado de alguma maneira desconhecida), diz a todas as crianças que ViewModeldevem obter seu estado. o modelo novamente. Se o estado mudou, eles são responsáveis ​​por executar seus PropertyChangedeventos, conforme necessário.

Isso lida com a interação entre, digamos, uma caixa de listagem e um painel de detalhes bastante bem. Quando o usuário seleciona uma nova opção, ele atualiza a EditModelopção e EditModelaltera os valores das propriedades expostas para o painel de detalhes. Os ViewModelfilhos responsáveis ​​por exibir as informações do painel de detalhes são automaticamente notificados de que precisam verificar novos valores e, se foram alterados, disparam seus PropertyChangedeventos.


/aceno com a cabeça. É bem parecido com o meu.
Ian

+1. Obrigado pela sua resposta, Scott, tenho praticamente as mesmas camadas que você e também não coloco lógica de negócios no modelo de exibição. (Para o registro, estou usando o EntityFramework Code First e tenho uma camada de serviço que se traduz entre os modelos de exibição e de entidade e vice-versa.) Portanto, considerando isso, acho que você está dizendo que não há muito custo para colocar toda / a maior parte da lógica de conversão na camada do modelo de visualização.
Devuxer # 13/11

@ DanM - Sim, eu concordo. Eu preferiria a conversão na ViewModelcamada. Nem todo mundo concorda comigo, mas isso depende de como sua arquitetura funciona.
Scott Whitlock

2
Eu ia dizer +1 depois de ler o primeiro parágrafo, mas depois li o seu segundo e discordo totalmente de colocar código específico da visualização nos ViewModels. A única exceção é se o ViewModel for criado especificamente para ficar atrás de uma View genérica (como a CalendarViewModelpara um CalendarViewUserControl ou a DialogViewModelpara a DialogView). Isso é apenas minha opinião embora :)
Rachel

@ Rachel - bem, se você continuasse lendo meu segundo parágrafo, veria que é exatamente isso que eu estava fazendo. :) Não há lógica de negócios nos meus ViewModels.
Scott Whitlock

8

Se a conversão for algo relacionado à exibição, como decidir a visibilidade de um objeto, determinar qual imagem exibir ou descobrir qual cor de pincel usar, eu sempre coloco meus conversores na exibição.

Se estiver relacionado aos negócios, como determinar se um campo deve ser mascarado ou se um usuário tiver permissão para executar uma ação, a conversão ocorrerá no meu ViewModel.

De seus exemplos, eu acho que você está perdendo um grande pedaço de WPF: DataTriggers. Você parece estar usando conversores para determinar valores condicionais, mas os conversores devem realmente ser para converter um tipo de dados em outro.

No seu exemplo acima

Exemplo: digamos que você tenha um sistema estéreo que possa estar no modo rádio, CD ou MP3. Suponha que haja recursos visuais correspondentes a cada modo em diferentes partes da interface do usuário. Você pode (1) deixar a visualização decidir quais gráficos correspondem a qual modo e ativá-los / desativá-los de acordo, (2) expor propriedades no modelo de visualização para cada valor de modo (por exemplo, IsModeRadio, IsModeCD) ou (3) expor propriedades no modelo de visualização para cada elemento / grupo gráfico (por exemplo, IsRadioLightOn, IsCDButtonGroupOn). (1) pareceu um ajuste natural para a minha opinião, porque já possui reconhecimento de modo. O que você acha nesse caso?

Eu usaria a DataTriggerpara determinar qual imagem exibir, não a Converter. Um conversor é para converter um tipo de dados em outro, enquanto um gatilho é usado para determinar algumas propriedades com base em um valor.

<Style x:Key="RadioImageStyle">
    <Setter Property="Source" Value="{StaticResource RadioImage}" />
    <Style.Triggers>
        <DataTrigger Binding="{Binding Mode}" Value="CD">
            <Setter Property="Source" Value="{StaticResource CDImage}" />
        </DataTrigger>
        <DataTrigger Binding="{Binding Mode}" Value="MP3">
            <Setter Property="Source" Value="{StaticResource MP3Image}" />
        </DataTrigger>
    </Style.Triggers>
</Style>

A única vez em que eu consideraria usar um conversor para isso é se o valor vinculado continha realmente os dados da imagem e eu precisava convertê-los em um tipo de dados que a interface do usuário pudesse entender. Por exemplo, se a fonte de dados contivesse uma propriedade chamada ImageFilePath, eu consideraria o uso de um Conversor para converter a sequência que contém o local do arquivo de imagem em um BitmapImageque possa ser usado como a Origem da minha imagem

<Style x:Key="RadioImageStyle">
    <Setter Property="Source" Value="{Binding ImageFilePath, 
            Converter={StaticResource StringPathToBitmapConverter}}" />
</Style>

O resultado final é que eu tenho um espaço de nomes de biblioteca cheio de conversores genéricos que convertem um tipo de dados em outro e raramente preciso codificar um novo conversor. Há ocasiões em que desejarei conversores para conversões específicas, mas elas não são frequentes o suficiente para que eu nunca me importe em escrevê-las.


+1. Você levanta alguns bons pontos. Eu já usei gatilhos antes, mas no meu caso, não estou trocando fontes de imagens (que são uma propriedade), estou trocando Gridelementos inteiros . Também estou tentando fazer coisas como definir pincéis para primeiro plano / plano de fundo / traçado com base nos dados do meu modelo de exibição e em uma paleta de cores específica definida no arquivo de configuração. Não tenho certeza se este é um ótimo ajuste para um gatilho ou um conversor. O único problema que estou tendo até agora ao colocar a maior parte da lógica de visualização no modelo de visualização é ligar todas as RaisePropertyChanged()chamadas.
Devuxer #

@ DanM Na verdade, eu faria todas essas coisas em um DataTrigger, mesmo trocando os elementos do Grid. Normalmente, coloco um local ContentControlonde meu conteúdo dinâmico deve estar e troco o ContentTemplategatilho. Eu tenho um exemplo no link a seguir, se você estiver interessado (role até a seção com o cabeçalho de Using a DataTrigger) rachel53461.wordpress.com/2011/05/05/28/…
Rachel #:

Eu usei modelos de dados e controles de conteúdo antes, mas nunca precisei de gatilhos porque sempre tive um modelo de visualização exclusivo para cada visualização. De qualquer forma, sua técnica faz todo o sentido e é bastante elegante, mas também é muito detalhada. Com o MatchToVisibility, ele pode ser reduzido para isso: <TextBlock Text="I'm a Person" Visibility={Binding ConsumerType, Converter={vc:MatchToVisibility IfTrue=Visible, IfFalse=Hidden, Value1=Person}}"e<TextBlock Text="I'm a Business" Visibility={Binding ConsumerType, Converter={vc:MatchToVisibility IfTrue=Visible, IfFalse=Hidden, Value1=Business}}"
devuxer 2/11/11

1

Depende do que você está testando , se houver.

Sem testes: misture o código de exibição com o ViewModel à vontade (você sempre pode refatorar posteriormente).
Testes no ViewModel e / ou inferior: use conversores.
Testes em camadas de modelo e / ou inferiores: misturar código de exibição com o ViewModel à vontade

ViewModel abstrai o modelo para a vista . Pessoalmente, eu usaria o ViewModel para pincéis, etc. e pularia os conversores. Teste nas camadas em que os dados estão em sua forma " mais pura " (ou seja, camadas de modelo ).


2
Pontos interessantes sobre o teste, mas acho que não estou vendo como a lógica do conversor no modelo de exibição prejudica a testabilidade do modelo de exibição? Certamente não estou sugerindo colocar controles de interface do usuário reais no modelo de exibição. Apenas ver-específicas imóveis como Visibility, SolidColorBrush, e Thickness.
Devuxer #

@ DanM: Se você estiver usando uma abordagem View-first , não há problema . No entanto, alguns usam uma abordagem ViewModel-first , na qual o ViewModel faz referência a uma View, isso pode ser problemático .
Jake Berger

Oi Jay, definitivamente uma abordagem de exibição em primeiro lugar. A visualização não sabe nada sobre o modelo de visualização, exceto os nomes das propriedades às quais ele precisa se ligar. Obrigado por acompanhar. +1.
Devuxer # 13/11

0

Provavelmente isso não resolverá todos os problemas mencionados, mas há dois pontos a serem considerados:

Primeiro, você precisa colocar o código do conversor em algum lugar da sua primeira estratégia. Você considera essa parte da visualização ou do modelo de visualização? Se faz parte da visualização, por que não colocar as propriedades específicas da visualização na visualização em vez do modelo de visualização?

Segundo, parece que o design do seu conversor não tenta modificar as propriedades reais dos objetos que já existem. Parece que eles já implementam INotifyPropertyChanged, então por que não usar criar um objeto de wrapper específico da visualização ao qual se vincular? Aqui está um exemplo simples:

public class RealData
{
    private bool mIsInteresting;
    public bool IsInteresting
    {
        get { return mIsInteresting; }
        set 
        {
            if (mIsInteresting != null) 
            {
                mIsInteresting = value;
                RaisePropertyChanged("IsInteresting");
            }
        }
    }
}

public class RealDataView
{
    private RealData mRealData;

    public RealDataView(RealData data)
    {
        mRealData = data;
        mRealData.PropertyChanged += OnRealDataPropertyChanged;
    }

    public Visibility IsVisiblyInteresting
    {
       get { return mRealData.IsInteresting ? Visibility.Visible : Visibility.Hidden; }
       set { mRealData.IsInteresting = (value == Visibility.Visible); }
    }

    private void OnRealDataPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == "IsInteresting") 
        {
            RaisePropertyChanged(this, "IsVisiblyInteresting");
        }
    }
}

Não pretendi sugerir que estou alterando propriedades do meu modelo de entidade diretamente na visualização ou no modelo de visualização. O modelo de vista é definitivamente uma camada diferente da minha camada de modelo de entidade. De fato, o trabalho que fiz até agora foi em visualizações somente leitura. Isso não quer dizer que meu aplicativo não envolva edição, mas não vejo conversões sendo feitas nos controles usados ​​para edição (portanto, assuma que todas as ligações sejam unidirecionais, exceto as seleções nas listas). Porém, um bom argumento sobre "visualizações de dados". Esse foi um conceito levantado no post dos discípulos da XAML a que me referi no topo da minha pergunta.
Devuxer #

0

Às vezes, é bom usar um conversor de valor para tirar proveito da virtualização.

Um exemplo disso é o que em um projeto em que tivemos que exibir dados com máscara de bits para centenas de milhares de células em uma grade. Quando decodificamos as máscaras de bits no modelo de exibição para cada célula, o programa demorou muito para ser carregado.

Porém, quando criamos um conversor de valor que decodificou uma única célula, o programa carregava em uma fração do tempo e era tão sensível quanto o conversor só é chamado quando o usuário está olhando para uma célula específica (e precisaria ser chamado apenas no máximo trinta vezes, sempre que o usuário mudou sua visualização na grade).

Não sei como a reclamação da MVVM foi essa solução, mas reduziu o tempo de carregamento em 95%.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.