Skip to content

Unable to render oversized UI element correctly #11161

@CodingOctocat

Description

@CodingOctocat

Description

Potentially related topics:

Why the Visual.VisualOffset is double type but run the compositor in floats? #5389
Q: Can we replace the MIL code with double? Or provide an enum type to control whether calculations use float or double?

I am developing timeline-related controls. If zoomed into frame mode view with each frame scale set to 500px, a 1-hour segment would span a width of 500px * 24fps * 3600s = 43,200,000 (≈2^25, 43 million). The element does not render correctly, appearing to lose precision and exhibit ghosting.

large_view_render_error.mp4
large_view_render_error_scroll.mp4

Reproduction Steps

mini demo: https://github.com/CodingOctocat/LargeViewRenderError

<Window x:Class="LargeViewRenderError.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:LargeViewRenderError"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        Title="MainWindow"
        Width="800"
        Height="450"
        Loaded="Window_Loaded"
        SnapsToDevicePixels="True"
        UseLayoutRounding="True"
        mc:Ignorable="d">
    <!--  所有元素的 SnapsToDevicePixels 和 UseLayoutRounding 都进行过排列组合测试。  -->
    <ScrollViewer x:Name="sv"
                  HorizontalScrollBarVisibility="Visible"
                  SnapsToDevicePixels="True"
                  UseLayoutRounding="True"
                  VerticalScrollBarVisibility="Visible">
        <Canvas x:Name="canvas"
                Width="1000000000"
                Height="800"
                Background="Wheat"
                MouseDown="Canvas_MouseDown"
                MouseMove="Canvas_MouseMove"
                MouseUp="Canvas_MouseUp"
                SnapsToDevicePixels="True"
                UseLayoutRounding="True" />
    </ScrollViewer>
</Window>
public partial class MainWindow : Window
{
    private AdornerLayer _adornerLayer = null!;

    private bool _isSelecting;

    private SelectionBoxAdorner? _selectionBoxAdorner;

    private Point _startPoint;

    public MainWindow()
    {
        InitializeComponent();
    }

    // 鼠标按下事件,开始选区
    private void Canvas_MouseDown(object sender, MouseButtonEventArgs e)
    {
        // 只有左键点击时才开始
        if (e.ButtonState == MouseButtonState.Pressed && e.ChangedButton == MouseButton.Left)
        {
            _startPoint = e.GetPosition(canvas);
            _isSelecting = true;

            // 创建 SelectionBoxAdorner 并添加到 AdornerLayer
            _selectionBoxAdorner = new SelectionBoxAdorner(canvas, _startPoint);
            _adornerLayer.Add(_selectionBoxAdorner);
        }
    }

    // 鼠标移动事件,更新选区大小
    private void Canvas_MouseMove(object sender, MouseEventArgs e)
    {
        if (_isSelecting && _selectionBoxAdorner != null)
        {
            var currentPoint = e.GetPosition(canvas);
            _selectionBoxAdorner.UpdateEndPoint(currentPoint);
            Debug.WriteLine(">>> " + currentPoint);
        }
    }

    // 鼠标松开事件,结束选区
    private void Canvas_MouseUp(object sender, MouseButtonEventArgs e)
    {
        if (_isSelecting)
        {
            _isSelecting = false;

            if (_selectionBoxAdorner != null)
            {
                _adornerLayer.Remove(_selectionBoxAdorner);
            }
        }
    }

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        _adornerLayer = AdornerLayer.GetAdornerLayer(canvas); // 获取 AdornerLayer
    }
}
public class SelectionBoxAdorner : Adorner
{
    private readonly Pen _border;

    private Point _end;

    private Point _start;

    public Brush BorderBrush { get; set; } = Brushes.DodgerBlue;

    public Brush Fill { get; set; }

    public SelectionBoxAdorner(UIElement adornedElement, Point start) : base(adornedElement)
    {
        BorderBrush = Brushes.DodgerBlue;
        BorderBrush.Freeze();
        Fill = new SolidColorBrush(Color.FromArgb(50, 0, 120, 215));
        Fill.Freeze();
        _border = new(BorderBrush, 1);

        _start = start;
        _end = start;
        IsHitTestVisible = false;

        // 测试
        UseLayoutRounding = true;
        SnapsToDevicePixels = true;
    }

    public Rect GetSelectionRect()
    {
        return new Rect(_start, _end);
    }

    public void UpdateEndPoint(Point end)
    {
        _end = end;
        _end.X = Math.Max(_end.X, 0);
        _end.Y = Math.Max(_end.Y, 0);
        InvalidateVisual();
    }

    protected override void OnRender(DrawingContext dc)
    {
        var rect = GetSelectionRect();
        dc.DrawRectangle(Fill, _border, rect);
    }
}

Expected behavior

Correctly render elements larger than 2^21 pixels.

Actual behavior

Elements larger than 2^21px cannot be rendered correctly. Issues such as loss of precision, screen flickering, and ghosting may occur.

Test:

2^23: Rendering error

23

2^22: Rendering imperfect

22

2^21: Perfect rendering

21

Regression?

No response

Known Workarounds

Using IScrollInfo + UI virtualization.

Impact

No response

Configuration

  • .NET9
  • Win11 24h2
  • x64

Other information

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions