Get datagridcell by value - c#

I have a datagrid, which is bound to a datatable, each cell has a unique value, so no two cells have the same value.
I want to change the cell with the value 1 (int32) to the color green. Note, the value of 1 is dynamic, thats just an example, it could be between 1-90.
I have searched around, and most of the help gives you the value of either a cell based on its coordinates i.e. (4,2) or the selected cell. This isn't what I want, I want to change the color of a cell based on its value.
Is there a way to do this, for example in JavaScript i would simply assign each cell an id equivalent to its value and then something like $('#' + 1).css('background-color:green;') (note: this might not be correct syntax, but you get the picture). Is there a way as simple as this or a standard way of doing this?
My datagrid
<DataGrid Name="grid" ItemsSource="{Binding}" Height="300" Width="900"
AutoGenerateColumns="True"
VerticalScrollBarVisibility="Disabled" HorizontalAlignment="Center" VerticalAlignment="Top" RowHeight="40">
<DataGrid.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</DataGrid.ItemsPanel>
</DataGrid>
Table creation
DataSet dataSet = new DataSet("myDS");
DataTable numbersTable = new DataTable("Numbers");
numbersTable.Columns.Add("Number", typeof(Int32));
for (int i = 1; i < 91; i++)
{
numbersTable.Rows.Add(i);
}
dataSet.Tables.Add(numbersTable);
grid.DataContext = numbersTable.DefaultView;

Please take a look on this Change DataGrid cell colour based on values
Public class NameToBrushConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string input = value as string;
switch (input)
{
case "John":
return Brushes.LightGreen;
default:
return DependencyProperty.UnsetValue;
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
}
Grid:
<DataGridTextColumn Binding="{Binding Name}">
<DataGridTextColumn.ElementStyle>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Background" Value="{Binding Name, Converter={StaticResource NameToBrushConverter}}"/>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
Brushs:
public string Name
{
get { return _name; }
set
{
if (_name != value)
{
_name = value;
OnPropertyChanged("Name");
OnPropertyChanged("NameBrush");
}
}
}
public Brush NameBrush
{
get
{
switch (Name)
{
case "John":
return Brushes.LightGreen;
}
return Brushes.Transparent;
}
}

There are a few ways that you can achieve your requirements. It's a little bit inconvenient because we have to apply a Style with a Trigger on the columns of the DataGrid rather than the DataGrid itself. This means that you can't use the AutoGenerateColumns feature and you'll have to define them all manually as I have below. Try this:
<DataGrid ItemsSource="{Binding YourItems}" AutoGenerateColumns="False">
<DataGrid.Resources>
<Style x:Key="BackgroundColourStyle" TargetType="{x:Type TextBlock}">
<Style.Triggers>
<Trigger Property="Text" Value="1">
<Setter Property="Background" Value="LightGreen" />
</Trigger>
</Style.Triggers>
</Style>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding YourPropertyName}"
ElementStyle="{StaticResource BackgroundColourStyle}">
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
UPDATE >>>
Ok, so to do this with a variable value, you'd be better off doing this the WPF data-centric way. As usual in WPF, we want to create data objects with all of the properties that we need to display in the UI. As such, you'd need to add a new bool property into whatever data type class that you are displaying in the DataGrid... maybe add a new column for that if you insist on using a DataTable.
However, I'd advise you to use a class and if you create one, you must ensure that you implement the INotifyPropertyChanged interface correctly in it. You could add properties into it like this:
public int NumberValue { get; set; } // implement `INotifyPropertyChanged` here
public bool HasHighlightValue { get; set; } // implement `INotifyPropertyChanged` here
Then we could use this property to highlight the relevant cell in the DataGrid:
<Style x:Key="BackgroundColourStyle" TargetType="{x:Type TextBlock}">
<Style.Triggers>
<Trigger Property="HasHighlightValue" Value="True">
<Setter Property="Background" Value="LightGreen" />
</Trigger>
</Style.Triggers>
</Style>
Finally, you can set this new property in a handler or code behind in response to some user action:
// reset previous cell value
YourDataType previousItem = YourItems.Where(I => i.HasHighlightValue).Single();
previousItem.HasHighlightValue = false;
// set new cell value
YourDataType item = YourItems.Where(I => i.NumberValue == relevantNumber).Single();
item.HasHighlightValue = true;

