2011-01-11 11 views
19

Sto cercando di ottenere il numero di riga nel RowHeader del WGrF 4 DataGrid in modo che abbia una colonna simile a Excel per i numeri di riga di DataGrid.WPF 4 DataGrid: ottenere il numero di riga in RowHeader

La soluzione che ho visto sul Web suggerisce di aggiungere un campo indice agli oggetti business. Questa non è un'opzione in quanto il DataGrid verrà sfruttato molto e non vogliamo tenere traccia di modificare costantemente questi campi indice.

Grazie mille

risposta

36

Un modo è quello di aggiungere loro in caso LoadingRow per il 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(); 
} 

Aggiornamento
Per arrivare a questo lavoro con .NET 3.5 DataGrid in WPF Toolkit è necessaria una piccola modifica. L'indice è ancora generato correttamente ma l'output non riesce quando si utilizza la virtualizzazione. La seguente modifica ai RowHeaderTemplate correzioni questo

<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> 

Modifica 2012-07-05
Se gli articoli vengono aggiunti o rimossi dalla lista sorgente di allora i numeri diventano fuori sincrono fino a quando la lista viene fatto scorrere in modo LoadingRow è chiamato di nuovo. Lavorando a questo problema è un po 'più complesso e la soluzione migliore che posso pensare in questo momento è quello di mantenere la soluzione LoadingRow sopra e anche

  • Sottoscrivi dataGrid.ItemContainerGenerator.ItemsChanged
  • Nel gestore di eventi, trovare tutte il bambino DataGridRows nella struttura ad albero visuale
  • impostare l'intestazione per l'indice per ogni DataGridRow

Ecco un comportamento allegato che fa questo. Usalo come questo

<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

L'indice inizia da 0, quindi dovresti probabilmente aggiungere 1. – kevindaub

+0

@ daub815: Good point –

+1

Googling, troverai questa risposta è offerta anche in altri forum ed è semplicemente sbagliata. Le condizioni in cui può funzionare sono così restrittive da rendere questa 'soluzione' uno scherzo. Ad esempio, se aggiungi 5 elementi al tuo elenco e non lo fai mai scorrere, non ridimensionalo mai, non aggiungere o rimuovere elementi, non fare mai nulla che possa influire sulla visualizzazione di tali elementi, quindi stai bene. Il fatto è che l'indice di riga è un indice in una raccolta interna di contenitori per dati di riga (elementi). Questo non ha nulla a che fare con un indice di un oggetto (dati) stesso all'interno della sua collezione. – Tony

7

Edit: A quanto pare lo scorrimento cambia l'indice in modo che il legame non funziona così ...

A (apparentemente) soluzione di template pulito:
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 
} 
+0

Sfortunatamente questa soluzione non funziona, riceverai i numeri di riga corretti per cominciare, ma quando inizi a scorrere, l'ordinamento ecc. Iniziano a fallire –

+0

Sfortunatamente, Meleak è corretto . I numeri di riga in RowHeader vengono richiamati con le righe. +1 però per il convertitore. Stavo avendo difficoltà a capire come afferrare l'indice delle righe. –

+0

Oh, non sapevo che lo scorrimento modificasse l'indice, non ho mai usato DataGrid in un progetto prima; e lì, però, non avresti bisogno di un codice disordinato, troppo brutto ... –

2

Tutto questo approcci non funziona se si aggiungono o rimuovono righe. Dovresti aggiornare gli indici di riga in questi casi. Guarda questo comportamento:

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 
} 
1

@Fredrik La risposta di Hedblad funziona per me. Grazie!

Ho aggiunto un'altra proprietà per ottenere un valore di "offset" in modo che DataGrid possa essere avviato da 0 o da 1 (o da qualsiasi altra impostazione).

Per utilizzare il comportamento, (nota 'b' è il namespace)

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

Le classi modificati:

/// <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 
} 
+0

Che cos'è il WPFUtil? –

+0

WPFUtil sembra essere la regione di Hedblad "Get Visuals" trasformata in un'altra classe. – Lakedaimon

0

LoadingRowEvent viene attivato da questo:

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