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

WPF DataGrid列调整事件:如何检测列缩放取消滚动条靠右吸附实现Excel预览效果

WPF DataGrid 列宽调整禁用滚动条吸附及Excel式预览实现方案

基础方案:禁用滚动条自动靠右吸附

核心原理是在列宽调整开始时记录当前水平滚动偏移量,调整过程中拦截默认的自动滚动逻辑,调整完成后恢复原有偏移量,即可解决吸附问题。

实现代码

首先创建自定义DataGrid类,重写列宽调整相关逻辑:

using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;

public class CustomDataGrid : DataGrid
{
    private double _savedHorizontalOffset;
    private bool _isColumnResizing;

    public CustomDataGrid()
    {
        AddHandler(Thumb.DragStartedEvent, new DragStartedEventHandler(OnColumnResizeStarted));
        AddHandler(Thumb.DragCompletedEvent, new DragCompletedEventHandler(OnColumnResizeCompleted));
    }

    private void OnColumnResizeStarted(object sender, DragStartedEventArgs e)
    {
        if (e.OriginalSource is Thumb thumb && thumb.TemplatedParent is DataGridColumnHeader)
        {
            _isColumnResizing = true;
            _savedHorizontalOffset = ScrollViewer.GetHorizontalOffset(this);
        }
    }

    private void OnColumnResizeCompleted(object sender, DragCompletedEventArgs e)
    {
        if (_isColumnResizing)
        {
            _isColumnResizing = false;
            var scrollViewer = GetVisualChild<ScrollViewer>(this);
            if (scrollViewer != null)
            {
                scrollViewer.ScrollToHorizontalOffset(_savedHorizontalOffset);
            }
        }
    }

    private static T GetVisualChild<T>(DependencyObject parent) where T : Visual
    {
        for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
        {
            var child = VisualTreeHelper.GetChild(parent, i);
            if (child is T typedChild)
                return typedChild;
            var result = GetVisualChild<T>(child);
            if (result != null)
                return result;
        }
        return null;
    }

    protected override void OnColumnWidthChanged(DataGridColumnEventArgs e)
    {
        if (!_isColumnResizing)
            base.OnColumnWidthChanged(e);
    }
}

XAML 替换使用

把你原有代码中的DataGrid标签替换为本地的CustomDataGrid即可:

<local:CustomDataGrid Grid.Column="0" ItemsSource="{Binding Items}" AutoGenerateColumns="False"
     SelectionMode="Extended" local:MultiSelect.IsEnabled="True" HorizontalScrollBarVisibility="Auto">
    <DataGrid.Columns>
        <DataGridTextColumn Header="Name" Binding="{Binding Name}" Width="100"/>
        <DataGridTextColumn Header="Name" Binding="{Binding Name}" Width="100"/>
        <DataGridTextColumn Header="Name" Binding="{Binding Name}" Width="100"/>
    </DataGrid.Columns>
</local:CustomDataGrid>

进阶方案:实现Excel式列宽调整预览效果

如果需要和Excel完全一致的拖动时显示虚线预览、松开后才修改列宽的效果,可以通过重写列头Thumb的拖动逻辑实现,拖动过程中仅绘制预览线,不实际修改列宽,完全避免滚动条实时调整的问题。

实现代码

using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Documents;
using System.Windows.Media;

public static class ExcelStyleColumnResizeBehavior
{
    public static readonly DependencyProperty EnableExcelStyleResizeProperty =
        DependencyProperty.RegisterAttached("EnableExcelStyleResize", typeof(bool), typeof(ExcelStyleColumnResizeBehavior), new PropertyMetadata(false, OnEnableExcelStyleResizeChanged));

    public static bool GetEnableExcelStyleResize(DependencyObject obj)
    {
        return (bool)obj.GetValue(EnableExcelStyleResizeProperty);
    }

    public static void SetEnableExcelStyleResize(DependencyObject obj, bool value)
    {
        obj.SetValue(EnableExcelStyleResizeProperty, value);
    }

    private static AdornerLayer _adornerLayer;
    private static ResizeAdorner _currentAdorner;

    private static void OnEnableExcelStyleResizeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is DataGrid dataGrid && (bool)e.NewValue)
        {
            dataGrid.Loaded += (s, args) => _adornerLayer = AdornerLayer.GetAdornerLayer(dataGrid);

            dataGrid.AddHandler(Thumb.DragStartedEvent, new DragStartedEventHandler((sender, args) =>
            {
                if (args.OriginalSource is Thumb thumb && thumb.TemplatedParent is DataGridColumnHeader header)
                {
                    thumb.Cursor = Cursors.SizeWE;
                    header.Tag = header.Column.Width;
                    _currentAdorner = new ResizeAdorner(header);
                    _adornerLayer.Add(_currentAdorner);
                    args.Handled = true;
                }
            }));

            dataGrid.AddHandler(Thumb.DragDeltaEvent, new DragDeltaEventHandler((sender, args) =>
            {
                if (args.OriginalSource is Thumb thumb && thumb.TemplatedParent is DataGridColumnHeader header && header.Tag is DataGridLength originalWidth)
                {
                    _currentAdorner.OffsetX = args.HorizontalChange;
                    args.Handled = true;
                }
            }));

            dataGrid.AddHandler(Thumb.DragCompletedEvent, new DragCompletedEventHandler((sender, args) =>
            {
                if (args.OriginalSource is Thumb thumb && thumb.TemplatedParent is DataGridColumnHeader header && header.Tag is DataGridLength originalWidth)
                {
                    var newWidth = Math.Max(0, originalWidth.Value + args.HorizontalChange);
                    header.Column.Width = new DataGridLength(newWidth, originalWidth.UnitType);
                    header.Tag = null;
                    _adornerLayer.Remove(_currentAdorner);
                    _currentAdorner = null;
                    args.Handled = true;
                }
            }));
        }
    }

    private class ResizeAdorner : Adorner
    {
        public double OffsetX { get; set; }
        private static readonly Pen _previewPen = new Pen(Brushes.Black, 1) { DashStyle = DashStyles.Dash };

        public ResizeAdorner(UIElement adornedElement) : base(adornedElement) { }

        protected override void OnRender(DrawingContext drawingContext)
        {
            var rect = new Rect(AdornedElement.RenderSize);
            var lineX = rect.Right + OffsetX;
            drawingContext.DrawLine(_previewPen, new Point(lineX, 0), new Point(lineX, ((DataGrid)VisualTreeHelper.GetParent(AdornedElement)).ActualHeight));
        }
    }
}

XAML 使用方式

直接在原有DataGrid上附加属性即可,无需修改其他逻辑:

<DataGrid Grid.Column="0" ItemsSource="{Binding Items}" AutoGenerateColumns="False"
     SelectionMode="Extended" local:MultiSelect.IsEnabled="True" HorizontalScrollBarVisibility="Auto"
     local:ExcelStyleColumnResizeBehavior.EnableExcelStyleResize="True">
    <DataGrid.Columns>
        <DataGridTextColumn Header="Name" Binding="{Binding Name}" Width="100"/>
        <DataGridTextColumn Header="Name" Binding="{Binding Name}" Width="100"/>
        <DataGridTextColumn Header="Name" Binding="{Binding Name}" Width="100"/>
    </DataGrid.Columns>
</DataGrid>

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.29 06:09:03