WPF MVVM - Update Dropdown When Clicked - c#

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?

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.

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)

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.

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

Related

How can I be notified when collection-type DP is changed?

My English skill is poor because I'm not a native English speaker.
I have created as following a behavior that working at the TextBox control.
The behavior has a collection-type DP named Items.
class HighlightBehavior : Behavior<TextBox>
{
public List<TextStyle>Items
{
get { return (List<TextStyle>)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
public static readonly DependencyProperty ItemsProperty =
DependencyProperty.Register("Items", typeof(List<TextStyle>), typeof(HighlightBehavior), new PropertyMetadata(ItemsChanged));
private static void ItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// break point
}
}
And... I have created a MainWindow to use as following code above behavior.
<MainWindow>
<TextBox>
<i:interaction.Behaviors>
<behavior:HighlightBehavior/>
</i:interaction.Behavior>
</TextBox>
</MainWindow>
And I have written a MainWindowViewModel that has a collection-type DP named HighlightItems.
class MainWindowViewModel : ViewModelBase
{
public List<TextStyle> HighlightItems
{
get { return (List<TextStyle>)GetValue(HighlightItemsProperty ); }
set { SetValue(HighlightItemsProperty , value); }
}
public static readonly DependencyProperty HighlightItemsProperty =
DependencyProperty.Register("HighlightItems", typeof(List<TextStyle>), typeof(HighlightBehavior), new PropertyMetadata(null));
public MainWindowViewModel()
{
SetValue(HighlightItemsProperty, new List<TextStyle>());
}
}
And I have bound the MainWindowViewModel to MainWindow and connected HighlightItems(DP) of MainWindowViewModel with Items(DP) of HighlightBehavior as the following code.
<MainWindow>
<TextBox>
<i:interaction.Behaviors>
<behavior:HighlightBehavior Items="{Binding HighlightItems, Mode=TwoWay}"/>
</i:interaction.Behavior>
</TextBox>
</MainWindow>
To sum up, the structure is the following figure.
I have expected that ItemsChanged of HighlightBehavior is called whenever Items changed.
But it is not called.
I want to get notification whenever collection-type DP(Items) of HighlightBehavior is changed.
What must I do to reach this goal?
Thank you for reading.
I'll wait for an answer.
I believe what you're looking for is ObservableCollection. This is a special type of collection which raises its CollectionChanged event whenever an item is added, removed, changed or moved.
I recommend the following:
Instead of declaring HighlightItems as List<TextStyle>, declare it as ObservableCollection<TextStyle>.
Add another method to HighlightBehavior to handle CollectionChanged, for example:
HighlightItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
Your current implementation of ItemsChanged will be called whenever HighlightItems is set. Use that to attach an event handler to CollectionChanged like so:
var col = (ObservableCollection<TextStyle>)e.NewValue;
if (col != null) { col.CollectionChanged += HighlightItemsCollectionChanged; }
Don't forget to remove any existing event handler to the previous collection in case HighlightItems is set move than once. You can add this to ItemsChanged along with the previous snippet:
col = (ObservableCollection<TextStyle>)e.OldValue;
if (col != null) { col.CollectionChanged -= HighlightItemsCollectionChanged; }
HighlightItemsCollectionChanged will now be called whenever an item is added or removed from HighlightItems. Do whatever you need to do in this method, or if you want the code to also run when the collection itself is replaced, you can make another method that actually does what you want, and then call that method from both ItemsChanged and HighlightItemsCollectionChanged.
Thank you.
I have changed the code following your advice and now I can receive a notification when the element of the collection is changed.
I knew about the ObservableCollection but I didn't know how to use right about CollectionChanged event.
In fact, previous I tried to use the ObservableCollection and registered the CollectionChanged delegate method at the Constructer as following but it is not called.
public ObservableCollection<TextStyle> Items
{
get { return (ObservableCollection<TextStyle>)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
// Using a DependencyProperty as the backing store for Items. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ItemsProperty =
DependencyProperty.Register("Items", typeof(ObservableCollection<TextStyle>), typeof(HighlightBehavior),
new PropertyMetaData(null));
public HighlightBehavior()
{
SetValue(ItemsProperty, new ObservableCollection<TextStyle>());
Items.CollectionChanged += OnCollectionChanged;
}
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// The code when the collection is changed.
}
Now, I have registered the CollectionChanged delegate method in the PropertyChangedCallback method as following and it(OnCollectionChanged method at the following code) is called.
public ObservableCollection<TextStyle> Items
{
get { return (ObservableCollection<TextStyle>)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
// Using a DependencyProperty as the backing store for Items. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ItemsProperty =
DependencyProperty.Register("Items", typeof(ObservableCollection<TextStyle>), typeof(HighlightBehavior),
new PropertyMetaData(ItemsChanged));
private static void ItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var col = (ObservableCollection<TextStyle>)e.NewValue;
if (col != null) { col.CollectionChanged += OnCollectionChanged; ; }
col = (ObservableCollection<TextStyle>)e.OldValue;
if (col != null) { col.CollectionChanged -= OnCollectionChanged; }
}
public HighlightBehavior()
{
SetValue(ItemsProperty, new ObservableCollection<TextStyle>());
}
Thank you for your answer in detail.

DependencyPropertyChanged callback event is not getting fired

I have a UserControl named MultiChartControl, which has a dependency property named MultiChartInputDetails.
public ChartsData MultiChartInputDetails
{
get { return (ChartsData)GetValue(MultiChartInputDetailsProperty); }
set { SetValue(MultiChartInputDetailsProperty, value); }
}
public static readonly DependencyProperty MultiChartInputDetailsProperty =
DependencyProperty.Register("MultiChartInputDetails", typeof(ChartsData), typeof(MultiChartControl), new UIPropertyMetadata(new PropertyChangedCallback(MultiChartInputDetailsChanged)));
But the following callback method is not getting fired even once:
private static void MultiChartInputDetailsChanged(DependencyObject d, DependencyPropertyChangedEventArgs args)
{
MultiChartControl chart = d as MultiChartControl;
if (chart != null)
{
if (chart.ChartGrid.Children != null)
chart.ChartGrid.Children.Clear();
chart.InitilizeData();
}
MessageBox.Show("MultiChartInputDetailsChanged fired");
}
And the Main master control:
<multicharting:MultiChartControl x:Uid="multicharting:MultiChartControl_1"
MultiChartInputDetails="{Binding Path=MultiChartsInputDetails, ElementName=Chart, Converter={StaticResource DebugConverter}}"/>
This is because the DependencyProperty is not set to bind by two-way. This is done as follows:
DependencyProperty.Register("MultiChartInputDetails",
typeof(ChartsData),
typeof(MultiChartControl),
new FrameworkPropertyMetadat(default(ChartsData),
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
MultiChartInputDetailsChanged)
Furthermore check whether there are any binding errors. If you do not want to provide a dependency property that performs a two-way binding per default then you could write your bindinga as follows:
<multicharting:MultiChartControl x:Uid="multicharting:MultiChartControl_1"
MultiChartInputDetails="{Binding Path=MultiChartsInputDetails,
Mode=TwoWay,
ElementName=Chart,
Converter={StaticResource DebugConverter}}"/>

Alter hosted Winform control from ViewModel

Sorry to be cliche... but I'm pretty new to WPF and MVVM so I'm not sure how to handle this properly. I have a WinForms control within one of my views that I need to modify in it's code behind when an event is raised in the ViewModel. My view's datacontext is inherited so the viewmodel is not defined in the views constructor. How would I go about properly handling this? I am not using any frameworks with built in messengers or aggregators. My relevant code is below. I need to fire the ChangeUrl method from my ViewModel.
EDIT: Based on the suggestion from HighCore, I have updated my code. I am still not able to execute the ChangeUrl method however, the event is being raised in my ViewModel. What modifications need to be made??
UserControl.xaml
<UserControl ...>
<WindowsFormsHost>
<vlc:AxVLCPlugin2 x:Name="VlcPlayerObject" />
</WindowsFormsHost>
</UserControl>
UserControl.cs
public partial class VlcPlayer : UserControl
{
public VlcPlayer()
{
InitializeComponent();
}
public string VlcUrl
{
get { return (string)GetValue(VlcUrlProperty); }
set
{
ChangeVlcUrl(value);
SetValue(VlcUrlProperty, value);
}
}
public static readonly DependencyProperty VlcUrlProperty =
DependencyProperty.Register("VlcUrl", typeof(string), typeof(VlcPlayer), new PropertyMetadata(null));
private void ChangeVlcUrl(string newUrl)
{
//do stuff here
}
}
view.xaml
<wuc:VlcPlayer VlcUrl="{Binding Path=ScreenVlcUrl}" />
ViewModel
private string screenVlcUrl;
public string ScreenVlcUrl
{
get { return screenVlcUrl; }
set
{
screenVlcUrl = value;
RaisePropertyChangedEvent("ScreenVlcUrl");
}
}
WPF does not execute your property setter when you Bind the property, instead you must define a Callback method in the DependencyProperty declaration:
public string VlcUrl
{
get { return (string)GetValue(VlcUrlProperty); }
set { SetValue(VlcUrlProperty, value); }
}
public static readonly DependencyProperty VlcUrlProperty =
DependencyProperty.Register("VlcUrl", typeof(string), typeof(VlcPlayer), new PropertyMetadata(null, OnVlcUrlChanged));
private static void OnVlcUrlChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var player = obj as VlcPlayer;
if (obj == null)
return;
obj.ChangeVlcUrl(e.NewValue);
}
private void ChangeVlcUrl(string newUrl)
{
//do stuff here
}

Is it possible to bind a command to a resource?

I have a view model that provides a RelayCommand LoadImage.
Typically I would use a button and bind the command to this button.
However I would like to call the LoadImage command from view's codebehind (I need to do some view related stuff that must not be put into view model)
The one way I am aware is to create an event handler for the button, e.g. Button_Click.
In Button_Click I would cast DataContext to the corresponding ViewModel and use this instance to call (DataContext as MyViewModel).LoadImage.Execute(...)
This is odd as I need to know the view model.
What I am trying, is to bind LoadImage not to a button but to a resource in the view, so the Button_Click event just need to call FindResource with a given name and cast it to ICommand without the necessity to know the specific ViewModel.
Is this possible? The command itself is not static as it needs to know the context in what it is called.
You can make it by creating a behavior, which requires Prism referred in your project:
public class LoadImageBehavior : Behavior<Button>
{
public public static static readonly DependencyProperty CommandProperty =
DependencyProperty.Register("Command", typeof (ICommand), typeof (LoadImageBehavior));
public ICommand Command
{
get { return (ICommand) GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Click += AssociatedObject_Click;
}
private void AssociatedObject_Click(object sender, RoutedEventArgs e)
{
//Logic...
if(Command != null && Command.CanExecute(null))
Command.Execute(null);
//Logic...
}
}
On Xaml:
<Button>
<i:Interaction.Behaviors>
<Behaviors:LoadImageBehavior Command="{Binding LoadImageCommand}"/>
</i:Interaction.Behaviors>
</Button>
Based on Bill Zhangs idea of behaviours I've created a generic version which is quite control agnostic and which allows to be reused.
The required assembly is
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
I've created a Trigger action that passes the execution along to an event handler:
using System.Windows;
using System.Windows.Input;
using System.Windows.Interactivity;
using System;
namespace Misc
{
public class CommandWithEventAction : TriggerAction<UIElement>
{
public event Func<object, object> Execute;
public static DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(CommandWithEventAction), null);
public ICommand Command
{
get
{
return (ICommand)GetValue(CommandProperty);
}
set
{
SetValue(CommandProperty, value);
}
}
public static DependencyProperty ParameterProperty = DependencyProperty.Register("Parameter", typeof(object), typeof(CommandWithEventAction), null);
public object Parameter
{
get
{
return GetValue(ParameterProperty);
}
set
{
SetValue(ParameterProperty, value);
}
}
protected override void Invoke(object parameter)
{
var result = Execute(Parameter);
Command.Execute(result);
}
}
}
To avoid any logic in a custom behaviour this allows to hook up any event to an event callback followed by a command call.
XAML:
<Button>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<misc:CommandWithEventAction Command="{Binding LoadImageCommand}" Parameter="Custom data" Execute="CommandWithEventAction_OnExecute"/>
</i:EventTrigger>
</i:Interaction.Triggers>
Execute
</Button>
This will pass the "Custom data" string boxed as object to a function called
CommandWithEventAction_OnExecute
its signature of Func<object,object> may use the parameter and need to return something that will then be boxed into object and passed to the LoadImageCommand

Binding Commands to Events?

What's a good method to bind Commands to Events? In my WPF app, there are events that I'd like to capture and process by my ViewModel but I'm not sure how. Things like losing focus, mouseover, mousemove, etc. Since I'm trying to adhere to the MVVM pattern, I'm wondering if there's a pure XAML solution.
Thanks!
Use System.Windows.Interactivity
…xmlns:i=http://schemas.microsoft.com/expression/2010/interactivity…
<Slider
<i:Interaction.Triggers>
<i:EventTrigger EventName="ValueChanged">
<i:InvokeCommandAction
Command="{Binding MyCommand}"
CommandParameter="{Binding Text, ElementName=textBox}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Slider>
Make sure your project references the assembly System.Windows.Interactivity.
Source: MSDN Blog Executing a command from an event of your choice
[Update]
Have a look to to Microsoft.Xaml.Behaviors.Wpf (available since 03.12.2018) Official package by Microsoft.
Have a look at Marlon Grech's Attached Command Behaviour, it could be exactly what you're looking for
In order to handle events, you must have some code that attaches itself to the event and executes your command in response. The final goal is to have in XAML:
MouseMoveCommand="{Binding MyCommand}"
In order to achieve this you need to define an attached property for each event that you want to handle. See this for an example and a framework for doing this.
I implemented it using Attached Properties and Reflection. I cannot say it is the best implementation, but I will maybe improve it and it may be a good start for you.
public class EventBinding : DependencyObject
{
public static string GetEventName(DependencyObject obj)
{
return (string)obj.GetValue(EventNameProperty);
}
public static void SetEventName(DependencyObject obj, string value)
{
obj.SetValue(EventNameProperty, value);
var eventInfo = obj.GetType().GetEvent(value);
var eventHandlerType = eventInfo.EventHandlerType;
var eventHandlerMethod = typeof(EventBinding).
GetMethod("EventHandlerMethod", BindingFlags.Static | BindingFlags.NonPublic);
var eventHandlerParameters = eventHandlerType.GetMethod("Invoke").GetParameters();
var eventArgsParameterType = eventHandlerParameters.
Where(p => typeof(EventArgs).IsAssignableFrom(p.ParameterType)).
Single().ParameterType;
eventHandlerMethod = eventHandlerMethod.MakeGenericMethod(eventArgsParameterType);
eventInfo.AddEventHandler(obj, Delegate.CreateDelegate(eventHandlerType, eventHandlerMethod));
}
private static void EventHandlerMethod<TEventArgs>(object sender, TEventArgs e)
where TEventArgs : EventArgs
{
var command = GetCommand(sender as DependencyObject);
command.Execute(new EventInfo<TEventArgs>(sender, e));
}
public static readonly DependencyProperty EventNameProperty =
DependencyProperty.RegisterAttached("EventName", typeof(string), typeof(EventHandler));
public static ICommand GetCommand(DependencyObject obj)
{
return (ICommand)obj.GetValue(CommandProperty);
}
public static void SetCommand(DependencyObject obj, ICommand value)
{
obj.SetValue(CommandProperty, value);
}
public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(EventBinding));
}
public class EventInfo<TEventArgs>
{
public object Sender { get; set; }
public TEventArgs EventArgs { get; set; }
public EventInfo(object sender, TEventArgs e)
{
Sender = sender;
EventArgs = e;
}
}
public class EventInfo : EventInfo<EventArgs>
{
public EventInfo(object sender, EventArgs e)
: base(sender, e) { }
}
public class EventBindingCommand<TEventArgs> : RelayCommand<EventInfo<TEventArgs>>
where TEventArgs : EventArgs
{
public EventBindingCommand(EventHandler<TEventArgs> handler)
: base(info => handler(info.Sender, info.EventArgs)) { }
}
Examples of usage:
View
<DataGrid local:EventBinding.EventName="CellEditEnding"
local:EventBinding.Command="{Binding CellEditEndingCommand}" />
Model
private EventBindingCommand<DataGridCellEditEndingEventArgs> _cellEditEndingCommand;
public EventBindingCommand<DataGridCellEditEndingEventArgs> CellEditEndingCommand
{
get
{
return _cellEditEndingCommand ?? (
_cellEditEndingCommand = new EventBindingCommand<DataGridCellEditEndingEventArgs>(CellEditEndingHandler));
}
}
public void CellEditEndingHandler(object sender, DataGridCellEditEndingEventArgs e)
{
MessageBox.Show("Test");
}
I don't think you can use it in pure XAML, but take a look at the Delegate Command.
Execute Command, Navigate Frame, and Delegating Command behaviour is a pretty good pattern. It is also can be used in the Expression Blend.
On the "best practices" side, you should think twice before converting an event to a command. Normally, command is something user does intentionaly, an event most often is just an interaction trail, and should not leave the view boundaries.

Categories