DataGrid Select Entire Column - c#

I am using WPF DataGrid and I would like to allow the user to select an entire column by clicking on the column header. I currently have the SelectionUnit set to CellOrRowHeader because I want the same functionality for rows (which works great). Is there an easy way to also allow selection of columns by clicking the column header?

You have many options. one is create your own Template for DataGrid's Header Style. Inside the DataTemplate tag you can change the template of the headers. (you can replace the Button with a TextBlock or anything you want.)
<DataGrid>
<DataGrid.ColumnHeaderStyle>
<Style TargetType="DataGridColumnHeader" >
<Setter Property="Foreground" Value="Blue" />
<Setter Property="ContentTemplate" >
<Setter.Value>
<DataTemplate>
<Button Content={Binding Content}" MouseDown="mouseDownEventHandler">
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGrid.ColumnHeaderStyle>
</DataGrid>
or if you are using DataGrid.Columns to populate your columns and you need to set each of them separately, you can use this:
<DataGrid>
<DataGrid.Columns>
<DataGridTextColumn HeaderTemplate="{StaticResource MyTemplate1"/>
<DataGridHyperlinkColumn HeaderTemplate="{StaticResource MyTemplate2"/>
</DataGrid.Columns>
</DatGrid>
where MyTemplate1 and 2 should be defined previously in your control's Resources.
Edit:
The other approach according to this link is to add a PreviewMouseDown to your DataGrid and then find out if the mouse down was happend on the header or not.
This is the simple version of her event handler:
DependencyObject dep = (DependencyObject)e.OriginalSource;
while ((dep != null) && !(dep is DataGridColumnHeader))
{
dep = VisualTreeHelper.GetParent(dep);
}
if (dep == null) return;
if (dep is DataGridColumnHeader)
{
MessageBox.Show(((DataGridColumnHeader)dep).Content.ToString());
}

You could also modify the ColumnHeaderStyle:
In XAML:
<DataGrid>
<DataGrid.ColumnHeaderStyle>
<Style TargetType="DataGridColumnHeader">
<EventSetter Event="Click" Handler="DataGridColumnHeader_OnClick"></EventSetter>
</Style>
</DataGrid.ColumnHeaderStyle>
</DataGrid>
In the code-behind:
private void DataGridColumnHeader_OnClick(object sender, RoutedEventArgs e)
{
var columnHeader = sender as DataGridColumnHeader;
if (columnHeader != null)
{
if (!Keyboard.IsKeyDown(Key.LeftCtrl))
{
dataGrid.SelectedCells.Clear();
}
foreach (var item in dataGrid.Items)
{
dataGrid.SelectedCells.Add(new DataGridCellInfo(item, columnHeader.Column));
}
}
}

In WPF there is no direct support for column selection so, need to Extend existing DataGrid and add custom support for Column Selection.
Below is the code that will allow used to select column on Header click.
For Below code to work following setting is required.
Xaml Code:
<local:DataGridEx SelectionUnit="CellOrRowHeader" CanUserSortColumns="False"/>
C# Code:
public class DataGridEx : DataGrid
{
/// <summary>
/// Holds the column that is selected.
/// </summary>
public object SelectedColumn
{
get { return GetValue(SelectedColumnProperty); }
set { SetValue(SelectedColumnProperty, value); }
}
// Using a DependencyProperty as the backing store for SelectedColumn. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedColumnProperty =
DependencyProperty.Register("SelectedColumn", typeof(object),
typeof(DataGridEx), new PropertyMetadata(null));
private T GetVisualParent<T>(DependencyObject child) where T : Visual
{
DependencyObject parent = VisualTreeHelper.GetParent(child);
if (parent == null || parent is T)
{
return parent as T;
}
else
{
return GetVisualParent<T>(parent);
}
}
protected override void OnPreviewMouseDown(MouseButtonEventArgs e)
{
DataGridColumnHeader dataGridColumnHeader = GetVisualParent<DataGridColumnHeader>(e.OriginalSource as DependencyObject);
if (dataGridColumnHeader == null)
{
return;
}
if (SelectedCells != null && SelectedCells.Count > 0)
{
UnselectAllCells();
SelectedCells.Clear();
}
SelectedColumn = dataGridColumnHeader.Column;
foreach (var item in this.Items)
{
this.SelectedCells.Add(new DataGridCellInfo(item, dataGridColumnHeader.Column));
}
base.OnPreviewMouseDown(e);
}
}

