wpf 如何对 RibbonComboBox 的 SelectedItem 进行数据绑定

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/15555449/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 08:13:58  来源:igfitidea点击:

How to databind SelectedItem of RibbonComboBox

wpfdata-bindingmvvmselecteditemribboncontrolslibrary

提问by Mike Fuchs

My question is basically this one. I thought it would help to provide some more information and code that makes it easier to reproduce the problem, though.

我的问题基本上是这个。不过,我认为提供更多信息和代码会有助于更轻松地重现问题。

Working with the Microsoft.Windows.Controls.Ribbon.RibbonComboBox from the RibbonControlsLibraryfeels like walking through a big bog full of bugs, not something you do if you know a way around it.

使用RibbonControlsLibrary 中的 Microsoft.Windows.Controls.Ribbon.RibbonComboBox感觉就像在一个充满错误的大沼泽中行走,如果您知道解决方法,就不会这样做。

Anywho. The biggest problem I encountered was databinding my SelectedItem.

任何人。我遇到的最大问题是数据绑定我的 SelectedItem。

The following is what I started with (after I found out about RibbonGallery?). To have ItemsSource and SelectedItem on subelements of the ComboBox and not even on the same level already gave me the heebie-jeebies, but that seems to be correct.

以下是我的开始(在我发现RibbonGallery 之后?)。在 ComboBox 的子元素上拥有 ItemsSource 和 SelectedItem 并且甚至不在同一级别已经给了我heebie-jeebies,但这似乎是正确的。

In the example app, I'm setting the SelectedItem in the constructor of the ViewModel. However, when running the app, no SelectedItem is shown. Even the VS designer is correctly showing "second option"!

在示例应用程序中,我在 ViewModel 的构造函数中设置 SelectedItem。但是,在运行应用程序时,不会显示 SelectedItem。甚至 VS 设计师也正确地显示了“第二个选项”!

Running app:Running AppVS designer: Visual Studio Designer

正在运行的应用程序:运行应用VS 设计器:Visual Studio 设计器

When debugging the SelectedItem setter, you'll notice multiple passes. After setting it the first time to "second option" in the ctor(1, see debug log below), it will reset to null (2) (by external code, I reckon in the control itself). When opening the dropdown in the UI, it will be set to null again (3), then when selecting a value, twice to this value (4,5). I selected "second option", then repeated the procedure with "first option" (6-9). This produced the following log (ignoring the one thousand and one binding exceptions from the ribbon control...):

在调试 SelectedItem setter 时,您会注意到多次通过。在ctor(1,见下面的调试日志)中第一次将它设置为“第二个选项”后,它将重置为空(2)(通过外部代码,我认为是控件本身)。在 UI 中打开下拉菜单时,它将再次设置为 null (3),然后在选择一个值时,两次设置为该值 (4,5)。我选择了“第二个选项”,然后用“第一个选项”(6-9)重复了这个过程。这产生了以下日志(忽略功能区控件中的一千零一个绑定异常......):

enter image description here

enter image description here

The big problem obviously is (2), which is resetting my initial selection. Looks like when the control is shown the first time, it is reset. A very ugly workaround would be to set the value by a timer. Setting it in the Loaded event of the user control does work for me in this example app, but in my heavier real-life app, it does not. Anyway, all of that feels wrong. Does anyone know a better solution?

大问题显然是(2),它正在重置我的初始选择。看起来当控件第一次显示时,它被重置。一个非常丑陋的解决方法是通过计时器设置该值。在这个示例应用程序中,在用户控件的 Loaded 事件中设置它对我有用,但在我更重的现实生活应用程序中,它不起作用。无论如何,这一切都让人感觉不对。有谁知道更好的解决方案?

Xaml:

Xml:

<UserControl x:Class="WpfApplication1.RibbonComboBoxDemo"
             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:r="http://schemas.microsoft.com/winfx/2006/xaml/presentation/ribbon" 
             xmlns:local="clr-namespace:WpfApplication1"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">

    <UserControl.DataContext>
        <local:ViewModel />
    </UserControl.DataContext>

    <Grid>
        <r:Ribbon >
            <r:RibbonTab Header="First Tab">
                <r:RibbonGroup Header="Group">
                    <r:RibbonComboBox >
                        <r:RibbonGallery SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
                            <r:RibbonGalleryCategory ItemsSource="{Binding Controls}" DisplayMemberPath="Caption" />
                        </r:RibbonGallery>
                    </r:RibbonComboBox>
                </r:RibbonGroup>
            </r:RibbonTab>
            <r:RibbonTab Header="Second Tab" />
        </r:Ribbon>
    </Grid>
