Vidyano v1.2 RTW is here

Published 7/23/2009 by david in Vidyano

Our team has just released the next version of Vidyano. From the official website:

 

Since we officially launched the first version of Vidyano, we have been getting a lot of valuable feedback from our customers and people playing around with the trial version. A lot of this feedback has already been processed and incorporated in this build, going from bug fixes to new features.

New features and improvements

  • OpenPage / HandledByController Part Generator: It is now possible to add OpenPage or HandledByController command buttons on a page by using the wizard. Developers can also choose which argument to pass by simply selecting it from a list that contains all compatible arguments in a given context.
  • Event-system for cross module communication: Modules are now able to communicate with each other by using the new event-system. Modules can register for specific events on which they would like to listen.
  • Constructor arguments: Developers can now use the Vidyano designers to add constructor arguments to page controllers. This comes in handy when using it in combination with the new OpenPage part generator.
  • Module designers: The look and feel of the module designers has been improved slightly so that more space is available to display the layout of the page.
  • Improved page layout parsing: The Vidyano page designer has been improved so that it is able to render a lot more xaml scenarios.
  • Larger models: Previous versions of Vidyano had trouble with larger models; this version has been improved so that the performance doesn't suffer too much.
  • Visual Studio code optimized: We have been reworking some internal code in order to better support changes in the Visual Studio environment, such as the upcoming VS2010 (expect a CTP version soon).
  • Shell Adorners: The default layout of the ShellContainer has been improved with additional AdornerDecorators.
  • Versioning: This is also the first public version of Vidyano which shows your modules automatically take advantage of the versioning system. Modules which have been build and deployed against the previous version don't have to be recompiled or deployed as they will use the internal adapter system.

As stated in the list of improvements, we have begun working on our Visual Studio 2010 compatible release. All Visual Studio related code (EnvDTE) has been refactored in a separate assembly, Vidyano.VisualStudio.VS90, so that we can adapt to changes more quickly. We expect to release a public CTP version pretty soon.

 

Besides the work on Vidyano for Visual Studio 2010, I would like to add that we have a very exciting roadmap ahead of us. So, expect a lot more new features and improvements over the next couple of months !


Basement Projects is a new section on my blog that I’ll use to share some of the projects in create during spare-time in my virtual basement.

This week’s project is all about Head Tracking and how it can be used to navigate in a 3D environment.

 

Download Video

 

Last week, I stumbled across an article that uses Head Tracking to interact with video games. It immediately grabbed my attention so I began to search around on how I could implement this in my own programs. The Head Tracking API used in Torben’s project is provided by Seeing Machines. They have a Non-Commercial License available that provides exactly what I wanted to take advantage off.

 

Before I get started I want to point out that my knowledge of 3D math is very basic. So if you see any formulas that don’t quite add up, drop me a message if you will and I will correct it. Also, the 3D engine I use in this project is probably not what you want for writing a full 3D video game, but since I have no experience with XNA or anything of that sort, I’ll stick with 3D WPF.

The ManagedFaceAPI Project

To keep things simple, I’ve written the Managed FaceAPI Wrapper library using Managed C++ code. I could have easily written a C# interop library that exposes the same functionality but I find that interacting with legacy code is a lot more straight forward with Managed C++. The library exposes the Engine class which is a wrapper for the FaceAPI C-calls.

 

The code used in Start method is just a copy-paste of the code found on the bottom of the FaceAPI Specifications page. Same goes for the Stop method.

void ManagedFaceAPI::Engine::Start(bool showVideoDisplay)
{
	// Ensure that only one engine is active
	if(Current != nullptr)
		return;

	// Save a reference for the current engine
	Current = this;

	// Register a callback function to receive log messages
	smLoggingRegisterCallback(0, receiveLogMessage);

	smAPIInit();

	// Register windows driver model (WDM) cameras
	smCameraRegisterType(SM_API_CAMERA_TYPE_WDM);
	
	// Create a new Head-Tracker engine
	smEngineCreate(SM_API_ENGINE_LATEST_HEAD_TRACKER,&engine);

	// Register a callback function to receive the tracking data
	smHTRegisterHeadPoseCallback(engine, 0, receiveHeadPose);

	// Fine-tune the engine
	smHTSetTrackingRanges(engine, 0.10f, 0.8f);
	smHTSetRestartThreshold(engine, 30.0f);

	// Create and show a video-display window
	destroyVideoWindow = showVideoDisplay;
	if(showVideoDisplay == TRUE)
		smVideoDisplayCreate(engine,&video_display,0,TRUE);
	
	// Start tracking
	smEngineStart(engine);
}
void ManagedFaceAPI::Engine::Stop()
{
	// Destroy engine
	smEngineDestroy(&engine);

	// Destroy video display
	if(destroyVideoWindow == TRUE)
		smVideoDisplayDestroy(&video_display);
	
	smAPIQuit();

	// Reset the current engine reference
	Current = nullptr;
}

