Using a single generic Command for handling multiple Buttons - c#

I am building a simple Calculator application. I'm also learning how to apply the MVVM pattern in my app.
I would like each one of the "Digit" buttons of my calculator to bind to the same Command, where they would only differ in the digit (Text) of the button that raised the command.
For example, When button "1" is clicked, i would like to receive the notification about it, extract "1" from the Sender's property, and continue the rest of the work needed.
This allows me to define a single method instead of 10 different handlers.
This is not possible as far as what i've seen in all MVVM tutorials till now, since the Command binding does not provide all this information to me when binding to the actual method that will handle the click.
Is there any way to easily do what i require?

Assuming I understand what you're trying to do, you can use the CommandParameter property to let different buttons supply values to the same command. For example:
...
<Button Content="1" Command="{Binding ButtonClickCommand}" CommandParameter="1"/>
<!-- Or, bind directly to the button's content using RelativeSource, like so: -->
<Button Content="2" Command="{Binding ButtonClickCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Self}, Path=Content}"/>
...
And in your command's delegate method:
private void ButtonClickCommandHandler(object parameter)
{
switch(int.Parse(parameter.ToString()))
{
case 1:
...
case 2:
...
}
}

Just provide the digit as a Button.CommandParameter. (Which is passed as a method parameter of ICommand.Execute (and CanExecute))

Related

Using custom class as ItemsControl Tag in WPF XAML?

I have a web app built with ASP.NET and React. I’m trying to port some components to a Windows WPF app, and this is my first time using WPF/XAML. My experience with XAML hasn’t been too bad... it’s like a more-verbose React, but one thing I can’t work out how to do properly is save bind parameters from nested lists to ItemsControl.
In React, I’d use something like onClick=“(e)=>this.myFunction(e, parentIndex)”, so that I could send 2 parameters at once, maybe representing an index and a value, or a childIndex and parentIndex, etc.
It is unclear to me how to properly do this with WPF’s flavour of XAML, and XAML in general. From what I’ve read, I have to use a ICommand (which I feel like is overkill, as the params I’m using are usually not user-input, and instead are references to other objects on the back-end) or set the tag of the initiating object (a button) to a custom class with the amount of attributes I need.
The second approach seems more sensible to me, but I can’t work out how to dynamically do this with an ItemsControl on the XAML frontend — all the tutorials I’ve seen do this in the codebehind, which I don’t think is possible as I’m using an ItemsControl.
How can I do this?
You can pass item to command via CommandParameter.
For assign command to mouseEvents you can use InputBindings.
Lets assume that you have ViewModel with command
public ICommand SomeClickCommand {get; private set}
public void SomeCLickCOmmandHandler (object parameter)
{
var yourItem = object as SomeItemType;
}
you can assign item to command in DataTemplate
<DataTemplate TargetType={x:Type someItemType}>
<Border>
<Border.InputBindings>
<MouseBinding Command="{Binding Path=DataContext.SomeClickCommand , RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}, Mode=FindAncestor}}"
CommandParameter="{Binding}"
Gesture="LeftDoubleClick" />
</Border.InputBindings>
<TextBlock Text={Binding SomeProperty} />
</Border>
</DataTemplate>

WPF MVVM event to command - invoke command based on event args

I have a WPF app which uses DevExpress controls and MVVM with PRISM.
I'm using DockLayoutManager's 'DockOperationCompleted' event to invoke a command on my view model like this:
<dxd:DockLayoutManager x:Name="dockContainer">
<i:Interaction.Triggers>
<i:EventTrigger EventName="DockOperationCompleted">
<i:InvokeCommandAction Command="{Binding DataContext.SaveLayoutCommand, ElementName=dockContainer}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<dxd:LayoutGroup/>
</dxd:DockLayoutManager>
The purpose of the 'SaveLayoutCommand' command is to save the layout so it can be restored later in time.
The 'DockOperationCompleted' event is raised after a DockItem gets docked or closed (there are other cases but they are irrelevant).
The problem is that when I close the main window, the dock items in my DockLayoutManager are getting closed one by one and thus 'SaveLayoutCommand' gets invoked for every closed dock item and I don't want this to happen.
The 'DockOperationCompletedEventArgs' with which the event gets raised has a 'DockOperation' property which I can check agains, but I'm not sure where exactly should this code fit in.
What I am trying to achieve is that the command should be invoked only in one case - when the item is docked
My question is : is there a way to 'filter' when the command gets invoked based on the event's event args?
Thanks :)
In addition to aKzenTs answer I want to point out that with DevExpress its rather easy to pass the event args to a command.
You should use EventToCommand from their MVVM library (PassEventArgsToCommand-Property). If you want to keep your viewmodel clean of DevExpress you can additionally use a Converter to transform the event args to an arbitrary object.
<dxmvvm:Interaction.Triggers>
<dxmvvm:EventToCommand Command="{Binding YOURCOMMAND}"
EventName="THEEVENT"
EventArgsConverter="{StaticResource YOUREVENTARGSCONVERTER}"
PassEventArgsToCommand="true" />
</dxmvvm:Interaction.Triggers>
There is no builtin way to filter the events that are raised before invoking an action. You can however implement your own custom trigger action that does the filtering.
Unfortunately it's also not easy to access the event args and passing them to the command. See this question as a reference:
MVVM Passing EventArgs As Command Parameter

