I have a TextBox in a UserControl which is binded to a property in the MainWindow's ViewModel.
Now when I type something in the Textbox, it updates the property in the viewmodel but if I change Textbox's text in code behind, the viewmodel property isn't updating.
Actually the textbox is getting the value from FileDialog which is opened when I click the button, so Textbox is getting its text from code behind.
UserControl XAML:
<StackPanel Orientation="Horizontal" Grid.Row="1" HorizontalAlignment="Left">
<TextBox x:Name="TextBoxFileOrFolder" Text="{Binding FolderOrFileName}" Grid.Row="1" Width="200" Height="100" HorizontalAlignment="Left"></TextBox>
<Button x:Name="ButtonRun" Content="Run" Click="ButtonRun_OnClick" Width="200" Height="100" Margin="10"></Button>
</StackPanel>
UserControl code behind
private void ButtonRun_OnClick(object sender, RoutedEventArgs e)
{
TextBoxFileOrFolder.Text = "FileName" + new Random().Next();
}
ViewModel:
public class MainViewModel: INotifyPropertyChanged
{
public MainViewModel()
{ }
private string folderOrFileName;
public string FolderOrFileName
{
get { return folderOrFileName; }
set
{
if (folderOrFileName!=value)
{
folderOrFileName = value;
RaisePropertyChanged();
}
}
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Raises the property changed.
/// </summary>
/// <param name="propertyName">Name of the property.</param>
protected virtual void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
# endregion
}
but if I change Textbox's text in code behind, the viewmodel property isn't updating.
That’s because if you set the Text property of the text box in the code-behind, you are overwriting the binding. So while you update the view, the link to your view model is gone, so there is nothing that will update it. And also, when the view model update the values, the view also will not be updated.
To solve this, simply don’t set properties that have a binding in the code-behind.
Instead of handling the button event in the code-behind and updating the view, you should have your button command bind to your view model and update the FolderOrFileName in the view model.
If you binding to Text property you should set property in the ViewModel to change value of TextBox:
public partial class MainWindow : Window
{
private MainViewModel _vm;
public MainWindow()
{
InitializeComponent();
_vm = new MainViewModel();
DataContext = _vm;
}
private void ButtonRun_OnClick(object sender, RoutedEventArgs e)
{
_vm.FolderOrFileName = "FileName" + new Random().Next();
}
}
In your case you should use command to modify data.
1) You should create class which inherit from ICommand:
public class DelegateCommand : ICommand
{
private readonly Predicate<object> _canExecute;
private readonly Action<object> _execute;
public event EventHandler CanExecuteChanged;
public DelegateCommand(Action<object> execute)
: this(execute, null)
{
}
public DelegateCommand(Action<object> execute,
Predicate<object> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
if (_canExecute == null)
{
return true;
}
return _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute(parameter);
}
public void RaiseCanExecuteChanged()
{
if (CanExecuteChanged != null)
{
CanExecuteChanged(this, EventArgs.Empty);
}
}
}
2) Next you should create command in ViewModel:
public class MainViewModel : INotifyPropertyChanged
{
public MainViewModel()
{
ChangeFileName = new DelegateCommand(OnChangeFileName);
}
public ICommand ChangeFileName { get; private set; }
private void OnChangeFileName(object param)
{
FolderOrFileName = "FileName" + new Random().Next();
}
private string folderOrFileName;
...
3) And finally you should add binding to Button.Command property in View:
<Button x:Name="ButtonRun" Content="Run" Command="{Binding ChangeFileName}" Width="200" Height="100" Margin="10"></Button>
Make sure your binding is set up as "TwoWay" - UI -> VM and VM -> UI
<TextBox x:Name="TextBoxFileOrFolder" Text="{Binding FolderOrFileName, Mode=TwoWay}" Grid.Row="1" Width="200" Height="100" HorizontalAlignment="Left"></TextBox>
Related
In my WPF app I have a datagrid
<DataGrid SelectedItem="{Binding SelItm ,Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding Bills}"
SelectionMode="Extended"
Name="myGrid"...
Now I want to enable a button if any rows of the datagrid are selected, if no rows are selected the button should be disabled, pretty simple stuff.
My button xaml is like
<Button
Command="{Binding PreviewButtonClicked}"
CommandParameter="{Binding SelItm, ElementName=myGrid}"
I've create a standard RelayCommand class
public class RelayCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Predicate<object> _canExecute;
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;
}
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);
}
}
and my viewmodel class contains:
public class myVM()
{
private string _SelItm;
public string SelItm
{
get { return _SelItm; }
set
{
SetValue(ref _SelItm, value);
}
}
public RelayCommand PreviewButtonClicked { get; set; }
public myVM()
{
PreviewButtonClicked = new RelayCommand(ShowPDF, CanShowPDF);
}
public void ShowPDF(object param)
{
//do stuff
}
public bool CanShowPDF(object param)
{
if (SelItm.Any())
{
return true;
}
return false;
}
}
But when I run the app I get the below error on line if (SelItm.Any())
System.ArgumentNullException: Value cannot be null.
What am I doing incorrectly ?
There are at least three options.
Option 1 - Codebehind
Add an event handler to the SelectionChanged event on the DataGrid.
<DataGrid SelectedItem="{Binding SelItm ,Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding Bills}"
SelectionMode="Extended"
SelectionChanged="DataGridSelectionChanged"
Name="myGrid"...
The SelectionChanged event will be invoked when there's a full row selected. (You can set the SelectionUnit as "Rows" on the DataGrid explicitly) Otherwise use the SelectedCellsChanged event.
In the handler of the SelectionChanged event:
private void DataGridSelectionChanged(object sender, SelectionChangedEventArgs args)
{
myButton.IsEnabled = myGrid.SelectedItems.Count > 0;
}
Option 2
You can bind the SelectedItem property on the DataGrid, to your viewmodel.
<DataGrid SelectedItem="{Binding SelItm ,Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding Bills}"
SelectionMode="Extended"
SelectedItem="{Binding SelectedItemInDataGrid}"
Name="myGrid"...
In the viewmodel, simplified and also assuming your vm implements INotifyPropertyChanged:
private object _selectedItemInDg;
public object SelectedItemInDataGrid
{
set {
_selectedItemInDg = value;
OnPropertyChanged(nameof(SelectedItemInDataGrid ));
OnPropertyChanged(nameof(IsButtonEnabled));
PreviewButtonClicked?.UpdateCanExecute();
}
get => _selectedItemInDg;
}
public bool IsButtonEnabled => _selectedItemInDg != null;
private void OnPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
And your button:
<Button IsEnabled="{Binding IsButtonEnabled}" Command="..." CommandParameter="...">Content of button</Button>
Option 3 - In CanExecute
You've got a method in your viewmodel, CanShowPDF(object param), which determines for the RelayCommand wether the command can be executed or not. WPF enables/disables the button automatically based on the CanExecute method of an ICommand instance.
I'd add a method to the RelayCommand class:
public void UpdateCanExecute() {
CanExecuteChanged?.Invoke();
}
Call the UpdateCanExecute method on your RelayCommand instance every time something changes that would affect the result of CanShowPDF.
The exception
You ask about an exception you get:
System.ArgumentNullException: Value cannot be null.
Probably you are calling a method on something that is null at that moment.
But it's hard to figure out the cause with the given information in the question and I don't actually know what SelItm is, although seen the name we could guess.
I am very new to WPF and relatively new to C# programming (programming in general), and I'm trying to develop a WPF application.
I have tried to go through several posts similar to this, but I can't seem to find the answer of why this is not working.
So, I'm having a hard time understanding the MVVM architecture, how and what it requires to switch between multiple user controls binded to a single <ContentControl />.
From what I understand and read so far, is that I have to bind the view model like this:
<ContentControl Content="{Binding ApplicationViewModel}"/>
So here is what I want to a achieve:
An ApplicationWindow.xaml with sidebar menu on the left side that will be shown at all times when the application is running, and a <ContentControl/> on the remaining space. Buttons shown on the sidebar menu will be:
Main (will show MainView.xaml User Control, should be the default User Control)
Settings (will show SettingsView.xaml User Control)
Exit (will close the application)
I understand that I need to bind the buttons to ICommand commands, and I understand the concept of a RelayCommand.cs class.
So let's jump into the simplified code of my idea and figure out what I need to understand and what I may have misunderstood in the process.
What MainView.xaml and SettingsView.xaml contain are not important right now, as I'm just trying to figure out how to show them in my application.
Here's the ApplicationWindow.xaml:
<Window x:Class="WpfApp1.ApplicationWindow"
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:WpfApp1"
xmlns:v="clr-namespace:WpfApp1.View"
xmlns:vm="clr-namespace:WpfApp1.ViewModel"
mc:Ignorable="d"
Title="ApplicationWindow" Height="1080" Width="1920"
WindowStyle="None" WindowState="Maximized">
<Window.Resources>
<DataTemplate DataType="{x:Type vm:MainViewModel}">
<v:MainView/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:SettingsViewModel}">
<v:SettingsView/>
</DataTemplate>
</Window.Resources>
<DockPanel>
<!--Menu bar on the left-->
<Border DockPanel.Dock="Left">
<StackPanel Orientation="Vertical" Background="Gray" Width="120">
<Button Content="Main" Command="{Binding ShowMainCommand}"/>
<Button Content="Settings" Command="{Binding ShowSettingsCommand}"/>
<Button Content="Exit" Command="{Binding ExitApplicationCommand}"/>
</StackPanel>
</Border>
<!--The content control that view the current view-->
<ContentControl Content="{Binding ApplicationViewModel}"/>
</DockPanel>
</Window>
Note: DataContext is set to ApplicationViewModel.cs in App.xaml.cs by overriding the OnStartup() method.
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
ApplicationWindow app = new ApplicationWindow
{
DataContext = new ApplicationViewModel()
};
app.Show();
}
}
Here's the ApplicationViewModel.cs:
public class ApplicationViewModel : ViewModelBase
{
#region Fields
private List<ViewModelBase> _viewModels;
private ViewModelBase _currentViewModel;
private ICommand _showMainCommand;
private ICommand _showSettingsCommand;
private ICommand _exitApplicationCommmand;
#endregion
#region Constructor
public ApplicationViewModel()
{
ViewModels = new List<ViewModelBase>
{
new MainViewModel(),
new SettingsViewModel()
};
CurrentViewModel = ViewModels[0];
}
#endregion
#region Public Properties
public List<ViewModelBase> ViewModels
{
get
{
return _viewModels;
}
set
{
if (_viewModels != value)
{
_viewModels = value;
OnPropertyChanged(nameof(ViewModels));
}
}
}
public ViewModelBase CurrentViewModel
{
get
{
return _currentViewModel;
}
set
{
if(_currentViewModel != value)
{
_currentViewModel = value;
OnPropertyChanged(nameof(CurrentViewModel));
}
}
}
#endregion
#region Commands
public ICommand ShowMainCommand
{
get
{
if(_showMainCommand == null)
{
_showMainCommand = new RelayCommand(action => ShowMain());
}
return _showMainCommand;
}
}
public ICommand ShowSettingsCommand
{
get
{
if (_showSettingsCommand == null)
{
_showSettingsCommand = new RelayCommand(action => ShowSettings());
}
return _showSettingsCommand;
}
}
public ICommand ExitApplicationCommand
{
get
{
if (_exitApplicationCommmand == null)
{
_exitApplicationCommmand = new RelayCommand(action => ExitApplication());
}
return _exitApplicationCommmand;
}
}
#endregion
#region Private Methods
private void ShowMain()
{
CurrentViewModel = ViewModels[0];
}
private void ShowSettings()
{
CurrentViewModel = ViewModels[1];
}
private void ExitApplication()
{
MessageBoxResult result = MessageBox.Show("Are you sure you want to exit?", "Exit", MessageBoxButton.YesNo);
if (result == MessageBoxResult.Yes)
{
System.Windows.Application.Current.Shutdown();
}
}
#endregion
}
So, from what I understand, the ApplicationWindow.xaml should be able to determine which view to show out from what the CurrentViewModel is set to.
For the sake of information (or miss-information), here are ViewModelBase.cs:
public class ViewModelBase : INotifyPropertyChanged
{
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
And RelayCommand.cs:
public class RelayCommand : ICommand
{
#region Fields
private readonly Action<object> _execute;
private readonly Predicate<object> _canExecute;
#endregion
#region Constructors
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
public RelayCommand(Action<object> execute) : this(execute, null)
{
}
#endregion
#region ICommand
public bool CanExecute(object parameters)
{
return _canExecute == null ? true : _canExecute(parameters);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameters)
{
_execute(parameters);
}
#endregion
}
I hope my thought process on this was clear to you, and that one of you smart programmers out there can help solving this, and help me understand why this isn't turning out as I want it to.
In case of what I'm trying to do is harder than Elon Musk's project on making life multiplanetary, feel free to explain why and suggest me a better way to
Your Content control binding should be pointed at the actual property you change when switching ViewModels
<ContentControl Content="{Binding CurrentViewModel}"/>
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.
I've been stuck on this problem for a few hours. I am attempting to implement an MVVM-style Word Add-In in WPF. I am not using an MVVM toolkit. I have a WPF user control that is docked within a WinForm. While I am able to see the WPF user control within the win form and interact with it, my generic RelayCommand<T> that is bound to a WPF button won't execute when I click the button. The RelayCommand lives in ViewModel.cs and the DataContext for the view is set through the code-behind. I'm sure I'm doing something silly, but can't figure out what it is and therefore not sure why RelayCommand property's get{} won't get executed. Please see the code below. Thanks in advance for the help!
RelayCommand.cs (code snippet excludes namespace and includes statements)
/// <summary>
/// RelayCommand
/// </summary>
/// <typeparam name="T">Generic Parameter</typeparam>
public class RelayCommand<T> : ICommand where T : class
{
#region Constructors
/// <summary>
/// RelayCommand constructor
/// </summary>
/// <param name="exec">Delegate that encapsulates a method that takes in a single parameter and returns void</param>
/// <param name="canExec">Delegate that encapsulates a method that defines a set of criteria and returns a true if criteria is met; else false</param>
public RelayCommand(Action<T> execute, Predicate<T> canExecute = null)
{
if (execute == null)
throw new ArgumentNullException("execute is null");
_canExecute = canExecute;
_execute = execute;
}
#endregion
#region Members
/// <summary>
/// Execute method
/// </summary>
/// <param name="param">Parameter</param>
public void Execute(object param)
{
T obj = param as T;
if(obj != null)
{
_execute(obj);
}
}
/// <summary>
/// CanExec is a method that shows whether or not execution can happen
/// </summary>
/// <param name="param">Parameter</param>
/// <returns>true if can execute; else false</returns>
public bool CanExecute(object param)
{
if (_canExecute == null)
return true;
T obj = param as T;
return obj == null || _canExecute(obj);
}
/// <summary>
/// CanExec event changed
/// </summary>
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
#endregion
#region Fields
private readonly Predicate<T> _canExecute;
private readonly Action<T> _execute;
#endregion
}
SubmissionUserControl.xaml (only the pertinent snippet. excludes some code)
<Button Grid.Column="2" x:Name="SubmitButton" Command="{Binding Path=SubmitCommentCommand}"
Content="Submit" HorizontalAlignment="Right" Margin="5"/>
SubmissionUserControl.xaml.cs (contains snippet where I reference the ViewModel)
ViewModel viewModel;
public SubmissionUserControl()
{
InitializeComponent();
viewModel = new ViewModel();
DataContext = viewModel;
}
ViewModel.cs (excludes some code. only shows the pertinent RelayCommand)
/// <summary>
/// SubmitCommentCommand responsible for interacting with UI to submit a comment.
/// </summary>
/// <returns>Returns a RelayCommand that executes a method to Save comments from the comment box</returns>
public ICommand SubmitCommentCommand
{
get
{
return _submitCommentCommand ?? (_submitCommentCommand = new RelayCommand<object>(param => this.SaveComment()));
}
}
To give you a more detailed start into MVVM and RelayCommands:
You do not have to declare your ViewModel in Xaml, this is mostly done programmatically on application root level, maybe with some DI.
When sticking to this MSDN Article your RelayCommand should look like this:
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
[DebuggerStepThrough]
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
}
Additionally you can define a generic RelayCommand to handle Commandparameters like this:
public class GenericRelayCommand<T> : ICommand
{
private readonly Action<T> _execute;
public Predicate<T> CanExecuteFunc { get; private set; }
public GenericRelayCommand(Action<T> execute) : this(execute, p => true)
{}
public GenericRelayCommand(Action<T> execute, Predicate<T> canExecuteFunc)
{
_execute = execute;
CanExecuteFunc = canExecuteFunc;
}
public bool CanExecute(object parameter)
{
var canExecute = CanExecuteFunc((T)parameter);
return canExecute;
}
public void Execute(object parameter)
{
_execute((T)parameter);
}
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
}
remove
{
CommandManager.RequerySuggested -= value;
}
}
}
In your ViewModel the RelayCommands should be definied like this (I implemented INotifyPropertyChanged as well for further WPF Xaml Property handling example):
public class ViewModel : INotifyPropertyChanged
{
private string _comment;
public string Comment
{
get { return _comment; }
set { _comment = value; OnPropertyChanged("Comment"); }
}
public GenericRelayCommand<string> SubmitComment1Command { get; set; }
public RelayCommand SubmitComment2Command { get; set; }
public ViewModel()
{
Comment = "Hello World!";
SubmitComment1Command = new GenericRelayCommand<string>(OnSubmitComment1);
SubmitComment2Command = new RelayCommand(OnSubmitComment2);
}
private void OnSubmitComment1(string obj)
{
//Save Comment Mock with CommandParameter
MessageBox.Show(obj);
}
private void OnSubmitComment2(object obj)
{
//Save Comment Mock with Property
MessageBox.Show(Comment);
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
I put your Button Example into a fresh WPF Application like this:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Width="525"
Height="350">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0"
Orientation="Horizontal">
<TextBox Name="textBox"
Width="200"
Text="{Binding Comment,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}" />
<Button x:Name="SubmitButton1"
Grid.Column="0"
Margin="5"
HorizontalAlignment="Right"
Command="{Binding Path=SubmitComment1Command}"
CommandParameter="{Binding ElementName=textBox,
Path=Text}"
Content="Submit1" />
</StackPanel>
<Button x:Name="SubmitButton2"
Grid.Column="1"
Margin="5"
HorizontalAlignment="Right"
Command="{Binding Path=SubmitComment2Command}"
Content="Submit2" />
</Grid>
</Window>
And set the DataContext like this for simplicity reasons (As stated before, this is normally done through some kind of DI at root level):
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
Then everything should work fine.
I solved this issue by telling the Model about data context in XAML instead of .cs file.
First: Tell Model the namespace in which you placed your view model, mine was like below:
xmlns:Local="clr-namespace:MKPL.Views.A01.S020"
Second: Add your ViewModel in XAML Resources like:
<UserControl.Resources>
<Local:ViewModel x:Key="dvm"/>
</UserControl.Resources>
Third: Add DataContext to the parent container,in my case that is Grid.
<Grid x:Name="LayoutRoot" DataContext="{Binding Source={StaticResource dvm}}">
Fourth: In your button code add the data context like:
<Button Grid.Column="2" x:Name="SubmitButton" Command="{Binding Path=SubmitCommentCommand, Source={StaticResource dvm}}"
Content="Submit" HorizontalAlignment="Right" Margin="5"/>
Hope it will help you
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
}