Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WPF 4 DataGrid: Getting the Row Number into the RowHeader

Tags:

I am looking to get the row number into the RowHeader of the WPF 4 DataGrid so it has an Excel-like column for the row numbers of the DataGrid.

The solution I've seen out there on the web suggests adding an index field to the business objects. This isn't really an option because the DataGrid will be getting resorted a lot and we don't want to have to keep track of changing these index fields constantly.

Thanks a lot

like image 700
Greg Andora Avatar asked Jan 11 '11 23:01

Greg Andora


People also ask

How to get row index of DataGrid in WPF?

PreviewMouseLeftButtonDown event and search in handler the visual tree up to a DatagridRow , which has DatagridRow. GetIndex() method. So you will get always the right row index. Save this answer.

Can user add rows DataGrid WPF?

WPF DataGrid (SfDataGrid) provides built-in row called AddNewRow. It allows user to add a new row to underlying collection. You can enable or disable by setting SfDataGrid.

What is DataGrid WPF?

The WPF Data Grid (GridControl) is a data-aware control designed to display and edit data in different layouts: tabular, treelike, and card. The GridControl allows users to manage large amounts of data (sort, group, filter, and so on).


2 Answers

One way is to add them in the LoadingRow event for the DataGrid.

<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
To get this to work with the .NET 3.5 DataGrid in WPF Toolkit a little modification is needed. The index is still generated correctly but the output fails when using virtualization. The following modification to the RowHeaderTemplate fixes this

<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
If items are added or removed from the source list then the numbers get out of sync until the list is scrolled so LoadingRow is called again. Working around this issue is a little more complex and the best solution I can think of right now is to keep the LoadingRow solution above and also

  • Subscribe to dataGrid.ItemContainerGenerator.ItemsChanged
  • In the event handler, find all the child DataGridRows in the visual tree
  • Set the Header to the index for each DataGridRow

Here is an attached behavior which does this. Use it like this

<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 } 
like image 87
Fredrik Hedblad Avatar answered Dec 24 '22 19:12

Fredrik Hedblad


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

A (seemingly) clean templating solution:
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> 

Converter:

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 } 
like image 38
H.B. Avatar answered Dec 24 '22 21:12

H.B.