Highlight one item in DataGrid - c#

I'm displaying Cars in a DataGrid and would like to highlight one special car, the CurrentlySelectedCar.
When the user double clicks on a car, this car is saved as CurrentlySelectedCar in my MainViewModel. Now, whenever the user comes back to the DataGrid, I would like to highlight this car = row, e.g. by using a red background.
I have found out how to highlight rows in a DataGrid based on certain values, but in my case, all I have is the CurrentlySelectedCar.
My First try:
<Style TargetType="DataGridRow">
<Style.Triggers>
<!-- not working-->
<DataTrigger Binding="{Binding CurrentlySelectedCar}" >
<Setter Property="Background" Value="Red"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
My second try:
<Style TargetType="DataGridRow">
<Style.Triggers>
<!-- not working either, "Binding can only be set on DependencyProperty of DependecyObject"-->
<DataTrigger Binding="{Binding Guid}" Value="{Binding CurrentlySelectedCar.Guid}" >
<Setter Property="Background" Value="Red"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
How can I highlight the row with this information?

I think that you have to do something like this described in this answer: Using bindings in DataTrigger condition
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource someMultiConverter}">
<Binding Path="Guid"></Binding>
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type Datagrid}}" Path="CurrentlySelectedCar.Guid"></Binding>
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Background" Value="Red"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
You have to write a multiconverter that return true if the two Guid are equals.

Try the following instead, since each datagrid row has an IsSelected property, you can bind to it directly.
<DataGrid EnableRowVirtualization="False">
<DataGrid.Resources>
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected}" >
<Setter Property="Background" Value="Red"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.Resources>
</DataGrid>
Updated response
The problem right now is your DataContext is the DataGridRow, and it doesn't have access to the MainViewModel. You can address this by passing the DataGridRow to a converter that is aware of the current MainViewModel. However, it is a lot of code, and is barely comprehensible.
<Window>
<Window.Resources>
<local:IsCurrentlySelectedCarConverter x:Key="IsCurrentlySelectedCarConverter" />
</Window.Resources>
...
<DataTrigger Binding="{Binding
Path=DataContext,
Converter={StaticResource IsCurrentlySelectedCarConverter}}" >
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Window>
Here's corresponding converter
public class IsCurrentlySelectedConverter : IValueConverter
{
public MainViewModel MainViewModel { get; set;}
public object Convert(object value, ....)
{
return (value == MainViewModel.CurrentlySelectedCar);
}
}
and you'll need to wire in the converter's MainViewModel manually in your view
this.Resources["IsCurrentlySelectedCarConverter"].MainViewModel = _mainViewModel;
and at this point you'd have to question the maintainability of the monster that has been created.
It may be better to replace each Car with a CarViewModel so that it has a property called IsSelected and you can maintain that in code. The following in my opinion is easier to follow what is actually going on.
public class CarViewModel : INotifyPropertyChanged
{
public MainViewModel { get; set; }
public bool IsSelected { get { return this == MainViewModel.CurrentlySelectedCar; } }
// Call RaisePropertyChanged("IsSelected") whenever
// CurrentlySelectedCar is changed
public void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName);
}
}

Related

WPF DataTrigger never fires to Expand TreeView

I have the following XAML which should expand my whole treeview when a specific property ExpandNodes is True, but it is never triggered.
<TreeView>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="False" />
<Style.Triggers>
<DataTrigger Binding="{Binding ExpandNodes}" Value="True">
<Setter Property="IsExpanded" Value="True" />
</DataTrigger>
<DataTrigger Binding="{Binding ExpandNodes}" Value="False">
<Setter Property="IsExpanded" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
// some other code
</TreeView>
In my ViewModel I set ExpandNodes to True during a specific event, yet the treeview remains non expanded. It's not an issue with my DataContext as I have other properties from the same Viewmodel that are bound and work fine.
My Viewmodel:
private bool _expandnodes;
public bool ExpandNodes
{
get
{
return _expandnodes;
}
set
{
_expandnodes = value;
OnPropertyChanged("ExpandNodes");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string PropName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(PropName));
}
}
Too compliacted, you can simply bind IsExpanded property to your object and raise PropertyChanged event.
<TreeView>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding ExpandNodes,
Mode=TwoWay, UpdateSourceTrigger=PropertyChanged,
TargetNullValue=False}" />
</Style>
</TreeView.ItemContainerStyle>
// some other code
</TreeView>

Image Visibility DataTrigger Set Default to False

