Настройка пользовательского MarkupExtension из кода

как вы устанавливаете пользовательский MarkupExtension из кода?

вы можете легко установить, если из Xaml. То же самое касается Binding и DynamicResource.

<TextBox FontSize="{Binding MyFontSize}"
         Style="{DynamicResource MyStyle}"
         Text="{markup:CustomMarkup}"/>

установка тех же значений через код позади требует немного другого подхода

  1. обязательные: использовать текстовое поле.SetBinding или BindingOperations.SetBinding

    Binding binding = new Binding("MyFontSize");
    BindingOperations.SetBinding(textBox, TextBox.FontSizeProperty, binding);
    
  2. DynamicResource: использовать SetResourceReference

    textBox.SetResourceReference(TextBox.StyleProperty, "MyStyle");
    
  3. CustomMarkup: Как установить пользовательский MarkupExtension из кода? Должен ли я позвонить ProvideValue и это тот случай, как я могу получитьIServiceProvider?*

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

я нашел удивительно мало на эту тему, так что, это можно сделать?


Г. Б.: ответил на вопрос. Просто добавляю некоторые детали, почему я хотел это сделать. Я попытался создать обходной путь для следующей проблемы.

проблема в том, что вы не можете наследовать от Binding и заменить ProvideValue так как он запечатан. Вам придется сделать что-то вроде этого: базовый класс для пользовательских расширений разметки привязки WPF. Но тогда проблема в том, что когда вы возвращаете Binding до Setter вы получаете исключение, но за пределами Style он работает нормально.

я читал в нескольких местах, что вы должны вернуть MarkupExtension сама если TargetObject это Setter чтобы позволить ему reeavaluate после того, как он применяется к фактическому FrameworkElement и это имеет смысл.

однако это работает только тогда, когда TargetProperty типа object, иначе исключение-обратно. Если вы посмотрите на исходный код BindingBase вы можете видеть, что он делает именно это, но кажется, что структура имеет какой-то секретный ингредиент, который заставляет его работать.

6 ответов


Я думаю, что нет эквивалента кода, службы доступны только через XAML. От MSDN:

MarkupExtension имеет только один виртуальный метод, ProvideValue. Входной параметр serviceProvider-это способ передачи служб реализациям при вызове расширения разметки процессором XAML.


Как насчет этого в качестве альтернативы, он генерируется в коде, но не обязательно так элегантно, как XAML:

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

реализация для Target просто:

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

единственное, что остается, - это возможность получить ссылку на "CustomMarkup" из объектной модели XAML. С вышеизложенным вам нужно повиснуть на ссылке на него.


если расширение разметки довольно простое и создает привязку и возвращает результат из ProvideValue (), вы можете добавить простой вспомогательный метод:

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

а затем в коде вы можете просто вызвать CommandExtension.SetBinding () вместо BindingOperations.SetBinding().

очевидно, что если вы делаете что-то более сложное, чем это, то это решение может быть неуместным.


Это Silverlight ТВ-шоу может пролить свет на эту проблему. Я помню, что они показывали некоторые примеры кода, которые могут быть полезны.


как указал Х. Б., a MarkupExtension предназначен только для использования в XAML.

что составляет Binding уникальным является то, что на самом деле происходит от MarkupExtension что позволяет использовать синтаксис расширения {Binding ...} или полная разметка <Binding>...</Binding> и использовать его в коде.

однако вы всегда можете попробовать создать промежуточный объект (что-то сродни BindingOperations), который знает, как использовать пользовательские расширения разметки и применить его к цели DependencyObject.

чтобы сделать это, я считаю, что вам нужно будет использовать XamlSetMarkupExtensionAttribute (для .NET 4) или IReceiveMarkupExtension интерфейс (для .NET 3.икс.) Я не совсем уверен, как использовать атрибут и/или интерфейса, но он может указать вам в правильном направлении.


Как установить таможни MarkupExtension из кода?

если вы можете изменить его, то просто извлеките логику в отдельный SomeMethod который можно вызвать самостоятельно и / или из ProvideValue.

вместо

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

вы просто называете это

customExtension.SomeMethod(textBox, TextBox.TextProperty);

часто мы создаем на заказ расширения собственность (используется так в xaml):

<TextBox Text="{local:SomeExtension ...}" />

это можно написать вот так:

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

поскольку я понял, что иногда необходимо использовать расширения разметки из кода, я всегда пытаюсь написать их таким образом.