Style DataTriggers working at design time, but not at runtime - why? - wpf

(before anyone asks, the title is right: I have code working only at design time in Blend, while the most common by far would be the opposite :o )
While designing a DataTemplate in Expression Blend, I can see my DataTriggers working fine, my sample ViewModel generates a random value for the level of a battery, and both border width and background color display accordingly, either in the BatteryLevelTemplate itself, and another panel containing a lot of devices with their respective (random) battery level, with a design-time DataContext.
Here is a screenshot from Expression Blend:
And here a screenshot from the running application. Notice that, while both use exactely the same class as DataContext (but as design time in Blend), at runtime only the default RedBattery color setter is applied, even if the value itself (which also affects width) varies:
And here are the relevant code parts:
<Border.Width>
<MultiBinding Converter="{StaticResource NormalValueConverter}" FallbackValue="10">
<Binding Path="NívelBateria"/>
<Binding Path="ActualWidth" ElementName="BatteryChargeContainer"/>
</MultiBinding>
</Border.Width>
<Border.Style>
<Style TargetType="Border">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="{StaticResource BatteryRed}"/>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding NívelBateria, Converter={StaticResource ValorMaiorQue}, ConverterParameter=0.25}" Value="True">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="{StaticResource BatteryOrange}"/>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding NívelBateria, Converter={StaticResource ValorMaiorQue}, ConverterParameter=0.5}" Value="True">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="{StaticResource BatteryYellow}"/>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding NívelBateria, Converter={StaticResource ValorMaiorQue}, ConverterParameter=0.75}" Value="True">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="{StaticResource BatteryGreen}"/>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
============
<DockPanel x:Name="PainelSetupsSensores" Background="#FFB8E6E8"/>
<DockPanel x:Name="PainelSensoresDisponiveis" Background="#FFF0F0F0"
Grid.RowSpan="2" Grid.Column="1"
DataContext="{Binding ReceiverAtivo}"
d:DataContext="{d:DesignInstance Type=local:ReceiverSimulado, IsDesignTimeCreatable=True}">
<ScrollViewer>
<ItemsControl ItemsSource="{Binding Sensores}" Margin="10">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</ScrollViewer>
</DockPanel>
====================
class ValorMaiorQue : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
double valor = (double)value;
double limite = double.Parse((string)parameter);
return valor > limite;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
UPDATE (using the valuable Snoop tip by Contango):
I "snooped" the BatteryCharge (Border) element, and found out an interesting thing:
The Width property, which is affected by a Multi Value Element Binding, is working fine and displays in a "Local", green-shaded row;
On the other hand, the Background property, which is not working, displays unsurprisingly as Style with the default red value. This one is not being "DataTriggered".
My doubt now is how I am supposed to use Snoop (or anything else) to find out why the DataTrigger is not being applied.

I discovered the problem "accidentally", and here goes the explanation:
I installed Snoop, TriggerTracing and also WPF Inspector to check properties applied by my DataTriggers, and found out the comparison provided by the DataConverter was always False;
Then I put a breakpoing inside the DataConverter, to discover that, for example, the string "0.75" provided by ConverterParameter was being Double.Parsed as 75.0;
Then I realized that my current language is pt-BR, and the decimal separator is comma instead of dot. Then I changed the converter, adding an InvariantCulture parameter to Double.Parse. And now it works!
double limite = double.Parse((string)parameter, CultureInfo.InvariantCulture);

Your run time DataContext is not being set correctly, so your code can't bind to the correct properties at runtime.
Note that the run time DataContext is completely separate to the design time DataContext, and uses different XAML to setting the runtime DataContext.
I would recommend using Snoop to fix the problem, you can use it to flag up binding errors due to a bad runtime DataContext, see my answer here:
ReSharper WPF error: "Cannot resolve symbol "MyVariable" due to unknown DataContext"

Related

Binding a DataTrigger value to this instance of DataTemplate

