如何取消ComboBox的SelectionChanged事件?

27

有没有一种简单的方法来提示用户确认组合框选择的更改,并在用户选择否时不进行更改处理?

我们有一个组合框,改变选择会导致数据丢失。基本上,用户选择一种类型,然后他们可以输入该类型的属性。如果他们更改了类型,我们会清除所有属性,因为它们可能不再适用。问题是,在理解选择之后,需要再次引发“SelectionChanged”事件。

这里是代码片段:

if (e.RemovedItems.Count > 0)
{
    result = MessageBox.Show("Do you wish to continue?", 
        "Warning", MessageBoxButton.YesNo, MessageBoxImage.Warning);

    if (result == MessageBoxResult.No)
    {
        if (e.RemovedItems.Count > 0)
            ((ComboBox)sender).SelectedItem = e.RemovedItems[0];
        else
            ((ComboBox)sender).SelectedItem = null;
    }
}

我有两个解决方案,但都不是很喜欢。

  1. 在用户选择了'No'之后,删除SelectionChanged事件处理程序,更改所选项目,然后重新注册SelectionChanged事件处理程序。这意味着您必须在类中保留事件处理程序的引用,以便可以添加和删除它。

  2. 创建一个ProcessSelectionChanged布尔值作为类的一部分。始终在事件处理程序的开头检查它。在我们将选择更改回来之前将其设置为false,然后在之后重置为true。这样做是可行的,但我不喜欢使用标志来基本上使事件处理程序无效。

有没有其他解决方案或对我提到的方法进行改进?


1
它可能有点老了,但是这个链接https://dev59.com/kHRC5IYBdhLWcg3wYP-h 可以帮到你吗? - M.Babcock
我本来想说为什么不使用JavaScript来进行这个检查,但是后来我注意到这是一个Winform应用程序,很抱歉:(。 - JonH
bool标志是标准解决方案。不在Items集合中包含该项,从而不给用户选择该项的方式,是一个好的解决方案。 - Hans Passant
不确定您所说的是不包括“Items”集合中的项目。列表中没有一个特定的项目会导致这种情况,而是任何对所选项目的更改。我希望允许他们更改类型,ComboBox 包含可用类型的列表,但如果他们选择了新类型,我想先警告他们并允许他们撤销。 - WPFNewbie
注意:SelectionChanged不是WinForms ComboBox上的事件处理程序,只有WPF ComboBox才有,因此我正在编辑问题和标签以进行修复。 - Rob
我刚遇到了同样的问题。在我的情况下,我想简单地禁止用户选择其可见性为折叠的ComboBoxItems。我的解决方案是从ComboBox继承自己的ComboBoxEx类,并覆盖OnPreviewMouseWheel以完全禁用鼠标滚轮。然后我重载了公共SelectionChanged事件,并添加了一个内部SelectionChanged处理程序来“预览”选择更改。如果新选择的项目不可见,则将其推进(或递减)到下一个可用项目。如果该项可见,则引发公共事件。非常干净,运行良好! - Geo...
8个回答

27

我找到了这个不错的实现。

 private bool handleSelection=true;

private void ComboBox_SelectionChanged(object sender,
                                        SelectionChangedEventArgs e)
        {
            if (handleSelection)
            {
                MessageBoxResult result = MessageBox.Show
                        ("Continue change?", MessageBoxButton.YesNo);
                if (result == MessageBoxResult.No)
                {
                    ComboBox combo = (ComboBox)sender;
                    handleSelection = false;
                    combo.SelectedItem = e.RemovedItems[0];
                    return;
                }
            }
            handleSelection = true;
        }

来源:http://www.amazedsaint.com/2008/06/wpf-combo-box-cancelling-selection.html


1
也许可以创建一个继承自ComboBox的类,并重写OnSelectedItemChanged(或OnSelectionChangeCommitted)方法。

是的,我也在考虑这个问题,我想看到一个实现。 - CRice

1

SelectionChanged 事件处理程序中进行验证,可以在选择无效时取消逻辑,但我不知道如何轻松地取消事件或项目选择。

我的解决方案是子类化 WPF 组合框并添加一个内部处理程序用于 SelectionChanged 事件。每当事件触发时,我的私有内部处理程序会引发自定义的 SelectionChanging 事件。

如果在相应的 SelectionChangingEventArgs 上设置了 Cancel 属性,则不会引发事件,并且 SelectedIndex 将恢复到其先前的值。否则,将引发新的遮蔽基本事件的 SelectionChanged。希望这能帮助你!


SelectionChanging事件的EventArgs和处理程序委托:

public class SelectionChangingEventArgs : RoutedEventArgs
{
    public bool Cancel { get; set; }
}

public delegate void 
SelectionChangingEventHandler(Object sender, SelectionChangingEventArgs e);

ChangingComboBox类的实现:

public class ChangingComboBox : ComboBox
{
    private int _index;
    private int _lastIndex;
    private bool _suppress;

    public event SelectionChangingEventHandler SelectionChanging;
    public new event SelectionChangedEventHandler SelectionChanged;

    public ChangingComboBox()
    {
        _index = -1;
        _lastIndex = 0;
        _suppress = false;
        base.SelectionChanged += InternalSelectionChanged;
    }

    private void InternalSelectionChanged(Object s, SelectionChangedEventArgs e)
    {
        var args = new SelectionChangingEventArgs();
        OnSelectionChanging(args);
        if(args.Cancel)
        {
            return;
        }
        OnSelectionChanged(e);
    }

    public new void OnSelectionChanged(SelectionChangedEventArgs e)
    {
        if (_suppress) return;

        // The selection has changed, so _index must be updated
        _index = SelectedIndex;
        if (SelectionChanged != null)
        {
            SelectionChanged(this, e);
        }
    }

    public void OnSelectionChanging(SelectionChangingEventArgs e)
    {
        if (_suppress) return;

        // Recall the last SelectedIndex before raising SelectionChanging
        _lastIndex = (_index >= 0) ? _index : SelectedIndex;
        if(SelectionChanging == null) return;

        // Invoke user event handler and revert to last 
        // selected index if user cancels the change
        SelectionChanging(this, e);
        if (e.Cancel)
        {
            _suppress = true;
            SelectedIndex = _lastIndex;
            _suppress = false;
        }
    }
}

1
在WPF中动态设置对象:

    if (sender.IsMouseCaptured)
    {
      //perform operation
    }

当选择更改事件触发其他控件的选择更改事件时,这种方法会很有效。要区分系统和用户交互更改。 - aggsol

0
最简单的解决方案是使用PreviewMouseLeftButtonDown ComboBox事件。e.Handled可以用来退出并且不触发combobox的change事件。

0

我不认为使用调度程序来发布(或延迟)属性更新是一个好的解决方案,这更像是一个不真正需要的解决方法。以下解决方案完全符合mvvm,并且不需要调度程序。

  • 首先,使用显式绑定模式将SelectedItem与绑定。 //这使我们能够决定是否使用UpdateSource()方法提交更改到VM或使用UpdateTarget()方法在UI中还原。
  • 接下来,在VM中添加一个确认更改是否允许的方法(此方法可以包含提示用户确认并返回布尔值的服务)。

在视图代码后台钩住SelectionChanged事件,并根据VM.ConfirmChange(...)方法返回值更新源(即VM)或目标(即V)如下:

    private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if(e.AddedItems.Count != 0)
        {
            var selectedItem = e.AddedItems[0];
            if (e.AddedItems[0] != _ViewModel.SelectedFormatType)
            {
                var comboBoxSelectedItemBinder = _TypesComboBox.GetBindingExpression(Selector.SelectedItemProperty); //_TypesComboBox is the name of the ComboBox control
                if (_ViewModel.ConfirmChange(selectedItem))
                {
                    // Update the VM.SelectedItem property if the user confirms the change.
                    comboBoxSelectedItemBinder.UpdateSource();
                }
                else
                {
                    //otherwise update the view in accordance to the VM.SelectedItem property 
                    comboBoxSelectedItemBinder.UpdateTarget();
                }
            }
        }
    }

