WPF Binding UI events to commands in ViewModel - c#

I’m doing some refactoring of a simple application to follow MVVM and my question is how do I move a SelectionChanged event out of my code behind to the viewModel? I’ve looked at some examples of binding elements to commands but didn’t quite grasp it. Can anyone assist with this. Thanks!
Can anyone provide a solution using the code below? Many thanks!
public partial class MyAppView : Window
{
public MyAppView()
{
InitializeComponent();
this.DataContext = new MyAppViewModel ();
// Insert code required on object creation below this point.
}
private void contactsList_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
//TODO: Add event handler implementation here.
//for each selected contact get the labels and put in collection
ObservableCollection<AggregatedLabelModel> contactListLabels = new ObservableCollection<AggregatedLabelModel>();
foreach (ContactListModel contactList in contactsList.SelectedItems)
{
foreach (AggregatedLabelModel aggLabel in contactList.AggLabels)
{
contactListLabels.Add(aggLabel);
}
}
//aggregate the contactListLabels by name
ListCollectionView selectedLabelsView = new ListCollectionView(contactListLabels);
selectedLabelsView.GroupDescriptions.Add(new PropertyGroupDescription("Name"));
tagsList.ItemsSource = selectedLabelsView.Groups;
}
}

You should use an EventTrigger in combination with InvokeCommandAction from the Windows.Interactivity namespace. Here is an example:
<ListBox ...>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding SelectedItemChangedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>
You can reference System.Windows.Interactivity by going Add reference > Assemblies > Extensions.
And the full i namespace is: xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity".

This question has a similar issue.
WPF MVVM : Commands are easy. How to Connect View and ViewModel with RoutedEvent
The way I deal with this issue is to have a SelectedItem property in the ViewModel, and then bind the SelectedItem of your ListBox or whatever to that property.

To refactor this you need to shift your thinking. You will no longer be handling a "selection changed" event, but rather storing the selected item in your viewmodel. You would then use two-way data binding so that when the user selects an item, your viewmodel is updated, and when you change the selected item, your view it updated.

Consider Microsoft.Xaml.Behaviors.Wpf, its owner is Microsoft which you can see in that page.
System.Windows.Interactivity.WPF owner is mthamil, anybody can tell me is it reliable ?
Example of Microsoft.Xaml.Behaviors.Wpf:
<UserControl ...
xmlns:behaviors="http://schemas.microsoft.com/xaml/behaviors"
...>
<Button x:Name="button">
<behaviors:Interaction.Triggers>
<behaviors:EventTrigger EventName="Click" SourceObject="{Binding ElementName=button}">
<behaviors:InvokeCommandAction Command="{Binding ClickCommand}" />
</behaviors:EventTrigger>
</behaviors:Interaction.Triggers>
</Button>
</UserControl>

Your best bet is using Windows.Interactivity. Use EventTriggers to attach an ICommand to any RoutedEvent.
Here is an article to get you started : Silverlight and WPF Behaviours and Triggers

I know it's a bit late but, Microsoft has made their Xaml.Behaviors open source and it's now much easier to use interactivity with just one namespace.
First add Microsoft.Xaml.Behaviors.Wpf Nuget packge to your project.
https://www.nuget.org/packages/Microsoft.Xaml.Behaviors.Wpf/
add xmlns:behaviours="http://schemas.microsoft.com/xaml/behaviors" namespace to your
xaml.
Then use it like this,
<Button Width="150" Style="{DynamicResource MaterialDesignRaisedDarkButton}">
<behaviours:Interaction.Triggers>
<behaviours:EventTrigger EventName="Click">
<behaviours:InvokeCommandAction Command="{Binding OpenCommand}" PassEventArgsToCommand="True"/>
</behaviours:EventTrigger>
</behaviours:Interaction.Triggers>
Open
</Button>
PassEventArgsToCommand="True" should be set as True and the RelayCommand that you implement can take RoutedEventArgs or objects as template. If you are using object as the parameter type just cast it to the appropriate event type.
The command will look something like this,
OpenCommand = new RelayCommand<object>(OnOpenClicked, (o) => { return true; });
The command method will look something like this,
private void OnOpenClicked(object parameter)
{
Logger.Info(parameter?.GetType().Name);
}
The 'parameter' will be the Routed event object.
And the log incase you are curious,
2020-12-15 11:40:36.3600|INFO|MyApplication.ViewModels.MainWindowViewModel|RoutedEventArgs
As you can see the TypeName logged is RoutedEventArgs
RelayCommand impelmentation can be found here.
Why RelayCommand
PS : You can bind to any event of any control. Like Closing event of Window and you will get the corresponding events.

<ListBox SelectionChanged="{eb:EventBinding Command=SelectedItemChangedCommand, CommandParameter=$e}">
</ListBox>
Command
{eb:EventBinding} (Simple naming pattern to find Command)
{eb:EventBinding Command=CommandName}
CommandParameter
$e (EventAgrs)
$this or $this.Property
string
https://github.com/JonghoL/EventBindingMarkup

