wpf Interaction EventTrigger with multiple sources - c#

I am trying to catch a RountedEvent fired from multiple instances of a UserControl. This works in the non-MVVM world easily by catching the routed event at the Window level (local:MyControl.MyEvent="Window_CodeBehindHandler"). But I need to forward that event on to the View Model, and I am struggling to get an Interaction.Triggers setup to behave the same. The EventTrigger behaves as expected if I pass in a specific SourceName or SourceObject or place the Interaction.Triggers inside of the UserControl in the XAML, but that doesn't work the same.
I have tried various versions of setting SourceName or SourceObject to the control type or name without luck.
I've put together an MRE here: WpfInteractionEventTriggerMRE. The "real" implementation has an ItemsView bound to a collection created at runtime of these control objects, with a lot more going on than a single "Select" event, so "just use an {x} control with Click already implemented and restyle it" suggestions aren't helpful.
<Window x:Class="WpfApp5.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp5"
xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800"
local:ElipseControl.Select="Window_Select"> <!-- This works as expected -->
<b:Interaction.Triggers> <!-- This does not -->
<b:EventTrigger EventName="Select">
<b:InvokeCommandAction Command="{Binding ElipseSelectedCommand}" />
</b:EventTrigger>
</b:Interaction.Triggers>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<local:ElipseControl Grid.Row="0" ElipseName="One" />
<local:ElipseControl Grid.Row="1" ElipseName="Two" />
</Grid>
</Window>
I am hoping there is something I am just overlooking here.

The EventTrigger that you are using doesn't support attached events. You will either have to implement your own custom event trigger or you could simply invoke the command from the Window_Select event handler in the window:
var viewModel = DataContext as YourViewModel;
if (viewModel != null)
viewModel.ElipseSelectedCommand.Execute(null);
This doesn't break the MVVM pattern in any way since you are invoking the very same command from the very same view compared to if you have used an interaction trigger. MVVM isn't about eliminating code from the view - it's about separation of concerns.

The custom event trigger worked. The MRE in the original question is updated. Here is the relevant updated code.
<Window x:Class="WpfApp5.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp5"
xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<b:Interaction.Triggers>
<local:RoutedEventTrigger RoutedEvent="local:ElipseControl.Select" >
<b:InvokeCommandAction Command="{Binding ElipseSelectedCommand}" PassEventArgsToCommand="True" />
</local:RoutedEventTrigger>
</b:Interaction.Triggers>
<Grid>
<ItemsControl ItemsSource="{Binding Elipses}" />
</Grid>
</Window>
As per custom event trigger, a slightly updated version:
public class RoutedEventTrigger : EventTriggerBase<DependencyObject>
{
private RoutedEventHandler _eventHandler;
public RoutedEvent RoutedEvent { get; set; }
public RoutedEventTrigger()
{
}
protected override void OnAttached()
{
var associatedElement = GetAssociatedElement();
if (associatedElement != null && RoutedEvent != null)
{
_eventHandler = new RoutedEventHandler(OnRoutedEvent);
associatedElement.AddHandler(RoutedEvent, _eventHandler);
}
}
protected override void OnDetaching()
{
var associatedElement = GetAssociatedElement();
if (associatedElement != null && RoutedEvent != null && _eventHandler != null)
{
associatedElement.RemoveHandler(RoutedEvent, _eventHandler);
}
base.OnDetaching();
}
private FrameworkElement GetAssociatedElement()
{
FrameworkElement associatedElement = null;
if (AssociatedObject is Behavior behavior)
{
associatedElement = ((IAttachedObject)behavior).AssociatedObject as FrameworkElement;
}
else if (AssociatedObject is FrameworkElement frameworkElement)
{
associatedElement = frameworkElement;
}
if (associatedElement == null)
{
throw new ArgumentException("Routed Event Trigger can only be associated to framework elements");
}
return associatedElement;
}
private void OnRoutedEvent(object sender, RoutedEventArgs e) => base.OnEvent(e);
protected override string GetEventName() => RoutedEvent?.Name ?? string.Empty;
}

