0

I want to iterate through the cells in my DataGrid so I can highlight some of the cells using this code:

//Loop through all rows.
for (int x = 0; x < yourGridName.Rows.Count; x++) {
  //Loop through all cells in that row and change its color.
  for (int y = 0; y < yourGridName.Rows[x].Cells.Count; y++) {
    yourGridName.Rows[x].Cells[y].Style.BackColor = System.Drawing.Color.Red;
  }
}

I have defined my DataGrid in WPF as follows:

<DataGrid AutoGenerateColumns="False"
  EnableRowVirtualization="True" ItemsSource="{Binding}"
  Margin="12,236,12,0" Name="conflictedDevicesDataGrid"
  RowDetailsVisibilityMode="VisibleWhenSelected" Grid.ColumnSpan="2"
  AlternatingRowBackground="#2FFF0000" IsManipulationEnabled="False">
  <DataGrid.Columns>
        ....
  </DataGrid.Columns>
</DataGrid>

Then I populated it with this code:

var conflictedDevicesDataTable = new DataTable();
conflictedDevicesDataTable.Rows.Add(new object[] {
  "CSV",roc.scadaNode, roc.deviceName, roc.deviceDescription, roc.rocChannel,
  roc.rocAddress, roc.rocGroup, roc.configuration, roc.revision
});
...
conflictedDevicesDataGrid.ItemsSource = conflictedDevicesDataTable.DefaultView;

However when I try and iterate through the columns and rows via:

conflictedDevicesDataGrid.Rows[x]

Rows is not an item, I can iterate through columns but not rows. Every example I find on Google says to iterate through a datagrid via .Rows[x] but I cannot seem to do this. How else can I iterate through each cell in my DataGrid and programmatically change the background colour?

dda
  • 6,030
  • 2
  • 25
  • 34
rollsch
  • 2,518
  • 4
  • 39
  • 65

1 Answers1

1

ok, so what you are trying to achieve is actually pretty difficult in WPF, for whatever reason.

the thing is that in WPF, as you discovered, you can't iterate on the cells of a given Row. So you have 2 options:

  • iterate on the cells in the VisualTree. I strongly discourage this option because first, it will mess things up pretty badly if you're using virtualization, and second, it's very ugly.

  • set a special property on your viewModel, and bind the dataGridCell's background property to this one through the DataGrid.CellStyle. The problem here is that you can't bind directly, since a DataGridCell's datacontext is the ViewModel corresponding to the row to which the cell belongs (go figure...), and not the item's property represented by the cell. So it gets quite complicated, because you have to do kind of a "transitive" biding.

I suggest you have a look here:

Binding a cell object's property to a DataGridCell in WPF DataGrid

and here:

How to style a WPF DataGridCell dynamically

this second link explains how I am doing the exact same thing you want to achieve, along with the drawback of such a method: slowness... (read the question, not the answer)

doing like this, you then can iterate on the items and set the property value in your ViewModel.

edit: here's some code for you (based on your question)

    <local:CellViewModelToTagConverter x:Key="CellViewModelToTagConverter" />

    <DataGrid AutoGenerateColumns="False"
              EnableRowVirtualization="True"
              ItemsSource="{Binding}"
              Margin="12,236,12,0"
              Name="conflictedDevicesDataGrid"
              RowDetailsVisibilityMode="VisibleWhenSelected"
              Grid.ColumnSpan="2"
              AlternatingRowBackground="#2FFF0000"
              IsManipulationEnabled="False">
        <DataGrid.Columns>
            <!--binding to the Text property of the CellViewModel for the column n°0-->
            <DataGridTextColumn Binding="{Binding [0].Text}">
                <DataGridTextColumn.CellStyle>
                    <Style TargetType="DataGridCell">
                        <!--this part is the most important, this is where you transfer the right dataContext to the cell-->
                        <Setter Property="Tag">
                            <Setter.Value>
                                <MultiBinding Converter="{StaticResource CellViewModelToTagConverter}" Mode="OneWay" UpdateSourceTrigger="PropertyChanged">
                                    <Binding />
                                    <Binding RelativeSource="{x:Static RelativeSource.Self}"/>
                                </MultiBinding>
                            </Setter.Value>
                        </Setter>
                        <!--and here, you bind the Background property-->
                        <Setter Property="Background" Value="{Binding Tag.Background, RelativeSource={RelativeSource Self}, Mode=OneWay}" />
                    </Style>
                </DataGridTextColumn.CellStyle>
            </DataGridTextColumn>
        </DataGrid.Columns>
    </DataGrid>

here is the converter's Code behind:

public class CellViewModelToTagConverter : MarkupExtension, IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        var row = values[0] as MyRowViewModel;
        var cell = values[1] as DataGridCell;

        if (row != null && cell != null)
        {
            var column = cell.Column as DataGridColumn;

            if (column != null)
                cell.SetBinding(FrameworkElement.TagProperty, new Binding {
                    Source = row[column.DataGridOwner.Columns.IndexOf(column)],
                    BindsDirectlyToSource = true
                });
        }

        return DependencyProperty.UnsetValue;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotSupportedException();
    }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        return new CellViewModelToTagConverter();
    }
}

of course, this means you have a MyRowViewModel somewhere:

internal class MyRowViewModel : Collection<MyCellViewModel>, INotifyPropertyChanged
{
}

and a MyCellViewModel with a Background dependencyProperty:

internal class MyCellViewModel : DependencyObject
{
private static readonly DependencyProperty BackgroundProperty = DependencyProperty.Register("Background", typeof(Brush), typeof(MyCellViewModel));
internal Brush Background
{
    get { return (Brush)(GetValue(BackgroundProperty)); }
    set { SetValue(BackgroundProperty, value); }
}
}

and this should do the trick (I hope I haven't forgotten anything, if I did you can always add a comment)

NB: this is a slightly modified version of my code of course, since my app is much more complex, and I did not test it like this so you might have to do some adjustments. Also, I have to have a CellViewModel for each type of cell in my case, since I also set the Foreground, Font, FontSyle, fontWeight etc... properties dynamically (hence my performance problems, by the way) but you might be ok with a simpler structure. You just have to adapt the idea to your case.

Community
  • 1
  • 1
David
  • 6,014
  • 4
  • 39
  • 55
  • That looks fantastically useful, do you have a full source available of your example? – rollsch Jul 18 '12 at 10:45
  • I do, I'll post it a little later today – David Jul 18 '12 at 11:27
  • I've done lots of reading but I'm not quite sure how to implement the INotifyPropertyChanged of MyRowViewModel. What exactly should it be doing and how should I implement it? My row consists of 8 DataGridTextColumn's and 1 DataGridCheckBoxColumn. I also am missing XwpfFont and XwpfFonBrusht and cannot find a reference to them anywhere, should these just simply be a Brush for the color changing method? – rollsch Jul 18 '12 at 15:27
  • I edited the typo with the XwpfThings, sorry? As for the INotifyPropertyChanged, I cannot really help as I tend not to use this method but use DependencyObjects instead – David Jul 18 '12 at 16:44