mgb
Topic Author
Posts: 1
Joined: 20 Feb 2024, 13:48

Get content from ContentControl / DataTemplate

15 May 2024, 19:23

Hello!
I would like to know how I can do something in the best way.

Consider that I have complex content, which is displayed more than once on the same screen.
Here I will exemplify some TextBlocks that need to be changed by c++ without binding, but they are more complex things.

I made an example at HelloWorld in c++.

This would be the example with non-duplicate content.

MainWindow.xaml
<Window
    x:Class="HelloWorld.MainWindow"
    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:local="clr-namespace:HelloWorld"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="NoesisGUI - Hello, World!"
    Background="#FF124C7A"
    FontFamily="./#Aero Matics"
    FontSize="36"
    mc:Ignorable="d">

    <Grid Width="600" Height="480">

        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>

        <StackPanel
            Grid.Row="0"
            Grid.Column="0"
            Margin="10">
            <TextBlock Name="MyTextBlock1" Text="Test 1" />
            <TextBlock Name="MyTextBlock2" Text="Test 2" />
            <TextBlock Name="MyTextBlock3" Text="Test 3" />
            <TextBlock Name="MyTextBlock4" Text="Test 4" />
            <TextBlock Name="MyTextBlock5" Text="Test 5" />
        </StackPanel>

    </Grid>
</Window>

Main.cpp: (Original content of the example, I just changed the MainWindow class with a very basic example)
    class MainWindow final : public Window
    {
    public:
        MainWindow()
        {
            Initialized() += MakeDelegate(this, &MainWindow::OnInitialized);
            Loaded() += MakeDelegate(this, &MainWindow::OnLoaded);
        }

    private:
        void OnLoaded(Noesis::BaseComponent* /*sender*/, const Noesis::RoutedEventArgs& /*e*/)
        {
            GetDisplay()->Render() += [this](NoesisApp::Display* /*_display*/)
                {
                    MyNumber++;

                    MyTextBlock1->SetText(std::to_string(MyNumber).data());
                    MyTextBlock2->SetText(std::to_string(MyNumber * 10).data());
                    MyTextBlock3->SetText(std::to_string(MyNumber * 20).data());
                    MyTextBlock4->SetText(std::to_string(MyNumber * 30).data());
                    MyTextBlock5->SetText(std::to_string(MyNumber * 40).data());
                };
        }

        void OnInitialized(BaseComponent*, const Noesis::EventArgs&)
        {
            SetDataContext(this);

            MyTextBlock1 = FindName<TextBlock>("MyTextBlock1");
            MyTextBlock2 = FindName<TextBlock>("MyTextBlock2");
            MyTextBlock3 = FindName<TextBlock>("MyTextBlock3");
            MyTextBlock4 = FindName<TextBlock>("MyTextBlock4");
            MyTextBlock5 = FindName<TextBlock>("MyTextBlock5");
        }

        TextBlock* MyTextBlock1{ nullptr };
        TextBlock* MyTextBlock2{ nullptr };
        TextBlock* MyTextBlock3{ nullptr };
        TextBlock* MyTextBlock4{ nullptr };
        TextBlock* MyTextBlock5{ nullptr };

        unsigned long long MyNumber = 0;

    private:
        NS_IMPLEMENT_INLINE_REFLECTION_(MainWindow, Window, "HelloWorld.MainWindow")
    };
Now consider that this StackPanel with these TextBlock needs to go to the other grid locations (0,1), (1,0), (1,1).
The way I can do it today is very bad, for example:

XAML:
<Window
    x:Class="HelloWorld.MainWindow"
    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:local="clr-namespace:HelloWorld"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="NoesisGUI - Hello, World!"
    Background="#FF124C7A"
    FontFamily="./#Aero Matics"
    FontSize="36"
    mc:Ignorable="d">

    <Grid Width="600" Height="480">

        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>

        <StackPanel
            Grid.Row="0"
            Grid.Column="0"
            Margin="10">
            <TextBlock Name="MyTextBlock01" Text="Test 1" />
            <TextBlock Name="MyTextBlock02" Text="Test 2" />
            <TextBlock Name="MyTextBlock03" Text="Test 3" />
            <TextBlock Name="MyTextBlock04" Text="Test 4" />
            <TextBlock Name="MyTextBlock05" Text="Test 5" />
        </StackPanel>
        <StackPanel
            Grid.Row="0"
            Grid.Column="1"
            Margin="10">
            <TextBlock Name="MyTextBlock11" Text="Test 1" />
            <TextBlock Name="MyTextBlock12" Text="Test 2" />
            <TextBlock Name="MyTextBlock13" Text="Test 3" />
            <TextBlock Name="MyTextBlock14" Text="Test 4" />
            <TextBlock Name="MyTextBlock15" Text="Test 5" />
        </StackPanel>
        <StackPanel
            Grid.Row="1"
            Grid.Column="0"
            Margin="10">
            <TextBlock Name="MyTextBlock21" Text="Test 1" />
            <TextBlock Name="MyTextBlock22" Text="Test 2" />
            <TextBlock Name="MyTextBlock23" Text="Test 3" />
            <TextBlock Name="MyTextBlock24" Text="Test 4" />
            <TextBlock Name="MyTextBlock25" Text="Test 5" />
        </StackPanel>
        <StackPanel
            Grid.Row="1"
            Grid.Column="1"
            Margin="10">
            <TextBlock Name="MyTextBlock31" Text="Test 1" />
            <TextBlock Name="MyTextBlock32" Text="Test 2" />
            <TextBlock Name="MyTextBlock33" Text="Test 3" />
            <TextBlock Name="MyTextBlock34" Text="Test 4" />
            <TextBlock Name="MyTextBlock35" Text="Test 5" />
        </StackPanel>

    </Grid>