Notice that I keep a static reference to the Engine class, ensuring that the unmanaged loopback function that receives the Head Tracking coordinates can pass them to the current Engine.

 

Next we’ll take a look at the receiveHeadPose function. This is the heart of our library. This loopback function will be called by the FaceAPI when the tracking engine is running, passing along the position of the head in 3D space, along with its 3D rotation and a confidence factor that indicates how much the values can be trusted.

void STDCALL receiveHeadPose(void *,smEngineHeadPoseData head_pose, smCameraVideoFrame video_frame)
{
	ManagedFaceAPI::P3D ^headPosition = gcnew ManagedFaceAPI::P3D();
	ManagedFaceAPI::P3D ^headRotation = gcnew ManagedFaceAPI::P3D();

	headPosition->X = head_pose.head_pos.x;
	headPosition->Y = head_pose.head_pos.y;
	headPosition->Z = head_pose.head_pos.z;

	headRotation->X = head_pose.head_rot.x_rads;
	headRotation->Y = head_pose.head_rot.y_rads;
	headRotation->Z = head_pose.head_rot.z_rads;

	ManagedFaceAPI::Engine::Current->FireHeadPoseChanged(headPosition, headRotation, head_pose.confidence);
}

As you can see, all this function does is wrap the coordinates it receives and passes to the current Engine class’s FireHeadPoseChanged method. This method will invoke the HeadPoseChanged event of type HeadPoseChangedDelegate.

void ManagedFaceAPI::Engine::FireHeadPoseChanged(P3D ^headPosition, P3D ^headRotation, float confidence)
{
	HeadPoseChanged(headPosition, headRotation, confidence);
}

That’s it for the unmanaged part of the project. On to the managed code :-)

The ManagedFaceAPIDemo Project

This is a WPF (Windows Presentation Foundation) project with a single window, MainWindow. Before jumping in the code, let me show you how I got the 3D world you see in the demo.

Sacred place 3D Scene

Since my 3D modeling skills are basically zero, I began to search around for readily available XAML 3D scenes. As expected, my search came up empty. So I needed to find a 3D program that is able to export some of the more popular file formats to XAML. A post on the XNA forums suggested a program called DeleD Lite for creating 3D game levels. After installing DeleD Lite I noticed it containing a sample called SacredPlace. This 3D scene was exactly the kind I wanted in my demo:

 

DeleD

 

Unfortunately, DeleD LITE has no export functionality to XAML. I’ve hit another wall. A bit of searching brought me to a program called 3D PaintBrush. One of its features was exactly what I was looking for:

Create content for Microsoft Expressions Blend
Export your 3D models (with animations) to XAML for using in Microsoft Expressions Blend

 

Wow, that’s a huge step forward. Now to bridge the gap between DeleD Lite and 3D PaintBrush. One of the common file formats between the two is Wavefront OBJ:

 

ExportToWavefront

Import

 

One Export/Import later I am where I was hoping to be:

 

3DPaintBrush

 

Just one export away from XAML as shown in the following screenshot:

 

ExportToXaml

 

Now that I have my 3D scene, I can start integrating it in my WPF application.

The Demo Application

In the XAML code of the MainWindow, I added the Viewport3D control. I then split up the 3D Scene so that most of the 3D mesh data is in another file in my project since its rather large. This leaves my 3D Viewport with the following code:

<Viewport3D>
        <Viewport3D.Resources>
                <ResourceDictionary Source="Scene.xaml" />
        </Viewport3D.Resources>

        <!-- ModelVisual3D Data goes here -->

</Viewport3D>