I would follow the top answer in this question
Basically your view model will contain a list of all your items and a list of selected items. You can then attach a behaviour to your listbox that manages your list of selected items.
Doing this means you having nothing in the code behind and the xaml is fairly easy to follow, also the behaviour can be re-used elsewhere in your app.
<ListBox ItemsSource="{Binding AllItems}" Demo:SelectedItems.Items="{Binding SelectedItems}" SelectionMode="Multiple" />

Sometimes solution of binding event to command through Interactivity trigger doesn't work, when it's needed to bind the event of custom usercontrol.
In this case you can use custom behavior.
Declare binding behavior like:
public class PageChangedBehavior
{
#region Attached property
public static ICommand PageChangedCommand(DependencyObject obj)
{
return (ICommand)obj.GetValue(PageChangedCommandProperty);
}
public static void SetPageChangedCommand(DependencyObject obj, ICommand value)
{
obj.SetValue(PageChangedCommandProperty, value);
}
public static readonly DependencyProperty PageChangedCommandProperty =
DependencyProperty.RegisterAttached("PageChangedCommand", typeof(ICommand), typeof(PageChangedBehavior),
new PropertyMetadata(null, OnPageChanged));
#endregion
#region Attached property handler
private static void OnPageChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = d as PageControl;
if (control != null)
{
if (e.NewValue != null)
{
control.PageChanged += PageControl_PageChanged;
}
else
{
control.PageChanged -= PageControl_PageChanged;
}
}
}
static void PageControl_PageChanged(object sender, int page)
{
ICommand command = PageChangedCommand(sender as DependencyObject);
if (command != null)
{
command.Execute(page);
}
}
#endregion
}
And then bind it to command in xaml:
<controls:PageControl
Grid.Row="2"
CurrentPage="{Binding Path=UsersSearchModel.Page,Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
PerPage="{Binding Path=UsersSearchModel.PageSize,Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Count="{Binding Path=UsersSearchModel.SearchResults.TotalItemCount}"
behaviors:PageChangedBehavior.PageChangedCommand="{Binding PageChangedCommand}">
</controls:PageControl>

As #Cameron MacFarland mentions, I would simply two-way bind to a property on the viewModel. In the property setter you could do whatever logic you require, such as adding to a list of contacts, depending on your requirements.
However, i wouldn't necessarily call the property 'SelectedItem' as the viewModel shouldn't know about the view layer and how it's interacting with it's properties. I'd call it something like CurrentContact or something.
Obviously this is unless you just want to create commands as an exercise to practice etc.

This is an implementation using a MarkupExtension. Despite the low level nature (which is required in this scenario), the XAML code is very straight forward:
XAML
<SomeControl Click="{local:EventBinding EventToCommand}" CommandParameter="{local:Int32 12345}" />
Marup Extension
public class EventBindingExtension : MarkupExtension
{
private static readonly MethodInfo EventHandlerImplMethod = typeof(EventBindingExtension).GetMethod(nameof(EventHandlerImpl), new[] { typeof(object), typeof(string) });
public string Command { get; set; }
public EventBindingExtension()
{
}
public EventBindingExtension(string command) : this()
{
Command = command;
}
// Do not use!!
public static void EventHandlerImpl(object sender, string commandName)
{
if (sender is FrameworkElement frameworkElement)
{
object dataContext = frameworkElement.DataContext;
if (dataContext?.GetType().GetProperty(commandName)?.GetValue(dataContext) is ICommand command)
{
object commandParameter = (frameworkElement as ICommandSource)?.CommandParameter;
if (command.CanExecute(commandParameter)) command.Execute(commandParameter);
}
}
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (serviceProvider.GetService(typeof(IProvideValueTarget)) is IProvideValueTarget targetProvider &&
targetProvider.TargetObject is FrameworkElement targetObject &&
targetProvider.TargetProperty is MemberInfo memberInfo)
{
Type eventHandlerType;
if (memberInfo is EventInfo eventInfo) eventHandlerType = eventInfo.EventHandlerType;
else if (memberInfo is MethodInfo methodInfo) eventHandlerType = methodInfo.GetParameters()[1].ParameterType;
else return null;
MethodInfo handler = eventHandlerType.GetMethod("Invoke");
DynamicMethod method = new DynamicMethod("", handler.ReturnType, new[] { typeof(object), typeof(object) });
ILGenerator ilGenerator = method.GetILGenerator();
ilGenerator.Emit(OpCodes.Ldarg, 0);
ilGenerator.Emit(OpCodes.Ldstr, Command);
ilGenerator.Emit(OpCodes.Call, EventHandlerImplMethod);
ilGenerator.Emit(OpCodes.Ret);
return method.CreateDelegate(eventHandlerType);
}
else
{
throw new InvalidOperationException("Could not create event binding.");
}
}
}

