Commands–InvokeMethod

The Basics

In addition to enhanced support for the ICommand interface Photon also provides some pretty funky features for invoking methods directly.  To get a quick idea of what can be achieved lets take a look at some samples:

Capture_thumb1

<UserControl x:Class="Samples.Photon.Documentation.InvokeMethodExampleView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Samples.Photon.Documentation"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:Photon="clr-namespace:Photon.Windows.Input;assembly=Photon.Corelib" >
<UserControl.Resources>
<local:InvokeMethodExampleViewModel x:Key="Model" />
</UserControl.Resources>
<StackPanel x:Name="LayoutRoot" DataContext="{Binding Source={StaticResource Model}}">
<!-- Hello world button -->
<Button VerticalAlignment="Top" HorizontalAlignment="Stretch">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<Photon:InvokeMethod MethodName="HelloWorld" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
<!-- Say hello button -->
<TextBox x:Name="SayHelloInput" />
<Button>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<Photon:InvokeMethod MethodName="SayHello" >
<Photon:InvokeMethod.Arguments>
<Photon:Argument Value="{Binding ElementName=SayHelloInput, Path=Text}" />
</Photon:InvokeMethod.Arguments>
</Photon:InvokeMethod>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</StackPanel>
</UserControl>





Now lets take a look at the model:

public class InvokeMethodExampleViewModel : ModelBase
{
public void HelloWorld()
{
MessageBox.Show("Hello World");
}

[Display(Name = "Show Hello Message")]
public void SayHello([Required] string name)
{
MessageBox.Show("Hello, " + name);
}
}



If you look at the screen shot you will see that one of the buttons has its content set to “Hello World” and the other to “Show Hello Message”, yet neither of them define a value for their content properties in the Xaml. This is because Photon will by default automatically set the content for buttons whose content has not been explicitly set. Photon will first look to see if the method has been adorned with a DisplayAttribute whose Name property has been specified, if none is found the method name is formatted into words. Using the DisplayAttribute is a great way of localising your UI using resource files.

You may also have noticed that the “Show Hello Message” button is not enabled in the screen shot. This is because the InvokeMethod is bound to the SayHello method whose “name” parameter is adorned with a  [Required] attribute. As the “name” parameter in the InvokeMethod is bound to {Binding ElementName=SayHelloInput, Path=Text}  the button’s enabled state tracks the Text property of the SayHelloInput control.

* The behaviour of UI elements bound to methods can be overridden by implementing a custom FeedbackPresenter (outside the scope of this article).

Invoke Targets

By default an InvokeMethod will target the current DataContext, however, in some cases this may not be what you want. For example, a ListBoxItem has its DataContext set to whichever list item it represents, if we want to execute a method exposed by the view model that defines the list we would normally be a little stuck (in Silverlight at least). To work around this Photon defines the Invoke.Target attached property. The Invoke.Target property is a Photon ScopeProperty, which means that it can be automatically tracked by child elements. Example:

<ListBox ItemsSource="{Binding People}" Photon:Invoke.Target="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding FullName}" />
<Button>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<!-- Binds to the scoped Target specified on the ListBox -->
<Photon:InvokeMethod MethodName="SayHello">
<Photon:InvokeMethod.Arguments>
<Photon:Argument Value="{Binding FullName}" />
</Photon:InvokeMethod.Arguments>
</Photon:InvokeMethod>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

public class InvokeMethodExampleViewModel : ModelBase
{
private ObservableCollection<Person> _people;


public InvokeMethodExampleViewModel()
{
_people = new ObservableCollection<Person> {new Person {FullName = "Jason Young"}};
}

[Display(Name = "Show Hello Message")]
public void SayHello([Required] string name)
{
MessageBox.Show(name);
}


public ObservableCollection<Person> People
{
get
{
return _people;
}
}
}

Method Binding Syntax

The InvokeMethod action uses pattern matching to distinguish between different method overloads. To distinguish between methods with different numbers of parameters you can simply use commas within brackets. For example, to bind to the first search method below you would set MethodName to ”Search()” and to bind to the second you would set MethodName to “Search(,)”.

public void Search()
{
}

public void Search(string expression, SearchType searchType)
{
}

If you need to distinguish between two overloads with the same number of parameters you can also specify the parameter type names. For example, to bind to the first search method below you would set MethodName to “Search(String)” and to bind to the second you would set MethodName to “Search(SavedSearch).

public void Search(string expression)
{
}

public void Search(SavedSearch savedSearch)
{
}

If for some reason you find yourself needing to distinguish between two parameter types with the same name you can also include full or partial namespace details for the parameters, for example “Search(Searching.SavedSearch)”. You can also mix approaches, for example “Search(String,,)” matches a search method with three parameters whose first parameters is a String. You only need to specify enough detail to make the method binding unambiguous.

The method binding engine automatically translates type aliases so it is safe to specify int for Int32, long for Int64, etc..

Finally, is you specify a method name with no brackets the binding engine will bind to any single method with that name regardless of the number of parameters.

Can Execute

In addition to supporting validation attributes to determine if a method is available you can also supply a “Can Execute” method. By convention these should be named in the form “bool Can<MethodName>”, so if you have a method called Search,  you would need to supply a method called CanSearch.

public void Search(string expression)
{
}

public bool CanSearch(string expression)
{
return (expression ?? string.Empty).Trim().Length > 2;
}

You can also use a property to define whether a method can be invoked. Properties have the advantage of not being tied to the signature of the corresponding method, and so can therefore be shared across multiple overloads, for example:

public void Search(string expression)
{
}

public void Search(SavedSearch savedSearch)
{
}

public bool CanSearch
{
get
{
// don't allow more than one search to be kicked off at a time
return !IsSearching;
}
}

Last edited Jan 30, 2011 at 9:24 PM by suedama1756, version 1

Comments

No comments yet.