After watching the scene render correctly, it was time to start manipulating the camera according to my head position. I therefore defined two dependency properties, one for the camera position and one for the direction I am looking at.

public static readonly DependencyProperty PositionProperty = DependencyProperty.Register("Position", typeof(Point3D), typeof(MainWindow), new UIPropertyMetadata(null));
public static readonly DependencyProperty LookDirectionProperty = DependencyProperty.Register("LookDirection", typeof(Vector3D), typeof(MainWindow), new UIPropertyMetadata(null));

These properties can now be bound to the camera on the Viewport3D control.

<Viewport3D.Camera>
    <PerspectiveCamera Position="{Binding Position}" LookDirection="{Binding LookDirection}" FieldOfView="45.0" />
</Viewport3D.Camera>

The constructor of the MainWindow starts the FaceAPI engine:

engine = new Engine();
engine.Start(true);

engine.HeadPoseChanged += new HeadPoseChangedDelegate(Engine_HeadPoseChanged);

The argument passed to the Start method indicates that we also want to view the FaceAPI webcam video data.

The Engine_HeadPoseChanged event handler is then called every time the FaceAPI detects a new head position in 3D space.

private void Engine_HeadPoseChanged(P3D headPosition, P3D headRotation, float confidence)
{
    Dispatcher.BeginInvoke(new Action(() => { Confidence = Convert.ToInt32(100.0 * confidence); }));

    if (confidence == 0.0)
    {
        if (!isSearching && !whereAreYou)
        {
            whereAreYou = true;
            Dispatcher.Invoke(new Action(() => { whereRU.Visibility = Visibility.Visible; }));
        }

        return;
    }

    if (isSearching)
    {
        isSearching = false;
        Dispatcher.Invoke(new Action(() => { searching.Visibility = Visibility.Collapsed; }));
    }

    if (whereAreYou)
    {
        whereAreYou = false;
        Dispatcher.Invoke(new Action(() => { whereRU.Visibility = Visibility.Collapsed; }));
    }

    Dispatcher.BeginInvoke(new Action(() =>
    {
        var rX = RadianToDegree(headRotation.X);
        var rY = RadianToDegree(headRotation.Y);

        rX *= -1; // Invert axis
        rY *= 2.5; // Faster horizontal movement

        if (compensateRotation)
            yRotateCompensation += rY / 2;
        
        rY += yRotateCompensation;

        Transform3DGroup transformGroup = new Transform3DGroup();
        transformGroup.Children.Add(new RotateTransform3D(new AxisAngleRotation3D(new Vector3D(1, 0, 0), rX)));
        transformGroup.Children.Add(new RotateTransform3D(new AxisAngleRotation3D(new Vector3D(0, 1, 0), rY)));

        var now = DateTime.Now;
        BeginAnimation(LookDirectionProperty, new Vector3DAnimation(transformGroup.Transform(new Vector3D(Position.X, Position.Y, Position.Z)), TimeSpan.FromMilliseconds((now - lastFrame).TotalMilliseconds * 5)));
        lastFrame = now;
    }));
}

This is the heart of our demo application. The flags at the beginning of the method (isSearching, whereAreYou) provide feedback to the user. The confidence property set on the first line is shown in the top-right corner of the MainWindow at runtime. After performing some transformations to the X and Y axis we finally animate the LookDirection property to the new direction. The animation is there to smoothen the movement from one position to another.

 

Now that we are able to look around, let’s focus on movement. I wanted to move in the direction we are looking, so I had to brush up on my 3D math. At first I wrote the code in the HeadPoseChanged method, but that slowed things down a lot. So I decided to split up the code in another thread that will check and update the position at regular intervals:

private void Timer_Tick(object sender, EventArgs e)
{
    var lookDirection = LookDirection;
    if (moveForward && lookDirection != null)
    {
        lookDirection.Normalize();
        lookDirection = Vector3D.Multiply(lookDirection, 5);

        var newPosition = Vector3D.Add(new Vector3D(Position.X, Position.Y, Position.Z), lookDirection);
        Position = new Point3D(newPosition.X, /* Discarding Y-value, unless you want to fly */ Position.Y, newPosition.Z);
    }
}

That’s all there is to it !

Getting The Code