Related

How to get invalid values in a WPF view

If I have a WPF view with a textbox that has a binding to a decimal (or any other number format) I automatically get a visual hint if I enter a letter or any other invald character and the value is not transferred to the viewmodel (the breakpoint on the setter is never reached). If I enter a number, everything works fine. To disable my save-Button (ICommand), I would like to get the info in my viewmodel that there is an error in the view in a MVVM-like fashion. Hints to where this behavior is documented are very welcome!
So the target situation looks like this:
what I want would be a disable "save and close":
XAML:
<TextBox Text="{Binding Path=SelectedItem.Punkte_Seite_max, UpdateSourceTrigger=PropertyChanged}"/>
ViewModel
public int Punkte_Seite_max
{
get { return _punkte_Seite_max; }
set
{
_punkte_Seite_max = value;
Changed(); //INotifyPropertyChanged call
}
}
What you want to be using is INotifyDataErrorInfo documentation found here. This lets you provide custom validation on the properties that you have bound to your ViewModel.
This is a sample I have shamelessly copied from CodeProject but I have done so to prevent any link rot. I have also tried to adapt it slightly to match your example.
ViewModel
public class ViewModel : INotifyDataErrorInfo
{
// A place to store all error messages for all properties.
private IDictionary<string, List<string>> propertyErrors = new Dictionary<string, List<string>>();
public string Preis
{
get { return _preis; }
set
{
// Only update if the value has actually changed.
if (!string.Equals(_preis, value, StringComparison.Ordinal))
{
_preis = value;
Changed();
this.Validate();
}
}
}
// The event to raise when the error state changes.
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
// A method of getting all errors for the given known property.
public System.Collections.IEnumerable GetErrors(string propertyName)
{
if (propertyName != null)
{
if (propertyErrors.TryGetValue(propertyName, out var errors))
{
return errors;
}
}
return null;
}
// Whether there are any errors on the ViewModel
public bool HasErrors
{
get
{
return propertyErrors.Values.Any(r =>r.Any());
}
}
private void Validate()
{
// 1. HERE YOU CAN CHECK WHETHER Preis IS VALID AND ANY OTHER PROPERTIES
// 2. Update the 'propertyErrors' dictionary with the errors
// 3. Raise the ErrorsChanged event.
}
}
XAML
You will need to change your XAML to something like this:
<TextBox>
<Binding Path="Preis" UpdateSourceTrigger="PropertyChanged" ValidatesOnNotifyDataErrors="True"/>
</TextBox>
Thanks to Bijington I got on the right track and found an answer which satisfies MVVM and also doesn't need code behind. In case someone is interested here's my solution to this issue.
The error shown above is created in the view because there is no converter in WPF from letters to int (how should there be one). To raise this issue the binding in needs to have NotifyOnValidationError=True.
<TextBox Text="{Binding Path=SelectedItem.Punkte_Seite_max, UpdateSourceTrigger=PropertyChanged, NotifyOnValidationError=True}"
This raises a bubbling up Validation.Error event that can be captured anywhere in the tree. I decided to capture it via a routed event trigger like so:
XAML:
<Window
...
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" >
<i:Interaction.Triggers>
<userInterface:RoutedEventTrigger RoutedEvent="{x:Static Validation.ErrorEvent}" >
<userInterface:ViewErrorCounterAction ViewErrorCounter="{Binding Path=ViewValidationErrorCount, Mode=TwoWay}"/>
</userInterface:RoutedEventTrigger>
</i:Interaction.Triggers>
So the twoway-binding is the MVVM-okayish link to my viewmodel.
ViewErrorCounterAction is based on this SO answer:
public class ViewErrorCounterAction : TriggerAction<DependencyObject> {
public ViewErrorCounterAction()
{
ViewErrorCounter = 0; // initalize with 0 as there should not be such errors when the window is loaded
}
public int ViewErrorCounter
{
get
{
return System.Convert.ToInt32(GetValue(ViewErrorCounterProperty));
}
set
{
SetValue(ViewErrorCounterProperty, value);
}
}
public static readonly DependencyProperty ViewErrorCounterProperty = DependencyProperty.Register("ViewErrorCounter", typeof(int), typeof(ViewErrorCounterAction), new PropertyMetadata(null));
protected override void Invoke(object parameter)
{
var e = (ValidationErrorEventArgs)parameter;
if ((e.Action == ValidationErrorEventAction.Added))
ViewErrorCounter = ViewErrorCounter + 1;
else if ((e.Action == ValidationErrorEventAction.Removed))
ViewErrorCounter = ViewErrorCounter - 1;
}
}
Finally routed Event Trigger is based on https://sergecalderara.wordpress.com/2012/08/23/how-to-attached-an-mvvm-eventtocommand-to-an-attached-event/
Hope this helps and I'd appreciate comments on how to better solve this issue if there are more elegant ways :)

