WPF MVVM - Simple Binding Button with Textbox (Icommand) - c#

What I'm trying to do is: when the textbox contains the value "123" it should enable the button and allow me to click it.
Solution Image &
View Image
I can not find a way to trigger the Button Command (Class called SpecialCommand.cs) based on my Button parameters. Could you support where I'm getting this MVVM pattern wrong?
WPF View [MainWindow.xaml]:
<Window.Resources>
<ViewModel:MainWindowVM x:Key="WindowVm"></ViewModel:MainWindowVM>
</Window.Resources>
<Grid>
<StackPanel>
<TextBox x:Name="textBox" Margin="0, 5" Text="123"/>
<Button Content="Click me!" Margin="0, 5" Command="{Binding SpecialCommand, Source={StaticResource WindowVm}}" CommandParameter="{Binding Text, ElementName=textBox, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
</StackPanel>
</Grid>
ViewModel [MainWindowVM.cs]:
public class MainWindowVM
{
private SpecialCommand _specialCommand;
public SpecialCommand SpecialCommand { get => _specialCommand; set => _specialCommand = value; }
public MainWindowVM()
{
_specialCommand = new SpecialCommand();
}
}
Command [SpecialCommand.cs]
public class SpecialCommand : ICommand
{
public bool CanExecute(object parameter)
{
if (parameter != null && (parameter as string) == "123")
return true;
return false;
}
public void Execute(object parameter)
{
MessageBox.Show("Button Pressed!");
}
public event EventHandler CanExecuteChanged;
}
What I believe, maybe this is what I'm getting wrong is since Button & Textbox are in the View I don't need add/modify any method in my SpecialCommand implementation. They should be able to see when a property is changed.
Like the CanExecuteChanged() below, this command raises a lot of times and seems overkill for this small task.
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}

There is no need to make it more complicated than it needs to be.
public class MainWindowVM
{
private string m_OneTwoThree;
public string OneTwoThree{
get { return OneTwoThree; }
set {
if (m_OneTwoThree != value){
m_OneTwoThree = value;
NotifyPropertyChanged(nameof(OneTwoThree)); //if you need this let me know
}
}
}
public MainWindowVM()
{
}
public ICommand RandomCommand { get { return new RelayCommand(OnRandom, IsOneTwoThree); } }
private void OnRandom()
{
//do stuff
}
private bool IsOneTwoThree(){
if (OneTwoThree == "123"){
return true;
}
return false;
}
}
You'll also have to update your xaml I don't think it'll be able to find 'OneTwoThree' so you'll have to bind it yourself, however you usually would.
<StackPanel>
<TextBox x:Name="textBox" Margin="0, 5" Text="{Binding OneTwoThree}"/>
<Button Content="Click me!" Margin="0, 5" Command="{Binding RandomCommand, Source={StaticResource WindowVm}}"/>
</StackPanel>
If you have any questions just ask.
This is my RelayCommand : Use RelayCommand("thing to execute", "if function returns true you can execute");
public class RelayCommand : ICommand
{
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
private Action methodToExecute;
private Func<bool> canExecuteEvaluator;
public RelayCommand(Action methodToExecute, Func<bool> canExecuteEvaluator)
{
this.methodToExecute = methodToExecute;
this.canExecuteEvaluator = canExecuteEvaluator;
}
public RelayCommand(Action methodToExecute)
: this(methodToExecute, null)
{
}
public bool CanExecute(object parameter)
{
if (this.canExecuteEvaluator == null) {
return true;
}
else {
bool result = this.canExecuteEvaluator.Invoke();
return result;
}
}
public void Execute(object parameter)
{
this.methodToExecute.Invoke();
}
}

