I have a datagrid bound to a list of a custom "model" class. In one of the cell I have 4 rectangles, representing 4 different string properties of my model class.
The rectangles are supposed to be black if the corresponding property is empty, red otherwise. I achieve this using a DataTrigger on each rectangle and it works fine but i have to write four time the same datatrigger with only the bound path changing. It seems like it could be more efficient.
Is there a clever way to define a DataTrigger once, and use a different bound path for each element (in my case rectangles) it is applied to ?
Thanks.
This is my model class:
class myModel
{
[...]
public string pr1 { get; set; }
public string pr2 { get; set; }
public string pr3 { get; set; }
public string pr4 { get; set; }
[...]
}
This is the datagrid bound to a List<myModel>
:
<DataGrid AutoGenerateColumns="False" Height="200" Name="myDg" ItemsSource="{Binding}">
<DataGrid.Columns>
[...]
<DataGridTemplateColumn Header="prs">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Name="spRow" Orientation="Horizontal">
<Rectangle Height="15" Name="rPr1" Width="10">
<Rectangle.Style>
<Style TargetType="{x:Type Rectangle}">
<Setter Property="Rectangle.Stroke" Value="Red"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=pr1}" Value="">
开发者_JAVA技巧 <Setter Property="Rectangle.Stroke" Value="Black"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Rectangle.Style>
</Rectangle>
<Rectangle Height="15" Name="rPr2" Width="10">
<Rectangle.Style>
<Style TargetType="{x:Type Rectangle}">
<Setter Property="Rectangle.Stroke" Value="Red"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=pr2}" Value="">
<Setter Property="Rectangle.Stroke" Value="Black"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Rectangle.Style>
</Rectangle>
<Rectangle Height="15" Name="rPr3" Width="10">
<Rectangle.Style>
<Style TargetType="{x:Type Rectangle}">
<Setter Property="Rectangle.Stroke" Value="Red"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=pr3}" Value="">
<Setter Property="Rectangle.Stroke" Value="Black"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Rectangle.Style>
</Rectangle>
<Rectangle Height="15" Name="rPr4" Width="10">
<Rectangle.Style>
<Style TargetType="{x:Type Rectangle}">
<Setter Property="Rectangle.Stroke" Value="Red"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=pr4}" Value="">
<Setter Property="Rectangle.Stroke" Value="Black"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Rectangle.Style>
</Rectangle>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
There are two ways...
Solution 1:
You can generalize the style at the ancestor level and then specialize the model properties in each rectangle.
The way you can do this is by biding specific pr
property to Tag
of each Rectangle and then use Tag
as a generic data trigger source.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal">
<TextBox Margin="5" x:Name="MyTextBox1" Text="pr1" />
<TextBox Margin="5" x:Name="MyTextBox2" Text="pr2" />
<TextBox Margin="5" x:Name="MyTextBox3" Text="pr3" />
<TextBox Margin="5" x:Name="MyTextBox4" Text="pr4" />
</StackPanel>
<StackPanel Grid.Row="1" Orientation="Horizontal">
<StackPanel.Resources>
<Style TargetType="{x:Type Rectangle}">
<Setter Property="Height" Value="20"/>
<Setter Property="Width" Value="20"/>
<Setter Property="Stroke" Value="Black"/>
<Setter Property="StrokeThickness" Value="1"/>
<Setter Property="Margin" Value="5"/>
<Style.Triggers>
<DataTrigger
Binding="{Binding Tag,
RelativeSource={RelativeSource Self}}"
Value="">
<Setter Property="Fill" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Resources>
<Rectangle Tag="{Binding Text, ElementName=MyTextBox1, Mode=OneWay}" />
<Rectangle Tag="{Binding Text, ElementName=MyTextBox2, Mode=OneWay}" />
<Rectangle Tag="{Binding Text, ElementName=MyTextBox3, Mode=OneWay}" />
<Rectangle Tag="{Binding Text, ElementName=MyTextBox4, Mode=OneWay}" />
</StackPanel>
</Grid>
So in above example when you clear the individual textbox you get a corresponding red rectangle. Notice that we have used Tag through a DataTrigger
but we can also use a normal Trigger...
<Trigger Property="Tag" Value="">
<Setter Property="Fill" Value="Red"/>
</Trigger>
Solution 2:
Use ItemsControl
and then 4 (or n
) items in your model to hold pr1..pr4 values.
EDIT
....
Change you model to include a list of pr
objects. I am using SourceFilter
class just to hold the two way editable string value that was held by pr'n'
properties... You can use any class that holds a string value through a property.
public List<SourceFilter> pr
{
get;
set;
}
Then I load the four properties as four SourceFilter
objects...
pr = new List<SourceFilter>();
pr.Add(new SourceFilter("pr1"));
pr.Add(new SourceFilter("pr2"));
pr.Add(new SourceFilter("pr3"));
pr.Add(new SourceFilter("pr4"));
And then I use ItemsControl's ItemPanel
, ItemsTemplate
, ItemsSource
to achieve exactly same effect from Step 1...
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ItemsControl Grid.Row="0" ItemsSource="{Binding pr, ElementName=MyWindow2}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Margin="5"
Text="{Binding Path=Source,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ItemsControl Grid.Row="1" ItemsSource="{Binding pr, ElementName=MyWindow2}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<DataTemplate.Resources>
<Style TargetType="{x:Type Rectangle}">
<Setter Property="Height" Value="20"/>
<Setter Property="Width" Value="20"/>
<Setter Property="Stroke" Value="Black"/>
<Setter Property="StrokeThickness" Value="1"/>
<Setter Property="Margin" Value="5"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Source}" Value="">
<Setter Property="Fill" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataTemplate.Resources>
<Rectangle />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
Advantage is here you do not have to specify specific pr1..pr4 bindings anywhere. Plus its extensible (as pr
can hold any n
values and will generate same number of rectangles automatically).
Yes, you can create a class that derives from DataTrigger
and has the unchanging details pre-set:
public class RedBlackDataTrigger : DataTrigger
{
public RedBlackDataTrigger()
{
Value = string.Empty;
Setters.Add(new Setter(Rectangle.StrokeProperty, new SolidColorBrush(Colors.Black)));
}
}
You can use it in XAML and just add the binding:
<Rectangle Height="15" Width="10">
<Rectangle.Style>
<Style TargetType ="Rectangle">
<Setter Property="Rectangle.Stroke" Value="Red"/>
<Style.Triggers>
<Your-Namespace:RedBlackDataTrigger Binding="{Binding Path=pr4}" />
</Style.Triggers>
</Style>
</Rectangle.Style>
</Rectangle>
Since you are also duplicating your Style
, you can take it a step further and create a derived style class:
public class RectangleStyle : Style
{
public RectangleStyle()
{
TargetType = typeof (Rectangle);
Setters.Add(new Setter(Rectangle.StrokeProperty, new SolidColorBrush(Colors.Red)));
}
public string ColorTrigger
{
set
{
Triggers.Add(new RedBlackDataTrigger {Binding = new Binding(value) });
}
}
}
Now you've reduced things to:
<Rectangle Height="15" Width="10">
<Rectangle.Style>
<Your-Namespace:RectangleStyle ColorTrigger="pr4" />
</Rectangle.Style>
</Rectangle>
You can even take it one step further and create a derived Rectangle
class to do it all.
精彩评论