Transferring Textbox data to my ViewModel

I have two textboxes with userinput, of which I need to transfer the data to my ViewModel. I tried looking around how to do this by binding it to a button (as the transfer is supposed to take place upon a buttonclick), but most advice to use bindings. However, to use bindings you have to declare properties in the ViewModel (afaik), but as these strings are used to create a new object, holding properties for them would be all but ideal because the two textboxes might expand to over 10 in the future. I've also tried messing around with CommandParameter but I only seem to be able to declare one.
So for clarification:
How do I transfer the contents of two (or more) textboxes to the corresponding ViewModel so I can create a new Object with them?
Edit:
In addition I'd also like to be able to reset the Text= field to be empty once the method handling the data has succesfully completed.
The View
<TextBox Name="UI1"/>
<TextBox Name="UI2"/>
<Button Source="*ImageSource*" Command="{Binding CallCreateObject}"/>
and the ModelView
private void OnCallCreateObject()
{
Object newObject = new Object(UI1, UI2, false)
}
This is a general example of what I'm trying to achieve
If you want to insert data from UI to ViewModel on Button Click than there is no reason to use binding. Binding is mainly used to sync data between UI and underlying models.
Still if you want only that then on button_click event you can do something like this.
private void button_Click(object sender, RoutedEventArgs e)
{
Model model = new Model();
model.Property1 = textBox1.Text;
model.Property2 = textBox2.Text;
textBox1.Text = string.Empty;
textBox2.Text = string.Empty;
}
That will solve your issue. But this approach is not recommended when you have a better thing that is called 'Binding'
If you want to bind your view with a viewmodel then try this:
Your view model:
public class Person : INotifyPropertyChanged
{
private string name;
// Declare the event
public event PropertyChangedEventHandler PropertyChanged;
public string PersonName
{
get { return name; }
set
{
name = value;
// Call OnPropertyChanged whenever the property is updated
OnPropertyChanged("PersonName");
}
}
// Create the OnPropertyChanged method to raise the event
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
Great, you have set up your view model. Now the view:
XML PersonView.xml:
<Grid Name="MyContainer">
<TextBox Text="{Binding PersonName}" />
<Button Name="SaveInfoButton" OnClick="SaveInfoButton_Click">Save info</Button>
</Grid>
Now that we have indicated with which property the textbox will be bind, lets indicate to the view the model that will use to update the property named PersonName. The idea is that when you click over the button, the property PersonName of our model Person gets updated with the value of the TextBox.
The xml class:
public partial class PersonView : UserControl
{
private readonly Person Model;
public PersonView()
{
//Components initialization, etc. etc...
this.Model = new Person();
this.DataContext = this.Model; // Here we are binding the model with our view.
}
private void SaveInfoButton_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(this.Model.PersonName); // this will print the value of your textbox.
}
}
Dont know if you noticed, but we didnt have the need of creating a new object when the user click the button. We just use our model and update the model properpies. If you add more textbox to your view, you'll have to added to our viewmodel as well as the given example.
Here is some post that can help you a little bit more(dont have enough time)
http://blog.scottlogic.com/2012/04/20/everything-you-wanted-to-know-about-databinding-in-wpf-silverlight-and-wp7-part-two.html
http://www.tutorialspoint.com/wpf/wpf_data_binding.htm
You could use bindings like this:
<TextBox Name="UI1" Text="{Binding Path=Ut1Value}"/>
<TextBox Name="UI2" Text="{Binding Path=Ut2Value}"/>
<Button Source="*ImageSource*" Command="{Binding CreateTheThingCommand}"/>
Then in your viewmodel you'll need to have the properties and command for those:
private string _ut1Value;
private string _ut2Value;
public string Ut1Value
{
get
{
return _ut1Value;
}
set
{
if (_ut1Value!= value)
{
_ut1Value= value;
OnPropertyChanged("Ut1Value");
}
}
}
public string Ut2Value
{
get
{
return _ut2Value;
}
set
{
if (_ut2Value!= value)
{
_ut2Value= value;
OnPropertyChanged("Ut2Value");
}
}
}
public ICommand CreateTheThingCommand
{
get { return new RelayCommand(CreateTheThing); }
}
private void CreateTheThing()
{
Object newObject = new Object(_ut1Value, _ut2Value, false);
// Do whatever with your new object
}
It sounds as if you need at least two ViewModel objects:
One to present the data from an existing object. This would be, essentially, what you have already.
A container ViewModel. This encapsulates the behaviours of the IEnumerable collection of objects, including the functionality required to Add a new object.
The container ViewModel would have the properties that you are struggling with, plus the CreateObject command, along with an IEnumerable (ObservableCollection) property to hold the existing ViewModel objects.
In your View, you would have one control to present the data in an existing ViewModel object, and a second control with a ListView (or similar) control to display the existing view controls and the set of TextBox controls, plus the button to create a new object (and add it to the list).
This would also allow you to add 'remove', 'sort', etc. functionality to the container ViewModel, without having to change the existing ViewModel.
A way to accomplish a scalable solution with minimal lines of code, would be to create hold a list of items you bind to in the view model.
This way you can use an ItemsControl in the UI to display a textbox for each item:
public class ViewModel
{
public List<Item> Items {get;} = new List<Item>
{
new Item { Value = "UI1" },
new Item { Value = "UI2" },
};
public class Item
{
public string Value {get;set;}
}
}
View:
<ItemsControl ItemsSource="{Binding Test}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Value}" Margin="5"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button Content="Commit" Margin="5" Click="ButtonBase_OnClick"/>
You can then create the object either from a click event or command:
private void OnCallCreateObject()
{
Object newObject = new Object(Items[0], Items[1], false);
}
The downside is that the order of the items is not explicit, so either you need to assume that the indexed order is correct, or order them manually.

