1

I am using Visual Studio 2017 Communitiy version/ .NET Framework 4.6.1/ WPF. Btw. I use MVVM.


My aim is to display all available serialports in a Combobox. I already implemented this but now I want to make the Combobox automatically update when the Dropdowmenu is opened or a new device is connected. Therefore I would need some property to make Binding to my ViewModel.

On MSDN I found Combobox.DroppedDown-Property which sounds good for me, but I can't use it, DroppedDown is not found...(is System.Windows.Forms the right Reference?).

Do you find my mistake or do you maybe have a better solution?

Thanks!

<UserControl x:Class="HC_SR04_MPU6050.View.SerialView_MPU_6050"
         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:local="clr-namespace:HC_SR04_MPU6050.View"
         mc:Ignorable="d">
<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="1*"/>
        <ColumnDefinition Width="1*"/>
        <ColumnDefinition Width="1*"/>
        <ColumnDefinition Width="1*"/>
        <ColumnDefinition Width="1*"/>
    </Grid.ColumnDefinitions>

    <Grid.RowDefinitions>
        <RowDefinition Height="1*"/>
        <RowDefinition Height="1*"/>
    </Grid.RowDefinitions>

    <Label Grid.Row="0" Content="MPU-6050" HorizontalAlignment="Center"/>
    <Label Grid.Row="0" Grid.Column="3" Content="Y-ROT[°]" HorizontalAlignment="Center"/>
    <Button Command="{Binding Connect_Clicked}" Grid.Row="1" Grid.Column="0" Content="Connect" Height="20" Width="60" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="6" ToolTip="Opens/Connects the selected COM-PORT"/>
    <Button Command="{Binding Measure_Clicked}" Grid.Row="1" Grid.Column="1" Content="MEASURE" Height="20" Width="60" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="6" ToolTip="Starts the Measurement routine"/>
    <Button Command="{Binding Stop_Clicked}" Grid.Row="1" Grid.Column="2" Content="STOP" Height="20" Width="60" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="6" ToolTip="Stops the communication/ Closes/Disconnects the selected COM-PORT"/>
    <Label Content="{Binding Rotation.Y_Rotation}" Grid.Row="1" Grid.Column="3" Height="25" Width="50" Margin="6" HorizontalAlignment="Center" VerticalAlignment="Center" BorderThickness="1" BorderBrush="Black" ToolTip="Output from Arduino/HC-SR04" VerticalContentAlignment="Center" FontSize="11"/>

    <ComboBox Grid.Column="4" Grid.Row="1" Width="62" Height="25" Margin="6" Text="{Binding Rotation.Port_Name}" ItemsSource="{Binding Rotation.AvailablePorts}"/>
