C++/WinRT UWP Xbox Game Bar Widget memory usage keeps increasing when updating XAML binding data with DispatcherTimer

YeaKong 25 Reputation points
2026-08-10T12:13:01.12+00:00

Hello,

I am developing a hardware monitoring widget based on Xbox Game Bar Widget using C++/WinRT and UWP XAML.

Environment

  • Windows 10 22H2
  • Visual Studio 2026
  • C++/WinRT
  • UWP Xbox Game Bar Widget
  • Microsoft.Gaming.XboxGameBar API

Scenario

A Win32 background process (SensorHost.exe) collects hardware sensor information and sends data through Named Pipe:

  • CPU temperature
  • CPU power
  • CPU fan speed
  • CPU clock
  • CPU utilization

The UWP Game Bar Widget receives this data and updates the XAML UI.

The current architecture:

SensorHost.exe
        |
        | Named Pipe
        |
HardwareManager
        |
        |
Widget1::m_data
        |
        |
DispatcherTimer (200ms)
        |
        |
x:Bind properties

Current implementation

Instead of updating TextBlock.Text directly, I changed to XAML data binding.

XAML:

<TextBlock
    x:Name="CpuFanText"
    Text="{x:Bind CpuFan}"
    Foreground="#F1F4F6"
    FontSize="18"
    FontWeight="Bold"/>

Widget1 constructor:

Widget1::Widget1() : m_data()
{
    InitializeComponent();

    App::Hardware().SetUpdateCallback(
        std::bind(&Widget1::UpdateDataCallback, this)
    );

    m_timer =
        Windows::UI::Xaml::DispatcherTimer();

    m_timer.Interval(
        std::chrono::milliseconds(200)
    );

    m_timer.Tick(
        { this, &Widget1::OnTimerTick }
    );

    m_timer.Start();
}

The callback only updates the local data:

void Widget1::UpdateDataCallback()
{
    auto data = App::Hardware().GetSensorData();
    m_data = data;
}

The UI is refreshed by the timer:

void Widget1::OnTimerTick(
    winrt::Windows::Foundation::IInspectable const&,
    winrt::Windows::Foundation::IInspectable const&)
{
    Bindings->Update();
}

The binding properties return hstring:

winrt::hstring Widget1::CpuTemp()
{
    return winrt::to_hstring((int)m_data.cpuTemp) + L" ℃";
}

winrt::hstring Widget1::CpuFan()
{
    return winrt::to_hstring((int)m_data.cpuFan) + L" RPM";
}

Problem

The memory usage of HardwareBar.exe continuously increases when the widget is running.

After further investigation, I found that the memory growth is directly related to the call:

void Widget1::OnTimerTick(
    winrt::Windows::Foundation::IInspectable const&,
    winrt::Windows::Foundation::IInspectable const&)
{
    Bindings->Update();
}

The behavior is:

When Bindings->Update(); is enabled:

void Widget1::OnTimerTick(
    winrt::Windows::Foundation::IInspectable const&,
    winrt::Windows::Foundation::IInspectable const&)
{
    Bindings->Update();
}

The memory usage of HardwareBar.exe continuously increases over time.

The working set keeps growing even though:

The amount of sensor data remains stable.

The widget UI size does not change.

No new pages or controls are created.

No additional threads are created.

When Bindings->Update(); is commented out:

void Widget1::OnTimerTick(
    winrt::Windows::Foundation::IInspectable const&,
    winrt::Windows::Foundation::IInspectable const&)
{
    // Bindings->Update();
}

The memory growth disappears.

The process memory remains stable during long-term execution.

Therefore, the issue appears to be specifically related to repeatedly calling Bindings->Update() on a C++/WinRT XAML x:Bind generated binding object.

The update interval is only 200ms:

m_timer.Interval(
    std::chrono::milliseconds(200)
);

which means Bindings->Update() is called approximately 5 times per second.

I would like to know:

Is repeatedly calling Bindings->Update() in C++/WinRT XAML expected to allocate internal objects that are not immediately released?

Is there a known memory leak issue with x:Bind and Bindings->Update()?

Is there a recommended pattern for updating real-time data in a C++/WinRT Xbox Game Bar Widget?

Widget1.h


#pragma once

#include "Widget1.g.h"
#include "App.h"

#include <winrt/Microsoft.Gaming.XboxGameBar.h>