WPF control derived from Label: are custom properties (with DP) not using the setter?

So far all my binding have worked well, as long as they were done with existing control properties of, say, Button, Label, and so on. For a single purpose I need a custom canvas (that inherits from Canvas) with one additional property. Yet the binding doesn't seem to work there. I then created a simpler example using a custom Label that can reproduce my problem. The custom label is defined in code as follows:
namespace CustomCanvasTest
{
class CustomLabel : Label
{
public string Str
{
get { return GetValue(StrProperty) as string; }
set
{
SetValue(StrProperty, value);
}
}
public static readonly DependencyProperty StrProperty =
DependencyProperty.Register("Str", typeof(string), typeof(CustomLabel));
}
}
The XAML of the main window:
<Window x:Class="CustomCanvasTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CustomCanvasTest"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<Grid>
<Button Content="Button" HorizontalAlignment="Left" Margin="313,10,0,0" VerticalAlignment="Top" Width="75" Command="{Binding ButtonClickCmd}"/>
<local:CustomLabel Content="{Binding SomeString}" Str="{Binding SomeString}" HorizontalAlignment="Left" Height="25" Margin="291,288,0,0" VerticalAlignment="Top" Width="176"/>
</Grid>
</Window>
And the ViewModel has the following code (the MainWindow.xaml.cs is untouched):
using System.ComponentModel;
using System.Windows.Input;
using WpfHelperClasses;
namespace CustomCanvasTest
{
class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
SomeString = "init";
}
private string someString;
public string SomeString
{
get { return someString; }
set
{
someString = value;
RaisePropertyChanged("SomeString");
}
}
#region INPC
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string name)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
#endregion
public ICommand ButtonClickCmd
{
get
{
return new DelegateCommand(param =>
{
SomeString = "blub";
});
}
}
}
}
When I click on the button, the label content changes visibly, but the breakpoint I set inside the setter for Str is never reached. I'm surely making some newbie mistake here, but even looking around the other answers here regarding similar problems I can't quite figure it out alone. Thanks for any help in advance!
From the XAML Loading and Dependency Properties article on MSDN:
For implementation reasons, it is computationally less expensive to
identify a property as a dependency property and access the property
system SetValue method to set it, rather than using the property
wrapper and its setter. This is because a XAML processor must infer
the entire object model of the backing code based only on knowing the
type and member relationships that are indicated by the structure of
the markup and various strings.
...
Because the current WPF implementation of the XAML processor behavior
for property setting bypasses the wrappers entirely, you should not
put any additional logic into the set definitions of the wrapper for
your custom dependency property. If you put such logic in the set
definition, then the logic will not be executed when the property is
set in XAML rather than in code.
So the answer to your question is: No, the setter is not necessarily called.
In order to get notified about value changes of a dependency property, you will have to register a PropertyChangedCallback via dependency property metadata:
public static readonly DependencyProperty StrProperty =
DependencyProperty.Register(
"Str", typeof(string), typeof(CustomLabel),
new PropertyMetadata(StrPropertyChanged));
private static void StrPropertyChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var label = obj as CustomLabel;
var str = e.NewValue as string;
...
}
Use UIPropertyMetadata for getting values, which are assigned to your Dependency property:
Provides property metadata for non-framework properties that do have rendering/user interface impact at the core level.
Example:
public class CustomLabel : Label
{
public static readonly DependencyProperty StrProperty =
DependencyProperty.Register("Str",
typeof(string),
typeof(CustomLabel),
new UIPropertyMetadata(String.Empty, IsStrTurn));
public string Str
{
get
{
return GetValue(StrProperty) as string;
}
set
{
SetValue(StrProperty, value);
}
}
private static void IsStrTurn(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
string strOld = e.OldValue as string;
string strNew = e.NewValue as string;
System.Diagnostics.Debug.WriteLine("The old value is " + strOld); // The old value is "init"
System.Diagnostics.Debug.WriteLine("The new value is " + strNew); // The new value is "blub"
}
}

