[英]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.