Skip to content

窗口隐藏(Visibility.Hidden)期间实例化的 hc:Divider 分隔线渲染宽度为 0 #1818

Description

@ABA2396

Describe the bug

当宿主窗口处于 Visibility.Hidden 状态(典型的 「最小化到托盘」 场景)时,如果有 hc:Divider(或任何以 hc:Row/hc:Col 为模板根的控件)的模板在此期间被实例化并加入可视化树,这些元素会永久渲染失败:

  • Divider 控件本身能拿到正常的布局尺寸(例如 420×15);
  • 但其模板内的三个 hc:Col(左线 / 内容 / 右线)的 ActualWidth/ActualHeight 均保持 0,视觉上完全不显示;
  • 窗口恢复 Visible 后不会自愈,手动调用 InvalidateMeasure / InvalidateArrange / UpdateLayout 也无法恢复;
  • 使用标准 Grid/Border 模板的元素在同样时序下显示正常,因此该问题特定于 Row/Col 栅格。

Steps to reproduce the bug

最小复现工程

DividerTest.csproj

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net10.0-windows10.0.17763.0</TargetFramework>
    <Nullable>enable</Nullable>
    <UseWPF>true</UseWPF>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="HandyControl" Version="3.6.0-rc3" />
  </ItemGroup>
</Project>

App.xaml

<Application x:Class="DividerTest.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="pack://application:,,,/HandyControl;component/Themes/SkinDefault.xaml" />
                <ResourceDictionary Source="pack://application:,,,/HandyControl;component/Themes/Theme.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

MainWindow.xaml

<Window x:Class="DividerTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:hc="https://handyorg.github.io/handycontrol"
        Title="Divider repro" Width="900" Height="600">
    <Window.Resources>
        <DataTemplate x:Key="LogTemplate">
            <hc:Divider MaxWidth="420" HorizontalContentAlignment="Center"
                        Content="{Binding}" />
        </DataTemplate>
    </Window.Resources>
    <ScrollViewer HorizontalScrollBarVisibility="Disabled">
        <ItemsControl x:Name="LogList" ItemTemplate="{StaticResource LogTemplate}" />
    </ScrollViewer>
</Window>

MainWindow.xaml.cs

using System;
using System.Windows;
using System.Windows.Threading;

namespace DividerTest;

public partial class MainWindow
{
    public MainWindow()
    {
        InitializeComponent();

        // 1s 后添加第一个 divider,随即最小化并隐藏窗口(模拟最小化到托盘)
        var t1 = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
        t1.Tick += (s, e) =>
        {
            t1.Stop();
            LogList.Items.Add("A: 添加后立即隐藏");
            WindowState = WindowState.Minimized;
            ShowInTaskbar = false;
            Visibility = Visibility.Hidden;

            // 2s 后(窗口仍隐藏)再添加一个
            var t2 = new DispatcherTimer { Interval = TimeSpan.FromSeconds(2) };
            t2.Tick += (s2, e2) =>
            {
                t2.Stop();
                LogList.Items.Add("B: 隐藏期间添加");

                // 2s 后恢复窗口
                var t3 = new DispatcherTimer { Interval = TimeSpan.FromSeconds(2) };
                t3.Tick += (s3, e3) =>
                {
                    t3.Stop();
                    Show();
                    WindowState = WindowState.Normal;
                    Activate();
                };
                t3.Start();
            };
            t2.Start();
        };
        t1.Start();
    }
}

运行后观察:窗口恢复显示时,两个 divider 都不渲染(分隔线与文字均不显示)。触发条件是 「模板实例化(首次布局展开)发生在窗口 Hidden 期间」——第一个 divider 虽然是在窗口可见时添加的,但其模板展开前窗口已被隐藏,同样复现。

Expected behavior

窗口恢复 Visible 后,hc:Divider 应正常渲染出分隔线与内容;即使模板在窗口隐藏期间实例化,布局也应在窗口恢复后自动恢复正常。

Screenshots

见附件 screenshot.png(同一 DataTemplate、同一时序下的对照):hc:Divider 原始模板的行完全空白;带 [fix] 标记的行是仅把模板根 hc:Row 替换为修复版子类(见 Additional context)后的效果,分隔线正常。

Image

NuGet package version

HandyControl 3.5.1

IDE

No response

Framework type

No response

Windows version

No response

Additional context

根因分析

Row.ArrangeOverridesrc/Shared/HandyControl_Shared/Controls/Panel/Grid/Row.cs):

protected override Size ArrangeOverride(Size finalSize)
{
    ...
    foreach (var child in cols)
    {
        if (!child.IsVisible)      // ← 问题所在
        {
            continue;
        }
        ...
        child.Arrange(childBounds);
    }
    return finalSize;
}

当模板在宿主窗口 Hidden 期间实例化时,布局过程中 hc:ColIsVisiblefalse(受祖先窗口隐藏状态影响),这些 Colcontinue 跳过、从未被 Arrange。窗口恢复可见后,被跳过 ArrangeCol 布局状态无法再恢复(InvalidateMeasure/InvalidateArrange 均无效),ActualWidth/ActualHeight 永久保持 0。Measure 阶段是正常的(ColDesiredSize 与可见时添加的完全一致),仅 Arrange 失败。

诊断数据(窗口恢复显示后读取可视化树):

[hc:Divider 原始]  Divider Actual=420.0x15.2, Line ActualW=0.0
    Row: _fixedWidth=81.0, RenderSize=420.0x15.2
      col[0] Col: Desired=169.3x1.0, Actual=0.0x0.0
      col[1] Col: Desired=81.0x15.2,  Actual=0.0x0.0
      col[2] Col: Desired=169.3x1.0, Actual=0.0x0.0
[MyRow 修复版]     Divider Actual=420.0x15.2, Line ActualW=169.7

建议修复(已验证)

Row.ArrangeOverride 中的检查改为读取元素自身的 Visibility 属性(不受祖先窗口隐藏状态影响):

if (child.Visibility != Visibility.Visible)
{
    continue;
}

已通过子类化 Row 覆写 ArrangeOverride(仅替换这一处判断,其余逻辑与 3.6.0-rc3 完全一致)验证:同一 DataTemplate、同一时序下,原始版 Line ActualW = 0,修复版 Line ActualW ≈ 169,分隔线与内容均正常渲染。

实际影响场景

任何 「最小化到托盘」 的应用(WindowState = Minimized + Visibility = Hidden)在隐藏期间动态添加 hc:Divider(或 Row/Col 栅格)内容——例如日志分区线、运行时生成的分区 UI——都会触发,且该元素此后永久不可见。

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