Listview MouseDoubleClickEvent create in behind code

When I have:
<UserControl.Resources>
<Style x:Key="itemstyle" TargetType="{x:Type ListViewItem}">
<EventSetter Event="MouseDoubleClick" Handler="HandleDoubleClick" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
</Style>
</UserControl.Resources>
<Grid>
<ListView ItemContainerStyle="itemstyle" Name="listView1" >
<ListView.View>
<GridView>
... etc
Code behind
protected void HandleDoubleClick(object sender, MouseButtonEventArgs e)
{
T item = (T)(((ListViewItem)sender).Content);
// item is the row item that was double clicked on
}
Everything works great.
Now I need to do the same thing on code behind. This is what I have worked out:
public Constructor(){
listview.AddHandler(
ListViewItem.MouseDoubleClickEvent,
new MouseButtonEventHandler(HandleDoubleClick)
);
}
protected void HandleDoubleClick(object sender, MouseButtonEventArgs e)
{
T item = (T)(((ListViewItem)sender).Content);
// error cause the sender is the listview
}
that event fires when I double click any part of the listview not just the listviewItem. Also I expect sender to be ListViewItem and it is not. the sender is actually the listview. Things that I have tried:
1) Because the sender is the listview I tried creating the event as:
listview.AddHandler(
// I use PreviewMouseDoubleClickEvent instead of MouseDoubleClickEvent because of the way the events bubles
ListViewItem.PreviewMouseDoubleClickEvent,
new MouseButtonEventHandler(HandleDoubleClick)
);
I get the same error the sender is the listview
2) Instead of doing:
T item = (T)((ListViewItem)sender).Content;
I do:
T item = (T)(listview.selectedItem);
the problem with this is that if the user double clicks anything on the listview that is not the row it will return the current selected item
why is my code not working? what am I doing wrong?
Attached Behavior Pattern
I think you should use attached behavior because
MVVM ♥ Attached Behavior
It is reusable: once you create the attached behavior and the commands for a double click, you can attach it to any other control type (so you could use the same code to double click an image etc)
Abstracts the dirty implementation so the xaml and the view model are both much more legible
The way they work is awesome, and I love using clever code.
Heres what you do:
Create the ListViewItemDoubleClickCommandBehavior
As you can see from the above diagram, this CommandBehavior class links the command to the control.
In the case above its for a buttonbase, but we are gonna make a ListViewItem one
You are going to need prism for this one, as the CommandBehaviorBase, which this inherits from, is part of that library. You can get it all here (but for this example you only need to Prism.Commands dll)
Naming convention says you should name this CommandBehavior in the format
[X][Y]CommandBehavior
X - The name of the control to which we are binding the command (in this case ListViewItem)
Y - The Action the user performs that will cause the command to happen (in this case DoubleClick)
Heres the code:
public class ListViewItemDoubleClickCommandBehaviour : CommandBehaviorBase<ListViewItem>
{
public ListViewItemDoubleClickCommandBehaviour(ListViewItem controlToWhomWeBind)
: base(controlToWhomWeBind)
{
controlToWhomWeBind.MouseDoubleClick += OnDoubleClick;
}
private void OnDoubleClick(object sender, System.Windows.RoutedEventArgs e)
{
ExecuteCommand();
}
}
Create a DoubleClick Static class
This class will house all the double click command stuff.
Its called doubleclick because it represents the actual act of double clicking. Each other kind of action (say you wanted a command for button down in a textbox) will have its own class with its command, commandparameter and behavior can we can then access.
The commands are dependency properties of type ICommand (I'm assuming you have implemented this interface, because you kinda need it for MVVM)
One dependency property for the command itself and one for the parameters the command needs (in this case you will probably use the selected item as a parameter)
This class has an instance of the ListViewItemDoubleClickCommandBehavior as a dependency property. This is how the link is created between the command you bind to your control and the double click event in the ListViewItemDoubleClickCommandBehaviour. We use some of the CommandBehaviorBase's magic methods to create the behavior and pass it the command to execute.
The OnSetCommand and OnSetCommandParameter callbacks are used to wire the Behavior to the command. Every time the command changes, we set that as the new command for the behavior to run. These callbacks are registered to the DependencyProperties in the PropertyMetadata part of the constructor. These get fired whenever the dependency property is changed.
Here is that class's code:
public static class DoubleClick
{
public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached(
"Command",
typeof(ICommand),
typeof(DoubleClick),
new PropertyMetadata(OnSetCommandCallback));
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.RegisterAttached(
"CommandParameter",
typeof(object),
typeof(DoubleClick),
new PropertyMetadata(OnSetCommandParameterCallback));
private static readonly DependencyProperty DoubleClickCommandBehaviorProperty =
DependencyProperty.RegisterAttached(
"DoubleClickCommandBehavior",
typeof(ListViewItemDoubleClickCommandBehaviour),
typeof(DoubleClick),
null);
public static void SetCommand(ListViewItem controlToWhomWeBind, ICommand value)
{
controlToWhomWeBind.SetValue(CommandProperty, value);
}
public static ICommand GetCommand(ListViewItem controlToWhomWeBind)
{
return (ICommand)controlToWhomWeBind.GetValue(CommandProperty);
}
public static void SetCommandParameter(ListViewItem controlToWhomWeBind, ICommand value)
{
controlToWhomWeBind.SetValue(CommandParameterProperty, value);
}
public static ICommand GetCommandParameter(ListViewItem controlToWhomWeBind)
{
return (ICommand)controlToWhomWeBind.GetValue(CommandParameterProperty);
}
private static void OnSetCommandCallback(DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs e)
{
ListViewItem controlToWhomWeBind= dependencyObject as ListViewItem;
if (controlToWhomWeBind!= null)
{
ListViewItemDoubleClickCommandBehaviour behavior = GetOrCreateBehavior(controlToWhomWeBind);
behavior.Command = e.NewValue as ICommand;
}
}
private static void OnSetCommandParameterCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
ListViewItem controlToWhomWeBind= dependencyObject as ListViewItem;
if (controlToWhomWeBind!= null)
{
ListViewItemDoubleClickCommandBehaviour behavior = GetOrCreateBehavior(controlToWhomWeBind);
behavior.CommandParameter = e.NewValue;
}
}
private static ListViewItemDoubleClickCommandBehaviour GetOrCreateBehavior(
ListViewItem controlToWhomWeBind)
{
ListViewItemDoubleClickCommandBehaviour behavior =
controlToWhomWeBind.GetValue(DoubleClickCommandBehaviorProperty) as
ListViewItemDoubleClickCommandBehaviour;
if (behavior == null)
{
behavior = new ListViewItemDoubleClickCommandBehaviour (controlToWhomWeBind);
controlToWhomWeBind.SetValue(DoubleClickCommandBehaviorProperty, behavior);
}
return behavior;
}
}
Note:
I know this looks like alot of complex code, but its a simple pattern once you use it often enough, and it abstracts all the complexity out of your view model, so that the binding of the command to the control looks remarkably simple.
Create the command in your viewmodel and bind to it in the xaml
Now that the grunt work is over, we do the easy part.
Creating the Command
I assume that you are familiar with commands, you create the command as you always do:
public ICommand DisplaySelectedItemCmd { get; protected set; }
//This method goes in your constructor
private void InitializeCommands()
{
//Initializes the command
this.DisplaySelectedItemCmd = new RelayCommand(
(param) =>
{
this.DisplaySelectedItem((object)param);
},
(param) => { return this.CanDisplaySelectedItem; }
);
}
//The parameter should be your listview's selected item. I have no idea what type it is so I made it an object
public void DisplaySelectedPolicy(object selectedListViewItem)
{
//Code to perform when item is double clicked
}
private bool CanDisplaySelectedPolicy
{
get
{
return true; //Change this bool if you have any reason to disable the double clicking, as this bool basically is linked to the double click command firing.
}
}
Creating the binding in xaml
And now for the beautiful part.
First add the xml namespace:
xmlns:commands="clr-namespace:CommandHandling"
and then bind on your ListViewItem
<Style TargetType="{x:Type ListViewItem}" x:Key="{x:Type ListViewItem}" >
<Setter Property="commands:DoubleClick.Command" Value="{Binding Path=bleh}"/>
</Style>
and done.
If this doesn't work let me know. (And anyone reading this in the future, you can ask me for help if you like)
u_u
In xaml you attach the event handler to the ListViewItem while in code behind you attach it to the ListView itself. That is why you get different behaviour. If you want to do the same in code behind you will have to loop al items in your items collection and bind the DoubleClick event of each one to your handler.
If there is no real reason to do this in code behind I would go for the xaml approach which fits the MVVM pattern better where you try to keep as few code as possible in the code behind.
Don't see why you are doing this in code behind unless you are wanting named content in your UserControl so you are changing it to a custom control. Try repeating your xaml code in code behind, ie create a style for ListViewItem and setting it as ItemContainerStyle for your listview.
Figure it out!! I am sure it should be the same with the double click...
In xaml I have:
<ListView IsSynchronizedWithCurrentItem="True" Name="listView" Margin="32,158,66,0" VerticalAlignment="Top">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<EventSetter Event="PreviewMouseUp" Handler="itemClicked"></EventSetter>
</Style>
</ListView.ItemContainerStyle>
<ListView.View>
... etc
and I can create the same thing with c# on code behind as:
EventSetter ev = new EventSetter();
ev.Event = ListViewItem.PreviewMouseUpEvent;
ev.Handler = new MouseButtonEventHandler(itemClicked);
Style myStyle = new Style();
myStyle.TargetType = typeof(ListViewItem);
myStyle.Setters.Add(ev);
listView.ItemContainerStyle = myStyle;
....
void itemClicked(object sender, MouseButtonEventArgs e)
{
// item was licked in listview implement behavior in here
}

