WPF Toolkit DataGridCell Style DataTrigger - c#

I am trying to change the color of a cell to Yellow if the value has been updated in the DataGrid.
My XAML:
<toolkit:DataGrid x:Name="TheGrid"
ItemsSource="{Binding}"
IsReadOnly="False"
CanUserAddRows="False"
CanUserResizeRows="False"
AutoGenerateColumns="False"
CanUserSortColumns="False"
SelectionUnit="CellOrRowHeader"
EnableColumnVirtualization="True"
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto">
<toolkit:DataGrid.CellStyle>
<Style TargetType="{x:Type toolkit:DataGridCell}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsDirty}" Value="True">
<Setter Property="Background" Value="Yellow"/>
</DataTrigger>
</Style.Triggers>
</Style>
</toolkit:DataGrid.CellStyle>
</toolkit:DataGrid>
The grid is bound to a List of arrays (displaying a table of values kind of like excel would). Each value in the array is a custom object that contains an IsDirty dependency property. The IsDirty property gets set when the value is changed.
When i run this:
change a value in column 1 = whole row goes yellow
change a value in any other column = nothing happens
I want only the changed cell to go yellow no matter what column its in. Do you see anything wrong with my XAML?

The reason this happens is because DataContext is set at row level and doesn't change for each DataGridCell. So when you bind to IsDirty it binds to the property of row-level data object, not cell-level one.
Since your example shows that you have AutoGenerateColumns set to false, I assume that you generate columns yourself have something like DataGridTextColumn with Binding property set to binding to actual value field. To get cell style changed to yellow you'd have to change CellStyle on each DataGridColumn like this:
foreach (var column in columns)
{
var dataColumn =
new DataGridTextColumn
{
Header = column.Caption,
Binding = new Binding(column.FieldName),
CellStyle =
new Style
{
TargetType = typeof (DataGridCell),
Triggers =
{
new DataTrigger
{
Binding = new Binding(column.FieldName + ".IsDirty"),
Setters =
{
new Setter
{
Property = Control.BackgroundProperty,
Value = Brushes.Yellow,
}
}
}
}
}
};
_dataGrid.Columns.Add(dataColumn);
}
You can experiment with changing DataContext of each cell using DataGridColumn.CellStyle. Perhaps only then you'll be able to bind cell's to 'IsDirty' directly from grid-level style like you do without doing it for each column individually. But I don't have actual data model you have to test this.

Related

C# WPF DataGrid Header Style Not Updating after On Click event handling