namespace winrt::HardwareBar::implementation
{
    struct Widget1 : Widget1T<Widget1>
    {
        Widget1();

        Widget1::~Widget1()
        {
            if (m_widget)
            {
                m_widget.SettingsClicked(
                    m_settingsToken);
            }
            App::Hardware().SetUpdateCallback(nullptr);
        }

        int32_t MyProperty();
        void MyProperty(int32_t value);

        void MyButton_Click(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& args);

        winrt::fire_and_forget StartSensorHost();
    

        void OnNavigatedTo(winrt::Windows::UI::Xaml::Navigation::NavigationEventArgs const& e);

        // Settings click handler for widget settings click event
        Windows::Foundation::IAsyncAction SettingsButtonClick(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::Foundation::IInspectable const& e);

        winrt::hstring CpuUtil();
        winrt::hstring CpuTemp();
        winrt::hstring CpuClock();
        winrt::hstring CpuPower();
        winrt::hstring CpuFan();
        void UpdateDataCallback();
        void OnTimerTick(
            winrt::Windows::Foundation::IInspectable const&,
            winrt::Windows::Foundation::IInspectable const&);

    private:
        winrt::event_token m_settingsToken{};
        Microsoft::Gaming::XboxGameBar::XboxGameBarWidget m_widget{ nullptr };
        Microsoft::Gaming::XboxGameBar::XboxGameBarWidgetControl m_widgetControl{ nullptr };
        SensorData m_data{0};
        winrt::Windows::UI::Xaml::DispatcherTimer m_timer{ nullptr };
    };
}

namespace winrt::HardwareBar::factory_implementation
{
    struct Widget1 : Widget1T<Widget1, implementation::Widget1>
    {
    };
}


Widget1.cpp


#include "pch.h"
#include "Widget1.h"
#include "Widget1.g.cpp"
#include <winrt/Windows.ApplicationModel.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.UI.Core.h>
#include <iomanip>
#include <sstream>

using namespace winrt;
using namespace winrt::Windows::UI::Xaml;
using namespace winrt::Windows::ApplicationModel;
using namespace Microsoft::Gaming::XboxGameBar;

std::string FormatFloat(double value, int precision = 2)
{
    std::ostringstream oss;
    oss << std::fixed << std::setprecision(precision) << value;
    return oss.str();
}

namespace winrt::HardwareBar::implementation
{
    Widget1::Widget1() : m_data()
    {
        InitializeComponent();

        App::Hardware().SetUpdateCallback(std::bind(&Widget1::UpdateDataCallback, this));

        m_timer =
            Windows::UI::Xaml::DispatcherTimer();

        m_timer.Interval(
            std::chrono::milliseconds(200)
        );

        m_timer.Tick(
            { this, &Widget1::OnTimerTick }
        );

        m_timer.Start();
    }

    void Widget1::UpdateDataCallback() {
        auto data =
            App::Hardware().GetSensorData();
        m_data = data;
    }

    void Widget1::OnTimerTick(
        winrt::Windows::Foundation::IInspectable const&,
        winrt::Windows::Foundation::IInspectable const&)
    {
        Bindings->Update();
    }

    winrt::hstring Widget1::CpuUtil() { 
        return winrt::to_hstring((int)m_data.cpuUtil) + L" %";
    }
    winrt::hstring Widget1::CpuTemp() {
        return winrt::to_hstring((int)m_data.cpuTemp) + L" ℃";
    }
    winrt::hstring Widget1::CpuClock() {
        return winrt::to_hstring((int)m_data.cpuClock) + L" MHz";
    }
    winrt::hstring Widget1::CpuPower() {
        return winrt::to_hstring(FormatFloat(m_data.cpuPower) + " W");
    }
    winrt::hstring Widget1::CpuFan() {
        return winrt::to_hstring((int)m_data.cpuFan) + L" RPM";
    }


    int32_t Widget1::MyProperty()
    {
        throw hresult_not_implemented();
    }

    void Widget1::MyProperty(int32_t /* value */)
    {
        throw hresult_not_implemented();
    }

    void Widget1::MyButton_Click(IInspectable const&, RoutedEventArgs const&)
    {
        //myButton().Content(box_value(L"Clicked"));
    }

