Passing two command parameters using a WPF binding - wpf

I have a command which I am executing from my XAML file using the following standard syntax:
<Button Content="Zoom" Command="{Binding MyViewModel.ZoomCommand}"/>
This worked fine until I realized that I needed TWO pieces of information from the view in order to make this operation complete the way users expect (the width and height of the canvas specfically).
It seems like it's possible to pass an array as an argument to my command, but I don't see there being a way to specify the binding to my two canvas properties in the CommandParameter:
<Button Content="Zoom"
Command="{Binding MyViewModel.ZoomCommand"
CommandParameter="{Binding ElementName=MyCanvas, Path=Width}"/>
How do I pass both Width and Height to my command? It doesn't seem like this is possible using commands from XAML and I need to wire up a click handler in my codebehind to get this information to pass to my zoom method.

Firstly, if you're doing MVVM you would typically have this information available to your VM via separate properties bound from the view. That saves you having to pass any parameters at all to your commands.
However, you could also multi-bind and use a converter to create the parameters:
<Button Content="Zoom" Command="{Binding MyViewModel.ZoomCommand">
<Button.CommandParameter>
<MultiBinding Converter="{StaticResource YourConverter}">
<Binding Path="Width" ElementName="MyCanvas"/>
<Binding Path="Height" ElementName="MyCanvas"/>
</MultiBinding>
</Button.CommandParameter>
</Button>
In your converter:
public class YourConverter : IMultiValueConverter
{
public object Convert(object[] values, ...)
{
return values.Clone();
}
...
}
Then, in your command execution logic:
public void OnExecute(object parameter)
{
var values = (object[])parameter;
var width = (double)values[0];
var height = (double)values[1];
}

In the converter of the chosen solution, you should add values.Clone() otherwise the parameters in the command end null
public class YourConverter : IMultiValueConverter
{
public object Convert(object[] values, ...)
{
return values.Clone();
}
...
}

Use Tuple in Converter, and in OnExecute, cast the parameter object back to Tuple.
public class YourConverter : IMultiValueConverter
{
public object Convert(object[] values, ...)
{
Tuple<string, string> tuple = new Tuple<string, string>(
(string)values[0], (string)values[1]);
return (object)tuple;
}
}
// ...
public void OnExecute(object parameter)
{
var param = (Tuple<string, string>) parameter;
}

If your values are static, you can use x:Array:
<Button Command="{Binding MyCommand}">10
<Button.CommandParameter>
<x:Array Type="system:Object">
<system:String>Y</system:String>
<system:Double>10</system:Double>
</x:Array>
</Button.CommandParameter>
</Button>

About using Tuple in Converter, it would be better to use 'object' instead of 'string', so that it works for all types of objects without limitation of 'string' object.
public class YourConverter : IMultiValueConverter
{
public object Convert(object[] values, ...)
{
Tuple<object, object> tuple = new Tuple<object, object>(values[0], values[1]);
return tuple;
}
}
Then execution logic in Command could be like this
public void OnExecute(object parameter)
{
var param = (Tuple<object, object>) parameter;
// e.g. for two TextBox object
var txtZip = (System.Windows.Controls.TextBox)param.Item1;
var txtCity = (System.Windows.Controls.TextBox)param.Item2;
}
and multi-bind with converter to create the parameters (with two TextBox objects)
<Button Content="Zip/City paste" Command="{Binding PasteClick}" >
<Button.CommandParameter>
<MultiBinding Converter="{StaticResource YourConvert}">
<Binding ElementName="txtZip"/>
<Binding ElementName="txtCity"/>
</MultiBinding>
</Button.CommandParameter>
</Button>