There is also following way which you can use to dynamically format DataGridCell whenever its value changes by using Binding.TargetUpdated or Binding.SourceUpdated event on DataGridCell.
To do this, you must do following:
Add event handler for AutoGeneratingColumn event on DataGrid.
For example:
<DataGrid ItemsSource="{Binding}" AutoGeneratingColumn="OnAutoGeneratingColumn"/>
In AutoGeneratingColumn handler identify if auto-generated column has Binding and if so, set NotifyOnTargetUpdated on it to true and set CellStyle on column which will include EventSetter for Binding.TargetUpdatedEvent event. For example:
void OnAutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
BindingBase bindingBase = null;
var dataGridBoundColumn = e.Column as DataGridBoundColumn;
if (dataGridBoundColumn != null)
{
bindingBase = dataGridBoundColumn.Binding;
}
else
{
var dataGridComboBoxColumn = e.Column as DataGridComboBoxColumn;
if (dataGridComboBoxColumn != null)
bindingBase = dataGridComboBoxColumn.SelectedItemBinding;
}
var binding = bindingBase as Binding;
if (binding != null)
{
binding.NotifyOnTargetUpdated = true;
e.Column.CellStyle = new Style(typeof(DataGridCell))
{
Setters =
{
new EventSetter(Binding.TargetUpdatedEvent, new EventHandler<DataTransferEventArgs>(OnDataGridCellBindingTargetUpdated))
}
};
}
}
Implement your custom logic for formatting DataGridCell when value changes in OnDataGridCellBindingTargetUpdated handler. For example:
private static void OnDataGridCellBindingTargetUpdated(object sender, DataTransferEventArgs e)
{
var dataGridCell = (DataGridCell)sender;
// Get context: column and item.
var column = dataGridCell.Column;
var item = dataGridCell.DataContext;
// TODO: based on context, format DataGridCell instance.
}

Related

Changing specific wpf datagrid row color using c#

I have a requirement to change color of specific data grid row at run time
I am setting row background color inside Loading Row event of data grid
private void MessagesDataGrid_LoadingRow(objects , DataGridRowEventArgs e)
{
var v = e.Row.Item.ToString();
int i = e.Row.GetIndex();
if (IoStatusViewModel.HighlightSelected == true )
{
e.Row.Focusable = true;
e.Row.Background = Brushes.Red;
if (v.Contains("MCP :"))
{
DisplayLogs = IoStatusViewModel.ChangeMcpLog(v);
e.Row.Item = DisplayLogs;
}
}
else
{
if (v.Contains("MCP :"))
{
DisplayLogs = IoStatusViewModel.ChangeMcpLog(v);
e.Row.Item = DisplayLogs;
}
}
}
This code is working fine as data grid loads but after some times the color of each rows in data grid starts changing and as time passes whole grid becomes red
I would do it within the class object you are binding against in conjunction with a style for the grid. First, your data being presented in the grid. How/where is that coming from. Is it some sort of List<> or ObservableCollect<> of items. Example
var yourBoundProperty = new List<SomeClass>();
… populate however you do.
public class SomeClass
{
public string SomeProp {get; set;}
public string YourMCPField {get; set;}
// make a SPECIAL FIELD... could be boolean, number setting, whatever flag
// but in this case, I just have boolean
public bool FieldContainsMCP { get { return YourMCPFieldContains( "MCP :"); }}
}
Now, in your Xaml… assuming in a Window declaration.
<Window … >
<Window.Resources>
<Style TargetType="{x:Type DataGridCell}" x:Key="MyColorTriggers">
<Style.Triggers>
<DataTrigger Binding="{Binding FieldContainsMCP}" Value="True">
<Setter Property="Background" Value="Red" />
<Setter Property="ExampleAnyOtherProperty" Value="someOtherValue" />
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<DataGrid … other settings you have
CellStyle="{StaticResource MyColorTriggers}" >
.. rest of your data column declarations
</DataGrid>
</Window>
This way the actual data source is the flag basis which is applied to the CellStyle triggering regardless of where you may be scrolling through records.

Problems changing datagrid color using WPF C#

I am using a datagrid and changing the color of the rows according to their conditions and I am performing this programmatically.
follow the example
as my datagrid is bound to a datatable I load information straight from the datatable
private void UpdateCor () {
gvDados.UpdateLayout ();
for (int i = 0; i <dt.Rows.Count; i ++)
{
var rowContext = (DataGridRow)
gvDados.ItemContainerGenerator.ContainerFromIndex (i);
      if (rowContext! = null)
      {
            if (dt.Rows [i] ["situation"]. ToString (). Equals (1))
                     rowContext.Background = Brushes.Green;
            else
                     rowContext.Background = Brushes.Red;
      }
}
}
With this I can update the color of my grid even though it is not the best method to be approached. my problem is this, whenever I use the scroll to go down or up the bar the colors become outdated. How do I prevent this from happening? that even when I roll the bar the colors stay fixed?
This is a similar question to this question.
Can be done using datatrigger:
<DataGrid ItemsSource="{Binding YourItemsSource}">
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding State}" Value="State1">
<Setter Property="Background" Value="Red"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding State}" Value="State2">
<Setter Property="Background" Value="Green"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
</DataGrid>
Often, XAML is too simple to express more complicated conditions. I prefer to put the logic which values should use which colors into a converter. This leads to a simpler XAML and much greater flexibility for the converter in C#.
<datagrid.rowstyle>
<style targettype="DataGridRow">
<Setter Property="Background" Value="{Binding RelativeSource={RelativeSource Self},
Path=Item.situation, Converter={StaticResource ValueToBackgroundConverter}}"/>
</style>
</datagrid.rowstyle>
In C#:
class ValueToBackgroundConverter: IValueConverter {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
if (value is int) {
int quantity = (int)value;
if (quantity>=100) return Brushes.White;
if (quantity>=10) return Brushes.WhiteSmoke;
if (quantity>=0) return Brushes.LightGray;
return Brushes.White; //quantity should not be below 0
}
//value is not an integer. Do not throw an exception
// in the converter, but return something that is obviously wrong
return Brushes.Yellow;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
throw new NotImplementedException();
}
}
Formatting the various parts of a WPF Datagrid is notoriously difficult and Microsoft is not providing the necessary information how to do it. Read my article Codeproject: Guide to WPF DataGrid formatting using binding to get a better understanding how to do it easily.

