Proporcionar números de fila para controles ItemsControl en MVVM con enlace a colecciones

Existen varios enfouqes para mostrar el número de fila en controles como DataGrid bajo el patrón MVVM. El más común es manejar el evento LoadingRow en el code-behind:

private void DataGridSoftware_LoadingRow(object sender, DataGridRowEventArgs e)
{
    e.Row.Header = e.Row.GetIndex() + 1;
}

Sin embargo, esto rompe con MVVM y requiere lógica adicional si la lista se actualiza dinámicamente.

Otra opción es agregar una propiedad Indice en el ViewModel:

public class DatosViewModel : ObservableObject
{
    private string _Nombre;
    public string Nombre
    {
        get { return _Nombre; }
        set { _Nombre = value; OnPropertyChanged("Nombre"); }
    }

    private int _Indice;
    public int Indice
    {
        get { return _Indice; }
        set { _Indice = value; OnPropertyChanged("Indice"); }
    }
}

Esto obliga a actualizar manualmente el índice en cadda operación de la colección, lo cual es tedioso y propenso a errores.

Tras probar varias alternativas, encontré una solución elegante usando CollectionViewSource y un IValueConverter. La clave está en que CollectionViewSource proporciona una vista con ordenamiento, filtrado y agrupación automáticos, y su método IndexOf devuelve la posición actual del elemento.

El conversor recibe el elemento y la vista, y devuelve el número de fila:

[ValueConversion(typeof(object), typeof(int))]
public class ConvertidorFilaAIndice : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        int indice = 0;
        var vistaOrigen = parameter as CollectionViewSource;
        if (vistaOrigen != null)
        {
            CollectionView vista = (CollectionView)vistaOrigen.View;
            indice = vista.IndexOf(value) + 1;
        }
        return indice > 0 ? indice : DependencyProperty.UnsetValue;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        => throw new NotImplementedException();
}

En XAML se declara el CollectionViewSource y se usa el conversor en el estilo del encabezado de fila del DataGrid:

<Window.Resources>
    <converter:ConvertidorFilaAIndice x:Key="convertidorIndice"/>
    <CollectionViewSource Source="{Binding DatosPrincipales}" x:Key="vistaDatos"/>
</Window.Resources>

<Grid>
    <DataGrid RowHeaderWidth="40" ItemsSource="{Binding Source={StaticResource vistaDatos}}">
        <DataGrid.RowHeaderStyle>
            <Style TargetType="{x:Type DataGridRowHeader}">
                <Setter Property="Content" 
                        Value="{Binding Converter={StaticResource convertidorIndice}, 
                                ConverterParameter={StaticResource vistaDatos}}"/>
            </Style>
        </DataGrid.RowHeaderStyle>
        <DataGrid.Columns>
            
        </DataGrid.Columns>
    </DataGrid>
</Grid>

El flujo es simple:

  1. La fuente de datos real se expone como CollectionViewSource, que administra internamente el orden y las posiciones.
  2. El conversor usa IndexOf de la vista para obtener la posición actual (empezando desde 1).
  3. Como CollectionViewSource se actualiza automáticamente al cambiar la colección subyacente, los números de fila siempre son correctos sin manipulación manual.

Este enfoque respeta MVVM, es simple y funciona con cualquier control que herede de ItemsControl (DataGrid, ListBox, ListView, etc.).

Etiquetas: WPF MVVM CollectionViewSource DataGrid IValueConverter

Publicado el 7-22 23:32