This task can also be solved with a different approach. Instead of programming a converter and enlarging the code in the XAML, you can also aggregate the various parameters in the ViewModel. As a result, the ViewModel then has one more property that contains all parameters.
An example of my current application, which also let me deal with the topic.
A generic RelayCommand is required: https://stackoverflow.com/a/22286816/7678085
The ViewModelBase is extended here by a command SaveAndClose. The generic type is a named tuple that represents the various parameters.
public ICommand SaveAndCloseCommand => saveAndCloseCommand ??= new RelayCommand<(IBaseModel Item, Window Window)>
(execute =>
{
execute.Item.Save();
execute.Window?.Close(); // if NULL it isn't closed.
},
canExecute =>
{
return canExecute.Item?.IsItemValide ?? false;
});
private ICommand saveAndCloseCommand;
Then it contains a property according to the generic type:
public (IBaseModel Item, Window Window) SaveAndCloseParameter
{
get => saveAndCloseParameter ;
set
{
SetProperty(ref saveAndCloseParameter, value);
}
}
private (IBaseModel Item, Window Window) saveAndCloseParameter;
The XAML code of the view then looks like this:
(Pay attention to the classic click event)
<Button
Command="{Binding SaveAndCloseCommand}"
CommandParameter="{Binding SaveAndCloseParameter}"
Click="ButtonApply_Click"
Content="Apply"
Height="25" Width="100" />
<Button
Command="{Binding SaveAndCloseCommand}"
CommandParameter="{Binding SaveAndCloseParameter}"
Click="ButtonSave_Click"
Content="Save"
Height="25" Width="100" />
and in the code behind of the view, then evaluating the click events, which then set the parameter property.
private void ButtonApply_Click(object sender, RoutedEventArgs e)
{
computerViewModel.SaveAndCloseParameter = (computerViewModel.Computer, null);
}
private void ButtonSave_Click(object sender, RoutedEventArgs e)
{
computerViewModel.SaveAndCloseParameter = (computerViewModel.Computer, this);
}
Personally, I think that using the click events is not a break with the MVVM pattern. The program flow control is still located in the area of ​​the ViewModel.

Related

PasswordBox PasswordHelper multibinding

In WPF the Password property of a PasswordBox is not a DependencyProperty so I can't bind to it directly. As a workaround I am using this PasswordHelper from https://www.wpftutorial.net/PasswordBox.html which attaches a PasswordHelper.Password to the PasswordBox so I can bind to it.
To prevent the password from staying in the DataContext as plain text I would like to use a converter which generates a salted hash of the password before saving it to the DataContext. As I need to save both the salt and the salted hash to the DataContext I am using a MultiBinding and a IMultiValueConverter converter StringToSaltedHashConverter.
My problem is that the Password and PasswordSalt properties of my DataContext are not getting updated when I fill the PasswordBox. I have checked with Snoop and both the Password and PasswordHelper.Password properties of the PasswordBoxare changing accordingly to what I type. Also my StringToSaltedHashConverter is being called and I am returning the correct values.
I have some other bindings on this form (username, first name, last name, gender...) and they all work fine. This is the only one that is not updating.
¿Why is my DataContext not getting updated?
XAML:
<PasswordBox x:Name="Password"
Style="{DynamicResource PasswordBoxStyle1}"
local:PasswordHelper.Attach="True">
<local:PasswordHelper.Password>
<MultiBinding Converter="{StaticResource StringToSaltedHashConverter}"
Mode="OneWayToSource">
<Binding Path="Password" />
<Binding Path="PasswordSalt" />
</MultiBinding>
</local:PasswordHelper.Password>
</PasswordBox>
Code behind:
public class StringToSaltedHashConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
string str = value as string;
string salt = Hash.CreateSalt();
string hash = Hash.CreateHash(str, salt);
object[] vs = { hash, salt };
return vs;
}
}
This is not the way to go. PasswordHelper class and their likes should be banned from the internet. The PasswordBox.Password property is not bindable for a very good reason which is well documented. Accessing this property will create a plain text string representation which can be effordlessly retrieved with free tools e.g. Microsoft Process Explorer.
When you get the Password property value, you expose the password as plain text in memory. To avoid this potential security risk, use the SecurePassword property to get the password as a SecureString.
Setting this property to null causes the underlying password to be set to Empty.
You even store the plain salt value in memory - I really hope this is neither a commercial nor a public application. You have absolutely no control when the Garbage Collector will remove any value from the memory. Since String is an immutable type, it is very likely that multiple copies of the user's password will remain public in memory.
Recommended authentication in a Windows environment is to use the Windows User Authentication API.
You should at least clear the PasswordBox.Password property, by setting it null.
Encryption should not be done in the view.
And please, write responsible code! User data is not your data!
MainWindow.xaml
<Window>
<Window.DataContext>
<ViewModel />
</Window.DataContext>
<PasswordBox x:Name="PasswordBox" />
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.PasswordBox.PasswordChanged += OnPasswordChanged;
}
private void OnPasswordChanged(object sender, RoutedEventArgs e)
{
(this.DataContext as ViewModel).Password = this.PasswordBox.SecurePassword;
}
}
ViewModel.cs
pucblic class ViewModel
{
private Model Model { get; }
private SecureString password;
public SecureString Password
{
private get => this.password;
public set
{
this.password = value;
OnPasswordChanged();
}
}
private void OnPasswordChanged()
{
// Hash password in the model e.g. to compare it to a hashed database value
this.Model.TryLogin(this.Password);
}
}
Model.cs
public class Model
{
public bool TryLogin(SecureString password)
{
IntPtr unmanagedString = Marshal.SecureStringToGlobalAllocUnicode(password);
string hashedPassword = HashPassword(Marshal.PtrToStringUni(unmanagedString));
return PasswordIsValid(hashedPassword);
}
private string HashPassword(string unsecurePassword)
{
// Hash algorithm
}
}
Before I need the password from a PasswordBox the User will tell me that he has finished with input by pressing a button.
The best option for me is to use the CommandParameter of the Button with the LoginCommand:
<StackPanel
Width="300"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Label Content="Username:" />
<TextBox Text="{Binding Username}" />
<Label Content="Password:" />
<PasswordBox PasswordChanged="PasswordChanged" />
<StackPanel
Orientation="Horizontal">
<Button
Name="LoginButton"
Command="{Binding LoginCommand}"
Content="Login" />
</StackPanel>
</StackPanel>
View code
private void PasswordChanged( object sender, RoutedEventArgs e )
{
LoginButton.CommandParameter = ( sender as PasswordBox ).SecurePassword;
}
ViewModel Command
Login = ReactiveCommand.CreateFromTask( async ( SecureString password, CancellationToken cancellationToken ) =>
{
var loggedIn = await AuthenticationService.LoginAsync( Username, password, cancellationToken );
...
} );
I just wanted to thank everyone for your answers and insighful comments. You made me realize that I should not be using a PasswordHelper and that I should not be trying to bind to the password at all.
Just in case anyone is having a similar problem with a MultiBinding not properly updating the DataContext, this can be fixed by adding the OneWayToSource mode to each of the bindings inside the MultiBinding.
<MultiBinding Converter="{StaticResource StringToSaltedHashConverter}"
Mode="OneWayToSource">
<Binding Path="Password"
Mode="OneWayToSource" />
<Binding Path="PasswordSalt"
Mode="OneWayToSource" />
</MultiBinding>

