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.SelectedValueRibbonGallery.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.SelectedItemTestGallery.SelectedValue的值。您会看到 TestGallery.SelectedValue 拥有正确的 TestEnumValueA。现在查看 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 中,但解决方案仍然有效。