microsoft / microsoft/microsoft-ui-xaml
ScrollView ExtentChanged Fires Unexpectedly with ItemsRepeater During Slow Thumb Scroll at DPI Scalings 125% and 175% on High-Resolution Displays
- Dominant language
- C++
- Stars
- 8.4k
- Forks
- 942
- Avg merge
- 2d 7h
- Merged PRs (30d)
- 105
Description
### Describe the bug
The `ExtentChanged` event is unexpectedly triggered when manipulating the scroll thumb of a `ScrollView` containing an `ItemsRepeater`. This occurs intermittently under specific DPI scaling and resolution conditions.
### Steps to reproduce the bug
1. On Windows 11, set the display scaling to 125% or 175% and resolution to 3840x2160.
2. Build and launch the sample code provided below.
3. Slowly slide the scroll thumb to the bottom.
4. Slowly slide the scroll thumb to the top.
5. Repeat steps 3 and 4 approximately 10 times.
Result: The debug output log shows that the `ExtentChanged` event is triggered unintentionally.
### Expected behavior
The `ExtentChanged` event should not occur during scroll thumb operations. It is expected to fire only when the content's extent (size) changes, not during scrolling.
### Screenshots
(No screenshots available; the issue is observable in the debug output log. See "Log Examples" in Additional Context for sample logs.)
### NuGet package version
WinUI 3 - Windows App SDK 1.7.0: 1.7.250310001
### Windows version
Windows 11 (24H2): Build 26100
### Additional context
**Actual Behavior**:
- At 3840x2160 resolution, with scaling at 125% (DPI 120) and 175% (DPI 168), the `ExtentChanged` event occurs intermittently during scrolling.
- `ExtentHeight` exhibits slight fluctuations (e.g., 504000.0 ↔ 504000.8 at 125%, 502856.6 ↔ 502857.2 at 175%).
- No occurrences at scaling 100%, 150%, 200%, 225%, 250%, 300%, 350% (tested at 3840x2160).
- No occurrence at 1920x1080 resolution with 175% scaling.
- **Note**: The issue is less likely to occur with fast thumb movement but is more frequent with slow movement. It may frequently occur at specific scroll positions (e.g., Offset around 502343.2).
**Sample Code**:
pch.h
```cpp
#pragma once
#include
#include
#include
#include
#undef GetCurrentTime
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
```
MainWindow.xaml.h
```cpp
#pragma once
#include "MainWindow.g.h"
namespace winrt::ScrollViewDpiTest::implementation
{
struct SimpleElementFactory : implements
{
Microsoft::UI::Xaml::UIElement GetElement(Microsoft::UI::Xaml::ElementFactoryGetArgs const& args);
void RecycleElement(Microsoft::UI::Xaml::ElementFactoryRecycleArgs const& args) {}
};
struct MainWindow : MainWindowT
{
MainWindow();
void OnExtentChanged(Windows::Foundation::IInspectable const& sender, Windows::Foundation::IInspectable const& args);
private:
Microsoft::UI::Xaml::Controls::ScrollView m_scrollView{ nullptr };
HWND GetWindowHandle();
};
}
namespace winrt::ScrollViewDpiTest::factory_implementation
{
struct MainWindow : MainWindowT
{
};
}
```
MainWindow.xaml.cpp
```cpp
#include "pch.h"
#include "MainWindow.xaml.h"
#if __has_include("MainWindow.g.cpp")
#include "MainWindow.g.cpp"
#endif
using namespace winrt;
using namespace Microsoft::UI::Xaml;
namespace winrt::ScrollViewDpiTest::implementation
{
Microsoft::UI::Xaml::UIElement SimpleElementFactory::GetElement(Microsoft::UI::Xaml::ElementFactoryGetArgs const& args)
{
auto textBlock = Controls::TextBlock();
textBlock.Height(50);
textBlock.Text(args.Data().as());
return textBlock;
}
MainWindow::MainWindow()
{
InitializeComponent();
auto rootGrid = Controls::Grid();
m_scrollView = Controls::ScrollView();
auto itemsRepeater = Controls::ItemsRepeater();
auto items = winrt::single_threaded_observable_vector();
for (int i = 0; i < 10000; ++i)
{
items.Append(L"Item " + to_hstring(i));
}
itemsRepeater.ItemsSource(items);
auto elementFactory = make();
itemsRepeater.ItemTemplate(elementFactory);
m_scrollView.Content(itemsRepeater);
m_scrollView.ExtentChanged({ this, &MainWindow::OnExtentChanged });
rootGrid.Children().Append(m_scrollView);
Content(rootGrid);
HWND hWnd = GetWindowHandle();
if (hWnd)
{
ShowWindow(hWnd, SW_MAXIMIZE);
}
}
void MainWindow::OnExtentChanged(Windows::Foundation::IInspectable const& /*sender*/, Windows::Foundation::IInspectable const& /*args*/)
{
double extentHeight = m_scrollView.ExtentHeight();
double viewportHeight = m_scrollView.ViewportHeight();
double verticalOffset = m_scrollView.VerticalOffset();
SYSTEMTIME st;
GetLocalTime(&st);
wchar_t buffer[256];
swprintf_s(buffer, 256, L"[ExtentChanged] [%04d-%02d-%02d %02d:%02d:%02d.%03d] ExtentHeight: %.1f, ViewportHeight: %.1f, Offset: %.1f\n",
st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds,
extentHeight, viewportHeight, verticalOffset);
OutputDebugStringW(buffer);
}
HWND MainWindow::GetWindowHandle()
{
auto windowNative = try_as<::IWindowNative>();
HWND hWnd{ nullptr };
if (windowNative)
{
windowNative->get_WindowHandle(&hWnd);
}
return hWnd;
}
}
```
MainWindow.xaml
```xaml
```
**Log Examples**:
- **Scale 125% (3840x2160)**:
```
[ExtentChanged] [2025-03-29 18:05:43.243] ExtentHeight: 504000.8, ViewportHeight: 1656.8, Offset: 294477.1
[ExtentChanged] [2025-03-29 18:05:43.287] ExtentHeight: 504000.0, ViewportHeight: 1656.8, Offset: 295230.2
[ExtentChanged] [2025-03-29 18:05:44.032] ExtentHeight: 504000.8, ViewportHeight: 1656.8, Offset: 502343.2
[ExtentChanged] [2025-03-29 18:05:44.371] ExtentHeight: 504000.0, ViewportHeight: 1656.8, Offset: 502343.2
```
- **Scale 175% (3840x2160)**:
```
[ExtentChanged] [2025-03-29 18:10:55.538] ExtentHeight: 502856.6, ViewportHeight: 1163.4, Offset: 178621.6
[ExtentChanged] [2025-03-29 18:10:55.573] ExtentHeight: 502857.2, ViewportHeight: 1163.4, Offset: 178621.6
[ExtentChanged] [2025-03-29 18:10:58.743] ExtentHeight: 502856.6, ViewportHeight: 1163.4, Offset: 501693.7
[ExtentChanged] [2025-03-29 18:10:58.775] ExtentHeight: 502857.2, ViewportHeight: 1163.4, Offset: 501693.7
```
**Additional Information**:
- "The issue occurs at non-integer multiples of DPI 96 (125%, 175%) but not at integer or near-integer multiples (100%, 150%, 200%, etc.), though it may also depend on resolution."
- "At 3840x2160, the issue occurs at 125% and 175%, but not at 1920x1080 with 175%. Comprehensive testing across other resolutions has not been conducted, so it may not occur at different resolutions."
- "This could be due to a calculation error in scaling, potentially causing issues with the virtualization of `ItemsRepeater` and its interaction with `ScrollView`, particularly noticeable on high-resolution displays with slow scrolling."
Contributor guide
Research direction
Start with the sample entry points in MainWindow.xaml.cpp and MainWindow.xaml, reproduce the issue with ScrollView and ItemsRepeater at 125% and 175% scaling on a 3840x2160 display, and observe ExtentHeight during slow thumb movement. Compare the behavior with the unaffected scaling and resolution combinations listed in the report. Done means ExtentChanged no longer fires solely because scrolling causes the extent to fluctuate.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- desktop
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100