I have a C# WPF project using a DataGrid to display a DataTable.
The DataTable was populated by reading in a CSV file (unknown number of headers) and populating it using the CSVHelper nuget package.
using (FileStream? stream = fileInfo.OpenRead())
{
using (StreamReader? reader = new(stream))
using (CsvReader? csv = new(reader, csvConfiguration))
{
using CsvDataReader? dr = new(csv);
dataTable.Load(dr);
}
}
I am displaying this DataTable using the following XAML setup.
<DataGrid x:Name="DataGrid"
ItemsSource="{Binding Data, UpdateSourceTrigger=PropertyChanged}"
HorizontalScrollBarVisibility="Visible"
VerticalScrollBarVisibility="Visible"
AlternatingRowBackground="AliceBlue"
AlternationCount="2"
CanUserSortColumns="False"
AutoGeneratingColumn="Data_AutoGeneratingColumn"
IsReadOnly="True"
CanUserReorderColumns="False"
Loaded="Data_Loaded"
>
<DataGrid.ColumnHeaderStyle>
<Style BasedOn="{StaticResource {x:Type DataGridColumnHeader}}" TargetType="DataGridColumnHeader">
<EventSetter Event="Click" Handler="DataGridColumnHeader_Click"/>
</Style>
</DataGrid.ColumnHeaderStyle>
</DataGrid>
I have added code to the Loaded Event for the DataGrid that cycles through all the DataGridColumn entries from DataGrid.Columns and will set their HeaderStyle depending on if they exist in a List<string> to either:
SelectedHeaderStyle
UnSelectedHeaderStyle
foreach (DataGridColumn column in grid.Columns)
{
string headerText = column.Header.ToString();
if (cSVConfigureContext.SelectedColumns.Contains(headerText))
{
column.HeaderStyle = selectedHeaderStyle;
}
else
{
column.HeaderStyle = notSelectedHeaderStyle;
}
}
I am also handling the Click event for TargetType DataGridColumnHeader which will toggle the HeaderStyle between 1 and 2.
private void DataGridColumnHeader_Click(object sender, RoutedEventArgs e)
{
var columnHeader = sender as DataGridColumnHeader;
if (columnHeader != null)
{
string headerText = columnHeader.Content.ToString();
if (cSVConfigureContext.SelectedColumns.Contains(headerText))
{
columnHeader.Style = notSelectedHeaderStyle;
cSVConfigureContext.SelectedColumns.Remove(headerText);
}
else
{
columnHeader.Style = selectedHeaderStyle;
cSVConfigureContext.SelectedColumns.Add(headerText);
}
}
e.Handled = true;
}
This part all seems to work.
However, adding a "Reset Headers" button to return to a default set of selected columns, is where things are not working.
If I load the window with the DataGrid and press the Reset button (which runs the same code used in the Loaded event, it works fine. But if I first click on any header to switch its state, the style on the column header does not change. I see the change in the SelectedColumns list of strings but no visual update in the DataGrid until I close that window and re-open a new DataGrid. But I can still click headers to change their state.
I've spent a couple hours googling options for forcing a redraw of the column headers but so far no luck.
Any ideas why the Reset button doesn't work after a Click event?
Any ideas why the Reset button doesn't work after a Click event?
The Style of the DataGridColumnHeader apparently overrides or take precedence over the HeaderStyle of the DataGrid.
Setting the Style property of all DataGridColumnHeader elements to null before setting the HeaderStyle property of the DataGrid will probably fix your issue.

Adding Tooltip to DataGrid Cells programmatically

i made different approaches to add tooltips to the cells of a DataGrid. I also found several information on this site, but i didn't make it work.
Here's what's the matter and what I've tried:
I have a DataGrid like:
DataGrid grid = new DataGrid();
Binding b = new Binding() { Mode = BindingMode.TwoWay, UpdateSourceTrigger = UpdateSourceTrigger.Default, Source = AnObersableCollection, NotifyOnSourceUpdated = true, Path = new PropertyPath(".") } );
grid.SetBinding(DataGrid.ItemsSourceProperty, b);
I'd like that every cell has a tooltip with the cell content as tooltip content, so that truncated text is seen in the tooltip. So i took the CellStyles and created one like this:
Style CellStyle_ToolTip = new Style();
CellStyle_ToolTip.Setters.Add(new Setter(DataGridCell.ToolTipProperty, new ToolTip() { Content = "Yeah!" } ));
That works for static ToolTip contents, but how can I achieve that the ToolTip has the displayed cell content as content?
I found out that
CellStyle_ToolTip.Setters.Add(new Setter(DataGridCell.ToolTipProperty, new ToolTip().SetBinding(ToolTip.ContentProperty, b) ));
does not work and produces an "Cannot set expression. It is marked as "NonShareable" and has already been used"-Error, which makes quite sense as the Binding is already in use. I came to this approach (which probably was complete nonsense) through several other discussions that all use xaml, which is not an option for me. I also found the following solution but don't know how to use without xaml.
<DataGrid.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=Content.Text}" />
</Style>
</DataGrid.CellStyle>
PS: All columns are DataGridTextColumns, except for one DataGridComboBoxColumn.
Using the CellStyle Property you could do:
Style CellStyle_ToolTip = new Style();
var CellSetter = new Setter(DataGridCell.ToolTipProperty, new Binding() {RelativeSource=new RelativeSource(RelativeSourceMode.Self), Path=new PropertyPath("Content.Text")});
CellStyle_ToolTip.Setters.Add(CellSetter);
grid.CellStyle = CellStyle_ToolTip;

Remove the default style of selected row

I know that question was ask a lot, but i only saw it in XAML code file.
I am working on a datagrid extension, so I am in C# code file and I would like to know how to remove the default style for selected row (In my case i want nothing change in the style, I have an image in row header that show wich row is selected).
Side question, can we have a selection like "Ctrl" is pressed and how in C# code?
Thanks
Edit
I try this code :
Style oRow = new Style(typeof(DataGridRow));
DataTrigger oTrigger2 = new DataTrigger();
Binding oBind = new Binding();
oBind.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(DataGridRow), 1);
oBind.Path = new PropertyPath(DataGridRow.IsSelectedProperty);
oTrigger2.Binding = oBind;
oTrigger2.Value = true;
oTrigger2.Setters.Add(new Setter(DataGridRow.BackgroundProperty, Brushes.Khaki));
oRow.Triggers.Add(oTrigger2);
this.RowStyle = oRow;
For now, i tryed to put the selected background in Khaki for test. But i get the old blue highlight.
Edit 2
Base on the idea of Sinatr, I change the DatagridRow for DatagridCell and ended with :
Style oRow = new Style(typeof(DataGridCell));
DataTrigger oTrigger2 = new DataTrigger();
Binding oBind = new Binding();
oBind.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(DataGridRow), 1);
oBind.Path = new PropertyPath(DataGridRow.IsSelectedProperty);
oTrigger2.Binding = oBind;
oTrigger2.Value = true;
oTrigger2.Setters.Add(new Setter(DataGridCell.BackgroundProperty, null));
oTrigger2.Setters.Add(new Setter(DataGridCell.BorderBrushProperty, null));
oRow.Triggers.Add(oTrigger2);
this.RowStyle = oRow;
I only need to get the foreground of the row to set the foreground of the cell. But i got a new question with that solution, is ok to set background to null or i should bind it to thr row background?
You have to replace CellStyle control template.
To example, this stupid templating
<DataGrid x:Name="dataGrid">
<DataGrid.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="DataGridCell">
<TextBlock Text="1" Background="Khaki" Foreground="Red"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGrid.CellStyle>
</DataGrid>
will produce this output (for just 1 Tuple<string, string, string>, whatever):
It stays the same for selected or not selected item.
Obviously you have to implement it more properly, but given answer should give you an idea.

