RibbonGallery.SelectedValue 未更新 SelectedItem
RibbonGallery.SelectedValue not updating SelectedItem
我正在使用 Microsoft 功能区库:System.Windows.Controls.Ribbon
。
我知道这个问题看起来很大,但我想做的其实并没有那么复杂,涉及的只是一些部分。
目标
我正在尝试将 RibbonComboBox
的 selection 绑定到我的 classes 之一的 属性,我称之为 TestBindingSource
,但我需要能够取消 selection 的更改。因此,如果他们 select 来自 RibbonComboBox
的项目,但随后取消了该更改,则 selection 需要保持原样。
RibbonComboBox
中显示的项目代表 Enum
的成员,我称之为 TestEnum
。我构建了另一个 class TestEnumGalleryItem
来表示 RibbonComboBox
中的 TestEnum
值,我使用 RibbonGallery.SelectedValue
和 RibbonGallery.SelectedValuePath
绑定到 属性 在 TestBindingSource
上。如果这太难理解,您应该能够从我的代码中明白我的意思。
代码
下面是我真实代码的简化版,尽量不要给我扣太多风格点。我已经在一个新项目中对此进行了测试,它可用于显示我正在 运行 遇到的问题。记得添加对 Microsoft 功能区库的引用。
MainWindow.xaml
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:VBTest"
mc:Ignorable="d"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Ribbon>
<RibbonTab Header="Test">
<RibbonGroup>
<RibbonComboBox Name="TestComboBox">
<RibbonGallery Name="TestGallery" MaxColumnCount="1" ScrollViewer.VerticalScrollBarVisibility="Auto" SelectedValuePath="EnumValue" SelectedValue="{Binding BindingSource.TestEnumValue}">
<RibbonGallery.ItemsSource>
<x:Array Type="local:TestEnumGalleryCategory">
<local:TestEnumGalleryCategory/>
</x:Array>
</RibbonGallery.ItemsSource>
<RibbonGallery.CategoryTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Items}">
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<RibbonGalleryItem ToolTipTitle="{Binding EnumName}" ToolTipDescription="{Binding EnumDescription}">
<TextBlock Text="{Binding EnumName}" Margin="0, -3, -0, -3"/>
</RibbonGalleryItem>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</RibbonGallery.CategoryTemplate>
</RibbonGallery>
</RibbonComboBox>
<RibbonButton Label="Break" Click="RibbonButton_Click"/>
</RibbonGroup>
</RibbonTab>
</Ribbon>
</Grid>
</Window>
MainWindow.xaml.vb
Imports System.ComponentModel
Class MainWindow
Public Sub New()
BindingSource = New TestBindingSource
InitializeComponent()
End Sub
Public Property BindingSource As TestBindingSource
Get
Return GetValue(BindingSourceProperty)
End Get
Set(ByVal value As TestBindingSource)
SetValue(BindingSourceProperty, value)
End Set
End Property
Public Shared ReadOnly BindingSourceProperty As DependencyProperty =
DependencyProperty.Register("BindingSource",
GetType(TestBindingSource), GetType(MainWindow))
Private Sub RibbonButton_Click(sender As Object, e As RoutedEventArgs)
Stop
End Sub
End Class
Public Enum TestEnum
ValueA
ValueB
End Enum
Public Class TestEnumGalleryCategory
Public Property Items As New List(Of TestEnumGalleryItem) From {New TestEnumGalleryItem With {.EnumValue = TestEnum.ValueA, .EnumName = "Value A", .EnumDescription = "A's description"},
New TestEnumGalleryItem With {.EnumValue = TestEnum.ValueB, .EnumName = "Value B", .EnumDescription = "B's description"}}
End Class
Public Class TestEnumGalleryItem
Public Property EnumValue As TestEnum = TestEnum.ValueA
Public Property EnumName As String
Public Property EnumDescription As String
End Class
Public Class TestBindingSource
Implements INotifyPropertyChanged
Private _TestEnumValue As TestEnum = TestEnum.ValueA
Property TestEnumValue As TestEnum
Get
Return _TestEnumValue
End Get
Set(value As TestEnum)
'Don't actually set new value, just leave it the same to simulate cancelation
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(NameOf(TestEnumValue)))
End Set
End Property
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
End Class
问题
当您 运行 代码时,您会看到 RibbonComboBox
默认显示“值 A”的代码。将 selection 更改为“值 B”。 RibbonComboBox
的 selection 发生变化,现在显示“Value B”。这不是我想要发生的,selection 应该立即变回“值 A”。
如果您查看 TestBindingSource.TestEnumValue
的代码,您会发现我实际上并没有在设置时保留新值,而是保留旧值以模拟用户取消更改。然后我引发 PropertyChanged
事件来更新 UI 所以它知道 属性 的实际值是什么。
更改为“值 B”后,单击“中断”按钮(包括在内是为了方便)。在Visual Studio手表window中,比较TestGallery.SelectedItem
和TestGallery.SelectedValue
的值。您会看到 TestGallery.SelectedValue
拥有正确的 TestEnum
值 ValueA
。现在查看 TestGallery.SelectedItem
,您会发现它仍然包含代表 ValueB
.
的项目
因此,即使 RibbonGallery
已被正确告知该值现在应为 ValueA
,它仍然显示 ValueB
。我该如何解决?
我会和你说实话,我没有太多时间花在这个错误上,而且我已经习惯了在功能区上不得不做一些棘手的解决方法。你可以给我任何关于如何获得的解决方案
RibbonGallery
(因此 RibbonComboBox
)正确更新将不胜感激。
经过更多的测试和研究,我意识到这个问题并不是色带库独有的。这实际上似乎也是正常 ComboBox
的问题,并且可能是所有 ItemsControls
。一旦意识到这一点,我就能够更有效地搜索答案并在此处找到解决方案:
https://nathan.alner.net/2010/04/25/cancelling-selection-change-in-a-bound-wpf-combo-box/
这不是一个完美的解决方案,但在我的特定情况下,将值设置为新选择然后立即将其设置回不会导致任何问题,所以我就是这样做的。作为记录,在设置回选时,我使用 DispatcherPriority.DataBind
而不是 DispatcherPriority.ContextIdle
,这样更改甚至不会显示在 UI 中,但解决方案仍然有效。
我正在使用 Microsoft 功能区库:System.Windows.Controls.Ribbon
。
我知道这个问题看起来很大,但我想做的其实并没有那么复杂,涉及的只是一些部分。
目标
我正在尝试将 RibbonComboBox
的 selection 绑定到我的 classes 之一的 属性,我称之为 TestBindingSource
,但我需要能够取消 selection 的更改。因此,如果他们 select 来自 RibbonComboBox
的项目,但随后取消了该更改,则 selection 需要保持原样。
RibbonComboBox
中显示的项目代表 Enum
的成员,我称之为 TestEnum
。我构建了另一个 class TestEnumGalleryItem
来表示 RibbonComboBox
中的 TestEnum
值,我使用 RibbonGallery.SelectedValue
和 RibbonGallery.SelectedValuePath
绑定到 属性 在 TestBindingSource
上。如果这太难理解,您应该能够从我的代码中明白我的意思。
代码
下面是我真实代码的简化版,尽量不要给我扣太多风格点。我已经在一个新项目中对此进行了测试,它可用于显示我正在 运行 遇到的问题。记得添加对 Microsoft 功能区库的引用。
MainWindow.xaml
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:VBTest"
mc:Ignorable="d"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Ribbon>
<RibbonTab Header="Test">
<RibbonGroup>
<RibbonComboBox Name="TestComboBox">
<RibbonGallery Name="TestGallery" MaxColumnCount="1" ScrollViewer.VerticalScrollBarVisibility="Auto" SelectedValuePath="EnumValue" SelectedValue="{Binding BindingSource.TestEnumValue}">
<RibbonGallery.ItemsSource>
<x:Array Type="local:TestEnumGalleryCategory">
<local:TestEnumGalleryCategory/>
</x:Array>
</RibbonGallery.ItemsSource>
<RibbonGallery.CategoryTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Items}">
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<RibbonGalleryItem ToolTipTitle="{Binding EnumName}" ToolTipDescription="{Binding EnumDescription}">
<TextBlock Text="{Binding EnumName}" Margin="0, -3, -0, -3"/>
</RibbonGalleryItem>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</RibbonGallery.CategoryTemplate>
</RibbonGallery>
</RibbonComboBox>
<RibbonButton Label="Break" Click="RibbonButton_Click"/>
</RibbonGroup>
</RibbonTab>
</Ribbon>
</Grid>
</Window>
MainWindow.xaml.vb
Imports System.ComponentModel
Class MainWindow
Public Sub New()
BindingSource = New TestBindingSource
InitializeComponent()
End Sub
Public Property BindingSource As TestBindingSource
Get
Return GetValue(BindingSourceProperty)
End Get
Set(ByVal value As TestBindingSource)
SetValue(BindingSourceProperty, value)
End Set
End Property
Public Shared ReadOnly BindingSourceProperty As DependencyProperty =
DependencyProperty.Register("BindingSource",
GetType(TestBindingSource), GetType(MainWindow))
Private Sub RibbonButton_Click(sender As Object, e As RoutedEventArgs)
Stop
End Sub
End Class
Public Enum TestEnum
ValueA
ValueB
End Enum
Public Class TestEnumGalleryCategory
Public Property Items As New List(Of TestEnumGalleryItem) From {New TestEnumGalleryItem With {.EnumValue = TestEnum.ValueA, .EnumName = "Value A", .EnumDescription = "A's description"},
New TestEnumGalleryItem With {.EnumValue = TestEnum.ValueB, .EnumName = "Value B", .EnumDescription = "B's description"}}
End Class
Public Class TestEnumGalleryItem
Public Property EnumValue As TestEnum = TestEnum.ValueA
Public Property EnumName As String
Public Property EnumDescription As String
End Class
Public Class TestBindingSource
Implements INotifyPropertyChanged
Private _TestEnumValue As TestEnum = TestEnum.ValueA
Property TestEnumValue As TestEnum
Get
Return _TestEnumValue
End Get
Set(value As TestEnum)
'Don't actually set new value, just leave it the same to simulate cancelation
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(NameOf(TestEnumValue)))
End Set
End Property
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
End Class
问题
当您 运行 代码时,您会看到 RibbonComboBox
默认显示“值 A”的代码。将 selection 更改为“值 B”。 RibbonComboBox
的 selection 发生变化,现在显示“Value B”。这不是我想要发生的,selection 应该立即变回“值 A”。
如果您查看 TestBindingSource.TestEnumValue
的代码,您会发现我实际上并没有在设置时保留新值,而是保留旧值以模拟用户取消更改。然后我引发 PropertyChanged
事件来更新 UI 所以它知道 属性 的实际值是什么。
更改为“值 B”后,单击“中断”按钮(包括在内是为了方便)。在Visual Studio手表window中,比较TestGallery.SelectedItem
和TestGallery.SelectedValue
的值。您会看到 TestGallery.SelectedValue
拥有正确的 TestEnum
值 ValueA
。现在查看 TestGallery.SelectedItem
,您会发现它仍然包含代表 ValueB
.
因此,即使 RibbonGallery
已被正确告知该值现在应为 ValueA
,它仍然显示 ValueB
。我该如何解决?
我会和你说实话,我没有太多时间花在这个错误上,而且我已经习惯了在功能区上不得不做一些棘手的解决方法。你可以给我任何关于如何获得的解决方案
RibbonGallery
(因此 RibbonComboBox
)正确更新将不胜感激。
经过更多的测试和研究,我意识到这个问题并不是色带库独有的。这实际上似乎也是正常 ComboBox
的问题,并且可能是所有 ItemsControls
。一旦意识到这一点,我就能够更有效地搜索答案并在此处找到解决方案:
https://nathan.alner.net/2010/04/25/cancelling-selection-change-in-a-bound-wpf-combo-box/
这不是一个完美的解决方案,但在我的特定情况下,将值设置为新选择然后立即将其设置回不会导致任何问题,所以我就是这样做的。作为记录,在设置回选时,我使用 DispatcherPriority.DataBind
而不是 DispatcherPriority.ContextIdle
,这样更改甚至不会显示在 UI 中,但解决方案仍然有效。