Introduction
Synergy is a platform toolkit that unleashes the power of WPF, Silverlight, and Windows Phone 7 platforms. In this blog, I will discuss the Windows 7 Phone part of this library and how it helps to somehow converge development practices to that of WPF and leverage data binding capabilities.
The Synergy toolkit can be accessed at its CodePlex home where Santa (myself) keeps delivering new gifts and wishes everyday. I have released v1 of the Windows Phone 7 library in the downloads section.
For a background on the WPF Synergy toolkit, you can refer to my previous blog here. I also wrote a docking Windows framework in the WPF edition of Synergy that you can refer here and a window theming article here.
You can follow me on twitter @ashishkaila.
Without further adieu, let me walk you through the features of the v1 release.
Base Framework for Windows Phone 7 Development
Commanding Support
The CommandBase
class provides a flexible implementation for custom commands. This class works on an Action<object>
instance for command invocation, and an optional Predicate<object>
for command execution evaluation. If the predicate is not specified, it is assumed that the command can be executed anytime. Simply speaking, we can instantiate the CommandBase
class as follows:
CommandBase myCommand = new CommandBase(arg=>Foo(arg), arg=>CanFoo(arg));
Dependency System Enhancements
Several enhancements in the dependency system have been provided that helps developers tap easily into the framework for advanced operations related to binding and dependency properties. These enhancements are as follows:
Observable Binding
The ObservableBinding
class, as the name suggests, observes a binding for value changes and provides the most recent value for the binding. This class is very useful within custom behaviors and trigger actions where the data context of the associated object does not propagate to these entities. This is so because Behavior
s and Trigger
s are attached properties within the static Interaction
class (declared in the System.Windows.Interactivity
assembly) and are therefore detached from the dependency system.
By declaring a dependency property of type Binding
and using ObservableBinding
within a Behavior
/TriggerAction
, you can both hook into the dependency property system of the associated object as well as evaluate the most recent value of the binding.
For instance, the EventToCommand
trigger action defines a dependency property Command
of type Binding
:
public static DependencyProperty CommandProperty =
DependencyProperty.Register("Command",
typeof (Binding),
typeof (EventToCommand),
new PropertyMetadata(null, OnCommandChanged));
It also declares an ObservableBinding
for the command:
private readonly ObservableBinding _observableCommandBinding = new ObservableBinding();
This _observableCommandBinding
member will provide us with the latest value of Binding
exposed by the Command
property.
There are certain rules to ensure that ObservableBinding
works as expected for Binding
properties. Basically, anytime a Binding
property changes, we have to unhook from the old Binding
and hook into the new binding. This is done in the OnCommandChanged
method which is called when a Binding
property is changed (that is the actual Binding
property and not the value of the Binding
):
private static void OnCommandParameterChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
EventToCommand eventToCommand = d as EventToCommand;
if (eventToCommand == null)
{
return;
}
eventToCommand._observableCommandParameterBinding.Detach();
if ((e.NewValue != null) && (eventToCommand.AssociatedObject != null))
{
eventToCommand._observableCommandParameterBinding.Attach(
eventToCommand.Command eventToCommand.AssociatedObject);
}
}
Also, when the associated object is initially attached to the TriggerAction
/ Behavior
, we need to attach to the ObservableBinding
:
protected override void OnAttached()
{
base.OnAttached();
if (Command != null)
{
_observableCommandBinding.Attach(Command, AssociatedObject);
}
}
If you skip the above step, the initial value of Binding
will not be tracked by ObservableBinding
.
ObservableDependencyProperty
ObservableDependencyProperty
on the other hand provides the ability to track changes in a dependency property declared in another DependencyObject
by way of event notifications. The constructor of ObservableDependencyProperty
takes two parameters: the string representing the name of the dependency property to observe, and a delegate of type DependencyPropertyChangedEventHandler
that will be called when the dependency property changes:
ObservableDependencyProperty _verticalScrollChange =
new ObservableDependencyProperty("VerticalOffset", OnVerticalScroll);
To track changes in a dependency property of the actual DependencyObject
, a ObservableDependencyProperty
instance must be attached to it via the AddValueChanged
method:
_verticalScrollChange.AddValueChanged(_scrollViewer);
Similarly, to detach or stop property change tracking, the RemoveValueChanged
method may be called:
_verticalScrollChange.RemoveValueChanged();
Complete MVVM Framework
Custom View Models can be implemented by simply inheriting from the base class ViewModelBase
. To raise a PropertyChanged
event, a type safe RaisePropertyChanged
method can be called with an expression containing your property reference, e.g.:
RaisePropertyChanged(x=>MyProperty);
ViewModelBase
also contains a Navigate
method that can navigate the phone to a bound view performing data binding behind the scenes (more on this in the next section), e.g.:
MyViewModel viewModel = new MyViewModel();
viewModel.Navigate();
Easier Navigation via Declarative View-ViewModel Bindings
In WPF, DataTemplates could bind a ViewModel to a View on an application level. This binding enables runtime View generation within ContentPresenter
. I wanted to extend the same idea to Windows Phone 7 screen navigation since URL based navigation within an application seems error prone and passing query parameters is tedious.
The ViewSelector
class provides this declarative binding capabilities in conjunction with the ViewBinding
class. Basically, in your application resources, you can define this mapping:
<Framework:ViewSelector x:Key="ApplicationViewSelector">
<Framework:ViewBinding ViewModelType="INameU.ViewModels.NameListViewModel"
ViewUri="/Views/NameListView.xaml" />
<Framework:ViewBinding ViewModelType="INameU.ViewModels.NameDetailViewModel"
ViewUri="/Views/NameDetailView.xaml" />
</Framework:ViewSelector>
where Framework is mapped as:
xmlns:Framework="clr-namespace:MixModes.Phone.Synergy.Framework;
assembly=MixModes.Phone.Synergy"
Once you have done this, you can call the Navigate
method from your ViewModel instance to navigate to the mapped view and data binding to it at the same time, without using any URL(s)!
DockPanel and WrapPanel Controls
I have ported both DockPanel
as well as WrapPanel
from the Silverlight Toolkit for Windows Phone 7 so you do not have to reference the toolkit if you are not using any additional functionality other than these panels.
EventToCommand TriggerAction
A common pain point is the absence of the Command
and CommandParameter
properties on controls such as Button
and ListBoxItem
. To fill this gap, the EventToCommand
trigger action can be used to map a RoutedEvent
to an ICommand
instance along with a CommandParameter
instance. To bind the RoutedCommand
to an EventToCommand
trigger, you need to add the trigger to the associated framework element:
<Button Content="Click Me">
<Custom:Interaction.Triggers>
<Custom:EventTrigger EventName="Click">
<Triggers:EventToCommand
Command="{Binding Path=DataContext.ShowDetailsCommand, ElementName=Page}"
PassEventArgsToCommand="True"/>
</Custom:EventTrigger>
</Custom:Interaction.Triggers>
</Button>
The PassEventArgsToCommand
property, when set to true, passes the RoutedEventArgs
as a CommandParameter
to the associated ICommand
’s Execute
method.
ScrollLoadBehavior
ScrollLoadBehavior
allows asynchronous loading of data when the user scrolls a scroll viewer vertically to the bottom. ScrollLoadBehavior
has an associated ICommand
and CommandParameter
that are executed anytime the scroll viewer reaches its bottommost point. Loading is detected via ICommand
’s CanExecute
method. For example:
<ListBox DataContext="{Binding}"
ItemsSource="{Binding Path=Names}">
<Custom:Interaction.Behaviors>
<Behavior:ScrollLoadBehavior Command="{Binding Path=LoadNamesCommand}" />
</Custom:Interaction.Behaviors>
</ListBox>
Notice the explicit DataContext="{Binding}"
in the ListBox
. This is necessary as without explicit parent data binding, ItemsSource
sets the DataContext
to its value, thereby failing the binding for ScrollLoadBehavior
.
Out of the Box Converters
Many frequently used out of the box converters have been provided in the toolkit:
EnumMatchConverter
– Matches a value of an enum member with a constant value and returns a boolean indicating the match. For example, if you want to enable a control when the state is StateEnum.IsError
, you can do the following:
Enabled="{Binding Path=State,
Converter={StaticResource EnumConverter}, ConverterParameter=IsError}"
NotConverter
– Inverts the value of a boolean expression.VisibilityConverter
– Based on a boolean value, shows (if value is true) or collapses (if value is false) visibility of a control.PipeConverter
– Pipes a value through multiple converters to compute the end value of the binding expression. For example, if you want to match a value to an enumeration member Loading
and then determine the visibility of a control, you could declare a pipe converter as follows:
<conv:PipeConverter x:Key="LoadingMessageVisibilityConverter">
<conv:EnumMatchConverter />
<conv:VisibilityConverter />
</conv:PipeConverter>
The values are piped from top to bottom, hence first EnumMatchConverter
is invoked and then VisibilityConverter
. To use it, simply refer to the PipeConverter
as follows:
Visibility="{Binding Path=State,
Converter = {StaticResource LoadingMessageVisibilityConverter},
ConverterParameter=Loading}"
ThemeBasedResourceConverter
– This converter returns resources based on the theme of Windows 7 Phone (either dark or light). To use this converter, we need to point to both theme resources via the LightThemeResource
and DarkThemeResource
properties. For example, if your application has navigation button images for both themes as NavigationLight.png and NavigationDark.png, you could use ThemeBasedResourceConverter
to set the appropriate image as the background of a button as follows:
<phone:PhoneApplicationPage.Resources>
<Converters:ThemeBasedResourceConverter x:Key="ThemeBasedResourceConverter"
LightThemeResource="../Resources/NavigationLight.png"
DarkThemeResource="../Resources/NavigationDark.png" />
</phone:PhoneApplicationPage.Resources>
And then refer to the above ThemeBasedResourceConverter
in the button:
<Button>
<Image Stretch="None"
Source="{Binding Path=., RelativeSource={RelativeSource Self},
Converter={StaticResource ThemeBasedResourceConverter}}">
</Image>
</Button>
So that’s all in v1 folks. I will be happy to receive any feedback you may have, and will continuously develop the Synergy toolkit to make your development easier whether you code in WPF, WP7, or Silverlight!