Related

Change DataGrid column visibility (WPF, MVVM)

I just recently started learning MVVM. I hope that a solution to this problem will come.
In my application, the user is authorized in the system, after which a window with a table opens. Users are divided into roles: Administrator and Employee. I want the Employee to be unable to see a certain column (ID).
I have an AuthorizationMeth class, where the IDRoleAuthorization variable stores role ID of the authorized user. How can I now use this value to hide the column ID? In my case if IDRoleAuthorization = 2 to hide column ID
Found solutions using the Freezable class and creating a FrameworkElement in XAML but I can't figure out how this is solved for my problem.
Methods/AuthorizationMeth.cs
public class AuthorizationMeth
{
public static int IDRoleAuthorization;
public bool Enter(string login, string password)
{
Intis6Context db = new Intis6Context();
if (login == "" || password == "")
{
MessageBox.Show("You have not completed all fields", "Authorization", MessageBoxButton.OK, MessageBoxImage.Error);
return false;
}
var auth_check = db.Users.AsNoTracking().FirstOrDefault(ch => ch.Login == login && ch.Password == password);
if (auth_check == null)
{
MessageBox.Show("Login or password entered incorrectly", "Authorization", MessageBoxButton.OK, MessageBoxImage.Error);
return false;
}
IDRoleAuthorization = auth_check.IdRole;
return true;
}
}
View/ContractView.xaml
<DataGrid Background="White" AutoGenerateColumns="False" EnableColumnVirtualization="True" EnableRowVirtualization="True"
ItemsSource="{Binding AllContrsupl_saleDTO, IsAsync=True}"
Grid.Row="0">
<DataGrid.Columns>
<DataGridTextColumn Header="ID" Binding="{Binding Path=Cnssid}"/>
<DataGridTextColumn Header="Author" Binding="{Binding Path=FULLNAMEstaff}"/>
<DataGridTextColumn Header="Type" Binding="{Binding Path=typeTable}"/>
Have you tried something like this?
If you want to Hide the "ID" column try this:
You need to assign a x:Name to your DataGrid control first. For example "myDataGrid" Then you can do this in Code behind.
if(IDRoleAuthorization == 2)
{
myDataGrid.Columns[0].Visibility = Visibility.Collapsed;
}
Hope this helps.
Don't expose a public static int IDRoleAuthorization field. Such a field must be a property (never define public fields) and at least read-only, and a read-only instance property (non-static) at best. Additionally, don't expose the numeric value, but a bool property e.g., IsAuthorized. The logic to determine whether the numeric code evaluates to an authorized user must be encapsulated and not spread across the application. External classes must depend on the result of this evaluation only.
The DataGridColumn definitions are not part of the visual tree. They are not rendered. they are just placeholders that contain information about the column, which will be generated by the DataGrid later. The actual column consists of a DataGridColumnHeader and DataGridCell elements.
Because of this, you can't configure a Binding on the DataGridColumn.Visbility property.
You can now simply toggle the cells and their associated header (which will not remove the column, but the values of the header and cells):
<!-- ToggleButton to simulate the IsAuthorized property -->
<ToggleButton x:Name="ToggleButton" Content="Hide/show column content" />
<DataGrid>
<DataGrid.Columns>
<DataGridTextColumn Header="Dynamic Column">
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=ToggleButton, Path=IsChecked}" Value="True">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGridTextColumn.CellStyle>
<DataGridTextColumn.HeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=ToggleButton, Path=IsChecked}" Value="True">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGridTextColumn.HeaderStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="Static Column" />
</DataGrid.Columns>
</DataGrid>
To remove the column completely in your scenario (no auto-generated columns), you must remove it manually from the DataGrid.Columns collection or toggle the column definitions Visibilty explicitly (by accessing the DataGrid.Columns).
Add and AuthorizationChanged event to your view model class and make the view listen to it. In the event handler set the columns visibility or remove/add the column.
Alternatively, write a simple attached behavior:
DataGridHelper.cs
class DataGridHelper : DependencyObject
{
public static string GetHidableColumnIndices(DependencyObject attachingElement) => (string)attachingElement.GetValue(HidableColumnIndicesProperty);
public static void SetHidableColumnIndices(DependencyObject attachingElement, string value) => attachingElement.SetValue(HidableColumnIndicesProperty, value);
public static readonly DependencyProperty HidableColumnIndicesProperty = DependencyProperty.RegisterAttached(
"HidableColumnIndices",
typeof(string),
typeof(DataGridHelper),
new PropertyMetadata(default(string), OnHidableColumnIndicesChanged));
public static Visibility GetColumnVisibility(DependencyObject attachingElement) => (Visibility)attachingElement.GetValue(ColumnVisibilityProperty);
public static void SetColumnVisibility(DependencyObject attachingElement, Visibility value) => attachingElement.SetValue(ColumnVisibilityProperty, value);
public static readonly DependencyProperty ColumnVisibilityProperty = DependencyProperty.RegisterAttached(
"ColumnVisibility",
typeof(Visibility),
typeof(DataGridHelper),
new PropertyMetadata(default(Visibility), OnColumnVisibilityChanged));
private static void OnColumnVisibilityChanged(DependencyObject attachingElement, DependencyPropertyChangedEventArgs e)
{
if (attachingElement is not DataGrid dataGrid)
{
throw new ArgumentException("Attaching element must be of type DataGrid.");
}
ToggleColumnVisibility(dataGrid);
}
private static void OnHidableColumnIndicesChanged(DependencyObject attachingElement, DependencyPropertyChangedEventArgs e)
{
if (attachingElement is not DataGrid dataGrid)
{
throw new ArgumentException("Attaching element must be of type DataGrid.");
}
ToggleColumnVisibility(dataGrid);
}
private static void ToggleColumnVisibility(DataGrid dataGrid)
{
IEnumerable<int> columnIndices = GetHidableColumnIndices(dataGrid)
.Split(new[] { ' ', ',' }, StringSplitOptions.RemoveEmptyEntries)
.Select(numericChar => int.Parse(numericChar));
foreach (int columnIndex in columnIndices)
{
dataGrid.Columns[columnIndex].Visibility = GetColumnVisibility(dataGrid);
}
}
}
Usage example
<DataGrid DatGridHelper.HidableColumnIndices="0,3,8"
DataGridHelper.ColumnVisiblility="{Binding IsAuthenticated, Converter={StaticResource BooleanToVisibilityConverter}}" />

