5
votes

I have a question about data binding DataGrid in WPF. I am using the VS 2010 Beta 2 which has its own DataGrid, not the Toolkit one, although I think it is pretty much the same.

I want to bind to a dataset which has 52 columns, one for every week of the year. For this reason I want to bind the data dynamically rather than specifying each field. The value for each field is true or false depending on some condition. Based on this value I want to show an image in the cell template if the condition is true and hide it if the condition is not true.

My problem is that all the examples of using templates that I have found refer to the case of fixed, predefined fields, where you can have a binding like Text ="{Binding UserName}". This is no good to me because I don't know what the field names will be at design time.

I have made up a simplified example which illustrates the problem. In this example a data table is generated which contains true and false values. The Image in my template is never visible. How would I make it invisible depending on the true or false value in the data?

<Window.Resources>

    <!--This is the bit that doesn't work...-->
    <Style TargetType="{x:Type Image}" x:Key="HideWhenFalse">
        <Setter Property="Visibility" Value="Hidden" />
        <Style.Triggers>
            <DataTrigger
        Binding="{Binding Path=???}" 
        Value="True"> <!--What to put for the path? -->
                <Setter Property="Visibility">
                    <Setter.Value>
                        Visible
                    </Setter.Value>
                </Setter>
            </DataTrigger>
        </Style.Triggers>
    </Style>
    <!--Up to here--> 

    <Style x:Key="{x:Type DataGridCell}" TargetType="{x:Type DataGridCell}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate>
                    <StackPanel>
                        <Image Source="Images/tick.bmp" Style="{StaticResource HideWhenFalse}">

                        </Image>
                    </StackPanel>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</Window.Resources>

<Grid>
    <DataGrid 
        x:Name="myDataGrid"
        AutoGenerateColumns="True" >

    </DataGrid>
</Grid>

Code behind:

public partial class MainWindow : Window {

public MainWindow()
{
    InitializeComponent();

    DataTable dtTable = new DataTable();

    dtTable.Columns.Add("A", typeof(Boolean));
    dtTable.Columns.Add("B", typeof(Boolean));
    dtTable.Columns.Add("C", typeof(Boolean));
    dtTable.Columns.Add("D", typeof(Boolean));
    dtTable.Columns.Add("E", typeof(Boolean));
    dtTable.Columns.Add("F", typeof(Boolean));

    for (int i = 0; i < 5; i++)
    {
        object[] oValues = new Object[dtTable.Columns.Count];

        for (int j = 0; j < dtTable.Columns.Count; j++)
        {
            oValues[j] = (j % 2 == 1) ? true : false;
        }

        dtTable.Rows.Add(oValues);
    }

    myDataGrid.ItemsSource = dtTable.DefaultView;
    myDataGrid.Items.Refresh();
}

}

NB This is probably obvious and I am approaching the problem in completely the wrong way. Here is a confession: I have been trying to get my head around WPF for a couple of months now and I still seem to find myself approaching EVERY problem the wrong way. I hope the penny drops soon.

1

1 Answers

9
votes

You can use a MultiBinding, with a first binding taking the actual data context from the cell (that would be the row), and the second one taking the column. From there, you can retrieve the cell value.

Converter code:

public class RowColumnToCellConverter : IMultiValueConverter {
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) {
        DataRowView row = values[0] as DataRowView;
        DataGridColumn column = values[1] as DataGridColumn;
        return row != null && column != null
            ? row[column.SortMemberPath]
            : DependencyProperty.UnsetValue;
    }

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

XAML:

    <Style x:Key="{x:Type DataGridCell}" TargetType="{x:Type DataGridCell}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate>
                    <StackPanel>
                        <TextBlock x:Name="TextOK" Text="OK" Visibility="Collapsed" />
                    </StackPanel>
                    <ControlTemplate.Triggers>
                        <DataTrigger Value="True">
                            <DataTrigger.Binding>
                                <MultiBinding Converter="{StaticResource RowColumnToCellConverter}">
                                    <Binding />
                                    <Binding RelativeSource="{x:Static RelativeSource.Self}" Path="Column" />
                                </MultiBinding>
                            </DataTrigger.Binding>
                            <Setter TargetName="TextOK" Property="Visibility" Value="Visible" />
                        </DataTrigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

I used a TextBlock instead an image for testing, but the code will be the same. Just avoid defining a style for the image if that can be done directly in the DataGridCell's style.