As usual, I’ve uploaded the code to my SkyDrive account. Note that in order to compile it, you will have to download the FaceAPI from the SeeingMachines website. There is also a binary version available for download, which should run just fine when you have a webcam installed. Make sure to install the prerequisites as well, as they contain the redistributable binaries for the FaceAPI.

Source

Prerequisites and Binaries

Final Thoughts

What began as a random idea turned out to be one of the most amusing basement projects I’ve created so far. The fact that I was able to accomplish what I had in mind with so little code was very rewarding. The whole project took me just over a day from idea to a working proof of concept. Also, if I had known that an API as FaceAPI existed for quite some time now, I would’ve created this much sooner. As a final note, I would just like to point out that at some times I became a bit dizzy from the effect on screen, so if you decide to play around with your own Head Tracking project, be prepared to take a break :-)

 

kick it on DotNetKicks.com


Vidyano – An interview

Published 6/25/2009 by David in Vidyano
Tags:

A couple weeks ago I was able to present a small demo of Vidyano to Katrien De Graeve, developer evangelist at Microsoft Belgium & Luxemburg. I showed some of the capabilities as well as some finished applications which were build with the help of Vidyano. After seeing all this in action, Katrien decided it was time to create a demo video so that other developers may be aware of what’s out there.

 

Last friday I went back to Zaventem, where we recorded a Dutch and an English video. In these video’s I explain some of the basic concepts of Vidyano and build a demo application on top of the Adventure Works database. If you are interested in finding out how you can write applications like the one in my previous blog post, watch them on the MSDN Chopsticks site:

 

vydiano_interview_thumb vydiano_interview_thumb
Interview in Dutch Interview in English

 

Source


WPF Presentation

Published 6/24/2009 by David in .NET
Tags:

A topic I just had to resurrect from my recently deceased blog is my WPF presentation I have been giving for the past year and a half. Although some people have declared me a bit insane at the time I was making it, it certainly has made up for it by a long shot.

 

The fun thing about this presentation is that it is entirely written in WPF. At the time of making this, I figured it would have been sad to present a major new User Interface technology using plain old Microsoft PowerPoint. There would’ve been way to much switching between demo’s and slides. So I wrote a little PowerPoint slideshow look-a-like and have it shown my demo’s in-place.

 

I have updated it a bit recently so that it includes some of the latest features of WPF as well such as pixel shaders and string formats. I will update this post when I manage to get that latest build online.

In the meantime you can download the “older” presentation from my SkyDrive account (sources or binaries):


Cura Comfort

Published 6/24/2009 by David in Vidyano
Tags: ,

A couple weeks ago, Rhea finished the development on Cura Comfort, a software product targeting the healthcare market. Cura Comfort was created with the help of Vidyano and already turned out to be a huge success.

 

The goal of this project was to facilitate the end users in keeping detailed files for patients each time they visit. Also, the application needs to run on Netbook pc’s, so system requirements are something to keep a close eye on. As visits usually to take no longer that 10 minutes, entering this data in the application needed to be fast and intuitive. Master data for this application comes from another, already in production, application called Cura. This application is able to plan visits for all care providers in a doctors practice any lots more. So synchronization between these two applications had to be developed as well.

 

First off, a custom Vidyano skin was written for the application, which shows the client’s profile color scheme. We’ve also chosen to implement a breadcrumb system for navigating between pages in the application. As you can see in the screenshots below, the result is quite beautiful. The entire application took merely 6 weeks to develop (from start to finish) by 3 juniors and 1 senior developer.

 

CC1 CC2

 CC3 CC4

 

I will try and get a short movie from the application in action, which will give you a better idea on how it works. A more detailed case study can also be found on the Vidyano website, here.


Switching to BlogEngine.NET

Published 6/23/2009 by David in Blog

So I finally decided to make the switch. After seeing BlogEngine.NET running on the Vidyano website, I was quite impressed with its stability and the features it has to offer. My blog was previously running on WordPress. While WordPress is a very fine blogging solution, the fact that it’s running on PHP is what made me switch to a .NET based engine. To be completely honest, having to apply regular security updates to both PHP as well as WordPress became a bit of a hassle to me. Thanks to Windows Update, I can eliminate half the work there and feel a lot safer.

 

