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.
Synergy toolkit can be accessed at its codeplex home where Santa (myself) keeps delivering new gifts and wishes everyday. I have released v1 of Windows Phone 7 library in the downloads section.
For a background on WPF synergy toolkit, you can refer to my previous blog here. I also wrote a docking windows framework in 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
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 command can be executed anytime. Simply speaking, one can instantiate CommandBase class as follows:
CommandBase myCommand = new CommandBase(arg=>Foo(arg), arg=>CanFoo(arg));
Moreover since WP7 and Silverlight do not provide out of the box capability to bind
Dependency System Enhancements
Several enhancements in 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
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 data context of associated object does not propagate to these entities. This is so because Behaviors and Triggers are attached properties within the static Interaction class (declared in 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 associated object as well as evaluate the most recent value of the binding.
For instance 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 Command property.
There are certain rules to ensure that ObservableBinding works as expected for Binding properties. Basically anytime 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 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 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 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 dependency property of the actual DependencyObject, ObservableDependencyProperty instance must be attached to it via AddValueChanged method:
_verticalScrollChange.AddValueChanged(_scrollViewer);
Similarly to detach or stop property change tracking, 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 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(); // Note: For this to work, your application must inherit from PhoneApplication class (see below)
Easier Navigation via Declarative View-ViewModel Bindings
In WPF DataTemplates could bind a ViewModel to View on an application level. This binding enabled 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.
ViewSelector class provides this declarative binding capabilities in conjunction with 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 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 Command and CommandParameter properties on controls such as Button and ListBoxItem. To fill this gap EventToCommand trigger action can be used to map a RoutedEvent to an ICommand instance along with a CommandParameter instance. To bind the RoutedCommand to 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>
PassEventArgsToCommand property when set to true, passes the RoutedEventArgs as CommandParameter to the associated ICommand’s Execute method.
ScrollLoadBehavior
ScrollLoadBehavior allows asynchronous loading of data when user scrolls a scroll viewer vertically to the bottom. ScrollLoadBehavior has an associated ICommand and CommandParameter that are executed anytime 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 enum member with a constant value and returns 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 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 one needs to point to both theme resources via 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 Synergy toolkit to make your development easier whether you code in WPF, WP7 or Silverlight !