Using IValueConverter in WinRT from C++
Nico Vuyge
2011-12-26
I'm currently experimenting with writing Windows 8 WinRT applications using C++, and wanted to use WPF value converters. This is basically the same in C++ as in C#, except for the C++ syntax. But there's one complication, which is why I'm writing this post.
Start by declaring your value converter in a C++ header file, in my case 'DistanceConverter.h':
namespace Utils
{
public ref class DistanceConverter : public Windows::UI::Xaml::Data::IValueConverter
{
public:
Platform::Object^ Convert(Platform::Object^ value, Platform::String^ typeName, Platform::Object^ parameter, Platform::String^ language)
{
// TODO: real implementation
return value;
}
Platform::Object^ ConvertBack(Platform::Object^ value, Platform::String^ typeName, Platform::Object^ parameter, Platform::String^ language)
{
// TODO: real implementation
return value;
}
};
}
There's not much to see here, imagine those hats and colons weren't there and you have C# instead of C++.
In XAML, you'll want to create such a convertor in a resource, and reference it in your databinding:
...
<UserControl x:Class="xxx.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:utils="using:Utils"
mc:Ignorable="d"
d:DesignHeight="768" d:DesignWidth="1366">
<UserControl.Resources>
<utils:DistanceConverter x:Key="distanceConverter" >
</UserControl.Resources>
...
<Slider Grid.Column="1" Grid.Row="1" Minimum="10" Maximum="1000000" Value="{Binding Distance, Mode=TwoWay, Converter={StaticResource distanceConverter}}" >
If this was C#, we 're done. However, this is C++ and we get the following error when we try to build: "XamlCompiler error WMC0001: Unknown type 'DistanceConverter' in XML namespace 'using:Utils'". It turns out that somebody else had posted the same question here a couple of months ago, but the promised sample code hadn't been posted yet. So I investigated what was going wrong here:
If you think about it, we're bound to have a compilation problem. We've written 'DistanceConverter.h', but we haven't included it anywhere, so the compiler won't generate any code for it, so there's nothing XAML can reference.
So where should we include this 'DistanceConverter.h'? After investigating the project structure, it turns out that the Visual C++ build system automatically generates a couple of C++ files in the output directory (XamlTypeInfo.g.h and XamplTypeInfo.g.cpp) that glue everything together. XamlTypeInfo.g.cpp starts with the following code:
#include "pch.h"
#include "XamlTypeInfo.g.h"
#include "App.xaml.h"
#include "MainPage.xaml.h"
...
So apparently the XAML include files for all XAML files in the project are included here automatically. Let's include the header file for our value convertor (DistanceConverter.h) in the corresponding XAML .h file (that looks like a logical place to include this file) and see what happens:
//
// MainPage.xaml.h
// Declaration of the MainPage.xaml class.
//
#pragma once
#include "pch.h"
#include "MainPage.g.h"
#include "DistanceConverter.h"
...
Just including the value converter (IValueConverter) header file to the XAML header file makes the project compile correctly. Even better, when trying this out we can see that the value converter works as expected! If we take a look again at XamlTypeInfo.g.cpp, we see that it has been changed. We find the following generated function in XamlTypeInfo.g.cpp:
IXamlType^ XamlTypeInfo::InfoProvider::XamlTypeInfoProvider::CreateXamlType(String^ typeName)
{
IXamlType^ xamlType = nullptr;
XamlUserType^ userType;
if (typeName == "Windows.Foundation.Object")
{
xamlType = ref new XamlSystemBaseType(typeName);
}
if (typeName == "Windows.UI.Xaml.Controls.UserControl")
{
xamlType = ref new XamlSystemBaseType(typeName);
}
if (typeName == "Utils.DistanceConverter")
{
userType = ref new XamlUserType(this, typeName, GetXamlTypeByName("Windows.Foundation.Object"));
userType->Activator = ref new XamlTypeInfo::InfoProvider::Activator(
[]() -> Platform::Object^
{
return ref new Utils::DistanceConverter();
});
xamlType = userType;
}
... (similar code for other types)
return xamlType;
}
Basically, the C++ build system generates a factory method to create instances for each class referenced in the XAML file! The implementation is actually a linear search through all C++ types referenced in all XAML files of the project, so I can imagine that this might get a bit slow if you use a lot of these classes in your XAML file.
None of this seems to be documented, nor did I find any sample that shows this mechanism. Hopefully this may save you some time in the future. We'll see if this mechanism will remain the same in future Windows 8 builds or not, but for the time being it appears that the C++ build system plays a crucial role in glueing XAML and C++ together.