【问题标题】:WPF MVVM - Update Dropdown When ClickedWPF MVVM - 单击时更新下拉列表
【发布时间】:2014-11-20 08:39:08
【问题描述】:

我有一个下拉列表 (ComboBox),它显示了机器上所有可用的 com 端口。现在,当您连接和断开设备时,端口来来去去。

出于性能原因,我不想继续调用System.IO.Ports.SerialPort.GetPortNames(),而是在用户单击组合框时调用它?这可能吗?是否有针对此问题的 MVVM 方法?

【问题讨论】:

  • 处理 DropDownOpened 事件并在处理程序中调用 GetPortsNames 以更新项目。使用 MVVM,您可以使用某种 Command 执行相同的操作,然后相应地更新 ViewModel 中的数据。

标签: c# wpf drop-down-menu combobox


【解决方案1】:

使用InvokeCommandAction

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"

DropDownOpenedCommand 是 ViewModel 上的 ICommand 属性。

<ComboBox>
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="DropDownOpened">
            <i:InvokeCommandAction Command="{Binding DropDownOpenedCommand}"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
</ComboBox>

编辑:显然是 DropDownOpened 而不是 SelectionChanged,正如 Patrice 评论的那样。

【讨论】:

  • 我喜欢这个——必须出去获取 NuGet 包 System.Windows.Interactivity v4.0 for WPF 才能工作
  • 对尝试在 Dot Net Core 3.1 中执行此操作的其他人的快速更新:根据此答案 stackoverflow.com/questions/8360209/… System.Windows.Interactivity 与 Dot Net Core 不兼容,但 Microsoft.Xaml.Behaviors .Wpf 提供所有相同的功能。
【解决方案2】:

您可以使用类似 MVVMLight 的 EventToCommand 来完成此操作。基本上,单击组合的事件将与您的 MVVM 命令绑定挂钩,然后会触发调用 GetPortNames() 的方法。

这里有一些替代方案:

MVVM Light: Adding EventToCommand in XAML without Blend, easier way or snippet?(检查接受的答案)

http://www.danharman.net/2011/08/05/binding-wpf-events-to-mvvm-viewmodel-commands/(棱镜)

【讨论】:

    【解决方案3】:

    我的建议是放弃“仅在点击时更新”的想法,并为此使用绑定和通知(除非出于某种原因,您认为会有如此多的连接/断开连接事件会减慢您的系统)。最简单的版本是依赖属性。

    像这样在 ViewModel 上提供一个 IObservableList&lt;Port&gt; 属性作为依赖属性:

        /// <summary>
        /// Gets or sets...
        /// </summary>
        public IObservableList<Port> Ports
        {
            get { return (IObservableList<Port>)GetValue(PortsProperty); }
            set { SetValue(PortsProperty, value); }
        }
    
        public static readonly DependencyProperty PortsProperty = DependencyProperty.Register("Ports", typeof(IObservableList<Port>), typeof(MyViewModelClass), new PropertyMetadata(new ObservableList<Port>));
    

    现在,无论何时连接或断开设备,您都可以在该列表中添加/删除项目,只是不要替换该列表。这将强制列表为列表上的每个操作发送ListChangedEvent,并且 ComboBox(或任何其他绑定的 UI)将对这些事件做出反应。

    这对您来说应该足够高效,因为这只会导致 UI ComboBox 在事件发生时更新。

    【讨论】:

    • 恕我直言,OP对性能的担忧不是UI更新,而是System.IO.Ports.SerialPort.GetPortNames()的频繁调用。
    【解决方案4】:

    我尝试将事件路由到命令:

    XAML:

    <ComboBox 
        ItemsSource="{Binding Items}" 
        local:ControlBehavior.Event="SelectionChanged"
        local:ControlBehavior.Command="{Binding Update}" />
    

    代码:

    using System;
    using System.Reflection;
    using System.Windows;
    using System.Windows.Input;
    
    namespace StackOverflow
    {
        public class ControlBehavior
        {
            public static DependencyProperty CommandParameterProperty = DependencyProperty.RegisterAttached("CommandParameter", typeof(object), typeof(ControlBehavior));
            public static DependencyProperty CommandProperty = DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(ControlBehavior));
            public static DependencyProperty EventProperty = DependencyProperty.RegisterAttached("Event", typeof(string), typeof(ControlBehavior), new PropertyMetadata(PropertyChangedCallback));
    
            public static void EventHandler(object sender, EventArgs e)
            {
                var s = (sender as DependencyObject);
                if (s != null)
                {
                    var c = (ICommand)s.GetValue(CommandProperty);
                    var p = s.GetValue(CommandParameterProperty);
                    if (c != null && c.CanExecute(s))
                        c.Execute(s);
                }
            }
    
            public static void PropertyChangedCallback(DependencyObject o, DependencyPropertyChangedEventArgs a)
            {
                if (a.Property == EventProperty)
                {
                    EventInfo ev = o.GetType().GetEvent((string)a.NewValue);
                    if (ev != null)
                    {
                        var del = Delegate.CreateDelegate(ev.EventHandlerType, typeof(ControlBehavior).GetMethod("EventHandler"));
                        ev.AddEventHandler(o, del);
                    }
                }
            }
    
            public string GetEvent(UIElement element)
            {
                return (string)element.GetValue(EventProperty); 
            }
            public static void SetEvent(UIElement element, string value)
            {
                element.SetValue(EventProperty, value);
            }
            public ICommand GetCommand(UIElement element)
            {
                return (ICommand)element.GetValue(CommandProperty);
            }
            public static void SetCommand(UIElement element, ICommand value)
            {
                element.SetValue(CommandProperty, value);
            }
            public object GetCommandParameter(UIElement element)
            {
                return element.GetValue(CommandParameterProperty);
            }
            public static void SetCommandParameter(UIElement element, object value)
            {
                element.SetValue(CommandParameterProperty, value);
            }
    
        }
    }
    

    【讨论】:

    • 确保在属性更改时取消注册事件以防止内存泄漏。我做了几乎相同的事情here,但这只是因为 InvokeCommandAction 不支持传递 EventArgs - 如果您不需要,我认为 InvokeCommandAction 更容易(请参阅我的答案)。
    猜你喜欢
    • 2021-03-15
    • 2019-01-05
    • 2020-04-19
    • 1970-01-01
    • 1970-01-01
    • 2010-10-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多