Table Of Contents
As some of you may know, myself and several of my regular partners in crime work with WPF. I have personally been working with it for quite a few years now, and have seen many articles/discussions and frameworks/CLR improvements occur. That said, I have always felt that there was something missing in terms of a real world framework that can quickly get any level of user up to speed with creating real world applications, quickly with a minimum of fuss.
I discussed this with 2 learned colleagues and together we have come up with what we consider to be one hell of a framework. The outcome of this extensive work that we have been carrying out for you in our spare time is a fully testable, WPF framework that will greatly reduce the amount of time you will need to spend creating a new WPF project. In some cases, we have observed a reduction in time and complexity of as much as 1000%.
And in a world where time means money, we are sure this will mean something to you. If this introduction has spiked your interest please read on, to learn about all the interesting and timesaving approaches we have included in the WPF framework that we have code named "Baboon framework
".
The only pre-requisites are the following:
- VS2008
- .NET 3.5 SP1
- Cinch (included in downloaded code)
- Log4Net (included in downloaded code)
Value Converters
The first thing to see is "Baboon framework
", excellent support for Bindings. Check out this handy IValueConverter
.
using System;
using System.Collections.Generic;
using System.Windows.Data;
using System.Windows;
using System.Globalization;
using BaboonApp.Interop;
namespace BaboonApp
{
[ValueConversion(typeof(String), typeof(String))]
public class BaboonConverter : IValueConverter
{
private static readonly string TargetBaboonString = "Baboon";
#region IValueConverter implementation
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
#if GetMeABaboonNatively
return NativeBaboonWrapper.ConvertToBaboon((string)value);
#else
return TargetBaboonString;
#endif
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
#if GetMeABaboonNatively
return NativeBaboonWrapper.ConvertToBaboon((string)value);
#else
return TargetBaboonString;
#endif
}
#endregion
}
}
Notice the use of the NativeBaboonWrapper
that is used if the GetMeABaboonNatively
compilation symbol is defined, more on this later.
And here is an example of using this within an XAML window binding.
<TextBox Text="{Binding Path=DataEntry1,
Converter={StaticResource BabConv}, Mode=TwoWay}"
Width="150" Margin="5"/>
In this example, we take some user input, and run it through a ViewModel
(yes that's right Baboon Framework
is MVVM ready, right out of the box) ICommand
. Basically what happens is the user types in the TextBox
in the XAML. And then Baboon Framework
does its stuff. And whatever (yes whatever) the user types is instantly turned into the string "Baboon
". See how much time that saves. Whatever is typed, yes whatever, is turned into "Baboon
". Not only does this save time, but it also means we only need one IValueConverter
, and it is also extremely excellent when it comes to testing. If the data entered does not equal "Baboon
", we have an issue. So our test is very small, and elegant. A simple test may look like this:
Assert.AreEqual(dataEntered, "Baboon");
Job done.
Here is a demo. User types a string
.
Baboon Framework
does its job:
And more proof:
ToolTips
We are not limited to using Baboon Framework
like this. Check out another example.We can also use it within ToolTips. Look at these 2 different ToolTips.
Here is some example XAML that achieves these:
<Grid Background="CornflowerBlue" HorizontalAlignment="Stretch" Height="40">
<Label Foreground="Black" Content="Use It In A Tooltip"
FontWeight="Bold" FontSize="14"/>
</Grid>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Stretch" Margin="10">
<Button Content="Look At My Tooltip" ToolTip="{Binding Path=DataStored1,
Converter={StaticResource BabConv}}" Margin="5"/>
<Button Content="Look At My Tooltip" Margin="5">
<Button.ToolTip>
<Image Source="{local:BaboonProvider RequiresImage=true,
BoundDataContext={Binding DataStored1}}" />
</Button.ToolTip>
</Button>
</StackPanel>
But wait, there is even more. Let's see it used on a Label
control.
<Grid Background="LightCyan" HorizontalAlignment="Stretch" Height="40">
<Label Foreground="Black" Content="Use It In Label"
FontWeight="Bold" FontSize="14"/>
</Grid>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Stretch" Margin="10">
<Label Content="{Binding Path=DataStored1,
Converter={StaticResource BabConv}}" Width="150" Margin="5"/>
</StackPanel>
Which you can see right here:
So what else can we do, well read on dear reader.
OpenFileDialog
We can even let the user pick a file of their choice, we can do this straight from the ViewModel, using code similar to this:
public void ExecutePickFileCommand()
{
openfileService.InitialDirectory = @"c:\";
bool? result = openfileService.ShowDialog(null);
if (result.HasValue && !String.IsNullOrEmpty(openfileService.FileName))
{
popupShowerService.ShowDialog("Popup1", null);
messagerService.ShowInformation(String.Format("File picked was Baboon"));
}
}
Which when run will show something like this:.
Brilliant... Isn't it, again anything the user picks is instantaneously converted (using the inbuilt power of Baboon
framework), straight into a "Baboon
". Sheer genius, tell me have you ever seen such raw power.
Markup Extensions
We have not just left it there, oh no, we went all the way to ease your binding woes. We created a dead simple BaboonProvider MarkupExtension
. Which makes it so easy to return "Baboon
" to all your bindings. Here is the BaboonProvider MarkupExtension
.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Data;
using System.Windows.Markup;
using System.Windows.Media.Imaging;
namespace BaboonApp
{
public class BaboonProvider : MarkupExtension
{
public Binding BoundDataContext { get; set; }
public Boolean RequiresImage { get; set; }
public override object ProvideValue(
IServiceProvider serviceProvider)
{
if (!RequiresImage)
{
BoundDataContext.Converter = new BaboonConverter { };
return BoundDataContext.ProvideValue(serviceProvider);
}
else
{
return new BitmapImage(
new Uri("../../Images/baboon.jpg",
UriKind.RelativeOrAbsolute));
}
}
}
}
Which you can use in your own XAML as follows:
<ListView ItemsSource="{Binding PersonData}"
Height="150" Background="LightGray">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Background" Value="LightGray" />
</Style>
</ListView.ItemContainerStyle>
<ListView.View>
<GridView>
<GridViewColumn Header="FirstName" Width="80">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Label Content="{local:BaboonProvider RequiresImage=false,
BoundDataContext={Binding FirstName}}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="LastName" Width="80">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Label Content="{local:BaboonProvider RequiresImage=false,
BoundDataContext={Binding LastName}}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Image" Width="80">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Image Source="{local:BaboonProvider RequiresImage=true,
BoundDataContext={Binding FirstName}}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
Where the source for the ListBox
is declared in the ViewModel as follows:
static PropertyChangedEventArgs personDataChangeArgs =
ObservableHelper.CreateArgs<BaboonViewModel>(x => x.PersonData);
public ObservableCollection<PersonViewModel> PersonData
{
get { return personData; }
set
{
personData = value;
NotifyPropertyChanged(personDataChangeArgs);
}
}
And a new Person
can be created using the following ViewModel ICommand
:
public void ExecuteAddNewPersonCommand()
{
PersonViewModel person = new PersonViewModel();
bool? result = popupShowerService.ShowDialog("NewPersonPopup", person);
if (result.HasValue && result.Value)
{
if (String.IsNullOrEmpty(person.FirstName) ||
String.IsNullOrEmpty(person.LastName))
{
messagerService.ShowInformation(
"You must fill in both first and last names");
}
else
{
personData.Add(person);
messagerService.ShowInformation("Person entered was added");
}
}
}
And here it is in action:
STEP 1: User fills in new Person details
STEP 2: New Person Details Magically Converted To Baboon
Baboon
framework, again saves the user time, by simply returning a new "Baboon
" populated Person. Which I am sure you would agree will speed up unit testing.
Getting the Baboon, Natively
Great as the .NET Framework is, I think we all have come across situations where we need to get some baboon-related data natively. And in those cases, the .NET Framework simply isn't enough.
To work around this limitation, we've included a native baboon converter, that is fully leveraging the power of C++ to get a baboon string.
By declaring a DLL exported header, NativeBaboon.hpp;
extern "C" __declspec(dllexport) const char* convert_to_baboon(const char* input);
and following up with its implementation, NativeBaboon.cpp;
#include "stdafx.h"
#include <string>
#include <exception>
#include "NativeBaboon.hpp"
extern "C" __declspec(dllexport) const char* convert_to_baboon(const char* input)
{
static const std::string baboon_string = "Baboon";
return baboon_string.c_str();
}
we achieve an implementatio0n that is guaranteed to convert any string
content into the more desired string
content of "Baboon
".
By wrapping the interop call to the native DLL in a wrapper class NativeBaboonWrapper
, it is possible to harness the awesome powers of native calls in a .NET-like fashion.
namespace BaboonApp.Interop
{
internal class NativeBaboonWrapper
{
[DllImport("NativeBaboon.dll")]
private static extern string convert_to_baboon(string input);
public static string ConvertToBaboon(string input)
{
return NativeBaboonWrapper.convert_to_baboon(input);
}
}
}
return NativeBaboonWrapper.ConvertToBaboon((string)value);
In some situations, a native baboon might prove to be simply too rad, and in these cases the use of a CLR baboon
is preferred. The BaboonConverter
in this sample project allows changing the converter implementation using a compilation symbol, GetMeABaboonNatively
, that can be set or unset in the project properties window:
Injecting, Destroying and Mocking the Baboon
Last week, a junior developer showed me some code he had written that used some features of the baboon
framework. We like to start junior developers off using baboon because it helps them understand common code problems and how they can be solved effectively. The code he showed me used a simple baboon
implementation and a class called Jungle
which consumed it.
namespace BaboonApp.Novice
{
class Jungle
{
public Double Boogie()
{
Baboon boon = BaboonProvider.GetBaboon();
return boon.Foo();
}
}
class Baboon
{
public Double Foo()
{
return 3.84; }
}
}
Great boogie! But what happens if your baboons need to be provided from somewhere other than the default BaboonProvider
in the framework? What happens if your company starts consuming baboons from a 3rd party service? And how can you test your Boogie()
independent of your baboon
source? I sent the underling off in search of an IOC container.
Later that day, he returned with a new version of his code and a basic understanding of the Castle Windsor IOC container. He had refactored the code to benefit from his learnings.
namespace BaboonApp.Intermediate
{
class Jungle
{
public int Boogie()
{
WinsdorContainer container = new WindsorContainer
( new XmlInterpreter(new ConfigResource("castle")));
IPrimateProvider pp = container.Resolve(typeof(IPrimateProvider))
as IPrimateProvider;
Baboon boon = pp.GetBaboon();
return boon.Foo();
}
}
}
Better! Now we can configure the IOC to give us an IPrimateProvider
implementation of our choice at runtime. It could be a 3rd party baboon
provider or even a mock baboon
provider dispensing mock baboons. The internals of Castle Windsor are beyond the scope of this document, but its free and easy to understand just visit the Castle website, or examine the code attached to this article.
However, now we have all these baboons on the loose and we need some kind of controller to manage their lifespans and ensure they don't run amock(programmers joke!). Enter the FactoryFuneral
wrapper. This wrapper is one of the most handy features of the baboon
framework and usefully encapsulates lots of IOC and lifetime management plumbing as you can see.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Cinch;
namespace BaboonApp.BaboonServices
{
public class BaboonFactoryFuneral : IFactoryFuneral<Baboon>
{
#region Data
private IMessageBoxService messagerService;
#endregion
#region Ctor
public BaboonFactoryFuneral()
{
messagerService = ViewModelBase.ServiceProvider.Resolve<IMessageBoxService>();
}
#endregion
#region IFactoryFuneral<Baboon> Members
public Baboon ReleaseOne()
{
BaboonApp.BaboonServices.IBaboonProvider provider =
(IBaboonProvider)ViewModelBase.ServiceProvider.GetService
(typeof(BaboonApp.BaboonServices.IBaboonProvider));
return provider.GetBaboon();
}
public Baboon ReleaseMany()
{
throw new Exception("Dangerous method call attempted");
}
public void Quash(Baboon boon)
{
boon.Dispose();
messagerService.ShowWarning("Death of a Baboon");
boon = null;
}
#endregion
}
}
Where the actual service looks like this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BaboonApp.BaboonServices
{
public interface IFactoryFuneral<T>
{
T ReleaseOne();
T ReleaseMany();
void Quash(T boon);
}
}
A handy class indeed! So with this final piece in place and my junior developer exhausted from all his learning, let's produce the final refactor. Here it is, this is how this service is injected into our service locator instance, inside the ViewModelBase
class, so it is available for all ViewModels:
ViewModelBase.ServiceProvider.Add(typeof(BaboonApp.BaboonServices.IBaboonProvider),
new BaboonApp.BaboonServices.BaboonProvider());
ViewModelBase.ServiceProvider.Add
(typeof(BaboonApp.BaboonServices.IFactoryFuneral<Baboon>),
new BaboonApp.BaboonServices.BaboonFactoryFuneral());
And there you have it, professional baboon
management done in a simple/easy and extensible manner, and we feel it's also done in an easy to understand way.
Further Reading
As if all that was not enough, we also published the much anticipated book, "Learning Baboon Converters 2nd Edition", which should be available from all leading bookstores and online vendors. We have barely scratched the surface of what can be done with Baboon
framework. If you feel this is the framework for you, don't waste any time buying the book, it is a compelling read.
Anyway that's about it for now, but read on to see what future delights await you. This is only the beginning.
At present, the Baboon
framework presented in this article does not deal with other primates, but does deal extraordinarily well with baboons. We have big plans for it to encompass not only all primates, but other animal species as well. Stay tuned for that exciting enhancement.
I am sure you would all agree that the Baboon
framework will be most invaluable with getting you up to speed with using Baboon
in your own WPF apps. This has been a labour of love and involves some of the most complex code that we have ever written. We think the outcome speaks for itself. Simply put, the Baboon
framework presented in this article is best in breed, and is the mother of all WPF frameworks, we feel it leaves no stone unturned, and deals with pretty much any binding need you could encounter in your WPF apps.