MVVM IValueConverter Convert method getting empty string argument when expecting a float

This may get a tad long, but here goes. I have created a small, wizard-style sample app using the MVVM pattern (basically a dumbed-down version of the code in my "real" app). In this app, the main window moves from through a List<..> of view models, with each view model displaying its associated view. I have 2 view model classes that are essentially identical, and they display the same view.
On the view is a combo box, populated with an array of float. The SelectedItem is bound to a float property on the view model. I have created a template for the combo box to display each item as a TextBlock, with the text taking the float value and going through a value converter.
The problem, when I switch back and forth between view models, all works fine as long as every view model I switch to is of the same class. As soon as I change the current page to an instance of a different view model, the value converter's Convert gets called with a 'value' parameter that is a zero-length string. Up til then, Convert was only being called with a float, as I would expect.
My question : why is the converter being called with the empty string ONLY in the case of switching view model classes?
I am attaching the main window XAML and view model, as well as the view/view models displayed for each "page". You'll notice that the main window view model has a list containing 2 instances of PageViewModel and 2 instances of OtherViewModel. I can switch back and forth between the first 2 fine, and the value converter only gets called with a float value. Once I switch to the first OtherViewModel instance, the converter gets an "extra" call with an empty string as the value.
Code snippets :
MainWindow
<Grid.Resources>
<DataTemplate DataType="{x:Type local:PageViewModel}">
<local:PageView />
</DataTemplate>
<DataTemplate DataType="{x:Type local:OtherViewModel}">
<local:PageView />
</DataTemplate>
</Grid.Resources>
<!-- Page -->
<ContentControl Margin="5,5,5,35"
Height="100"
IsTabStop="False"
Content="{Binding CurrentPage}" />
<!-- Commands -->
<Button Margin="5,115,0,0"
Width="75"
Content="< Back"
VerticalAlignment="Top"
HorizontalAlignment="Left"
Command="{Binding BackCommand}" />
<Button Margin="85,115,0,0"
Width="75"
Content="Next >"
VerticalAlignment="Top"
HorizontalAlignment="Left"
Command="{Binding NextCommand}" />
MainWindowViewModel
public MainWindowViewModel()
{
m_pages = new List<BaseViewModel>();
m_pages.Add(new PageViewModel(1, 7f));
m_pages.Add(new PageViewModel(2, 8.5f));
m_pages.Add(new OtherViewModel(3, 10f));
m_pages.Add(new OtherViewModel(4, 11.5f));
m_currentPage = m_pages.First();
m_nextCommand = new BaseCommand(param => this.OnNext(), param => this.EnableNext());
m_backCommand = new BaseCommand(param => this.OnBack(), param => this.EnableBack());
}
// Title
public string Title
{
get
{
return (CurrentPage != null) ? CurrentPage.Name : Name;
}
}
// Pages
BaseViewModel m_currentPage = null;
List<BaseViewModel> m_pages = null;
public BaseViewModel CurrentPage
{
get
{
return m_currentPage;
}
set
{
if (value == m_currentPage)
return;
m_currentPage = value;
OnPropertyChanged("Title");
OnPropertyChanged("CurrentPage");
}
}
// Back
ICommand m_backCommand = null;
public ICommand BackCommand
{
get
{
return m_backCommand;
}
}
public void OnBack()
{
CurrentPage = m_pages[m_pages.IndexOf(CurrentPage) - 1];
}
public bool EnableBack()
{
return CurrentPage != m_pages.First();
}
// Next
ICommand m_nextCommand = null;
public ICommand NextCommand
{
get
{
return m_nextCommand;
}
}
public void OnNext()
{
CurrentPage = m_pages[m_pages.IndexOf(CurrentPage) + 1];
}
public bool EnableNext()
{
return CurrentPage != m_pages.Last();
}
}
Notice the 2 instance of one view model followed by 2 instances of the other.
PageView
<Grid.Resources>
<x:Array x:Key="DepthList"
Type="sys:Single">
<sys:Single>7</sys:Single>
<sys:Single>8.5</sys:Single>
<sys:Single>10</sys:Single>
<sys:Single>11.5</sys:Single>
</x:Array>
<local:MyConverter x:Key="MyConverter" />
</Grid.Resources>
<TextBlock Text="Values:"
Margin="5,5,0,0">
</TextBlock>
<ComboBox Width="100"
Height="23"
VerticalAlignment="Top"
HorizontalAlignment="Left"
Margin="5,25,0,0"
DataContext="{Binding}"
SelectedItem="{Binding Depth}"
ItemsSource="{Binding Source={StaticResource DepthList}}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource MyConverter}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
PageViewModel/OtherViewModel/MyConverter
public class PageViewModel : BaseViewModel
{
public PageViewModel(int index, float depth)
{
Depth = depth;
Name = "Page #" + index.ToString();
}
public float Depth
{
get;
set;
}
}
public class OtherViewModel : BaseViewModel
{
public OtherViewModel(int index, float depth)
{
Depth = depth;
Name = "Other #" + index.ToString();
}
public float Depth
{
get;
set;
}
}
[ValueConversion(typeof(DateTime), typeof(String))]
public class MyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Debug.WriteLine("IValueConverter.Convert : received a " + value.GetType().Name);
string text = "";
if (value is float)
{
text = value.ToString();
}
else
{
throw new ArgumentException("MyConverter : input value is NOT a float.");
}
return text;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return float.Parse(value as string);
}
}
Note: I can remove the exception in the Convert method, and everything seems to work fine. But, I would like to know why this is happening. Why is the converter getting an empty string instead of the expected float, and only when we switch view models?
Any insights would be greatly appreciated. Thanks in advance...
Joe
I've had the same issue (with an enum instead of a float).
When the View is closed the ComboBox Selection is emptied. You can check this by handling SelectionChanged event and inspecting the SelectionChangedEventArgs RemovedItems collection.
This ends in String.Empty being passed into your ValueConverter.
In my case, I have modified the ValueConverter.Convert to allow string.Empty as a valid value, and return string.Empty.
This is the code I used:
// When view is unloaded, ComboBox Selection is emptied and Convert is passed string.Empty
// Hence we need to handle this conversion
if (value is string && string.IsNullOrEmpty((string)value))
{
return string.Empty;
}
Try
public BaseViewModel
{
public virtual float Depth{get;set;}
...
}
Then
public class PageViewModel : BaseViewModel
{
...
public override float Depth { get; set; }
}
and
public class OtherViewModel : BaseViewModel
{
...
public override float Depth { get; set; }
}
Then you only need one DataTemplate
<Grid.Resources>
<DataTemplate DataType="{x:Type local:BaseViewModel}">
<local:PageView />
</DataTemplate>
</Grid.Resources>
I'm guessing the strange value being passed to the converter is due to DataTemplates being switched.
Not tested