You need a way to tell the command that it should check whether it CanExecute. This is done by triggering the event CanExecuteChanged. It tells the button to requery the CanExecute property.
For this to work, I would add a text property to your view model and bind the textbox to it.
In SpecialCommand add the method:
public void TriggerCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
In the class MainWindowVM add the property:
private string _text;
public string Text
{
get { return _text; }
set {
if (value != _text) {
_text = value;
_specialCommand.TriggerCanExecuteChanged();
}
}
}
View Model to implementing INotifyPropertyChanged (see comments):
public class MainWindowVM : INotifyPropertyChanged
{
public SpecialCommand SpecialCommand { get; set; } = new SpecialCommand();
private string _text;
public string Text
{
get { return _text; }
set {
if (value != _text) {
_text = value;
OnPropertyChanged(nameof(Text));
SpecialCommand.TriggerCanExecuteChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}

Related

Command CanExecute not changing

This example isn't supposed to make sense, i'm just practicing.
I have this command:
public class NewCommand : ICommand
{
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return !((string)parameter == "sample");
}
public void Execute(object parameter)
{
MessageBox.Show("The New command was invoked");
}
}
and it works great (pressing Button opens message box), except changing string in TextBox does nothing. When Button should be blocked, it isn't.
This is my XAML and ViewModel:
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBox Text="{Binding TextB}"></TextBox>
<Button Command="{Binding Path=NewCommand}" FontSize="128">New</Button>
</StackPanel>
public class ViewModel : INotifyPropertyChanged
{
private string _textB;
public ICommand NewCommand { get; set; }
public string TextB
{
get => _textB;
set
{
if (_textB == value) return;
_textB = value;
OnPropertyChanged(nameof(TextB));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string memberName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(memberName));
}
}
CanExecute always return true, because parameter is null, and doesn't match "sample". Bind CommandParameter:
<TextBox Text="{Binding TextB}"/>
<Button Command="{Binding Path=NewCommand, UpdateSourceTrigger=PropertyChanged}"
CommandParameter="{Binding Path=TextB}"
FontSize="128" Content="New" />

"Long" delay causes my view's button not to re-enable

I have a listview and a button in each column. When the user clicks a button, it triggers an asynchronous action in the viewmodel where I disable all buttons and do a big action. Once the action is completed, I re-enable them.
If the action takes too long, though, the buttons don't automatically get re-enabled, even though I'm setting the bound property to true and am notifying the view. If the user does ANY GUI action after the action is complete, the buttons will re-enable.
The other weird thing: If I do an await Task.Delay instead of doing Thread.Sleep (NB: I'm doing real work in the full application), it works correctly.
What's going on here?
I've simplified the code here by eliminating the model (all logic lives in the VM).
View-model code:
namespace WpfTestApp
{
public class viewmodel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public RelayAsyncCommand<object> RunCommand { get; private set; }
private ObservableCollection<subVM> _subVMs;
public ObservableCollection<subVM> SubVMs
{
get => _subVMs; set
{
_subVMs = value;
NotifyPropertyChanged();
}
}
public viewmodel()
{
RunCommand = new RelayAsyncCommand<object>(OnRun);
SubVMs = new ObservableCollection<subVM>
{
new subVM("ItemA"),
new subVM("ItemB"),
};
}
private async void OnRun(object o)
{
subVM vm = o as subVM;
if (vm != null)
{
ChangeRunMode(false);
Thread.Sleep(500);
}
ChangeRunMode(true);
}
private void ChangeRunMode(bool on)
{
foreach (subVM vm in SubVMs)
{
vm.ButtonEnabled = on;
}
}
}
public class subVM : INotifyPropertyChanged
{
private string name = "";
public string Name
{
get => name;
set
{
if (value != name)
{
name = value;
}
}
}
public subVM(string name)
{
Name = name;
}
private bool tsk = true;
public bool ButtonEnabled
{
get => tsk;
set
{
if (tsk != value)
{
tsk = value;
NotifyPropertyChanged("ButtonEnabled");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
View XAML:
<Window x:Class="WpfTestApp.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:WpfTestApp"
mc:Ignorable="d"
Title="MainWindow" Height="130" Width="350">
<Window.DataContext>
<local:viewmodel/>
</Window.DataContext>
<ListView Margin="5"
BorderBrush="DarkSlateGray" BorderThickness="1"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ItemsSource="{Binding SubVMs}">
<ListView.View>
<GridView>
<GridViewColumn Header="Name"
Width="200" DisplayMemberBinding ="{Binding Name}"/>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<Button
Content="Load"
IsEnabled="{Binding ButtonEnabled, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
Margin="0"
VerticalAlignment="Center"
Command="{Binding Path=DataContext.RunCommand, IsAsync=True, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}}"
CommandParameter="{Binding}"
/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</Window>
Things I've tried:
I tried await Task.Run on my actual problem, and it still happens.
RelayCommand and the Async version (I thought these were standard boilerplate, but here you go):
public class RelayAsyncCommand<T> : RelayCommand<T>
{
private bool isExecuting = false;
public event EventHandler Started;
public event EventHandler Ended;
public bool IsExecuting
{
get { return this.isExecuting; }
}
public RelayAsyncCommand(Action<T> execute, Predicate<T> canExecute)
: base(execute, canExecute)
{
}
public RelayAsyncCommand(Action<T> execute)
: base(execute)
{
}
public override Boolean CanExecute(Object parameter)
{
return ((base.CanExecute(parameter)) && (!this.isExecuting));
}
public override void Execute(object parameter)
{
try
{
this.isExecuting = true;
if (this.Started != null)
{
this.Started(this, EventArgs.Empty);
}
Task task = Task.Factory.StartNew(() =>
{
this._execute((T)parameter);
});
task.ContinueWith(t =>
{
this.OnRunWorkerCompleted(EventArgs.Empty);
}, TaskScheduler.FromCurrentSynchronizationContext());
}
catch (Exception ex)
{
this.OnRunWorkerCompleted(new RunWorkerCompletedEventArgs(null, ex, true));
}
}
private void OnRunWorkerCompleted(EventArgs e)
{
this.isExecuting = false;
if (this.Ended != null)
{
this.Ended(this, e);
}
}
}
public class RelayCommand<T> : ICommand
{
#region Fields
readonly protected Action<T> _execute;
readonly protected Predicate<T> _canExecute;
#endregion // Fields
#region Constructors
public RelayCommand(Action<T> execute)
: this(execute, null)
{
}
public RelayCommand(Action<T> execute, Predicate<T> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#endregion // Constructors
#region ICommand Members
[DebuggerStepThrough]
public virtual bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute((T)parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public virtual void Execute(object parameter)
{
_execute((T)parameter);
}
#endregion // ICommand Members
}
inside the OnRun method you are actually locking the UI thread, preventing any UI update/refresh; you should await for the long operation (and that's exactly what you noticed doing await Task.Delay):
private async void OnRun(object o)
{
subVM vm = o as subVM;
if (vm != null)
{
ChangeRunMode(false);
await Task.Run(() =>
{
//put here your long operation as per your example
for (int i = 0; i < 500; i++)
{
for (int k = 0; k < 100000; k++) ;
}
});
}
ChangeRunMode(true);
}
indeed if you take a closer look to your window, you'll see that currently everything is freezed during the long operation execution, not just only the buttons being disabled.
I figured out my problem:
The button deactivating isn't coming from my binding when I'm running asynchronously. It's coming from the RelayAsyncCommand's CanExecute. CanExecute here returns false while the task is running, but we don't trigger a requery ever when it's done.
Easily fixed by adding a private set to the IsExecuting property, which calls the invalidate/requery function on change (just like the standard notifypropertychanged pattern). For posterity, here's the full fixed RelayAsyncCommand:
public class RelayAsyncCommand<T> : RelayCommand<T>
{
private bool _isExecuting = false;
public event EventHandler Started;
public event EventHandler Ended;
public bool IsExecuting
{
get { return _isExecuting; }
private set
{
if (value != _isExecuting)
{
_isExecuting = value;
CommandManager.InvalidateRequerySuggested();
}
}
}
public RelayAsyncCommand(Action<T> execute, Predicate<T> canExecute)
: base(execute, canExecute)
{
}
public RelayAsyncCommand(Action<T> execute)
: base(execute)
{
}
public override bool CanExecute(object parameter)
{
return ((base.CanExecute(parameter)) && (!IsExecuting));
}
public override void Execute(object parameter)
{
try
{
IsExecuting = true;
Started?.Invoke(this, EventArgs.Empty);
Task task = Task.Factory.StartNew(() =>
{
_execute((T)parameter);
});
task.ContinueWith(t =>
{
OnRunWorkerCompleted(EventArgs.Empty);
}, TaskScheduler.FromCurrentSynchronizationContext());
}
catch (Exception ex)
{
OnRunWorkerCompleted(new RunWorkerCompletedEventArgs(null, ex, true));
}
}
private void OnRunWorkerCompleted(EventArgs e)
{
IsExecuting = false;
Ended?.Invoke(this, e);
}
}
Thanks Peter for making me actually look at (what I thought was) boilerplate code.

How to bind with Mode=OneWay and only propagate changes back on save?

Question
How can I make it so that changes to a note are only propagated back to the list, when the Save button is clicked instead on "lost focus"?
And the Save button should only be enabled when the note has been changed.
UI
The example application looks like this:
The current behaviour is:
Clicking on a note puts its text into the TextBox; that's fine.
The changed text from the TextBox gets written back to the list when the TextBox loses the focus (default binding behaviour); but I only want that to happend when the Save button is clicked.
The Save button is always activated because the CanExecute(object parameter) isn't correctly implemented yet; it should only get activated when the TextBox text is different from the selected note's text.
My research so far
Option 1: Some Internet sources say to bind a different property to the TextBox and to programmatically check whether it is different from the SelectedItem of the ListView. I would have hoped that there was a way without introducing a third property in addition to the already existing ListOfNotes and SelectedNote.
Option 2: Some Internet sources recommend to configure Mode=OneWay so that clicking an item in the ListView updates the TextBox, but not the other way around. This sounds like the solution I would prefer, but I wasn't able to figure out from the code examples how to raise an event programmatically so that the change in the TextBox gets written back to the ListView when the Save button is clicked.
I've found other Stackoverflow questions that seem to be similar to mine, but the answers to those haven't helped me fix the problem:
WPF databinding after Save button click
Code
This example currently does two-way binding on focus lost. How do I need to change it to get the above described behaviour?
https://github.com/lernkurve/WpfBindingOneWayWithSaveButton
MainWindow.xaml
<Window x:Class="WpfBindingOneWayWithSaveButton.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:wpfBindingOneWayWithSaveButton="clr-namespace:WpfBindingOneWayWithSaveButton"
mc:Ignorable="d"
Title="MainWindow" Height="188.636" Width="299.242">
<Window.DataContext>
<wpfBindingOneWayWithSaveButton:MainWindowsViewModel />
</Window.DataContext>
<Grid>
<GroupBox Header="List of notes" HorizontalAlignment="Left" VerticalAlignment="Top" Height="112" Width="129" Margin="0,24,0,0">
<ListView ItemsSource="{Binding ListOfNotes}" SelectedItem="{Binding SelectedNote}" DisplayMemberPath="Text" HorizontalAlignment="Left" Height="79" VerticalAlignment="Top" Width="119" Margin="0,10,-2,0"/>
</GroupBox>
<GroupBox Header="Change selected note" HorizontalAlignment="Left" Margin="134,24,0,0" VerticalAlignment="Top" Height="112" Width="151">
<Grid HorizontalAlignment="Left" Height="89" Margin="0,0,-2,0" VerticalAlignment="Top" Width="141">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="40*"/>
<ColumnDefinition Width="101*"/>
</Grid.ColumnDefinitions>
<TextBox Text="{Binding SelectedNote.Text}" HorizontalAlignment="Left" Height="23" TextWrapping="Wrap" VerticalAlignment="Top" Width="121" Margin="10,7,0,0" Grid.ColumnSpan="2"/>
<Button Command="{Binding SaveCommand}" Content="Save" HorizontalAlignment="Left" VerticalAlignment="Top" Width="121" Margin="10,35,0,0" Grid.ColumnSpan="2"/>
</Grid>
</GroupBox>
</Grid>
</Window>
MainWindowsViewModel.cs
using System.Collections.ObjectModel;
using System.Windows.Input;
namespace WpfBindingOneWayWithSaveButton
{
public class MainWindowsViewModel
{
public ObservableCollection<Note> ListOfNotes { get; set; }
public Note SelectedNote { get; set; }
public ICommand SaveCommand { get; set; }
public MainWindowsViewModel()
{
ListOfNotes = new ObservableCollection<Note>
{
new Note { Text = "Note 1" },
new Note { Text = "Note 2" }
};
SaveCommand = new SaveCommand(this);
}
}
}
SaveCommand.cs
using System;
using System.Windows.Input;
namespace WpfBindingOneWayWithSaveButton
{
public class SaveCommand : ICommand
{
private MainWindowsViewModel vm;
public SaveCommand(MainWindowsViewModel vm)
{
this.vm = vm;
}
public bool CanExecute(object parameter)
{
// What should go here?
return true;
// Pseudo code
// return (is the TextBox text different from the original note text)
}
public void Execute(object parameter)
{
// What should go here?
// Pseudo code
// Let WPF know that the TextBox text has changed
// Invoke the binding so it propagates the TextBox text back to the list
}
public event EventHandler CanExecuteChanged;
}
}
Note.cs
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace WpfBindingOneWayWithSaveButton
{
public class Note : INotifyPropertyChanged
{
private string text;
public string Text
{
get { return text; }
set
{
text = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Bind the text to the CommandParameter of the SaveButton so it gets passed to the Save method for updating.
<TextBox x:Name="NoteTextBox" Text="{Binding SelectedNote.Text, Mode=OneTime}" ../>
<Button Command="{Binding SaveCommand}"
CommandParameter="{Binding ElementName=NoteTextBox, Path=Text}",
Content="Save" />
and
public bool CanExecute(object parameter)
{
return vm.SelectedNote.Text != parameter as string;
}
public void Execute(object parameter)
{
vm.SelectedNote.Text = parameter as string;
}
Option one is the easiest to implement, you will need to clone the Note object and set it to a separate property.
in your xaml, change your list view to the following so it now binds the SelectedIndex instead of the SelectedItem.
<ListView ItemsSource="{Binding ListOfNotes}" SelectedIndex="{Binding SelectedIndex}" DisplayMemberPath="Text" ...
And change TextBox to the following so it updates the binding as you type
<TextBox Text="{Binding Path=SelectedNote.Text, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left" ...
In Note.cs we add the Clone() method.
public class Note : INotifyPropertyChanged
{
public Note Clone()
{
return new Note()
{
Text = this.Text
};
}
//... The rest stays the same
}
In MainWindowsViewModel.cs we add new properties for the SelectedIndex and clone the object when we detect a index has changed. We also need to add INotifyPropertyChanged so we can update the SelectedNote from the codebehind when we do the Clone()
public class MainWindowsViewModel : INotifyPropertyChanged
{
private int _selectedIndex = -1;
private Note _selectedNote;
public int SelectedIndex
{
get { return _selectedIndex; }
set
{
if (_selectedIndex.Equals(value))
return;
_selectedIndex = value;
CloneSelectedNote();
}
}
private void CloneSelectedNote()
{
if (SelectedIndex >= 0)
{
SelectedNote = ListOfNotes[SelectedIndex].Clone();
}
else
{
SelectedNote = null;
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public Note SelectedNote
{
get { return _selectedNote; }
set
{
if(Equals(_selectedNote, value))
return;
_selectedNote = value;
OnPropertyChanged();
}
}
//... The rest stays the same
}
In SaveCommand.cs we add the logic for CanExecute and add the subscriptions to CommandManager.RequerySuggested, this automatically makes it requery the CanExecute any time any binding changes. This can be a little ineffecent, if you wanted to you could expose a RaiseCanExecuteChanged() publicly but it would be MainWindowsViewModel responsibility to call it any time vm.SelectedIndex or vm.SelectedNote.Text changed.
public class SaveCommand : ICommand
{
private MainWindowsViewModel vm;
public SaveCommand(MainWindowsViewModel vm)
{
this.vm = vm;
}
public bool CanExecute(object parameter)
{
if (vm.SelectedIndex < 0 || vm.SelectedNote == null)
return false;
return vm.ListOfNotes[vm.SelectedIndex].Text != vm.SelectedNote.Text;
}
public void Execute(object parameter)
{
vm.ListOfNotes[vm.SelectedIndex] = vm.SelectedNote;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
}
UPDATE: Here is a updated version that does not use CommandManager
MainWindowsViewModel.cs
public class MainWindowsViewModel : INotifyPropertyChanged
{
private int _selectedIndex = -1;
private Note _selectedNote;
public int SelectedIndex
{
get { return _selectedIndex; }
set
{
if (_selectedIndex.Equals(value))
return;
_selectedIndex = value;
CloneSelectedNote();
RecheckSaveCommand();
}
}
private void CloneSelectedNote()
{
if (SelectedIndex >= 0)
{
SelectedNote = ListOfNotes[SelectedIndex].Clone();
}
else
{
SelectedNote = null;
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public Note SelectedNote
{
get { return _selectedNote; }
set
{
if(Equals(_selectedNote, value))
return;
if (_selectedNote != null)
{
PropertyChangedEventManager.RemoveHandler(_selectedNote, SelectedNoteTextChanged, nameof(Note.Text));
}
_selectedNote = value;
if (_selectedNote != null)
{
PropertyChangedEventManager.AddHandler(_selectedNote, SelectedNoteTextChanged, nameof(Note.Text));
}
OnPropertyChanged();
}
}
private void SelectedNoteTextChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
{
RecheckSaveCommand();
}
private void RecheckSaveCommand()
{
var command = this.SaveCommand as WpfBindingOneWayWithSaveButton.SaveCommand; //"this." and "WpfBindingOneWayWithSaveButton." are not necessary but I wanted to be explicit.
if (command != null)
{
command.RaiseCanExecuteChanged();
}
}
//...
}
SaveCommand.cs
public class SaveCommand : ICommand
{
private MainWindowsViewModel vm;
public SaveCommand(MainWindowsViewModel vm)
{
this.vm = vm;
}
public bool CanExecute(object parameter)
{
if (vm.SelectedIndex < 0 || vm.SelectedNote == null)
return false;
return vm.ListOfNotes[vm.SelectedIndex].Text != vm.SelectedNote.Text;
}
public void Execute(object parameter)
{
vm.ListOfNotes[vm.SelectedIndex] = vm.SelectedNote;
}
public event EventHandler CanExecuteChanged;
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
You should not use OneWay but rather an UpdateSourceTrigger of value Explicit. BindingGroups can do this for you though, here's a simple example:
<!-- For change observation -->
<TextBlock Text="{Binding Text}"></TextBlock>
<StackPanel>
<StackPanel.BindingGroup>
<BindingGroup x:Name="EditGroup"></BindingGroup>
</StackPanel.BindingGroup>
<TextBox Text="{Binding Text}"></TextBox>
<Button>
<Button.Command>
<local:CommitGroupCommand BindingGroup="{x:Reference EditGroup}"/>
</Button.Command>
Save
</Button>
</StackPanel>
public class CommitGroupCommand : ICommand
{
public BindingGroup BindingGroup { get; set; }
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
BindingGroup.UpdateSources();
}
}
(You could add a validation rule to your binding that requires the value to be different and use that for the CanExecute implementation.)
Using this method allows you to bind directly to the object you intend to edit, so you don't need to copy around values first.

Binding ICommand to button?

I am new to the MVVM pattern and things are coming to me ever so slowly, I want to be able to click a button on my form and then it dynamically create a textbox at runtime. I have a 'Add Title' and also 'Add Question' which both add textboxes but at different locations, you can add as many questions under one title. I have Created a class called Standard in this class it holds:
public class Standard
{
string _title;
ObservableCollection<string> _questions;
public event PropertyChangedEventHandler PropertyChanged;
#region NofiftyPropChnage
protected void NotifyOfPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
protected void NotifyOfPropertyChanged<TProperty>(Expression<Func<TProperty>> property)
{
NotifyOfPropertyChanged(property.GetMemberInfo().Name);
}
#endregion
#region Properties
public string Title
{
get { return _title; }
set
{
_title = value;
NotifyOfPropertyChanged(() => Title);
}
}
public ObservableCollection<string> Questions
{
get { return _questions; }
set
{
_questions = value;
NotifyOfPropertyChanged(() => Questions);
}
}
#endregion
}
This class holds a Title property and also a list of Questions property because you can add Questions under a Title.
I also have a ViewModel class which holds:
class ViewModel :INotifyPropertyChanged
{
#region NotifyPropertyChange
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyOfPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
protected void NotifyOfPropertyChanged<TProperty>(Expression<Func<TProperty>> property)
{
NotifyOfPropertyChanged(property.GetMemberInfo().Name);
}
#endregion
private ObservableCollection<Standard> _standardCollection;
public ObservableCollection<Standard> StandardCollection
{
get
{
return _standardCollection;
}
set
{
_standardCollection = value;
NotifyOfPropertyChanged(() => StandardCollection);
}
}
}
This class holds a list of standards, a standard is when you click save with the text boxes and information in the text boxes done. It saves as a Standard
Finally my XAML code:
<Grid>
<button Content="Add Title"/>
<button Content="Add Question"/>
<StackPanel>
<ItemsControl ItemsSource="{Binding StandardCollection}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:Standard}">
<Grid>
<TextBox Text="{Binding Title}"/>
<ItemsControl ItemsSource="{Binding Questions}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Questions}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
Everything runs and there are no errors but when I click 'Add Title' or 'Add Question' no textbox appears, any help?
Ok, I'll have another shot at this one. I've stripped out the Title part and just concentrated on the Questions in order to keep this as a minimal example. First you'll need a base class that implements INotifyPropertyChanged for your view models:
public abstract class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged<T>(Expression<Func<T>> propertyExpresion)
{
var property = (MemberExpression)propertyExpresion.Body;
this.OnPropertyChanged(property.Member.Name);
}
protected void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Next you'll need a class that implements ICommand for your buttons to bind to which causes handlers to get called when those buttons are pressed:
// by Josh Smith, http://msdn.microsoft.com/en-us/magazine/dd419663.aspx
public class RelayCommand : ICommand
{
#region Fields
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
#endregion // Fields
#region Constructors
public RelayCommand(Action<object> execute)
: this(execute, null)
{
}
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#endregion // Constructors
#region ICommand Members
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
_execute(parameter);
}
#endregion // ICommand Members
}
Those two classes were written by others, if you add MVVM Lite project to your project you'll get them provided for you.
Next we need to create a view model with an ObservableCollection of Questions and a handler that gets called when the user presses the button:
public class MyViewModel : ObservableObject
{
public ICommand AddQuestionCommand {get; private set;}
ObservableCollection<string> _questions = new ObservableCollection<string>();
public ObservableCollection<string> Questions
{
get { return _questions; }
set
{
_questions = value;
OnPropertyChanged(() => Questions);
}
}
public MyViewModel()
{
this.AddQuestionCommand = new RelayCommand(new Action<object>((o) => OnAddQuestion()));
}
private void OnAddQuestion()
{
this.Questions.Add("new item");
}
}
Obviously you'll need to create an instance of this and set it as your window's DataContext. When the command gets triggerd the handler gets called and it in turn adds a new string to the collection. The XAML now needs to bind a button to that command and use the Questions collection to create a list of TextBlocks that display them all:
<StackPanel>
<Button Content="Add Question" Command="{Binding AddQuestionCommand}" HorizontalAlignment="Left"/>
<ItemsControl ItemsSource="{Binding Questions}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding .}" Width="200" HorizontalAlignment="Left"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
Hopefully this should give you a starting point. If I've missed something or you need clarification on anything then pls post a follow-up and I'll do my best.
Standard needs to implement the INotifyPropertyChanged interface. Generally you shouldn't do this more than once though, just declare one base class that implements that stuff and inherit all your view models from that. Also if you use package manager to add MVVM Lite to your project then you'll get a lot of this stuff provided for you.
I have no idea why these other guys are banging on about the INotifyPropertyChanged interface, as that has so very little to do with ICommand, although it does appear that you have tried to use it without adding it to the Standard class definition.
Either way, it sounds to me like you need to use the RelayCommand, or similar. This is a class that extends the ICommand interface... you can think of it as a delegate command. Instead of defining a separate class for each command, you can simply define the command logic and the canExecute handler inline. Here is a simplified example:
public ICommand SaveCommand
{
get { return new RelayCommand(execute => Save(), canExecute => CanSave()); }
}
...
<Button Content="Save" Command="{Binding SaveCommand}" />
You can find an implementation of it in the RelayCommand.cs page on GitHub and a description of it in the Commands, RelayCommands and EventToCommand page on MDSN Magazine.
You will need to change your code heavily to make it work. Do the following:
Step 1. Add Class RelayCommand:
public class RelayCommand : ICommand
{
public Func<bool> CanExecute { get; set; }
public Action Execute { get; set; }
public RelayCommand()
{
}
public RelayCommand(Action execute)
{
Execute = execute;
}
#region ICommand Members
bool ICommand.CanExecute(object parameter)
{
if (this.CanExecute == null)
{
return true;
}
else
{
return this.CanExecute();
}
}
event EventHandler ICommand.CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
void ICommand.Execute(object parameter)
{
this.Execute();
}
#endregion
}
Step 2. Add Commands in ViewModel
public ICommand AddTitle { get; private set; }
public ICommand AddQuestion { get; private set; }
public ViewModel()
{
_standardCollection = new ObservableCollection<Standard>();
AddTitle = new RelayCommand(OnAddTitle);
AddQuestion = new RelayCommand(OnAddQuestion);
}
void OnAddTitle()
{
_standardCollection.Add(new Standard());
}
void OnAddQuestion()
{
_standardCollection.Last().Questions.Add(new Question("Some Question"));
}
Step 3. Bind buttons
<Button Content="Add Title" Command="{Binding AddTitle}"/>
<Button Content="Add Question" Command="{Binding AddQuestion}"/>
You will also have to fix you layount in XAML.
Since the user can change the question text, you should create a separate class Question.
Try implementing INotifyPropertyChanged on class Standard.
public class Standard : INotifyPropertyChanged
{
string _title;
ObservableCollection<string> _questions;
public event PropertyChangedEventHandler PropertyChanged;
#region NofiftyPropChnage
protected void NotifyOfPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
protected void NotifyOfPropertyChanged<TProperty>(Expression<Func<TProperty>> property)
{
NotifyOfPropertyChanged(property.GetMemberInfo().Name);
}
#endregion
#region Properties
public string Title
{
get { return _title; }
set
{
_title = value;
NotifyOfPropertyChanged(() => Title);
}
}
public ObservableCollection<string> Questions
{
get { return _questions; }
set
{
_questions = value;
NotifyOfPropertyChanged(() => Questions);
}
}
#endregion
}

How do I pass a variable as a CommandParameter

I'm trying to send a variable from the ViewModel as a parameter to a command. The command looks like this:
public class EditPersonCommand : ICommand
{
private bool _CanExecute = false;
public bool CanExecute(object parameter)
{
PersonModel p = parameter as PersonModel;
CanExecuteProperty = (p != null) && (p.Age > 0);
return CanExecuteProperty;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter) { }
private bool CanExecuteProperty
{
get { return _CanExecute; }
set
{
if (_CanExecute != value)
{
_CanExecute = value;
EventHandler can_execute = CanExecuteChanged;
if (can_execute != null)
{
can_execute.Invoke(this, EventArgs.Empty);
}
}
}
}
}
The ViewModel looks like this:
public class PersonViewModel : ViewModelBase
{
private PersonModel _PersonModel;
private EditPersonCommand _EditPersonCommand;
///<remarks>
/// must use the parameterless constructor to satisfy <Window.Resources>
///</remarks>
public PersonViewModel()
: this(new PersonModel())
{
}
public PersonViewModel(PersonModel personModel)
{
_PersonModel = personModel;
}
public ICommand EditPersonCommand
{
get
{
if (_EditPersonCommand == null)
{
_EditPersonCommand = new EditPersonCommand();
}
return _EditPersonCommand;
}
}
}
The xaml looks like this:
<Button Content="Edit" HorizontalAlignment="Right" Height="20" Width="80"
Command="{Binding EditPersonCommand}"
CommandParameter="{Binding _PersonModel}" />
I've tried creating a property in the ViewModel instead of using the private local variable name, but that didnt work either. The object parameter always shows null in the call to CanExecute and the button is never enabled. If I change the CommandParameter value to Hello, then I receive Hello in the call to CanExecute, so I'm not sure why the variable doesnt work. Any help would be appreciated.
Update: I've also tried making a public property to the model (which I dont really want to expose the model, but just tried it to see if it works, but it doesnt).
// Added this to the ViewModel
public PersonModel PersonModelProp
{
get
{
return _PersonModel;
}
set
{
_PersonModel = value;
OnPropertyChanged("PersonModelProp");
}
}
And changed the xaml to this:
<Button Content="Edit" HorizontalAlignment="Right" Height="20" Width="80"
Command="{Binding EditPersonCommand}"
CommandParameter="{Binding PersonModelProp}" />
But still no luck. The ViewModel does implement INotifyPropertyChanged
Is the CommandParameter always null or are you only checking the first time it is being executed?
It appears that the order in which you declare your properties matters in this case since setting the Command property causes the CanExecute to fire immediately before the CommandParameter has been set.
Try moving the CommandParameter property before the Command property:
<Button Content="Edit" HorizontalAlignment="Right" Height="20" Width="80"
CommandParameter="{Binding PersonModelProp}"
Command="{Binding EditPersonCommand}" />
Also, see here and here.
Edit
To ensure that your events are being raised properly you should raise the CanExecuteChanged event when the PersonModelProp value changes.
The Command:
public class EditPersonCommand : ICommand
{
public bool CanExecute(object parameter)
{
PersonModel p = parameter as PersonModel;
return p != null && p.Age > 0;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
//command implementation
}
public void RaiseCanExecuteChanged()
{
var handler = CanExecuteChanged;
if(handler != null)
{
handler(this, EventArgs.Empty);
}
}
}
And the view model:
public class PersonViewModel : ViewModelBase
{
private PersonModel _PersonModel;
private EditPersonCommand _EditPersonCommand;
///<remarks>
/// must use the parameterless constructor to satisfy <Window.Resources>
///</remarks>
public PersonViewModel()
: this(new PersonModel())
{
_EditPersonCommand = new EditPersonCommand();
}
public PersonViewModel(PersonModel personModel)
{
_PersonModel = personModel;
}
public ICommand EditPersonCommand
{
get
{
return _EditPersonCommand;
}
}
public PersonModel PersonModelProp
{
get
{
return _PersonModel;
}
set
{
_PersonModel = value;
OnPropertyChanged("PersonModelProp");
EditPersonCommand.RaiseCanExecuteChanged();
}
}
}
Two points to the answer:
First, as #akton mentioned, you can only bind to public properties. It doesn't have to be a DependencyProperty though.
Second, which took me some tome to figure out, is that you have to set the binding for the CommandParameter before the Command property. i.e.
<Button Content="Edit" HorizontalAlignment="Right" Height="20" Width="80"
CommandParameter="{Binding PersonModelProp}"
Command="{Binding EditPersonCommand}" />
Hope this helps :)
_PersonModel is private and so cannot be accessed. Create a public property that exposes it and bind to that in the CommandParameter. Remember to make the property a dependency property (technically not required but it helps) and the ViewModel should implement INotifyProperty changed and fire the PropertyChanged event so the binding is updated.
I think you have a problem in your EditPersonCommand (it not fired ok).I check it with relayCommand and it work!
This is the code:
ViewModel:
public class PersonViewModel : ViewModelBase
{
private PersonModel _PersonModel;
private ICommand _EditPersonCommand;
///<remarks>
/// must use the parameterless constructor to satisfy <Window.Resources>
///</remarks>
public PersonViewModel()
: this(new PersonModel())
{
}
public PersonViewModel(PersonModel personModel)
{
PersonModelProp = personModel;
}
public ICommand EditPersonCommand
{
get
{
if (_EditPersonCommand == null)
{
_EditPersonCommand = new RelayCommand(ExecuteEditPerson,CanExecuteEditPerson);
}
return _EditPersonCommand;
}
}
private bool CanExecuteEditPerson(object parameter)
{
PersonModel p = parameter as PersonModel;
return (p != null) && (p.Age > 0);
}
private void ExecuteEditPerson(object o)
{
}
public PersonModel PersonModelProp
{
get
{
return _PersonModel;
}
set
{
_PersonModel = value;
NotifyPropertyChanged("PersonModelProp");
}
}
}
And this RelayCommand (Fire events ok!)
public class RelayCommand : ICommand
{
#region Constants and Fields
private readonly Predicate<object> canExecute;
private readonly Action<object> execute;
#endregion
#region Constructors and Destructors
public RelayCommand(Action<object> execute)
: this(execute, null)
{
}
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
{
throw new ArgumentNullException("execute");
}
this.execute = execute;
this.canExecute = canExecute;
}
#endregion
#region Events
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
}
remove
{
CommandManager.RequerySuggested -= value;
}
}
#endregion
#region Implemented Interfaces
#region ICommand
[DebuggerStepThrough]
public bool CanExecute(object parameter)
{
return this.canExecute == null || this.canExecute(parameter);
}
public void Execute(object parameter)
{
this.execute(parameter);
}
#endregion
#endregion
}
Xmal:
<Button Content="Edit" HorizontalAlignment="Right" Height="20" Width="80"
CommandParameter="{Binding PersonModelProp}"
Command="{Binding EditPersonCommand}" />

Categories

Resources