get row content by field from DataGridRow

I have a DataGrid with several columns in my application.. I need to use different row foreground colour in case of some certain field content. I use following callback on LoadingRowevent:
void userGrid_LoadingRow(object sender, DataGridRowEventArgs e){
if (e.Row.Item != null){
// check some row's field value
...
// modify row forecolor
e.Row.Foreground = new SolidColorBrush(Colors.Red);
...
}
}
But how to get value of some certain row's field by it's name or index?
void userGrid_LoadingRow(object sender, DataGridRowEventArgs e)
{
var dataGrid = (DataGrid)sender;
IEnumrable<DataGridRow> rows = dataGrid.Items.Where(r => (r.DataContext as YourItemsSourceEntity).SomeProperty == yourCondition)
}
Alternatively i would add a condition to your ItemsSource .
public class YourItemsSourceEntity
{
public bool IsSomething { get; }
}
xaml :
<DataGrid ItemsSource="{Binding Items}">
<DataGrid.ItemContainerStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding IsSomething}" Value="True">
<Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.ItemContainerStyle>
</DataGrid>
as to the comment below : Is this what you ment ?
void userGrid_LoadingRow(object sender, DataGridRowEventArgs e)
{
var item = e.Row.DataContext as (YourItemsSourceEntity);
var id = item.ID ;
}

How to set background of a datagrid cell during AutoGeneratingColumn event depending on its value?