I have an image that need to be showed based on condition, is that attachment file or not. The problem is, I've set trigger that set the value of the condition, but seems like the condition isn't work and the value always set to true.
<Image
Width="30"
Height="30"
Source="Resources/Images/chat_file_attach.png">
<Image.Style>
<Style TargetType="{x:Type Image}">
<Style.Triggers>
<DataTrigger Binding="{Binding AttachStat}" Value="False">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
<DataTrigger Binding="{Binding AttachStat}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
The question is. Is there any way to make the default value to false? I've set it to true on the C# looping data, whenever the condition is attachment included.
Take a look in your output window and search for:
System.Windows.Data Warning: 40 : BindingExpression path error
I think the AttachStat property is not available in the image's DataContext.
Use a single DataTrigger and make sure that the DataContext of the Image has a public AttachStat property:
<Image x:Name="img" Width="30" Height="30" Source="Resources/Images/chat_file_attach.png">
<Image.Style>
<Style TargetType="{x:Type Image}">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding AttachStat}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
img.DataContext = new YourClass();
...
public class YourClass : INotifyPropertyChanged
{
private bool _attachStat;
public bool AttachStat
{
get
{
return _attachStat;
}
set
{
_attachStat = value;
NotifyPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
You can use a BoolToVisibilityConverter to convert a Boolean to and from a Visibility value.
In the resource section:
<BooleanToVisibilityConverter x:Key="BoolToVisibilityConverter" />
And the image:
<Image Width="30"
Height="30"
Source="Resources/Images/chat_file_attach.png"
Visibility="{Binding AttachStat,
Converter={StaticResource BoolToVisibilityConverter}}" />

Creating a self-updating Textblock user control in WPF

I'm trying to create a re-usable textblock user control in WPF. The basic idea is as follows:
User does not directly specify the content of the textblock
There are three dependancy properties in my user control called IsToggled, ToggleTrueText, and ToggleFalseText.
The control will display ToggleTrueText if IsToggled is true; or display ToggleFalseText if IsToggled is false.
When IsToggled changes during runtime, the text automatically changes to either ToggleTrueText or ToggleFalseText
I started by adding a PropertyChangedCallback to the IsToggled DP:
Code-behind of the UserControl:
public static readonly DependencyProperty IsToggledProperty =
DependencyProperty.Register("IsToggled", typeof(bool),
typeof(TagToggle), new PropertyMetadata(new
PropertyChangedCallback(OnToggleStateChanged)));
public bool IsToggled
{
get { return (bool)GetValue(IsToggledProperty); }
set { SetValue(IsToggledProperty, value); }
}
//ToggleTrueText and ToggleFalseText are declared similarly to IsToggled
...
private static void OnToggleStateChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
...
}
Xaml of the user control:
<Grid x:Name="LayoutRoot">
<TextBlock x:Name="TheTextBlock" Text="{Binding WhatDoIBindTo}"/>
</Grid>
However, I'm not sure what would be the best way to ensure that TheTextBlock updates its text whenever IsToggled changes during runtime.
Try this:
private static void OnToggleStateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TagToggle ctrl = d as TagToggle;
if (ctrl != null)
{
TheTextBlock.Text = ctrl.IsToggled ? ToggleTrueText. : ToggleFalseText;
}
}
If you want to bind the Text property of the TextBlock you need to make sure that you are binding to properties of the UserControl. You could do this by setting the DataContext property of the TextBlock:
<TextBlock x:Name="TheTextBlock" DataContext="{Binding RelativeSource={RelativeSource AncestorType=UserControl}}">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Text" Value="{Binding ToggleTrueText}" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsToggled}" Value="False">
<Setter Property="Text" Value="{Binding ToggleFalseText}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
You can used trigger for this
Please check below code
<TextBlock x:Name="TheTextBlock">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsToggled}" Value="True">
<Setter Property="Text" Value="{Binding ToggleTrueText}"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsToggled}" Value="False">
<Setter Property="Text" Value="{Binding ToggleFalseText}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>

How can i change datagrid row's foreground based on a condition?

Hy, I am new in WPF programming and I have a little problem that I can't solve it by my self.
I have created a list of object:
List<TestInfo> Info = new List<TestInfo>();
where TestInfo:
public class TestInfo
{
public string Serial { get; set; }
public string Test { get; set; }
public string Result { get; set; }
}
After that I have grouped it:
ListCollectionView groupedInfo = new ListCollectionView(Info);
groupedInfo.GroupDescriptions.Add(new PropertyGroupDescription("Serial"));
Then binded it to datagrid:
dataGrid.ItemsSource = groupedInfo;
Now, my question is, in this case, how can i change datagrid's row forecolor based on a condition? Something like: if groupedInfo.Result == F change row forecolor.
I can't manage to do it myself. Please help!
Here is an example of how to do it based on a condition:
<DataGrid>
<DataGrid.CellStyle>
<Style x:Key="DGCell" TargetType="{x:Type DGCell}" >
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Foreground" Value="Gray"/>
</Trigger>
</Style.Triggers>
</Style>
</DataGrid.CellStyle>
</DataGrid>
And to use it on a column you do:
<DataGrid>
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Name}">
<DataGridTextColumn.CellStyle>
<Style TargetType="DGCell">
Thanks all for your help. I have finally solved it and learned what triggers are [:D]. The solution for me was using a DataTrigger. The code is below:
<Style TargetType="DataGridCell">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Result}" Value="P">
<Setter Property="Foreground" Value="Green" />
</DataTrigger>
</Style.Triggers>
</Style>

