WinUI Stuff #6: How to add a Data Converter to the Data Binding of your WinUI controls (C++)

What is in for you?

In this tutorial I want to talk about Data Converters. This seems to be a less important topic, but once, you will need it, all of a sudden. And then you will either circumvent the problem, or you need to dig into that subject while you rather want to do something entirely different. So, this is what this tutorial is for.

I almost forgot to explain what a Data Converter is for: you usually need it in your XAML code if you want to get a property and the data type of the property is different than the one your code can provide. For example: you want to display a numerical value, let’s say a quantity. Then your code will provide the quantity as an integer, but XAML requires a string. The data converter will then be used to convert the integer to a string.

If you have read tutorial number four (How to program Property Change Notification ), you might have realized that we already had a similar problem, there. By then, we adjusted the property function accordingly and returned a string. In today’s tutorial we will make that right.

So, nothing new in the user experience, but leaner and finally better code. And from my experience, that is a lot to gain!

Oh wait, no extension to the user experience seems to be a bit stale. We will add some functionality to our “Clear” button: whenever the RNA does not contain any nucleotides we will switch off (means: hide) the entire control. And we will also do some formatting of the number of nucleotides being displayed.

At the end of this tutorial, our RNA gets a nicely styled headline and the control which displays the number of nucleotides gets a proper unit.

In this tutorial you will learn about:

  • The implementation of a Data Converter
  • The “Visible” attribute and how to use it within a Data Converter
  • Header property of <ListView> (<GridView>, and many more).
  • Styling of such ‘internal’ elements like “Header”
  • How to use property change notifications. 
  • Boxing and unboxing data of different data types

You will find the code in my GitHub repositories: https://github.com/AgentSmith-Dev/How2-WinUI

What you should already know

It would help you a lot, if you already have read the How 2 number four, since we basically modify that code.

The User Experience

<ListView>’s Header property

My frequent readers know that I always start with the user experience. And there will be no deviation from this routine today.

Since we want to add the capability to hide the RNA, we need to have an element which can be hidden even if the RNA is already empty. Otherwise, we could not see any change if we hide and empty RNA.

Let us use the “Header” property of the <ListView> element. In WinUI3 XAML many controls (<ListView>, <GridView>, …) have a “Header” property with which you can display a text to be used as a headline or description. We will write a simple “RNA”:

<!--    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++    -->
<!--    List to show the RNA -->
<!--    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++    -->
<ListView   x:Name="List_RNA"
    ItemsSource="{x:Bind     GetWinUIRNA_Page_Property.Get_obsvecRNA_Property, Mode=OneWay}"
    ItemTemplateSelector="{StaticResource    Key_WinUI_DataTemplateSelector_RNA_Nucleotids}"
    Width="100"
    Header="RNA"
>
clWinUIControl_RNAList.xaml

Well, alhough it works as promised, it looks a bit boring. It would be nice if we could style it a bit. Of course, WinUI can do that. We just move the “Header” from an attribute of the <ListView> node to a child node:

<!--    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++    -->
<!--    List to show the RNA -->
<!--    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++    -->
<ListView   x:Name="List_RNA"
    ItemsSource="{x:Bind     GetWinUIRNA_Page_Property.Get_obsvecRNA_Property, Mode=OneWay}"
    ItemTemplateSelector="{StaticResource    Key_WinUI_DataTemplateSelector_RNA_Nucleotids}"
    Width="100"
>
    <ListView.HeaderTemplate>
        <DataTemplate>
            <TextBlock  Text="RNA"  Style="{StaticResource  TitleTextBlockStyle}"
                        HorizontalAlignment="Center"
                        />
        </DataTemplate>
    </ListView.HeaderTemplate>
</ListView>
clWinUIControl_RNAList.xaml

I’d say, the code speaks for itself: we define the header in the <ListView.HeaderTemplate> node, then we wrap up everything we want to display in a <DataTemplate> node. Inside this node we have the XAML to display the header. In the example we have a simple <TextBlock> – but you could much more, if you you want to, you could even wrap up another <ListView>.

If you want to just style your headline, you can combine both approaches to make it more readable:

<!--    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++    -->
<!--    List to show the RNA -->
<!--    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++    -->
<ListView   x:Name="List_RNA"
    ItemsSource="{x:Bind     GetWinUIRNA_Page_Property.Get_obsvecRNA_Property, Mode=OneWay}"
    ItemTemplateSelector="{StaticResource    Key_WinUI_DataTemplateSelector_RNA_Nucleotids}"
    Width="100"
    Header="RNA"
>
    <ListView.HeaderTemplate>
        <DataTemplate>
            <TextBlock  Text="{Binding}"  Style="{StaticResource  TitleTextBlockStyle}"
                        HorizontalAlignment="Center"
                        />
        </DataTemplate>
    </ListView.HeaderTemplate>
</ListView>
clWinUIControl_RNAList.xaml

In the last version, we kept the “Header” as an attribute of the <ListView> node. The <ListView.HeaderTemplate> node does the styling. Notice where the text comes from: in the <TextBlock> node we set the text attribute Text=“{Binding}”. This tells the XAML compiler to bind the content of the “Header” attribute to the content of the “Text” attribute.

Nice, isn’t it?!

But let’s stop with playing around with XAML – there is work to do.

The need for a Data Converter

But, when we clear the RNA, the text of the header will still be there. It would be much nicer if there wouldn’t be a header if there aren’t any nucleotides. We need a “Visibility” property for the entire <ListView> control:

<!--    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++    -->
<!--    List to show the RNA -->
<!--    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++    -->
<ListView   x:Name="List_RNA"
    ItemsSource="{x:Bind     GetWinUIRNA_Page_Property.Get_obsvecRNA_Property, Mode=OneWay}"
    ItemTemplateSelector="{StaticResource    Key_WinUI_DataTemplateSelector_RNA_Nucleotids}"
    Width="100"
    Header="RNA"
    Visibility="{x:Bind GetWinUIRNA_Page_Property.iGetRNANbNucleotides, Converter={StaticResource Converter_NoNucleotides}, Mode=OneWay}"
>
    <ListView.HeaderTemplate>
        <DataTemplate>
            <TextBlock  Text="{Binding}"  Style="{StaticResource  TitleTextBlockStyle}"
                        HorizontalAlignment="Center"
                        />
        </DataTemplate>
    </ListView.HeaderTemplate>
</ListView>
clWinUIControl_RNAList.xaml

If you try to compile that code, you get an error like: XamlCompiler error WMC1121: Invalid binding assignment: Cannot directly bind type ‚System.Int32‘ to ‚Microsoft.UI.Xaml.Visibility‘. Use a cast, converter or function binding to change the type.

The compiler tells us that it can not convert to return value of iGetRNANBNucleotides() which is an integer to a system type which is either winrt::Microsoft::UI::Xaml::Visibility::Visible or winrt::Microsoft::UI::Xaml::Visibility::Collapsed.

We need a Data Converter which does this conversion.

We start with the IDL:

namespace How2_DataConverter
{
	[default_interface]
	runtimeclass clWinUI_Converter_NoNucleotides : Microsoft.UI.Xaml.Data.IValueConverter
	{
		clWinUI_Converter_NoNucleotides();
	}
}
clWinUI_Converter_NoNucleotides.idl

That is fairly simply. We just need a converter class which is derived from the interface IValueConverter.

The MIDL compiler does a lot of work for us and already creates the prototypes for two required functions:

C++
namespace winrt::How2_DataConverter::implementation
{
    struct clWinUI_Converter_NoNucleotides : clWinUI_Converter_NoNucleotidesT<clWinUI_Converter_NoNucleotides>
    {
        clWinUI_Converter_NoNucleotides() = default;

        winrt::Windows::Foundation::IInspectable Convert(winrt::Windows::Foundation::IInspectable const& value, winrt::Windows::UI::Xaml::Interop::TypeName const& targetType, winrt::Windows::Foundation::IInspectable const& parameter, hstring const& language);
        winrt::Windows::Foundation::IInspectable ConvertBack(winrt::Windows::Foundation::IInspectable const& value, winrt::Windows::UI::Xaml::Interop::TypeName const& targetType, winrt::Windows::Foundation::IInspectable const& parameter, hstring const& language);
    };
}
clWinUI_Converter_NoNucleotides.h

The Convert() function is the function we need. We will get our integer in the parameter “value” and we will return a winrt::Microsoft::UI::Xaml::Visibility value. I’ll talk about the implementation in a minute. Before, we need to tell XAML to use our newly created converter.

First, we let XAML know about the converter function:

HTML
<Page.Resources>
    <local:clWinUI_Converter_NoNucleotides x:Key="Converter_NoNucleotides"/>
clWinUIControl_RNAList.xaml

Then we can use it:

HTML
<!--    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++    -->
<!--    List to show the RNA -->
<!--    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++    -->
<ListView   x:Name="List_RNA"
    ItemsSource="{x:Bind     GetWinUIRNA_Page_Property.Get_obsvecRNA_Property, Mode=OneWay}"
    ItemTemplateSelector="{StaticResource    Key_WinUI_DataTemplateSelector_RNA_Nucleotids}"
    Width="100"
    Header="RNA"
    Visibility="{x:Bind GetWinUIRNA_Page_Property.iGetRNANbNucleotides, Converter={StaticResource Converter_NoNucleotides}, Mode=OneWay}"
>
    <ListView.HeaderTemplate>
        <DataTemplate>
            <TextBlock  Text="{Binding}"  Style="{StaticResource  TitleTextBlockStyle}"
                        HorizontalAlignment="Center"
                        />
        </DataTemplate>
    </ListView.HeaderTemplate>
</ListView>
HTML

Convert() – the converter function

Let’s have a look at the implementation of the Convert() function:

C++
//  ------------------------------------------------------------------------------------------------------
/*! \brief  Converter function (to be used by XAML) to convert the number of nucleotides to a boolean
*   \date	10/16/2025	AGS Start
*   \param	value   (integer) value with the number of nucleotides
*   \return Collapsed   empty
*           Visible   non-empty
*/
//  ------------------------------------------------------------------------------------------------------
winrt::Windows::Foundation::IInspectable clWinUI_Converter_NoNucleotides::Convert(winrt::Windows::Foundation::IInspectable const& value, winrt::Windows::UI::Xaml::Interop::TypeName const& targetType, winrt::Windows::Foundation::IInspectable const& parameter, hstring const& language)
{
    language; parameter; targetType;

    winrt::Windows::Foundation::IInspectable    IRet;

    //  unbox the number of nucleotides
    int iNb = unbox_value<int>(value);

    //  box the visibility 
    IRet = box_value(iNb != 0   ?   winrt::Microsoft::UI::Xaml::Visibility::Visible 
                                :   winrt::Microsoft::UI::Xaml::Visibility::Collapsed);

    return  IRet;
}
clWinUI_Converter_NoNucleotides.cpp

