Silverlight ChangePropertyAction on Button control not working - silverlight

Out of the box if you crank up Blend put a button on a grid and set a couple of ChangeProeprtyActions on it ... will fail with an error message.
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
x:Class="SL4_Button_States.MainPage"
Width="640" Height="480">
<Grid x:Name="LayoutRoot" Background="White">
<Button x:Name="button" Content="Button" Height="39" Margin="68,42,299,0" VerticalAlignment="Top">
<i:Interaction.Triggers>
<i:EventTrigger>
<ei:ChangePropertyAction PropertyName="Content" Value="Button is now loaded!" TargetObject="{Binding ElementName=button}"/>
</i:EventTrigger>
<i:EventTrigger EventName="Click">
<ei:ChangePropertyAction PropertyName="Content" Value="temporary"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</Grid>
The error message is this:
Webpage error details
Message: Unhandled Error in Silverlight Application The type 'Object' was not found. [Line: 2 Position: 10] at MS.Internal.XcpImports.CreateFromXaml(String xamlString, Boolean createNamescope, Boolean requireDefaultNamespace, Boolean allowEventHandlers, Boolean expandTemplatesDuringParse)
at MS.Internal.XcpImports.CreateFromXaml(String xamlString, Boolean createNamescope, Boolean requireDefaultNamespace, Boolean allowEventHandlers)
at Microsoft.Expression.Interactivity.TypeConverterHelper.ExtendedStringConverter.ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, Object value)
at Microsoft.Expression.Interactivity.Core.ChangePropertyAction.Invoke(Object parameter)
at System.Windows.Interactivity.TriggerBase.InvokeActions(Object parameter)
at System.Windows.Interactivity.EventTriggerBase.OnEvent(EventArgs eventArgs)
at System.Windows.Interactivity.EventTriggerBase.OnEventImpl(Object sender, EventArgs eventArgs)
at MS.Internal.CoreInvokeHandler.InvokeEventHandler(UInt32 typeIndex, Delegate handlerDelegate, Object sender, Object args)
at MS.Internal.JoltHelper.FireEvent(IntPtr unmanagedObj, IntPtr unmanagedObjArgs, Int32 argsTypeIndex, Int32 actualArgsTypeIndex, String eventName, UInt32 flags)
Line: 1
Char: 1
Code: 0
URI: http://localhost:62193/Silverlight.js
If you add a ChangePropertyAction on a TextBlock ... works fine:
<TextBlock Height="20" Margin="207,132,138,0" TextWrapping="Wrap" Text="TextBlock" VerticalAlignment="Top">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<ei:ChangePropertyAction PropertyName="Text" Value="Mouse Left BUtton Down"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBlock>
If you have any idea why this happens please enlighten me ... if there is another/better way of doing this, other than states please let me know.
Thanks.

It is failing because the Content property on a Button is not just a string but an object. Even though Blend will let you set it I'm not sure that it is possible. To change the text content of the button you would need to do something like this:
<Button x:Name="button" HorizontalAlignment="Left" Height="23" Margin="154,98,0,0" VerticalAlignment="Top" Width="113">
<Button.Content>
<TextBlock x:Name="btnText" Text="Orginal" />
</Button.Content>
<i:Interaction.Triggers>
<i:EventTrigger>
<ei:ChangePropertyAction PropertyName="Text" Value="Loaded" TargetName="btnText"/>
</i:EventTrigger>
<i:EventTrigger EventName="Click">
<ei:ChangePropertyAction PropertyName="Text" Value="Clicked" TargetName="btnText"/>
</i:EventTrigger>
</i:Interaction.Triggers>

How to change Button's Content via ChangePropertyAction in Blend?
All following steps are in Express Blend 4.
Add a Button in ArtBoard. Drag "ChangePropertyAction" from Assets to
the button.
In Data panel, create a new ContentControl data source (click "Create Object Data Source", search "ContentControl" and click OK. The datasource will be named as ContentControlDataSource).
In Object and Timeline, focus the ChangePropertyAction of button. In Property->Common Properties->PropertyName, select "Content", and create a data binding to the ContentControlDataSource with Value. (while setting Value of Content, click the small rectangle at the end of value. Choose 'Data Binding...", in the "Create Data Binding" dialog, select ContentControlDataSource in Data Field)
In Object and Timeline panel, unfocus then focus again the ChangePropertyAction of button. In Property->Common Properties->Value, you can see a yellow rectangle around the text like "(ContentControl) New". Click the small yellow rectangle at the end and select "Reset". After the yellow rectangle disappear, click the New button after Value property. When the "Select Object" dialog pops up, search "TextBlock" and add it. Then set the TextBlock's Text as you want. Build and test it now. Click the button and you can see the text is changed to what you set.
The XAML looks like:
<Button Content="Button" HorizontalAlignment="Left" Margin="125,51,0,0" VerticalAlignment="Top" Width="75">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<ei:ChangePropertyAction PropertyName="Content">
<ei:ChangePropertyAction.Value>
<TextBlock Text="HelloText"/>
</ei:ChangePropertyAction.Value>
</ei:ChangePropertyAction>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>

Button x:Name="button" Content="Button" Height="39" Margin="68,42,299,0" Click="button_Click" MouseRightButtonDown="button_MouseRightButtonDown" VerticalAlignment="Top"
private void button_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
((Button)sender).Content = "Right Click";
}
private void button_Click(object sender, RoutedEventArgs e)
{
((Button)sender).Content = "Button Click";
}