I'm still fighting with manipulation of cell backgrounds so I'm asking a new question.
A user "H.B." wrote that I can actually set the cell style during the AutoGeneratingColumn event - Change DataGrid cell colour based on values. The problem is that I'm not sure how to do it.
What I want:
Set different background colours for each cell depending on its value. If the value is null I also want it not to be clickable (focusable I guess).
What I have / I'm trying to do:
private void mydatagrid_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
foreach (Cell cell in e.Column)
{
if (cell.Value < 1)
{
cell.Background = Color.Black;
cell.isFocusable = false;
}
else
{
cell.Background = Color.Pink;
}
}
}
This is just the pseudocode. Is something like this is possible during column auto-generation and if so, how can I edit my code so it will be valid?
I read about value convertors but I want to know if it's somehow possible programmatically, without writing XAML.
Please understand that I'm still a beginner to C#/WPF/DataGrid.
Solution part1:
I used the answer I accepted. Just put it into
<Window.Resources>
<local:ValueColorConverter x:Key="colorConverter"/>
<Style x:Key="DataGridCellStyle1" TargetType="{x:Type DataGridCell}">
<Setter Property="Padding" Value="5"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridCell}">
<Border Padding="{TemplateBinding Padding}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" SnapsToDevicePixels="True">
<Border.Background>
<MultiBinding Converter="{StaticResource colorConverter}">
<Binding RelativeSource="{RelativeSource AncestorType=DataGridCell}" Path="Content.Text"/>
<Binding RelativeSource="{RelativeSource AncestorType=DataGridCell}" Path="IsSelected"/>
</MultiBinding>
</Border.Background>
<ContentPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
And made for it a MultiBinding convertor so I can also set the background color for selected cells.
Problem:
Now I only have to solve the problem of setting focus of empty cells. Any hints?
<Style.Triggers>
<Trigger Property="HasContent" Value="False">
<Setter Property="Focusable" Value="False"/>
</Trigger>
</Style.Triggers>
This doesn't work. I had empty strings in the empty cells, but they are filled with ´null´s so it should work, right? Or what am I doing wrong :| ?
Solution part 2:
So the code above won't work as long as the cell value is a ´TextBox´ so I decided to find another way to deal with it which can be found in my answer here: https://stackoverflow.com/a/16673602/2296407
Thanks for trying to help me :)
I can propose two different solutions for your question: the first is "code-behind-style" (which you are asking for but personally I think it is not right approach in WPF) and more WPF-style (which more tricky but keeps code-behind clean and utilizes styles, triggers and converters)
Solution 1. Event handling and code-behind logic for coloring
First of all, the approach you've chosen will not work directly - the AutoGeneratingColumn event is meant to be used for altering the entire column appearance, not on the cell-by-cell basis. So it can be used for, say, attaching the correct style to entire column basing on it's display index or bound property.
Generally speaking, for the first time the event is raised your datagrid will not have any rows (and consequently - cells) at all. If you really need to catch the event - consider your DataGrid.LoadingRow event instead. And you will not be able to get the cells that easy :)
So, what you do: handle the LoadingRow event, get the row (it has the Item property which holds (surprisingly :)) your bound item), get the bound item, make all needed calculations, get the cell you need to alter and finally alter the style of the target cell.
Here is the code (as item I use a sample object with the int "Value" property that I use for coloring).
XAML
<DataGrid Name="mygrid" ItemsSource="{Binding Items}" AutoGenerateColumns="True" LoadingRow="DataGrid_OnLoadingRow"/>
.CS
private void DataGrid_OnLoadingRow(object sender, DataGridRowEventArgs e)
{
Dispatcher.BeginInvoke(DispatcherPriority.Render, new Action(() => AlterRow(e)));
}
private void AlterRow(DataGridRowEventArgs e)
{
var cell = GetCell(mygrid, e.Row, 1);
if (cell == null) return;
var item = e.Row.Item as SampleObject;
if (item == null) return;
var value = item.Value;
if (value <= 1) cell.Background = Brushes.Red;
else if (value <= 2) cell.Background = Brushes.Yellow;
else cell.Background = Brushes.Green;
}
public static DataGridRow GetRow(DataGrid grid, int index)
{
var row = grid.ItemContainerGenerator.ContainerFromIndex(index) as DataGridRow;
if (row == null)
{
// may be virtualized, bring into view and try again
grid.ScrollIntoView(grid.Items[index]);
row = (DataGridRow)grid.ItemContainerGenerator.ContainerFromIndex(index);
}
return row;
}
public static T GetVisualChild<T>(Visual parent) where T : Visual
{
T child = default(T);
int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < numVisuals; i++)
{
var v = (Visual)VisualTreeHelper.GetChild(parent, i);
child = v as T ?? GetVisualChild<T>(v);
if (child != null)
{
break;
}
}
return child;
}
public static DataGridCell GetCell(DataGrid host, DataGridRow row, int columnIndex)
{
if (row == null) return null;
var presenter = GetVisualChild<DataGridCellsPresenter>(row);
if (presenter == null) return null;
// try to get the cell but it may possibly be virtualized
var cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(columnIndex);
if (cell == null)
{
// now try to bring into view and retreive the cell
host.ScrollIntoView(row, host.Columns[columnIndex]);
cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(columnIndex);
}
return cell;
}
Solution 2. WPF-style
This solution uses code-behind only for value-to-color convertions (assuming that that logic of coloring is more complex than equality comparison - in that case you can use triggers and do not mess with converters).
What you do: set DataGrid.CellStyle property with style that contains a data trigger, which checks if the cell is within a desired column (basing on it's DisplayIndex) and if it is - applies background through a converter.
XAML
<DataGrid Name="mygrid" ItemsSource="{Binding Items}" AutoGenerateColumns="True">
<DataGrid.Resources>
<local:ValueColorConverter x:Key="colorconverter"/>
</DataGrid.Resources>
<DataGrid.CellStyle>
<Style TargetType="DataGridCell">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Column.DisplayIndex}" Value="1">
<Setter Property="Background" Value="{Binding RelativeSource={RelativeSource Self}, Path=Content.Text, Converter={StaticResource colorconverter}}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.CellStyle>
</DataGrid>
.CS
public class ValueColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var str = value as string;
if (str == null) return null;
int intValue;
if (!int.TryParse(str, out intValue)) return null;
if (intValue <= 1) return Brushes.Red;
else if (intValue <= 2) return Brushes.Yellow;
else return Brushes.Green;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
UPD: If you need to color entire datagrid, XAML is much easier (no need to use triggers). Use the following CellStyle:
<DataGrid.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="Background" Value="{Binding RelativeSource={RelativeSource Self}, Path=Content.Text, Converter={StaticResource colorconverter}}"/>
</Style>
</DataGrid.CellStyle>
What i meant is that you can set the CellStyle property of the column, you can not manipulate cells directly as they are not available in this event. The style can contain your conditional logic in the form of DataTriggers (will need a converter as you have "less-than" and not equals) and Setters.
Also if the logic is not specific to the columns you can set the style globally on the grid itself. The point of using the event would be to manipulate the column properties which you can not access otherwise.
I am not sure whether this property (Cell.Style) is available in your WPF Datagrid. Probably some alternative exists in your case. It has worked for WinForms datagrid.
cell.Style.BackColor = System.Drawing.Color.Black;