    winrt::fire_and_forget Widget1::StartSensorHost()
    {
        // 使用 co_await 异步等待,不阻塞 UI 线程
        co_await FullTrustProcessLauncher::LaunchFullTrustProcessForCurrentAppAsync();
    }

    void Widget1::OnNavigatedTo(Windows::UI::Xaml::Navigation::NavigationEventArgs const& e)
    {
        m_widget =
            e.Parameter()
            .as<Microsoft::Gaming::XboxGameBar::XboxGameBarWidget>();

        m_widget.MinWindowSize(
            { 200,30 }
        );

        m_widget.MaxWindowSize(
            { 600,70 }
        );

        m_widgetControl = XboxGameBarWidgetControl(m_widget);
        // Hook up event that's fired when our settings button is clicked
        m_settingsToken = m_widget.SettingsClicked({ this, &Widget1::SettingsButtonClick });

        
    }

    Windows::Foundation::IAsyncAction Widget1::SettingsButtonClick(
        winrt::Windows::Foundation::IInspectable const& sender,
        winrt::Windows::Foundation::IInspectable const& e)
    {
        auto strong_this{ get_strong() };
        co_await m_widget.ActivateSettingsAsync();

        //Comment out code below to  demonstrate how to activate settings with a Uri string
        //
        //hstring appExtID = L"WidgetSettings"; // ID of Settings Widget 
        //hstring appID = L"App";
        //hstring uriSubPath = L"[uriSubPath]";
        //hstring uriQuery = L"[?uriQuery]";
        //hstring uriFragment = L"[#uriFragment]";

        //Uri uri = m_widgetControl.CreateActivationUri(appID, appExtID, uriSubPath, uriQuery, uriFragment);

        //co_await m_widget.ActivateSettingsWithUriAsync(uri);
    }
}


Widget1.xaml


<Page
    x:Class="HardwareBar.Widget1"
    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="using:HardwareBar"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"  Padding="0"
    Margin="0" BorderThickness="0" Background="Transparent">

    <Grid Opacity="1" Padding="10,0,0,0"
        Margin="0" BorderThickness="0">
        
        <StackPanel
            HorizontalAlignment="Left"
            VerticalAlignment="Center"
            Orientation="Horizontal"
            Spacing="9" BorderThickness="0">

            <TextBlock
        Text="CPU"
        Foreground="#F1F4F6"
        FontSize="18"
        FontWeight="Bold"/>

        <TextBlock
            x:Name="CpuUtilText"
            Text="{x:Bind CpuUtil}"
            Foreground="#F1F4F6"
            FontSize="18"
            FontWeight="Bold"/>

            <TextBlock
            x:Name="CpuTempText"
            Text="{x:Bind CpuTemp}"
            Foreground="#F1F4F6"
            FontSize="18"
            FontWeight="Bold"/>

            <TextBlock
            x:Name="CpuClockText"
            Text="{x:Bind CpuClock}"
            Foreground="#F1F4F6"
            FontSize="18"
            FontWeight="Bold"/>

            <TextBlock
            x:Name="CpuPowerText"
            Text="{x:Bind CpuPower}"
            Foreground="#F1F4F6"
            FontSize="18"
            FontWeight="Bold"/>

            <TextBlock
            x:Name="CpuFanText"
            Text="{x:Bind CpuFan}"
            Foreground="#F1F4F6"
            FontSize="18"
            FontWeight="Bold"/>

        </StackPanel>
    </Grid>
</Page>


Developer technologies | Universal Windows Platform (UWP)

Answer accepted by question author
Jay Pham (WICLOUD CORPORATION) 4,355 Reputation points Microsoft External Staff Moderator
2026-08-11T00:30:21.5166667+00:00

Hi @YeaKong ,

Thank you for sharing the results of your investigation.

Using SwapChainPanel with Direct2D/Direct3D is a valid solution for this high-frequency rendering scenario. Since memory usage is now stable and performance has improved, the reported issue can be considered operationally resolved.

Microsoft documents that Bindings.Update() updates the values of all compiled bindings on the page or user control. In the original implementation, calling it every 200 ms caused all five properties to be reevaluated repeatedly, including creating new formatted hstring values. This produces continuous allocation and UI update activity.

However, increasing working-set memory alone does not conclusively demonstrate an x:Bind memory leak. Confirming a leak would require native heap snapshots showing allocations that remain retained and continue increasing over time.

