DataTemplate中的DependencyProperty无法正常工作

时间:2015-02-02 17:01:07

标签: c# wpf extension-methods dependency-properties

我有一个让我发疯的问题。

我在扩展类中有一个dependecyproperty,Uid。我这是设置一种静态语言" string(tools:TranslateExtension.Uid =" MAINWINDOW_ARTICLES")。然后我使用Text =" {tools:Translate}"触发查找设置语言。

这将调用ProvideValue方法,该方法将设置绑定。当直接从控件中调用它时,就像在下面的第二个按钮上一切正常。但是当我从DataTemplate中执行此操作时,GetUid将返回一个空字符串而不是" MAINWINDOW_ARTICLES"。我似乎不明白为什么。有什么想法吗?

可以看到一个可运行的例子:

<Window x:Class="DXSample.MainWindow" 
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        xmlns:tools="clr-namespace:DXSample"
        xmlns:System="clr-namespace:System;assembly=mscorlib"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <DataTemplate x:Key="template">
            <TextBlock tools:TranslateExtension.Uid="MAINWINDOW_ARTICLES" Text="{tools:Translate}"/>
        </DataTemplate>
    </Window.Resources>

    <StackPanel>
        <Button ContentTemplate="{StaticResource template}"/>
        <Button Content="{tools:Translate}" tools:TranslateExtension.Uid="MAINWINDOW_ARTICLES"/>
    </StackPanel>
</Window>

[ContentProperty("Parameters")]
public class TranslateExtension : MarkupExtension
{
    private DependencyProperty property;
    private DependencyObject target;

    private readonly Collection<BindingBase> parameters = new Collection<BindingBase>();

    public Collection<BindingBase> Parameters
    {
        get { return parameters; }
    }

    private bool IsDataBound
    {
        get { return BindingOperations.IsDataBound(target, property); }
    }

    public static string GetUid(DependencyObject obj)
    {
        return (string)obj.GetValue(UidProperty);
    }

    public static void SetUid(DependencyObject obj, string value)
    {
        obj.SetValue(UidProperty, value);
    }

    public static readonly DependencyProperty UidProperty = DependencyProperty.RegisterAttached("Uid",
                                                                                                typeof(string),
                                                                                                typeof(TranslateExtension),
                                                                                                new UIPropertyMetadata(string.Empty));

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        IProvideValueTarget service =
            serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
        if (service == null)
            throw new InvalidOperationException("IProvideValueTarget service is unavailable");

        DependencyProperty dependencyProperty = service.TargetProperty as DependencyProperty;
        if (dependencyProperty == null)
            throw new ArgumentException("Target property must be of type DependencyProperty");

        DependencyObject dependencyObject = service.TargetObject as DependencyObject;
        if (dependencyObject == null)
            return this;

        target = dependencyObject;
        property = dependencyProperty;

        BindDictionary();


        return dependencyObject.GetValue(dependencyProperty);
    }

    private void element_Loaded(object sender, RoutedEventArgs e)
    {
        if (!IsDataBound)
            BindDictionary();
    }

    private void element_Unloaded(object sender, RoutedEventArgs e)
    {
        if (IsDataBound)
            BindingOperations.ClearBinding(target, property);
    }

    private void BindDictionary()
    {
        string uid = GetUid(target);
        if (string.IsNullOrEmpty(uid))
        {
            Debug.WriteLine("UID NULL OR EMPTY");
            return;
        }
        string vid = property.Name;

        Binding binding = new Binding("Dictionary");
        binding.Source = LanguageContext.Instance;
        binding.Mode = BindingMode.TwoWay;
        //LanguageConverter converter = new LanguageConverter(uid, vid);
        if (parameters.Count == 0)
        {
            //binding.Converter = converter;
            BindingOperations.SetBinding(target, property, binding);
        }
        else
        {
            MultiBinding multiBinding = new MultiBinding();
            multiBinding.Mode = BindingMode.TwoWay;
            //multiBinding.Converter = converter;
            multiBinding.Bindings.Add(binding);
            if (string.IsNullOrEmpty(uid))
            {
                Binding uidBinding = parameters[0] as Binding;
                if (uidBinding == null)
                    throw new ArgumentException("Uid Binding parameter must be the first, and of type Binding");
            }
            foreach (Binding parameter in parameters)
                multiBinding.Bindings.Add(parameter);
            BindingOperations.SetBinding(target, property, multiBinding);
        }
    }
}

public class LanguageContext
{
    static LanguageContext _languageContext;
    public static LanguageContext Instance { get { if (_languageContext == null) _languageContext = new LanguageContext { Dictionary = "test string" }; return _languageContext; } }
    public string Dictionary { get; set; }
}

1 个答案:

答案 0 :(得分:1)

在模板方案中, 标记扩展在设置UidProperty之前得到解决 (您可以通过在UidPropertyIdentifier中提供PropertyChangedCallback来验证):

public static readonly DependencyProperty UidProperty = 
  DependencyProperty.RegisterAttached("Uid", typeof(string),
                                      typeof(TranslateExtension),
                           new UIPropertyMetadata(string.Empty, OnUidPropertyChanged));

private static void OnUidPropertyChanged(DependencyObject d,
                                         DependencyPropertyChangedEventArgs args)
{
   // Breakpoint will hit after ProvideValue in case of template.
}

这就是为什么在BindDictionary()方法中,你得到uid的值为默认值String.Empty。


现在,对于解决方案,你必须以某种方式 在UidProperty变更时进行绑定 (即使UidProperty绑定了某些值,你需要更新你的绑定)你可以通过挂钩到目标对象的依赖属性描述符的AddValueChanged来做。但AddValueChanged有一些与之相关的内存泄漏问题(如果有人参与,你可以阅读更多关于here的信息)。

所以,你可以做的是使用PropertyChangeNotifier类来监听依赖属性更改,并且可以像这样绑定到字典:

public override object ProvideValue(IServiceProvider serviceProvider)
{
    .....

    DependencyObject dependencyObject = service.TargetObject as DependencyObject;
    if (dependencyObject == null)
        return this;

    PropertyChangeNotifier notifier = new PropertyChangeNotifier(dependencyObject,
                   TranslateExtension.UidProperty);
    notifier.ValueChanged += (s, e) => BindDictionary();

    .......
}