0

这是一个老问题,但是在反复努力解决这个问题后,我想出了这个解决方案:

ComboBoxHelper.cs:

public class ComboBoxHelper
{
    private readonly ComboBox _control;

    public ComboBoxHelper(ComboBox control)
    {
        _control = control;

        _control.PreviewMouseLeftButtonDown += _control_PreviewMouseLeftButtonDown; ;
        _control.PreviewMouseLeftButtonUp += _control_PreviewMouseLeftButtonUp; ;
    }

    public Func<bool> IsEditingAllowed { get; set; }
    public Func<object, bool> IsValidSelection { get; set; }
    public Action<object> OnItemSelected { get; set; }

    public bool CloseDropDownOnInvalidSelection { get; set; } = true;

    private bool _handledMouseDown = false;
    private void _control_PreviewMouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
    {
        var isEditingAllowed = IsEditingAllowed?.Invoke() ?? true;

        if (!isEditingAllowed)
        {
            e.Handled = true;   
            return;
        }
        
        _handledMouseDown = true;
    }
    private void _control_PreviewMouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
    {
        if (!_handledMouseDown) return;
        _handledMouseDown = false;

        var fe = (FrameworkElement)e.OriginalSource;

        if (fe.DataContext != _control.DataContext)
        {
            //ASSUMPTION: Click was on an item and not the ComboBox itself (to open it)
            var item = fe.DataContext;

            var isValidSelection = IsValidSelection?.Invoke(item) ?? true;
            
            if (isValidSelection)
            {
                OnItemSelected?.Invoke(item);
                _control.IsDropDownOpen = false;
            }
            else if(CloseDropDownOnInvalidSelection)
            {
                _control.IsDropDownOpen = false;
            }

            e.Handled = true;
        }
    }
}

可以在自定义的UserControl中这样使用:

public class MyControl : UserControl
{
    public MyControl()
    {
        InitializeComponent();

        var helper = new ComboBoxHelper(MyComboBox); //MyComboBox is x:Name of the ComboBox in Xaml
        
        helper.IsEditingAllowed = () => return Keyboard.Modifiers != Modifiers.Shift; //example
        
        helper.IsValidSelection = (item) => return item.ToString() != "Invalid example.";
        
        helper.OnItemSelected = (item) =>
        {
            System.Console.WriteLine(item);
        };
    }
}

这与SelectionChanged事件无关,事件触发不会产生比所需更频繁的副作用。因此,其他人可以安全地监听事件,例如更新他们的UI。还避免了由事件处理程序内部将选择重置为有效项引起的"递归"调用。

上述对于DataContext的假设可能并不是所有情况的完美选择,但可以轻松适应。一种可能的替代方法是检查ComboBox是否是e.OriginalSource的可视父级,当选择项目时它并不是。


0
没看完整个帖子,但是成功地取消了ComboBox在这种情况下显示下拉菜单的功能,我们只需要允许用户使用ctrl-c/复制文本即可。
        if (e.KeyCode == Keys.Control || e.KeyCode == Keys.C)
        {
            Clipboard.SetText(comboBoxPatternSearch.Text);
            e.Handled = true;
            e.SuppressKeyPress = true;
            return;
        }

我认为e.Handled什么都没做,是SuppressKeyPress起了作用。很抱歉没有更多时间,只是想贡献一下,因为我正好遇到了这个问题。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接