Single click edit in WPF DataGrid

I want the user to be able to put the cell into editing mode and highlight the row the cell is contained in with a single click. By default, this is double click.
How do I override or implement this?
Here is how I resolved this issue:
<DataGrid DataGridCell.Selected="DataGridCell_Selected"
ItemsSource="{Binding Source={StaticResource itemView}}">
<DataGrid.Columns>
<DataGridTextColumn Header="Nom" Binding="{Binding Path=Name}"/>
<DataGridTextColumn Header="Age" Binding="{Binding Path=Age}"/>
</DataGrid.Columns>
</DataGrid>
This DataGrid is bound to a CollectionViewSource (Containing dummy Person objects).
The magic happens there : DataGridCell.Selected="DataGridCell_Selected".
I simply hook the Selected Event of the DataGrid cell, and call BeginEdit() on the DataGrid.
Here is the code behind for the event handler :
private void DataGridCell_Selected(object sender, RoutedEventArgs e)
{
// Lookup for the source to be DataGridCell
if (e.OriginalSource.GetType() == typeof(DataGridCell))
{
// Starts the Edit on the row;
DataGrid grd = (DataGrid)sender;
grd.BeginEdit(e);
}
}
The answer from Micael Bergeron was a good start for me to find a solution thats working for me. To allow single-click editing also for Cells in the same row thats already in edit mode i had to adjust it a bit. Using SelectionUnit Cell was no option for me.
Instead of using the DataGridCell.Selected Event which is only fired for the first time a row's cell is clicked, i used the DataGridCell.GotFocus Event.
<DataGrid DataGridCell.GotFocus="DataGrid_CellGotFocus" />
If you do so you will have always the correct cell focused and in edit mode, but no control in the cell will be focused, this i solved like this
private void DataGrid_CellGotFocus(object sender, RoutedEventArgs e)
{
// Lookup for the source to be DataGridCell
if (e.OriginalSource.GetType() == typeof(DataGridCell))
{
// Starts the Edit on the row;
DataGrid grd = (DataGrid)sender;
grd.BeginEdit(e);
Control control = GetFirstChildByType<Control>(e.OriginalSource as DataGridCell);
if (control != null)
{
control.Focus();
}
}
}
private T GetFirstChildByType<T>(DependencyObject prop) where T : DependencyObject
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(prop); i++)
{
DependencyObject child = VisualTreeHelper.GetChild((prop), i) as DependencyObject;
if (child == null)
continue;
T castedProp = child as T;
if (castedProp != null)
return castedProp;
castedProp = GetFirstChildByType<T>(child);
if (castedProp != null)
return castedProp;
}
return null;
}
From: http://wpf.codeplex.com/wikipage?title=Single-Click%20Editing
XAML:
<!-- SINGLE CLICK EDITING -->
<Style TargetType="{x:Type dg:DataGridCell}">
<EventSetter Event="PreviewMouseLeftButtonDown" Handler="DataGridCell_PreviewMouseLeftButtonDown"></EventSetter>
</Style>
CODE-BEHIND:
//
// SINGLE CLICK EDITING
//
private void DataGridCell_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
DataGridCell cell = sender as DataGridCell;
if (cell != null && !cell.IsEditing && !cell.IsReadOnly)
{
if (!cell.IsFocused)
{
cell.Focus();
}
DataGrid dataGrid = FindVisualParent<DataGrid>(cell);
if (dataGrid != null)
{
if (dataGrid.SelectionUnit != DataGridSelectionUnit.FullRow)
{
if (!cell.IsSelected)
cell.IsSelected = true;
}
else
{
DataGridRow row = FindVisualParent<DataGridRow>(cell);
if (row != null && !row.IsSelected)
{
row.IsSelected = true;
}
}
}
}
}
static T FindVisualParent<T>(UIElement element) where T : UIElement
{
UIElement parent = element;
while (parent != null)
{
T correctlyTyped = parent as T;
if (correctlyTyped != null)
{
return correctlyTyped;
}
parent = VisualTreeHelper.GetParent(parent) as UIElement;
}
return null;
}
The solution from http://wpf.codeplex.com/wikipage?title=Single-Click%20Editing worked great for me, but I enabled it for every DataGrid using a Style defined in a ResourceDictionary. To use handlers in resource dictionaries you need to add a code-behind file to it. Here's how you do it:
This is a DataGridStyles.xaml Resource Dictionary:
<ResourceDictionary x:Class="YourNamespace.DataGridStyles"
x:ClassModifier="public"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style TargetType="DataGrid">
<!-- Your DataGrid style definition goes here -->
<!-- Cell style -->
<Setter Property="CellStyle">
<Setter.Value>
<Style TargetType="DataGridCell">
<!-- Your DataGrid Cell style definition goes here -->
<!-- Single Click Editing -->
<EventSetter Event="PreviewMouseLeftButtonDown"
Handler="DataGridCell_PreviewMouseLeftButtonDown" />
</Style>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Note the x:Class attribute in the root element.
Create a class file. In this example it'd be DataGridStyles.xaml.cs. Put this code inside:
using System.Windows.Controls;
using System.Windows;
using System.Windows.Input;
namespace YourNamespace
{
partial class DataGridStyles : ResourceDictionary
{
public DataGridStyles()
{
InitializeComponent();
}
// The code from the myermian's answer goes here.
}
I solved it by adding a trigger that sets IsEditing property of the DataGridCell to True when the mouse is over it. It solved most of my problems. It works with comboboxes too.
<Style TargetType="DataGridCell">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="IsEditing" Value="True" />
</Trigger>
</Style.Triggers>
</Style>
i prefer this way based on Dušan Knežević suggestion. you click an that's it ))
<DataGrid.Resources>
<Style TargetType="DataGridCell" BasedOn="{StaticResource {x:Type DataGridCell}}">
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver"
Value="True" />
<Condition Property="IsReadOnly"
Value="False" />
</MultiTrigger.Conditions>
<MultiTrigger.Setters>
<Setter Property="IsEditing"
Value="True" />
</MultiTrigger.Setters>
</MultiTrigger>
</Style.Triggers>
</Style>
</DataGrid.Resources>
I looking for editing cell on single click in MVVM and this is an other way to do it.
Adding behavior in xaml
<UserControl xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:myBehavior="clr-namespace:My.Namespace.To.Behavior">
<DataGrid>
<i:Interaction.Behaviors>
<myBehavior:EditCellOnSingleClickBehavior/>
</i:Interaction.Behaviors>
</DataGrid>
</UserControl>
The EditCellOnSingleClickBehavior class extend System.Windows.Interactivity.Behavior;
public class EditCellOnSingleClick : Behavior<DataGrid>
{
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.LoadingRow += this.OnLoadingRow;
this.AssociatedObject.UnloadingRow += this.OnUnloading;
}
protected override void OnDetaching()
{
base.OnDetaching();
this.AssociatedObject.LoadingRow -= this.OnLoadingRow;
this.AssociatedObject.UnloadingRow -= this.OnUnloading;
}
private void OnLoadingRow(object sender, DataGridRowEventArgs e)
{
e.Row.GotFocus += this.OnGotFocus;
}
private void OnUnloading(object sender, DataGridRowEventArgs e)
{
e.Row.GotFocus -= this.OnGotFocus;
}
private void OnGotFocus(object sender, RoutedEventArgs e)
{
this.AssociatedObject.BeginEdit(e);
}
}
Voila !
I slightly edit solution from Dušan Knežević
<DataGrid.Resources>
<Style x:Key="ddlStyle" TargetType="DataGridCell">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="IsEditing" Value="True" />
</Trigger>
</Style.Triggers>
</Style>
</DataGrid.Resources>
and apply the style to my desire column
<DataGridComboBoxColumn CellStyle="{StaticResource ddlStyle}">
There are two issues with user2134678's answer. One is very minor and has no functional effect. The other is fairly significant.
The first issueis that the GotFocus is actually being called against the DataGrid, not the DataGridCell in practice. The DataGridCell qualifier in the XAML is redundant.
The main problem I found with the answer is that the Enter key behavior is broken. Enter should move you to the next cell below the current cell in normal DataGrid behavior. However, what actually happens behind the scenes is GotFocus event will be called twice. Once upon the current cell losing focus, and once upon the new cell gaining focus. But as long as BeginEdit is called on that first cell, the next cell will never be activated. The upshot is that you have one-click editing, but anyone who isn't literally clicking on the grid is going to be inconvenienced, and a user-interface designer should not assume that all users are using mouses. (Keyboard users can sort of get around it by using Tab, but that still means they're jumping through hoops that they shouldn't need to.)
So the solution to this problem? Handle event KeyDown for the cell and if the Key is the Enter key, set a flag that stops BeginEdit from firing on the first cell. Now the Enter key behaves as it should.
To begin with, add the following Style to your DataGrid:
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridCell}" x:Key="SingleClickEditingCellStyle">
<EventSetter Event="KeyDown" Handler="DataGridCell_KeyDown" />
</Style>
</DataGrid.Resources>
Apply that style to "CellStyle" property the columns for which you want to enable one-click.
Then in the code behind you have the following in your GotFocus handler (note that I'm using VB here because that's what our "one-click data grid requesting" client wanted as the development language):
Private _endEditing As Boolean = False
Private Sub DataGrid_GotFocus(ByVal sender As Object, ByVal e As RoutedEventArgs)
If Me._endEditing Then
Me._endEditing = False
Return
End If
Dim cell = TryCast(e.OriginalSource, DataGridCell)
If cell Is Nothing Then
Return
End If
If cell.IsReadOnly Then
Return
End If
DirectCast(sender, DataGrid).BeginEdit(e)
.
.
.
Then you add your handler for the KeyDown event:
Private Sub DataGridCell_KeyDown(ByVal sender As Object, ByVal e As KeyEventArgs)
If e.Key = Key.Enter Then
Me._endEditing = True
End If
End Sub
Now you have a DataGrid that hasn't changed any fundamental behavior of the out-of-the-box implementation and yet supports single-click editing.
Several of these answers inspired me, as well as this blog post, yet each answer left something wanting. I combined what seemed the best parts of them and came up with this fairly elegant solution that seems to get the user experience exactly right.
This uses some C# 9 syntax, which works fine everywhere though you may need to set this in your project file:
<LangVersion>9</LangVersion>
First, we get down from 3 clicks to 2 with this:
Add this class to your project:
public static class WpfHelpers
{
internal static void DataGridPreviewMouseLeftButtonDownEvent(object sender, RoutedEventArgs e)
{
// The original source for this was inspired by https://softwaremechanik.wordpress.com/2013/10/02/how-to-make-all-wpf-datagrid-cells-have-a-single-click-to-edit/
DataGridCell? cell = e is MouseButtonEventArgs { OriginalSource: UIElement clickTarget } ? FindVisualParent<DataGridCell>(clickTarget) : null;
if (cell is { IsEditing: false, IsReadOnly: false })
{
if (!cell.IsFocused)
{
cell.Focus();
}
if (FindVisualParent<DataGrid>(cell) is DataGrid dataGrid)
{
if (dataGrid.SelectionUnit != DataGridSelectionUnit.FullRow)
{
if (!cell.IsSelected)
{
cell.IsSelected = true;
}
}
else
{
if (FindVisualParent<DataGridRow>(cell) is DataGridRow { IsSelected: false } row)
{
row.IsSelected = true;
}
}
}
}
}
internal static T? GetFirstChildByType<T>(DependencyObject prop)
where T : DependencyObject
{
int count = VisualTreeHelper.GetChildrenCount(prop);
for (int i = 0; i < count; i++)
{
if (VisualTreeHelper.GetChild(prop, i) is DependencyObject child)
{
T? typedChild = child as T ?? GetFirstChildByType<T>(child);
if (typedChild is object)
{
return typedChild;
}
}
}
return null;
}
private static T? FindVisualParent<T>(UIElement element)
where T : UIElement
{
UIElement? parent = element;
while (parent is object)
{
if (parent is T correctlyTyped)
{
return correctlyTyped;
}
parent = VisualTreeHelper.GetParent(parent) as UIElement;
}
return null;
}
}
Add this to your App.xaml.cs file:
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
EventManager.RegisterClassHandler(
typeof(DataGrid),
DataGrid.PreviewMouseLeftButtonDownEvent,
new RoutedEventHandler(WpfHelpers.DataGridPreviewMouseLeftButtonDownEvent));
}
Then we get from 2 to 1 with this:
Add this to the code behind of the page containing the DataGrid:
private void TransactionDataGrid_PreparingCellForEdit(object sender, DataGridPreparingCellForEditEventArgs e)
{
WpfHelpers.GetFirstChildByType<Control>(e.EditingElement)?.Focus();
}
And wire it up (in XAML): PreparingCellForEdit="TransactionDataGrid_PreparingCellForEdit"
I know that I am a bit late to the party but I had the same problem and came up with a different solution:
public class DataGridTextBoxColumn : DataGridBoundColumn
{
public DataGridTextBoxColumn():base()
{
}
protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem)
{
throw new NotImplementedException("Should not be used.");
}
protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
{
var control = new TextBox();
control.Style = (Style)Application.Current.TryFindResource("textBoxStyle");
control.FontSize = 14;
control.VerticalContentAlignment = VerticalAlignment.Center;
BindingOperations.SetBinding(control, TextBox.TextProperty, Binding);
control.IsReadOnly = IsReadOnly;
return control;
}
}
<DataGrid Grid.Row="1" x:Name="exportData" Margin="15" VerticalAlignment="Stretch" ItemsSource="{Binding CSVExportData}" Style="{StaticResource dataGridStyle}">
<DataGrid.Columns >
<local:DataGridTextBoxColumn Header="Sample ID" Binding="{Binding SampleID}" IsReadOnly="True"></local:DataGridTextBoxColumn>
<local:DataGridTextBoxColumn Header="Analysis Date" Binding="{Binding Date}" IsReadOnly="True"></local:DataGridTextBoxColumn>
<local:DataGridTextBoxColumn Header="Test" Binding="{Binding Test}" IsReadOnly="True"></local:DataGridTextBoxColumn>
<local:DataGridTextBoxColumn Header="Comment" Binding="{Binding Comment}"></local:DataGridTextBoxColumn>
</DataGrid.Columns>
</DataGrid>
As you can see I wrote my own DataGridTextColumn inheriting everything vom the DataGridBoundColumn. By overriding the GenerateElement Method and returning a Textbox control right there the Method for generating the Editing Element never gets called.
In a different project I used this to implement a Datepicker column, so this should work for checkboxes and comboboxes as well.
This does not seem to impact the rest of the datagrids behaviours..At least I haven't noticed any side effects nor have I got any negative feedback so far.
Update
A simple solution if you are fine with that your cell stays a textbox (no distinguishing between edit and non-edit mode). This way single click editing works out of the box. This works with other elements like combobox and buttons as well. Otherwise use the solution below the update.
<DataGridTemplateColumn Header="My Column header">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding MyProperty } />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Update end
Rant
I tried everything i found here and on google and even tried out creating my own versions. But every answer/solution worked mainly for textbox columns but didnt work with all other elements (checkboxes, comboboxes, buttons columns), or even broke those other element columns or had some other side effects. Thanks microsoft for making datagrid behave that ugly way and force us to create those hacks. Because of that I decided to make a version which can be applied with a style to a textbox column directly without affecting other columns.
Features
No Code behind. MVVM friendly.
It works when clicking on different textbox cells in the same or different rows.
TAB and ENTER keys work.
It doesnt affect other columns.
Sources
I used this solution and #m-y's answer and modified them to be an attached behavior.
http://wpf-tutorial-net.blogspot.com/2016/05/wpf-datagrid-edit-cell-on-single-click.html
How to use it
Add this Style.
The BasedOn is important when you use some fancy styles for your datagrid and you dont wanna lose them.
<Window.Resources>
<Style x:Key="SingleClickEditStyle" TargetType="{x:Type DataGridCell}" BasedOn="{StaticResource {x:Type DataGridCell}}">
<Setter Property="local:DataGridTextBoxSingleClickEditBehavior.Enable" Value="True" />
</Style>
</Window.Resources>
Apply the style with CellStyle to each of your DataGridTextColumns like this:
<DataGrid ItemsSource="{Binding MyData}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="My Header" Binding="{Binding Comment}" CellStyle="{StaticResource SingleClickEditStyle}" />
</DataGrid.Columns>
</DataGrid>
And now add this class to the same namespace as your MainViewModel (or a different Namespace. But then you will need to use a other namespace prefix than local). Welcome to the ugly boilerplate code world of attached behaviors.
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace YourMainViewModelNameSpace
{
public static class DataGridTextBoxSingleClickEditBehavior
{
public static readonly DependencyProperty EnableProperty = DependencyProperty.RegisterAttached(
"Enable",
typeof(bool),
typeof(DataGridTextBoxSingleClickEditBehavior),
new FrameworkPropertyMetadata(false, OnEnableChanged));
public static bool GetEnable(FrameworkElement frameworkElement)
{
return (bool) frameworkElement.GetValue(EnableProperty);
}
public static void SetEnable(FrameworkElement frameworkElement, bool value)
{
frameworkElement.SetValue(EnableProperty, value);
}
private static void OnEnableChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is DataGridCell dataGridCell)
dataGridCell.PreviewMouseLeftButtonDown += DataGridCell_PreviewMouseLeftButtonDown;
}
private static void DataGridCell_PreviewMouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
EditCell(sender as DataGridCell, e);
}
private static void EditCell(DataGridCell dataGridCell, RoutedEventArgs e)
{
if (dataGridCell == null || dataGridCell.IsEditing || dataGridCell.IsReadOnly)
return;
if (dataGridCell.IsFocused == false)
dataGridCell.Focus();
var dataGrid = FindVisualParent<DataGrid>(dataGridCell);
dataGrid?.BeginEdit(e);
}
private static T FindVisualParent<T>(UIElement element) where T : UIElement
{
var parent = VisualTreeHelper.GetParent(element) as UIElement;
while (parent != null)
{
if (parent is T parentWithCorrectType)
return parentWithCorrectType;
parent = VisualTreeHelper.GetParent(parent) as UIElement;
}
return null;
}
}
}
<DataGridComboBoxColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="cal:Message.Attach"
Value="[Event MouseLeftButtonUp] = [Action ReachThisMethod($source)]"/>
</Style>
</DataGridComboBoxColumn.CellStyle>
public void ReachThisMethod(object sender)
{
((System.Windows.Controls.DataGridCell)(sender)).IsEditing = true;
}

Categories

Resources