You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WPF Grid列调整大小后丢失星号(Star)比例问题求助

解决WPF自定义TreeView列比例丢失问题

针对你遇到的调整列宽后表头丢失3:3:1比例、Grid.IsSharedSizeScope不支持星号尺寸同步的问题,提供以下三种可行方案:

方案1:通过附加属性维护列比例与同步

创建附加属性统一管理同组Grid的列比例,监听布局变化和手动调整操作,自动维护比例或同步列宽:

附加属性代码

public static class GridColumnSync
{
    // 同步组标识,同组Grid列宽保持一致
    public static readonly DependencyProperty SyncGroupProperty =
        DependencyProperty.RegisterAttached("SyncGroup", typeof(string), typeof(GridColumnSync), 
            new PropertyMetadata(null, OnSyncGroupChanged));

    // 列比例值,对应3:3:1设置为3、3、1
    public static readonly DependencyProperty ColumnRatioProperty =
        DependencyProperty.RegisterAttached("ColumnRatio", typeof(double), typeof(GridColumnSync), 
            new PropertyMetadata(1.0));

    // 标记是否手动调整过列宽
    private static readonly DependencyProperty IsManuallyAdjustedProperty =
        DependencyProperty.RegisterAttached("IsManuallyAdjusted", typeof(bool), typeof(GridColumnSync), 
            new PropertyMetadata(false));

    public static void SetSyncGroup(Grid element, string value) => element.SetValue(SyncGroupProperty, value);
    public static string GetSyncGroup(Grid element) => (string)element.GetValue(SyncGroupProperty);

    public static void SetColumnRatio(ColumnDefinition element, double value) => element.SetValue(ColumnRatioProperty, value);
    public static double GetColumnRatio(ColumnDefinition element) => (double)element.GetValue(ColumnRatioProperty);

    private static void OnSyncGroupChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is Grid grid)
        {
            if (e.OldValue != null)
            {
                grid.SizeChanged -= OnGridSizeChanged;
                grid.RemoveHandler(GridSplitter.DragCompletedEvent, new DragCompletedEventHandler(OnGridSplitterDragCompleted));
            }
            if (e.NewValue != null)
            {
                grid.SizeChanged += OnGridSizeChanged;
                grid.AddHandler(GridSplitter.DragCompletedEvent, new DragCompletedEventHandler(OnGridSplitterDragCompleted));
            }
        }
    }

    private static void OnGridSizeChanged(object sender, SizeChangedEventArgs e)
    {
        if (sender is Grid grid && !(bool)grid.GetValue(IsManuallyAdjustedProperty))
        {
            double totalRatio = grid.ColumnDefinitions.Sum(cd => GetColumnRatio(cd));
            double availableWidth = grid.ActualWidth - grid.Padding.Left - grid.Padding.Right;

            foreach (var cd in grid.ColumnDefinitions)
            {
                double ratio = GetColumnRatio(cd);
                cd.Width = new GridLength(ratio / totalRatio, GridUnitType.Star);
            }

            // 同步同组其他Grid的列宽(可通过VisualTreeHelper遍历获取同组Grid)
            SyncSameGroupColumns(grid);
        }
    }

    private static void OnGridSplitterDragCompleted(object sender, DragCompletedEventArgs e)
    {
        if (sender is Grid grid)
        {
            grid.SetValue(IsManuallyAdjustedProperty, true);
            SyncSameGroupColumns(grid);
        }
    }

    private static void SyncSameGroupColumns(Grid sourceGrid)
    {
        string group = GetSyncGroup(sourceGrid);
        // 此处可通过VisualTreeHelper遍历可视化树,找到所有标记了相同SyncGroup的Grid,同步列宽
        // 示例逻辑(需根据实际场景调整):
        // var parent = VisualTreeHelper.GetParent(sourceGrid);
        // foreach (var child in FindVisualChildren<Grid>(parent))
        // {
        //     if (GetSyncGroup(child) == group && child != sourceGrid)
        //     {
        //         for (int i = 0; i < sourceGrid.ColumnDefinitions.Count && i < child.ColumnDefinitions.Count; i++)
        //         {
        //             child.ColumnDefinitions[i].Width = sourceGrid.ColumnDefinitions[i].Width;
        //         }
        //     }
        // }
    }

    private static IEnumerable<T> FindVisualChildren<T>(DependencyObject parent) where T : DependencyObject
    {
        for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
        {
            var child = VisualTreeHelper.GetChild(parent, i);
            if (child is T t)
                yield return t;
            foreach (var item in FindVisualChildren<T>(child))
                yield return item;
        }
    }
}

XAML使用方式

给表头和所有项的Grid设置相同的SyncGroup,并给每个列定义对应比例:

<!-- 表头Grid -->
<Grid local:GridColumnSync.SyncGroup="TreeViewColumns">
    <Grid.ColumnDefinitions>
        <ColumnDefinition local:GridColumnSync.ColumnRatio="3"/>
        <ColumnDefinition local:GridColumnSync.ColumnRatio="3"/>
        <ColumnDefinition local:GridColumnSync.ColumnRatio="1"/>
    </Grid.ColumnDefinitions>
    <!-- 表头内容 -->
</Grid>

