What is in for you
In the last tutorial we talked about Property Change notifications and Data Binding. I would like to stretch the topic a bit and add some knowledge about how to update a control in a control (IF that control uses an observable vector).
To understand when you need the knowledge of this tutorial it is best to understand with an example, which follows in a second.
Since this is part II of the previous tutorial, we will extend the code of that tutorial. 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.
To fully understand this tutorial, it is almost mandatory to read part I: How to program a Property Change notification to keep your UI in sync.
So, what’s the challenge for today? Imagine you want to clean up and structure your code a bit. Having this in mind, it would be a good idea to wrap up the output of the number of nucleotides in a separate control. This could be extended to an entire “status” control in the future. And while the updating of the number of nucleotides was quite easy (that is what we did in the previous part), it turns out that it gets a bit more complicated if we have it wrapped up in a separate control.
Today, there really won’t be any change to the UI. It’s “only” code improvements – but sometimes that is a lot!
We still have our opening screen with a – let’s call it – “null” RNA. Means there are not any nucleotides:
With the Copy & Paste operation we programmed the user may copy a nucleotide sequence into the clipboard, maybe from the Windows text editor:
And paste it to our application:
In this tutorial you will learn about:
- How to get notified when a change in an observable vector happens
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
Even though, a user will not notify any change, we need to adapt the user interface. First, let’s create an additional user control which call clWinUIControl_RNAInfo. Right click on your solution in the project wizard, choose “Add”, then “New” and the project wizard will open:
When you close the window with “Add” the project wizard will do most of the work and create the required files.
Like the user control clWinUIControl_RNAList which we created in the first tutorial, our control needs to know about the RNA, that’s what the getter/setter GetWinUIRNA_Property() is for. And it will require property change notifications as well, that’s why the class will be derived from Microsoft.UI.Xaml.Data.INotifyPropertyChanged. We modify the IDL accordingly:
import "clWinUIRNA.idl";
namespace How2_PropertyChange_II
{
[default_interface]
runtimeclass clWinUIControl_RNAInfo : Microsoft.UI.Xaml.Controls.UserControl, Microsoft.UI.Xaml.Data.INotifyPropertyChanged
{
clWinUIControl_RNAInfo();
clWinUIRNA GetWinUIRNA_Property;
}
}
clWinUIControl_RNAInfo.idlWe move the XAML code from our MainWindow to our new control:
<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>
clWinUIContro_RNAIinfo.xaml.cppMainWindow will use the newly created user control instead:
<StackPanel Grid.Column="1" Orientation="Vertical" VerticalAlignment="Center">
<Button x:Name="Btn_Paste"
IsEnabled="{x:Bind bHasRNAClipboardData, Mode=OneWay}"
Click="OnBtnPastAtRNAEnd_Click"
Width="150"
>Paste at end</Button>
<Button x:Name="Btn_Clear"
IsEnabled="{x:Bind bHasRNAData, Mode=OneWay}"
Click="OnBtnClearRNA_Click"
Width="150"
Margin="0, 10, 0, 0"
>Clear RNA</Button>
<local:clWinUIControl_RNAInfo GetWinUIRNA_Property="{x:Bind GetWinUIRNA_Window_Property, Mode=OneWay}"/>
</StackPanel>
MainWindow.xamlProblems with updating the control
When we compile and run the program, we see that our new control is not being updated when the RNA sequence changes. The number of nucleotides just shows “0” (which is the initialization value).
In the previous tutorial, we updated the control by calling:
m_MainFrame_PropertyChanged(*this, Microsoft::UI::Xaml::Data::PropertyChangedEventArgs{ L"<Name of the property>" });
C++There, the display of the number of nucleotides were inside MainFrame. We could access the vector by calling the member function iGetRNANbNucleotides() (formerly: strGetRNANbNucleotides()). When we raised a property change notification with the parameter “iGetRNANbNucleotides” we forced WinUI to update the control <TextBlock>. But since we moved that logic from MainWindow to a separate control, there is no property function anymore which MainWindow could use and there is also no property function anymore to be used as a parameter to raise a property change notification.
We need a different approach.
The solution, once more, lies in IObservableVector. Remember, we use the IObservableVector template class as the base class for our RNA to hold the nucleotide sequence. Each time, IObservableVector is changing, it raises an event. All we need to do is to add an event handler for such changes.
An event handler for IObservableVector
First of all, we need an event token:
namespace winrt::How2_PropertyChange_II::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_PropertyChange_II::clWinUIRNA GetWinUIRNA_Property();
void GetWinUIRNA_Property(winrt::How2_PropertyChange_II::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_PropertyChange_II::clWinUIRNA m_WinUIRNA = nullptr;
};
}
clWinUIControl_RNAInfo.xaml.hThen we can attach our own event handler to the vector. For that we will extend the GetWinUIRNA_Property() function a bit. Remember: GetWinUIRNA_Property() will be (usually just once) called when our user control is being used (see in MainFrame.xaml how this is being done).
void clWinUIControl_RNAInfo::GetWinUIRNA_Property(winrt::How2_PropertyChange_II::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_RNAInfo::OnVectorChanged_Event_RNA });
} // endif (this->m_WinUIRNA == value) {
return;
}
clWinUIContro_RNAInfo.xaml.cppFinally, we add our own event handler:
void clWinUIControl_RNAInfo::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_WinUIControl_RNAInfo_PropertyChanged(*this, Microsoft::UI::Xaml::Data::PropertyChangedEventArgs{ L"GetWinUIRNA_Property" });
break;
}
case winrt::Windows::Foundation::Collections::CollectionChange::ItemInserted: {
// a nucleotide has been inserted into the RNA
m_WinUIControl_RNAInfo_PropertyChanged(*this, Microsoft::UI::Xaml::Data::PropertyChangedEventArgs{ L"GetWinUIRNA_Property" });
break;
}
case winrt::Windows::Foundation::Collections::CollectionChange::ItemRemoved: {
// a nucleotide has been removed from the RNA
m_WinUIControl_RNAInfo_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_RNAInfo.xaml.cppNow, each time the vector changes, the framework calls OnVectorChanged_EventRNA().
As you see, you will also get the information, what has been changed. See Microsoft’s documentation for the values.
Here, no matter if the vector increased, decreased or cleared, we raise our own property change notification, to update the control.
And that’s it!
Summary
Let’s summarize the major thoughts:
- For a “control in a control” (as we had it with a user control in MainWindow) we can not directly raise a property change notification since there is no – let’s call it – “socket”.
- If the user control gets its data and state from an IOberservableVector, we can add our own change notification handler which will be called when the vector has been changed.
- The event handler is attached to the vector by calling IOberservableVector<template>::VectorChanged().
- This event handler raises a property change notification for the user control.