Related

MVVM Switching Between Views

I'm new to WPF so bear with me. I have a WinForms Application that I am trying to redo in WPF. In my current WinForms Application, I stick all my Controls into one Form, and hide/show them based on what buttons are hit, as well as making use of a second form.
My goal: Create different views to switch between smoothly based on what button is hit, instead of hiding Controls or making separate Forms and then hiding those.
I currently have a MainWindow view (My initial launch window), where with a button, I switch to my CreateAccount view. What I am having issues with is, how can I make my button in my CreateAccount go "back" to my MainWindow?
My end goal is to be able to switch between 4 views based off Button clicks.
Here is my MainWindow.xaml
<Window x:Class="MusicPlayer.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MusicPlayer"
xmlns:Views="clr-namespace:MusicPlayer.Views"
xmlns:ViewModels="clr-namespace:MusicPlayer.ViewModels"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<DataTemplate x:Name="CreateAccountTemplate" DataType="{x:Type ViewModels:CreateAccountViewModel}">
<Views:CreateAccountView DataContext="{Binding}"/>
</DataTemplate>
</Window.Resources>
<Grid>
<Button x:Name="TestButton" Content="Button" HorizontalAlignment="Left" Margin="164,182,0,0" VerticalAlignment="Top" Height="61" Width="68" Click="CreateAccountView_Clicked"/>
<PasswordBox HorizontalAlignment="Left" Margin="164,284,0,0" VerticalAlignment="Top" Width="120"/>
<ContentPresenter Content="{Binding}"/>
</Grid>
</Window>
My MainWindow.xaml.cs
using System;
using System.Windows;
using MusicPlayer.ViewModels;
namespace MusicPlayer {
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
}
protected override void OnClosed(EventArgs e) {
base.OnClosed(e);
Application.Current.Shutdown();
} //end of onClosed
private void CreateAccountView_Clicked(object sender, RoutedEventArgs e) {
DataContext = new CreateAccountViewModel();
} //end of CreateAccountView_Clicked
}
}
And here is my CreateAccount.xaml
<UserControl x:Class="MusicPlayer.Views.CreateAccountView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:Views="clr-namespace:MusicPlayer.Views"
xmlns:ViewModels="clr-namespace:MusicPlayer.ViewModels"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
</UserControl.Resources>
<Grid Background="White">
<Button Content="Button" HorizontalAlignment="Left" Margin="276,279,0,0" VerticalAlignment="Top" Height="60" Width="59" Click="Button_Click"/>
</Grid>
</UserControl>
And my CreateAccountView.xaml.cs
using System.Windows;
using System.Windows.Controls;
using MusicPlayer.ViewModels;
namespace MusicPlayer.Views {
public partial class CreateAccountView : UserControl {
//public static readonly DependencyProperty TestMeDependency = DependencyProperty.Register("MyProperty", typeof(string), typeof(CreateAccountView));
public CreateAccountView() {
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e) {
DataContext = new MainWindowViewModel();
}
}
}
It seems to me that your current attempt is on the right track. The main issue with the code you posted is that the CreateAccountView.Button_Click() handler doesn't have access to the DataContext property it should be setting:
private void Button_Click(object sender, RoutedEventArgs e) {
DataContext = new MainWindowViewModel();
}
This DataContext property belongs to the CreateAccountView user control. However, this is not the controlling context for what's being displayed. So changing the value of that DataContext property doesn't have any useful effect. (Indeed, a user control should not set its own DataContext property at all, because doing so discards whatever context the client code using that user control had set.)
There's not enough context to know exactly what the best way for you to do this would be. I don't think it would be possible to provide enough context here on Stack Overflow. The overall architecture will depend on too many little details about your program. But, one way to approach this which I think is a good one would be this:
Create a "main" view model that governs the overall behavior of the app
Create individual view models that relate to different states of the UI
Have the main view model configure the individual view models to switch the current view model as appropriate, given the user input (e.g. clicking buttons)
Translating that into code, looks something like this…
First, the view models:
class MainViewModel : NotifyPropertyChangedBase
{
private object _currentViewModel;
public object CurrentViewModel
{
get => _currentViewModel;
set => _UpdateField(ref _currentViewModel, value);
}
private readonly HomeViewModel _homeViewModel;
private readonly Sub1ViewModel _sub1ViewModel;
private readonly Sub2ViewModel _sub2ViewModel;
public MainViewModel()
{
_sub1ViewModel = new Sub1ViewModel
{
BackCommand = new DelegateCommand(() => CurrentViewModel = _homeViewModel)
};
_sub2ViewModel = new Sub2ViewModel
{
BackCommand = new DelegateCommand(() => CurrentViewModel = _homeViewModel)
};
_homeViewModel = new HomeViewModel
{
ShowSub1Command = new DelegateCommand(() => CurrentViewModel = _sub1ViewModel),
ShowSub2Command = new DelegateCommand(() => CurrentViewModel = _sub2ViewModel)
};
CurrentViewModel = _homeViewModel;
}
}
class HomeViewModel : NotifyPropertyChangedBase
{
private ICommand _showSub1Command;
public ICommand ShowSub1Command
{
get => _showSub1Command;
set => _UpdateField(ref _showSub1Command, value);
}
private ICommand _showSub2Command;
public ICommand ShowSub2Command
{
get => _showSub2Command;
set => _UpdateField(ref _showSub2Command, value);
}
}
class Sub1ViewModel : NotifyPropertyChangedBase
{
private ICommand _backCommand;
public ICommand BackCommand
{
get => _backCommand;
set => _UpdateField(ref _backCommand, value);
}
}
class Sub2ViewModel : NotifyPropertyChangedBase
{
private ICommand _backCommand;
public ICommand BackCommand
{
get => _backCommand;
set => _UpdateField(ref _backCommand, value);
}
}
Of course, these view models contain only the implementation details needed to handle the UI switching. In your program, each would also include the stuff specific to each view state that you need.
In my little sample, the "home" view contains a couple of buttons, used to select the individual sub-views available:
<UserControl x:Class="WpfApp1.HomeView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Home: "/>
<Button Content="Sub1" Command="{Binding ShowSub1Command}"/>
<Button Content="Sub2" Command="{Binding ShowSub2Command}"/>
</StackPanel>
</UserControl>
The sub views just contain the button required to go back to the home view:
<UserControl x:Class="WpfApp1.Sub1View"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Sub1 View: "/>
<Button Content="Back" Command="{Binding BackCommand}"/>
</StackPanel>
</UserControl>
<UserControl x:Class="WpfApp1.Sub2View"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Sub2 View: "/>
<Button Content="Back" Command="{Binding BackCommand}"/>
</StackPanel>
</UserControl>
Finally, the main window sets the main view model, and declares templates to use for each of the specific sub views:
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:l="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<l:MainViewModel/>
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type l:HomeViewModel}">
<l:HomeView/>
</DataTemplate>
<DataTemplate DataType="{x:Type l:Sub1ViewModel}">
<l:Sub1View/>
</DataTemplate>
<DataTemplate DataType="{x:Type l:Sub2ViewModel}">
<l:Sub2View/>
</DataTemplate>
</Window.Resources>
<StackPanel>
<ContentControl Content="{Binding CurrentViewModel}"/>
</StackPanel>
</Window>
Importantly, you'll see that none of the view objects include any code-behind. It's not necessary when you approach the problem this way, at least not for the purpose of controlling basic behaviors in the code. (You may still wind up with code-behind for view objects, but this will usually only be for the purpose of implementing specific user-interface behaviors unique to that view object, not for dealing with the view model state.)
Using this approach, you let WPF do as much of the heavy-lifting as possible. It also decouples all of the view model objects from each other. There's a clear hierarchy: only the top-level "main" view model even knows about the other view models. This allows the sub-view models ("home", "sub1", and "sub2") to be reused as necessary in other scenarios without any modification or special-case handling within them.
Here are the helper classes I used above:
class NotifyPropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void _UpdateField<T>(ref T field, T newValue,
Action<T> onChangedCallback = null,
[CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, newValue))
{
return;
}
T oldValue = field;
field = newValue;
onChangedCallback?.Invoke(oldValue);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
class DelegateCommand : ICommand
{
private readonly Action _execute;
public DelegateCommand(Action execute)
{
_execute = execute;
}
#pragma warning disable 67
public event EventHandler CanExecuteChanged;
#pragma warning restore
public bool CanExecute(object parameter) => true;
public void Execute(object parameter) => _execute();
}