How do I enable (and conversely disable) a button based on the values in multiple other controls?

How do I enable (and conversely disable) a button when a textbox has a value, and a combo box has a selected item?
How could I set up the bindings to get the button to disable/enable appropriately?
This is not the way you should think. WPF encourages to use MVVM so you should prepare your VM class so that it has the appropriate properties that you should bind to(and probably model class too). Do not put logic/validation logic into your GUI.
Why not consider to use command binding? See/try for example the following simplified example:
<Window.CommandBindings>
<CommandBinding Command="Save" CanExecute="CommandBinding_CanExecute" Executed="CommandBinding_Executed" />
</Window.CommandBindings>
<StackPanel>
<TextBox Name="TextBox1"/>
<Button Content="Save" Command="Save"/>
</StackPanel>
CommandBinding has a property [CanExecute] that you can use for enabling/disabling your button in the code behind:
private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = (this.TextBox1.Text == "test");
}
private void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
{
// put your command-logic here
}
In this example you have to enter the value "test" to enable the button and execute your command-logic.
Bind the Button to a Command (e.g. Save-Command)
Bind the TextBox.Text to a property (e.g. string MyTextBoxText)
Bind the SelectedItem of the ComboBox to a property (or even the itemSource) (e.g. object MySelectedItem)
The CanExecute of the command has code like this:
return !string.IsNullOrWhiteSpace(MyTextBoxText) && (MySelectedItem != null);
Another way to do this is using a MultiBinding and Converter on the button you want to enable/disable
<Window ... xmlns:local="...">
<Window.Resources>
<local:MyMultiValueConverter x:Key="MyMultiValueConverter" />
</Window.Resources>
...
<ComboBox x:Name="myComboBox">...</ComboBox>
<TextBox x:Name="myTextBox">...</TextBox>
...
<Button Content="My Button">
<Button.IsEnabled>
<MultiBinding Converter="{StaticResource MyMultiValueConverter}">
<Binding ElementName="myComboBox" Path="SelectedValue" />
<Binding ElementName="myTextBox" Path="Text" />
</MultiBinding>
</Button.IsEnabled>
</Button>
...
</Window>
You need to create an implementation of the IMultiValueConverter interface that tests the values of both the ComboBox.SelectedValue and TextBox.Text properties and return either true or false which will then be assigned to the Button.IsEnabled property. Here's a simple converter that works but you'd want to make sure to tailor one to your specific needs:
public class MyMultiValueConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (values == null)
return false;
return values.All(c => c is String ? !String.IsNullOrEmpty((string)c) : c != null);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
While this approach does work, I tend to agree with the other answers in that you should use commands when possible over multibindings and converters.