<!-- TreeView项模板中的Grid -->
<Grid local:GridColumnSync.SyncGroup="TreeViewColumns">
    <Grid.ColumnDefinitions>
        <ColumnDefinition local:GridColumnSync.ColumnRatio="3"/>
        <ColumnDefinition local:GridColumnSync.ColumnRatio="3"/>
        <ColumnDefinition local:GridColumnSync.ColumnRatio="1"/>
    </Grid.ColumnDefinitions>
    <!-- 项内容 -->
</Grid>

方案2:绑定列宽到ViewModel属性

将表头和所有项的列宽绑定到同一个ViewModel的属性,实现双向同步,初始时设置为3:3:1的星号比例:

ViewModel代码

public class TreeViewModel : INotifyPropertyChanged
{
    private GridLength _column1Width = new GridLength(3, GridUnitType.Star);
    public GridLength Column1Width
    {
        get => _column1Width;
        set { _column1Width = value; OnPropertyChanged(); }
    }

    private GridLength _column2Width = new GridLength(3, GridUnitType.Star);
    public GridLength Column2Width
    {
        get => _column2Width;
        set { _column2Width = value; OnPropertyChanged(); }
    }

    private GridLength _column3Width = new GridLength(1, GridUnitType.Star);
    public GridLength Column3Width
    {
        get => _column3Width;
        set { _column3Width = value; OnPropertyChanged(); }
    }

    // 重置列比例为3:3:1
    public void ResetColumnRatio()
    {
        Column1Width = new GridLength(3, GridUnitType.Star);
        Column2Width = new GridLength(3, GridUnitType.Star);
        Column3Width = new GridLength(1, GridUnitType.Star);
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

XAML使用方式

确保表头和项的Grid都绑定到同一个ViewModel实例:

<!-- 资源中声明ViewModel -->
<Window.Resources>
    <local:TreeViewModel x:Key="TreeVm"/>
</Window.Resources>

<!-- 表头Grid -->
<Grid DataContext="{StaticResource TreeVm}">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="{Binding Column1Width, Mode=TwoWay}"/>
        <ColumnDefinition Width="{Binding Column2Width, Mode=TwoWay}"/>
        <ColumnDefinition Width="{Binding Column3Width, Mode=TwoWay}"/>
    </Grid.ColumnDefinitions>
    <!-- 表头内容 -->
</Grid>

<!-- TreeView项模板 -->
<TreeView ItemTemplate="{DynamicResource TreeItemTemplate}">
    <TreeView.Resources>
        <DataTemplate x:Key="TreeItemTemplate">
            <Grid DataContext="{StaticResource TreeVm}">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="{Binding Column1Width, Mode=TwoWay}"/>
                    <ColumnDefinition Width="{Binding Column2Width, Mode=TwoWay}"/>
                    <ColumnDefinition Width="{Binding Column3Width, Mode=TwoWay}"/>
                </Grid.ColumnDefinitions>
                <!-- 项内容 -->
            </Grid>
        </DataTemplate>
    </TreeView.Resources>
</TreeView>

方案3:自定义RatioGrid控件

重写Grid的布局逻辑,在ArrangeOverride中强制维护列比例,直到用户手动调整列宽:

自定义Grid代码

public class RatioGrid : Grid
{
    public static readonly DependencyProperty ColumnRatiosProperty =
        DependencyProperty.Register("ColumnRatios", typeof(double[]), typeof(RatioGrid), 
            new PropertyMetadata(new double[] { 1, 1, 1 }));

    public double[] ColumnRatios
    {
        get => (double[])GetValue(ColumnRatiosProperty);
        set => SetValue(ColumnRatiosProperty, value);
    }

    private bool _isManuallyAdjusted;

    protected override void OnMouseUp(MouseButtonEventArgs e)
    {
        base.OnMouseUp(e);
        // 检测GridSplitter拖动操作,标记为手动调整
        if (e.OriginalSource is GridSplitter)
        {
            _isManuallyAdjusted = true;
        }
    }

    protected override Size ArrangeOverride(Size arrangeSize)
    {
        // 未手动调整时,强制按比例分配列宽
        if (!_isManuallyAdjusted && ColumnRatios != null && ColumnRatios.Length == ColumnDefinitions.Count)
        {
            double totalRatio = ColumnRatios.Sum();
            double availableWidth = arrangeSize.Width - Padding.Left - Padding.Right;

            for (int i = 0; i < ColumnDefinitions.Count; i++)
            {
                double ratio = ColumnRatios[i];
                ColumnDefinitions[i].Width = new GridLength(ratio / totalRatio, GridUnitType.Star);
            }
        }

        return base.ArrangeOverride(arrangeSize);
    }
}

XAML使用方式

表头和项的Grid都替换为自定义的RatioGrid,并设置列比例:

<!-- 表头Grid -->
<local:RatioGrid ColumnRatios="3,3,1">
    <RatioGrid.ColumnDefinitions>
        <ColumnDefinition/>
        <ColumnDefinition/>
        <ColumnDefinition/>
    </RatioGrid.ColumnDefinitions>
    <!-- 表头内容 -->
</local:RatioGrid>

<!-- TreeView项模板中的Grid -->
<local:RatioGrid ColumnRatios="3,3,1">
    <RatioGrid.ColumnDefinitions>
        <ColumnDefinition/>
        <ColumnDefinition/>
        <ColumnDefinition/>
    </RatioGrid.ColumnDefinitions>
    <!-- 项内容 -->
</local:RatioGrid>

内容的提问来源于stack exchange,提问作者user30607100

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 01:44:57