CallMethodAction binding to Button fails

There are no errors. I am trying to learn MVVM. The setup is simple. I get no output when clicking on the button. Xaml should be ok since I have produced interactions part in Blend by dragging behavior to the button. Note: My intention to use methods, but not command, since commands cover only click, but not for example DoubleClick.
<Window
x:Class="MVVM_1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:local="clr-namespace:MVVM_1"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="525"
Height="350"
mc:Ignorable="d"
>
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<Grid>
<Button x:Name="button"
Width="75"
Margin="198,168,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Content="TestMethod">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown" SourceName="button">
<ei:CallMethodAction MethodName="MethodTesting"
TargetObject="{Binding}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</Grid>
using System.Windows;
namespace MVVM_1
{
public class ViewModel
{
public static void MethodTesting()
{
MessageBox.Show("Success!");
}
}
}
You need to use a command to bind the control to the click event.
MVVM - Commands, RelayCommands and EventToCommand
Instead of:
public static void MethodTesting()
{
MessageBox.Show("Success!");
}
Use this example:
public ICommand ButtonClickCommand
{
get { return new DelegateCommand<object>(FuncToCall, FuncToEvaluate);}
}
private void FuncToCall(object context)
{
//this is called when the button is clicked
MessageBox.Show("Success!");
}
private bool FuncToEvaluate(object context)
{
//this is called to evaluate whether FuncToCall can be called
//for example you can return true or false based on some validation logic
return true;
}
Also under this How do you create an OnClick command in WPF MVVM with a programmatically created button? - is another great example.