I have a ListView, with its items represented by an ItemTemplate like so:
<ListView dependencyObjects:InterestingItem.Interesting="{Binding InterestingItem}"
ItemsSource="{Binding Quotations}" >
<ListView.ItemTemplate>
<DataTemplate>
<Border>
<Grid>
<StackPanel x:Name="NotImportant">
</StackPanel>
<Grid x:Name="HiddenGrid"
Background="Red"
Visibility="Hidden" >
<Grid.Style>
<Style TargetType="Grid">
<Style.Triggers>
<Grid.Triggers>
<DataTrigger Binding="{Binding Path=DataContext.InterestingItem,
RelativeSource={RelativeSource AncestorType={x:Type ListView }}}"
Value="*this instance here*!">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Grid.Triggers>
</Style.Triggers>
</Style>
</Grid.Style>
</Grid>
</Grid>
</Border>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The ListView has an attached property InterestingItem that is one of the items in the ListView.
What I can't hook up is when the InterestingItem is the same as one of the items, the second Grid should become visible.
I would prefer not to change and bind to the actual objects in the list - but rather have the ListView control which item is to be altered.
What is the Value in the DataTrigger that I need?
There are multiple issues in your XAML and conceptually that prevent it from working.
To bind attached properties, you have to use the correct syntax with parentheses.
Path="{Binding (local:InterestingItem.Interesting), RelativeSource={RelativeSource AncestorType={x:Type ListView}}}"
See the Binding path syntax documenation for reference.
The Triggers property does only support EventTriggers, see FrameworkElement.Triggers.
Note that the collection of triggers established on an element only supports EventTrigger, not property triggers (Trigger). If you require property triggers, you must place these within a style or template and then assign that style or template to the element either directly through the Style property, or indirectly through an implicit style reference.
You cannot bind the Value property of DataTrigger, since it is not a dependency property.
You could of course change the bound type to expose a property that indicates if it is a special object or not and bind that in XAML using a DataTrigger, similar to this (where IsSpecial is the new bool property).
<Grid x:Name="HiddenGrid"
Background="Red">
<TextBlock Text="Hidden Grid"/>
<Grid.Style>
<Style TargetType="{x:Type Grid}">
<Setter Property="Visibility" Value="Hidden"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsSpecial}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
</Grid>
If you want to stick to your current approach, you could create a custom IMultiValueConverter that enables binding multiple properties. It would check if all of the bound values are equal and return Visibility.Visible or Visibility.Hidden otherwise. This example uses Linq to check this and supports an arbitrary number of values bound, but there are many other options.
public class EqualityToVisibilityConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values is null || values.Length < 2)
return Binding.DoNothing;
return values.Distinct().Count() == 1 ? Visibility.Visible : Visibility.Hidden;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new InvalidOperationException();
}
}
Next, instantiate the converter in the resources of the ListView or any other resource dictionary in scope and bind the Visibility property of the Grid to both the current item (just <Binding/>) and the attached property local:InterestingItem.Interesting with a MultiBinding that uses the converter to convert them to a Visibility.
<ListView local:InterestingItem.Interesting="{Binding InterestingItem}"
ItemsSource="{Binding Quotations}">
<ListView.Resources>
<local:EqualityToVisibilityConverter x:Key="EqualityToVisibilityConverter"/>
</ListView.Resources>
<ListView.ItemTemplate>
<DataTemplate>
<Border>
<Grid>
<StackPanel x:Name="NotImportant">
<TextBlock Text="Not Important"/>
</StackPanel>
<Grid x:Name="HiddenGrid"
Background="Red">
<Grid.Visibility>
<MultiBinding Converter="{StaticResource EqualityToVisibilityConverter}">
<Binding/>
<Binding Path="(local:InterestingItem.Interesting)"
RelativeSource="{RelativeSource AncestorType={x:Type ListView}}"/>
</MultiBinding>
</Grid.Visibility>
<TextBlock Text="Hidden Grid"/>
</Grid>
</Grid>
</Border>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
There are two other things to note here. I have added two dummy TextBlocks, otherwise the result will not be visible, as the panels are empty. Replace them with your content. Furthermore, both the StackPanel and the Grid are overlapping in the parent Grid, I do not know if this is intentional or not, but you can change it by adding rows or columns and moving the elements there.
What is the Value in the DataTrigger that I need?
I am afraid XAML has no support for something like the this keyword in C#.
You may use a MultiBinding with an IMultiValueConverter implementation that determines whether the items are equal:
<Grid x:Name="HiddenGrid" Background="Red">
<Grid.Style>
<Style TargetType="Grid">
<Setter Property="Visibility" Value="Hidden" />
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding>
<MultiBinding.Converter>
<local:MultiConverter />
</MultiBinding.Converter>
<Binding Path="{Binding Path=DataContext.InterestingItem,
RelativeSource={RelativeSource AncestorType={x:Type ListView }}}" />
<Binding Path="{Binding}" />
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
</Grid>
Converter:
public class MultiConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) =>
values != null && values.Length == 2 && values[0] == values[1];
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) =>
throw new NotSupportedException();
}
Note that you cannot set the Visibility property of the Grid to a local value if you want to be able to override the value using a Style setter.
<Grid x:Name="HiddenGrid" Background="Red" Visibility="Hidden">

