Definir o MarkupExtension personalizado a partir do código

Como você define um MarkupExtension personalizado a partir do código?

Você pode facilmente definir se de Xaml. O mesmo vale para Binding e DynamicResource .

  

Definir os mesmos valores por meio do código requer uma abordagem um pouco diferente

  1. Ligação: Use textBox.SetBinding ou BindingOperations.SetBinding

     Binding binding = new Binding("MyFontSize"); BindingOperations.SetBinding(textBox, TextBox.FontSizeProperty, binding); 
  2. DynamicResource: Use SetResourceReference

     textBox.SetResourceReference(TextBox.StyleProperty, "MyStyle"); 
  3. CustomMarkup: como defino um MarkupExtension personalizado a partir do código? Devo chamar ProvideValue e nesse caso, como posso obter um IServiceProvider ? *

     CustomMarkupExtension customExtension = new CustomMarkupExtension(); textBox.Text = customExtension.ProvideValue(??); 

Eu encontrei surpreendentemente pouco sobre o assunto, então, pode ser feito?


A HB respondeu à pergunta. Apenas adicionando alguns detalhes aqui porque eu queria fazer isso. Eu tentei criar uma solução alternativa para o seguinte problema.

O problema é que você não pode derivar de Binding e replace ProvideValue desde que ele é selado. Você terá que fazer algo assim: Uma class base para extensões de marcação de binding WPF personalizadas . Mas então o problema é que quando você retorna um Binding para um Setter você recebe uma exceção, mas fora do Style funciona bem.

Eu li em vários lugares que você deve retornar o próprio MarkupExtension se o TargetObject for um Setter para permitir a reavaliação depois que ele for aplicado a um FrameworkElement real e isso fizer sentido.

  • Extensão de marcação no acionador de dados
  • Limitação enorme de um MarkupExtension
  • Uma class base para extensões de marcação de binding WPF personalizadas (nos comentários)

No entanto, isso só funciona quando o TargetProperty é do tipo object , caso contrário, a exceção está de volta. Se você olhar o código-fonte para o BindingBase verá que ele faz exatamente isso, mas parece que o framework tem algum ingrediente secreto que faz com que ele funcione.

Eu acho que não há código equivalente, os serviços só estão disponíveis via XAML. Do MSDN :

MarkupExtension tem apenas um método virtual, ProvideValue. O parâmetro input serviceProvider é como os serviços são comunicados às implementações quando a extensão de marcação é chamada por um processador XAML.

E quanto a isso como uma alternativa, ele é gerado no código, mas não necessariamente tão elegante quanto o XAML:

  var markup = new CustomMarkup(); markup.ProvideValue(new Target(textBox, TextBox.TextProperty)); 

A implementação do Target é simplesmente:

 public struct Target : IServiceProvider, IProvideValueTarget { private readonly DependencyObject _targetObject; private readonly DependencyProperty _targetProperty; public Target(DependencyObject targetObject, DependencyProperty targetProperty) { _targetObject = targetObject; _targetProperty = targetProperty; } public object GetService(Type serviceType) { if (serviceType == typeof(IProvideValueTarget)) return this; return null; } object IProvideValueTarget.TargetObject { get { return _targetObject; } } object IProvideValueTarget.TargetProperty { get { return _targetProperty; } } } 

A única coisa que resta é a capacidade de obter uma referência de volta ao ‘CustomMarkup’ do modelo de object XAML. Com o exposto, você precisa se agarrar a uma referência a ele.

Se a sua extensão de marcação é bastante simples e cria uma binding e retorna o resultado de ProvideValue (), então você pode adicionar um método auxiliar simples:

 public class CommandExtension : MarkupExtension { public CommandExtension(string name) { this.Name = name; } public string Name { get; set; } public override object ProvideValue(IServiceProvider serviceProvider) { return GetBinding(this.Name).ProvideValue(serviceProvider); } static Binding GetBinding(string name) { return new Binding("Commands[" + name + "]") { Mode = BindingMode.OneWay }; } public static void SetBinding(DependencyObject target, DependencyProperty dp, string commandName) { BindingOperations.SetBinding(target, dp, GetBinding(commandName)); } } 

E, em seguida, no código, você pode simplesmente chamar CommandExtension.SetBinding () em vez de BindingOperations.SetBinding ().

Obviamente, se você está fazendo algo mais complexo do que isso, esta solução pode não ser apropriada.

Este programa de TV Silverlight pode lançar alguma luz sobre este assunto. Lembro-me deles mostrando algumas amostras de código que podem ser úteis.

Como HB apontou, um MarkupExtension destina-se apenas a ser usado dentro do XAML.

O que torna o Binding exclusivo é que ele realmente deriva de MarkupExtension que é o que torna possível usar a syntax de extensão {Binding ...} ou a marcação completa ... e usá-la no código.

No entanto, você sempre pode tentar criar um object intermediário (algo semelhante a BindingOperations ) que saiba como usar sua extensão de marcação personalizada e aplicá-la a um DependencyObject destino.

Para fazer isso, acredito que você precisaria fazer uso da XamlSetMarkupExtensionAttribute (para .NET 4) ou IReceiveMarkupExtension (para .NET 3.x). Não tenho certeza de como usar o atributo e / ou a interface, mas isso pode apontar você na direção certa.

Como defino um MarkupExtension personalizado a partir do código?

Se você puder modificá-lo, simplesmente extraia a lógica em SomeMethod separado, que pode ser chamado sozinho e / ou de ProvideValue .

Então, em vez de

 textBox.Text = customExtension.ProvideValue(??); 

você acabou de chamá-lo

 customExtension.SomeMethod(textBox, TextBox.TextProperty); 

Freqüentemente estamos criando extensões de propriedades personalizadas (usadas assim no xaml):

  

Isso pode ser escrito assim:

 public class SomeExtension : MarkupExtension { public override object ProvideValue(IServiceProvider serviceProvider) { var provider = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget; var target = provider.TargetObject as DependencyObject; var property = provider.TargetProperty as DependencyProperty; // defer execution if target is data template if (target == null) return this; return SomeMethod(target, property); } public object SomeMethod(DependencyObject target, DependencyProperty property) { ... // do something } } 

Como percebi que às vezes é necessário usar extensões de marcação do código, estou sempre tentando escrevê-las dessa maneira.