How to pass the updated text by CommandParameter in TextChagned event? - c#

This is an interesting case. I use MVVMLight to catch the event TextChagned of a textbox and pass it to a command in ViewModel and somehow the text value passed by CommandParameter is still the old text before the update.
Anyone knows how to get the new text?
<Grid>
<TextBox x:Name="myTextBox" HorizontalAlignment="Left"
Height="23" Margin="10,10,0,0" TextWrapping="Wrap" Text="Hello"
VerticalAlignment="Top" Width="120">
<i:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged">
<cmd:EventToCommand Command="{Binding Mode=OneWay,Path=TextChangedCommand}"
PassEventArgsToCommand="True"
CommandParameter="{Binding Path=Text,ElementName=myTextBox}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
<Button Content="Button" HorizontalAlignment="Left" Margin="352,214,0,0"
VerticalAlignment="Top" Width="75" Click="Button_Click"/>
</Grid>
public class MainViewModel : ViewModelBase
{
/// <summary>
/// Initializes a new instance of the MainViewModel class.
/// </summary>
public MainViewModel()
{
////if (IsInDesignMode)
////{
//// // Code runs in Blend --> create design time data.
////}
////else
////{
//// // Code runs "for real"
////}
}
private RelayCommand<string> _textChangedCommand;
public RelayCommand<string> TextChangedCommand
{
get
{
if (this._textChangedCommand == null)
{
this._textChangedCommand = new RelayCommand<string>(this.TextChanged);
}
return this._textChangedCommand;
}
}
private void TextChanged(string input)
{
MessageBox.Show(input);
}
}

I was digging into the code of MVVM Light and in EventToCommand.cs's Invoke method it seems that the parameter still has its old value. I didn't look further, but maybe this is a bug in MVVM.
There is a workaround you can do though.
Create a new class implementing IEventArgsConverter, something similar to this:
public class TextChangedEventArgsConverter : IEventArgsConverter
{
public object Convert(object value, object parameter)
{
var textChangedEventArgs = (TextChangedEventArgs) value;
return ((TextBox) textChangedEventArgs.Source).Text;
}
}
Add it to the Resources collection of your Window or ResourceDictionary where you have the EventToCommand that's not working.
<Window.Resources>
<textChanged:TextChangedEventArgsConverter x:Key="TextChangedEventArgsConverter" />
</Window.Resources>
And change the EventToCommand to the following:
<cmd:EventToCommand Command="{Binding Mode=OneWay,Path=TextChangedCommand}"
PassEventArgsToCommand="True"
EventArgsConverter="{StaticResource TextChangedEventArgsConverter}" />
So the specified EventArgsConverter will receive the actual TextChangedEventArgs and will extract the Text itself, which will be the correct value.
With these changes, I was able to achieve what you are looking for.
By the way: don't use CommandParameter and PassEventArgsToCommand="True" at the same time. As the documentation says, if you are setting PassEventArgsToCommand to true, the type parameter of your RelayCommand should be the event argument type of the event which is TextChangedEventArgs in this case.

Related

How to pass property value of a control to "CommandParameter" property of same control in WPF

I am following MVVM pattern. I want to pass property value of a control to "CommandParameter" property of same control . But runtime exception of "Object reference not set to an instance of object" is being faced.
WPF:
<Button
x:Name="btnBrowseFirmware1"
Grid.Row="2"
Grid.Column="1"
Width="135"
Height="35"
Command="{Binding OpenFileDialogCommand}"
CommandParameter="{Binding Name ,ElementName=btnBrowseFirmware1}"
Content="Browse "
Foreground="White"
/>
Viewmodel:
public class ConfigurationParametersViewModel : WorkspaceViewModelBase
{
public ICommand OpenFileDialogCommand { get; private set; }
public ConfigurationParametersViewModel()
: base("ConfigurationParameters", true)
{
OpenFileDialogCommand = new RelayCommand<string>(OpenFileDialogCommandFunc);
}
private void OpenFileDialogCommandFunc(string browseButtonName)
{
OpenFileDialog fileDialog = new OpenFileDialog();
Some Code...
}
}
While changing the binding to CommandParameter="{Binding Name ,RelativeSource={RelativeSource Self}}" (as Mr.B suggested) will solve your problem, I would recommend not to send UI Element names to the ViewModel. This will "break" the MVVM pattern. Make a command foreach Open File Action. This will also avoid a long if(browserButtonName= "thisOrThat") clause, which is hard to maintain. This has also more advantages. Just to name one: You could bind this command to KeyBindings. For example CTRL+O will call the OpenFileCommand.
And if you want to go for excellence you can even use a Service to abstract your OpenFileDialog WPF OpenFileDialog with the MVVM pattern?
You cannot use ElementName for element iteself, you should use RelativeSource=Self instead:
<Button x:Name="btnBrowseFirmware1"
Grid.Row="2"
Grid.Column="1"
Width="135"
Height="35"
Command="{Binding OpenFileDialogCommand}"
CommandParameter="{Binding Name ,RelativeSource={RelativeSource Self}}"
Content="Browse "
Foreground="White"
/>