</UserControl>

ViewModel:

视图模型:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;

namespace WpfApplication1
{
    public class ViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged(string propertyName)
        {
            if (this.PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        public ObservableCollection<ControlBaseModel> Controls { get; private set; }


        private ControlBaseModel _selectedItem;
        public ControlBaseModel SelectedItem { get { return _selectedItem; } set { LogSelectedItemChange(value); _selectedItem = value; OnPropertyChanged("SelectedItem"); } }

        public ViewModel()
        {
            this.Controls = new ObservableCollection<ControlBaseModel>();

            this.Controls.Add(new ControlBaseModel() { Caption = "first option" });
            this.Controls.Add(new ControlBaseModel() { Caption = "second option" });

            this.SelectedItem = this.Controls[1]; // set to second option
        }

        int i = 0;
        private void LogSelectedItemChange(ControlBaseModel value)
        {
            i++;
            string setObject = "null";
            if (value != null)
            {
                setObject = value.Caption;
            }
            Debug.WriteLine(string.Format("{0}: SelectedItem.set(): {1}", i, setObject));
        }

    }

    public class ControlBaseModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged(string propertyName)
        {
            if (this.PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        private string _name;
        public string Name { get { return _name; } set { _name = value; OnPropertyChanged("Name"); } }

        private string _caption;
        public string Caption { get { return _caption; } set { _caption = value; OnPropertyChanged("Caption"); } }
    }
}

回答by Mike Fuchs

While the View/UserControl loaded event is occuring before the ComboBox SelectedItem is reset to null in my application, the ComboBox loaded event is in fact fired twice, the second time "late" enough. So my current solution, which I will ditch gladly for a better one, is this:

虽然在我的应用程序中将 ComboBox SelectedItem 重置为 null 之前发生了 View/UserControl 加载事件,但实际上 ComboBox 加载事件被触发了两次,第二次“迟到”就足够了。所以我目前的解决方案,我很乐意放弃一个更好的解决方案,是这样的:

<r:RibbonComboBox>
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="Loaded">
            <i:InvokeCommandAction Command="{Binding LoadedCommand}" />
        </i:EventTrigger>
    </i:Interaction.Triggers>
    <r:RibbonGallery SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
        <r:RibbonGalleryCategory ItemsSource="{Binding Controls}" DisplayMemberPath="Caption"/>
    </r:RibbonGallery>
</r:RibbonComboBox>

ViewModel:

视图模型:

private ControlBaseModel _lastNonNullSelectedItem;

public ObservableCollection<ControlBaseModel> Controls { get; private set; }

private ControlBaseModel _selectedItem;
public ControlBaseModel SelectedItem 
{ 
    get { return _selectedItem; } 
    set 
    { 
        if (value != null) { _lastNonNullSelectedItem = value; } 
        _selectedItem = value; 
        OnPropertyChanged("SelectedItem"); 
    } 
}
public ICommand LoadedCommand { get; private set; }


public ViewModel()
{
    this.Controls = new ObservableCollection<ControlBaseModel>();
    this.LoadedCommand = new ActionCommand(OnLoaded); // ActionCommand: simple implementation of ICommand

    this.Controls.Add(new ControlBaseModel() { Caption = "first option" });
    this.Controls.Add(new ControlBaseModel() { Caption = "second option" });

    this.SelectedItem = this.Controls[1]; // set to second option
}

private void OnLoaded()
{
    this.SelectedItem = _lastNonNullSelectedItem;
}

回答by Nick Strupat

I ended up just using the standard ComboBox.

我最终只使用了标准的 ComboBox。

<ComboBox SelectedItem="{Binding Item}" ItemsSource="{Binding Items}"/>

If you want the same (very similar) style as the RibbonComboBox, use

如果您想要与 RibbonComboBox 相同(非常相似)的样式,请使用

<ComboBox SelectedItem="{Binding Item}" ItemsSource="{Binding Items}" IsEditable="True" IsReadOnly="True"/>