The function head Convert() is used by all the different converters. Therefore, it does neither know, what data type needs to be converted, nor does it know into what data type needs the conversion to be done. For these kind of problems, WinRT has the ability to wrap up a value in an IInspectable class. That happens in the Convert() function: we get the date to be converted in an IInspectable. And we also return the converted date in an IInspectable. To “unwrap” (WinRT calls it “unboxing a value”) we use the unbox_value<source_type> function. To “wrap” (WinRT calls it “boxing a value”) we use the box_vale<target_type> function.

If you’re not sure about the source type you get, you can also use a conversion with try_as<>:

C++
//  Following is demo code which does nothing
//  if you're not sure about the source type, use the try_as<> version
int iNb_Demo;
if ((value.try_as<int>(iNb_Demo))) {
    //  ok
}
else {
    //  nok
}
C++

Some work to be done in the “machine room”

The code compiles without errors. But when you run it you will see that no proper updating happens when you paste a RNA sequence. The reason is that our <ListView> does not get informed about a change in the observable collection. You may find a detailed explanation and the code that needs to be added in the previous tutorial about Property Change Notification: How to program a Property Change notification (Part II) in user control to keep your UI in sync (C++).

Here, I will just summarize the additional code:

First, our RNA-List control needs be able to handle property change notifications. We add Microsoft.UI.Xaml.Data.INotifyPropertyChanged as a base class in the IDL:

C++
namespace How2_DataConverter
{
    [default_interface]
    runtimeclass clWinUIControl_RNAInfo : Microsoft.UI.Xaml.Controls.UserControl,   Microsoft.UI.Xaml.Data.INotifyPropertyChanged
    {
        clWinUIControl_RNAInfo();

        clWinUIRNA  GetWinUIRNA_Property;

    }
}
clWinUIControl_RNAInfo.idl

In the header we add an event which will be raised upon a change and we add the PropertyChanged() functions as well. We also add an event for changes of the observable vector:

C++
namespace winrt::How2_DataConverter::implementation
{
    struct clWinUIControl_RNAInfo : clWinUIControl_RNAInfoT<clWinUIControl_RNAInfo>
    {
        clWinUIControl_RNAInfo()
        {
            // Xaml objects should not call InitializeComponent during construction.
            // See https://github.com/microsoft/cppwinrt/tree/master/nuget#initializecomponent
        }

        winrt::How2_DataConverter::clWinUIRNA GetWinUIRNA_Property();
        void GetWinUIRNA_Property(winrt::How2_DataConverter::clWinUIRNA const& value);

        winrt::event_token  PropertyChanged(winrt::Microsoft::UI::Xaml::Data::PropertyChangedEventHandler const& handler);
                    void    PropertyChanged(winrt::event_token const& token) noexcept;


    protected:
                    void    OnVectorChanged_Event_RNA(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::Foundation::Collections::IVectorChangedEventArgs const& rArgs);


        winrt::event<Microsoft::UI::Xaml::Data::PropertyChangedEventHandler>  m_WinUIControl_RNAInfo_PropertyChanged;
        winrt::event_token  m_Event_RNAChanged; //!<    event token for changes in the RNA vector

        winrt::How2_DataConverter::clWinUIRNA  m_WinUIRNA = nullptr;

    };
}
clWinUIControl_RNAInfo.xaml.h

We extend our GetWinUIRNA_Page_Property() function so we will be informed about changes in the vector:

C++
void clWinUIControl_RNAList::GetWinUIRNA_Page_Property(winrt::How2_DataConverter::clWinUIRNA const& value)
{
    if (this->m_WinUIRNA == value) {
        //  no change -> skip
    }
    else {
        this->m_WinUIRNA = value;

        //  check we already have an event token. If so, remove it first
        //  ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
        if (!m_Event_RNAChanged) {
            //  no token (yet) -> nothing to remove -> done
        }
        else {
            //  remove event
            m_WinUIRNA.Get_obsvecRNA_Property().VectorChanged(m_Event_RNAChanged);
        }   //  endif (m_Event_RNAChanged == 0) {

        //  attach event handler to the RNA vector
        m_Event_RNAChanged = m_WinUIRNA.Get_obsvecRNA_Property().VectorChanged({ this, &clWinUIControl_RNAList::OnVectorChanged_Event_RNA });

    }   //  endif (this->m_WinUIRNA == value) {
}
clWinUIControl_RNAList.xaml.cpp

And we add a function which forces an update when a change in the vector occurs:

C++
void    clWinUIControl_RNAList::OnVectorChanged_Event_RNA(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::Foundation::Collections::IVectorChangedEventArgs const& rArgs)
{
    sender;

    switch (rArgs.CollectionChange()) {
    case    winrt::Windows::Foundation::Collections::CollectionChange::Reset: {
        //  all nucleotides have been removed from the RNA
        m_WinUIRNA_PropertyChanged(*this, Microsoft::UI::Xaml::Data::PropertyChangedEventArgs{ L"GetWinUIRNA_Page_Property" });
        break;
    }
    case    winrt::Windows::Foundation::Collections::CollectionChange::ItemInserted: {
        //  a nucleotide has been inserted into the RNA
        m_WinUIRNA_PropertyChanged(*this, Microsoft::UI::Xaml::Data::PropertyChangedEventArgs{ L"GetWinUIRNA_Page_Property" });
        break;
    }
    case    winrt::Windows::Foundation::Collections::CollectionChange::ItemRemoved: {
        //  a nucleotide has been removed from the RNA
        m_WinUIRNA_PropertyChanged(*this, Microsoft::UI::Xaml::Data::PropertyChangedEventArgs{ L"GetWinUIRNA_Property" });
        break;
    }
    case    winrt::Windows::Foundation::Collections::CollectionChange::ItemChanged: {
        assert(false);
        break;
    }
    }   //  endswitch (rArgs.CollectionChange()) {

    return;
}
clWinUIControl_RNAList.xam.cpp