Display deleted rows in a DataTable

So I have a DataTable that I bind to the DataGrid in XAML.
User is allowed to add, modify and remove rows for a table. I would like to mark rows with a specific colour, depending on the action that user makes. For instance, if user adds a row, that row will be marked as Green. If user modifies a row, that row will then be marked as orange. And if user removes the row, that row will be marked as red.
The problem that I have is that the removed row is no longer visible once I call row.Delete(); from a view model.
Is there a way to keep a DataRow marked for removal shown in a DataGrid? I know how to achieve the row background effect, depending on user action. The only problem is I don't know how to keep the deleted row visible. The idea is that user can either revert changes or apply them and that's when the pending deletion row should be actually deleted.
EDIT (Added example on how I update row background colour):
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Path=Row.RowState}" Value="{x:Static data:DataRowState.Deleted}" />
<Condition Binding="{Binding RelativeSource={RelativeSource Mode=Self}, Path=IsSelected}" Value="False" />
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter Property="Background" Value="IndianRed" TargetName="DGR_Border"/>
<Setter Property="Foreground" Value="Black"/>
<Setter Property="FontWeight" Value="Bold"/>
</MultiDataTrigger.Setters>
</MultiDataTrigger>
I think, when user marks row for deleting - you should save it index somewhere (int[] array or List<int> for example), then call yourDataGridView.Rows.RemoveAt(index) for each element in that collection when user finished working with table.
Maybe something like:
//Collection for rows indexes which will be deleted later
List<int> rowsToDelete = new List<int>();
//Call this when user want to delete element
private void MarkElementsToRemove()
{
if (yourDataGrid.SelectedItems.Count > 0)
{
//Get selected by user rows
for (int i = 0; i < yourDataGrid.SelectedItems.Count; ++i)
{
DataGridRow row = (DataGridRow)yourDataGrid.SelectedItems[i];
//Fill rows background with some color to mark them visually as "deleted"
row.Background = new SolidColorBrush(Color.FromRgb(255, 0, 0));
//Get row index to remove it later and add it to collection
rowsToDelete.Add(row.GetIndex());
}
}
}
// Call this when user finished work with DataGrid and items may be removed
private void RemoveMarkedElements()
{
foreach (int index in rowsToDelete)
{
yourDataGrid.Items.RemoveAt(index);
}
rowsToDelete.Clear();
}
Instead of index you may save whole DataGridRow and call yourDataGrid.Remove(wholeRow);.
And for reverse deletion, you just unmark it by removing color and removing row index or whole row from a collection.
If I understood you correctly, you need to use the Delete key not to delete lines, but to put a marker on them.
And in the DataGrid, you need to highlight color the rows marked with this marker.
You have not shown your table, so I will demonstrate in my simple conciliation.
The example uses the BaseInpc and RelayCommand classes.
In addition to them, the command extension method is used:
using System.Windows.Input;
namespace Simplified
{
public static class CommandExtensionMethods
{
public static bool TryExecute(this ICommand command, object parameter)
{
bool can = command.CanExecute(parameter);
if (can)
command.Execute(parameter);
return can;
}
public static bool TryExecute(this ICommand command)
=> TryExecute(command, null);
}
}
ViewModel:
using Simplified;
using System.Data;
using System.Windows.Input;
namespace DeferredRowDeletion
{
public class DrdViewModel : BaseInpc
{
public DataTable Table { get; } = new DataTable();
public DrdViewModel()
{
Table.Columns.Add("Name", typeof(string));
Table.Columns.Add("Value", typeof(int));
Table.Columns.Add("Marked for deletion", typeof(bool));
foreach (var name in new string[] { "First", "Second", "Third", "Fourth", "Fifth" })
{
var row = Table.NewRow();
row[0] = name;
row[1] = Table.Rows.Count;
row[2] = Table.Rows.Count % 2 == 1;
Table.Rows.Add(row);
}
}
private ICommand _markRemoveChangeCommand;
private bool _isRemoveRowsImmediately;
public ICommand MarkRemoveChangeCommand => _markRemoveChangeCommand
?? (_markRemoveChangeCommand = new RelayCommand<DataRow>(
row => row[2] = !(bool)(row[2] ?? false),
row => !IsRemoveRowsImmediately
));
public bool IsRemoveRowsImmediately
{
get => _isRemoveRowsImmediately;
set => Set(ref _isRemoveRowsImmediately, value);
}
}
}
Window XAML:
<Window x:Class="DeferredRowDeletion.DrdWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:DeferredRowDeletion"
mc:Ignorable="d"
Title="DrdWindow" Height="450" Width="800">
<FrameworkElement.DataContext>
<local:DrdViewModel/>
</FrameworkElement.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<CheckBox Content="Removw Rows Immediately"
IsChecked="{Binding IsRemoveRowsImmediately}"
Margin="5"/>
<DataGrid x:Name="dataGrid" Grid.Row="1"
ItemsSource="{Binding Table, Mode=OneWay}"
AutoGeneratingColumn="OnAutoGeneratingColumn"
CanUserDeleteRows="{Binding IsRemoveRowsImmediately}"
PreviewKeyDown="OnPreviewKeyDown">
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding [Marked for deletion]}" Value="true">
<Setter Property="Background" Value="HotPink"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
</DataGrid>
</Grid>
</Window>
Code Behind Window:
using Simplified;
using System.Data;
using System.Windows;
using System.Windows.Input;
namespace DeferredRowDeletion
{
public partial class DrdWindow : Window
{
public DrdWindow()
{
InitializeComponent();
}
private void OnAutoGeneratingColumn(object sender, System.Windows.Controls.DataGridAutoGeneratingColumnEventArgs e)
{
if (e.PropertyName == "Marked for deletion")
e.Cancel = true;
}
private void OnPreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
if (e.Key == Key.Delete)
{
DrdViewModel viewModel = (DrdViewModel)DataContext;
var rowView = dataGrid.CurrentItem as DataRowView;
if (rowView != null && !rowView.IsEdit)
viewModel.MarkRemoveChangeCommand.TryExecute(rowView.Row);
}
}
}
}
If you are unable to use this example, then write the reason and add details to the explanation of your question.
The answer is supplemented by clarifications for the added details:
I think I should've mentioned that I use DataRow's RowState property bound to the DataTrigger to update row background colour. Added details to the question.
To control the visibility of rows, you need to change the value of the DataTable.DefaultView.RowStateFilter property.
This is not hard to do in the ViewModel.
But an additional problem is that the RowState property does not notify about its change.
So the trigger binding won't work just like that.
In my example, I solved this by calling Items.Refresh ().
Perhaps you are using a different solution since you have not written about any problems associated with this.
using Simplified;
using System.Data;
using System.Windows.Input;
namespace DeferredRowDeletion
{
public class ShowDeletedRowsViewModel : BaseInpc
{
public DataTable Table { get; } = new DataTable();
public ShowDeletedRowsViewModel()
{
Table.Columns.Add("Name", typeof(string));
Table.Columns.Add("Value", typeof(int));
foreach (var name in new string[] { "First", "Second", "Third", "Fourth", "Fifth" })
{
var row = Table.NewRow();
row[0] = name;
row[1] = Table.Rows.Count;
Table.Rows.Add(row);
}
// Commits all the changes
Table.AcceptChanges();
Table.Rows[1].Delete();
Table.Rows[3].Delete();
// Show Deleded Rows
IsVisibilityDelededRows = true;
}
private ICommand _markRemoveChangeCommand;
private bool _isVisibilityDelededRows;
public ICommand MarkRemoveChangeCommand => _markRemoveChangeCommand
?? (_markRemoveChangeCommand = new RelayCommand<DataRow>(
row => IsVisibilityDelededRows ^= true,
row => !IsVisibilityDelededRows
));
public bool IsVisibilityDelededRows
{
get => _isVisibilityDelededRows;
set => Set(ref _isVisibilityDelededRows, value);
}
protected override void OnPropertyChanged(string propertyName, object oldValue, object newValue)
{
base.OnPropertyChanged(propertyName, oldValue, newValue);
if (propertyName == nameof(IsVisibilityDelededRows))
{
// Change the row filter if the associated property has changed
if (IsVisibilityDelededRows)
{
Table.DefaultView.RowStateFilter |= DataViewRowState.Deleted;
}
else
{
Table.DefaultView.RowStateFilter &= ~DataViewRowState.Deleted;
}
}
}
}
}
<Window x:Class="DeferredRowDeletion.SdrWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:DeferredRowDeletion" xmlns:data="clr-namespace:System.Data;assembly=System.Data"
mc:Ignorable="d"
Title="SdrWindow" Height="450" Width="800">
<FrameworkElement.DataContext>
<local:ShowDeletedRowsViewModel/>
</FrameworkElement.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<StackPanel>
<CheckBox x:Name="cbAutoRefresh" Content="Auto Items.Refresh()" IsChecked="True" Margin="5"/>
<CheckBox Content="Visibility Deleded Rows"
IsChecked="{Binding IsVisibilityDelededRows}"
Margin="5"/>
</StackPanel>
<DataGrid x:Name="dataGrid" Grid.Row="1"
ItemsSource="{Binding Table, Mode=OneWay}"
PreviewKeyUp="OnPreviewKeyUp">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=Row.RowState, Mode=OneWay}"
Header="RowState"/>
</DataGrid.Columns>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Row.RowState}" Value="{x:Static data:DataRowState.Deleted}">
<Setter Property="Background" Value="HotPink"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
</DataGrid>
</Grid>
</Window>
private void OnPreviewKeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.Delete && cbAutoRefresh.IsChecked == true)
dataGrid.Items.Refresh();
}

