问题概述
我有一个名为EnumDisplayConverter
的自定义IValueConverter
。它应该取一个Enum
值并返回名称,以便可以显示它。不知怎的,即使这个转换器被用于Enum
类型的属性之间的绑定,转换器也被传递了一个值String.Empty
。这当然会导致一个错误,因为String
不是Enum
,更不用说它真的出乎意料。
要复制的代码
以下代码可用于重现错误。接下来是复制的步骤和对代码意图的解释。
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:VBTest"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<DockPanel>
<ListBox Name="LB_Foos" DockPanel.Dock="Left" ItemsSource="{Binding FooOptions}" SelectionChanged="ListBox_SelectionChanged"/>
<ComboBox ItemsSource="{x:Static local:MainWindow.SelectableThings}" SelectedItem="{Binding OpenFoo.SelectableChosenThing}" VerticalAlignment="Center" HorizontalAlignment="Center" Width="100">
<ComboBox.ItemTemplate>
<DataTemplate>
<ContentControl>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Setter Property="Content" Value="{Binding Converter={x:Static local:EnumDisplayConverter.Instance}}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding}" Value="-1">
<Setter Property="Content" Value="None"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</DockPanel>
</Window>
Imports System.Collections.ObjectModel
Imports System.Globalization
Class MainWindow
Shared Sub New()
Dim Things = (From v As Thing In [Enum].GetValues(GetType(Thing))).ToList
Things.Insert(0, -1)
SelectableThings = New ReadOnlyCollection(Of Thing)(Things)
End Sub
Public Shared ReadOnly Property SelectableThings As IReadOnlyList(Of Thing)
Public ReadOnly Property FooOptions As New ReadOnlyCollection(Of Integer)({1, 2, 3, 4})
'This is a placeholder method meant to set OpenFoo to a new instance of Foo when a selection is made.
'In the actual application, this is done with data binding and involves async database calls.
Private Sub ListBox_SelectionChanged(sender As Object, e As SelectionChangedEventArgs)
OpenFoo = Nothing
Select Case LB_Foos.SelectedItem
Case 1
OpenFoo = New Foo With {.ChosenThing = Nothing}
Case 2
OpenFoo = New Foo With {.ChosenThing = Thing.A}
Case 3
OpenFoo = New Foo With {.ChosenThing = Thing.B}
Case 4
OpenFoo = New Foo With {.ChosenThing = Thing.C}
End Select
End Sub
Public Property OpenFoo As Foo
Get
Return GetValue(OpenFooProperty)
End Get
Set(ByVal value As Foo)
SetValue(OpenFooProperty, value)
End Set
End Property
Public Shared ReadOnly OpenFooProperty As DependencyProperty =
DependencyProperty.Register("OpenFoo",
GetType(Foo), GetType(MainWindow))
End Class
Public Enum Thing
A
B
C
End Enum
Public Class Foo
Public Property ChosenThing As Thing?
Public Property SelectableChosenThing As Thing
Get
Return If(_ChosenThing, -1)
End Get
Set(value As Thing)
Dim v As Thing? = If(value = -1, New Thing?, value)
ChosenThing = v
End Set
End Property
End Class
Public Class EnumDisplayConverter
Implements IValueConverter
Public Shared ReadOnly Property Instance As New EnumDisplayConverter
Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert
If value Is Nothing Then Return Nothing
Return [Enum].GetName(value.GetType, value)
End Function
Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack
Return Binding.DoNothing
End Function
End Class
复制步骤
- 运行
MainWindow
- 从左侧的
ListBox
中选择任意项目 - 从
ListBox
中选择其他项目 - 观察未处理的异常
代码说明
如果不清楚代码应该做什么,我会解释一下。
CCD_ 11表示用户正在通过CCD_ 12编辑的数据对象。每个CCD_ 13都有一个CCD_不具有Thing
也是一种选择,这就是为什么ChosenThing
是Thing?
(即Nullable(Of Thing)
(。
Null
数据项在ComboBox
中不起作用;没有选择";。为了绕过这一点,我将值-1
添加到我的可选Thing
值列表中。在Foo.SelectableChosenThing
中,我检查-1
,并将其转换为Null
以获得Foo.ChosenThing
的实际值。这使我能够正确地绑定到ComboBox
。
问题详细信息
只有当OpenFoo
在被赋予新值之前被设置为Nothing
时,错误才会出现。如果我去掉线路OpenFoo = Nothing
,一切都正常。然而,在实际应用程序中,我希望在加载选择时将OpenFoo
设置为Nothing
——此外,它并没有解释为什么会发生这种情况。
当所涉及的属性为预期类型Thing
时,为什么EnumDisplayConverter
被传递为String
类型的value
?
经过调查,发现问题来自ComboBox
控件。当ComboBox
的所选项目为null
时,它将null
的显示值替换为String.Empty
。以下是中的一个片段。NETComboBox.UpdateSelectionBoxItem
:参考源
...
// display a null item by an empty string
if (item == null)
{
item = String.Empty;
itemTemplate = ContentPresenter.StringContentTemplate;
}
SelectionBoxItem = item;
SelectionBoxItemTemplate = itemTemplate;
...
这发生在考虑任何DataTemplate
s之前,所以当调用我的DataTemplate
get时,它被赋予了String.Empty
的值来显示,而不是null
。
解决方案是要么
- 将
DataTrigger
添加到ContentControl
的Style
,该CCD_50检查String.Empty
,并且在这种情况下不使用转换器 - 修改
EnumDisplayConverter
以检查非Enum
值并返回DependencyProperty.UnsetValue