A customer was having trouble compiling their C++/WinRT project that used XAML binding.
They had a data template that binds the Content
property of the AwesomeThing
to a TextBlock
‘s text:
<!-- XAML --> <DataTemplate x:DataType="local:AwesomeThing"> <TextBlock Text="{x:Bind Content}" /> </DataTemplate>
But when they tried to compile this, they got a C++/WinRT error:
MainPage.xaml.g.hpp(200): error C2665: 'winrt::to_hstring': none of the 13 overloads could convert all the argument types while trying to match the argument list '(winrt::Windows:: Foundation:: IInspectable)' while compiling class template member function 'void winrt:: Contoso:: implementation::MainPageT< winrt:: Contoso:: implementation:: MainPage>::MainPage_ obj1_ Bindings:: Update_ Content( winrt:: Windows:: Foundation:: IInspectable, int32_t)' see reference to class template instantiation 'winrt:: Contoso:: implementation:: MainPageT< winrt:: Contoso:: implementation:: MainPage>:: MainPage_ obj1_ Bindings' being compiled MainPage.xaml.g.hpp(200): error C2660: 'winrt:: Contoso:: implementation:: MainPageT< winrt:: Contoso:: implementation::MainPage>:: MainPage_ obj1_ Bindings:: Set_ Windows_ UI_ Xaml_ Controls_ TextBlock_ Text': function does not take 1 arguments see declaration of 'winrt:: Contoso:: implementation:: MainPageT< winrt:: Contoso:: implementation:: MainPage>:: MainPage_ obj1_ Bindings:: Set_ Windows_ UI_ Xaml_ Controls_ TextBlock_ Text'
What’s going on here?
Let’s see if we can decode the error message.
The first error says that somebody is trying to call winrt::
with something that can’t be converted to a string. How curious.
The Visual Studio Error List window shows only the first line of each error message. To see the important supplemental information, you have to switch to the Output window. That’s what tells us that the argument that couldn’t be converted to a string is an IInspectable
.
The second error says that somebody passed only one parameter to the Set_
function. This is probably a cascade failure: The function takes two parameters, but one of them ended up being an error, so there was only one valid parameter left.
Looking at the source code confirms this diagnosis:
void Update_Content(::winrt::Windows::Foundation::IInspectable obj, int32_t phase) { if((phase & ((1 << 0) | NOT_PHASED )) != 0) { Set_Windows_UI_Xaml_Controls_TextBlock_Text(obj7, ::winrt::to_hstring(obj)); } }
What we’ve learned so far is that the XAML compiler generated some code that takes an IInspectable
and tries to convert to a string (via to_hstring
), so it can set the Text
property of a TextBlock
.
Gosh, where in our XAML markup could we be trying to assign an IInspectable
to a Text
property?
Well, I made it easy because it is in fact the only binding in the entire XAML fragment: It’s the Text="{x:Bind Content}"
. (The other clue is that the method is named Update_
.)
The Content
property was declared like this:
// Contoso.idl namespace Contoso { runtimeclass AwesomeThing { Object Name{ get; }; Object Content{ get; }; /* and other properties */ } }
Notice that the Content
property is declared as an Object
, which is the MIDL compiler’s name for what in the ABI is called IInspectable
.
And that’s why we have the error. The XAML markup is trying to bind the TextBlock.
property (which is a string) to the AwesomeÂThing.
property (which is an IInspectable
). The XAML compiler sees that this is a type mismatch and tries to coerce the conversion by calling to_hstring
and hoping for the best.
Unfortunately, it didn’t work.
You can fix this in a few ways.
The simplest way is to change the type of the Content
property to String
. Then you’re binding a string to a string, and everything lines up.
On the other hand, maybe your Content
property is an Object
on purpose, say, because it can hold multiple things. Maybe it’s a string. Maybe it’s an integer. Maybe it’s a XAML element tree.
In that case, you’ll have to teach XAML how to convert that mysterious object to a string. You can do this with a converter, or since you’re already invested in x:Bind
, you can use a function binding. Write a function to convert that mysterious object into a string, using an algorithm that describes how you want the conversion to occur. For example, maybe you’ll write it like this:
// Contoso.idl
namespace Contoso
{
runtimeclass AwesomeThing
{
Object Name{ get; };
Object Content{ get; };
/* and other properties */
static String ContentToString(Object o);
}
}
// C++/WinRT implementation
winrt::hstring AwesomeThing::ContentToString(IInspectable const& o)
{
if (auto s = o.try_as<winrt::hstring>()) {
return s.value();
}
if (auto i = o.try_as<int>()) {
// assume that "%d" is acceptable
return winrt::to_hstring(i.value());
}
if (auto e = o.try_as<winrt::FrameworkElement>()) {
// ?? figure out what to return here
return L"";
}
return L"";
}
<!-- XAML -->
<DataTemplate x:DataType="local:AwesomeThing">
<TextBlock Text="{x:Bind local:AwesomeThing.ContentToString(Content)}" />
</DataTemplate>
You might have thought of adding a new property that does the conversion:
// Contoso.idl namespace Contoso { runtimeclass AwesomeThing { Object Name{ get; }; Object Content{ get; }; /* and other properties */ String ContentAsString{ get; }; } } // C++/WinRT implementation winrt::hstring AwesomeThing::ContentAsString() { auto o = Content(); if (auto s = o.try_as<winrt::hstring>()) { return s.value(); } if (auto i = o.try_as<int>()) { // assume that "%d" is acceptable return winrt::to_hstring(i.value()); } if (auto e = o.try_as<winrt::FrameworkElement>()) { // ?? figure out what to return here return L""; } return L""; } <!-- XAML --> <DataTemplate x:DataType="local:AwesomeThing"> <TextBlock Text="{x:Bind ContentAsString}" /> </DataTemplate>
This works, but only because x:Bind
defaults to one-time binding. If you switch to one-way binding:
<!-- XAML -->
<DataTemplate x:DataType="local:AwesomeThing">
<TextBlock Text="{x:Bind ContentAsString, Mode=OneWay}" />
</DataTemplate>
then changes to the Content
property will not trigger a recalculation of ContentAsString
. You have to remember to raise a PropertyChanged
event for ContentAsString
whenever the Content
property changes.
The function binding avoids this need to remember to raise extra property change notifications: Passing Content
as a parameter to the function binding not only provides a parameter to the ContentAsString
function, the XAML compiler will see that the parameter is a property and recalculate the binding whenever Content
changes.
0 comments