WPF: Como usar conversores sem criar recursos estáticos

Para não terem de criar um recurso estático sempre que querem usar um conversor, podem criar conversores que são simultaneamente extensões markup.

Normalmente declaram-se os conversores da seguinte maneira:

<UserControl.Resources>
    <my:BoolVisibilityConverter x:Key="visibilityConverter" />
</UserControl.Resources>

E depois chamam-se assim:

<Label
    Content="Please wait!"
    Visibility="{Binding IsBusy, Converter={StaticResource visibilityConverter}}" />

Não sei quanto a vocês, mas eu não retiro nenhum prazer em ter de declarar um conversor sempre que quero usar um.

Para ultrapassar esta inconveniência, podemos criar híbridos de conversores e extensões markup. Vi esta abordagem pela primeira vez no blog Dr. WPF.

Primeiro criamos uma classe template genérica que vai ser a base de todos os nossos conversores.

public abstract class ConverterMarkupExtension<T> : MarkupExtension, IValueConverter where T: class, new()
{
    private static T _converter = null;
    
    public ConverterMarkupExtension()
    {
    }
    
    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        return _converter ?? (_converter = new T());
    }
    
    public abstract object Convert(object value, Type targetType, object parameter, CultureInfo culture);
    public abstract object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture);
}

Depois herdamos dessa classe ao criar um conversor:

[ValueConversion(typeof(bool), typeof(Visibility))]
public class BoolVisibilityConverter : ConverterMarkupExtension<BoolVisibilityConverter>
{
    public BoolVisibilityConverter()
    {
    }
    
    public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value != null && value is bool)
        {
            if ((bool)value)
            {
                return Visibility.Visible;
            }
        }
        
        return Visibility.Collapsed;
    }
    
    public override object ConvertBack(object value, Type targetType, object parameter,  CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

Agora sempre que queremos usar o conversor não temos de o declarar explicitamente. Apenas fazemos assim:

<Label
    Content="Please wait!"
    Visibility="{Binding IsBusy, Converter={my:BoolVisibilityConverter}}" />

Podem-se estar a perguntar: porque é que precisamos de uma classe template genérica? Porque não remover o template e a instância singleton da classe abstracta e simplesmente fazer return this; no método ProvideValue?

Certamente poderiam fazer assim e funcionaria correctamente como está, mas ao usar a instância singleton estão a separar a extensão markup do conversor, que têm tempos de vida diferentes.

Imaginem que no futuro as extensões markup passarão a ser disposed depois do método ProvideValue ser chamado. Nesse cenário, persistir o conversor separadamente evita o problema.

Nuno Freitas
Publicado por Nuno Freitas em 07 abril, 2014

Artigos relacionados