WPF DataGridColumn style depending on tag - c#

following the post in here, I've managed to create Tag property to the columns of my datagrid. Now, I would like to have different colors of columns depending on the value in tag. The goal is to have a table with days in columns, and today's current day would be highlighted (the date corresponding to column is stored in it's tag). Is it achievable? I found the solution based on coloring cells programmatically, but it doesn't suit my needs (multithreading issue - sometimes cell is painted before it stops to fill with data and then painting doesn't work).
Thanks in advance.
Edit:
Ok, I've followed the answer given by Nayeem Mansoori and came to this:
Style:
<Style x:Key="CellStyle" TargetType="{x:Type DataGridCell}">
<Setter Property="Foreground" Value="Black" />
<Setter Property="Background" Value="{TemplateBinding local:DataGridCellThemeProps.Brush}" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="#B2DCDCDC" />
<Setter Property="BorderBrush" Value="#B2DCDCDC" />
</Trigger>
<Trigger Property="IsSelected" Value="False">
<Setter Property="Background" Value="#7FFFFFFF" />
<Setter Property="BorderBrush" Value="#7FFFFFFF" />
</Trigger>
</Style.Triggers>
</Style>
DataGrid column:
<DataGridTextColumn local:DataGridCellThemeProps.Brush="Thistle"
Header="{x:Static props:Resources.Monday}"
Binding="{Binding Monday, Converter={StaticResource ZeroConverter}, Mode=TwoWay}"
CellStyle="{StaticResource CellStyle}" Width="75"/>
Class:
public static class DataGridCellThemeProps {
public static SolidColorBrush GetBrush(DependencyObject obj) {
return (SolidColorBrush)obj.GetValue(BrushProperty);
}
public static void SetBrush(DependencyObject obj, SolidColorBrush value) {
obj.SetValue(BrushProperty, value);
}
public static readonly DependencyProperty BrushProperty =
DependencyProperty.RegisterAttached(
"Brush",
typeof(SolidColorBrush),
typeof(DataGridCellThemeProps),
new FrameworkPropertyMetadata(Brushes.Black));
}
When I run my app, I get exception:
Exception: {"'Set property 'System.Windows.Setter.Value' threw an exception.' Line number '25' and line position '14'."}
Inner Exception: {"Expression type is not a valid Style value."}
I've double checked namespace of the class, it's correct. Any ideas?

Related

C# WPF UI is not updated by datatrigger when I change a property in the listview item

I want to make a chess game in WPF, I decided to use listview to display my fields. I have a class named Field with properties. I have a list containing 64 fields and i changed to style of my listview in xaml so its look like a chess field. However, i have problem with the datatriggers. I need to change the picture (that will show the chess piece) when a property in that element in my list is changed.Nothing happens when the 'hasPiece' property is changed. I use INotifyPropertyChanged so i think i have no problem with the UI refresh, because i can update any displayed data in labels and testboxs. That is why i know i successfully changed the property when i selected 1 item, because i binded to a label. So when i select a field(an item) the 'hasPiece' property is updated to hidden, but the image still remains visible. I dont know much about datatriggers and I dont know what i did wrong.
<ListView Grid.Row="1" Grid.Column="1" ItemsSource="{Binding Fields}" Width="410" SelectedItem="{Binding selectedField,Mode=TwoWay}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Height" Value="50"></Setter>
<Setter Property="Width" Value="50"></Setter>
<Setter Property="Margin" Value="0"></Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListViewItem">
<Border Name="Border" BorderThickness="4,4,4,4" Background="{Binding Brush}" BorderBrush="{Binding Brush}">
<Grid Name="Grid" Background="{Binding Brush}">
<Image Name="PieceImage" Height="40" Width="40" Source="Images/something.jpg" >
</Image>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter TargetName="Grid" Property="Background" Value="{Binding MouseOverBrush}"/>
<Setter TargetName="Border" Property="BorderBrush" Value="{Binding MouseOverBrush}"/>
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="Border" Property="BorderBrush" Value="Red"></Setter>
</Trigger>
<DataTrigger Binding="{Binding hasPiece}" Value="Hidden">
<Setter TargetName="PieceImage" Property="Visibility" Value="Hidden">
</Setter>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>
This is what i currently do when an item is selected:
private Field selected { get; set; }
public Field selectedField
{
get { return selected; }
set
{
selected = value;
// fieldId = selected.ID.ToString();
removeImage(selected.ID);
}
}
public void removeImage(int id)
{
foreach (Field item in Fields)
{
if (item.ID == id)
{
item.hasPiece = System.Windows.Visibility.Hidden;
fieldId = item.hasPiece.ToString();
}
}
}
I currently just want to test everything that needed, i know later i have to change things, but now i just want to know how i can change something in the style with datatriggers properly.
I solved the problem!
I implemented the INotifyPropertyChanged interface to my ViewModel, so the UI got updated when a variable which was created there is changed or when I added or removed items to my Observable collection which was also created there.
However if I want to edit my collection I have to implement this interface to my model.
My problem was not related to DataTrigger. My problem was the lack of INotifypropertyChanged in my model.

I added style for dataGridCell, but when I try to find it, exception is appearing that style didn't find

In the code there are two styles which I want toggle in the DataGridColumn "CreationDateDisplayer". DataGrid have two conditions which are described by the type CurrentDisplayingListTypes. Method ToggleCurrentDisplayingList successfully change condition of DataGrid.
I want change column styles when DataGrid condition is changed. I try to change style in the code "CreationDateDisplayer.CellStyle = Application.Current.FindResource("Default") as Style;", but "FindResourse" don't find style.
(I removed code which can't be related with my problem)
<Window.Resources>
<Style TargetType="DataGridCell" x:Key="Default">
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
</Style>
<Style TargetType="DataGridCell" x:Key="CreationDate" BasedOn="{StaticResource Default}" x:Name="RecordingList">
<Setter Property="Foreground" Value="Green"/>
<Setter Property="FontWeight" Value="Bold"/>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Foreground" Value="#d92525"/>
<Setter Property="Background" Value="White"/>
<Setter Property="BorderBrush" Value="White"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
//some button which method is "ToggleCurrentDisplayingList"
<DataGrid x:Name="RecordingsDisplayer">
<DataGridTextColumn x:Name="CreationDateDisplayer" CellStyle="{StaticResource CreationDate}"/>
<Grid>
</Window.Resources>
private void ToggleCurrentDisplayingList(object sender, RoutedEventArgs e)
{
switch (currentDisplayingList)
{
case CurrentDisplayingListTypes.RecordingList:
RecordingsDisplayer.ItemsSource = basket;
CreationDateDisplayer.CellStyle = Application.Current.FindResource("Default") as Style;
currentDisplayingList = CurrentDisplayingListTypes.Basket;
break;
case CurrentDisplayingListTypes.Basket:
RecordingsDisplayer.ItemsSource = recordingList;
CreationDateDisplayer.CellStyle = Application.Current.FindResource("CreationDate") as Style;
currentDisplayingList = CurrentDisplayingListTypes.RecordingList;
break;
}
}
Application.Current.FindResource("Default") will perform search starting from Application.Resources.
By the look of things, "Default" Style is defined close to DataGrid, so use DataGrid's own FindResource method
CreationDateDisplayer.CellStyle = RecordingsDisplayer.FindResource("Default") as Style;
If the resource is not found on the calling element, the parent element in the logical tree is searched next, then the application, then themes, and finally system resources.
since ge it directly from window Resources:
CreationDateDisplayer.CellStyle = this.Resources["Default"] as Style;

Generic style with specific trigger values

I have several TextBlocks in my UserControl that I want to change to become Bold and have a Red font when a Property is triggered. The thing is that each of them are changed by different properties. I saw a solution here with Tags, but couldn't quite get it to work for me. I apologize if this is a duplicate, but I couldn't find any solution that solved my problem.
My Style looks like this:
<Style x:Key="TextBlockTrigger" TargetType="{x:Type TextBlock}">
<Style.Triggers>
<Trigger Property="Tag" Value="true">
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Foreground" Value="Red"/>
</Trigger>
</Style.Triggers>
</Style>
And this is the TextBlock I'm trying to use it on:
<TextBlock Name="TextBlock1" x:Uid="TextBlock1" Text="This text should become bold and Red"
Style="{StaticResource TextBlockTrigger}" Tag="{Binding Path=TriggerProperty, UpdateSourceTrigger=PropertyChanged}"/>
I added a button with a codebehind function that reads the Tag, and a breakpoint shows that Tag is set to true, but the text is still regular black.
TriggerProperty is set by a function call in the View Constructor, after InitializeComponent:
public MyWindow()
{
InitializeComponent();
UpdateServerProperties();
}
public void UpdateServerProperties()
{
//other code
if(ServerValue == true)
{
TriggerProperty = true;
OnPropertyChanged("TriggerProperty");
}
}
It's a bit simplified, but the actual code is overly complicated, but results in the same thing. ServerValue gets a value, and I have confirmed TriggerProperty does get updated to true.
The Tag property has an object type. Xaml has no way of knowing that true represents a bool value, so it just assumes you meant it to be a string. Assuming you are setting Tag to a boolean value, your Trigger is evaluating Equals(true, "true"), so the condition fails.
Try using {x:Static} to point to some constant boolean value. I keep a KnownBoxes class around for this sort of thing:
public static class KnownBoxes
{
public static readonly object True = true;
public static readonly object False = false;
// ... more common values ...
}
These values are easily referenced from Xaml, e.g., {x:Static ns:KnownBoxes.True}.
Alternatively, you could use the element syntax:
<Trigger Property="Tag">
<Trigger.Value>
<s:Boolean xmlns:s="clr-namespace:System;assembly=mscorlib">True</s:Boolean>
</Trigger.Value>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Foreground" Value="Red"/>
</Trigger>
Or you could set Tag to the string "true", though that may cause some confusion when someone else works on your code :).
If you set TriggerProperty to true dynamically at runtime, the class where this property is defined should implement the INotifyPropertyChanged interface and raise the PropertyChanged event for the trigger to trigger.
If you set the Tag property to a hard-coded value of true, your sample markup should work as expected:
<TextBlock Name="TextBlock1" x:Uid="TextBlock1" Text="This text should become bold and Red"
Style="{StaticResource TextBlockTrigger}" Tag="true"/>
You should also set the Value property to a typed bool value in your Style:
<Style x:Key="TextBlockTrigger" TargetType="{x:Type TextBlock}"
xmlns:s="clr-namespace:System;assembly=mscorlib">
<Style.Triggers>
<Trigger Property="Tag">
<Trigger.Value>
<s:Boolean>True</s:Boolean>
</Trigger.Value>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Foreground" Value="Red"/>
</Trigger>
</Style.Triggers>
</Style>

Why is my WPF listview code throwing an excpetion when programatically selecting a row

I am trying to programmatically highlight the first row in a WPF listview control using VS2008 with 3.5 of the .NET framework. Here is the C# code for this:
ListViewItem Val = (ListViewItem)ListView1.Items[0];
Val.IsSelected = true;
The code throws an exception at the first line, which is after ListView1 is populated with data. The message in the exception says:
"Unable to cast object of type 'Message.LV1Data' to type 'System.Windows.Controls.ListViewItem'."
LV1Data is the class I am using to bind columns in this control. So, it looks like it is trying to return an LV1Data object instead of a ListViewItem object. Does anyone have any suggestions as to what I am doing wrong or what I need to do in order to programmatically highlight a listview row?
Here is the XAML code for the ListView control:
<ListView x:Name="ListView1" ItemContainerStyle="{StaticResource alternatingListViewItemStyle}" AlternationCount="2" SelectionChanged="ListView1_SelectionChanged"
SelectionMode="Multiple" HorizontalAlignment="Left" ItemsSource = "{Binding ElementName=LobbyWindow, Path=ListCollection1}">
<ListView.View>
<GridView>
<GridViewColumn DisplayMemberBinding="{Binding Game}">
<GridViewColumnHeader Content="Game" FontWeight="Bold" />
</GridViewColumn>
<GridViewColumn DisplayMemberBinding="{Binding Stakes}">
<GridViewColumnHeader Content="Stakes" Width="68" FontWeight="Bold" />
</GridViewColumn>
<GridViewColumn Width="30" DisplayMemberBinding="{Binding Seats}">
<GridViewColumnHeader Content="Seats" FontWeight="Bold" />
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
EDIT
<!-- Define the resource for the alternating background background used in the ListView objects. -->
<StackPanel.Resources>
<Style x:Key="alternatingListViewItemStyle" TargetType="{x:Type ListViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}"/>
<Style.Resources>
<!-- Foreground for Selected ListViewItem -->
<!-- <SolidColorBrush x:Key="{x:Static SystemColors.HighlightTextBrushKey}" Color="Black"/> -->
<!-- Background for Selected ListViewItem -->
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="Green"/>
<SolidColorBrush x:Key="{x:Static SystemColors.ControlBrushKey}" Color="Brown"/>
</Style.Resources>
<Style.Triggers>
<!-- setting up triggers for alternate background colors -->
<Trigger Property="ItemsControl.AlternationIndex" Value="1">
<Setter Property="Background" Value="#FFD9F2BF"></Setter>
</Trigger>
<Trigger Property="ItemsControl.AlternationIndex" Value="2">
<Setter Property="Background" Value="White"></Setter>
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="LightBlue"></Setter>
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="BorderBrush" Value="LightBlue" />
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsSelected" Value="True"></Condition>
<Condition Property="ItemsControl.AlternationIndex" Value="0"></Condition>
</MultiTrigger.Conditions>
<Setter Property="Background" Value="LightBlue"></Setter>
</MultiTrigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsSelected" Value="True"></Condition>
<Condition Property="ItemsControl.AlternationIndex" Value="1"></Condition>
</MultiTrigger.Conditions>
<Setter Property="Background" Value="LightGreen"></Setter>
</MultiTrigger>
</Style.Triggers>
<!-- setting row height here -->
</Style>
</StackPanel.Resources>
You bound to an items source, which means asking for the items[x] will return a type of the data you bound to (whatever type is stored in ListCollection1).
If you want to alter it's IsSelected, you'll have to create that property on the type in ListCollection1, and bind to it in a style or template.
The IsSelected property you create will have to be implemented as a DependencyProperty, or the Type it's in will have to implement INotifyPropertyChanged, and trigger that event when the property changes.
<ListView ItemsSource="...">
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
Then you'll cast to that dataobject type, and set it's IsSelected value.
Find in your code the ListCollection1. It's definition will look like List<Element>. Element is the type you need to cast to.
Element needs to either look like
public class Element : INotifyPropertyChanged
{
private _IsSelected;
public Boolean IsSelected
{
get { return _IsSelected; }
set
{
_IsSelected = value;
if (PropertyChanged != null)
PropertyChanged("IsSelected");
}
}
//snip Implement interface INotifyPropertyChanged.
//snip your other code
}
-OR-
public class Element : DependencyObject
{
public static DependencyProperty IsSelectedProperty =
DependencyProperty.Register("IsSelected" ...
//snip your other code
}
Then your code should look like this.
Element Val = (Element)ListView1.Items[0];
Val.IsSelected = true;
Items is bound to your business object, so that is why it is not actually returning a listview item. You can try three things:
Use SetSelectedItems and only pass in an IEnumerable of one object
OR, you can get the object and then ask for the ListViewItem it refers to
(ListViewItem)ListView1.ItemContainerGenerator.ContainerFromItem(ListView1.Items[0])
OR, you can bind to the IsSelected property and manage that in your viewmodel

Get Valdiation error in Tooltip if there is an error, else show explanatory Tooltip (using TextBox Style)

Update: Found a Solution, see below!
I'm try to implement the following behavior:
There are several TextBoxes in a UserControl. Each TextBox-ToolTip should show a specific string, which is located in a Resource.resx-file.
If the Valdiation of this TextBox returns an error, the returned error string will be shown in the ToolTip.
This should be done using a Style. My current status is, that I can show the specific Validation.Errors AND a default ToolTip, which is the same for every TextBox which uses the Style.
So my Style is :
<Style TargetType="{x:Type TextBox}">
<Setter Property="ToolTip" Value="ExampleToolTip"/>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors).CurrentItem.ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
With this Style, i get the behavior described above.
Now I want the Style part
<Style TargetType="{x:Type TextBox}">
<Setter Property="ToolTip" Value="ExampleToolTip"/>
...
</Style>
to be TextBox-Specific.
I tried to write an Attatched Property for my TextBoxes, so that I can define a second string, which should be used as the standard ToolTip.
The Attached Property Code ist shown below:
public class TextBox2 : DependencyObject
{
public static void SetToolTipStandard(TextBox target, string value)
{
target.SetValue(ToolTipStandardProperty, value);
}
public static string GetToolTipStandard(TextBox target)
{
return (string)target.GetValue(ToolTipStandardProperty);
}
public static DependencyProperty ToolTipStandardProperty = DependencyProperty.RegisterAttached(
"ToolTipStandard",
typeof(string),
typeof(TextBox),
new PropertyMetadata());
}
Now I want to set a TextBox2.ToolTipStandard property on my TextBoxes in XAML, and the TextBox-Style should take this Property to set the default ToolTip-Text. I tried several combinations of Bindings but with no success. Is there a way to achieve this behavior?
An idea, you can put a hidden border in the ControlTemplate with a BorderThickness of 0 around the input control which provides the error tooltip. If there is an error you set the visibility to visible and the error tooltip is shown.
It's just an idea, but it may work.
I managed to get the desired behaviour by naming all my TextBoxes and adding multiple Triggers to my TextBox-Style. Code follows:
<UserControl.Resources>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<!--Here are the static resource strings for the normal-state ToolTip-->
<Trigger Property="Name" Value="TextBox1">
<Setter Property="ToolTip" Value="{x:Static properties:UIStrings.TextBox1_ToolTip_String}"/>
</Trigger>
<Trigger Property="Name" Value="TextBox2">
<Setter Property="ToolTip" Value="{x:Static properties:UIStrings.TextBox2_ToolTip_String}"/>
</Trigger> ...
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors).CurrentItem.ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
</UserControl.Resources>

Categories