I am trying to understand how to setup EventTriggerBehaviors in a UWP project.
So I understand that I need to have the package Microsoft.Xaml.Behaviors.Uwp.Managed installed, and the following namespaces declared in my XAML file:
xmlns:Core="using:Microsoft.Xaml.Interactions.Core"
xmlns:Interactivity="using:Microsoft.Xaml.Interactivity"
The button by itself should be declared as:
<Button x:Name="btnTest >
<Interactivity:Interaction.Behaviors>
<Core:EventTriggerBehavior EventName="GotFocus" >
<Core:EventTriggerBehavior.Actions>
<Core:InvokeCommandAction Command="{Binding ... }" />
</Core:EventTriggerBehavior.Actions>
</Core:EventTriggerBehavior>
</Interactivity:Interaction.Behaviors>
</Button>
but then I got lost... What I would like is once the button get focus, it set some text (based on button name) within a text box.
Do I need a service, and what should be the ViewModel code?
and actually, is anyone able to recommend great reading, examples, books ... on the subject please?
Update following James reply:
The XAML InvokeCommandAction becomes:
<Core:InvokeCommandAction Command="{Binding OnButtonFocusCommand}" CommandParameter="{Binding Name, ElementName=btnTest}" />
But how do I receive the parameter within the method in the ViewModel?
The InvokeCommandAction Command property requires an implementation of an ICommand in your view model in order to perform an action when the EventTriggerBehavior is fired.
You might have something like this in the XAML:
<Button x:Name="btnTest">
<Interactivity:Interaction.Behaviors>
<Core:EventTriggerBehavior EventName="GotFocus">
<Core:EventTriggerBehavior.Actions>
<Core:InvokeCommandAction Command="{Binding OnButtonFocusCommand}" />
</Core:EventTriggerBehavior.Actions>
</Core:EventTriggerBehavior>
</Interactivity:Interaction.Behaviors>
</Button>
Then in the bound view-model, you would have something similar to this:
public ViewModel()
{
OnButtonFocusCommand = new DelegateCommand(() =>
{
this.TextBoxText = "Hello, World";
});
}
public ICommand OnButtonFocusCommand { get; private set; }
The DelegateCommand is not built in to the platform though but you can find many implementations of a DelegateCommand or RelayCommand online.
EDIT: You can also do this with a passed parameter using like this:
public ViewModel()
{
OnButtonFocusCommand = new DelegateCommand<RoutedEventArgs>(args =>
{
this.TextBoxText = "Hello, World";
});
}
The RoutedEventArgs would be the type of parameter you're passing through. In the case of what's passed by the Focus event, this is the parameter that you'll receive. You'll need the DelegateCommand{T} for these scenarios.
The examples of DelegateCommand that I've referenced also have a mechanism to check whether to run the action by validating the model. You can do that like so:
public ViewModel()
{
OnButtonFocusCommand = new DelegateCommand<RoutedEventArgs>(args =>
{
this.TextBoxText = "Hello, World";
},
args => args.OriginalSource is TextBox);
}
For your scenario with updating the text of a TextBox, you would need to create a property in your view-model (in my example I showed the TextBoxText being updated). That property would then need binding up to the Text property of your TextBox in XAML.
For things to take a look at, off the top of my head, I would suggest taking a look at an MVVM framework (possibly MvvmLight) and reading up on that if you've not already.
Also the official Microsoft samples on GitHub may cover a lot of topics which might be useful to you.
If you need any more information, get in touch and I'm happy to help.
Related
I am new to WPF and I see the best pattern call MVVM. I have try to deep in it and I see that the command can only execute on a button or menuitem, etc. But I have a doubt how to execute the ViewModel command when I'm focusing on a textbox and hit the enter key when I finish my editing.
I have google this but I got nothing from all that answer. So hope all of you help me. How to execute command when hit the enter key in textbox?
In my opinion the easiest way is to use a KeyBinding, which allows you to bind a KeyGesture to an ICommand implementation.
In your case, you can write in your XAML something like this:
<TextBox AcceptsReturn="False">
<TextBox.InputBindings>
<KeyBinding Key="Enter" Command="{Binding YourCommand}" />
</TextBox.InputBindings>
</TextBox>
So when your TextBox is focused and you press Enter, YourCommand will be executed.
I hope it can help you.
You can achieve your requirement using behaviors in WPF.
In XAML,
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
<TextBox Text="MyText">
<i:Interaction.Behaviors>
<i:BehaviorCollection>
<EventToCommand EventName="TextChanged" Command="{Binding ViewModelCommand}">
**// You can provide other events to be triggered in the EventName property based on your requirement like "Focused" or "UnFocused".Focused event will be fired if you enter into edit mode and UnFocused event will be triggered if you press enter key.**
<i:BehaviorCollection>
</i:Interaction.Behaviors>
</TextBox>
In ViewModel.cs,
Public class ViewModel
{
private Command viewCommand;
public ViewModel()
{
viewCommand = new Command(CommandMethod);
}
public Command ViewModelCommand
{
get { return viewCommand }
set { viewCommand = value}
}
private void CommandMethod()
{
//This method will hit if you modify enter/delete text in the TextBox
}
}
One thing I am really not sure about is how to properly pass mouse events to the ViewModel. There is the way of binding triggers using the interactivity extension like for instance in: WPF event binding from View to ViewModel?
But this does not forward the MouseEventArgs to my knowledge, and this solution does not appear very elegant to me.
So what would be the proper solution? One way is to register an event and to handle it in the code behind, e.g.:
private void ListBox_PreviewMouseDown(object sender, System.Windows.Input.MouseEventArgs e)
{
var listbox = sender as ListBox;
if (listbox == null)
return;
var vm = listbox.DataContext as MainViewModel;
if (vm == null)
return;
// access the source of the listbox in viewmodel
double x = e.GetPosition(listbox).X;
double y = e.GetPosition(listbox).Y;
vm.Nodes.Add(new Node(x, y));
}
Here I assume that the listbox's ItemsSource is bound to the vm.Nodes property. So again the question: is it the proper way of doing it? Or is there a better one?
Good timing, I wrote some code to do exactly this about two hours ago. You can indeed pass arguments, and personally I thnk it is elegant because it allows you to fully test your user interface. MVVM Lite allows you to bind events to commands with EventToCommand, so start by adding the relevant namespaces to your control/window:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:cmd ="http://www.galasoft.ch/mvvmlight"
Now add event triggers to the child control whose events you want to intercept:
<ItemsControl ... etc ... >
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDown">
<cmd:EventToCommand Command="{Binding Mode=OneWay, Path=MouseDownCommand}" PassEventArgsToCommand="True" />
</i:EventTrigger>
<i:EventTrigger EventName="MouseUp">
<cmd:EventToCommand Command="{Binding Mode=OneWay, Path=MouseUpCommand}" PassEventArgsToCommand="True" />
</i:EventTrigger>
<i:EventTrigger EventName="MouseMove">
<cmd:EventToCommand Command="{Binding Mode=OneWay, Path=MouseMoveCommand}" PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ItemsControl>
In my specific case I'm rendering a collection of items onto a canvas, hence my use of ItemsControl, but it'll work on anything including the parent window. It will also work for key strokes (e.g. KeyDown) but if your child control isn't focus-able then you'll have to add the trigger to the parent instead. In any case all that remains is to add the relevant handlers to your view model:
public class MyViewModel : ViewModelBase
{
public ICommand MouseDownCommand { get; set; }
public ICommand MouseUpCommand { get; set; }
public ICommand MouseMoveCommand { get; set; }
public ICommand KeyDownCommand { get; set; }
// I'm using a dependency injection framework which is why I'm
// doing this here, otherwise you could do it in the constructor
[InjectionMethod]
public void Init()
{
this.MouseDownCommand = new RelayCommand<MouseButtonEventArgs>(args => OnMouseDown(args));
this.MouseUpCommand = new RelayCommand<MouseButtonEventArgs>(args => OnMouseUp(args));
this.MouseMoveCommand = new RelayCommand<MouseEventArgs>(args => OnMouseMove(args));
this.KeyDownCommand = new RelayCommand<KeyEventArgs>(args => OnKeyDown(args));
}
private void OnMouseDown(MouseButtonEventArgs args)
{
// handle mouse press here
}
// OnMouseUp, OnMouseMove and OnKeyDown handlers go here
}
One last thing I will mention that is only a little bit off-topic is that often you'll need to communicate back to the code-behind e.g. when the user presses the left mouse button you might need to capture the mouse, but this can easily be accomplished with attached behaviors. The mouse capture behavior is simple enough, you just add a "MouseCaptured" boolean property to your view model, bind your attached behavior to it and have it's changed handler respond accordingly. For anything more complicated you might want to create an event inside your view model which your attached behaviour can then subscribe to. Either way, your UI is now fully unit-testable and your code-behind has been moved into generic behaviors for re-use in other classes.
I think your approach is good. Those events, that work with View, can be in your code-behind if you handlers work via ViewModel. However, there is an alternative use GalaSoft.MvvmLight (link to download), in which have EventToCommand, supports parameter PassEventArgsToCommand.
Example of using:
<Button>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseEnter">
<cmd:EventToCommand Command="{Binding FooCommand}"
PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
I think you can use both approaches. Your solution is simple, does not require the use of the any frameworks but uses code-behind, in this case it is not critical. One thing is certain, it is advisable not to keep ViewModel event handlers, use the command or store these handlers on View side.
Some new notes
I think, your way does not violate the principles of MVVM, all event handlers working with View, should be on the side of the View, the main thing - it's event handlers need to work with a ViewModel and have a dependency via an interface, but not directly with the UI.
The only principle MVVM that you break - is the mantra "no code" and this is not the main principle of MVVM. The main principles:
Split data Model of View
Application logic should not be tied to UI
Support testability code
Once the code-behind violate at least one of these principles, you must already see the alternatives to solve their problem.
Also, you can read opinions about it on this link:
WPF MVVM Code Behind
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.
Let's say I currently have an ItemsControl whose DataTemplate is a bunch of buttons. I'm wiring up these buttons' click events, but how am I to know which button was clicked? Should I not use a ItemsControl?
I'm trying to have no code-behind, but being pragmatic may be necessary.
<ItemsControl>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Margin="10">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<GalaSoft_MvvmLight_Command:EventToCommand Command="{Binding ItemsControlButtonClicked, Mode=OneWay}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
If you want to know what Item was clicked, then pass {Binding } as the CommandParameter and it will pass the selected object to your Command
If you want to know what Button was clicked, I would do that in the code-behind since ViewModels do not need to know anything about the UI, and that includes buttons.
Also since your control is a Button, you should use the Command property instead of a Click trigger.
<Button Command="{Binding ItemsControlButtonClicked}" />
You can send parameters along with the command and based on these parameters you can find out which button was clicked
In my project I also use the MVVM Light I has an dropdown with collection of items, and a button which user press and action depend on selected item from drop down
you should create a Relay command with parameter look at the example from my code
public RelayCommand<Project> StartTimer { get; private set; }//declare command
StartTimer = new RelayCommand<Project>(OnStartTimer);
private void OnStartTimer(Project project)
{
if (project != null)
{
currentProject = project;
if (!timer.IsTimerStopped)
{
timer.StopTimer();
}
else
{
Caption = "Stop";
timer.StartTimer();
}
}
on the view I bind the drop down with collection of class Project
and for button command parameter I bind the selected item form drop down
look at the code
<ComboBox Name="projectcomboBox" ItemsSource="{Binding Path=Projects}" IsSynchronizedWithCurrentItem="True" DisplayMemberPath="FullName"
SelectedValuePath="Name" SelectedIndex="0" >
</ComboBox>
<Button Name="timerButton" Content="{Binding Path=Caption}" Command="{Binding Path=StartTimer}"
CommandParameter="{Binding ElementName=projectcomboBox, Path=SelectedItem}" ></Button>
pay attention to Command and CommandParameter binding
also you can use this approache not only for drop down
Well, you can use the Sender.DataContext which is the actual data.
Create command properties in your view model class (using Josh Smith's RelayCommand pattern is the simplest way to do this) and bind each button's Command to the appropriate one. Not only is this straightforward to do and simple to maintain, it also gives you an easy way of implementing the enable/disable behavior when you need to.
I'm having trouble getting the RelayCommand to enable/disable the attached control properly.
I've got an EventToCommand element attached to a button. The command is databound to the ViewModel. Initially, the button is disabled (expected behavior), but I cannot seem to get the CanExecute logic to check it's value. When CurrentConfigFile is set and exists, the button should be enabled. I've executed code and checked the file's value in debug to make sure it's set, but the control is still disabled. I've tried CommandManager.InvalidateRequerySuggested() and command.RaiseCanExecuteChanged(), but it will not enable.
I've wondered if lambdas don't work correctly for the CanExecute behavior (even though the examples use them) or that the CanExecute behavior needs to be databound to another element.
Here's my code:
// The FileInfo being checked for existence before the button should be enabled
public const string CurrentConfigFilePN = "CurrentConfigFile";
public FileInfo CurrentConfigFile
{
get
{
return _currentConfigFile;
}
set
{
if (_currentConfigFile == value)
{
return;
}
var oldValue = _currentConfigFile;
_currentConfigFile = value;
// Update bindings, no broadcast
RaisePropertyChanged(CurrentConfigFilePN);
}
}
public MainViewModel()
{
// snip //
SaveCommand = new RelayCommand(SaveConfiguration,
() => CurrentConfigFile != null && CurrentConfigFile.Exists);
}
private void SaveConfiguration()
{
// export model information to xml document
ExportXMLConfiguration(CurrentConfigFile);
}
and markup
<Button x:Name="SaveButton" Content="Save" Width="75" Margin="20,5">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<GalaSoft:EventToCommand x:Name="SaveETC"
Command="{Binding SaveCommand}"
MustToggleIsEnabledValue="true" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
Update:
As per Isak Savo's suggestion, I bound the RelayCommand directly to the button with
<Button x:Name="SaveButton" Content="Save" Width="75" Margin="20,5"
Command="{Binding SaveCommand}"/>
and it started disabled and correctly enabled when the FileInfo was set. Guess I should remember not to fix what isn't broken!
Why don't you just bind to the Command directly from the Button?
<Button Command="{Binding SaveCommand}" Content="Save" />
Maybe the EventToCommand thing you are using is messing things up with the command's CanExecute notification.
And regarding the CanExecute problem - are you sure that your CanExecute handler is called after the CurrentConfigFile property is set? I've found that even though WPF mostly does a good job of requerying CanExecute, I still sometimes need to force a requery through the CommandManager.
EDIT: As pointed out in the comments, the OP has already tried the command manager approach.
In msdn is written:
When first called, FileInfo calls Refresh and caches information about the file. On subsequent calls, you must call Refresh to get the latest copy of the information.
However, I would not do such a check in the CanExecute-handler. This may slow down your UI because CanExecute is called a lot of times and I could imagine that such IO-checks can become slow, for example if the file lies on a network share.