DependencyProperty binding mode twoway but propertychangedeventhandler is null

I'm trying to follow the MVVM design paradigm with C# and XAML. I'm running into trouble with a nested user control. I'm trying to bind an element on the nested user control to one of the values in the ViewModel (which is bound to the View via the DataContext property). The same ViewModel is used for both the outer and nested user controls.
It partially works as is, but changes only go one-way from the ViewModel to the nested user control. I need the changes made in the nested user control to propagate back to the ViewModel.
Starting with the XAML for the main View, I have:
<UserControl>
<!-- ... -->
<UserControl.DataContext>
<local:MyViewModel x:Name="myViewModel" />
</UserControl.DataContext>
<!-- ... -->
<local:NestedUserControl
x:Name="nestedUserControl"
CustomNestedValue="{Binding Path=CustomValue, ElementName=myViewModel, Mode=TwoWay}" />
</UserControl>
In the C# code for the ViewModel:
// Constructor
public MyViewModel()
{
CustomValue = true;
}
private bool _customValue;
public bool CustomValue
{
get { return _customValue; }
set
{
if (_customValue != value)
{
_customValue = value;
RaisePropertyChanged ("CustomValue");
}
}
}
And in the code behind of the NestedUserControl, I have:
public static readonly DependencyProperty CustomNestedValueProperty =
DependencyProperty.Register (
"CustomNestedValue",
typeof (bool),
typeof (NestedUserControl),
new FrameworkPropertyMetatdata
{
BindsTwoWayByDefault = true,
PropertyChangedCallback =
new PropertyChangedCallback (CustomNestedValueChangedCallback)
});
public bool CustomNestedValue
{
get { return (bool) GetValue (CustomNestedValueProperty); }
set { SetValue (CustomNestedValueProperty, value); }
}
protected static void CustomNestedValueChangedCallback (
DependencyObject Source,
DependencyPropertyChangedEventArgs e)
{
bool value = (bool) e.NewValue;
NestedUserControl control = source as NestedUserControl;
control.OnCustomValueChange (value);
}
public void OnCustomValueChange (bool value)
{
RaisePropertyChanged ("CustomNestedValue");
// Do other stuff ...
}
// This function is where the nested user control gets direct
// interactions from the user which cause the dependency
// property to change. When this event occurs, the change needs
// to be communicated back up to the view model.
private void _onPreviewMouseDown (object sender, MouseButtonEventArgs e)
{
CustomNestedValue = !CustomNestedValue;
}
[Note: Not only do I set the binding mode to TwoWay when setting the binding in XAML, but I attempted to make this the default behavior of the DependencyProperty in the code above. No luck.]
Both the code behind for the nested user control and the ViewModel code contain the below PropertyChangedEventHandler event/response, which is necessary for the INotifyPropertyChanged interface. From what I understand, this is how bindings between XAML elements and the ViewModel are kept in sync.
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
try
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
catch (Exception e)
{
// ...
}
}
When I run the code, whenever the RaisePropertyChanged function is called for the NestedUserControl, the PropertyChanged event is always null. This is only a problem for the nested usercontrol, and not the outer one. Shouldn't this event be automatically set via the binding mechanism?
I've been struggling with this for several days now to no avail. Any help would be much appreciated. Thanks!
Binding to a DependencyObject operates without using the INotifyPropertyChanged interface. In fact, if you set a breakpoint in the getter or setter of the CustomNestedValue property of the NestedUserControl, you'll find it will never hit when binding in XAML. In essence, the INotifyPropertyChanged is a way of achieving binding without descending from DependencyObject.
When the MyViewModel.CustomValue is bound to the NestedUserControl, the binding code calls (in pseudo code):
NestedUserControl.SetBinding(binding, NestedUserControl.CustomNestedValueProperty)
The INotifyPropertyChanged.PropertyChanged event is never registered and will remain null. However, this doesn't necessarily answer why the value isn't going back to the ViewModel.
Regardless, you could remove a few moving pieces and go with
public static readonly DependencyProperty CustomNestedValueProperty =
DependencyProperty.Register("CustomNestedValue",
typeof (bool),
typeof (NestedUserControl),
null);
public bool CustomNestedValue
{
get { return (bool) GetValue (CustomNestedValueProperty); }
set { SetValue (CustomNestedValueProperty, value); }
}
That's how most of my DependencyProperties are written and they do support TwoWay binding.

Categories