I've created a "DarkMode" theme for my application but the IsSelected binding for the ListViewItem is not working. When I select an item/items they highlight as expected (I.e. the MultiTriggers are applying the correct highlight colour), so the ListView obviously thinks IsSelected == True. But the Setter is never called so the property in my model is never updated.
If I remove the x:Key="RemoteComputerItem" style it works fine (I.e. the Setter is called and my model is updated). Removing all of the triggers in that style make no difference (I obviously don't see the items being highlighted, but the Setter is still not called). So that only leaves the following:
<ControlTemplate TargetType="{x:Type ListViewItem}">
<Border x:Name="Bd">
<GridViewRowPresenter HorizontalAlignment="{TemplateBinding HorizontalAlignment}" VerticalAlignment="{TemplateBinding VerticalAlignment}" />
</Border>
</ControlTemplate>
I believe the problem lies within the control template above, but I can't figure out how to fix it. Am I missing something? or do I need to use something else in place of the GridViewRowPresenter?
My XAML is below. I haven't included the C# as I think the problem lies here and don't want to make the question too long, but I'm happy to add it if needed.
View:
<ListView x:Name="ADComputers" Margin="0" Padding="0" Grid.Row="1" Grid.Column="2"
ItemsSource="{Binding SelectedItem.Computers, ElementName=ADTree, Mode=TwoWay}"
SelectionMode="Multiple" ItemContainerStyle="{DynamicResource RemoteComputerItem}">
<ListView.Resources>
<Style TargetType="{x:Type ListViewItem}" BasedOn="{StaticResource RemoteComputerItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
<Style.Triggers>
<DataTrigger Binding="{Binding StatusText}" Value="Offline">
<Setter Property="Foreground" Value="Gray" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.Resources>
<ListView.View>
<GridView>
<GridViewColumn Header="" Width="20">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Image Source="" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn x:Name="Name" Width="120" DisplayMemberBinding="{Binding Name}">
<GridViewColumn.Header>
<GridViewColumnHeader Content="Computer Name" util:SortOrderGlyph.IsSorted="False" util:SortOrderGlyph.SortDirection="True">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<cal:ActionMessage MethodName="SortCol">
<cal:Parameter Value="Name" />
<cal:Parameter Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type GridViewColumnHeader}}}" />
</cal:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
</GridViewColumnHeader>
</GridViewColumn.Header>
</GridViewColumn>
<!-- More columns like above, removed to shorten -->
</GridView>
</ListView.View>
</ListView>
Separate Resource Dictionary for DarkMode theme:
<Style x:Key="RemoteComputerItem" TargetType="{x:Type ListViewItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<Border x:Name="Bd">
<GridViewRowPresenter HorizontalAlignment="{TemplateBinding HorizontalAlignment}" VerticalAlignment="{TemplateBinding VerticalAlignment}" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" TargetName="Bd">
<Setter.Value>
<SolidColorBrush Color="{DynamicResource ControlMouseOverColor}" />
</Setter.Value>
</Setter>
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="Selector.IsSelectionActive" Value="False" />
<Condition Property="IsSelected" Value="True" />
</MultiTrigger.Conditions>
<Setter Property="Background" TargetName="Bd">
<Setter.Value>
<SolidColorBrush Color="{DynamicResource SelectedBackgroundColor}" />
</Setter.Value>
</Setter>
</MultiTrigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="Selector.IsSelectionActive" Value="True" />
<Condition Property="IsSelected" Value="True" />
</MultiTrigger.Conditions>
<Setter Property="Background" TargetName="Bd">
<Setter.Value>
<SolidColorBrush Color="{DynamicResource SelectedBackgroundColor}" />
</Setter.Value>
</Setter>
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
EDIT:
Here is some more code to give you the full picture of the bindings, but as I mentioned above the bindings work perfectly when I remove the control template for the ListViewItem.
As you can see above, the ListView is bound to SelectedItem.Computers of the element ADTree. Here is the XAML for ADTree:
<TreeView Margin="0, -1, 0, 0" Grid.Row="1" Grid.Column="0" x:Name="ADTree" ItemsSource="{Binding ADRoot.Children}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}" BasedOn="{StaticResource {x:Type TreeViewItem}}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</TreeView.ItemContainerStyle>
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type models:OUModel}" ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<Image Width="16" Height="16" Margin="3,0" />
<TextBlock Text="{Binding Name}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
The TreeView is bound to ADRoot.Children:
class ADTreeViewModel : OUModel
{
public OUModel ADRoot { get; set; } = new OUModel(null, false);
}
class OUModel : PropertyChangedBase, ITreeViewItemViewModel
{
// TreeView is bound to this property, which holds a collection of more OUModel objects
public BindableCollection<ITreeViewItemViewModel> Children { get; set; } = new BindableCollection<ITreeViewItemViewModel>();
// The ListView's ItemSource is bound to ADTree.SelectedItem.Computers which is this property
public List<RemoteComputer> Computers
{
get { return computers; }
set { computers = value; NotifyOfPropertyChange(); }
}
// PropertyChangedBase is from Caliburn.Micro
class RemoteComputer : PropertyChangedBase
{
// All other properties in this class are being displayed correctly in the ListView, so the DataContext is correct E.g.:
public string Name { get; set; }
private bool isSelected = false;
// The ListViewItem's IsSelected property is bound to this
public bool IsSelected
{
get { return isSelected; }
set
{
isSelected = value;
NotifyOfPropertyChange(); // Caliburn.Micro gets the property name itself
// I've also tried adding the following
NotifyOfPropertyChange(() => Parent.Children);
NotifyOfPropertyChange(() => Parent);
}
}
}
Here is the Live Property Explorer for an item which IS selected:
Related
I have a DataGrid with a column that is a DataGridComboBoxColumn. For the items in those combo boxes I need to display an image and some text. Below is what I was able to do. But the initial binding isn't working. And once I select an item in the combobox the images all disappear in all the comboboxes.
Is what I am doing on the right path to getting this to work with some sort of trigger or binding I am missing? Or is there a better way to display both an image and text in a combobox in a datagrid?
<DataGridComboBoxColumn Header="Status" SelectedValueBinding="{Binding Status}" SelectedItemBinding="{Binding Status}" SelectedValuePath="Tag" xmlns:s="clr-namespace:System;assembly=mscorlib"
>
<DataGridComboBoxColumn.ItemsSource>
<x:Array Type="{x:Type StackPanel}">
<StackPanel Orientation="Horizontal">
<Image Source="{DynamicResource DispatchedImage}">
</Image>
<TextBlock Text="DISP"/>
<StackPanel.Tag>
<s:String>"1"</s:String>
</StackPanel.Tag>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Image Source="{DynamicResource ConfirmedImage}">
</Image>
<TextBlock Text="CONF"/>
<StackPanel.Tag>
<s:String>"2"</s:String>
</StackPanel.Tag>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Image Source="{DynamicResource PickedUpImage}">
</Image>
<TextBlock Text="P/U"/>
<StackPanel.Tag>
<s:String>"3"</s:String>
</StackPanel.Tag>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Image Source="{DynamicResource DeliveredImage}">
</Image>
<TextBlock Text="DEL"/>
<StackPanel.Tag>
<s:String>"4"</s:String>
</StackPanel.Tag>
</StackPanel>
</x:Array>
</DataGridComboBoxColumn.ItemsSource>
</DataGridComboBoxColumn>
You should bind the ItemsSource of the ComboBox to an IEnumerable<T> where T is a type that has a property for the display value and the id value:
public class CustomerComboBoxItem
{
public string Name { get; set; }
public int Value { get; set; }
}
Add the following property to your view model:
public CustomerComboBoxItem[] Items { get; } = new CustomerComboBoxItem[]
{
new CustomerComboBoxItem(){ Name ="DISP", Value = 1 },
new CustomerComboBoxItem(){ Name ="CONF", Value = 2 },
new CustomerComboBoxItem(){ Name ="P/U", Value = 3 },
new CustomerComboBoxItem(){ Name ="DEL", Value = 4 }
};
...and use an ItemTemplate:
<DataGrid ...>
<DataGrid.Resources>
<DataTemplate x:Key="template">
<StackPanel Orientation="Horizontal">
<Image>
<Image.Style>
<Style TargetType="Image">
<Style.Triggers>
<DataTrigger Binding="{Binding Text}" Value="DISP">
<Setter Property="Source" Value="{DynamicResource DispatchedImage}" />
</DataTrigger>
<DataTrigger Binding="{Binding Text}" Value="CONF">
<Setter Property="Source" Value="{DynamicResource ConfirmedImage}" />
</DataTrigger>
<DataTrigger Binding="{Binding Text}" Value="P/U">
<Setter Property="Source" Value="{DynamicResource PickedUpImage}" />
</DataTrigger>
<DataTrigger Binding="{Binding Text}" Value="DEL">
<Setter Property="Source" Value="{DynamicResource DeliveredImage}" />
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</DataTemplate>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridComboBoxColumn Header="Status"
SelectedValueBinding="{Binding Status}"
SelectedValuePath="Id">
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding Path=DataContext.Items, RelativeSource={RelativeSource AncestorType=DataGrid}}" />
<Setter Property="ItemTemplate" Value="{StaticResource template}" />
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding Path=DataContext.Items, RelativeSource={RelativeSource AncestorType=DataGrid}}" />
<Setter Property="ItemTemplate" Value="{StaticResource template}" />
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
...
</DataGrid.Columns>
</DataGrid>
See the section on data templating in the docs for more information.
I have a ListView Control in WPF which displays a list of boxes from which the user can select one from the collection. What I'm currently trying to accomplish is to have Selected Item's Background Property changed to a different color for a proper identification.
I found a Code Snippet which partially solves my problem, however, the Background value of the SelectedItem reverts back to its original when I press anywhere within the module.
What I'd like to happen is for the Background value to persist for the SelectedItem regardless if I press on other controls or even if the Window gets unfocused.
Below is my XAML Code Snippet with the Setters and Triggers I currently have in place.
<ListView Grid.Row="2"
ItemsSource="{Binding FakeDispatchBoxCollection}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<ContentPresenter />
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter Property="IsSelected" Value="True" />
</Trigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<md:Card Margin="0,0,0,10"
Cursor="Hand">
<md:Card.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsSelected, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListViewItem}}}" Value="true">
<Setter Property="md:Card.Background" Value="LightGray" />
</DataTrigger>
</Style.Triggers>
</Style>
</md:Card.Style>
<Grid Margin="5">
<!--#region Row & Column Definitions -->
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.60*" />
<ColumnDefinition Width="0.40*" />
</Grid.ColumnDefinitions>
<!--#endregion-->
<Label Grid.Row="0" Grid.Column="0"
Content="{Binding DispatchBoxName}"
FontWeight="ExtraBlack"
FontSize="15"
FontFamily="Baloo Bhai 2" />
<Label Grid.Row="1" Grid.Column="0"
Content="{Binding DispatchBoxType}"
FontWeight="SemiBold"
FontSize="13"
FontFamily="Baloo Bhai 2" />
<Label Grid.Row="0" Grid.Column="1"
Grid.RowSpan="2"
Content="{Binding UnitCount}"
VerticalAlignment="Center"
HorizontalAlignment="Right"
FontWeight="ExtraBlack"
FontSize="20"
FontFamily="Baloo Bhai 2" />
</Grid>
</md:Card>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
In the Item's Data Template (ItemTemplate property), you don't have any items that can accept keyboard focus. There are only Labels.
And in the ListViewItem Template Trigger (ItemContainerStyle property), you set the IsSelected property dependency on the keyboard focus inside the ListViewItem.
Also, you shouldn't use a ListView if you don't specify columns. In this case it is better to use ListBox.
An example of a correct implementation:
<Window.Resources>
<!--To simplify the example.-->
<CompositeCollection x:Key="source">
<sys:String>First</sys:String>
<sys:String>Second</sys:String>
<sys:String>Third</sys:String>
<sys:String>Fourth</sys:String>
</CompositeCollection>
</Window.Resources>
<ListBox ItemsSource="{DynamicResource source}">
<ItemsControl.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<ContentPresenter />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Label x:Name="label" Content="{Binding}"/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=IsSelected, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}}}"
Value="true">
<Setter TargetName="label" Property="Background" Value="Coral" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListBox>
I have this TabItem:
<TabItem x:Name="tabTFGWReadingMainHall" Header="Main Hall" DataContext="{Binding Meeting}">
<TabItem.Style>
<Style TargetType="{x:Type TabItem}">
<Setter Property="IsEnabled" Value="False"/>
<Setter Property="IsSelected" Value="False"/>
<Style.Triggers>
<DataTrigger Binding="{Binding NumberClasses, ConverterParameter=1, Converter={StaticResource IsEqualOrGreaterThanConverter}}" Value="True">
<Setter Property="IsEnabled" Value="True" />
</DataTrigger>
<DataTrigger Binding="{Binding SelectedItem, ElementName=comboActiveStudentAssignmentType}" Value="{x:Static StudentInfoEnums:StudentAssignmentType.BibleReadingMain}">
<Setter Property="IsSelected" Value="True"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TabItem.Style>
<Border x:Name="borderBibleReadingMain" BorderThickness="5">
<Border.Style>
<Style TargetType="Border">
<Setter Property="BorderBrush" Value="Transparent"/>
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedItem, ElementName=comboActiveStudentAssignmentType}" Value="{x:Static StudentInfoEnums:StudentAssignmentType.BibleReadingMain}">
<Setter Property="BorderBrush" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<StackPanel>
<StackPanel.Style>
<Style TargetType="{x:Type StackPanel}">
<Setter Property="IsEnabled" Value="False"/>
<Style.Triggers>
<DataTrigger Binding="{Binding NumberClasses, ConverterParameter=1, Converter={StaticResource IsEqualOrGreaterThanConverter}}" Value="True">
<Setter Property="IsEnabled" Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<Label Content="Student:"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<TextBox x:Name="textBibleReadingMain" Grid.Column="0" Margin="2" IsEnabled="False"
DataContext="{Binding TFGW.BibleReadingItem.Main}"
Text="{Binding DataContext.BibleReadingMain, ElementName=oclmEditor, Mode=TwoWay, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged}"/>
<Button x:Name="buttonBibleReadingMain" Grid.Column="1" Background="Transparent"
DataContext="{Binding DataContext, ElementName=oclmEditor}"
Command="{Binding ApplicationCommand}" CommandParameter="BibleReadingMain">
<Image Source="Images/AssignmentTypeBibleReading16.png" Margin="2"/>
</Button>
</Grid>
<Label Content="Study:"/>
<ComboBox DataContext="{Binding DataContext, ElementName=oclmEditor}"
ItemsSource="{Binding ReadingStudyPointsList}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Number}"/>
<TextBlock Text=" - "/>
<TextBlock Text="{Binding Title}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
</Border>
</TabItem>
As you can see, it has some DataTriggers implemented. Thus, when you change the comboActiveStudentAssignmentType (over on the right edge of the editor):
This is how the controls on the left change:
Before:
After:
The DataTriggers are working right. The Border is set correctly and the TabItem gets selected. But, if possible, I would like to end up with this:
I have done some research on this and it appears that I need to use BringIntoView to get the desired results. But that needs to be called from the code behind. I added a SelectionChanged handler and did this:
private void comboActiveStudentAssignmentType_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
OCLMEditorViewModel vm = this.DataContext as OCLMEditorViewModel;
if(borderBibleReadingMain != null &&
vm.ActiveStudentAssignmentType == Data.StudentInfo.Enums.StudentAssignmentType.BibleReadingMain)
borderBibleReadingMain.BringIntoView();
// TODO: Add others here
}
Whilst it did get fired, it did not seem to make any difference. I still had to scroll the reactangle to bring it into view.
How should I be doing this?
Thank you.
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="">
<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.
I have a ListView that is using a DataTemplate. I swap out the Item DataTemplate based on the IsSelected property of the particular item. This allows me to display an edit template and a read template. The ListView contains two columns. In read mode the two columns are readonly TextBoxes and in edit mode the left column is a editable TextBox and the right column is a drop down. Everything works great as long as I don't click directly on one of the TextBoxes when in read mode. If I click outside the control the row is selected just fine, when selecting inside the control, however, the row is not selected. This prevents me from entering edit mode when one of the cells are clicked.
I've pasted my xaml below. You can see that the GridBlock and GridEdit styles will be controlled by the IsSelected property of the ListView. This is what allows me to swap out the DataTemplate (really hide or collapse) based on that DP. I've further specialized these styles to allow for watermarked textboxes when the value is empty and the control doesn't have focus. I think this is where my problem lies but for the life of me I can't think of a way to do this declaratively. Also, I'm new to WPF so I'm sure there is a pattern for this sort of thing it's just very difficult to formulate a query that will return meaningful results from google or bing. Thanks for any and all help in advance.
Here are my styles and datatemplates:
<Style TargetType="{x:Type FrameworkElement}" x:Key="GridBlockStyle">
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Visibility"
Value="{Binding Path=IsSelected,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ListViewItem}},
Converter={StaticResource boolToVis},
ConverterParameter=False}" />
</Style>
<Style TargetType="{x:Type FrameworkElement}" x:Key="GridEditStyle">
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Visibility"
Value="{Binding Path=IsSelected,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ListViewItem}},
Converter={StaticResource boolToVis},
ConverterParameter=True}" />
</Style>
<Style x:Key="TextBoxReadOnly" TargetType="{x:Type TextBox}" BasedOn="{StaticResource GridBlockStyle}">
<Setter Property="KeyboardNavigation.TabNavigation" Value="None" />
<Setter Property="AllowDrop" Value="true" />
<Setter Property="Background" Value="Transparent"></Setter>
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Stretch" />
<Setter Property="Padding" Value="8,5,3,3" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<Grid>
<Label x:Name="TextPrompt" Content="{TemplateBinding Tag}" Visibility="Collapsed" Focusable="False" Foreground="Silver"></Label>
<ScrollViewer Margin="0" x:Name="PART_ContentHost" Foreground="{DynamicResource OutsideFontColor}" />
</Grid>
<ControlTemplate.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsFocused" Value="False"></Condition>
<Condition Property="Text" Value=""></Condition>
</MultiTrigger.Conditions>
<MultiTrigger.Setters>
<Setter Property="Visibility" TargetName="TextPrompt" Value="Visible"></Setter>
</MultiTrigger.Setters>
</MultiTrigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Foreground" Value="DimGray" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="TextBoxEditable" TargetType="{x:Type TextBox}" BasedOn="{StaticResource GridEditStyle}">
<Setter Property="KeyboardNavigation.TabNavigation" Value="None" />
<Setter Property="AllowDrop" Value="true" />
<Setter Property="Background" Value="Transparent"></Setter>
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Stretch" />
<Setter Property="Padding" Value="8,5,3,3" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<Grid>
<Border x:Name="BorderBase" Background="White" BorderThickness="1.4,1.4,1,1"
BorderBrush="Silver" />
<Label x:Name="TextPrompt" Content="{TemplateBinding Tag}" Visibility="Collapsed" Focusable="False" Foreground="Silver"></Label>
<ScrollViewer Margin="0" x:Name="PART_ContentHost" Foreground="{DynamicResource OutsideFontColor}" />
</Grid>
<ControlTemplate.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsFocused" Value="False"></Condition>
<Condition Property="Text" Value=""></Condition>
</MultiTrigger.Conditions>
<MultiTrigger.Setters>
<Setter Property="Visibility" TargetName="TextPrompt" Value="Visible"></Setter>
</MultiTrigger.Setters>
</MultiTrigger>
<Trigger Property="IsFocused" Value="True">
<Setter Property="BorderThickness" TargetName="BorderBase" Value="2.4,2.4,1,1"></Setter>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Foreground" Value="DimGray" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And here is my ListView view:
<ListView.View>
<GridView>
<GridViewColumn Width="120">
<GridViewColumnHeader Content="Resource ID" />
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid>
<TextBox Margin="3" Tag="Enter Resource ID" Text="{Binding Path=ResourceID, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource TextBoxReadOnly}" IsReadOnly="True" />
<TextBox Width="90" Tag="Enter Resource ID" Margin="3"
Style="{StaticResource TextBoxEditable}" Text="{Binding Path=ResourceID, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Width="120">
<GridViewColumnHeader Content="Code" />
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid>
<TextBox Margin="3" Tag="Enter Code" Text="{Binding Path=Code}"
Style="{StaticResource TextBoxReadOnly}" IsReadOnly="True" />
<ComboBox Margin="3" Style="{StaticResource GridEditStyle}"
ItemsSource="{Binding Source={StaticResource CodeViewSource}, Mode=OneWay}"
SelectedItem="{Binding Path=Code, Mode=TwoWay}"
IsSynchronizedWithCurrentItem="False"
util:ComboBoxWidthFromItemsBehavior.ComboBoxWidthFromItems="True" />
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Width="120">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Button Margin="5" Content="Delete"
Command="{Binding Path=DataContext.RemoveORIEntryCommand,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type UserControl}}}">
<Button.Resources>
<Converter:AgencyItemIDParametersConverter x:Key="RemoveListViewItemParametersConverter" />
</Button.Resources>
<Button.CommandParameter>
<MultiBinding Converter="{StaticResource RemoveListViewItemParametersConverter}">
<MultiBinding.Bindings>
<Binding Path="AgencyID" />
<Binding Path="ID" />
</MultiBinding.Bindings>
</MultiBinding>
</Button.CommandParameter>
</Button>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
I had a similar problem in a ListView.
Basically each item of the ListView had a RadioButton and a TextBox. The RadioButton IsChecked property was binded to the ListViewItem select property. The thing was that when I selected the TextBox the item was not selected, hence not checking the RadioButton.
I managed to solve the problem with the IsKeyboardFocusWithin property. I set a trigger in the ListViewItem style so when this property is true the isSelected property would be set to true also.
You can check this thread.