DataTemplate 中的 DependencyProperty 不起作用
DependencyProperty in DataTemplate not working
我遇到了一个让我抓狂的问题。
我在扩展类中有一个依赖属性 Uid。我这样做是为了设置静态 "language" 字符串(工具:TranslateExtension.Uid="MAINWINDOW_ARTICLES")。然后我使用 Text="{tools:Translate}" 来触发对设置语言的查找。
这将调用将设置绑定的 ProvideValue 方法。当直接从控件调用它时,就像在下面的第二个按钮上一样,一切正常。但是当我从 DataTemplate 中执行此操作时,GetUid 将 return 一个空字符串而不是 "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; }
}
在模板场景中,标记扩展在设置 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 class 来监听依赖项 属性 的变化,并且可以像这样绑定到字典:
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();
.......
}
我遇到了一个让我抓狂的问题。
我在扩展类中有一个依赖属性 Uid。我这样做是为了设置静态 "language" 字符串(工具:TranslateExtension.Uid="MAINWINDOW_ARTICLES")。然后我使用 Text="{tools:Translate}" 来触发对设置语言的查找。
这将调用将设置绑定的 ProvideValue 方法。当直接从控件调用它时,就像在下面的第二个按钮上一样,一切正常。但是当我从 DataTemplate 中执行此操作时,GetUid 将 return 一个空字符串而不是 "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; }
}
在模板场景中,标记扩展在设置 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 class 来监听依赖项 属性 的变化,并且可以像这样绑定到字典:
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();
.......
}