MVVM Light is too fast :) - silverlight

I have a simple WM7 Page with a TextBox. Futher, I assigned EventToCommand (a RelayCommand<string>) to this TextBox, reacting to the TextChanged event. For testing pourposes I made additional method TextBox_TextChanged in the page's code behind. Both the command and TextBox_TextChanged print a message box with the textbox content.
Initial value of the TextBox is "ABC". Then I press D and:
TextBox_TextChanged prints ABCD.
The command prints ABC. D is missing.
Why is the command so fast?
Command declaration:
public RelayCommand<string> TextChanged {get; private set;}
Command initialization:
TextChanged = new RelayCommand<string>((s) => MessageBox.Show(s));
Command binding:
<TextBox x:Name="SearchTextBox" Margin="10,0" TextWrapping="Wrap" Text="{Binding SearchString, Mode=TwoWay}" FontStyle="Italic" TextChanged="SearchTextBox_TextChanged" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged">
<GalaSoft_MvvmLight_Command:EventToCommand Command="{Binding TextChanged, Mode=OneWay}" CommandParameter="{Binding Text, ElementName=SearchTextBox}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>

I can't reproduce this behaviour. I have tried using EventToCommand and a Behaviour(which simply listens to TextChanged event).
Without seeing the code I suspect this might be to do with how you get the text of the search box or a logic error elsewhere.
This is a snippet of how I use EventToCommand:
<TextBox Name="SearchTextBox">
<i:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged">
<cmd:EventToCommand Command="{Binding TestTextChangedCommand,Mode=OneWay}" CommandParameter="{Binding Path=Text, ElementName=SearchTextBox}"/>
</i:EventTrigger>
<i:Interaction.Triggers>
</TextBox>
In the viewmodel
m_TestTextChangedCommand = new RelayCommand<string>(val => System.Diagnostics.Debug.WriteLine(val));
As you can see I used a commandparameter to pass the value of the textbox to the viewmodel. This way the viewmodel doesn't have to know about the textbox to get the text value.
An alternative to this approach would be to use behaviours and TwoWay binding to update a property:
<TextBox Name="SearchTextBox" Text="{Binding TextInViewModel, Mode=TwoWay}" >
<i:Interaction.Behaviors>
<sc:UpdateOnTextChangedBehavior/>
</i:Interaction.Behaviors>
</TextBox>
UpdateOnTextChangedBehavior class:
public class UpdateOnTextChangedBehavior : Behavior<TextBox>
{
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.TextChanged +=
new TextChangedEventHandler(AssociatedObject_TextChanged);
}
void AssociatedObject_TextChanged(object sender, TextChangedEventArgs e)
{
System.Diagnostics.Debug.WriteLine(((TextBox)sender).Text);
BindingExpression binding =
this.AssociatedObject.GetBindingExpression(TextBox.TextProperty);
if (binding != null)
{
binding.UpdateSource();
}
}
protected override void OnDetaching()
{
base.OnDetaching();
this.AssociatedObject.TextChanged -=
new TextChangedEventHandler(AssociatedObject_TextChanged);
}
}
What the above does is mimick the behaviour of desktop WPF Binding with UpdateSourceTrigger=PropertyChanged, which is missing in Silverlight. So what will happen, whenever you type into the text box TextInViewModel property will get updated. This property doesn't haven to be a DependencyProperty, it could just be a normal CLR property.

This works with TextBox via parameter for RelayCommand. IOW - RelayCommand<TextBox>
<TextBox Height="72" HorizontalAlignment="Left" Margin="8,136,0,0" Name="txtFilter" Text="" VerticalAlignment="Top" Width="460" >
<interactivity:Interaction.Triggers>
<interactivity:EventTrigger EventName="TextChanged">
<cmd:EventToCommand Command="{Binding SearchedTextChanged}" CommandParameter="{Binding ElementName=txtFilter}" />
</interactivity:EventTrigger>
</interactivity:Interaction.Triggers>
</TextBox>
public RelayCommand<TextBox> SearchedTextChanged { get; set; }
SearchedTextChanged = new RelayCommand<TextBox>(OnSearchedTextChanged);
private void OnSearchedTextChanged(TextBox val)
{
if (val != null)
{
System.Diagnostics.Debug.WriteLine(val.Text);
}
}

I had a similar issue and found that the databinding operation does not always fire until the TextBox loses focus. However, the Command will fire immediately.
If you want to guarantee that the databinding has occurred before you use the value, you can call the BindingExpression.UpdateSource() method on your control. Try something like this:
var bindTarget = SearchTextBox.GetBindingExpression(TextBox.TextProperty);
bindTarget.UpdateSource();
To avoid referring to your TextBox directly in your ViewModel (as you should with MVVM), you can use FocusManager.GetFocusedElement(). This is particularly useful when dealing with ApplicationBar buttons as they don't seem to receive focus when used.

