c# - 可编辑的 ComboBox 绑定(bind)和更新源触发器

标签 c# wpf binding combobox

要求

我想要一个ComboBox,用户可以在其中输入一些文本或从下拉列表中选择文本。当用户在输入后按 Enter 或从下拉列表中简单地选择项目时,应该更新绑定(bind)源(在我的情况下是最佳 View 行为)。

问题

  • 当设置 UpdateSourceTrigger=PropertyChange (默认)时,源更新将在每个字符后触发,这不好,因为属性 setter 调用的成本很高;
  • 当设置 UpdateSourceTrigger=LostFocus 时,从下拉列表中选择项目将需要再执行一次操作才能真正失去焦点,这不太人性化(需要在单击选择后额外单击项)。

我尝试使用UpdateSourceTrigger=Explicit,但效果不佳:

<ComboBox IsEditable="True" VerticalAlignment="Top" ItemsSource="{Binding List}"
          Text="{Binding Text, UpdateSourceTrigger=LostFocus}"
          SelectionChanged="ComboBox_SelectionChanged"
          PreviewKeyDown="ComboBox_PreviewKeyDown" LostFocus="ComboBox_LostFocus"/>

public partial class MainWindow : Window
{
    private string _text = "Test";
    public string Text
    {
        get { return _text; }
        set
        {
            if (_text != value)
            {
                _text = value;
                MessageBox.Show(value);
            }
        }
    }

    public string[] List
    {
        get { return new[] { "Test", "AnotherTest" }; }
    }

    public MainWindow()
    {
        InitializeComponent();
        DataContext = this;
    }

    private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (e.AddedItems.Count > 0)
            ((ComboBox)sender).GetBindingExpression(ComboBox.TextProperty).UpdateSource();
    }

    private void ComboBox_PreviewKeyDown(object sender, KeyEventArgs e)
    {
        if(e.Key == Key.Enter)
            ((ComboBox)sender).GetBindingExpression(ComboBox.TextProperty).UpdateSource();
    }

    private void ComboBox_LostFocus(object sender, RoutedEventArgs e)
    {
        ((ComboBox)sender).GetBindingExpression(ComboBox.TextProperty).UpdateSource();
    }

}

此代码有 2 个问题:

  • 当从下拉菜单中选择项目时,源会使用之前选择的值进行更新,为什么?
  • 当用户开始输入内容,然后单击下拉按钮从列表中选择内容时 - 源再次更新(由于焦点丢失?),如何避免这种情况?

我有点害怕落入 XY problem 之下这就是为什么我发布了最初的要求(也许我走错了方向?)而不是要求帮助我解决上述问题之一。

最佳答案

您更新源以响应特定事件的方法是正确的,但是您必须考虑 ComboBox 更新事物的方式。另外,您可能希望将 UpdateSourceTrigger 设置为 LostFocus,这样您就不需要处理那么多更新案例。

您还应该考虑将代码移至可重用的附加属性,以便将来可以将其应用于其他地方的组合框。碰巧我过去也创建过这样的属性。

/// <summary>
/// Attached properties for use with combo boxes
/// </summary>
public static class ComboBoxBehaviors
{
    private static bool sInSelectionChange;

    /// <summary>
    /// Whether the combo box should commit changes to its Text property when the Enter key is pressed
    /// </summary>
    public static readonly DependencyProperty CommitOnEnterProperty = DependencyProperty.RegisterAttached("CommitOnEnter", typeof(bool), typeof(ComboBoxBehaviors),
        new PropertyMetadata(false, OnCommitOnEnterChanged));

    /// <summary>
    /// Returns the value of the CommitOnEnter property for the specified ComboBox
    /// </summary>
    public static bool GetCommitOnEnter(ComboBox control)
    {
        return (bool)control.GetValue(CommitOnEnterProperty);
    }

    /// <summary>
    /// Sets the value of the CommitOnEnterProperty for the specified ComboBox
    /// </summary>
    public static void SetCommitOnEnter(ComboBox control, bool value)
    {
        control.SetValue(CommitOnEnterProperty, value);
    }

