I have implement the command Binding on Text Box and implement the command for click action in Button. Initially focus is persists in the Text Box only. My issue is me tried to click the button on view through mouse. In this case my text box lost focus command triggered first that is fine but that the button click command is not invoked. Text Box lost focus event handled the event to traverse.
public partial class MainWindow : Window
{
public MainWindow()
{
this.DataContext = new ViewModel();
InitializeComponent();
txtServer.Focus();
}
}
public class RelayCommand : ICommand
{
public event EventHandler CanExecuteChanged;
private Action<object> execute;
private Predicate<object> canExecute;
private event EventHandler CanExecuteChangedInternal;
public RelayCommand(Action<object> execute = null, Predicate<object> canExecute=null )
{
this.execute = execute;
this.canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
execute.Invoke(parameter);
}
}
public class ViewModel
{
private void ActionRequestedEvent(object param)
{
}
private ICommand _ActionCommand;
public ICommand ActionCommand
{
get
{
if (_ActionCommand == null)
this._ActionCommand = new RelayCommand(param =>
{
ActionRequestedEvent(param);
});
return _ActionCommand;
}
set
{
_ActionCommand = value;
}
}
}
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<TextBox Width="150" Name="txtServer" Height="25">
<i:Interaction.Triggers>
<i:EventTrigger EventName="LostFocus">
<i:InvokeCommandAction Command="{Binding ActionCommand}" CommandParameter="DataSetServerLostFocus"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
<Button Content="..." Width="25" Height="25" Margin="3 0 0 0" Command="{Binding ActionCommand}"/>
</StackPanel>
Most likely your problem is not an implementation problem, but a problem of the testing method you have chosen.
As I assume, you have set breakpoints and are trying to "catch" a call to the ActionRequestedEvent (object param) method.
When the command is executed for the first time, control is transferred to Debug Studio.
You press "Continue" and the command is not executed a second time.
This is due to the fact that after activating the Studio, your Window loses the user focus and therefore the command for the button is no longer called.
Here is an example of testing - you will see the result of calling the method in the Studio's Output Window.
using System.Diagnostics;
using System.Windows.Input;
namespace LostFocusCommand
{
public class ViewModel
{
private int num;
private void ActionRequestedEvent(object param)
{
Debug.WriteLine($"{++num}: {param}");
}
private ICommand _actionCommand;
public ICommand ActionCommand => _actionCommand
?? (_actionCommand = new RelayCommand(ActionRequestedEvent));
}
}
public partial class LostFocusCommandWindow : Window
{
public LostFocusCommandWindow()
{
InitializeComponent();
}
}
<Window x:Class="LostFocusCommand.LostFocusCommandWindow"
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:LostFocusCommand"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
mc:Ignorable="d"
Title="LostFocusCommandWindow" Height="450" Width="800"
FocusManager.FocusedElement="{Binding ElementName=txtServer}">
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<TextBox Width="150" x:Name="txtServer" Height="25">
<i:Interaction.Triggers>
<i:EventTrigger EventName="LostFocus">
<i:InvokeCommandAction Command="{Binding ActionCommand}"
CommandParameter="DataSetServerLostFocus"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
<Button Content="..." Width="25" Height="25" Margin="3 0 0 0"
Command="{Binding ActionCommand}"
CommandParameter="Button"/>
</StackPanel>
</StackPanel>
</Window>
I also wanted to point out to you the incorrect implementation of ICommand.
In some cases, such an implementation may not work correctly.
Use the implementation at this link: BaseInpc, RelayCommand and RelayCommand<T> classes.
Related
I try to bind my CheckBox into my commnd.
Base view model
public ViewModelBase()
{
SelectedFileCommand = new SelectedFileCommand(this);
}
<Page.DataContext>
<viewmodel:ViewModelBase/>
</Page.DataContext>
Command
public class SelectedFileCommand : ICommand
{
public event EventHandler CanExecuteChanged;
public ViewModelBase ViewModel { get; set; }
public SelectedFileCommand(ViewModelBase viewModel)
{
ViewModel = viewModel;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
}
}
}
My CheckBox
<CheckBox IsChecked="{Binding IsSelected}"
<i:Interaction.Triggers>
<i:EventTrigger EventName="Checked">
<i:InvokeCommandAction Command="{Binding SelectedFileCommand}" CommandParameter="{Binding}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</CheckBox>
I also Try:
<CheckBox DataContext="{Binding}"
<i:Interaction.Triggers>
<i:EventTrigger EventName="IsChecked">
<i:InvokeCommandAction Command="{Binding SelectedFileCommand}"
CommandParameter="CheckBox.IsChecked"/>
</i:EventTrigger>
</i:Interaction.Triggers>
But my Execute function not called.
EDIT
I forgot to mention that this CheckBox is inside ListViewItem
Working solution
<CheckBox IsChecked="{Binding IsSelected}"
Command="{Binding DataContext.CheckBoxSelectedFileCommand, ElementName=mainView}"
CommandParameter="{Binding IsChecked}"/>
If the checkbox is in a listview when you say Command="{Binding SelectedFileCommand}" you will bind to the listview item's datacontext. If yor command is in the viewmodel of your window this won't work. Something like this will bind to the command that is in your main viewmodel.
Command="{Binding DataContext.SelectedFileCommand, ElementName=mainView}"
Here I gave the window x:Name=mainView. This way I can bind to properties of it's dataContext.
And, IsChecked is not an event you should use "Checked".
Last, the command parameter issue. Since there are two different events for checkbox (Checked/Unchecked) you can use two commands and not pass any parameters. Or you can put a property in the list item viewmodel like;
public bool IsChecked { get; set; }
and you can bind your checkbox's IsChecked property to this property. And finally you can bind command parameter to this new property.
Edit: Full example
<Window x:Class="WpfApp2.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:i="http://schemas.microsoft.com/expression/2010/interactivity"
x:Name="mainView"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<ListView ItemsSource="{Binding Items}">
<ListView.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Text}" IsChecked="{Binding IsChecked}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Checked">
<i:InvokeCommandAction Command="{Binding DataContext.SelectedFileCommand, ElementName=mainView}"
CommandParameter="{Binding IsChecked}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</CheckBox>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Window>
Codebehind:
using System;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Input;
namespace WpfApp2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainViewModel();
}
}
public class MainViewModel
{
public ObservableCollection<ItemViewModel> Items { get; set; } = new ObservableCollection<ItemViewModel>();
public ICommand SelectedFileCommand { get; set; }
public MainViewModel()
{
SelectedFileCommand = new SelectedFileCommand(this);
this.Items.Add(new ItemViewModel() { Text = "Item 1" });
this.Items.Add(new ItemViewModel() { Text = "Item 2" });
this.Items.Add(new ItemViewModel() { Text = "Item 3" });
this.Items.Add(new ItemViewModel() { Text = "Item 4" });
}
}
public class ItemViewModel
{
public string Text { get; set; }
public bool IsChecked { get; set; }
}
public class SelectedFileCommand : ICommand
{
public event EventHandler CanExecuteChanged;
public MainViewModel ViewModel { get; set; }
public SelectedFileCommand(MainViewModel viewModel)
{
ViewModel = viewModel;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
var x = parameter;
}
}
}
I'm struggling with some databinding. In my MainWindow I have 2 buttons which are databound to a property Result in a class "Properties" - strictly for holding properties that I will be using for databinding. The buttons are hidden by default, and when I want them to become visible I simply set Result property that they are bound to to "True"
I know the databinding is working because if I set the property to a static value, the buttons are visible / not visible. See below for my XAML
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<StackPanel.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVis" />
</StackPanel.Resources>
<Button x:Name="btnBack" Height="25" Content="<- Back" Visibility="{Binding Path=Result, Converter={StaticResource BoolToVis},
UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Top" Width="75" Click="btnBack_Click" Margin="0,0,10,0" />
<Button x:Name="btnNext" Height="25" Content="Next ->" Visibility="{Binding Path=Result, Converter={StaticResource BoolToVis},
UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Top" Width="75" Click="btnNext_Click" Margin="10,0,0,0"/>
</StackPanel>
So they are bound to "Result" property, and I have UpdateSourceTrigger=Propertychangedin my binding.
In my "Properties" class I have the below and AM implementing INotifyPropertyChanged
bool _result;
#endregion
public bool Result {
get
{
return _result;
}
set
{
_result = value;
NotifyPropertyChanged("Result");
}
}
#region EVENTS
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
But for some reason when I change the property to "True" the PropertyChanged Event is null and therefore the event never fires.
Any idea as to why this is happening? Could it be because this code isn't in my ViewModel and just in a separate class?
Make sure that you have set the DataContext of the window to an instance of your Properties class and that you don't set the DataContext property of any parent element of the StackPanel to something else because the DataContext is inherited.
Please refer to the following sample code. The Buttons do become visible as expected after the 3 second delay:
public partial class MainWindow : Window
{
private Properties _viewModel = new Properties();
public MainWindow()
{
InitializeComponent();
DataContext = _viewModel;
this.Loaded += async (s, e) =>
{
await Task.Delay(3000);
_viewModel.Result = true;
};
}
}
public class Properties : INotifyPropertyChanged
{
bool _result;
public bool Result
{
get
{
return _result;
}
set
{
_result = value;
NotifyPropertyChanged("Result");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
<Window x:Class="WpfApplication1.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:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="300" Width="300">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<StackPanel.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVis" />
</StackPanel.Resources>
<Button x:Name="btnBack" Height="25" Content="<- Back" Visibility="{Binding Path=Result, Converter={StaticResource BoolToVis},
UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Top" Width="75" Margin="0,0,10,0" />
<Button x:Name="btnNext" Height="25" Content="Next ->" Visibility="{Binding Path=Result, Converter={StaticResource BoolToVis},
UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Top" Width="75" Margin="10,0,0,0"/>
</StackPanel>
</Window>
Below is a very simple Prism.Wpf example with a DelegateCommand that has both Execute and CanExecute delegates.
Suppose that CanExecute depends on some property. It seems that Prism's DelegateCommand doesn't re-evaluate CanExecute condition automatically when this property changes, as RelayCommand does in other MVVM frameworks. Instead, you have to call RaiseCanExecuteChanged() explicitly in the property setter. This leads to a lot of repetitive code in any non-trivial viewmodel.
Is there a better way?
ViewModel:
using System;
using Prism.Commands;
using Prism.Mvvm;
namespace PrismCanExecute.ViewModels
{
public class MainWindowViewModel : BindableBase
{
private string _title = "Prism Unity Application";
public string Title
{
get { return _title; }
set { SetProperty(ref _title, value); }
}
private string _name;
public string Name
{
get { return _name; }
set
{
SetProperty(ref _name, value);
// Prism doesn't track CanExecute condition changes?
// Have to call it explicitly to re-evaluate CanSubmit()
// Is there a better way?
SubmitCommand.RaiseCanExecuteChanged();
}
}
public MainWindowViewModel()
{
SubmitCommand = new DelegateCommand(Submit, CanSubmit);
}
public DelegateCommand SubmitCommand { get; private set; }
private bool CanSubmit()
{
return (!String.IsNullOrEmpty(Name));
}
private void Submit()
{
System.Windows.MessageBox.Show(Name);
}
}
}
View:
<Window x:Class="PrismCanExecute.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
Title="{Binding Title}"
Width="525"
Height="350"
prism:ViewModelLocator.AutoWireViewModel="True">
<Grid>
<!--<ContentControl prism:RegionManager.RegionName="ContentRegion" />-->
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Name: " />
<TextBox Width="150"
Margin="5"
Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Button Width="50"
Command="{Binding SubmitCommand}"
Content="Submit" Margin="10"/>
<!--<Button Width="50"
Content="Cancel"
IsCancel="True" Margin="10"/>-->
</StackPanel>
</StackPanel>
</Grid>
</Window>
As #l33t explained, this is by deign. If you want the DelegateCommand to monitor VM properties for changes automatically, just use the ObservesProperty method off of the delegateCommand:
var command = new DelegateCommand(Execute).ObservesProperty(()=> Name);
This is by design. It's performance related.
Though, you can replace the Prism DelegateCommand with a custom-made command that does what you want. E.g. this implementation seems to do the trick. However, I would not recommend using it. You will very likely run into performance problems if you have lots of commands.
Also, see this answer.
Note: I am using Visual Studio 2012
I have the following DataTemplate (note: there is a TextBlock and Button)
<DataTemplate DataType="{x:Type ctlDefs:myButton}">
<StackPanel>
<TextBlock Text="{Binding LabelText}" Margin="10,0,10,0"/>
<Button Content="{Binding LabelText}" Margin="0,0,10,0"
Width="{Binding ButtonWidth}"
Height="35"
Command="{Binding ButtonCommand}"
Visibility="Visible"
/>
</StackPanel>
</DataTemplate>
My screen is dynamically adding controls of myButton to an Items Control
<ItemsControl ItemsSource="{Binding CommandButtons, Mode=OneWay}"
FlowDirection="LeftToRight"
DockPanel.Dock="Right"
>
The first time I render the view, the Textblock shows up for each button, but the Button itself does not. If I hide the view and then show it again, the button will appear sometimes (if the CommandButtons list does not change, if it changes, it never shows).
After rendering the view, I looked in the Output window and no binding errors.
What am I missing.
I've knocked up a quick application to work with your sample code and didn't seem to have any issues with the view. Below is what it looks like when run.
I've included my code below in case it helps. Note: I didn't add a real ICommand object for brevity and I accidentally named the MyButton class with capital M instead of small m like your example
ViewModelBase.cs
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
this.OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, e);
}
}
}
public class MainWindowViewModel : ViewModelBase
{
public MainWindowViewModel()
{
this.CommandButtons = new ObservableCollection<MyButton>();
}
public ObservableCollection<MyButton> CommandButtons { get; private set; }
}
App.xaml.cs
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
var mainvm = new MainWindowViewModel();
var window = new MainWindow
{
DataContext = mainvm
};
window.Show();
mainvm.CommandButtons.Add(new MyButton { ButtonWidth = 50, LabelText = "Button 1" });
mainvm.CommandButtons.Add(new MyButton { ButtonWidth = 50, LabelText = "Button 2" });
mainvm.CommandButtons.Add(new MyButton { ButtonWidth = 50, LabelText = "Button 3" });
}
}
MainWindow.xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ctlDefs="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate DataType="{x:Type ctlDefs:MyButton}">
<StackPanel>
<TextBlock Text="{Binding LabelText}" Margin="10,0,10,0"/>
<Button Content="{Binding LabelText}" Margin="0,0,10,0"
Width="{Binding ButtonWidth}"
Height="35"
Command="{Binding ButtonCommand}"
Visibility="Visible"
/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<ItemsControl ItemsSource="{Binding CommandButtons, Mode=OneWay}"
FlowDirection="LeftToRight"
DockPanel.Dock="Right"
/>
</Grid>
MyButton.cs
public class MyButton : ViewModelBase
{
private string _labelText;
public string LabelText
{
get { return this._labelText; }
set { this._labelText = value; this.OnPropertyChanged("LabelText"); }
}
private double _buttonWidth;
public double ButtonWidth
{
get { return _buttonWidth; }
set { _buttonWidth = value; this.OnPropertyChanged("ButtonWidth"); }
}
private ICommand _buttonCommand;
public ICommand ButtonCommand
{
get { return _buttonCommand; }
set { _buttonCommand = value; this.OnPropertyChanged("ButtonCommand"); }
}
}
How do you make a Button call ICommand.CanExecute when the command parameter is changed?
This is my current XAML.
<Button Content="Delete" Command="{Binding DeleteItemCommand}" CommandParameter="{Binding SelectedItem, ElementName=DaGrid}" />
EDIT It appears this is only an issue in WPF.
I'm not sure what you're doing wrong, but here is an example of a Button being controlled both by a BindingParameter and a CanExecute Flag. Perhaps your binding parameter isn't a DependencyProperty, and therefore, when it changes the Button isn't being notified.
<UserControl x:Class="SilverlightICommandTest.MainPage"
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:ct="clr-namespace:SilverlightICommandTest"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<UserControl.Resources>
<ct:TestModel x:Key="Model" />
</UserControl.Resources>
<StackPanel x:Name="LayoutRoot" Orientation="Vertical" Background="White" DataContext="{StaticResource Model}">
<CheckBox Content="Enable" IsChecked="{Binding TestCmd.CanDoCommand, Mode=TwoWay}" />
<Grid HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding ElementName=testSlider, Path=Value}" Width="40" Grid.Column="0" />
<Slider Name="testSlider" Minimum="0" Maximum="100" SmallChange="1" Grid.Column="1" />
</Grid>
<Button Command="{Binding TestCmd}" CommandParameter="{Binding ElementName=testSlider, Path=Value}" Content="Do Something" />
</StackPanel>
</UserControl>
And the code file:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace SilverlightICommandTest
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
}
}
public class TestModel : DependencyObject
{
TestCommand _testCmd = new TestCommand();
public TestCommand TestCmd { get { return _testCmd; } }
public TestModel()
{
}
}
public class TestCommand : DependencyObject, ICommand
{
public static readonly DependencyProperty CanDoCommandProperty = DependencyProperty.Register("CanDoCommand", typeof(Boolean), typeof(TestCommand), new PropertyMetadata(false, new PropertyChangedCallback(CanDoCommandChanged)));
public Boolean CanDoCommand
{
get { return (Boolean)GetValue(CanDoCommandProperty); }
set { SetValue(CanDoCommandProperty, value); }
}
public event EventHandler CanExecuteChanged;
public TestCommand()
{
}
public Boolean CanExecute(Object parameter)
{
return this.CanDoCommand && (((Int32)(Double)parameter) % 2 == 0);
}
public void Execute(Object parameter)
{
MessageBox.Show("Oh Hai!");
}
private void OnCanDoCommandChanged(DependencyPropertyChangedEventArgs args)
{
if (this.CanExecuteChanged != null)
{
this.CanExecuteChanged(this, new EventArgs());
}
}
private static void CanDoCommandChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
((TestCommand)sender).OnCanDoCommandChanged(args);
}
}
}
In the future I recommend doing a little more research on the pattern first (http://www.silverlightshow.net/items/Model-View-ViewModel-in-Silverlight.aspx), and if you still can't figure it out, post more of your source code.
Strange. Normally OnCommandParameterChanged calls UpdateCanExecute (both internal methods). Does the Binding to CommandParameter work as expected?
You need to call CommandManager.InvalidateRequerySuggested to re-evaluate CanExecute. Note that it will re-evaluate it for all commands, not just the one your want...