Some code I sued (similar to yours Command example):
Command declaration:
public RelayCommand<string> TextChanged {get; private set;}
Command initialization:
TextChanged = new RelayCommand<string>((s) => MessageBox.Show(s));
Command binding:
<TextBox x:Name="SearchTextBox" Margin="10,0" TextWrapping="Wrap" Text="{Binding SearchString, Mode=TwoWay}" FontStyle="Italic" TextChanged="SearchTextBox_TextChanged" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged">
<GalaSoft_MvvmLight_Command:EventToCommand Command="{Binding TextChanged, Mode=OneWay}" CommandParameter="{Binding Text, ElementName=SearchTextBox}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
For some reasons messagebox shows a string with one character delay.

Related

InvokeCommandAction not calling

I have a combobox and I need a command in my view model to bind to its ContextMenuOpening event. I've tried referencing System.Windows.Interactivity and using InvokeCommandAction, but the command is not calling. Does anyone see where I'm going wrong?
<ComboBox x:Name="comboBoxAs" Grid.Column="0" VerticalAlignment="Top" Margin="928,62,0,0" Height="25"
ItemsSource="{Binding Source={StaticResource sas}}"
SelectedItem="{Binding Path=as, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource ComboBoxDefault}" HorizontalAlignment="Left" Width="212" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="ContextMenuOpening">
<i:InvokeCommandAction Command="{Binding ContextMenuOpeningCommand, Mode=OneWay}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
ViewModel:
public ICommand ContextMenuOpeningCommand
{
get
{
if (_contextMenuOpeningCommand == null)
{
_contextMenuOpeningCommand = new RelayCommand<object>(param => this.ContextMenuOpening(),
null);
}
return _contextMenuOpeningCommand;
}
}
public void ContextMenuOpening()
{
System.Windows.MessageBox.Show("test", "test");
}
private ICommand _contextMenuOpeningCommand;
Please try DropDownOpened to see whether the command gets hit. I tried it and it works here. Hope this helps :)

Binding commands to ToggleButton Checked and Unchecked events

I have a ToggleButton in my C# WPF application where I would like to bind one Command to the Checked event and one Command to the Unchecked event.
What I have currently is the following:
<ToggleButton Name="btnOpenPort" Style="{StaticResource myOnOffBtnStyle}" Content="Open Port"
Checked="btnOpenPort_Checked" Unchecked="btnOpenPort_Unchecked"
IsChecked="{Binding Path=PortViewModel.PortIsOpen, Mode=OneWay}"
Canvas.Left="75" Canvas.Top="80" Height="25" Width="100"/>
But this is not what I aim to do. Because in this case, I would have to set properties in the code behind for the Checked and Unchecked event.
Instead, I would like to call a Command (ICommand) in my ViewModel once the Checked or Unchecked event gets fired so that I don't need any code-behind for my toggle button.
Is there a way to bind a command directly for these two events in XAML?
Similar to the command property of the "standard" button control in WPF?
EDIT
This is how it works with regards to #har07 hint:
1: Added references if you dont have it yet:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="clr-namespace:Microsoft.Expression.Interactivity.Core;assembly=Microsoft.Expression.Interactions"
2: Implemented Interaction.Triggers for Checked and Unchecked events:
<ToggleButton
Name="btnOpenPort" Style="{StaticResource myOnOffBtnStyle}" Content="Open Port"
IsChecked="{Binding Path=PortViewModel.PortIsOpen, Mode=OneWay}"
Canvas.Left="75" Canvas.Top="80" Height="25" Width="100">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Checked">
<i:InvokeCommandAction Command="{Binding Path=PortViewModel.OpenPort}"/>
</i:EventTrigger>
<i:EventTrigger EventName="Unchecked">
<i:InvokeCommandAction Command="{Binding Path=PortViewModel.ClosePort}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ToggleButton>
With this solution, I don't have to change a single line of code in my ViewModel or my code behind.
I can just call my ICommand as I would do it with a standard button following MVVM pattern.
you may not be able to bind two commands for each checked and unchecked directly however you can still bind a command, which will be invoked for both. you also have option for attached behaviors if you need different command for both events.
<ToggleButton Command="{Binding MyCommand}"/>
in the vm
public ICommand MyCommand { get; private set; }
you will need to initialize it accordingly
and to determine the current state you may have a condition on the bonded property PortIsOpen
void Execute(object state)
{
if(PortIsOpen)
{
//checked
}
else
{
//unchecked
}
}
or perhaps you may pass it as a parameter too
eg
<ToggleButton Command="{Binding MyCommand}"
CommandParameter="{Binding IsChecked,RelativeSource={RelativeSource Self}}"/>
and use it as
void Execute(object state)
{
if((bool)state)
{
//checked
}
else
{
//unchecked
}
}
Maybe we can use EventTriggers
<ToggleButton>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Checked">
<i:InvokeCommandAction Command="{Binding Path=CheckedCommand}"/>
</i:EventTrigger>
<i:EventTrigger EventName="Unchecked">
<i:InvokeCommandAction Command="{Binding Path=UncheckedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ToggleButton>
to use Triggers we have to reference System.Windows.Interactivity
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
You can put the logic to handle checked/unchecked event in the setter of PortIsOpen property :
private bool _portIsOpen;
public bool PortIsOpen
{
get { return _portIsOpen; }
set
{
if(value) HandleCheckedEvent();
else HandleUnCheckedEvent();
....
}
}
Or you can use Ineraction.Triggers extension to bind event to commmand :
WPF Binding UI events to commands in ViewModel
<ToggleButton Name="btnOpenPort" Style="{StaticResource myOnOffBtnStyle}" Content="Open Port"
Checked="{Binding ICommand}" Unchecked="{Binding ICommand}"
IsChecked="{Binding Path=PortViewModel.PortIsOpen, Mode=OneWay}"
Canvas.Left="75" Canvas.Top="80" Height="25" Width="100"/>
Replace ICommand with your ICommand property name.