</Window>
CPP:
    class MainWindow final : public Window
    {
    public:
        MainWindow()
        {
            Initialized() += MakeDelegate(this, &MainWindow::OnInitialized);
            Loaded() += MakeDelegate(this, &MainWindow::OnLoaded);
        }

    private:
        void OnLoaded(Noesis::BaseComponent* /*sender*/, const Noesis::RoutedEventArgs& /*e*/)
        {
            GetDisplay()->Render() += [this](NoesisApp::Display* /*_display*/)
                {
                    MyNumber++;

                    MyTextBlock01->SetText(std::to_string(MyNumber).data());
                    MyTextBlock02->SetText(std::to_string(MyNumber * 10).data());
                    MyTextBlock03->SetText(std::to_string(MyNumber * 20).data());
                    MyTextBlock04->SetText(std::to_string(MyNumber * 30).data());
                    MyTextBlock05->SetText(std::to_string(MyNumber * 40).data());

                    MyTextBlock11->SetText(std::to_string(MyNumber).data());
                    MyTextBlock12->SetText(std::to_string(MyNumber * 10).data());
                    MyTextBlock13->SetText(std::to_string(MyNumber * 20).data());
                    MyTextBlock14->SetText(std::to_string(MyNumber * 30).data());
                    MyTextBlock15->SetText(std::to_string(MyNumber * 40).data());

                    MyTextBlock21->SetText(std::to_string(MyNumber).data());
                    MyTextBlock22->SetText(std::to_string(MyNumber * 10).data());
                    MyTextBlock23->SetText(std::to_string(MyNumber * 20).data());
                    MyTextBlock24->SetText(std::to_string(MyNumber * 30).data());
                    MyTextBlock25->SetText(std::to_string(MyNumber * 40).data());

                    MyTextBlock31->SetText(std::to_string(MyNumber).data());
                    MyTextBlock32->SetText(std::to_string(MyNumber * 10).data());
                    MyTextBlock33->SetText(std::to_string(MyNumber * 20).data());
                    MyTextBlock34->SetText(std::to_string(MyNumber * 30).data());
                    MyTextBlock35->SetText(std::to_string(MyNumber * 40).data());
                };
        }

        void OnInitialized(BaseComponent*, const Noesis::EventArgs&)
        {
            SetDataContext(this);

            MyTextBlock01 = FindName<TextBlock>("MyTextBlock01");
            MyTextBlock02 = FindName<TextBlock>("MyTextBlock02");
            MyTextBlock03 = FindName<TextBlock>("MyTextBlock03");
            MyTextBlock04 = FindName<TextBlock>("MyTextBlock04");
            MyTextBlock05 = FindName<TextBlock>("MyTextBlock05");

            MyTextBlock11 = FindName<TextBlock>("MyTextBlock11");
            MyTextBlock12 = FindName<TextBlock>("MyTextBlock12");
            MyTextBlock13 = FindName<TextBlock>("MyTextBlock13");
            MyTextBlock14 = FindName<TextBlock>("MyTextBlock14");
            MyTextBlock15 = FindName<TextBlock>("MyTextBlock15");

            MyTextBlock21 = FindName<TextBlock>("MyTextBlock21");
            MyTextBlock22 = FindName<TextBlock>("MyTextBlock22");
            MyTextBlock23 = FindName<TextBlock>("MyTextBlock23");
            MyTextBlock24 = FindName<TextBlock>("MyTextBlock24");
            MyTextBlock25 = FindName<TextBlock>("MyTextBlock25");

            MyTextBlock31 = FindName<TextBlock>("MyTextBlock31");
            MyTextBlock32 = FindName<TextBlock>("MyTextBlock32");
            MyTextBlock33 = FindName<TextBlock>("MyTextBlock33");
            MyTextBlock34 = FindName<TextBlock>("MyTextBlock34");
            MyTextBlock35 = FindName<TextBlock>("MyTextBlock35");
        }

        TextBlock* MyTextBlock01{ nullptr };
        TextBlock* MyTextBlock02{ nullptr };
        TextBlock* MyTextBlock03{ nullptr };
        TextBlock* MyTextBlock04{ nullptr };
        TextBlock* MyTextBlock05{ nullptr };

        TextBlock* MyTextBlock11{ nullptr };
        TextBlock* MyTextBlock12{ nullptr };
        TextBlock* MyTextBlock13{ nullptr };
        TextBlock* MyTextBlock14{ nullptr };
        TextBlock* MyTextBlock15{ nullptr };

        TextBlock* MyTextBlock21{ nullptr };
        TextBlock* MyTextBlock22{ nullptr };
        TextBlock* MyTextBlock23{ nullptr };
        TextBlock* MyTextBlock24{ nullptr };
        TextBlock* MyTextBlock25{ nullptr };

        TextBlock* MyTextBlock31{ nullptr };
        TextBlock* MyTextBlock32{ nullptr };
        TextBlock* MyTextBlock33{ nullptr };
        TextBlock* MyTextBlock34{ nullptr };
        TextBlock* MyTextBlock35{ nullptr };

        unsigned long long MyNumber = 0;

    private:
        NS_IMPLEMENT_INLINE_REFLECTION_(MainWindow, Window, "HelloWorld.MainWindow")
    };