How can I use 3 different styles based on two unrelated triggers

I have a Gridview.Column where the style of the content is changed depending on the content of another column by a IMultiValueConverter. This part works as expected.
If the values of the two values are the same then the text of this column is LightGray otherwise black.
<GridViewColumn Header="New Name" Width="300">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding newFileName}">
<TextBlock.Style>
<Style>
<Setter Property="TextBlock.Foreground" Value="Black"></Setter>
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource FileNameColorConverter}">
<!--<Binding Path="Selected"/>-->
<Binding Path="newFileName"/>
<Binding Path="fileName"/>
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="TextBlock.Foreground" Value="LightGray"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
Now I have another property in this object that is bound to this GridView. If this is TRUE the text of this column should become red, instead of black, if it is false, the FileNameColorConverter should decide the style of the column, same as it is now.
How can I do this? I am at the moment lost, and have no idea where to put this logic, I am quite sure there is a way to have this also in XAML.
Edit
What I tried is adding another trigger after the first one, but it didn't worked for me, if this is the way to go, whats wrong with my code?
I added this after my first </DataTrigger> but it didn't had an effect.
<DataTrigger Value="True">
<DataTrigger.Binding>
<Binding Path="FileNameError"/>
</DataTrigger.Binding>
<Setter Property="TextBlock.Foreground" Value="Red"></Setter>
</DataTrigger>
Multiple data triggers with exclusive conditions on same data work for me perfectly... Can you check if your FileNameError is True for atleast 1 item? And if are you changing it dynamically then are you raising PropertyChanged event?
You can try my code and see...
XAML:
<Page.Resources>
<local:MultiBindingConverter x:Key="MultiBindingConverter"/>
<coll:ArrayList x:Key="MyData">
<local:Student Id="1" local:Student.Name="Test1" Active="False"/>
<local:Student Id="2" local:Student.Name="Test2" Active="True"/>
<local:Student Id="3" local:Student.Name="Test3" Active="False"/>
<local:Student Id="4" local:Student.Name="Test4" Active="False"/>
</coll:ArrayList>
</Page.Resources>
<Grid>
<ListBox ItemsSource="{StaticResource MyData}"
DisplayMemberPath="Name">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Style.Resources>
<SolidColorBrush
x:Key="{x:Static SystemColors.HighlightBrushKey}"
Color="Yellow"/>
</Style.Resources>
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding
Converter="{StaticResource
MultiBindingConverter}">
<Binding Path="Id"/>
<Binding Path="Active"/>
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Foreground" Value="Red"/>
</DataTrigger>
<DataTrigger Value="3">
<DataTrigger.Binding>
<Binding Path="Id"/>
</DataTrigger.Binding>
<Setter Property="Foreground" Value="Blue"/>
</DataTrigger>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Foreground" Value="Gray"/>
</Trigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</Grid>
Code Behind:
public class MultiBindingConverter : IMultiValueConverter
{
#region IMultiValueConverter Members
public object Convert(object[] values, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
if (values[0] != DependencyProperty.UnsetValue
&& values[0] != null && values[1] != null)
{
var value1 = (int)values[0];
var value2 = (bool)values[1];
var result = (value1 > 1 && value2);
if (result)
{
return true;
}
return false;
}
return false;
}
public object[] ConvertBack
(object value, Type[] targetTypes,
object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
public class Student : INotifyPropertyChanged
{
public int Id
{
get;
set;
}
public string Name
{
get;
set;
}
public bool Active
{
get;
set;
}
public Student()
{ }
public Student(int id, string name)
{
this.Id = id;
this.Name = name;
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
Add another trigger after the existing one which triggers on the additional property with Value="True". This should then override the existing trigger if the property is true, allowing you to set the column to be red. However, if the property is false the trigger will do nothing and the existing trigger's effects will be visible.
Look into the GridViewColumn.CellTemplateSelector Property.
Depending on some logic, the DataTemplateSelector, you give the cell a different DataTemplate.

Categories

Resources