5

I have a dropdown (ComboBox) that displays all the com ports available on a machine. Now, ports come and go when you connect and disconnect devices.

For performance reasons I don't want to keep calling System.IO.Ports.SerialPort.GetPortNames(), but rather just call that when the user clicks on the Combobox? Is this possible? Is there an MVVM approach to this problem?

Zeus82
  • 6,065
  • 9
  • 53
  • 77
  • 3
    Handle the `DropDownOpened` event and call `GetPortsNames` in the handler to update the items. With MVVM you would do the same thing with some kind of `Command` and then update the data in the ViewModel accordingly. – Patrice Gahide Sep 25 '14 at 14:30

4 Answers4

11

Use InvokeCommandAction.

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

DropDownOpenedCommand is an ICommand property on your ViewModel.

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

Edit: obviously DropDownOpened not SelectionChanged, as Patrice commented.

Mike Fuchs
  • 12,081
  • 6
  • 58
  • 71
  • 2
    I like this -- had to go out and get the NuGet Package `System.Windows.Interactivity v4.0 for WPF` for it to work – bdimag Sep 25 '14 at 17:41
  • 1
    A quick update for anyone else trying to do this in Dot Net Core 3.1: As per this answer https://stackoverflow.com/questions/8360209/how-to-add-system-windows-interactivity-to-project/56240223#56240223 System.Windows.Interactivity is not compatible with Dot Net Core, but Microsoft.Xaml.Behaviors.Wpf provides all the same features. – Malacandrian Apr 11 '20 at 12:26
0

You can use something like MVVMLight's EventToCommand to accomplish this. Basically, the event of clicking the combo would be hooked to your MVVM command binding, which would then fire the method that calls GetPortNames().

Here are some alternatives:

MVVM Light: Adding EventToCommand in XAML without Blend, easier way or snippet? (check the accepted answer)

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

Community
  • 1
  • 1
0

What I would recommend is scrapping the 'only update on clicks' idea, and just use binding and notifications for this (unless for some reason you think there will be so many Connect/Disconnect events it will slow your system). The simplest version of that would be a dependency property.

Provide an IObservableList<Port> property as a dependency property on your ViewModel like this:

    /// <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>));

Now you may add/remove items to/from that list whenever you connect or disconnect devices, just do not replace the list. This will force the list to send off a ListChangedEvent for each action on the list, and the ComboBox (or any other bound UI) will react to those events.

This should be performant enough for you, as this will only cause the UI ComboBox to update whenever an event goes through.

GEEF
  • 1,175
  • 5
  • 17
  • IMHO, the OP's concerns about performance are not the UI updates, but the frequently calling of `System.IO.Ports.SerialPort.GetPortNames()`. – quinmars Jun 16 '16 at 10:13
0

I took a stab at routing events to a command:

XAML:

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

Code:

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);
        }

    }
}
bdimag
  • 953
  • 8
  • 11
  • 1
    Make sure you unregister your event when the property changes to prevent memory leaks. I've done almost the same thing [here](http://stackoverflow.com/a/16317999/385995), but only because InvokeCommandAction does not support passing the EventArgs - if you don't need to that, I think InvokeCommandAction is much easier (see my answer). – Mike Fuchs Sep 25 '14 at 17:37