Bind a views DialogResult to a ViewModels property?

I am relatively new to WPF, XAML and Data-bindings. I have a view (Window) and a view-model.
I have tried to implement the MVVM pattern which means neither the view nor the view-model hold a reference to each other. All data exchange happens via data-bindings.
So far so good but now I have run into a problem I can't find a solution for.
On my view I have a button Start which is bound to a command.
<Button Command="{Binding NextCommand}" Content="Next">
NextCommand is of type ActionCommand : ICommand
In my case NextCommand simply calls a private method within the view-model.
The problem I can not find a solution so far is the following:
How to close the window at the end of the view-models NextCommandAction method?
private void NextCommandAction(object o)
{
...
...
// close the window
}
Since I do not have a reference to the view I can not just set DialogResult = true;
The only working solution I have found so far is to add a hidden radio-button to the view and bind it's value to a property CloseView and create a method CloseView within the xaml.cs file which is bound to the Checked event of the hidden radio-button. Within that method I set DialogResult = true;
Although this works I feel like there has to be a better solution than adding hidden elements to your view!
You can pass the window reference as CommandParameter to the Close command and do whatever required on the window.
<Button Content="Close" Command="{Binding Path=CloseCommand}"
CommandParameter="{Binding ElementName=Window}"/>
private void CloseCommand(object sender)
{
Window wnd = sender as Window;
wnd.Close();
}
CommandParameter="{Binding ElementName=Window}" assumes that you have an element in your XAML named "Window". e.g, your Window tag would need Name="Window"
This question was one of the first things that came up when I googled to check if DialogResult is a dependency property (it isn't :-) )
Add a dependency property to your Window:
public static readonly DependencyProperty InteractionResultProperty =
DependencyProperty.Register(
nameof(InteractionResult),
typeof(Boolean?),
typeof(MyWpfWindow1),
new FrameworkPropertyMetadata(default(Boolean?),
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
OnInteractionResultChanged));
public Boolean? InteractionResult
{
get => (Boolean?) GetValue(InteractionResultProperty);
set => SetValue(InteractionResultProperty, value);
}
private static void OnInteractionResultChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((MyWpfWindow1) d).DialogResult = e.NewValue as Boolean?;
}
I named my property InteractionResult though a good name would have also worked.
In the xaml right after the
you can bind it with a style
<Window.Style>
<Style TargetType="{x:Type z:MyWpfWindow1}">
<Setter Property="InteractionResult"
Value="{Binding UpdateResult}" />
</Style>
</Window.Style>
UpdateResult is the property in my viewmodel.
private Boolean? _updateResult;
public Boolean? UpdateResult
{
get => _updateResult;
set => SetValue(ref _updateResult, value);
}
The SetValue method is the usual notify property
protected virtual Boolean SetValue<T>(ref T field, T value,
[CallerMemberName]String propertyName = null)
{
if (Equals(field, value))
return false;
field = value;
RaisePropertyChanged(propertyName);
return true;
}
and the property gets set in the usual way
<Button Content="Cancel"
Command="{Binding CancelCommand}" />
ICommand CancelCommand { get; }
private void OnCancel()
{
UpdateResult = false;
}
Disclaimer: works on my computer.
Inspired by Chandrashekhar Joshi's answer
(but not using the elements's name):
Define CommandParameter in Button:
<Button
  Command="{Binding CloseCommand}"
  CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=Window}}"
  Content="Close" />
