[英]WPF Binding only working when using a Converter
我正在努力使用可重用且相对简单的 UserControl。
我的数据存储在实现简单 IHasValue 接口的类中:
public interface IHasValue<T>
{
T Value { get; }
ValueType Type { get; }
}
我需要一个可重用的控件来编辑应用程序周围多个位置的数据。 这是我目前拥有的控件,当 SelectedValue 为空时,它只显示一个带加号的按钮,单击它会设置 SelectedValue(稍后我将添加用户输入)并显示值而不是按钮:
<UserControl x:Class="DotsCompanion.Controls.HasValueFloatSelectorControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:DotsCompanion.Controls"
xmlns:conv="clr-namespace:DotsCompanion.Converters"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<conv:NullToTrueFalseConverter x:Key="NullToTrueFalseConverter" />
<conv:MyDebugConverter x:Key="MyDebugConverter" />
<DataTemplate x:Key="EmptyValueTemplate" DataType="{x:Type ContentControl}">
<Button Click="NewValueClick">
<TextBlock Text="+"></TextBlock>
</Button>
</DataTemplate>
<DataTemplate x:Key="HasValueTemplate" DataType="{x:Type ContentControl}">
<StackPanel DataContext="{Binding Path=DataContext, RelativeSource={RelativeSource AncestorType={x:Type ContentControl}}}">
<!-- THIS IS THE PROBLEMATIC BINDING -->
<TextBlock Text="{Binding Path=SelectedValue, Converter={StaticResource MyDebugConverter}}"></TextBlock>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<Grid Name="LayoutRoot">
<ContentControl>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedValue, Converter={StaticResource NullToTrueFalseConverter}}" Value="true">
<Setter Property="ContentTemplate" Value="{DynamicResource HasValueTemplate}"/>
</DataTrigger>
<DataTrigger Binding="{Binding SelectedValue, Converter={StaticResource NullToTrueFalseConverter}}" Value="false">
<Setter Property="ContentTemplate" Value="{DynamicResource EmptyValueTemplate}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</Grid>
</UserControl>
这是控件的极其简单的代码隐藏:
public partial class HasValueFloatSelectorControl : UserControl
{
public IHasValue<float> SelectedValue
{
get { return (IHasValue<float>)GetValue(SelectedValueProperty); }
set { SetValue(SelectedValueProperty, value); }
}
public static readonly DependencyProperty SelectedValueProperty =
DependencyProperty.Register("SelectedValue", typeof(IHasValue<float>), typeof(HasValueFloatSelectorControl), new PropertyMetadata(default(IHasValue<float>)));
public HasValueFloatSelectorControl()
{
InitializeComponent();
LayoutRoot.DataContext = this;
}
private void NewValueClick(object sender, EventArgs e)
{
SelectedValue = new FloatPrimitive(2.0f);
}
}
FloatPrimitive 是实际存储数据(浮点数)的类,它实现 IHasValue 并覆盖 ToString() 以便将浮点数显示为字符串。
问题是,将 Text 绑定到 SelectedValue 似乎仅在使用转换器时才有效。 在上面的代码中,MyDebugConverter 只是按原样返回值:
// This should be literally useless AFAIK
public class MyDebugConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
如果我移除转换器,绑定只会显示任何内容。 根据输出窗口跟踪,DataItem 为空,但使用实时属性浏览器我可以看到正确设置了 DataContext,而 SelectedValue 确实是一个 FloatPrimitive。
我尝试寻找解决方案,但到目前为止,我只找到了相反的情况(绑定不能与转换器一起使用)。 我对 WPF 还很陌生,所以我不确定从哪里开始调试,因为这似乎是一种相当罕见的情况,目前在线资源也无济于事。
我试过的:
这可能是因为我正在使用接口吗? 我对项目中的另一个用户控件有一个非常相似的设置,它工作得很好,但它使用抽象类 + 继承而不是接口 + 实现。
显然,问题在于您的属性SelectedValue
是interface
类型。
如果您将SelectedItem
的类型更改为FloatPrimitive
,则一切都按预期工作(不是解决方案,只是对我的观点的测试)。 我还通过测试和研究确认问题在于interface
方面,而不是它是泛型类型的事实。
WPF 在底层使用类型转换器来处理绑定更新期间类型之间的转换。
Integer
、 String
等类型,有一堆默认的TypeConverter
。TypeConverterAttribute
应用于要转换的类,您可以为自己的类定义TypeConverter
。String
,如果所有其他方法都失败了,它将调用Object.ToString()
。 问题是:正如在这个答案中所解释的,当 WPF 处理数据绑定时,它不会检查正在传输的值的实际类型,它只查看源和目标属性的声明类型。 出于这个原因,您在FloatPrimitive
声明什么转换运算符或是否覆盖ToString()
FloatPrimitive
; WPF 只能看到IHasValue<T>
因为这是SelectedItem
属性的类型。
IHasValue<T>
显然没有默认的TypeConverter
并且您还没有声明自己的。 由于TextBlock.Text
是String
,WPF 通常会尝试调用IHasValue<T>
Object.ToString()
,但IHasValue<T>
不是Object
。 实现IHasValue<T>
的Object
肯定是Object
的后代,但 WPF 不知道这一点,它只知道它是一个IHasValue<T>
。 所以转换失败,结果返回null
。
您实际上可以看到此转换失败。 如果您将PresentationTraceSources.TraceLevel=High
添加到您的{Binding SelectedItem ...}
(并离开Converter=...
),输出将显示:
System.Windows.Data Warning: 101 : BindingExpression (hash=16490914): GetValue at level 0 from HasValueFloatSelectorControl (hash=72766) using DependencyProperty(SelectedValue): FloatPrimitive (hash=14200498)
System.Windows.Data Warning: 80 : BindingExpression (hash=16490914): TransferValue - got raw value FloatPrimitive (hash=14200498)
System.Windows.Data Warning: 84 : BindingExpression (hash=16490914): TransferValue - implicit converter produced <null>
System.Windows.Data Warning: 89 : BindingExpression (hash=16490914): TransferValue - using final value <null>
绑定正确获取FloatPrimitive
,尝试转换它,但最终为null
,这解释了TextBlock
为空的原因。
显而易见的解决方案是为调用ToString()
IHasValue<T>
声明您自己的TypeConverter
。 不幸的是,这不起作用(我自己尝试过)。 似乎 WPF 只是不支持interface
的TypeConverter
。 这个问题显示了一个有类似问题的人(他们试图转换为interface
类型)。
MyDebugConverter
修复了问题当您使用Binding.Converter
,WPF 将在尝试其他任何操作之前调用提供的IValueConverter
。 但是IValueConverter
不需要返回目标属性的确切类型,因此 WPF 需要检查输出值的类型。 如果IValueConverter
输出不是目标属性的确切类型,WPF 将尝试使用TypeConverter
来完成工作。
问题是,这需要 WPF 实际检查输出值的类型。 因此,即使MyDebugConverter
什么都不做,WPF 也假定输入是IHasValue<T>
,但实际上查看了输出并发现它现在是FloatPrimitive
。 所以现在它尝试将其转换为后者而不是前者。 这允许它使用适用的默认TypeConverter
来调用Object.ToString()
。
如上所述,声明您自己的TypeConverter
不适用于interface
类型,这让您有几个选择:
SelectedItem
的类型更改为某个基class
,而不是interface
。 (根据您的要求,这可能不是一种选择)Text
(或类似名称)属性添加到您的IHasValue<T>
实现(可能还有interface
本身),它将返回ToString()
。 绑定到该属性而不是对象。FloatPrimitive
有一个public string Text => ToString();
,然后您可以绑定到SelectedItem.Text
。String
属性。 如果你用ContentControl
替换你的TextBlock
并绑定ContentControl.Content
,一切都会按预期显示(内部ContentPresenter
知道调用ToString()
)。 这也为您提供了添加DataTemplate
的选项,以防您想显示的不仅仅是文本。 示例: <ContentControl Content="{Binding SelectedValue}"/>
String
类型或其他类型属性时,将Binding.Converter
与IValueConverter
一起使用。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.