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.
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, 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 to 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();
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 But
ton 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 !
History
- 30th January, 2011: Initial post