XAML - InputBindings prevent MouseLeftButtonDown-Events being fired

I have a class derived from ItemsControl in which I implement my own selection-algorithm which uses the MouseLeftButtonDown to change the selection.
Now I needed a specific control to handle Mouseclicks in the ViewModel, so I wrote the following:
<controls:DraggableItemsContainer bla="blub">
<controls:DraggableItemsContainer.InputBindings>
<MouseBinding Gesture="LeftClick" Command="{Binding DeselectSubGroupsCommand}" />
</controls:DraggableItemsContainer.InputBindings>
</controls:DraggableItemsContainer>
What happens now is, that I don't get the MouseLeftButtonDown-event anymore - which is comprehensable because the command "e.handles" the click.
But in this case, that's not what I want. Is there a way to fire the event anyway?
PS: Yes I need to do the selection in the MouseLeftButtonDown-event and not in any Preview-event
Two options:
You can have your custom ItemsControl hook PreviewMouseDown instead of MouseDown.
You can continue to hook MouseDown, but do it by calling AddHandler, and pass true for the handledEventsToo parameter.
If it's important that you get notified after the MouseBinding has done its work, then you need to use AddHandler. If you're okay with getting the message first, PreviewMouseDown is simpler.
I think your issue is that you try to catch the same event, on the same control, using two different approaches.
Try this:
<Grid>
<Grid.InputBindings>
<MouseBinding Gesture="LeftClick" Command="{Binding DeselectSubGroupsCommand}" />
</Grid.InputBindings>
<controls:DraggableItemsContainer bla="blub">
.....
</controls:DraggableItemsContainer>
</Grid>
Make sure that in your control you have e.Handled = false.
This should allow for your internal logic to run, and then execute the command. If you need it in the opposite order..... I don't know.

Custom control toolbar need to invoke methods on my VM. How to do that?