WPF/Catel: TextBox doesn't fire TextChanged event when Shift + character is entered

i got the following XAML:
<TextBox TextWrapping="Wrap"
Width="300"
Text="{Binding SearchText, Mode=TwoWay,
UpdateSurceTrigger=PropertyChanged}"
VerticalContentAlignment="Center"
ToolTip="Suchbegriff eingeben.">
<i:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged">
<catel:EventToCommand Command="{Binding SearchTxt_TextChangedCmd}"
DisableAssociatedObjectOnCannotExecute="False"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
It's a textbox which is bound to a string in mvvm.
The problem is when i press "shift + a character" at the first letter then the event is not fired.
When i press caps lock + any character, then the event will be fired as usual.
It alsow works when i press any character on the keyboard.
Can anyone help me please?
Thx in advance,
Edit: Solution after the suggestion of Geert:
In the view.xaml:
<TextBox Height="31"
HorizontalAlignment="Right"
Margin="0,59,20,0"
Name="textBox2"
VerticalAlignment="Top"
Width="282"
Text="{Binding BehaviorText, Mode=TwoWay}">
<i:Interaction.Behaviors>
<catel:UpdateBindingOnTextChanged UpdateDelay="200" />
</i:Interaction.Behaviors>
</TextBox>
In the ViewModel (using Catel):
public String BehaviorText
{
get { return GetValue<String>(BehaviorTextProperty); }
set { SetValue(BehaviorTextProperty, value); }
}
public static readonly PropertyData BehaviorTextProperty =
RegisterProperty("BehaviorText", typeof(String), null, (sender, e) => ((MainWindowViewModel)sender).OnUpdateBindingOnTextChanged());
private void OnUpdateBindingOnTextChanged()
{
Console.WriteLine(BehaviorText);
}
Use the UpdateBindingOnTextChanged behavior instead. If that doesn't fit your needs, please create a repro and upload it at https://catelproject.atlassian.net

EventTrigger not working inside ItemsControl in MVVM