Automatic editing of WPF datagrid content when datagrid-cell gets focus

I have a datagrid in WPF with a DataGridTextColum and a DataGridTemplateColum.
<DataGridTextColumn Width="4*" IsReadOnly="True" x:Name="dataGridColumnDescription"
Header="Description" Binding="{Binding Description}">
</DataGridTextColumn>
<DataGridTemplateColumn CellStyle="{StaticResource CellEditing}" IsReadOnly="False" Width="*" Header="Value"
CellEditingTemplateSelector="{StaticResource myCellEditingTemplateSelectorValue}"
CellTemplateSelector="{StaticResource myCellTemplateSelectorValue}">
</DataGridTemplateColumn>
The CellTemplateSelectors return a DataTemplate with a TextBlock for the the Celltemplate resp. a TextBox for CellEditing!
<DataTemplate x:Key="dGridStringValueTemplate">
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Text="{Binding Path=Value}"/>
</DataTemplate>
<DataTemplate x:Key="dGridStringValueTemplateEditing">
<TextBox TextAlignment="Center" VerticalAlignment="Center" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" BorderThickness="1" Text="{Binding Path=Value, UpdateSourceTrigger=LostFocus}"/>
</DataTemplate>
Now I want to automatically Focus the TextBox when the DataGridCell gets the focus. The user should be able to edit the TextBox content without doubleclicking the cell.
I found this article:
DataGrid Tips & Tricks: Single-Click Editing
where I can get the Current DataGridCell, but how can I access the content to give the Textbox the focus to edit the content?
This is my style:
<Style x:Key="CellEditing" TargetType="{x:Type DataGridCell}">
<EventSetter Event="PreviewMouseLeftButtonDown" Handler="myDataGridMain_PreviewMouseLeftButtonDown"></EventSetter>
</Style>
This is my event handler:
private void myDataGridMain_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
DataGridCell cell = sender as DataGridCell; // cell ist not null
DataGridTemplateColumn col = cell.Column as DataGridTemplateColumn; //col is not null
DataTemplate template = col.CellTemplate; //this is null
}
How can I get the textbox with that event handler?
This seems to work :
<DataGrid ItemsSource="{Binding Items}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox FocusManager.FocusedElement="{Binding RelativeSource={RelativeSource Self}}"></TextBox>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
This approach works for me. It uses the fact that the DataGrid will always create a new instance of the template when the editing starts:
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox Text="{Binding MyProperty}"
Loaded="TextBox_Loaded"></TextBox>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
and in the code behind:
private void TextBox_Loaded(object sender, RoutedEventArgs e)
{
((TextBox)sender).Focus();
((TextBox)sender).SelectAll();
}
As an added bonus, it also selects all text in the cell. It should work no matter how you enter the editing mode (double click, single click, pressing F2)
I managed it, not the best solution but it works...
When Cell gets focus I set it to editing mode.
private void myDataGridMain_OnFocus(object sender, RoutedEventArgs e)
{
DataGridCell cell = sender as DataGridCell;
if (cell != null)
cell.IsEditing = true;
//var test = FindVisualChild<TextBlock>(cell);
}
On Keydown I search for the visual child and give the focus.
private void myDataGridMain_KeyDown(object sender, KeyEventArgs e)
{
DataGridCell cell = sender as DataGridCell;
if (e.Key == Key.Enter)
{ //give cell the focus
cell.Focus();
}
else
{
if ((cell != null))
{
TextBox textbox = FindVisualChild<TextBox>(cell);
if (textbox != null)
{ //TextBox has benn found
if ((textbox as TextBox).IsFocused == false)
{
(textbox as TextBox).SelectAll();
}
(textbox as TextBox).Focus();
}
CheckBox chkbox = FindVisualChild<CheckBox>(cell);
if (chkbox != null)
{ //Checkbox has been found
(chkbox as CheckBox).Focus();
}
ComboBox combbox = FindVisualChild<ComboBox>(cell);
if (combbox != null)
{ //ComboBox has been found
(combbox as ComboBox).Focus();
}
}
}
}
Find Visual Child!
public static T FindVisualChild<T>(DependencyObject obj) where T : DependencyObject
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
if (child != null && child is T)
return (T)child;
else
{
T childOfChild = FindVisualChild<T>(child);
if (childOfChild != null)
return childOfChild;
}
}
return null;
}
the simple answer for that create new control derived from datagrid control
using System.Windows.Controls;
public class CustomDataGrid : DataGrid
{
protected override void OnSelectedCellsChanged(SelectedCellsChangedEventArgs e)
{
//to make sure cell is selected
var cells = e.AddedCells.FirstOrDefault();
if (cells != null)
{
this.BeginEdit();
}
base.OnSelectedCellsChanged(e);
}
}
Hisham's suggestion works perfectly for me, but I would use OnCurrentCellChanged instead, since OnSelectedCellsChanged will not work when the SelectionUnit is CellOrRowHeader. In the latter case BeginEdit() would only be triggered when selection moves to a cell in another row. Stepping left or right will not trigger the event at all.
Also, it is probably advisable to add a DependencyProperty to the custom control and check against it before triggering BeginEdit(), to prevent this behavior if so desired (as done by other DataGrids, such as XCeed). But this is not a critic - just something that I usually do.
protected override void OnCurrentCellChanged(EventArgs e)
{
// Make sure a cell is selected and only enter edit mode
// if this is the desired behavior
if (CurrentCell != null && EditTrigger == EditTriggers.CellsCurrent)
{
this.BeginEdit();
}
base.OnCurrentCellChanged(e);
}