How to bind a method in my viewmodel to TextChanged?

I'm having a bit of trouble figuring out how to correctly bind my method to the viewmodel.
Here is my current XAML:
<TextBox x:Name="Length" Style="{StaticResource LengthStyle}" Height="Auto" Width="35"
TextChanged="{Binding Validate}" Text="{Binding Length, ValidatesOnDataErrors=True, UpdateSourceTrigger=LostFocus}" />
Then in my viewmodel I have a method that looks like this:
public string Validate(Column c){
//unimportant
}
I'm just confused on how I can get this to work. Should I have a setter property that calls this? I would have just set this up as an event in the code behind but the project I'm working on prohibits that. Thanks.
Create a property like the following
private string length;
public string Length
{
get
{
return length;
}
set
{
length = value;
//do whatever you want
}
}
Include these 2 interactivity references :
1. System.Windows.Interactivity
2. Microsoft.Expression.Interactions
Then in your xaml declare this :
xmlns:interactivity="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:interactions="clr-namespace:Microsoft.Expression.Interactivity.Input;assembly=Microsoft.Expression.Interactions"
The xaml for textbox will be like :
<TextBox>
<interactivity:Interaction.Triggers>
<interactivity:EventTrigger EventName="TextChanged">
<behaviours:ExecuteCommandAction Command="{Binding Path=DataContext.ValidateCommand,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type UserControl}}}"
CommandParameter="PassTheColumnHere"/>
</interactivity:EventTrigger>
</interactivity:Interaction.Triggers>
</TextBox>
You may need to make 2 changes as per your requirement:
1. Instead of Ancestortype "Usercontrol"(if your xaml is not a user control") , then give the file name.
2.Pass the command parameter, in your case the column as you are mentioning.
After that declare the command "ValidateCommand" in your view model:
public ICommand ValidateCommand{ get; private set; }
Inside the constructor initialize it:
ValidateCommand = new DelegateCommand<Column>(Validate);
and the rest logic you can implement in your method:
public void Validate(Column c){
//your logic
}

WPF Button Command not firing ICommand in ViewModel

I have a View with a button as follows:
<Button Grid.Column="2" Grid.Row="1" Content="Test" Margin="10,4"
Command="{Binding DataContext.CmdTestButtonClicked}"
CommandParameter="{Binding}" />
In the view's code-behind, I set the DataContext to the ViewModel:
public GlobalSettings()
{
InitializeComponent();
...
DataContext = Helpers.IoCHelper.GlobalSettingsVM;
...
}
My ViewModel derives from a base class which exposes the ICommand:
public class GlobalSettingsVM : CollectionViewModel<GlobalSettings> { ... }
public abstract class CollectionViewModel<TModel> : IInstallModuleViewModel, INotifyPropertyChanged,
INotifyDataErrorInfo where TModel : Model, new()
{
...
public ICommand CmdTestButtonClicked
{
get
{
return _testButtonClicked ??
(_testButtonClicked = new RelayCommand(TestButtonClicked));
}
}
protected virtual void TestButtonClicked(object o)
{
// I never get here
}
}
I don't have any other issues using this pattern throughout my application, however all my other implementations have the Button within a ListView, so there I have to use RelativeSource={RelativeSource AncestorType={x:Type ListView}}.
Why would this command never fire? Do I need to set a RelativeSource here as well?
This
Command="{Binding DataContext.CmdTestButtonClicked}"
Implies that the Command will look for a property called DataContext in the object to which the button is bound.
If the DataContext of the button is a GlobalSettingsVM this should work:
Command="{Binding CmdTestButtonClicked}"
You could also use the MVVM Light toolkit wich is very convenient and helping on these situations.
You would get Something like this :
<Button Grid.Column="2" Grid.Row="1" Content="Test" Margin="10,4"
<i:Interaction.Triggers>
<i:EventTrigger EventName="OnClick" >
<Command:EventToCommand Command="{Binding DataContext.CmdTestButtonClicked}" CommandParameter="{Binding}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
In my case, I was listening to PreviewMouseLeftButtonDown under constructor of xaml.cs class which was stopping command event callback to the view model.
this.PreviewMouseLeftButtonDown += (s, e) => DragMove();
Instead of above line, in xaml file for window added MouseLeftButtonDown="Window_MouseLeftButtonDown" click handler and handled window drag within it, as below
private void Window_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
DragMove();
}