I am currently looking into migrating my previous blog posts to this blog. So far it has been a disaster, so I don’t know if I will make the time to persist on getting this done. I’d rather focus on new content and will probably resurrect an older topic when I see fit.

 

On a side note, I also started using Windows Live Writer to write blog posts. I’m very excited so far as this will definitely make it a lot easier for me to maintain this blog.


Real-Time Multilingual WPF Demo

Published 8/3/2008 by David in .NET
Tags:

Introduction

realtime_multilingual_wpf_demo1

Download source code


Something to keep in mind in the very early stages of your application development cycle is whether or not you want to offer your end-users multilingual support. We found two articles on The Code Project that tackle this problem using various approaches (see Localizing WPF Applications using Locbaml and WPF Multi-Lingual at Runtime for more info). With the development of one of our latest products, Vidyano, our goal is to offer developers a set of tools that allow them to create full-blown WPF applications much faster. Both approaches didn't really met our needs because they are way too complex for what we had in mind. In light of this, we started from a completely different approach which we would like to share with the community, in the hope that more applications will offer support for multilingual user interfaces.

Reinventing the wheel?


With today's modern translation software we already have all the tools we need to translate our applications without the need to hire an independent translation agency. In March this year Google introduced a new online service called the Google AJAX Language API. This service allows us to translate blocks of text from within a webpage or an external application. This made us think about the whole concept of offering translation services from within our applications. Wouldn't it be nice if we could fallback on this service to offer the end-user a quick and dirty translation of the application they are currently using? Of course, at the moment this translation will almost never be exactly what you want it to be, but it could allow you to get a basic translation of your application that you can fine-tune in a later stage. We could even hand this basic translation to an independent translation agency and have them clean up the bits.


There are a couple of samples on the API pages that show us how to call the service from a non-javascript language. All we have to do to get us started is implement this functionality in any .NET language.

Making the call

The url we need to call to get a response from the Language API is as follow:

 

http://ajax.googleapis.com/ajax/services/language/translate?v=1.0&q=Hello%World&langpair=en%7Cfr

 

The "Hello%20World" in the url above is our text we need to translate, together with the language pair at the end we query the service to translate this block of text from English to French. At the time of writing, there are no less than 24 languages available. Let's make this call from within C#:

// Create a WebRequest, passing in the text to translate along with the source and target language
var url = string.Format("http://ajax.googleapis.com/ajax/services/language/translate?v=1.0&q={0}&langpair={1}%7C{2}", str, source.Code, target.Code);
var req = (HttpWebRequest)WebRequest.Create(url);
 
// (Note: You must supply a valid referer header !)
// req.Referer = Get your Google API Key at http://code.google.com/apis/ajaxsearch/key.html ...
 
WebResponse response = req.GetResponse();
var streamReader = new StreamReader(response.GetResponseStream());

This streamReader will hold the response for our request. In the Google AJAX API's case, this response is returned to us in the JSON format. Luckily for us, there is already a JSON project available for .NET, called JSON.NET. We can use this library to deserialize the response to a .NET object.

 

We need two classes to store the response data:

/// <summary>
/// JSON Response Class.
/// </summary>
class TranslationResponse
{
    [JsonProperty("responseData")]
    public Translation Data { get; set; }

    [JsonProperty("responseDetails")]
    public string Details { get; set; }

    [JsonProperty("responseStatus")]
    public int Status { get; set; }
}

/// <summary>
/// JSON Translation Response.
/// </summary>

class Translation
{
    [JsonProperty("translatedText")]
    public string TranslatedText { get; set; }
}

 

Now that we have a class definition that can hold our response data, we can use the JSON.NET deserializer to get an instance from TranslationResponse.

var serializer = new JsonSerializer();
var translationResponse = (TranslationResponse)serializer.Deserialize(new StringReader(streamReader.ReadToEnd()), typeof(TranslationResponse));

The translationResponse.TranslatedText property will return the translated text for our request.

Applying extension methods

The code we just wrote can easily be written in an extension method on the string class. Our static GoogleTranslateExtensions class implements this extension method.

public static string Translate(this string str, Languages.Language source, Languages.Language target)

Languages.Language is a nested class that contains the language description, the ISO code and a flag that defines whether this is a LeftToRight or RightToLeft language.

