首页 文章

WPF 4 DataGrid:将行号放入RowHeader

提问于
浏览
19

我希望将行号放入WPF 4 DataGrid的RowHeader中,以便它具有类似Excel的列,用于DataGrid的行号 .

我在网上看到的解决方案建议在业务对象中添加索引字段 . 这不是一个真正的选择,因为DataGrid将会被大量使用,我们不希望不断跟踪这些索引字段的变化 .

非常感谢

5 回答

  • 37

    一种方法是在 DataGrid 的LoadingRow事件中添加它们 .

    <DataGrid Name="DataGrid" LoadingRow="DataGrid_LoadingRow" ... />
    
    void DataGrid_LoadingRow(object sender, DataGridRowEventArgs e)
    {
        // Adding 1 to make the row count start at 1 instead of 0
        // as pointed out by daub815
        e.Row.Header = (e.Row.GetIndex() + 1).ToString(); 
    }
    

    Update
    要使其与WPF Toolkit中的.NET 3.5 DataGrid一起使用,需要进行一些修改 . 索引仍然正确生成,但使用虚拟化时输出失败 . 对 RowHeaderTemplate 的以下修改修复了这个问题

    <toolkit:DataGrid LoadingRow="DataGrid_LoadingRow">
        <toolkit:DataGrid.RowHeaderTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type toolkit:DataGridRow}},
                                          Path=Header}"/>
            </DataTemplate>
        </toolkit:DataGrid.RowHeaderTemplate>
    </toolkit:DataGrid>
    

    Edit 2012-07-05
    如果在源列表中添加或删除项目,则数字将不同步,直到滚动列表,因此再次调用 LoadingRow . 解决这个问题有点复杂,我现在能想到的最好的解决方案是保持上面的 LoadingRow 解决方案 .

    • 订阅 dataGrid.ItemContainerGenerator.ItemsChanged

    • 在事件处理程序中,在可视树中查找所有子项 DataGridRows

    • 将 Headers 设置为每个 DataGridRow 的索引

    这是一个执行此操作的附加行为 . 像这样使用它

    <DataGrid ItemsSource="{Binding ...}"
              behaviors:DataGridBehavior.DisplayRowNumber="True">
    

    DisplayRowNumber

    public class DataGridBehavior
    {
        #region DisplayRowNumber
    
        public static DependencyProperty DisplayRowNumberProperty =
            DependencyProperty.RegisterAttached("DisplayRowNumber",
                                                typeof(bool),
                                                typeof(DataGridBehavior),
                                                new FrameworkPropertyMetadata(false, OnDisplayRowNumberChanged));
        public static bool GetDisplayRowNumber(DependencyObject target)
        {
            return (bool)target.GetValue(DisplayRowNumberProperty);
        }
        public static void SetDisplayRowNumber(DependencyObject target, bool value)
        {
            target.SetValue(DisplayRowNumberProperty, value);
        }
    
        private static void OnDisplayRowNumberChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
        {
            DataGrid dataGrid = target as DataGrid;
            if ((bool)e.NewValue == true)
            {
                EventHandler<DataGridRowEventArgs> loadedRowHandler = null;
                loadedRowHandler = (object sender, DataGridRowEventArgs ea) =>
                {
                    if (GetDisplayRowNumber(dataGrid) == false)
                    {
                        dataGrid.LoadingRow -= loadedRowHandler;
                        return;
                    }
                    ea.Row.Header = ea.Row.GetIndex();
                };
                dataGrid.LoadingRow += loadedRowHandler;
    
                ItemsChangedEventHandler itemsChangedHandler = null;
                itemsChangedHandler = (object sender, ItemsChangedEventArgs ea) =>
                {
                    if (GetDisplayRowNumber(dataGrid) == false)
                    {
                        dataGrid.ItemContainerGenerator.ItemsChanged -= itemsChangedHandler;
                        return;
                    }
                    GetVisualChildCollection<DataGridRow>(dataGrid).
                        ForEach(d => d.Header = d.GetIndex());
                };
                dataGrid.ItemContainerGenerator.ItemsChanged += itemsChangedHandler;
            }
        }
    
        #endregion // DisplayRowNumber
    
        #region Get Visuals
    
        private static List<T> GetVisualChildCollection<T>(object parent) where T : Visual
        {
            List<T> visualCollection = new List<T>();
            GetVisualChildCollection(parent as DependencyObject, visualCollection);
            return visualCollection;
        }
    
        private static void GetVisualChildCollection<T>(DependencyObject parent, List<T> visualCollection) where T : Visual
        {
            int count = VisualTreeHelper.GetChildrenCount(parent);
            for (int i = 0; i < count; i++)
            {
                DependencyObject child = VisualTreeHelper.GetChild(parent, i);
                if (child is T)
                {
                    visualCollection.Add(child as T);
                }
                if (child != null)
                {
                    GetVisualChildCollection(child, visualCollection);
                }
            }
        }
    
        #endregion // Get Visuals
    }
    
  • 2

    Edit: Apparently scrolling changes the index so the binding won't work like that...

    一个(看似)干净的模板解决方案:
    XAML:

    <Window
        ...
        xmlns:local="clr-namespace:Test"
        DataContext="{Binding RelativeSource={RelativeSource Mode=Self}}">
        <Window.Resources>
            <local:RowToIndexConv x:Key="RowToIndexConv"/>
        </Window.Resources>
            <DataGrid ItemsSource="{Binding GridData}">
                <DataGrid.RowHeaderTemplate>
                    <DataTemplate>
                        <TextBlock Margin="2" Text="{Binding RelativeSource={RelativeSource AncestorType=DataGridRow}, Converter={StaticResource RowToIndexConv}}"/>
                    </DataTemplate>
                </DataGrid.RowHeaderTemplate>
            </DataGrid>
    </Window>
    

    转换器:

    public class RowToIndexConv : IValueConverter
    {
    
        #region IValueConverter Members
    
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            DataGridRow row = value as DataGridRow;
            return row.GetIndex() + 1;
        }
    
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    
        #endregion
    }
    
  • 1

    如果添加或删除行,则所有这些方法都不起作用 . 在这种情况下,您应该刷新行索引 . 看看这个行为:

    public static class DataGridBehavior
    {
        #region RowNumbers property
    
        public static readonly DependencyProperty RowNumbersProperty =
            DependencyProperty.RegisterAttached("RowNumbers", typeof (bool), typeof (DataGridBehavior), 
            new FrameworkPropertyMetadata(false, OnRowNumbersChanged));
    
        private static void OnRowNumbersChanged(DependencyObject source, DependencyPropertyChangedEventArgs args)
        {
            DataGrid grid = source as DataGrid;
            if (grid == null)
                return;
            if ((bool)args.NewValue)
            {
                grid.LoadingRow += onGridLoadingRow;
                grid.UnloadingRow += onGridUnloadingRow;
            }
            else
            {
                grid.LoadingRow -= onGridLoadingRow;
                grid.UnloadingRow -= onGridUnloadingRow;
            }
        }
    
        private static void refreshDataGridRowNumbers(object sender)
        {
            DataGrid grid = sender as DataGrid;
            if (grid == null)
                return;
    
            foreach (var item in grid.Items)
            {
                var row = (DataGridRow)grid.ItemContainerGenerator.ContainerFromItem(item);
                if (row != null)
                    row.Header = row.GetIndex() + 1;
            }
        }
    
        private static void onGridUnloadingRow(object sender, DataGridRowEventArgs e)
        {
            refreshDataGridRowNumbers(sender);
        }
    
        private static void onGridLoadingRow(object sender, DataGridRowEventArgs e)
        {
            refreshDataGridRowNumbers(sender);
        }
    
        [AttachedPropertyBrowsableForType(typeof(DataGrid))]
        public static void SetRowNumbers(DependencyObject element, bool value)
        {
            element.SetValue(RowNumbersProperty, value);
        }
    
        public static bool GetRowNumbers(DependencyObject element)
        {
            return (bool) element.GetValue(RowNumbersProperty);
        }
    
        #endregion
    }
    
  • 0

    @Fredrik Hedblad的回答对我有用 . 谢谢!

    我添加了另一个属性来获取“偏移”值,因此DataGrid可以从0或1(或任何设置)开始 .

    要使用该行为,(注意'b'是命名空间)

    <DataGrid ItemsSource="{Binding ...}"
          b:DataGridBehavior.DisplayRowNumberOffset="1"
          b:DataGridBehavior.DisplayRowNumber="True">
    

    修改后的类:

    /// <summary>
    /// Collection of DataGrid behavior
    /// </summary>
    public static class DataGridBehavior
    {
        #region DisplayRowNumberOffset
    
        /// <summary>
        /// Sets the starting value of the row header if enabled
        /// </summary>
        public static DependencyProperty DisplayRowNumberOffsetProperty =
            DependencyProperty.RegisterAttached("DisplayRowNumberOffset",
                                                typeof(int),
                                                typeof(DataGridBehavior),
                                                new FrameworkPropertyMetadata(0, OnDisplayRowNumberOffsetChanged));
    
        public static int GetDisplayRowNumberOffset(DependencyObject target)
        {
            return (int)target.GetValue(DisplayRowNumberOffsetProperty);
        }
    
        public static void SetDisplayRowNumberOffset(DependencyObject target, int value)
        {
            target.SetValue(DisplayRowNumberOffsetProperty, value);
        }
    
        private static void OnDisplayRowNumberOffsetChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
        {
            DataGrid dataGrid = target as DataGrid;
            int offset = (int)e.NewValue;
    
            if (GetDisplayRowNumber(target))
            {
                WPFUtil.GetVisualChildCollection<DataGridRow>(dataGrid).
                        ForEach(d => d.Header = d.GetIndex() + offset);
            }
        }
    
        #endregion
    
        #region DisplayRowNumber
    
        /// <summary>
        /// Enable display of row header automatically
        /// </summary>
        /// <remarks>
        /// Source: 
        /// </remarks>
        public static DependencyProperty DisplayRowNumberProperty =
            DependencyProperty.RegisterAttached("DisplayRowNumber",
                                                typeof(bool),
                                                typeof(DataGridBehavior),
                                                new FrameworkPropertyMetadata(false, OnDisplayRowNumberChanged));
    
        public static bool GetDisplayRowNumber(DependencyObject target)
        {
            return (bool)target.GetValue(DisplayRowNumberProperty);
        }
    
        public static void SetDisplayRowNumber(DependencyObject target, bool value)
        {
            target.SetValue(DisplayRowNumberProperty, value);
        }
    
        private static void OnDisplayRowNumberChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
        {
            DataGrid dataGrid = target as DataGrid;
            if ((bool)e.NewValue == true)
            {
                int offset = GetDisplayRowNumberOffset(target);
    
                EventHandler<DataGridRowEventArgs> loadedRowHandler = null;
                loadedRowHandler = (object sender, DataGridRowEventArgs ea) =>
                {
                    if (GetDisplayRowNumber(dataGrid) == false)
                    {
                        dataGrid.LoadingRow -= loadedRowHandler;
                        return;
                    }
                    ea.Row.Header = ea.Row.GetIndex() + offset;
                };
                dataGrid.LoadingRow += loadedRowHandler;
    
                ItemsChangedEventHandler itemsChangedHandler = null;
                itemsChangedHandler = (object sender, ItemsChangedEventArgs ea) =>
                {
                    if (GetDisplayRowNumber(dataGrid) == false)
                    {
                        dataGrid.ItemContainerGenerator.ItemsChanged -= itemsChangedHandler;
                        return;
                    }
                    WPFUtil.GetVisualChildCollection<DataGridRow>(dataGrid).
                        ForEach(d => d.Header = d.GetIndex() + offset);
                };
                dataGrid.ItemContainerGenerator.ItemsChanged += itemsChangedHandler;
            }
        }
    
        #endregion // DisplayRowNumber
    }
    
  • 7

    LoadingRowEvent由此触发:

    ICollectionView view = CollectionViewSource.GetDefaultView(_dataGrid.ItemsSource);
    view.Refresh();
    

相关问题