So I started trying to create some type of data that could be reused, such as:
<Window
    x:Class="HelloWorld.MainWindow"
    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:local="clr-namespace:HelloWorld"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="NoesisGUI - Hello, World!"
    Background="#FF124C7A"
    FontFamily="./#Aero Matics"
    FontSize="36"
    mc:Ignorable="d">

    <Window.Resources>
        <Style TargetType="{x:Type TextBlock}">
            <Setter Property="Foreground" Value="White" />
            <Setter Property="FontSize" Value="12" />
            <Setter Property="TextAlignment" Value="Center" />
        </Style>


        <DataTemplate x:Key="MyCustomElementDataTemplate">
            <StackPanel Margin="10">
                <TextBlock Name="MyTextBlock1" Text="Test 1" />
                <TextBlock Name="MyTextBlock2" Text="Test 2" />
                <TextBlock Name="MyTextBlock3" Text="Test 3" />
                <TextBlock Name="MyTextBlock4" Text="Test 4" />
                <TextBlock Name="MyTextBlock5" Text="Test 5" />
            </StackPanel>
        </DataTemplate>
    </Window.Resources>

    <Grid Width="600" Height="480">

        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>

        <ContentControl Grid.Row="0" Grid.Column="0" x:Name="MyCustomElementContentControl1" ContentTemplate="{StaticResource MyCustomElementDataTemplate}" />
        <ContentControl Grid.Row="0" Grid.Column="1" x:Name="MyCustomElementContentControl2" ContentTemplate="{StaticResource MyCustomElementDataTemplate}" />
        <ContentControl Grid.Row="1" Grid.Column="0" x:Name="MyCustomElementContentControl3" ContentTemplate="{StaticResource MyCustomElementDataTemplate}" />
        <ContentControl Grid.Row="1" Grid.Column="1" x:Name="MyCustomElementContentControl4" ContentTemplate="{StaticResource MyCustomElementDataTemplate}" />

    </Grid>