Define Command (and Implementation):
CloseCommand = new DelegateCommand<Window>((w) => w.DialogResult = true);

WPF MVVM: ICommand Binding to controls

I've totally lost in the command binding that is used in MVVM. How should I bind my object to the window and/or its command to the control to get method called on the Button Click?
Here is a CustomerViewModel class:
public class CustomerViewModel : ViewModelBase
{
RelayCommand _saveCommand;
public ICommand SaveCommand
{
get
{
if (_saveCommand == null)
{
_saveCommand = new RelayCommand(param => this.Save(), param => this.CanSave);
NotifyPropertyChanged("SaveCommand");
}
return _saveCommand;
}
}
public void Save()
{
...
}
public bool CanSave { get { return true; } }
...
ViewModelBase implements the INotifyPropertyChanged interface
Here is how Button is bound to the command:
<Button Content="Save" Margin="3" Command="{Binding DataContext.Save}" />
An instance of the CustomerViewModel is assigned to the DataContext of the window that contains a Button.
The given example is not working: I've put break point into the Save method but execution doesn't pass to the method. I've saw a lot of examples (on the stackoverflow too), but can't figure out how binding should be specified.
Please advise, any help will be appreciated.
Thanks.
P.S. Probably I need to specify RelativeSource in the Button binding... something like this:
Command="{Binding Path=DataContext.Save, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"
but which type should be specified for ancestor?
What you are trying to do is to bind directly to the Save method. This is not how to do it.
Assuming that you have set the DataContext of your View to an instance of CustomerViewModel, this is how you bind to the SaveCommand:
<Button Content="Save" Margin="3" Command="{Binding SaveCommand}" />
You do not have to call NotifyPropertyChanged("SaveCommand");.

Resources