How do I pass the Button as CommandParameter from XAML in a Xamarin.Forms Page?

I would like to pass a Xamarin.Forms.Button in it's own Command as the CommandParameter to my ViewModel. I know how to achieve this from the code behind e.g. ...
XAML (with most properties missed out for brevity)
<Button x:Name="myButton"
Text="My Button"
Command="{Binding ButtonClickCommand}"/>
XAML.cs
public partial class MyTestPage
{
public MyTestPage()
{
InitializeComponent();
myButton.CommandParameter = myButton;
}
}
ViewModel
public class MyViewModel : ViewModelBase
{
public MyViewModel()
{
ButtonClickCommand = new Command(
(parameter) =>
{
var view = parameter as Xamarin.Forms.Button;
if (view != null)
{
// Do Stuff
}
});
}
public ICommand ButtonClickCommand { get; private set; }
}
... BUT is it possible to declare the CommandParameter in the XAML itself? Or in other words what is the binding syntax to set the parameter to the button itself?
<Button x:Name="myButton"
Text="My Button"
Command="{Binding ButtonClickCommand}"
CommandParameter="{[WHAT WOULD GO HERE]}"/>
btw I've already tried CommandParameter="{Binding RelativeSource={RelativeSource Self}}" and that didn't work.
Thanks,
Xamarin.Forms has a Reference markup extension that does just that:
<Button x:Name="myButton"
Text="My Button"
Command="{Binding ButtonClickCommand}"
CommandParameter="{x:Reference myButton}"/>
Although, this is the first time I'm seeing this need, and you probably can better separate your Views from your ViewModels and solve this by using a cleaner pattern, or by not sharing a command across buttons.
<Button x:Name="myButton"
Text="My Button"
Command="{Binding ButtonClickCommand}"
CommandParameter="{x:Reference myButton}"/>
In your ViewModel
public YourViewModel()
{
ButtonClickCommand= new Command(ButtonClicked);
}
private async void ButtonClicked(object sender)
{
var view = sender as Xamarin.Forms.Button;
}
A simple method would be:
In XAML:
<Button Text="BUTTON-TEST"
Clicked="Avaliar"
CommandParameter="like"/>
In C#:
private void Avaliar(object sender, EventArgs e)
{
Console.WriteLine(((Button)sender).CommandParameter);
}
<Button x:Name="myButton"
Text="My Button"
Command="{Binding ButtonClickCommand}"
CommandParameter={Binding RelativeSource=
{RelativeSource
Mode=FindAncestor,
AncestorType={x:Type Button}}/>
Should work, but im still at a loss why you need the button? The point of MVVM is to seperate Data and UI. everything you should need todo to the button can be done via DataBindings.
If the above doesnt work, the only other thing to try is to give the button an x:Key and CommandParamter = {StaticResource 'x:Key'}

Untangle EventToCommand on two controls with MVVM Light

Hi am pretty new to WPF and I have started to implement an app using mvvm pattern based on the mvvm light framework. I found it great but I got a problem using two EventToCommand on controls that are supposed to interact together. I am guessing I am doing something wrong... would you help me to find out what exactly?
I have one window with two controls: a combo box which allows the selection of a Name and a TextBox that displays a Caption. A name has a default caption (hardcoded for the example in the constructor of the ViewModel, see below). So when the user select a name the textbox should display the default caption. However, the caption is editable, which means that the user can change the Caption (as long as he does not change the Name again).
In the example below, I have implemented this using MVVM pattern with MVVM Light framework.
The Ok button, is only bound to a command that logs the value in the OutPut window (to see properties values in the ViewModel).
As you will see in the source code comment, the problem comes from the fact that NameSelectionChanged command triggers the CaptionTextChanged command with an "outdated value". For now, I implemented a hacky workaround (not in the code below) by setting a boolean value that ignores the code in CaptionTextChanged when executing the RaisePropertyChanged in NameSelectionChanged but it is not really satisfactory.
the View in XAML
<Window x:Class="TwoControls.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:vm="clr-namespace:TwoControls"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<vm:DummyViewModelLocator x:Key="Locator" d:IsDataSource="True" />
</Window.Resources>
<Window.DataContext>
<Binding Path="GetViewModel" Source="{StaticResource Locator}" />
</Window.DataContext>
<Grid>
<ComboBox ItemsSource="{Binding ColumnNames}" x:Name="NamesComboBox" HorizontalAlignment="Left" VerticalAlignment="Top" Width="120">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<cmd:EventToCommand Command="{Binding NameSelectionChanged}" CommandParameter="{Binding SelectedValue, ElementName=NamesComboBox}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
<TextBox Text="{Binding Caption, Mode=TwoWay}" Name="CaptionTextBox" HorizontalAlignment="Left" Height="23" Margin="0,45,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120">
<i:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged">
<cmd:EventToCommand Command="{Binding CaptionTextChanged}" CommandParameter="{Binding Text, ElementName=CaptionTextBox}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
<Button Content="Ok" Command="{Binding ClickOk}" HorizontalAlignment="Left" Margin="120,170,0,0" VerticalAlignment="Top" Width="75"/>
</Grid>
</Window>
the view model in C#
public class MainViewModel : ViewModelBase
{
private readonly List<string> _names;
private readonly string[] _captions;
public MainViewModel()
{
_names = new List<string>(new[]{"TOTO","TATA","TUTU"});
_captions = new[] {"toto", "tata", "tutu"};
}
public string Name { get; set; }
public string Caption { get; set; }
public ICommand NameSelectionChanged
{
get
{
return new RelayCommand<string>((input) =>
{
Name = input;
int index = _names.IndexOf(input);
Caption = _captions[index];
//Trigger the execution of CaptionTextChanged with the old value of the TextBox
//even if Caption and TextBox.Text are bound TwoWay....
base.RaisePropertyChanged(()=>this.Caption);
});
}
}
public ICommand CaptionTextChanged
{
get
{
return new RelayCommand<string>((input) =>
{
Caption = input;
});
}
}
public ICommand ClickOk
{
get
{
return new RelayCommand(() =>
{
Console.WriteLine("Name=" + Name +";" +"Caption=" + Caption);
});
}
}
public List<string> ColumnNames
{
get { return _names; }
}
}
Ps: targeted .NET is 3.5 and MVVMLight's version is 4.1.27.1
You don't need to use any events to do this in WPF. All this is possible by just using property Bindings if you implement the INotifyPropertyChanged interface on your view model as is customary... how about this:
private ObservableCollection<string> columnNames = new
ObservableCollection<string>();
public ObservableCollection<string> ColumnNames
{
get { return columnNames; }
set { columnNames = value; NotifyPropertyChanged("ColumnNames"); }
}
private string selectedColumnName;
public string SelectedColumnName
{
get { return selectedColumnName; }
set
{
selectedColumnName = value;
NotifyPropertyChanged("SelectedColumnName");
int index = _names.IndexOf(value); // <<< Changes are reflected here
Caption = _captions[index];
}
}
private string caption = string.Empty;
public string Caption
{
get { return caption; }
set { caption = value; NotifyPropertyChanged("Caption"); }
}
In XAML:
<Grid>
<ComboBox ItemsSource="{Binding ColumnNames}" SelectedItem="{Binding
SelectedColumnName}" x:Name="NamesComboBox" HorizontalAlignment="Left"
VerticalAlignment="Top" Width="120" />
<TextBox Text="{Binding Caption, Mode=TwoWay}" Name="CaptionTextBox"
HorizontalAlignment="Left" Height="23" Margin="0,45,0,0" TextWrapping="Wrap"
VerticalAlignment="Top" Width="120" />
<Button Content="Ok" Command="{Binding ClickOk}" HorizontalAlignment="Left"
Margin="120,170,0,0" VerticalAlignment="Top" Width="75"/>
</Grid>
Let me know how it goes.

Categories

Resources