WPF UserControl, with Buttons and KeyBindings

I'm trying to implement a dialer in WPF. I have a window, and inside it a user control. The user control has lots of buttons, but the user can also use the num pad to enter numbers.
I created a small sample project to show where I'm at:
MainWindow.xaml
<Window x:Class="wpf_dialer.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:wpf_dialer"
Title="MainWindow" Height="200" Width="525">
<Window.Resources>
<local:DialerViewModel x:Key="myViewModel" />
</Window.Resources>
<Grid DataContext="{StaticResource myViewModel}">
<local:Dialer />
</Grid>
</Window>
Dialer.xaml
<UserControl x:Class="wpf_dialer.Dialer"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:wpf_dialer"
mc:Ignorable="d"
d:DesignHeight="200" d:DesignWidth="300"
d:DataContext="{d:DesignInstance local:DialerViewModel}"
Loaded="UserControl_Loaded">
<UserControl.InputBindings>
<KeyBinding Key="A" Command="{Binding CommandDialValue, Mode=OneTime}" CommandParameter="." />
<KeyBinding Key="Back" Command="{Binding CommandDialValue, Mode=OneTime}" CommandParameter="Back" />
<KeyBinding Key="Enter" Command="{Binding CommandAcceptValue, Mode=OneTime}" CommandParameter="KeyBinding" />
</UserControl.InputBindings>
<UniformGrid Columns="1">
<TextBox IsEnabled="False" Text="{Binding DialedValue, Mode=OneWay}" MinWidth="200" FontSize="20" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="10"/>
<Button Content="OK" Margin="60,30" Command="{Binding CommandAcceptValue, Mode=OneTime}" CommandParameter="Button" />
</UniformGrid>
</UserControl>
DialerViewModel.cs
using System;
using System.ComponentModel;
using System.Windows.Input;
namespace wpf_dialer
{
class DialerViewModel : INotifyPropertyChanged
{
private Random RAND = new Random();
private string _dialed_value = "00";
public string DialedValue
{
get { return _dialed_value; }
set
{
if (_dialed_value == value) return;
_dialed_value = value;
RaisePropertyChanged("DialedValue");
}
}
public ICommand CommandDialValue { get { return new CommandImpl(DialValue); } }
public ICommand CommandAcceptValue { get { return new CommandImpl(Alert); } }
private void DialValue(object parameter)
{
if (parameter.ToString() == "Back")
{
if (DialedValue.Length > 0)
{
DialedValue = DialedValue.Substring(0, DialedValue.Length - 1);
}
}
else
{
DialedValue += RAND.Next(0, 10).ToString();
}
}
private void Alert(object parameter)
{
System.Windows.MessageBox.Show(parameter.ToString());
}
#region INotifyPropertyChanged
protected void RaisePropertyChanged(string property_name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property_name));
}
}
public event PropertyChangedEventHandler PropertyChanged;
#endregion
private class CommandImpl : ICommand
{
private readonly Action<object> _action = null;
public CommandImpl(Action<object> action)
{
_action = action;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter) { return true; }
public void Execute(object parameter) { _action(parameter); }
}
}
}
Objectives:
As soon as the window is loaded, when the user presses the A key, the CommandDialValue is executed;
When the user presses Enter, a message box is displayed with the text "KeyBinding". The CommandAcceptValue must be called from the KeyBinding, and NOT from the button;
Problems:
When the window is loaded, the KeyBindings don't execute. They are executed when I click a button somewhere in the UserControl;
When I press Enter, the button's command is executed, but I want the user control's KeyBinding to be executed;
This dialer must be held in a UserControl (or a ControlTemplate, or DataTemplate), because it's contained in a very elaborate window.
I don't want to put the KeyBindings on the Window, because then the UserControl is not reusable, and because its DataContext is not the same as the user control.
UPDATE:
I solved the second problem by setting Focusable="False" on all buttons.
To prevent the buttons from gaining focus, I set Focusable="False" for all buttons.
To set the focus when the window opens, I set Focusable="True" on the UserControl, and on the Loaded event I called Focus().
Dialer.xaml
d:DataContext="{d:DesignInstance local:DialerViewModel}"
Loaded="UserControl_Loaded" Focusable="True">
<UserControl.InputBindings>
Dialer.xaml.cs
private void UserControl_Loaded(object sender, RoutedEventArgs e) {
Focus();
}
I found no combination of FocusManager.FocusedElement that worked. I tried {Binding ElementName=myUserControl}, and {Binding RelativeSource={RelativeSource Self}}.
It's a question of Focus. When your window is loaded for the first time, your user control does not have Focus. So key bindings' gesture will not intercept your keypress. You have, at the first app loading time, to give Focus to your user control. (Focusable ="True"(i don't know if this helps but i am sure the FocusManager will helps)). Then your key gestures will work well.

WPF RoutedCommand CanExecute event is not fired

I have created a UserControl that has a command (DeleteCommand) inside:
public partial class TestControl : UserControl
{
public static RoutedCommand DeleteCommand = new RoutedCommand();
private void DeleteCommandExecute(object sender, ExecutedRoutedEventArgs e)
{
}
private void DeleteCommandCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
public TestControl()
{
InitializeComponent();
CommandBinding deleteCommandBinding = new CommandBinding(DeleteCommand, DeleteCommandExecute, DeleteCommandCanExecute);
this.CommandBindings.Add(deleteCommandBinding);
}
}
I have put this UserControl inside a Window:
<Window x:Class="TestRoutedCommand.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestRoutedCommand"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Button Content="Fire event" Margin="156,29,205,254" Command="{x:Static local:TestControl.DeleteCommand}" />
<local:TestControl Margin="126,135,135,46"/>
</Grid>
</Window>
There is also a Button which is using the DeleteCommand. My problem is that this button is always disabled and the DeleteCommandCanExecute handler is never called, although e.CanExecute is always set to true.
I have tried to call:
CommandManager.InvalidateRequerySuggested();
but nothing happens. The event is never fired. Maybe I am doing the CommandBinding wrong.
What I want to achieve is that when the user clicks on the button that the DeleteCommandExecute handler is fired. My goal is to create commands for my MenuButtons which will trigger some methods in my UserControls which can be deep in the Visual Tree.
Slightly change your XAML:
<Grid>
<Button Content="Fire event" Margin="156,29,205,254" Command="{x:Static local:TestControl.DeleteCommand}" CommandTarget="{Binding ElementName=Control1}" />
<local:TestControl x:Name="Control1" Margin="126,135,135,46"/>
</Grid>
CommandTarget says where to find needed handlers.

WPF binding issues while using MVVM

Time for my first question :)
I have the following:
public class BuilderViewModel : INotifyPropertyChanged
{
#region Implementation of INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
#endregion
private double _contentScale = 1.0;
public double ContentScale
{
get { return _contentScale; }
set
{
_contentScale = value;
NotifyPropertyChanged("ContentScale");
}
}
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#region Commands
bool CanZoomIn() { return true; }
void ZoomInExecute()
{
ContentScale += 1.0;
}
public ICommand ZoomIn { get { return new RelayCommand(ZoomInExecute, CanZoomIn); } }
#endregion
}
And the corresponding view:
<UserControl x:Class="PS_IDE.FormBuilder.View.Builder"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PS_IDE.FormBuilder.ViewModel">
<UserControl.DataContext>
<local:BuilderViewModel />
</UserControl.DataContext>
<TextBox Text="{Binding ContentScale}" Width="100" />
</UserControl>
I'm trying to have the ZoomIn command in BuilderViewModel update the text box value in it's view. The command is being fired from another user control, UIBuilder, which includes Builder. If I debug and fire the command from UIBuilder, I can see it updating ContentScale properly.
However, my text box value does not get updated (it only says "1", which is the initial value of ContentScale).
I know I'm missing something and hope someone can point me in the right direction.
EDIT: Added the control that is firing the command
<UserControl x:Class="PS_IDE.FormBuilder.UIBuilder"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:PS_IDE.FormBuilder"
xmlns:ViewModel="clr-namespace:PS_IDE.FormBuilder.ViewModel"
xmlns:View="clr-namespace:PS_IDE.FormBuilder.View" mc:Ignorable="d">
<UserControl.DataContext>
<ViewModel:BuilderViewModel />
</UserControl.DataContext>
<DockPanel LastChildFill="True">
....
<ToolBarTray DockPanel.Dock="Bottom" HorizontalAlignment="Right">
<ToolBar>
<Button Height="24" Width="24" ToolTip="Zoom In" Command="{Binding ZoomIn}">
<Image Source="Images/ZoomIn.png" Height="16"/>
</Button>
....
</ToolBar>
</ToolBarTray>
<View:Builder x:Name="builder" />
</DockPanel>
</UserControl>
With the setting in both view:
<UserControl.DataContext>
<local:BuilderViewModel />
</UserControl.DataContext>
you are basically creating two viewmodels, one for each view. So when your Command updates the property it does it on one of the viewmodel but your textbox is bound to a different viewmodel.
To resolve it remove the DataContext setting from the Builder.xaml
Additionally you need to pass your DataContext to your Builder control (with this both view will share the same viewmodel).
So modify your UIBuilder.xaml:
<View:Builder x:Name="builder" DataContext="{Binding}" />
Use Mode TwoWay in your binding
Text ="{Binding ElementName=BuilderViewModel,
Path=ContentScale,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"
Nota : use observable collection in order to send notify

Categories

Resources