Adding Tooltip to DataGrid Cells programmatically - c#

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;

Related

How to set ListView Column Header Property IsHitTestVisible to False programatically?

I am adding columns to my ListView programmaticaly with this line of code:
gridView.Columns.Add(New GridViewColumn With {.Header = myWorksheet.Cells(1, myVar).Value, .DisplayMemberBinding = New Binding(myWorksheet.Cells(1, myVar).Value)})
What I want to do is set the HitTest to False so the user cannot resize the columns that were added, I tried doing this in my XAML but it did not work:
<GridView.ColumnHeaderContainerStyle>
<Style BasedOn="{StaticResource {x:Type GridViewColumnHeader}}" TargetType="{x:Type GridViewColumnHeader}">
<Setter Property="IsHitTestVisible" Value="False"/>
</Style>
</GridView.ColumnHeaderContainerStyle>
This probably requires a solution in VB.Net because the XAML code does not apply to columns created programmaticaly?
Added C# tag because I found out it can be converted to VB.Net.
XAML style is applied to all columns added in XAML and from code behind. If you want to set IsHitTestVisible not for all, but only for some column's header, then it will be tricky.
I don't know elegant solution for it. So far I know there is no way to get easy GridViewColumnHeader object, which will be styled.
My offer would be to inject a control instead of setting header text, e.g. TextBlock, which will iterate through parents and set GridViewColumnHeader.IsHitTestVisible to false.
public class TextBlockNoHit<ParentHitTestVisibleType> : TextBlock where ParentHitTestVisibleType: UIElement
{
public bool lsParentHitTestVisible { get; set; }
public TextBlockNoHit()
{
Loaded += TextBlockNoHit_Loaded;
}
private void TextBlockNoHit_Loaded(object sender, RoutedEventArgs e)
{
Loaded -= TextBlockNoHit_Loaded;
DependencyObject dpo = this;
while(dpo!=null)
{
dpo = VisualTreeHelper.GetParent(dpo);
if(dpo is ParentHitTestVisibleType gvch)
{
gvch.IsHitTestVisible = lsParentHitTestVisible;
}
}
}
}
Using then:
var col = new GridViewColumn() { Header = new TextBlockNoHit<GridViewColumnHeader>() { Text = "myWorksheet.Cells...", IsHitTestVisible = false, lsParentHitTestVisible = false}, DisplayMemberBinding = new Binding("myWorksheet.Cells...") };

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;

WPF Toolkit DataGridCell Style DataTrigger

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.

Categories