Make select controls visible based on selected tab using xaml

I've got following code:
private Dictionary<int, UserControl> tabControls = new Dictionary<int, UserControl>();
public MainWindow()
{
InitializeComponent();
tabControls[0] = new Panel1();
tabControls[1] = new Panel2();
tabControls[2] = new Panel3();
tabControls[3] = new Panel4();
tabControls[4] = new Panel5();
tabControls[5] = new Panel6();
tabControls[6] = new Panel7();
tabControls[7] = new Panel8();
}
public object SelectedTab
{
//this is assigned from xaml binding
set
{
OnCurrentTabChanged(tabControl.SelectedIndex);
}
}
void OnCurrentTabChanged(int tabIndex)
{
if (dataDisplay != null)
{
dataDisplay.Children.Clear();
dataDisplay.Children.Add(tabControls[tabIndex]);
}
}
Every time the user selects different tab, an other control appears.
Is there any way to simplify this using xaml?
I cannot put the controls themselves inside the tab control
I've done this before with another TabControl which has it's headers and frame hidden. Then I just bind the SelectedIndex to your other tab's SelectedIndex, and the two are synchronized
<!-- TabControl without the TabHeaders -->
<Style x:Key="TabControl_NoHeadersStyle" TargetType="{x:Type TabControl}">
<Setter Property="SnapsToDevicePixels" Value="true"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabControl}">
<DockPanel>
<!-- This is needed to draw TabControls with Bound items -->
<StackPanel IsItemsHost="True" Height="0" Width="0" />
<ContentPresenter x:Name="PART_SelectedContentHost"
ContentSource="SelectedContent" />
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Then you can setup your two tab controls, each bound to different sources, and bind the SelectedIndex of one to the SelectedIndex of the other
<TabControl x:Name="MainTabControl" />
<TabControl ItemsSource="{Binding Panels}"
SelectedIndex="{Binding ElementName=MainTabControl, Path=SelectedIndex}"
Style="{StaticResource TabControl_NoHeadersStyle}" />
Another alternative is to bind the SelectedIndex to something in your code-behind, then anytime it changes, raise a PropertyChanged notification on another property that exposes the panel you want to display.
<TabControl SelectedIndex="{Binding SelectedTabIndex} />
<ContentControl Content="{Binding SelectedPanel}" />
and in the code behind
public int SelectedTabIndex
{
get { return _selectedTabIndex;}
set
{
if (_selectedTabIndex != value)
{
_selectedTabIndex = value;
RaisePropertyChanged("SelectedTabIndex");
RaisePropertyChanged("SelectedPanel");
}
}
}
public UserControl SelectedPanel
{
get { return tabControls[SelectedTabIndex]; }
}
TabItem has an IsSelected propery you could bind to that I think would simplify the syntax.
public bool TabIsSelected
{
get { return tabIsSelected; }
set
{
if (value && dataDisplay != null)
{
dataDisplay.Children.Clear();
dataDisplay.Children.Add(tabControls[tabIndex]);
}
tabIsSelected = value;
}
But I still don't get why you can't just put the control in the tabitem?
using codebehind
void OnCurrentTabChanged(int tabIndex)
{
if (dataDisplay != null)
{
UIElemnt[] pp = dataDisplay.Children.Cast<UIElement>().ToArray();
Array.ForEach(pp, x=> x.visibility = Visibility.Collapsed);
pp[tabIndex].visibility = Visibility.Visible;
}
}

DataGrid column width doesn't auto-update

<DataGridTextColumn Binding="{Binding Name}" Width="*"/>
<DataGridTextColumn Binding="{Binding Change}" Width="Auto"/>
When the value of Change updates, its column doesn't update to fit the new value. So the column stays too small and the value is clipped.
Any ideas?
The DataGrid will increase column sizes to fit as the data becomes longer, but it does not automatically decrease column sizes when the length of the data decreases. In your example, you're right aligning the 'Change' column, and using the rest of the space for the 'Name' column.
Now, when a 'Change' property grows large enough that it should increase the column's width, the 'Name' column is refusing to shrink to accommodate, so you have to force a refresh yourself.
The following steps should do this for you (I've included a sample app to demo):
1) In your DataGridTextColumn Bindings (all except your * sized column) set NotifyTargetUpdated=True.
2) On your DataGrid, add a handler to the TargetUpdated event.
3) In your TargetUpdated event handler:
-- a) Set the DataGrid's * sized column's width to 0.
-- b) Call the UpdateLayout() method on the DataGrid.
-- c) Set the DataGrid's * sized column's width back to new DataGridLength(1, DataGridLengthUnitType.Star)
Example XAML:
<Window x:Class="DataGridTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<CollectionViewSource x:Key="MyObjectCollection" />
</Window.Resources>
<DockPanel>
<Button DockPanel.Dock="Bottom" Content="Click to Make Item 1s Text Longer" Click="Button_Click" />
<Grid>
<DataGrid x:Name="dg" ItemsSource="{Binding Source={StaticResource MyObjectCollection}}" AutoGenerateColumns="False" TargetUpdated="dg_TargetUpdated">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding First}" Width="1*"/>
<DataGridTextColumn Binding="{Binding Last, NotifyOnTargetUpdated=True}" Width="Auto" />
</DataGrid.Columns>
</DataGrid>
</Grid>
</DockPanel>
</Window>
Example Code Behind:
using System;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.ComponentModel;
namespace DataGridTest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private ObservableCollection<MyObject> myObjectList = new ObservableCollection<MyObject>();
public MainWindow()
{
InitializeComponent();
(this.FindResource("MyObjectCollection") as CollectionViewSource).Source = this.myObjectList;
this.myObjectList.Add(new MyObject() { First = "Bob", Last = "Jones" });
this.myObjectList.Add(new MyObject() { First = "Jane", Last = "Doe" });
}
private void Button_Click(object sender, RoutedEventArgs e)
{
this.myObjectList[0].Last = "BillyOBrian";
}
private void dg_TargetUpdated(object sender, DataTransferEventArgs e)
{
dg.Columns[0].Width = 0;
dg.UpdateLayout();
dg.Columns[0].Width = new DataGridLength(1, DataGridLengthUnitType.Star);
}
}
public class MyObject : INotifyPropertyChanged
{
private string firstName;
public string First
{
get { return this.firstName; }
set
{
if (this.firstName != value)
{
this.firstName = value;
NotifyPropertyChanged("First");
}
}
}
private string lastName;
public string Last
{
get { return this.lastName; }
set
{
if (this.lastName != value)
{
this.lastName = value;
NotifyPropertyChanged("Last");
}
}
}
public MyObject() { }
#region -- INotifyPropertyChanged Contract --
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
#endregion INotifyPropertyChanged Contract
}
}
i have had similar problem with my listview, the solution i found on how-to-autosize-and-right-align-gridviewcolumn-data-in-wpf here on stackoverflow.
In my case it was adding this piece of code into collectionchanged event handler of the observable collection the list view was bound to:
void listview_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) {
// this is a listview control
GridView view = this.View as GridView;
foreach(GridViewColumn c in view.Columns) {
if(double.IsNaN(c.Width)) {
c.Width = c.ActualWidth;
}
c.Width = double.NaN;
}
}
It works for me, although sometimes the user can notice "blink" over the columns.
WPF will just resize a datagrid's column width set to Auto if needed, i.e: the content cannot be displayed entirely. So when the content's width shrinks, the column does not resize as the content can still been seen entirely.
the only way I can see to force wpf to recalculate the columns' widths would be to force them all to 0 and then back to auto in the code behind, with one or two updateLayout() thrown in, but this is not very nice programming :-/
basically, in your code behind:
foreach (DataGridColumn c in dg.Columns)
c.Width = 0;
// Update your DG's source here
foreach (DataGridColumn c in dg.Columns)
c.Width = DataGridLength.Auto;
and you probably need a dg.UpdateLayout() or two somewhere in there (after the update and the setting back to auto probably)
One way you could solve this is by defining the width property of the column in a style setting and binding that setting to a property of the object you are binding to.
<DataGridTextColumn Binding="{Binding Change}" ElementStyle="{StaticResource ChangeColumnStyle}"/>
In your ResourceDictionary:
<Style TargetType="{x:Type DataGridTextColumn }" x:Key="ChangeColumnStyle">
<Setter Property="Width" Value="{Binding ColumnWidth}"
</Style>
ColumnWidth should be a property of your object. Now if you update this property from the setter of your 'Change' property (by using some self-defined algorithm, taking stuff like font into account), and calling:
RaisePropertyChanged("ColumnWidth");
It should update your column width.
public int Change
{
get { return m_change; }
set
{
if (m_change != value)
{
m_change = value;
ColumnWidth = WidthAlgo(numberOfCharacters);
RaisePropertyChanged("Change");
RaisePropertyChanged("ColumnWidth");
}
}
}
Have you tried this?
<DataGridTextColumn Binding="{Binding Path= Id}" Header="ID" IsReadOnly="True" Width="1*" />

Categories