Moving a complicated XAML binding to a Style Setter

I'm using a checkbox with a custom style and control template. I would like to move the height and width bindings over to the style as well, but I can't seem to get it right.
Here's the checkbox XAML
<CheckBox Style="{StaticResource MyCheckStyle}"
HorizontalAlignment="Center"
Height="{Binding FontSize,
Source={x:Static gap:Settings.Default},
Converter={StaticResource MyMathConverter},
ConverterParameter='x+10'}"
Width="{Binding ActualHeight, RelativeSource={RelativeSource Self}}"
IsChecked="{Binding ValueT, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"
/>
I'm trying to achieve a check box whose height in pixels is 10 larger than the current font size in points (a hack but please ignore that for now).
I'm binding Width to ActualHeight so that they'll always be square (my custom template would allow any size otherwise)
I'm using a converter (Ivan Krivyakov's MathConverter) to do the math.
The size I'm binding to is stored in a static instance of a Settings class generated from a Settings file.
MyCheckStyle is currently as follows:
<Style x:Key="MyCheckStyle" TargetType="{x:Type CheckBox}">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="Template" Value="{StaticResource MyCheckBoxTemplate}" />
</Style>
So far, this all works perfectly. But my attempt at moving the Width Binding to the style crashes the application:
<!-- WIDTH BINDING. CAUSES A CRASH DUE TO ACCESS VIOLATION -->
<Setter Property="Width">
<Setter.Value>
<Binding>
<Binding.Source>
<RelativeSource Mode="Self"/>
</Binding.Source>
<Binding.Path>
<PropertyPath Path="ActualHeight"/>
</Binding.Path>
</Binding>
</Setter.Value>
</Setter>
My attempts at the moving the Height binding are a complete failure. I can't figure out how to replicate the "x:Static" markup extension in element syntax, among other things. Intellisense/ReSharper isn't helping me much.
<Setter Property="Height">
<Setter.Value>
<Binding>
<!-- NO IDEA WHAT TO PUT HERE? -->
</Binding>
</Setter.Value>
</Setter>
</Style>
Anyone able to set me right? Is what I'm doing possible?
Have you ever done
Prop="{Binding Source={RelativeSouce ...}}"?
Nope. It's not Source, it's RelativeSource. The curly brace markup-extension syntax sets the same properties as the XML element syntax, but with curly braces. That's all. If it's RelativeSource with curly braces, it's RelativeSource with angle brackets.
<Setter Property="Width">
<Setter.Value>
<Binding>
<!-- RelativeSource, not Source -->
<Binding.RelativeSource>
<RelativeSource Mode="Self"/>
</Binding.RelativeSource>
<Binding.Path>
<PropertyPath Path="ActualHeight"/>
</Binding.Path>
</Binding>
</Setter.Value>
</Setter>
For x:Static, that's another markup extension (a subclass of System.Windows.Markup.MarkupExtension). If markup extension class names have the Extension postfix, the XAML parser lets you omit the "Extension" part. So as it happens that class is actually named StaticExtension. If you want static member Foo.Bar in the local namespace, easy:
<x:StaticExtension Member="local:Foo.Bar" />
The Binding class is a subclass of MarkupExtension (via BindingBase), which is why you can use the curly-brace syntax with it. However, it doesn't have the "Extension" name postfix. That's optional. There's no particular consistency there.
Key point: Every one of these things is a class. XAML is a notation for instantiating instances of classes and initializing their properties with either scalar values, or with other class instances. Curly-brace markup extension syntax is a funny little addition -- but a useful one, as you can see comparing the XAML above with what I'm about to suggest as a replacement.
Now, that's all well worth knowing, but you don't need any of it here.
tl;dr
<Setter
Property="Width"
Value="{Binding ActualHeight, RelativeSource={RelativeSource Self}}"
/>
<Setter
Property="Height"
Value="{Binding FontSize,
Source={x:Static gap:Settings.Default},
Converter={StaticResource MyMathConverter},
ConverterParameter='x+10'}"
/>
Done.