Microsoft also documents that x:Bind defaults to OneTime. For changing data, the usual pattern is Mode=OneWay with an observable source implementing INotifyPropertyChanged. This lets individual properties notify the UI only when their values change, instead of refreshing the complete compiled binding set.

For a small number of text fields, another option is to cache the formatted values and update only the TextBlock values that changed. For a continuously rendered overlay, SwapChainPanel is appropriate because it is specifically designed to host DirectX swap-chain content within a XAML UI.

I also recommend stopping the DispatcherTimer and unregistering the hardware callback when the widget is unloaded or destroyed to prevent the widget from being retained beyond its intended lifetime.

Relevant Microsoft documentation:

In summary, the implementation change resolved the reported symptom. The current evidence indicates that frequent full binding reevaluation caused substantial allocation and UI processing, but it does not independently establish a confirmed memory leak in x:Bind.

If you were able to resolve the issue during troubleshooting, I would greatly appreciate it if you could share your feedback by interacting with the system or leaving a comment.

Thank you.

Was this answer helpful?

2 people found this answer helpful.

2 additional answers

Sort by: Most helpful
  1. saleha mubeen 10 Reputation points
    2026-08-10T12:41:33.85+00:00

    Bindings->Update() itself shouldn't normally cause an unbounded memory increase, so I'd first suspect the combination of frequent binding reevaluation and the lifetime of the widget/callback.

    One thing that stands out is this:

    std::bind(&Widget1::UpdateDataCallback, this)
    

    If Hardware().SetUpdateCallback() stores that callback, it can keep the Widget1 instance alive. Make sure the callback is cleared when the widget is unloaded/destroyed, and that the DispatcherTimer is also stopped and released.

    For the binding side, Bindings->Update() forces all x:Bind expressions to be reevaluated every 200 ms. Your properties also create new hstring values on every evaluation:

    return winrt::to_hstring((int)m_data.cpuFan) + L" RPM";
    

    That shouldn't produce a permanent leak by itself, but it does create allocations continuously. With multiple properties and a long-running Game Bar widget, it's worth testing whether the growth disappears if you update only the properties that actually changed.

    I'd try these tests separately:

    Temporarily remove Bindings->Update() and see whether memory remains stable.

    Call Bindings->Update() at a much lower frequency, e.g. once per second.

    Update a single TextBlock instead of refreshing the entire binding tree.

    Stop the timer and unregister the hardware callback in the widget's teardown/unloaded path.

    Check whether SetUpdateCallback() replaces the previous callback or accumulates callbacks internally.

    Use the Visual Studio native memory profiler/Windows Performance Recorder to determine whether the growth is native heap, XAML objects, or retained delegates.

    Also, x:Bind doesn't automatically mean that manually calling Bindings->Update() is necessary for every property. If you want incremental updates, another approach is to expose bindable properties and notify the UI only when the underlying sensor value changes rather than forcing the entire binding graph to refresh every 200 ms.

    The most important thing I'd investigate first is whether Bindings->Update() is actually leaking, or whether it is simply exposing an existing retention/allocation issue elsewhere. If commenting out that one call makes the memory completely stable, a small profiler trace comparing Update() vs. direct property updates should make the cause much easier to isolate.

    Was this answer helpful?

    2 people found this answer helpful.
    0 comments No comments

  2. YeaKong 25 Reputation points
    2026-08-10T18:40:15.0533333+00:00

    After further investigation, I found that the issue was caused by the UI update approach.

    Initially, I used XAML TextBlocks with frequent binding updates to display real-time hardware monitoring data. This caused continuous allocations and increasing memory usage over time.

    For real-time visualization scenarios, frequently updating XAML controls is not an ideal approach. I changed the rendering method to use SwapChainPanel with Direct2D/Direct3D, which renders the data directly through the graphics pipeline instead of constantly updating UI elements.

    After this change, memory usage became stable and the performance improved significantly.

    The conclusion is that XAML controls are suitable for normal application UI, but for high-frequency real-time monitoring or overlay scenarios, a Direct2D/Direct3D rendering approach is more appropriate.

    Thanks to everyone who provided suggestions during the investigation. @Jack Dang (WICLOUD CORPORATION) @saleha mubeen

    Was this answer helpful?

    1 person found this answer helpful.
    0 comments No comments

Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.