A second Data Converter with a bit more logic

In our tutorial number four we added the display of the number of nucleotides to the main window:

C++
<Grid   Margin="0, 10, 0, 0"    Width="150"
        BorderBrush="{StaticResource  CardStrokeColorDefaultBrush}"
        Background="{StaticResource CardBackgroundFillColorSecondaryBrush}"
        BorderThickness="1"
        CornerRadius="{StaticResource   ControlCornerRadius}"
        HorizontalAlignment="Left">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>
    <TextBlock    Grid.Row="0"    
                  Text="Number of Nucleotides"    
                  Style="{StaticResource  CaptionTextBlockStyle}"
                  HorizontalAlignment="Center"
                  />
    <TextBlock    Grid.Row="1"    
                  Text="{x:Bind   hstrGetRNANbNucleotides, Mode=OneWay}"    
                  Style="{StaticResource  TitleTextBlockStyle}"
                  HorizontalAlignment="Center"
                  />
</Grid>
MainWindow.xaml

We got the number of the nucleotides by the code behind function hstrGetRNANbNucleotides():

C++
//  ------------------------------------------------------------------------------------------------------
/*! \brief  property (to be used by XAML): number of nucleotides in the RNA object
*   
*           Used for one-way binding
*   \date   09/24/2025  AGS Start
*/
//  ------------------------------------------------------------------------------------------------------
hstring MainWindow::hstrGetRNANbNucleotides()
{
		return  to_hstring(this->m_WinUIRNA.Get_obsvecRNA_Property().Size());
}
MainWindow.xaml.cpp

XAML can only use this function because it returns a string. But internally we deal with an integer. Therefore, we had to converted the integer to a string by using to_hstring(). And this was only necessary to satisfy XAML needs. Not a good approach!

Now, we want to return the integer directly and let do XAML the conversion itself. Let’s change our property function:

C++
//  ------------------------------------------------------------------------------------------------------
/*! \brief  property (to be used by XAML): number of nucleotides in the RNA object
*
*           Used for one-way binding
*   \date   09/29/2025  AGS Start
*/
//  ------------------------------------------------------------------------------------------------------
int32_t clWinUIRNA::iGetRNANbNucleotides()
{
    return  Get_obsvecRNA_Property().Size();
}
clWinUIRNA.cpp
C++
<Grid   Margin="0, 10, 0, 0"    Width="150"
                BorderBrush="{StaticResource  CardStrokeColorDefaultBrush}"
                Background="{StaticResource CardBackgroundFillColorSecondaryBrush}"
                BorderThickness="1"
                CornerRadius="{StaticResource   ControlCornerRadius}"
                HorizontalAlignment="Left">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>
    <TextBlock    Grid.Row="0"    
                          Text="Number of Nucleotides"    
                          Style="{StaticResource  CaptionTextBlockStyle}"
                          HorizontalAlignment="Center"
                          />
    <TextBlock    Grid.Row="1"    
                          Text="{x:Bind   GetWinUIRNA_Property.iGetRNANbNucleotides, Mode=OneWay}"    
                          Style="{StaticResource  TitleTextBlockStyle}"
                          HorizontalAlignment="Center"
                          />
</Grid>
clWinUIControl_RNAInfo.xaml

If you try to compile it by now, you will realize that it compiles fine. So, what about the Data Converter?

WinUI does automatic type conversion for simple types, like int to string. That is what we see in the example. But what about formatting? Let’s assume we have a rather large RNA (which is not uncommon in the real life of a biochemistry laboratory)? An RNA with more than one thousand nucleotides? Then, we would like to have our ‘thousands’ separator. And also a unit would be fine: we will add a “peaces” unit: “pcs”.

Finally: the Data Converter

We start again with the IDL:

C++
namespace How2_DataConverter
{
	[default_interface]
	runtimeclass clWinUI_Converter_NbNucleotides : Microsoft.UI.Xaml.Data.IValueConverter
	{
		clWinUI_Converter_NbNucleotides();
	}
}
clWinUI_Converter_NbNucleotides.idl

The MIDL creates the prototypes for the Convert() functions:

C++
namespace winrt::How2_DataConverter::implementation
{
    struct clWinUI_Converter_NbNucleotides : clWinUI_Converter_NbNucleotidesT<clWinUI_Converter_NbNucleotides>
    {
        clWinUI_Converter_NbNucleotides() = default;

        winrt::Windows::Foundation::IInspectable Convert(winrt::Windows::Foundation::IInspectable const& value, winrt::Windows::UI::Xaml::Interop::TypeName const& targetType, winrt::Windows::Foundation::IInspectable const& parameter, hstring const& language);
        winrt::Windows::Foundation::IInspectable ConvertBack(winrt::Windows::Foundation::IInspectable const& value, winrt::Windows::UI::Xaml::Interop::TypeName const& targetType, winrt::Windows::Foundation::IInspectable const& parameter, hstring const& language);
    };
}
clWinUI_Converter_NbNucleotides.h

Add the converter function to the XAML and use it:

C++
<UserControl
    x:Class="How2_DataConverter.clWinUIControl_RNAInfo"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:How2_DataConverter"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">


    <UserControl.Resources>
        <local:clWinUI_Converter_NbNucleotides x:Key="Converter_NbNucleotides"/>
    </UserControl.Resources>
    
    <Grid   Margin="0, 10, 0, 0"    Width="150"
                    BorderBrush="{StaticResource  CardStrokeColorDefaultBrush}"
                    Background="{StaticResource CardBackgroundFillColorSecondaryBrush}"
                    BorderThickness="1"
                    CornerRadius="{StaticResource   ControlCornerRadius}"
                    HorizontalAlignment="Left">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <TextBlock    Grid.Row="0"    
                              Text="Number of Nucleotides"    
                              Style="{StaticResource  CaptionTextBlockStyle}"
                              HorizontalAlignment="Center"
                              />
        <TextBlock    Grid.Row="1"    
                              Text="{x:Bind   GetWinUIRNA_Property.iGetRNANbNucleotides, Converter={StaticResource Converter_NbNucleotides}, Mode=OneWay}"    
                              Style="{StaticResource  TitleTextBlockStyle}"
                              HorizontalAlignment="Center"
                              />
    </Grid>
    
</UserControl>
clWinUIControl_RNAInfo.xaml

Convert() – the converter function

Let’s have a quick look at the implementation of the Convert() function:

C++
//  ------------------------------------------------------------------------------------------------------
	/*! \brief  Converter function (to be used by XAML) to convert the number of nucleotides to a string
*   \date	09/29/2025	AGS Start
	*   \param	value   (integer) value with the number of nucleotides
*/
//  ------------------------------------------------------------------------------------------------------
winrt::Windows::Foundation::IInspectable clWinUI_Converter_NbNucleotides::Convert(winrt::Windows::Foundation::IInspectable const& value, winrt::Windows::UI::Xaml::Interop::TypeName const& targetType, winrt::Windows::Foundation::IInspectable const& parameter, hstring const& language)
{
    language; parameter; targetType;

    winrt::Windows::Foundation::IInspectable    IRet;

		int iNb = unbox_value<int>(value);

    std::string str;
    std::string strRd;
    winrt::hstring  hstr;

    //  format the number in iNb to #.###.###
    //  ''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    while(1){
			...
    } //  endwhile (1) {

    str += " pcs";

    hstr = to_hstring(str);

    IRet = box_value(hstr);

    return  IRet;
}
clWinUI_Converter_NbNucleotides.cpp

The while loop does some string formatting. The interesting part is at the end. Like we did in the previous Convert() function, we use the box_value() function to return a string.

Summary

Let’s summarize the major thoughts:

  • A Data Converter is being used if XAML requires a different data type than a property can provide.
  • Each Data Converter is of type Microsoft.UI.Xaml.Data.IValueConverter and has two Convert() functions.
  • The Convert() function gets the value which needs to be converted as a parameter of type IInspectable. The value can be accessed by unbox_value<source type>().
  • The Convert() function returns the converted value again as an IInspectable. To store the value in the IInspectable the box_value<target type>() can be used.
  • The Data Converter needs to be declared in the <XYZ.Resources> section of the XAML.
  • The Data Converter can be accessed in a XAML control by using the Converter=”…” property.
  • Property Change Notification mechanisms need to be in place

WinUI Stuff: How to program Property Change Notification and Data Binding to keep your UI in sync (C++)

What is in for you

In today’s tutorial (which is actually already number four) we will have a deeper look at how the UI is being updated when a change to the program data occurs. So UI and internal

If you have followed the previous tutorials, you might have wondered where the UI is being updated. We made some changes to the internal data structures (like performing a copy & paste operation) but we never really cared about the visual appearance. Obviously, WinUI provides some “magic” which does this kind of updates in the background.

Like in the previous tutorials, we will once more use the handling of an RNA molecule with its nucleotides (if this is the first How 2 you are reading, then check the introduction of the first tutorial “How to make a Data Template Selector/Part I” about the background of the biochemistry we simulate). Today, I won’t add too much code. I will rather mainly stick to the code of tutorial number three and have a deeper look at how the UI update works internally.

But since a tutorial without any (new) code might be a bit boring, we will add a small feature to our “RNA designer” and add a control which will tell us the number of nucleotides in the RNA. That control is a perfect example of how UI updating happens in the background.

The user experience is fairly simple. We will just have an additional control showing the number of nucleotides in the RNA. The control will be updated by the app when a change to the RNA occurs.

If we copy a nucleotide sequence e.g. from the windows notepad to the clipboard…

… we can paste it using the “Paste at end” button and we will see the RNA as well as the number of nucleotides:

In this tutorial you will learn about:

  • The principles of data binding
  • How and when to use mono-directional and bi-directional data binding
  • How to use property change notifications.

You will find the code in my GitHub repositories: https://github.com/AgentSmith-Dev/How2-WinUI

What you should already know

The User Experience

We need an additional control in the main window which is MainWindow.xaml. Our control is composed of two <TextBlock> elements. One showing the title, the other one showing the number of nucleotides. For a background color and a border we wrap everything up in a <Grid> structure:

<Grid   Margin="0, 10, 0, 0"    Width="150"
        BorderBrush="{StaticResource  CardStrokeColorDefaultBrush}"
        Background="{StaticResource CardBackgroundFillColorSecondaryBrush}"
        BorderThickness="1"
        CornerRadius="{StaticResource   ControlCornerRadius}"
        HorizontalAlignment="Left">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>
    <TextBlock    Grid.Row="0"    
                  Text="Number of Nucleotides"    
                  Style="{StaticResource  CaptionTextBlockStyle}"
                  HorizontalAlignment="Center"
                  />
    <TextBlock    Grid.Row="1"    
                  Text="{x:Bind   hstrGetRNANbNucleotides, Mode=OneWay}"    
                  Style="{StaticResource  TitleTextBlockStyle}"
                  HorizontalAlignment="Center"
                  />
</Grid>
MainWindow.xaml

There is nothing special about the xaml statements so far. It is just one attribute where all the “magic” starts which tells WinUI which text to be displayed:

Text="{x:Bind   hstrGetRNANbNucleotides, Mode=OneWay}"
MainWindow.xaml

The code behind function hstrGetRNAnbNucleotides() is being called which obviously will return the number of the nucleotides (let’s skip the “Mode” parameter for a couple of minutes).

In the IDL we declare the function for the COM-like environment:

namespace How2_PropertyChange_DataBinding
{
    [default_interface]
    runtimeclass MainWindow : Microsoft.UI.Xaml.Window, Microsoft.UI.Xaml.Data.INotifyPropertyChanged
    {
                    MainWindow();

        clWinUIRNA  GetWinUIRNA_Window_Property{get;};
            Boolean bHasRNAClipboardData;
            Boolean bHasRNAData{get;};
            String  hstrGetRNANbNucleotides{get;};
    }
}
MainWindow.idl

The implementation uses our vector with the nucleotides, asks for its size and converts it to a string:

//  ------------------------------------------------------------------------------------------------------
/*! \brief  property (to be used by XAML): number of nucleotides in the RNA object
*   
*   \date   09/24/2025  AGS Start
*/
//  ------------------------------------------------------------------------------------------------------
hstring MainWindow::hstrGetRNANbNucleotides()
{
		return  to_hstring(this->m_WinUIRNA.Get_obsvecRNA_Property().Size());
}
MainWindow.cpp

Architecture

In the previous tutorials we already talked about the overall architecture of the application. As you see, our main window has access to the RNA data:

App.xaml wrapping MainWindow.xaml wrapping clWinUIContrl_RNAList.xaml wrapping ListView

Architecture of the WinUI3 HowTo „DataTemplateSelector – Part I“

From a MVVM (model view-view model) or MVC (model view control) perspective MainWindow.xaml represents the view and m_WinUIRNA the model. 

Data Binding

The mechanism to connect the view and the model with each other is called “Data Binding”:

Databinding in View-Model context

That’s exactly what happens in the XAML directive above, the function hstrGetRNAnbNucleotides() allows the view to connect to the model. 

Direction of the data flow with Data Binding

The previously mentioned “Mode” parameter is set to “OneWay” which means that the view is being updated by the data from the model. If we change the data in the view and keep Mode=OneWay the data in the model will not be updated. Of course not, there is no connector in the model available. If we want a two-way connection, we had to set Mode=TwoWay and we had to implement an additional “connector” on the model side. We will look at this later on.

Property change notifications

Now we have a connection between the view and the model in place. What we still need is a mechanism to inform the view (our main window, more precise our control in the main window) about a change in the model.

In WinUI this is being done by events (we already used events in tutorial number three for our copy & paste operation. There, we wanted to get informed by third party applications about a change in the clipboard). Each event which an application uses is specific for a certain type of “delegate” (a delegate is a function which is being called when the event is being raised). For the property changed notification the delegate is of type IPropertyChangedNotification. What we therefore need is an event which can be used for the communication between view and model. The event template is PropertyChangedEventHandler. Since the model needs to inform the view, the event is part of the MainWindow class:

private:

  event_token m_EventToken_ClipboardChanged;  //!<    event token for clipboard changed event
  winrt::event<Microsoft::UI::Xaml::Data::PropertyChangedEventHandler>	m_MainFrame_PropertyChanged;    //!<    event token for property changed notifications


  winrt::How2_PropertyChange_DataBinding::clWinUIRNA  m_WinUIRNA;
	                                            bool    m_bHasRNAClipboardData = false;
MainFrame.h

Furthermore, we need to add the INotifyPropertyChanged interface to the class which provides the event. That is done by inheritance in the IDL:

namespace How2_PropertyChange_DataBinding
{
    [default_interface]
    runtimeclass MainWindow : Microsoft.UI.Xaml.Window, Microsoft.UI.Xaml.Data.INotifyPropertyChanged
    {
                    MainWindow();

        clWinUIRNA  GetWinUIRNA_Window_Property{get;};
            Boolean bHasRNAClipboardData;
            Boolean bHasRNAData{get;};
            String  hstrGetRNANbNucleotides{get;};
            String  strRNANbNucleotides_Property;

    }
}
MainWindow.idl