Here is my question. I have UserControl that wraps group of buttons and it looks like this: (I show 2 buttons to illustrate what it is)
<Button Content="Cancel"
IsEnabled="{Binding State, Converter={StaticResource CancelEnabledConverter}}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<ei:CallMethodAction MethodName="Cancel" TargetObject="{Binding}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
<Button Content="Delete"
IsEnabled="{Binding State, Converter={StaticResource DeleteEnabledConverter}}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<ei:CallMethodAction MethodName="Delete" TargetObject="{Binding}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
Now, when I place this UserControl on my view - I go by convention and create Cancel and Delete methods on my VM. So, view's XAML looks clean.
I want to create custom control that will have same functionality. Inside control I will have to handle onClick events for buttons and would like to call methods on VM just like it works now. What my code going to look like? I guess I need to access DataContext programmatically and call method by name somehow. I envision using control like so:
<myToolBar Mode="SaveExitDelete" />
So, this will be nice and short. But myToolBar will show 3 buttons and those buttons will call 3 methods(named by convention) on DataContext.
Any pointers?
EDIT
Main question is to how programmaticaly BIND command or method to button. I understand how commanding works, I'm using PRISM and it's got built-in DelegateCommand that I can use. I don't know how to create binding programmaticaly when I know Method name or command name.
Here is how I can see it working:
var button = new DitatToolbarButton();
button.Caption = "Cancel &\nExit";
button.Icon = new BitmapImage(new Uri("img_btn_cancel.png", UriKind.Relative));
button.Command = Binding("CancelCommand");
Obviously 3rd line is wrong but this is what I want. I want to be able to hardcode string that will contain name of command that I will expect VM to have.
Typically, this sort of thing would be done with Commands. In the case of a Button control, which already has the "Command" DependencyProperty, it's as simple as this:
<Button Command="{Binding DoItCommand}">Do it</Button>
and in your view-model class:
private ICommand DoItCommand
{
get
{
return new DelegateCommand(param => DoIt(param), param => CanDoIt(param));
}
}
where DoIt() and CanDoIt() are methods in your view-model and DelegateCommand is defined something like this:
public class DelegateCommand : ICommand
{
public DelegateCommand(Action<object> execute, Predicate<object> canExecute)
{
// ...
There's a decent example of this here. On a custom control, you can declare the Command DependencyProperty yourself. And on a framework control that does not have a Command DependencyProperty, you can use an attached property.

How to handle WPF event in MVVM for nested controls in a Window

FINAL NOTE
Final solution found in another post
Although I appreciated the clarification that was provided, the ultimate solution was in-fact provided by another solution as linked above. No matter WHAT I tried, the binding via the "Element Name" component was not working. I had to go based on the "Relative" hierarchy up the datagrid...
<Button Name="btnPrintReport"
Command="{Binding DataContext.MyPrintCommand,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type DataGrid}}}"
CommandParameter="{Binding}"
Height="16" Width="16" HorizontalAlignment="Center" >
<Image Source="MyButtonImage.png" IsHitTestVisible="True"/>
</Button>
Hope something not too complicated in WPF / MVVM environment. Here's the scenario.
I have a Window (.xaml) and a corresponding View Model (.cs). The form displays fine with all the data bindings no problem. (note: this is NOT done via any commercial "framework" )
One of the controls that is in the view window is a custom user control of a datagrid with all pre-defined columns, headings and content to be displayed when the view is shown. This works all no problem even though the control is not directly "defined" in the main window .xaml file, but just dropped on the form as the user control itself (which has its own obvious .cs code-behind).
With the main window's "DataContext" pointing to the View Model, and the user control that has a datagrid
<DataGrid AutoGenerateColumns="False"
Name="dataMyStuff"
ItemsSource="{Binding Path=MyTablePropertyOnViewModel,
NotifyOnSourceUpdated=True,
NotifyOnTargetUpdated=True}" ... />
Now, what I'm looking for. On this data grid, I have a column that has an image in the first column. When I click on this image, I want to print a report specific to the record as represented by this row (it has a PK value I use). So, how do I tell the image "KeyUp" event to go to the View Model event handler since that is where the data is, and some other methods I'll need for preparing the call to the report. The view portion of the grid is for cosmetic display to the user, and thus no "functionality" directly in this control.
-- EDIT -- per progress from answers
I've adjusted my datagrid per comments from Josh and Rachel, however, something still does not appear to be quite right... Seeing the button was using a "Command" instance, I interpreted this as it needed to attach to an instance of an "ICommand" interface object on my view model. So, I created an instance. I know the command handler works as it is also used for common things like Add, Edit, Save, Cancel, Exit, etc... So I have a new one for this printing purpose. For simplicity, I have it created as an Always Execute, so there is no method to handle the "CanExecute" portion of the control. I've set the button's "Command" to almost all iterations I could think of an still nothing, but here's an update of what I'm seeing going on.
<UserControl>
<Data grid columns / template, etc to the button>
<DataTemplate>
<Button Name="btnPrintReport"
Command="{Binding DataContext.MyPrintCommand}" >
<Image Source="myPrintImage.png"/>
</Button>
</DataTemplate>
</Data grid columns, etc>
</UserControl>
In my ViewModel class (myICommandButtonHandler inherits from ICommand)
private myICommandButtonHandler myPrintCommand;
public myICommandButtonHandler MyPrintCommand
{
get { if (myPrintCommand == null)
myPrintCommand = new myICommandButtonHandler(myPrint);
return myPrintCommand;
}
}
private void myPrint()
{
MessageBox.Show( "Doing the print job..." );
}
Now, what I'm seeing. During step through initialization of all the controls and such. I click menu item to call my Window to be displayed. FIRST, it creates an instance of the View Model controller. THEN, it calls the Window and passes in the View Model controller as parameter so it is immediately set at the Window level as the "DataContext" of the window. The main window then goes into it's "InitializeComponents" call and starts to build out all the other controls embedded, including this separate class that contains the data grid in question. At the constructor of this usercontrol (that has the datagrid), there is no "data context" set yet as the rest of the controls have not been initialized yet, and I don't know why / when the "bindings" apparently get "hooked" in. So, it appears that trying to do the binding to the data grid's command button are failing. HOWEVER, at run-time, the actual data IS updated in the grid, so I know that much is working.
So, the data grid has its "ItemsSource" set to a property of a "DataView" property on the view model, but the binding of the "button" doesn't appear to see the "MyPrintCommand" handler that I thought would get the hit.. and its action is to just display a message box (for now).
Usually I use an AttachedCommand Behavior which allows me to bind Events to ViewModel Commands. For example, you could use
<Image ...
local:CommandBehavior.Event="KeyUp"
local:CommandBehavior.Command="{Binding DataContext.PrintCommand, ElementName=dataMyStuff}"
local:CommandBehavior.CommandParameter="{Binding }"/>
I'd would recommend using a different event than KeyUp, since I don't think Images can have Keyboard focus so the KeyUp event will never get fired.
A better alternative is to use a Button and overwrite it's Template to be your Image. This will maintain the Click functionality, and give you access to Command and CommandParameter properties
<Button Command="{Binding DataContext.PrintCommand, ElementName=dataMyStuff}"
CommandParameter="{Binding }">
<Button.Template>
<Image ... />
</Button.Template>
</Button>
Also, the CommandParameter="{Binding }" will simply pass the current DataRow's DataContext (your data object) to the command
Change the data template to be a button that has a image as its content. Use the command and commandparameter properties on the button to call your printing method. You can declare your print command in your viewmodel, and bind to it. Your parameter could be the selected row in your datagrid.

Categories

Resources