Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Baboon Framework : A Revolutionary WPF Framework that will Save You Time and Effort

0.00/5 (No votes)
1 Apr 2010 9  
This is true code, what you say

Table Of Contents

Introduction

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".

Pre-Requisites

The only pre-requisites are the following:

  • VS2008
  • .NET 3.5 SP1
  • Cinch (included in downloaded code)
  • Log4Net (included in downloaded code)

How Does It Work / What Does It Do

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)
            {
                //modify the binding by setting the converter
                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:

/// <summary>
/// DataEntry1
/// </summary>
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:

/// <summary>
/// AddNewPersonCommand
/// </summary>
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
{
    // Wrapper
    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();
        }
    }
    
    //Simple baboon implementation
    class Baboon
    {
        public Double Foo()
        {
           return 3.84; //average height of male baboon
        }
    }
}

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
{
    /// <summary>
    /// BoonPipe is an IOC container wrapper that uses a factory 
    /// pipeline implementation to Emit and Quash baboon instances. 
    /// </summary>
    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

        /// <summary>
        /// IFactoryPipe Method
        /// </summary>
        /// <returns></returns>
        public Baboon ReleaseOne()
        {
            BaboonApp.BaboonServices.IBaboonProvider provider =
                (IBaboonProvider)ViewModelBase.ServiceProvider.GetService
		(typeof(BaboonApp.BaboonServices.IBaboonProvider));
            return provider.GetBaboon();
        }

        /// <summary>
        /// IFactoryFuneralMethod. Considered too dangerous to implement just yet.
        /// </summary>
        /// <returns></returns>
        public Baboon ReleaseMany()
        {
            throw new Exception("Dangerous method call attempted");
        }

        /// <summary>
        /// IFactoryFuneralMethod
        /// </summary>
        /// <returns></returns>
        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.

Known Issues

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.

That's It Folks

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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here