The INotifyPropertyChanged interface requires the possibility of adding and removing delegates. Therefore, you would get an error message when you would compile the IDL by now. It would complain about two missing functions: PropertyChanged(). We need to add these as well:

  struct MainWindow : MainWindowT<MainWindow>
  {
      MainWindow();
		~MainWindow();

      winrt::How2_PropertyChange_DataBinding::clWinUIRNA  GetWinUIRNA_Window_Property();
                                  bool    bHasRNAClipboardData();
                                   void   bHasRNAClipboardData(bool value);
									 bool   bHasRNAData();

							    hstring    hstrGetRNANbNucleotides();
							    
                        winrt::event_token  PropertyChanged(winrt::Microsoft::UI::Xaml::Data::PropertyChangedEventHandler const& handler);
                                    void    PropertyChanged(winrt::event_token const& token) noexcept;
MainWindow.xaml.h
    //  ------------------------------------------------------------------------------------------------------
    /*! \brief  init/deinit handler for property changed events
    *   \date   09/15/2025  AGS Start
    */
    //  ------------------------------------------------------------------------------------------------------
    winrt::event_token MainWindow::PropertyChanged(winrt::Microsoft::UI::Xaml::Data::PropertyChangedEventHandler const& handler)
    {
        return m_MainFrame_PropertyChanged.add(handler);
    }

    void MainWindow::PropertyChanged(winrt::event_token const& token) noexcept
    {
        m_MainFrame_PropertyChanged.remove(token);

        return;
    }
MainWindow.xaml.cpp

The first version of PropertyChanged() ATTACHES an event handler which returns a unique event token (If you look into WinUIs implementation in base.h you will find that the event token is something like an iterator for an internal vector which points to the position where all the different event handler are stored). The second version DETACHES an event handler, which is then identified by exactly that event token.

You do not need to call PropertyChanged() yourself, unless you want to add your own separate event handler. WinUI calls the PropertyChanged() function when MainWindow is being instantiated and connects it with its build-in WinUI event handler. You can check this out by placing breakpoints in the functions when you launch the application.

Now we have everything in place, let’s summarize it up:

A connector through which MainWindow (the view) can get the RNA data (the model) from. This connector is our hstrGetRNANbNucleotides() function.

  • A mechanism where the model can inform the view about a change in the data. This is done through our event m_MainFrame_PropertyChanged which is connected with the WinUI framework through an internal event handler which is being attached by a call of PropertyChanged() by the framework.

What still needs to be done

What still needs to be done is to raise an event when the internal data has been changed. That is fairly easy: each time a change to the internal data (=the model) occurs we need to raise an event. That will result in a call of a connector of the model.

For example, if the user performs a paste operation and we convert the text string in the clipboard into nucleotide objects, we need
a) update our internal state which tells the application that there are RNA data available (which is for the enable state of our “Clear” button) and
b) update the number of nucleotides:

//  ------------------------------------------------------------------------------------------------------
/*! \brief  paste clipboard content to the end of the RNA
*   \date   09/15/2025  AGS Start
*/
//  ------------------------------------------------------------------------------------------------------
winrt::Windows::Foundation::IAsyncAction    MainWindow::PasteClipboardToRNA()
{
    winrt::Windows::ApplicationModel::DataTransfer::DataPackageView dataPackageView = nullptr;

    dataPackageView = winrt::Windows::ApplicationModel::DataTransfer::Clipboard::GetContent();

    winrt::hstring  hstrClipBoard = co_await    dataPackageView.GetTextAsync();

		int iPos=0;
    while (iPos < (int)hstrClipBoard.size()) {
			wchar_t c = hstrClipBoard[iPos];
        m_WinUIRNA.CreateAndAddNucleotide(c);
        iPos++;

		}   //  endwhile (iPos < hstrClipBoard.size()) {

    //  notify interested parties
    m_MainFrame_PropertyChanged(*this, Microsoft::UI::Xaml::Data::PropertyChangedEventArgs{ L"bHasRNAData" });
    m_MainFrame_PropertyChanged(*this, Microsoft::UI::Xaml::Data::PropertyChangedEventArgs{ L"hstrGetRNANbNucleotides" });

    co_return;
}
MainWindow.xaml.cpp

If you simply want to update all controls, just call PropertyChangedEventArgs() with an empty string:

m_MainFrame_PropertyChanged(*this, Microsoft::UI::Xaml::Data::PropertyChangedEventArgs{ L"" });
C++

A quick deeper look

Our event in m_MainFrame_PropertyChanged is of type winrt::event<Microsoft::UI::Xaml::Data::PropertyChangedEventHandler>. If you look once more into base.h you will find that the () operator is being overloaded. Its parameters are a list of arguments. In our code the first argument being passed is “us” (which is the ‘this‘ object) the second is a newly created instance of the Microsoft::UI::Xaml::Data::PropertyChangedEventArgs class which gets a string as a parameter. The WinUI documentation states this string is “the short name of the property that changed”, which is usually a getter function.

Obviously, all we need to do, if a change of data in the ‘model’ occurs is to call the ‘connector’ which supplies the (changed) data.

Observable collections

Remember our first tutorial where we set up the application? We chose an “observable vector” to store the nucleotides of the RNA. The class we chose is winrt::Windows::Foundation::Collections::IObservableVector<T> . The IObservableVector class encapsulates the mechanisms for property changed notifications. It implements its own event internally. If a change to the vector occurs that event will automatically be raised and the view(s) which use the vector will be informed. If you want to go more into detail about the internals, the generated code in clWinUIControl_RNAList.xaml.g.hpp might be a good start.

The take-away from this: if you implement a vector by using the IObservableVector<T> class you do not need to worry about the implementation of property change mechanisms by yourself. The WinUI framework does it for you.

However, if you want to get informed about a change to your vector for a reason, you may add your own delegate by calling the IObservableVector<T> member function VectorChanged().

Once more: Mode=TwoWay

In our example we provided data binding in only one direction: when the internal data are updated the view of the data are updated as well. In data binding we added the connector with the Mode=OneWay.

Let’s look at a short “two-way” example. We add another control which not only shows the number of nucleotides in the RNA but where we also can enter the number of nucleotides. If we enter a number lower than the number of nucleotides in the RNA we will shorten the RNA, if we enter a higher number we will add invalid nucleotides at the end.

Let’s update the user interface first and add that additional control:

<!-- Two way data binding -->
<Grid   Margin="0, 10, 0, 0"    Width="150"
        BorderBrush="{StaticResource  CardStrokeColorDefaultBrush}"
        Background="{StaticResource CardBackgroundFillColorSecondaryBrush}"
        BorderThickness="1"
        CornerRadius="{StaticResource   ControlCornerRadius}"
        HorizontalAlignment="Left">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>
    <TextBlock    Grid.Row="0"    
                  Text="Number of Nucleotides (two way)"    
                  Style="{StaticResource  CaptionTextBlockStyle}"
                  HorizontalAlignment="Center"
                  />
    <TextBox    Grid.Row="1"    
                  Text="{x:Bind   strRNANbNucleotides_Property, Mode=TwoWay}"    
                  HorizontalAlignment="Center"
                  />
</Grid>
MainWindow.xaml

Note the “Mode=TwoWay” parameter in the <TextBox>.

In the IDL we define a separate ‘connector’ which now also has to work both ways. We need to be able to read AND write data from AND to our ‘model’:

namespace How2_PropertyChange_DataBinding
{
    [default_interface]
    runtimeclass MainWindow : Microsoft.UI.Xaml.Window, Microsoft.UI.Xaml.Data.INotifyPropertyChanged
    {
                    MainWindow();

        clWinUIRNA  GetWinUIRNA_Window_Property{get;};
            Boolean bHasRNAClipboardData;
            Boolean bHasRNAData{get;};
            String  hstrGetRNANbNucleotides{get;};
            String  strRNANbNucleotides_Property;

    }
}
MainWindow.idl

Of course, we could now replace the hstrGetRNANbNucleotides() function which does the same as the getter implementation of strRNANbNucleotides_Property(). For readability of the tutorial, we will keep it that way.

We need to raise events for that control in PasteClipboardToRNA() as well as in OnBtnClearRNA_Click():

//  notify interested parties
m_MainFrame_PropertyChanged(*this, Microsoft::UI::Xaml::Data::PropertyChangedEventArgs{ L"bHasRNAData" });
m_MainFrame_PropertyChanged(*this, Microsoft::UI::Xaml::Data::PropertyChangedEventArgs{ L"hstrGetRNANbNucleotides" });
m_MainFrame_PropertyChanged(*this, Microsoft::UI::Xaml::Data::PropertyChangedEventArgs{ L"strRNANbNucleotides_Property" });
C++

Finally, the implementation of the getter/setter:

//  ------------------------------------------------------------------------------------------------------
/*! \brief  property (to be used by XAML): number of nucleotides in the RNA object
*
*           Used for two-way binding
*   \date   09/24/2025  AGS Start
*/
//  ------------------------------------------------------------------------------------------------------
hstring MainWindow::strRNANbNucleotides_Property()
{
    return  to_hstring(this->m_WinUIRNA.Get_obsvecRNA_Property().Size());
}
void MainWindow::strRNANbNucleotides_Property(hstring const& value)
{
    int iSize;

		iSize = _wtoi(value.c_str());

    while ((int)this->m_WinUIRNA.Get_obsvecRNA_Property().Size() < iSize) {
			this->m_WinUIRNA.CreateAndAddNucleotide('?');
    }   //  endwhile (this->m_WinUIRNA.Get_obsvecRNA_Property().Size() < iSize) {

    while ((int)this->m_WinUIRNA.Get_obsvecRNA_Property().Size() > iSize) {
			this->m_WinUIRNA.Get_obsvecRNA_Property().RemoveAtEnd();
		}   //  endwhile (this->m_WinUIRNA.Get_obsvecRNA_Property().Size() > iSize) {


    return;
}
MainWindow.xaml.cpp

Each time we change the value in the <TextBox>, the setter of strRNANbNucleotides_Property() is being called. Remember: since we use an observable vector, there is no need to manually raise an event after we have modified the vector – WinUI does it for us.

What you should know about Mode=OneTime

We talked about the two modes Mode=OneWay and Mode=TwoWay. There is a third mode: Mode=OneTime. This is kind of a ‘static data binding’. If you set Mode=OneTime your getter function will only be called once. That call happens exactly by the time your control is being created. Please keep in mind: your ‘connector’ will NOT BE CALLED ANYMORE. No matter if and when you raise events! And that is a common pitfall: if you forget to set the Mode parameter, WinUI will set it to its default value which is Mode=OneTime.

And I can assure you: hours of developer time (including my own) have been wasted with controls that do not update themselves, because the Mode parameter had not set.

Why is Mode=OneTime available, anyway? Microsoft brings up performance issues. Which is very likely right. The OneTime option simply does not implement any property change notification which of course safes time. However, in my experience, most of the time you do NOT have ‘static’ data binding BUT dynamic data binding, means the internal data change and UI has to reflect to that.

And, by the way, we are already done!

Summary

Let’s summarize the major thoughts:

  • Property changed notifications are being used to synchronize the ‘view’ (the UI in your application) with the ‘model’ (the data of your application).
  • The mechanism for the communication between those two is called ‘data binding’.
  • This mechanism uses WinUI events.
  • To implement data binding between a control and its internal data representation the view needs to implement an event which templates the PropertyChangedEventHandler class. 
  • To enable property change notification, your view (where you implement the event) needs to implement the interface Microsoft.UI.Xaml.Data.INotifyPropertyChanged. That is being done in the IDL. This also requires the implementation of two PropertyChanged() functions.
  • When an internal date has changed you need to raise the data binding event with the name of the property attached to.
  • The Mode property defines the direction of the data binding. Caution: if you do not specify the Mode property it will be set to its default which is Mode=OneTime, which is kind of a static property
  • The “observable collections” (IObservableVector, IObservableMap, …) have all these mechanisms ‘build in’.