</Grid>
</UserControl>
Andreas
  • 27
  • 4
  • Not 100% sure what your trying to do, but if you need to see if the combo box dropped down, the wpf event DropDownOpened can be defined in the xaml. System.Windows.Forms properties are for win forms controls and can't be used on wpf controls. So are you trying to modify what items are available in the combo box? and if so have you tried just adding/removing values from Rotation.AvailablePorts? – Hack Jun 12 '18 at 12:34
  • I want to update the list of available ports so I don't have to reopen my application (I'm just_to_lazy). DropDownOpened does not work for me; There's always the Exception that VS can't convert `System.Reflection.RuntimeEventInfo` to `System.Reflection.MethodInfo`. I use Binding. That's why I'm in need of a property(correct?). I also tried IsDropDownOpen (it's a property) and then the program is in halt-mode. – Andreas Jun 12 '18 at 14:16

3 Answers3

1

You can also see if an interaction trigger can help you.

Just put something like this in your user control open tag:

xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

then inside your combo box tag put:

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

This way every time the drop down is opened you get a call to your UpdateCommListCommand.

You may get some refresh issues tough. I'm not sure the list update will fire before the opened combo box is actually displayed.

0

I don't see where you're setting the ViewModel so I'll assume it's in code behind. (Personally I prefer to set it in XAML also and never touch code behind in MVVM but that's nothing to do with this.)

If you're using your ViewModel for binding then the ViewModel is responsible for translating anything you want a view to react to and notify the listening views. However, business / application logic doesn't really live in ViewModel either. What I suggest may be a Singleton class that listens to and communicates the ports for you. Have it manage the list of ports etc. Then your ViewModel can listen to that class and update itself whenever a device is connected etc. As soon as the ViewModel updates (if wired correctly) then the view should update as well. So that solves when a device is connected your view updates... Super easy so far. Now let's make it update as well when you click the ComboBox dropdown.

To make this happen we want to add an ICommand to the ViewModel (I would do this in the form of a RelayCommand that you'll need to make yourself or try a third party version like SpeckyWPF nuget package.)

Once you have a command in the ViewModel have it point to the method you use to update the serial ports.

Now in the View, when you click the ComboBox you have an event. I suggest not using code behind, which may seem easier, but to make an AttachableProperty or custom ComboBox control so that you can attach a command to this event. (There are also third party tools to help you do this as well.)

What will happen is your ViewModel becomes the notifying in all cases. You simply bind to the command to update it and call it when needed and or just wait for a device to be plugged in. The View itself won't require any change except for adding the command and all of the notifications are in the VM and the logic in the class it belongs for serial port.

Michael Puckett II
  • 6,586
  • 5
  • 26
  • 46
0

You can use a ManagementEventWatcher object to fire up an event when the serial ports are changed (added/removed).

ManagementEventWatcher is in the System.Management library.

For example in the ViewModel

using System.Collections.ObjectModel;
using System.IO.Ports;
using System.Management;

public class ViewModel
{
    private ManagementEventWatcher _watcher;

    public ViewModel()
    {
        WqlEventQuery query = new WqlEventQuery("SELECT * FROM Win32_DeviceChangeEvent");
        _watcher = new ManagementEventWatcher(query);
        _watcher.EventArrived += (s, e) => RefreshPortNames();
        _watcher.Start();
    }

    public ObservableCollection<string> PortNames { get; } = new ObservableCollection<string>(SerialPort.GetPortNames());

    private void RefreshPortNames()
    {
        // The `ManagementEventWatcher.EventArrived` event is fired from a different thread than the UI thread
        // so we need to return to UI thread to manipulate the `PortNames` property.
        System.Windows.Application.Current.Dispatcher.Invoke(() =>
        {
            PortNames.Clear();

            foreach (string portName in SerialPort.GetPortNames())
            {
                PortNames.Add(portName);
            }
        });
    }

    //TODO: Dispose `_watcher` object if this `ViewModel` is short-lived.
}

Then you just need to bind the PortNames property to ComboBox.ItemsSource as you normally do.

<ComboBox ItemsSource="{Binding PortNames}" />
Jan Paolo Go
  • 5,842
  • 4
  • 22
  • 50
  • First of all Thanks, at least it does init if I don't `Start` the `_watcher`. Yep, I have some Dispatcher Thread issue. This happens when the ObservableCollection will be changed. So at PortName.Clear(){or any other manipulation of the Collection}. It wants an Dispatcher Thread to change the SourceCollection (PortNames). I'll check on the web, but if you have one more hint I would be really grateful. – Andreas Jun 12 '18 at 15:11
  • I edited the `RefreshPortNames` method to get rid of the dispatcher thread issue. You can also use `Task` and `TaskScheduler.FromCurrentSynchronizationContext()` if you want to be more generic. – Jan Paolo Go Jun 12 '18 at 15:17
  • One more question, do you know how to print out also a small comment like in the devicemanager on windows e.g. it is not only written COM4; it's COM4 Arduino Mega 2560... just some extra info to find out which port you need to select – Andreas Jun 12 '18 at 15:43
  • I believe this answer should help you. https://stackoverflow.com/a/2876126/5588197. Which also uses the `System.Management` library :) – Jan Paolo Go Jun 12 '18 at 15:45