How does one style a specific row or rows in a WPF Datagrid during runtime? Each change depends on some values of the shown data?
I can't tell from your question whether you are adding columns to your grid at run time, but either way you can add a CellStyle to the grid at design time that handles your specific styling needs using DataTriggers.
For instance, the following would make all rows red where the Name property = "Billy Bob":
<DataGrid AutoGenerateColumns="True" Name="dataGrid1">
<DataGrid.CellStyle>
<Style TargetType="{x:Type DataGridCell}">
<Style.Triggers>
<DataTrigger Binding="{Binding Name}" Value="Billy Bob" >
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.CellStyle>
</DataGrid>
If you are adding columns programmatically at run time and you want to apply a certain style to them, you can still define those styles at design time in your xaml.
<DataGrid AutoGenerateColumns="False" Name="dataGrid1">
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridCell}" x:Key="MyCellStyle">
<Setter Property="Foreground" Value="Green"/>
</Style>
</DataGrid.Resources>
...
Then when you are adding the columns you can apply that style to them:
col.CellStyle = (Style)dataGrid1.Resources("MyCellStyle");
Update
If you have a list of songs and you want to change the row color of every song that has an artist whose name starts with an "a", then you could use an IValueConverter.
The following converter would do the trick:
public class ArtistNameConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
try
{
return value.ToString().StartsWith(parameter.ToString());
}
catch
{
return false;
}
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Then you could use the converter in your xaml like so:
<DataGrid AutoGenerateColumns="True" Name="dataGrid1">
<DataGrid.Resources>
<converters:ArtistNameConverter x:Key="ArtistNameConverter"></converters:ArtistNameConverter>
</DataGrid.Resources>
<DataGrid.CellStyle>
<Style TargetType="{x:Type DataGridCell}">
<Style.Triggers>
<DataTrigger Binding="{Binding ArtistName, Converter={StaticResource ArtistNameConverter}, ConverterParameter=a}" Value="True" >
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.CellStyle>
</DataGrid>
Notice how I am passing an "a" into the converter as the parameter. You can pass in whatever letter you want, and the rows that have artists that start with that letter will have their background color set to red.
Update 2
If you want to pass in a variable of some sort to the converter, you can use MultiBinding.
The Converter would look like this:
public class ArtistNameConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
try
{
return values[0].ToString().StartsWith(values[1].ToString());
}
catch
{
return false;
}
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
The first parameter passed in is the artist name, the second is the letter.
And you would use it in your grid like this:
<DataGrid.CellStyle>
<Style TargetType="{x:Type DataGridCell}">
<Style.Triggers>
<DataTrigger Value="True" >
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource ArtistNameConverter}">
<Binding Path="ArtistName" />
<Binding Mode="OneWay" ElementName="FirstLetter" Path="Text" />
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.CellStyle>
In this example, the first letter is coming from the "Text" property of a control called "FirstLetter". You can change that binding to whatever you want.
Related
I have a data grid with value object in first name and last name
the value object compares 2 string values and return enum of equal ,not equal
<DataGridTextColumn Header="Pat. first name"
Binding="{Binding Path=PatFirstName}">
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell" >
<Setter Property="Background" Value="{Binding Path=PatFirstName.Result,Converter={StaticResource resultToColorConver}}"/>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="Pat. last name"
Binding="{Binding Path=PatLastName}">
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell" >
<Setter Property="Background" Value="{Binding Path=PatLastName.Result,Converter={StaticResource resultToColorConver}}"/>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
and using this convertor
public class ResultToColorConvertor : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var result = (CompareResult)value;
if(result == CompareResult.NotEqual)
{
return Brushes.Yellow;
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
but when selecting rows - I get this result :
how can I return to the selected blue background behavior (line in Patient ID or NDC columns)
I tried Brushes.Transparent,DependencyProperty.UnsetValue and Binding.DoNothing;
I suggest you to define the style on the Textbox of the Cell, instead of the CellStyle itself:
<DataGridTextColumn.ElementStyle>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Background" Value="{Binding Path=PatFirstName.Result,Converter={StaticResource resultToColorConver}}"/>
</Style>
</DataGridTextColumn.ElementStyle>
Using a transparent background should then work in your converter.
I have a DataGrid that styles the background of rows based on if the row is listed as "to delete".
The rows change colour when loading into the table and when scrolling around, but do not immediately change.
I use "ctrl+del" to mark the selected rows for deletion.
How can I get the row to update on this keypress?
DataGrid Implementation
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<Trigger Property="ItemsControl.AlternationIndex" Value="0">
<Setter Property="Background" Value="WhiteSmoke" />
</Trigger>
<Trigger Property="ItemsControl.AlternationIndex" Value="1">
<Setter Property="Background" Value="LightGray" />
</Trigger>
<DataTrigger Value="True">
<DataTrigger.Binding>
<Binding>
<Binding.Converter>
<local:IsDeletedConverter/>
</Binding.Converter>
</Binding>
</DataTrigger.Binding>
<DataTrigger.Setters>
<Setter Property="Background" Value="OrangeRed"/>
</DataTrigger.Setters>
</DataTrigger>
</Style.Triggers>
<Setter Property="Background">
<Setter.Value>
<Binding Path="">
<Binding.Converter>
<local:ValueToBrushConverter/>
</Binding.Converter>
</Binding>
</Setter.Value>
</Setter>
</Style>
</DataGrid.RowStyle>
Converter
public class IsDeletedConverter : IValueConverter
{
public object Convert(object values, Type targetType, object parameter, CultureInfo culture)
{
if (values is ExpandoObject eo)
{
if ((Application.Current.MainWindow as MainWindow).LoadedTable.ToDelete.Contains((int)ExpandoUtils.GetExpandoProperty(eo, "id")))
return true;
}
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
I have a button inside ItemsControl (pagination kind control) I have a property named current_page in my view model.
I want to compare current_page value to Content of Button that is (1 / 2 / 3) and want to change the foreground color of a button.
Please have a look at the code.
My ViewModel
public PaginationModel pagination
{
get { return _pagination; }
set { _pagination = value; OnPropertyChanged("pagination"); }
}
My Pagination Model
public class PaginationModel: INotifyPropertyChanged {
private int _total_items;
public int total_items {
get {
return _total_items;
}
set {
_total_items = value;
OnPropertyChanged("total_items");
}
}
private int _items_per_page;
public int items_per_page {
get {
return _items_per_page;
}
set {
_items_per_page = value;
OnPropertyChanged("items_per_page");
}
}
private int _current_page;
public int current_page {
get {
return _current_page;
}
set {
if (value <= total_pages + 1 && value > 0) {
_current_page = value;
OnPropertyChanged("current_page");
}
}
}
private int _total_pages;
public int total_pages {
get {
return _total_pages;
}
set {
_total_pages = value;
OnPropertyChanged("total_pages");
}
}
private ObservableCollection < string > _PageList;
public ObservableCollection < string > PageList {
get {
_PageList = new ObservableCollection < string > ();
for (int i = 0; i < total_pages; i++) {
_PageList.Add((i + 1).ToString());
}
return _PageList;
}
set {
_PageList = value;
OnPropertyChanged("PageList");
}
}
}
My Layout
<ItemsControl ItemsSource="{Binding pagination.PageList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="0">
<Button Content="{Binding}" CommandParameter="{Binding RelativeSource={RelativeSource Self}}"
Command="{Binding DataContext.ChangePage, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"
Width="20"
Margin="10,0"></Button>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
Button Style Template
<Style TargetType="{x:Type Button}" >
<Setter Property="Foreground" Value="{StaticResource WordOrangeBrush}" />
<Setter Property="Background" Value="Transparent"></Setter>
<Setter Property="BorderThickness" Value="0"></Setter>
<Setter Property="FontFamily" Value="{StaticResource HelveticaNeue}"></Setter>
<Setter Property="FontSize" Value="14"></Setter>
<Setter Property="Foreground" Value="#ff9f00"></Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Padding="{TemplateBinding Padding}" Background="{TemplateBinding Background}">
<ContentPresenter VerticalAlignment="Center" HorizontalAlignment="Center" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="Transparent"></Setter>
<Setter Property="Foreground" Value="#ff9f00" />
</Trigger>
<Trigger Property="IsMouseOver" Value="False">
<Setter Property="Background" Value="Transparent"></Setter>
<Setter Property="Foreground" Value="White" />
</Trigger>
<!--I want to bind current page value in place of 1-->
<DataTrigger Binding="{Binding}" Value="1">
<Setter Property="Foreground" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
A Value Converter Solution
You cannot bind the Value of a DataTrigger to a property, because it is not a dependency property. You can work around this by creating a custom multi-value converter that checks pages for equality.
public class PageEqualityToBooleanConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return (int)values[0] == System.Convert.ToInt32(values[1]);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new InvalidOperationException();
}
}
The first value passed to the converter is the current page as int and the second value is the page on the Button as string. It is a bit odd that your current_page is of type int, but the PageList is a collection of type string, that is why we need to convert the second value.
Create an instance of the converter in your resources before your style, so you can reference it.
<local:PageEqualityToBooleanConverter x:Key="PageEqualityToBooleanConverter"/>
Finally, replace your DataTrigger with the one below. We are using a MultiBinding to be able to bind more than one value. The converter will convert both values to True, if the page values match, otherwise False.
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource PageEqualityToBooleanConverter}">
<Binding Path="DataContext.pagination.current_page" RelativeSource="{RelativeSource AncestorType={x:Type ItemsControl}}"/>
<Binding/>
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Foreground" Value="Red"/>
</DataTrigger>
The first binding will find the parent ItemsControl to access the view model that contains the current_page. The second binding will bind to the data context of the associated button, which is the page string. If you change the PageList item type to int this will still work.
Recommendations
I want to point out some observations on your code that might help you to improve it.
As I already mentioned, it seems odd that your collection item type differs from your current item property type. Consider making it int or creating a separate page item view model if you plan on having other properties than the page number in it.
You are defining an implicit style for Button, which gets applied to all Button controls in scope. If you use it in your pagination control only this might be ok, but if you intend to use it in other places, the DataTrigger should not be included in it, as it is specific to this data context only. Create separate style based on this one. Consider #BionicCode's comment about this.
As #Andy pointed out in the comments, a ListBox could be a better fit for a paginator, because it has the notion of a SelectedItem (and IsSelected properties on its item containers that can be bound in a DataTrigger), which is what you are trying to do here manually.
The Value property of a DataTrigger cannot be data-bound.
What you should do is to create a type that represents a Page, add a Number and IsCurrentPage property to it and change the type of PageList to be an ObservableCollection<Page> instead of an ObservableCollection<string>.
You could then simply look up the corresponding Page in the PageList and set its IsCurrentPage property from the view model whenever the current_page property is set.
You should also make sure that the getter of the PageList property doesn't create a new collection each time it's invoked.
i want to change style when toggle button checked
<ToggleButton.Style>
<Style TargetType="ToggleButton" BasedOn="{StaticResource ToggleButtonPrimary}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=ButtonNude, Path=IsChecked}" Value="True">
<Setter Property="Style" Value="{StaticResource ToggleButtonDanger}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ToggleButton.Style>
but my code not work and app crash
As suggested by #clemens , the right way would be to have template and apply in controlTemplate with TargetName
There is one more (may be ugly) way:
On view:
<StackPanel>
<StackPanel.Resources>
<local:Myconverter x:Key="MyConverter" />
<Style TargetType="ToggleButton" x:Key="ToggleButtonPrimary">
<Setter Property="Background" Value="blue" />
</Style>
<Style TargetType="ToggleButton" x:Key="ToggleButtonDanger">
<Setter Property="Background" Value="Red" />
</Style>
</StackPanel.Resources>
<CheckBox Margin="20" x:Name="chk" />
<ToggleButton Width="100" Height="100" >
<ToggleButton.Style>
<MultiBinding Converter="{StaticResource MyConverter}">
<MultiBinding.Bindings>
<Binding ElementName="chk" Path="IsChecked"/>
<Binding RelativeSource="{RelativeSource AncestorType=StackPanel}"/>
</MultiBinding.Bindings>
</MultiBinding>
</ToggleButton.Style>
</ToggleButton>
</StackPanel>
Converter:
public class Myconverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if ((bool)values[0])
{
return (values[1] as FrameworkElement).Resources["ToggleButtonDanger"];
}
else
return (values[1] as FrameworkElement).Resources["ToggleButtonPrimary"];
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Hope that helps.
If any possible to give condition on DataTrigger?
<DataTrigger Binding="{Binding MessageBoxImage}" Value="{x:Static MessageBoxImage.Error}">
<Setter Property="Source" Value="../Images/Error48.png"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding MessageBoxImage}" Value="{x:Static MessageBoxImage.Hand}">
<Setter Property="Source" Value="../Images/Error48.png"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding MessageBoxImage}" Value="{x:Static MessageBoxImage.Stop}">
<Setter Property="Source" Value="../Images/Error48.png"></Setter>
</DataTrigger>
So, this is my Xaml code, in that Error,Hand,Stop all are setting same image
My question is any possible to give OR condition for these three values? (or one line statement)
Thanks,
You can use MultiDataTrigger for AND condition. As for OR condition you can use converter.
<Window.Resources>
<someNs:ORConverter x:Key = "ORConverter"/>
</Window.Resources>
....
<DataTrigger Value="True">
<DataTrigger.Binding>
<Binding Path="MessageBoxImage" Converter="{StaticResource ORConverter}">
<Binding.ConverterParameter>
<x:Array Type="MessageBoxImage">
<x:Static MemberType="MessageBoxImage" Member="Error" />
<x:Static MemberType="MessageBoxImage" Member="Information" />
<x:Static MemberType="MessageBoxImage" Member="Question" />
</x:Array>
</Binding.ConverterParameter>
</Binding>
</DataTrigger.Binding>
<Setter Property="Source" Value="../Images/Error48.png"></Setter>
</DataTrigger>
And the converter's code:
public class ORConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
var list = parameter as IList;
if (list == null)
return false;
foreach (var o in list)
{
if (Equals(o, value))
return true;
}
return false;
}
public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException("Cannot convert back");
}
}
With the simple DataTrigger is meant to check for a single values. if Possible you could use the Multi-DataTrigger to check the multiple conditions.