Apply ValueConverter in style when source comes from x:Static resx file

After quite some search and reading other questions & posts, I was not able to find how to solve this. Note: I'm relatively new to WPF (not to binding in general).
Here's what I'm after:
I'd like to have all TextBlock controls in a window to be styled in some way.
The style should also apply a ValueConverter to make text all uppercase.
Finally, each TextBlock Text could come either from binding to a view-model property, or from binding to a resource string from a .resx file
Here's an excerpt of what I'm playing with:
<!-- in Window resources -->
<help:AllCapsStringConverter x:Key="AllCaps"/>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="Brown" />
<Setter Property="Text">
<Setter.Value>
<Binding>
<Binding.Converter>
<help:AllCapsStringConverter />
</Binding.Converter>
<!-- also tried adding:
<Binding.RelativeSource>
<RelativeSource Mode="Self" />
</Binding.RelativeSource>
-->
</Binding>
<!-- also tried with:
<Binding Converter="{StaticResource AllCaps}"/>
<Binding Path="Text" Converter="{StaticResource AllCaps}"/>
-->
</Setter.Value>
</Setter>
</Style>
<!-- in Window content -->
<TextBlock Text="{x:Static resx:MyResources.MyTitle}" />
Here's the value converter, which on its own has proved to be working:
class AllCapsStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null && value is string)
{
return ((string)value).ToUpper();
}
else
{
return value;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
TextBlocks get the foreground color, but no conversion kicks in.
I was able to apply the converter locally to the single TextBlock, but I don't want to apply that to all TextBlocks around the window:
<TextBlock>
<TextBlock.Text>
<Binding Source="{x:Static resx:MyResources.MyTitle}"
Converter="{StaticResource AllCaps}"/>
</TextBlock.Text>
</TextBlock>
<!-- which is the same as -->
<TextBlock Style="{StaticResource CustomerInfoTitleStyle}"
Text="{Binding Source={x:Static resx:MyResources.MyTitle}, Converter={StaticResource AllCaps}}" />
Your converter is not working because your TextBlock is overriding the Text property of the Style, which includes the converter you have added to the binding.
For example:
<Grid.Resources>
<Style x:Key="MyTextBlockStyle" TargetType="TextBlock">
<Setter Property="Foreground" Value="Red"/>
<Setter Property="FontSize" Value="16"/>
<Setter Property="Text" Value="You won't see this."></Setter>
</Style>
</Grid.Resources>
<TextBlock Text="You will see this." Style="{StaticResource MyTextBlockStyle}"/>
Hopefully from the above you can see why your approach is not working.
A better solution would just be to set the value of Text with the value converter on the TextBlock, rather than in the Style.
If you don't want to do that, one common cheat you could use to get around this could be to bind the TextBlock's Text property to the Tag property, like so:
<Grid.Resources>
<local:AllCapsConverter x:Key="AllCaps"/>
<Style x:Key="MyTextBlockStyle" TargetType="TextBlock">
<Setter Property="Foreground" Value="Red"/>
<Setter Property="FontSize" Value="16"/>
<Setter Property="Text" Value="{Binding Tag, RelativeSource={RelativeSource Self}, Converter={StaticResource AllCaps}}"/>
</Style>
</Grid.Resources>
<TextBlock Tag="You will see this." Style="{StaticResource MyTextBlockStyle}"/>
I have a strong dislike of this approach, but it does get you what you want. I would prefer you just use the converter when you set the binding on the TextBlock.
lukegv's approach is another alternative. However, there is no need to use a Label, as you are overriding the template and (similar to my example above) you are just binding to the Content property of the Label. You could just as easily get what you need from a ContentControl.
<Grid.Resources>
<local:AllCapsConverter x:Key="AllCaps"/>
<Style x:Key="MyContentControl" TargetType="ContentControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<TextBlock Text="{Binding Content, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource AllCaps}}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Grid.Resources>
<ContentControl Style="{StaticResource MyContentControl}" Content="You will see this too!"/>
I'm not a particularly huge fan of this idea either though, as you lose access to all the other TextBlock properties. For example, if I wanted to set the FontWeight property of my TextBlock then I would be stuffed.
Try to use a 'Label' and build a template, because a 'TextBlock' is not a control.
<Style x:Key="AllCapsLabel" TargetType="{x:Type Label}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Label">
<TextBlock Foreground="Brown" Text="{Binding Content, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource AllCaps}}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And use it this way:
<Label Style="{StaticResource AllCapsLabel}" Content="whatever you want" />
If the content is plain text (aka a 'String'), the text should always be uppercase.