I want to bind multiple buttons dynamically in MVVM.
1.I Dynamically created buttons using ItemControl
2. It did not Invoke Trigger Click Event.
Please help me on this.
<ItemsControl ItemsSource="{Binding ComponentList,Mode=TwoWay}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Button Tag="{Binding WorkFlowCompId}">
<Button.Content>
<TextBlock Text="{Binding ComponentName,Mode=TwoWay}"/>
</Button.Content>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{Binding ComponentSelected}"
CommandParameter="{Binding WorkFlowCompId,Mode=TwoWay}" >
</i:InvokeCommandAction>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Your problem is that the command is getting the context from its template and there it cannot access the root of the ViewModel. Add this class to your solution:
public class DataContextProxy : FrameworkElement
{
public DataContextProxy()
{
this.Loaded += new RoutedEventHandler(DataContextProxyLoaded);
}
void DataContextProxyLoaded(object sender, RoutedEventArgs e)
{
Binding binding = new Binding();
if (!String.IsNullOrEmpty(BindingPropertyName))
{
binding.Path = new PropertyPath(BindingPropertyName);
}
binding.Source = this.DataContext;
binding.Mode = BindingMode;
this.SetBinding(DataContextProxy.DataSourceProperty, binding);
}
public Object DataSource
{
get { return (Object)GetValue(DataSourceProperty); }
set { SetValue(DataSourceProperty, value); }
}
public static readonly DependencyProperty DataSourceProperty =
DependencyProperty.Register("DataSource", typeof(Object), typeof(DataContextProxy), null);
public string BindingPropertyName { get; set; }
public BindingMode BindingMode { get; set; }
}
then use it in you XAML like so:
<UserControl.Resources>
<library:DataContextProxy x:Key="DataContextProxy"/>
</UserControl.Resources>
Then in your command binding:
<Button Tag="{Binding WorkFlowCompId}">
<Button.Content>
<TextBlock Text="{Binding ComponentName,Mode=TwoWay}"/>
</Button.Content>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{Binding DataSource.ComponentSelected, Source={StaticResource DataContextProxy}"
CommandParameter="{Binding WorkFlowCompId,Mode=TwoWay}" >
</i:InvokeCommandAction>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
A first pass at what you Xaml should look like:-
<ItemsControl ItemsSource="{Binding ComponentList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Command="{Binding SelectComponent}">
<TextBlock Text="{Binding ComponentName}"/>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I suspect as Derek alludes to in his comment you have a ComponentSelected command on the container. However you should move this command ot the view model for the component. Note I've also renamed it to SelectComponent so that is sounds like an action rather than a property.
TwoWay binding has been removed it wouldn't be doing anything in this case. Assigning a Tag value from a simple binding should be setting off alarm bells that the design is having some problems.
BTW, since you are doing a form of selection would not a ListBox be more appropriate in this case?

How to fire event from button inside datagrid in silverlight and MVVM

I have button at first column in datagrid. I am using MVVM and try to bind Command to Command in ViewModel but when I click button in each row, it don't work (It don't call Command in ViewModel) but if I move that button out of datagrid it's working properly.
How can I fire event from button inside datagrid in MVVM?
Update 1:
XAML's code is:
<datagrid:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal"
VerticalAlignment="Center">
<Button x:Name="button" Content="View" Margin="5" DataContext="{StaticResource XDataContext}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{Binding ViewOrganizationCommand}"
CommandParameter="{Binding ElementName=dtgOrganizations, Path=SelectedItem}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</StackPanel>
</DataTemplate>
</datagrid:DataGridTemplateColumn.CellTemplate>
ViewModel's code is:
public ViewModelCommand ViewOrganizationCommand { get; set; }
Instead of using EventTrigger, why not simply bind to Button.Command directly, like so?
<Button
...other properties...
Command="{Binding ViewOrganizationsCommand}"
CommandParameter="{Binding}"/>
That will bind the command, and set the CommandParameter to the DataContext of the Button, which presumably is a good thing to bind the parameter to. If it's not, just bind CommandParameter to something else that helps you uniquely identify the specific row being clicked on.
This article brings the solution with DataContextProxy. Applying this solution makes possible to write a button code like Austin Lamb's answer.
The command binding using the Event Trigger looks fine (This is how i always do it),
But I suspect that your command is never assigned to (You are using Automatic Properties)
I usually do it like this:
private ViewModelCommand viewOrganizationCommand;
public ViewModelCommand ViewOrganizationCommand
{
get
{
if (viewOrganizationCommand == null)
viewOrganizationCommand = new ViewModelCommand(Action, CanDoIt);
return viewOrganizationCommand;
}
}
try setting you Button's DataContext to the StackPanel and set the Command and CommandParameter of the Button.
<datagrid:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal"
VerticalAlignment="Center" DataContext="{StaticResource XDataContext}">
<Button x:Name="button" Content="View" Margin="5" Command="{Binding ViewOrganizationCommand}" CommandParameter="{Binding ElementName=dtgOrganizations, Path=SelectedItem}" >
</Button>
</StackPanel>
</DataTemplate>
</datagrid:DataGridTemplateColumn.CellTemplate>
I solved this problem by use EventToCommand behavior in MVVMLight Toolkit
Each row of the DataGrid has it's DataContext set to that object. If the ItemSource for the grid is an ObservableCollection, each row's DataContext is the Organization object.
There are two ways to handle this.
Create a wrapping or extension ViewModel that exposes the command. Then the command should fire.Here is some psuedo code.
public class OrganizationExtensionViewModel
{
<summary>
/// Private currentOrginization property.
/// </summary>
private Organization currentOrginization;
public Organization CurrentOrganization
{
get
{
return this.currentOrginization;
}
set
{
if (this.currentOrginization != value)
{
this.currentOrginization = value;
this.RaisePropertyChanged("CurrentOrganization");
}
}
}
public ViewModelCommand ViewOrganizationCommand { get; set; }
public OrganizationExtensionViewModel(Organization o)
{
this.CurrentOrganization = o;
this.ViewOrganizationCommand = new ViewModelCommand(this.ViewOrgnaizationClicked);
}
}
Define the ViewModel in the xaml as a StaticResource and refer to it as the binding path.
...
In the grid
<Button x:Name="button" Content="View" Margin="5" Command="{Binding ViewOrganizationCommand, Source={StaticResource viewModel}}" />

Resources