    /// <summary>
    /// Called when the value of the CommitOnEnter property changes for a given ComboBox
    /// </summary>
    private static void OnCommitOnEnterChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        ComboBox control = sender as ComboBox;
        if (control != null)
        {
            if ((bool)e.OldValue)
            {
                control.KeyUp -= ComboBox_KeyUp;
                control.SelectionChanged -= ComboBox_SelectionChanged;
            }
            if ((bool)e.NewValue)
            {
                control.KeyUp += ComboBox_KeyUp;
                control.SelectionChanged += ComboBox_SelectionChanged;
            }
        }
    }

    /// <summary>
    /// Handler for the KeyUp event attached to a ComboBox that has CommitOnEnter set to true
    /// </summary>
    private static void ComboBox_KeyUp(object sender, KeyEventArgs e)
    {
        ComboBox control = sender as ComboBox;
        if (control != null && e.Key == Key.Enter)
        {
            BindingExpression expression = control.GetBindingExpression(ComboBox.TextProperty);
            if (expression != null)
            {
                expression.UpdateSource();
            }
            e.Handled = true;
        }
    }

    /// <summary>
    /// Handler for the SelectionChanged event attached to a ComboBox that has CommitOnEnter set to true
    /// </summary>
    private static void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (!sInSelectionChange)
        {
            var descriptor = DependencyPropertyDescriptor.FromProperty(ComboBox.TextProperty, typeof(ComboBox));
            descriptor.AddValueChanged(sender, ComboBox_TextChanged);
            sInSelectionChange = true;
        }
    }

    /// <summary>
    /// Handler for the Text property changing as a result of selection changing in a ComboBox that has CommitOnEnter set to true
    /// </summary>
    private static void ComboBox_TextChanged(object sender, EventArgs e)
    {
        var descriptor = DependencyPropertyDescriptor.FromProperty(ComboBox.TextProperty, typeof(ComboBox));
        descriptor.RemoveValueChanged(sender, ComboBox_TextChanged);

        ComboBox control = sender as ComboBox;
        if (control != null && sInSelectionChange)
        {
            sInSelectionChange = false;

            if (control.IsDropDownOpen)
            {
                BindingExpression expression = control.GetBindingExpression(ComboBox.TextProperty);
                if (expression != null)
                {
                    expression.UpdateSource();
                }
            }
        }
    }
}

以下是在 xaml 中设置属性的示例:

<ComboBox IsEditable="True" ItemsSource="{Binding Items}" Text="{Binding SelectedItem, UpdateSourceTrigger=LostFocus}" local:ComboBoxBehaviors.CommitOnEnter="true" />

我认为这会给你你正在寻找的行为。请随意按原样使用它或根据您的喜好对其进行修改。

行为实现存在一个问题,如果您开始键入现有值(并且不按 Enter 键),然后从下拉列表中选择相同的值,在这种情况下,源不会更新,直到您按输入、更改焦点或选择不同的值。我确信这可以解决,但是对于我来说,花时间在这上面还不够,因为这不是一个正常的工作流程。

关于c# - 可编辑的 ComboBox 绑定(bind)和更新源触发器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30577176/

相关文章:

c# - PInvokeStackImbalance C# 调用非托管 C++ 函数

c# - 如何使用 Unity 将通用接口(interface)自动注册到该接口(interface)的非通用实现

c# - ASP.NET 和 C# 重定向

c# - 在 wpf c# 的网格中显示选中复选框的值

c# - 从 SQLite 数据库 C# WPF 中提取 DateTime 的问题

c# - 设置ImageBrush的图像偏移量?

c# - HTMLBody 拒绝输出我指定的字体大小,总是以不同的大小结束

C# WPF - 如何将 TextBox Enter 键按下绑定(bind)到方法?

c++ - 带有带有dlopen的共享库的不同数学符号绑定(bind),并直接链接到可执行文件(Linux)

r - lapply/R中的 promise