</Window>
CPP:
    struct MyComplexComponent
    {
        TextBlock* MyTextBlock1{ nullptr };
        TextBlock* MyTextBlock2{ nullptr };
        TextBlock* MyTextBlock3{ nullptr };
        TextBlock* MyTextBlock4{ nullptr };
        TextBlock* MyTextBlock5{ nullptr };
    };

    class MainWindow final : public Window
    {
    public:
        MainWindow()
        {
            Initialized() += MakeDelegate(this, &MainWindow::OnInitialized);
            Loaded() += MakeDelegate(this, &MainWindow::OnLoaded);
        }

    private:
        void OnLoaded(Noesis::BaseComponent* /*sender*/, const Noesis::RoutedEventArgs& /*e*/)
        {
            GetDisplay()->Render() += [this](NoesisApp::Display* /*_display*/)
                {
                    MyNumber++;

                    component1.MyTextBlock1->SetText(std::to_string(MyNumber).data());
                    component2.MyTextBlock1->SetText(std::to_string(MyNumber).data());
                    component3.MyTextBlock1->SetText(std::to_string(MyNumber).data());
                    component4.MyTextBlock1->SetText(std::to_string(MyNumber).data());

                    printf("Call %llu\n", MyNumber);
                };
        }

        void InitializeComponent(MyComplexComponent* component, const char* name)
        {
            ContentControl* cc = FindName<ContentControl>(name);
            DataTemplate* dp = cc->GetContentTemplate();

            component->MyTextBlock1 = DynamicCast<Noesis::TextBlock*>(dp->FindName("MyTextBlock1"));
            component->MyTextBlock2 = DynamicCast<Noesis::TextBlock*>(dp->FindName("MyTextBlock2"));
            component->MyTextBlock3 = DynamicCast<Noesis::TextBlock*>(dp->FindName("MyTextBlock3"));
            component->MyTextBlock4 = DynamicCast<Noesis::TextBlock*>(dp->FindName("MyTextBlock4"));
            component->MyTextBlock5 = DynamicCast<Noesis::TextBlock*>(dp->FindName("MyTextBlock5"));
        }

        void OnInitialized(BaseComponent*, const Noesis::EventArgs&)
        {
            SetDataContext(this);

            InitializeComponent(&component1, "MyCustomElementContentControl1");
            InitializeComponent(&component2, "MyCustomElementContentControl2");
            InitializeComponent(&component3, "MyCustomElementContentControl3");
            InitializeComponent(&component4, "MyCustomElementContentControl4");
        }

        MyComplexComponent component1;
        MyComplexComponent component2;
        MyComplexComponent component3;
        MyComplexComponent component4;

        unsigned long long MyNumber = 0;

    private:
        NS_IMPLEMENT_INLINE_REFLECTION_(MainWindow, Window, "HelloWorld.MainWindow")
    };
However, some strange things happened.
1 - The "MyTextBlock1/2/3/4" pointer is the same between all 4 structs.
The MyTextBlock1 pointer is the same in all structs.
The MyTextBlock2 pointer is the same in all structs, and so on.

I was wondering why this happens, and I came to the conclusion that when I do FindName<ContentControl>(name)->GetContentTemplate();
I'm getting the template itself, and not the elements created from the template within the ContentControl, so I always get the same pointer regardless of which ContentControl I indicate.
Would it be this? Because if I do it in my OnInitialized():
component1.MyTextBlock1->SetText("My Test Text");
It sets the text in MyTextBlock1 of all 4 elements.

So how do I actually get the element inside my ContentControl that was created by the DataTemplate?
It's possible?

2 - Why after I used ContentControl, my Render() loop no longer changes the text?
Could it be because of what I explained above? Does it take the element pointer in the template, and not the one created in the final element, so it changes the text in it and is not actually applied to the created element?

Thanks.
 
User avatar
sfernandez
Site Admin
Posts: 3027
Joined: 22 Dec 2011, 19:20

Re: Get content from ContentControl / DataTemplate

15 May 2024, 21:20

Hi, is exactly as you said, you are getting a pointer to the elements that define the template, not the ones generated for each ContentControl.
There are ways to reach the runtime elements just by traversing the visual tree (using VisualTreeHelper), but it is a bad practice.

Why don't you want to use data binding to update your UI? It will be more natural and a lot easier. You just need to create a view model that exposes 4 properties, one for each part of the UI that is repeated (let's call it MainViewModel). Then another view model for each of the parts (let's call it PartViewModel) where you will expose the properties for the text that you want to update. And the xaml will look like this:
<DataTemplate x:Key="PartTemplate">
  <StackPanel Margin="10">
    <TextBlock Text="{Binding Text1}" />
    <TextBlock Text="{Binding Text2}" />
    <TextBlock Text="{Binding Text3}" />
    <TextBlock Text="{Binding Text4}" />
    <TextBlock Text="{Binding Text5}" />
  </StackPanel>
</DataTemplate>
<Grid>
  <Grid.RowDefinitions>
    <RowDefinition/>
    <RowDefinition/>
  </Grid.RowDefinitions>
  <Grid.ColumnDefinitions>
    <ColumnDefinition/>
    <ColumnDefinition/>
  </Grid.ColumnDefinitions>
  <ContentControl Grid.Row="0" Grid.Column="0" Content="{Binding Part1}" ContentTemplate="{StaticResource PartTemplate}"/>
  <ContentControl Grid.Row="0" Grid.Column="1" Content="{Binding Part2}" ContentTemplate="{StaticResource PartTemplate}"/>
  <ContentControl Grid.Row="1" Grid.Column="0" Content="{Binding Part3}" ContentTemplate="{StaticResource PartTemplate}"/>
  <ContentControl Grid.Row="1" Grid.Column="1" Content="{Binding Part4}" ContentTemplate="{StaticResource PartTemplate}"/>
</Grid>
And on the render callback you just need to update the view models without worrying about UI elements.

Who is online

Users browsing this forum: Ahrefs [Bot], Semrush [Bot] and 13 guests