When creating a Datagrid programatically, how to add a column with a textbox with wrapping?

I am creating a DataGrid programatically and I want to add a column that has a TextBox that has word-wrap enabled. I am able to do it in the XAML with the following:
<DataGridTextColumn.ElementStyle>
<Style>
<Setter Property="TextBlock.TextWrapping" Value="Wrap" />
</Style>
</DataGridTextColumn.ElementStyle>
But I can't see to figure out how to replicate this when creating the databases dynamically, this is my code so far (which doesn't compile at the moment):
var datagrid = new DataGrid();
datagrid.Columns.Add(new DataGridTemplateColumn()
{
Header = "Type",
Width = new DataGridLength(200),
// here I want to set FONT = 12 and
// TextBlock.TextWrappingProperty = TextWrapping.Wrap
}
Any clues?

Binding DataGridTextColumn color from C# codebehind

Similar to WPF - help converting XAML binding expression to codebehind
I am attempting to use Binding to change the color of certain elements in a DataGridTextColumn. Because I need an arbitrary number of DataGrids in separate tabs, I am creating them iteratively in the codebehind. Here is my code for creating the column:
// create a value column
column = new DataGridTextColumn();
column.Binding = new Binding("Value");
BindingOperations.SetBinding(column, DataGridTextColumn.ForegroundProperty, new Binding("TextColor"));
listGrid.Columns.Add(column);
The Value binding works fine, but the TextColor property's getter never gets called.
The grid's ItemsSource property is set to a list of VariableWatcher objects, and here are some of its properties:
public bool Value
{
get { return _variable.Value; }
}
// used to set DataGridTextColumn.Foreground
public Brush TextColor
{
get
{
Color brushColor;
if (_valueChanged)
brushColor = Color.FromRgb(255, 0, 0);
else
brushColor = Color.FromRgb(0, 0, 0);
return new SolidColorBrush(brushColor);
}
}
VariableWatcher implements INotifyPropertyChanged as follows:
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
In one of VariableWatcher's methods, I have the following lines:
_valueChanged = true;
NotifyPropertyChanged("Value");
NotifyPropertyChanged("TextColor");
Stepping over the "Value" line activates a breakpoint in the Value getter, and the Value text is updated in the display. However, stepping over the "TextColor" line does NOT activate the breakpoint in the TextColor getter, and the text color does not change. Any idea what's going on here?
EDIT: Here is the answer, thanks to Damascus. (I would have put this in a comment on his answer, but it wouldn't format my code properly.) I added this to the XAML file:
<Window.Resources>
<Style x:Key="BoundColorStyle" TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="{Binding TextColor}" />
</Style>
</Window.Resources>
and in the C# code, I replaced the BindingOperations line with
column.ElementStyle = this.FindResource("BoundColorStyle") as Style;
Workaround for this:
Create a style in your resources, looking like that:
<Style x:Key="MyStyle" TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="{Binding TextColor}" />
</Style>
And set this style in the ElementStyle property of your DataGridColumn, should be something like that in your code:
column = new DataGridTextColumn();
column.Style = this.FindResource("MyStyle") as Style;
The reason for it being that ElementStyle directly works in the column's Content (ie. the TextBlock displaying a value)
It can be achieved through code behind as well without style.
The important point is to set binding source explicitly (look in below code) for new binding expression that is set for target property.
In this example, DataGrid generates dynamic columns and is bounded with an object of ColloectionViewSource (i.e cycleDataview) and the source of cycleDataview is an object of ObservableCollection that is cycleRecord.
// Create view source
this.cycleDataview = new CollectionViewSource();
this.cycleDataview.Source = this.cycleRecords;
// Set Item Source to data grid
this.DataGridCycleData.ItemsSource = this.cycleDataview.View;
// Generate Columns for datagrid
var columns = this.cycleRecords.First().CyclePartCols.Select((x, i) => new {PreDescriptor = x.PreDescriptor, Index = i }).ToArray();
foreach (var column in columns)
{
Binding binding = new Binding(string.Format("CyclePartCols[{0}].PartValue", column.Index));
Binding bindingColor = new Binding(string.Format("CyclePartCols[{0}].TextColor", column.Index));
**bindingColor.Source = this.cycleRecords;** // Binding source is required to set
DataGridTextColumn dgc = new DataGridTextColumn();
txtblckCol.Text = column.PreDescriptor;
dgc.Header = txtblckCol;
dgc.Binding = binding;
this.DataGridCycleData.Columns.Add(dgc);
BindingOperations.SetBinding(dgc, DataGridTextColumn.ForegroundProperty, bindingColor);
}
Use the workaround from damascus but only with code behind:
var myStyle = new Style
{
TargetType = typeof(TextBlock)
};
textColumnStyleExt.Setters.Add(new Setter(TextBlock.ForegroundProperty, new Binding("TextColor"));
column = new DataGridTextColumn();
column.Binding = new Binding("Value");
column.ElementStyle = myStyle;

Categories