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
}
Related
WPF - Bindable chat view with selectable text
I want to create a simple text chat app using WPF. And of course user should be able to select and, for example, copy text.
It's very easy to use, for example, ListView with ItemsSource bound to messages. And appearance can be tuned, but the main problem is text selection. It's possible to select text only in one single control (one message).
At the moment i use WebBrowser for showing messages. So i have tons of HTML+JS+CSS. I think i dont even have to say how terrible it is.
Can you please point me to right direction?
You could take a look at the FlowDocument for that. This class can be used for customizing the appearance of the blocks (paragraphs) similar to an ItemsControl, it can contain UI controls too (in case you need it). And of course, the text selection will work across the whole document.
Unfortunately, the FlowDocument doesn't support bindings, so you will have to write some code for that.
Let me give you an example. You could use a Behavior from the System.Windows.Interactivity namespace to create a reusable functionality extension for the FlowDocument class.
This is what you could start with:
<FlowDocumentScrollViewer>
<FlowDocument ColumnWidth="400">
<i:Interaction.Behaviors>
<myApp:ChatFlowDocumentBehavior Messages="{Binding Messages}">
<myApp:ChatFlowDocumentBehavior.ItemTemplate>
<DataTemplate>
<myApp:Fragment>
<Paragraph Background="Aqua" BorderBrush="BlueViolet" BorderThickness="1"/>
</myApp:Fragment>
</DataTemplate>
</myApp:ChatFlowDocumentBehavior.ItemTemplate>
</myApp:ChatFlowDocumentBehavior>
</i:Interaction.Behaviors>
</FlowDocument>
</FlowDocumentScrollViewer>
(the i namespace is xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity")
So there is our ChatFlowDocumentBehavior which has a bindable Messages property for displaying the chat messages. Also, there is an ItemTemplate property where you define how a single chat message should look like.
Note the Fragment class. This is just a simple wrapper (code below). The DataTemplate class won't accept a Paragraph as its content, but we need our items to be Paragraphs.
You can configure that Paragraph as you wish (like colors, fonts, maybe additional child items or controls etc.)
So, the Fragment class is a simple wrapper:
[ContentProperty("Content")]
sealed class Fragment : FrameworkElement
{
public static readonly DependencyProperty ContentProperty = DependencyProperty.Register(
nameof(Content),
typeof(FrameworkContentElement),
typeof(Fragment));
public FrameworkContentElement Content
{
get => (FrameworkContentElement)GetValue(ContentProperty);
set => SetValue(ContentProperty, value);
}
}
The behavior class has a little bit more code but isn't complicated.
sealed class ChatFlowDocumentBehavior : Behavior<FlowDocument>
{
// This is our dependency property for the messages
public static readonly DependencyProperty MessagesProperty =
DependencyProperty.Register(
nameof(Messages),
typeof(ObservableCollection<string>),
typeof(ChatFlowDocumentBehavior),
new PropertyMetadata(defaultValue: null, MessagesChanged));
public ObservableCollection<string> Messages
{
get => (ObservableCollection<string>)GetValue(MessagesProperty);
set => SetValue(MessagesProperty, value);
}
// This defines how our items will look like
public DataTemplate ItemTemplate { get; set; }
// This method will be called by the framework when the behavior attaches to flow document
protected override void OnAttached()
{
RefreshMessages();
}
private static void MessagesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!(d is ChatFlowDocumentBehavior b))
{
return;
}
if (e.OldValue is ObservableCollection<string> oldValue)
{
oldValue.CollectionChanged -= b.MessagesCollectionChanged;
}
if (e.NewValue is ObservableCollection<string> newValue)
{
newValue.CollectionChanged += b.MessagesCollectionChanged;
}
// When the binding engine updates the dependency property value,
// update the flow doocument
b.RefreshMessages();
}
private void MessagesCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
AddNewItems(e.NewItems.OfType<string>());
break;
case NotifyCollectionChangedAction.Reset:
AssociatedObject.Blocks.Clear();
break;
}
}
private void RefreshMessages()
{
if (AssociatedObject == null)
{
return;
}
AssociatedObject.Blocks.Clear();
if (Messages == null)
{
return;
}
AddNewItems(Messages);
}
private void AddNewItems(IEnumerable<string> items)
{
foreach (var message in items)
{
// If the template was provided, create an instance from the template;
// otherwise, create a default non-styled paragraph instance
var newItem = (Paragraph)(ItemTemplate?.LoadContent() as Fragment)?.Content ?? new Paragraph();
// This inserts the message text directly into the paragraph as an inline item.
// You might want to change this logic.
newItem.Inlines.Add(message);
AssociatedObject.Blocks.Add(newItem);
}
}
}
Having this as a starting point, you can extend the behavior to suit your needs. E.g. add event handling logic for removing or reordering of the messages, implement comprehensive message templates etc.
It's almost always possible to implement the functionality with as less code as possible, using XAML features: styles, templates, resources etc. However, for the missing features, you just need to fall back to the code. But in that case, always try to avoid code-behind in the views. Create Behaviors or attached properties for that.
A textbox should give you what you are looking for I think. You will need to do the styling so it looks like you want but here is code:
XAML:
<TextBox Text="{Binding AllMessages}"/>
ViewModel:
public IEnumerable<string> Messages { get; set; }
public string AllMessages => GetAllMessages();
private string GetAllMessages()
{
var builder = new StringBuilder();
foreach (var message in Messages)
{
//Add in whatever for context
builder.AppendLine(message);
}
return builder.ToString();
}
You will probably want to use a RichTextBox for better formatting.
I have a button, When it's clicked it populates my Datagrid. The code is written within the .xaml.cs file, which I believe breaks the MVVM rule but it's just a temporary situation. I know it's not ideal for MVVM.
Calculate.xaml.cs
public void PopulateGrid(object sender, RoutedEventArgs e)
{
BindableCollection<Payments> PaymentCollection = new BindableCollection<Payments>
....
Datagrid.ItemsSource = PaymentCollection
....
}
My question is if there's a way to read the Datagrids ItemsSource From the ViewModel.
What I've Tried
LoansViewModel
public BindableCollection<Payments> paymentCollection {get; set;}
Calculate.xaml
<telerik:RadGridView ItemsSource="{Binding paymentCollection, Mode=TwoWay}" ... />
The collection paymentCollection Doesn't Update after calculate is clicked.
Just do this the correct MVVM way. Get rid of your PopulateGrid method in the .xaml.cs file and eliminate setting the Click property in your xaml. Instead bind the command property of the button to an ICommand property in your ViewModel the same way you are binding the ItemsSource of the RadGridView. You will need an implementation of ICommand to use and MVVM Lights RelayCommand is one option for that.
Here is the code for the ICommand:
private ICommand _populateGridCommand;
public ICommand PopulateGridCommand
{
get
{
if (_populateGridCommand == null)
{
_populateGridCommand = new RelayCommand(() => PopulateGrid());
}
return _populateGridCommand;
}
}
public void PopulateGrid()
{
PaymentCollection.Clear();
//load data and then add to the collection
}
UPDATE
To do this in code behind, you'll need to access the ViewModel and work on the collection from it. I don't like this but it should work.
public void PopulateGrid(object sender, RoutedEventArgs e)
{
var loansVM = DataGrid.DataContext as LoansViewModel;
loansVM.paymentsCollection.Clear();
var newData = //load data
foreach (var data in newData)
loansVM.paymentsCollection.Add(data);
}
Your xaml code looks like it should work provided the DataContext of your grid is set to your ViewModel instance where your paymentCollection property is declared.
Once your binding is set, it calls the get on the paymentCollection property. If your collection property object is not reassigned any further, and you add and remove elements from it, and it notifies on those changes via INotifyCollectionChanged, it will work. This is how ObservableCollection works and used most commonly for such scenarios.
However, if when you calculate, you re-assign your paymentCollection property with a new instance, your grid will not update, because you now have an entirely different collection. In that case you will need to notify the view that the paymentCollection property itself has changed. In which case you should implement it as a notification property:
private BindableCollection<Payments>_paymentCollection;
public BindableCollection<Payments> paymentCollection {
get { return _paymentCollection; }
set {
_paymentCollection = value;
OnPropertyChanged("paymentCollection");
}
}
protected void OnPropertyChanged(string name) {
PropertyChangedEventHandler handler = PropertyChanged;
if(handler != null) {
handler(this, new PropertyChangedEventArgs(name));
}
}
I have MyContainer User Control with Grid inside. Each cell of this Grid contains a some control derived from MyControlBase class. These controls are added dynamically.
I need to implement a FocusedControl bound property in MyContainer to get currently focused or set focus to any of MyControlBase children. I know about FocusManager.FocusedElement but haven't ideas how to implement it properly.
Dunno if this helps your situation, or if it's even the right way to go about mine, but in a pinch I've found that listening for the Loaded event on the object I want to focus and then using Dispatcher.BeginInvoke to send a Focus request to my control on the Background or ApplicationIdle priority has worked when other methods haven't. For example:
private void MyControlLoaded(object sender, RoutedEventArgs e)
{
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, new Action( () => MyControl.Focus() ));
}
OK, I found how to do it myself.
First define our new dependence property FocusedAdapterProperty as usual:
public static readonly DependencyProperty FocusedAdapterProperty;
static SpreadGridControl()
{
FocusedAdapterProperty = DependencyProperty.Register("FocusedAdapter",
typeof(object), typeof(SpreadGridControl),
new FrameworkPropertyMetadata(null, null));
}
public object FocusedAdapter
{
get { return GetValue(FocusedAdapterProperty); }
set { SetValue(FocusedAdapterProperty, value); }
}
Next add GotFocus handler to parent container e.g. <Grid GotFocus="Grid_OnGotFocus">
Check the e.OriginalSource and search most common ancestor of required type and set property to new value:
private void Grid_OnGotFocus(object sender, RoutedEventArgs e)
{
var control = UIHelpers.TryFindParent<ControlBase>
((DependencyObject)e.OriginalSource);
if (control != null)
FocusedAdapter = control.Adapter;
}
The implementation of TryFindParent can be found here
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.
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.");
}
}
}