public class Language
{
    internal Language(string desc, string code) :
        this(desc, code, false) { }

    internal Language(string desc, string code, bool rightToLeft)
    {
        Description = desc;
        Code = code;
        RightToLeft = rightToLeft;
    }

    public string Description { get; private set; }
    public string Code { get; private set; }
    public bool RightToLeft { get; private set; }
}

The static Languages class is there for convenience reasons. It returns a static instance of the Language class for each available language.

public static class Languages
{
    static Languages()
    {
        English = new Language("English", "en"); languages.Add(English);
        ...
    }

    public static Language English { get; private set; }
    ...
}

This way we can very easily translate strings in our application by for instance typing the following code:

public static void Main()
{
    // You may have to set your proxy here first
    // GoogleTranslateExtensions.Proxy = new WebProxy("xxx.xxx.xxx.xxx", 8080);

    // Will write "Bonjour monde" to the console window.
    Console.WriteLine("Hello World".Translate(Languages.English, Languages.French));
}

Note the Proxy on the static GoogleTranslateExtensions class. You may have to set your proxy here.

Moving to Windows Presentation Foundation

When moving to WPF, we need a way to translate all of those hardcoded strings we define in our XAML page. There are several possibilities to achieve this, we could for instance use a method binding and pass the string as an argument. However, we decided to implement this functionality by offering the developer a markup extension that can easily be wrapped around the string that needs translating. This is the TranslateExtension class defined in the Vidyano.Presentation project. This markup extension takes a string as parameter in its constructor.

<TextBlock Text="{vi:Translate Hello World}" />

The TranslateExtension class derives from the abstract MarkupExtension class. This markup extension will return a BindingExpression whenever its ProvideValue method is called by the .NET runtime, so it is defined as follow:

[MarkupExtensionReturnType(typeof(BindingExpression))]
public class TranslateExtension : MarkupExtension

Before we dive any further into this class, I would like to show you another one first, the LanguageSelector class. This class is a custom ContentControl control class and defines the scope for our translation. Every object in its content can define the {vi:Translate ... } markup extension. The reason for this wrapper control is that the markup extension needs to know from which language, to which language it needs to translate. This is done by adding two attached properties on the LanguageSelector class: SourceLanguageProperty and TargetLanguageProperty. By defining these attached properties with theFrameworkPropertyMetadataOptions.Inherits option, we effectively allow all child objects in the tree to query the current source and target language.

So the following XAML code is what you would write to translate some text in a TextBlock.

<vi:LanguageSelector xml:lang="en-US">
    <TextBlock Text="{vi:Translate Hello World}" />
</vi:LanguageSelector>

The xml:lang attribute defines the source language for this scope. Let's go back to our markup extension.

As I mentioned before, the TranslateExtension class returns a BindingExpression from its ProvideValue method. You might wonder why we simply don't return the translated text. This is because we would like the text to be updated whenever we change the target language on our LanguageSelectorparent control. In order to accomplish this we need to set a converter on the binding we return. This is the TranslateConverter class. This converter class however is a bit more complicated than the average converter.

As with any converter class, this class implements the IValueConverter interface. Where this converter class differs from your average converter is the fact that it derives from the FrameworkElement class. This allows us to define two dependency properties on the converter, source and target language, which we will bind to the attached properties on the LanguageSelector class. Now, whenever the Convert method is called on our converter, the converter will use its source and target language dependency properties to call the Translate extension method which we created at the beginning of this article.

Languages.Language sourceLang = Languages.FromString(SourceLanguage);
Languages.Language targetLang = Languages.FromString(TargetLanguage);

if (sourceLang != null && targetLang != null && sourceLang != targetLang)
{
    // Asynchronously invoke the Translate method
    Action translate = () => translation = text.Translate(sourceLang, targetLang);
    translate.BeginInvoke(Translated, null);

    // Return "Loading..." as long as the translation is in progress
    return LanguageSelector.GetLoadingString(targetObject);
}

return text;

There's a catch here though, the Convert method is only called once as long as the text doesn't change. So we need to find a way to trigger an invalidation of this converter whenever the source or target language have changed. This is accomplished by hooking in the changed handlers on our dependency properties:SourceLanguageChanged and TargetLanguageChanged.

In these handler methods we make sure the following code is called:

var converter = obj as TranslateConverter;
if (converter != null)
{
    // Invalidate the binding on our target object
    BindingExpressionBase expression = BindingOperations.GetBindingExpressionBase(
        converter.targetObject, converter.targetProperty);
    if (expression != null)
        expression.UpdateTarget();
}

The targetObject and targetProperty are passed to us in the constructor. Let's jump to the creation of our converter.

// Get the TargetObject and TargetProperty via the IProvideValueTarget service
var provideValueService = (IProvideValueTarget)serviceProvider.GetService(
    typeof(IProvideValueTarget));
if (provideValueService == null)
    return null;

var targetObject = provideValueService.TargetObject as DependencyObject;
var targetProperty = provideValueService.TargetProperty as DependencyProperty;

if (targetObject != null && targetProperty != null)
{
    // There might already be a Binding
    if (Binding == null)
        Binding = new Binding();

    // Create the Converter, passing the targetObject and targetProperty
    var converter = new TranslateConverter(targetObject, targetProperty);

    Binding.Converter = converter;
    // Text may be string.Empty if a the markup extension is created with a Binding
    Binding.ConverterParameter = Text;

    // Bind the converter's SourceLanguageProperty and TargetLanguageProperty
    // to the attached properties
    var sourceLanguageBinding = new Binding
    {
        Path = new PropertyPath("(0)", LanguageSelector.SourceLanguageProperty),
        Source = targetObject
    };

    var targetLanguageBinding = new Binding
    {
        Path = new PropertyPath("(0)", LanguageSelector.TargetLanguageProperty),
        Source = targetObject
    };

    converter.SetBinding(TranslateConverter.SourceLanguageProperty,
        sourceLanguageBinding);
    converter.SetBinding(TranslateConverter.TargetLanguageProperty,
        targetLanguageBinding);

    // Return the new/updated binding
    return Binding.ProvideValue(serviceProvider);
}

return null;

The first thing to note here is the Binding. You may also use the markup extension with a binding instead of a hardcoded string. This allows you to go beyond simple UI translation and also offer translations for your data.

<vi:LanguageSelector xml:lang="en-US">
    <TextBlock Text="{vi:Translate Binding={Binding Description}}" />
</vi:LanguageSelector>

In the above sample, the textblock will bind to a Description property on the current DataContext's object. The texblock will refresh its Text whenever either the target or source language changes or whenever the Description property changes.

That's all we need to get a real-time multilingual application. There's just one thing I would like to add.

Caching and Fine-Tuning Translations

In order to go a bit easy on the Language API web requests, we added a small cache to the project. Text that needs to be translated will first be checked against a small SQL Compact Database file which will be copied to your working directory, if it didn't exist already, whenever the program starts translating. Besides its caching abilities, this local storage also adds another powerful feature to your application.

As we all know, sometimes translations can be a little "off". By opening the WPF window and selecting your target language, all translations are written into this cache. This means that if you change the rows inside the local cache, you can fine-tune your translations. The next time you open your application or change the target language, you will get your more accurate values from the cache instead of the Google AJAX Language API.

lock (Cache)
{
    // Look for the text block in the Source table.
    langSource = Cache.Source.FirstOrDefault(s => s.LangCode == source.Code &&
        s.Value == str);
    if (langSource != null)
    {
        // Get the translation for the text block and the target language from
        // the Translations table.
        Translations trans = langSource.Translations.FirstOrDefault(
            t => t.LangCode == target.Code);
        if (trans != null)
            return trans.Value;
    }
    else
    {
        // Insert the text block in the Source table.
        langSource = new Source { LangCode = source.Code, Value = str };
        Cache.Source.InsertOnSubmit(langSource);

        Cache.SubmitChanges();
    }
}

...

lock (Cache)
{
    // Some other thread might already added this information, so check first.
    Translations trans = langSource.Translations.FirstOrDefault(
        t => t.LangCode == target.Code);
    if (trans == null)
    {
        // Add the new translation for the text block.
        langSource.Translations.Add(new Translations {
            LangCode = target.Code, Value = translationResponse.Data.TranslatedText });
        Cache.SubmitChanges();
    }
}

Source Code


Download source code from SkyDrive


David Sleeckx's weblog

covering his work, research and programming-related interests