WPF: Use a different template depending on size and number of items

I'm trying to work out how I can alter the template depending on the size and number of items. This is very similar to the ribbon which dynamically changes depending on the size, or Windows 7 thumbnail of programs.
In this case, it's an ItemTemplate of a ListBox and I want to reduce the size of the image or not display it, rather than having scroll bars.
<ListBox ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Title}" />
<Image Source="{Binding ImageUrl}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Thanks
You could set a style on the ListBox, which switches ItemTemplate based on the number of items.
<ListBox ItemsSource="{Binding Items}">
<ListBox.Resources>
<local:SizeConverter x:Key="SizeConverter"/>
<DataTemplate x:Key="SmallTemplate"></DataTemplate>
<DataTemplate x:Key="MediumTemplate"></DataTemplate>
<DataTemplate x:Key="LargeTemplate"></DataTemplate>
</ListBox.Resources>
<ListBox.Style>
<Style TargetType="ListBox">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Items.Count, Converter={StaticResource SizeConverter}}" Value="Small">
<Setter Property="ItemTemplate" Value="{StaticResource SmallTemplate}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=Items.Count, Converter={StaticResource SizeConverter}}" Value="Medium">
<Setter Property="ItemTemplate" Value="{StaticResource MediumTemplate}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=Items.Count, Converter={StaticResource SizeConverter}}" Value="Large">
<Setter Property="ItemTemplate" Value="{StaticResource LargeTemplate}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.Style>
</ListBox>
The SizeConverter would be an IValueConverter which returns a size category based on the incoming count, the convert method could be something like this:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
int count = (int)value;
if (count < 4) return "Large";
if (count < 12) return "Medium";
return "Small";
}
You probably want to use the DataTemplateSelector functionality in WPF:
From the link:
Typically, you create a
DataTemplateSelector when you have
more than one DataTemplate for the
same type of objects and you want to
supply your own logic to choose a
DataTemplate to apply based on the
properties of each data object. Note
that if you have objects of different
types you can set the DataType
property on the DataTemplate. If you
do that then there is no need to
create a DataTemplateSelector.
Furthermore, if you have objects of
the same type but with different
properties, you can also consider
using a DataTrigger or a data
converter. For more information, see
Data Templating Overview.
Or alternatively, as mentioned above, a DataTrigger may be of use.

Dynamic Binding in WPF DataGridCell Template

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

Resources