Related

Disable button when move mouse out of control

Say I have a Telerik RadGridView, outside there is a button. When the mouse is clicking row in the RadGridView, the button is enabled. If the mouse moves outside the RadGridView, then the button is disabled.
My code is
rgv_LostFocus(object sender, eventArgs e)
{
// do something
MyViewModel.IsButtonEnabled = false;
}
However I don't want to use code behind. Maybe using behavior?
You could use an interaction trigger from System.Windows.Interactivity and a ChangedPropertyAction from Microsoft.Expression.Interactions.dll:
<telerik:RadGridView xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions">
<i:Interaction.Triggers>
<i:EventTrigger EventName="LostFocus">
<ei:ChangePropertyAction TargetObject="{Binding}" TargetName="IsButtonEnabled" Value="false" />
</i:EventTrigger>
</i:Interaction.Triggers>
...
</telerik:RadGridView>
Please refer to this blog post for information about how to handle events in a MVVM application.
Per #mm8 hint.
<telerik:RadGridView xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity">
<i:Interaction.Triggers>
<i:EventTrigger EventName="LostFocus">
<i:InvokeCommandAction Command="{Binding LostFocusCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</telerik:RadGridView>
Then in ViewModel,
public DelegateCommand LostFocusCommand = new DelegateCommand(RadGridViewLostFocus);
In the method 'RadGridViewLostFocus' set the bool property as false;
private void RadGridViewLostFocus()
{
IsButtonEnabled = false;
}

Inner GroupBox does not swallow the Interactivity event

I have a GroupBox within a parent GroupBox. Both of them have their own
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<i:InvokeCommandAction Command="{Binding ...}" />
</i:EventTrigger>
</i:Interaction.Triggers>
When I press the inner GroupBox it fires its own Command and then the parent Command is also triggered.
How do I prevent that? How do I make the inner GroupBox swallow the event?
You could use another implementation of TriggerAction that supports passing the event args as a command parameter to the command, for example the EventToCommand class in the MvvmLight library:
<GroupBox Header="Outer" xmlns:mvvm="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Platform">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<i:InvokeCommandAction Command="{Binding OuterCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock>...</TextBlock>
<GroupBox Header="Inner" Grid.Row="1">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<mvvm:EventToCommand Command="{Binding InnerCommand}" PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
<TextBlock>inner...</TextBlock>
</GroupBox>
</Grid>
</GroupBox>
public class ViewModel
{
public ViewModel()
{
OuterCommand = new RelayCommand(arg => true, (arg)=> { MessageBox.Show("outer"); });
InnerCommand = new RelayCommand(arg => true,
(arg) =>
{
MessageBox.Show("inner");
MouseButtonEventArgs mbea = arg as MouseButtonEventArgs;
if (mbea != null)
mbea.Handled = true;
});
}
public RelayCommand OuterCommand { get; }
public RelayCommand InnerCommand { get; }
}
The ugly thing with this solution is that the view model has a dependency upon the view related MouseButtonEventArgs type though. If you don't like this you can implement your own behaviour as suggested by #adabyron here:
MVVM Passing EventArgs As Command Parameter
Then you could set the Handled property of the MouseButtonEventArgs directly in the behaviour instead of passing it along to the view model.

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.

How to create hotkey for button by modifying xaml?

I am trying to learn keybinding in WPF by a simple example.
here is my XAML file:
<Window.Resources>
<RoutedUICommand x:Key="myNewCommand"></RoutedUICommand>
</Window.Resources>
<Window.CommandBindings>
<CommandBinding Command="{StaticResource myNewCommand}" Executed="Button_Click"></CommandBinding>
</Window.CommandBindings>
<Window.InputBindings>
<KeyBinding Command="{Binding myNewCommand}" Key="B" />
</Window.InputBindings>
<Grid>
<Button Command="{Binding myNewCommand}" Click="Button_Click" Content="Click Here"/>
</Grid>
And this is code behind for Button_Click :
private void Button_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("hello");
}
I get the "hello" message when I click on button, but no response when I press "B" on keyboard.
I want to have this binding without changing the Button_click, can I do it just in XAML? how?
Your command bindings aren't correct. You should replace {Binding myNewCommand} with {Binding Source={StaticResource myNewCommand}}. It is also not necessary to have a Click handler on the button when you have already bound a command.
<Window.Resources>
<RoutedUICommand x:Key="myNewCommand"/>
</Window.Resources>
<Window.CommandBindings>
<CommandBinding Command="{StaticResource myNewCommand}"
Executed="MyCommandExecuted"/>
</Window.CommandBindings>
<Window.InputBindings>
<KeyBinding Command="{Binding Source={StaticResource myNewCommand}}" Key="B" />
</Window.InputBindings>
<Grid>
<Button Command="{Binding Source={StaticResource myNewCommand}}"
Content="Click Here"/>
</Grid>
The Executed handler:
private void MyCommandExecuted(object sender, RoutedEventArgs e)
{
MessageBox.Show("hello");
}

MVVM Light is too fast :)

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.

Resources