2

I have a custom class providing 3d-point information with readonly properties:

public class Point3d
{
    private readonly double x;
    public double X { get { return x; } }

    private readonly double y;
    public double Y { get { return y; } }

    private readonly double z;
    public double Z { get { return z; } }

    public PoseCartesian(double x, double y, double z)
    {
        this.x = x;
        this.y = y;
        this.z = z;
    }
}

To display multiple 3d-points I use a DataGrid with four columns in WPF. The first column is supposed to show the row-numbers of the entry.

<DataGrid Name="dgrPoints" AutoGenerateColumns="False" 
    ItemsSource="{Binding UpdateSourceTrigger=Default, Mode=OneWay}"
    SelectionChanged="dgr_Poses_SelectionChanged" CanUserSortColumns="False"
    IsReadOnly="True">
    <DataGrid.Columns>
          <DataGridTextColumn x:Name="colI" Binding="{Binding Mode=OneWay,
               RelativeSource={RelativeSource AncestorType=DataGridRow},
               Converter={local:RowToIndexConverter}}"/>
          <DataGridTextColumn x:Name="colX" Binding="{Binding X}" Header="X"/>
          <DataGridTextColumn x:Name="colY" Binding="{Binding Y}" Header="Y"/>
          <DataGridTextColumn x:Name="colZ" Binding="{Binding Z}" Header="Z"/>
     </DataGrid.Columns>
</DataGrid>

To bind a collection of points to the DataGrid I use a ObservableCollection:

private ObservableCollection<Point3d> pointList;

The binding is concluded by setting the DataContext:

this.pointList = new ObservableCollection<Point3d>();
dgrPoints.ItemsSource= this.poseList;

To retrieve the row numbers I use the following converter suggested by Andy in this answer:

public class RowToIndexConverter : MarkupExtension, IValueConverter
{
    static RowToIndexConverter converter;

    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        DataGridRow row = value as DataGridRow;
        if (row != null)
            return row.GetIndex() + 1;
        else
            return -1;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        if (converter == null) converter = new RowToIndexConverter();
        return converter;
    }

    public RowToIndexConverter()
    {
    }
}

This all works fine and sets the according row number in the first column of the DataGrid whenever a new row (Point3d) is added. But whenever a row is removed the row-numbers will not update, since the converter is triggered by adding an new DataGridRow. Right now I force un update by setting the DataGrid.DataContext to null and add it again:

this.pointList.RemoveAt(0);
dgrPoints.ItemsSource = null;
dgrPoints.ItemsSource = this.pointList;

This can not be a proper solution to achieve un update for the row numbers. What is the best way to trigger the converter for every row whenever the pointList changes?

I do not want to use the DataGridRow.Header property, since this displaces my columns. Correct me if I am wrong. Other than that, every suggestion considering different approaches are welcome.

millokeller
  • 252
  • 2
  • 12

1 Answers1

1

You could handle the LoadingRow and ItemContainerGenerator.ItemsChanged events and set the Tag property of the DataGridRow containers to the corresponding row number:

public Window1()
{
    InitializeComponent();
    dgrPoints.ItemContainerGenerator.ItemsChanged += ItemContainerGenerator_ItemsChanged;
    dgrPoints.LoadingRow += DgrPoints_LoadingRow;
    dgrPoints.ItemsSource = ...;
}

private void DgrPoints_LoadingRow(object sender, DataGridRowEventArgs e)
{
    e.Row.Tag = (e.Row.GetIndex() + 1).ToString();
}

private void ItemContainerGenerator_ItemsChanged(object sender, ItemsChangedEventArgs e)
{
    IEnumerable<DataGridRow> rows = FindVisualChildren<DataGridRow>(dgrPoints);
    foreach (DataGridRow row in rows)
        row.Tag = (row.GetIndex() + 1).ToString();
}

private static IEnumerable<T> FindVisualChildren<T>(DependencyObject dependencyObject) where T : DependencyObject
{
    if (dependencyObject != null)
    {
        for (int i = 0; i < VisualTreeHelper.GetChildrenCount(dependencyObject); i++)
        {
            DependencyObject child = VisualTreeHelper.GetChild(dependencyObject, i);
            if (child != null && child is T)
            {
                yield return (T)child;
            }

            foreach (T childOfChild in FindVisualChildren<T>(child))
            {
                yield return childOfChild;
            }
        }
    }
}

<DataGrid Name="dgrPoints" AutoGenerateColumns="False" 
          ItemsSource="{Binding UpdateSourceTrigger=Default, Mode=OneWay}"
          CanUserSortColumns="False"
          IsReadOnly="True">
    <DataGrid.Columns>
        <DataGridTextColumn x:Name="colI" Binding="{Binding Path=Tag, Mode=OneWay, 
            RelativeSource={RelativeSource AncestorType=DataGridRow}}"/>
        <DataGridTextColumn x:Name="colX" Binding="{Binding X}" Header="X"/>
        <DataGridTextColumn x:Name="colY" Binding="{Binding Y}" Header="Y"/>
        <DataGridTextColumn x:Name="colZ" Binding="{Binding Z}" Header="Z"/>
    </DataGrid.Columns>
</DataGrid>
mm8
  • 163,881
  • 10
  • 57
  • 88