DataGrid bind command to cell click - wpf

Here's a quick question. I have a DataGrid that looks like this:
<DataGrid ItemsSource="{Binding Path=Sections}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=ImportName, Mode=OneWay}" Header="Imported" />
<DataGridTextColumn Binding="{Binding Path=FoundName, Mode=TwoWay}" Header="Suggested" />
</DataGrid.Columns>
</DataGrid>
I want to bind "Suggested" column cells to a command in my VM, so that each time user clicks the cell for editing, my command would execute and show a dialog for the user. I've found an interesting solution to a similar problem described here: DataGrid bind command to row select
I like the fact that it manages this from XAML without any code-behind that attaches to the cell editing event. Unfortunately, I've no idea how to convert it in a way that would allow me to bind command to cells in a specific column, rather than the entire row. Any advice in regards to that?

You can use BeginningEdit event in the DataGrid control to handle this scenario. This event will fires before a row or cell enters edit mode. You can identify the selected column from the EventArgs.
Example:
private void dgName_BeginningEdit(object sender, DataGridBeginningEditEventArgs e)
{
if (e.Column.Header.ToString() == "Suggested")
{
//Do Operation
}
}
If you are using MVVM pattern, there are options to pass EventArgs to VM. If you are uusing MVVMLight Toolkit, there is an option called PassEventArgs and set it to TRUE.
In VM,
//Relay Command
private RelayCommand<DataGridBeginningEditEventArgs> _cellBeginningEditCommand;
public RelayCommand<DataGridBeginningEditEventArgs> CellBeginningEditCommand
{
get
{
return _cellBeginningEditCommand ?? (_cellBeginningEditCommand = new RelayCommand<DataGridBeginningEditEventArgs>(CellBeginningEditMethod));
}
}
//Command Handler
private void CellBeginningEditMethod(DataGridBeginningEditEventArgs args)
{
if(args.Column.Header.ToString() == "Suggested")
{
//Do Operation
}
}

Related

WPF Datagrid Items Refresh lose focus

Refreshing my datagrid when my observableCollection gets updated in the viewmodel have been a nightmare. After I discover the DataGrid won't respond to the events raised by the ObservableCollection I discovered DataGrid.Items.Refresh.
It does refresh but then the DataGrid loses focus. I have a simple list and I want to change a value when I press a key and then update. Its unacceptable the user have to pick the mouse again when using keyboard shortcuts ...
Here is a simple example:
<DataGrid x:Name="MyDataGrid" SelectionMode="Single" AutoGenerateColumns="False" IsReadOnly="True" KeyUp="MyDataGrid_KeyUp">
<DataGrid.Columns>
<DataGridTextColumn Header="First Name" Binding="{Binding Path=First}"/>
<DataGridTextColumn Header="Last Name" Binding="{Binding Path=Last}"/>
</DataGrid.Columns>
</DataGrid>
And the code behind:
private void MyDataGrid_KeyUp(object sender, KeyEventArgs e)
{
if (e.Key.Equals(Key.Space))
{
MyDataGrid.Items.Refresh();
}
}
p.s. In this example I'm setting the ItemsSource in my code behind and not binding to a ObservableCollection. Also i'm using just the codebehind and not a ViewModel but the problem is the same.
edit: The initial problem was that I wasnt using the NotifyPropertyChanged in my class. However, the problem here presented is still "open", I can't really understand the lost focus question when I do the Refresh()
Refreshing my datagrid when my observableCollection gets updated in the viewmodel have been a nightmare. - Why has this been a nightmare? Should be easy though.
Regarding your problem. Please try the following
private void MyDataGrid_KeyUp(object sender, KeyEventArgs e)
{
if (e.Key.Equals(Key.Space))
{
MyDataGrid.Items.Refresh();
MyDataGrid.Focus();
}
}
You can find the related doc here.
Edit
Let's try this one
private void MyDataGrid_KeyUp(object sender, KeyEventArgs e)
{
if (e.Key.Equals(Key.Space))
{
MyDataGrid.Items.Refresh();
FocusManager.SetFocusedElement(MyDataGrid);
}
}
For more information, please have a look here.
Scheduling the refresh via dispatcher worked for me (with a TreeView).
So instead of doing this (loses focus):
tree.Items.Refresh();
I do this (does not lose focus):
Dispatcher.BeginInvoke(new Action(() => tree.Items.Refresh()));
No idea why but it works for me.

How can I perform a custom action on the data of a DataGridRow when it is selected?

I've been trying to figure out how to get this custom behaviour into a datagrid with out having much look when searching online for solutions.
Given the following datagrid (some xaml removed for brevity):
<DataGrid ItemsSource="{Binding Items}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTemplateColumn Width="auto">
<DataGridTemplateColumn.HeaderTemplate>
<DataTemplate>
<CheckBox />
</DataTemplate>
</DataGridTemplateColumn.HeaderTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Selected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
I have the checkbox successfully bound to the databound object for each row. (Note: I'm using a DataGridTemplateColumn rather than DataGridCheckBoxColumn so that you do not need to double-click to change the value).
What I would like to achieve is to have the ability to tick the checkbox / update the Selected property of the databound object when the user selects a row. Effectively making the entire row click set the checked property of the checkbox. Ideally, I'd like to do this without a code behind file if possible as I'm trying to keep my code behinds as clean as possible.
Another feature which I would like, if possible, would be that clicking on a row would toggle it's selected property so that if you click on another one, the previous one stays selected as well as the new one.
Any help is much appreciated.
For clarity. I understood
Another feature which I would like, if possible, would be that
clicking on a row would toggle it's selected property so that if you
click on another one, the previous one stays selected as well as the
new one.
in the way, that you want the CheckBox of the an item, respectively the Selected property on the items ViewModel, to stay selected, when the next DataGridRow is selected, but not the DataGridRow itself? Is that correct?
My suggestion is to extend the behavior of your DataGrid using *WPF behavior*s (This is a good introduction. This way you can keep your codebehind clear, but don't have to twist XAML to make it do what you want.
This is basically the idea of behaviors: Writing testable code, which is not coupled to your concrete view, but nonetheless allowing you to write complicated stuff in 'real' code and not in XAML. In my opinion your case is a typical task for behaviors.
Your behavior could look about as simple as this.
public class CustomSelectionBehavior : Behavior<DataGrid>
{
protected override void OnAttached()
{
// Set mode to single to be able to handle the cklicked item alone
AssociatedObject.SelectionMode = DataGridSelectionMode.Single;
AssociatedObject.SelectionChanged += AssociatedObject_SelectionChanged;
}
protected override void OnDetaching()
{
AssociatedObject.SelectionChanged -= AssociatedObject_SelectionChanged;
}
private void AssociatedObject_SelectionChanged(object sender, SelectionChangedEventArgs args)
{
// Get DataContext of selected row
var item = args.AddedItems.OfType<ItemViewModel>();
// Toggle Selected property
item.Selected = !item.Selected;
}
}
Attaching the behavior to your specific DataGrid, is done in XAML:
<DataGrid ...>
<i:Interaction.Behaviors>
<b:CustomSelectionBehavior />
</i:Interaction.Behaviors>
...
</DataGrid>
You need to reference
System.Windows.Interactivity.dll
which contains the Behavior<T> baseclass as well.

How do i handle cell double click event on WPF DataGrid, equivalent to windows DataGrid's Events?

As you know, in windows C#'s gridview, if we want to handle a click/double click event on cell then there are events like CellClick, CellDoubleClick, etc.
So, i wanna do same like as windows gridview with WPF DataGrid. I have searched so far but neither answer is applicable nor useful. Some of them says use the MouseDoubleClick event but, in this event, we have to check for each row as well as item in that row, so it is time consuming to check every cell for data and timing is most important here.
My DataGrid is bounded to DataTable and AutoGeneratedColumn is False. If your answer is based on AutoGeneratedColumn=True then it is not possible. Even, i 'm changing the styles of datagrid cell according to data, so there is no way to change AutoGeneratedColumn property.
A Cell Clicking/Double Clicking event should be as faster as windows grid's event. If it is possible then tell me how, and if not, then what is the alternative to do it?
Please Help Me.....
Thanks a lot....
I know this may be a little late to the party, but this might be useful to someone else down the road.
In your MyView.xaml:
<DataGrid x:Name="MyDataGrid" ...>
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridCell}">
<EventSetter Event="MouseDoubleClick" Handler="DataGridCell_MouseDoubleClick"/>
</Style>
</DataGrid.Resources>
<!-- TODO: The rest of your DataGrid -->
</DataGrid>
In your MyView.xaml.cs:
private void DataGridCell_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
var dataGridCellTarget = (DataGridCell)sender;
// TODO: Your logic here
}
An alternative way would to be define a DataGridTemplateColumn instead of using the predefined columns like DataGridCheckBoxColumn, DataGridComboBoxColumn and then add an event handler to the UI element defined in the data template.
Below I have defined a MouseDown event handler for a TextBlock Cell.
<DataGrid AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock MouseDown="TextBlock_MouseDown"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
In the Code behind file:
private void TextBlock_MouseDown(object sender, MouseButtonEventArgs e)
{
TextBlock block = sender as TextBlock;
if (block != null)
{
// Some Logic
// block.Text
}
}
I know coding WPF is sometimes a PITA. Here you would have to handle the MouseDoubleClick event anyway. Then search the source object hierarchy to find a DataGridRow and do whatever with it.
UPDATE: Sample code
XAML
<dg:DataGrid MouseDoubleClick="OnDoubleClick" />
Code behind
private void OnDoubleClick(object sender, MouseButtonEventArgs e)
{
DependencyObject source = (DependencyObject) e.OriginalSource;
var row = GetDataGridRowObject(source);
if (row == null)
{
return;
}
else
{
// Do whatever with it
}
e.Handled = true;
}
private DataGridRow GetDataGridRowObject(DependencyObject source)
{
// Write your own code to recursively traverse up via the source
// until you find a DataGridRow object. Otherwise return null.
}
}
I have used something like this:
<DataGrid.InputBindings>
<MouseBinding Gesture="LeftDoubleClick" Command="{Binding ShowOverlay}" CommandParameter="{Binding Parameter}" />
</DataGrid.InputBindings>
And handle my commands in my View Model.

How to make a WPF DataGrid display a ViewModel collection with binding and a DataTemplate

First off, an apology as I am sure this is question's answer is quite simple. Nonetheless I still cannot seem to find an answer.
This is my first week using WPF. I simply wish to display the contents of some sort of list inside of a DataGrid. I am currently trying to use an ObservableCollection<> and a DataGrid, but that can change. How do I DataTemplate the list and display it?
The list is in a ViewModel, which has been set in Apps.xaml.cs as the DataSource for the MainWindow.xaml:
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
var window = new MainWindow();
// Create the ViewModel to which
// the main window binds.
var viewModel = new MainWindowViewModel();
// Allow all controls in the window to
// bind to the ViewModel by setting the
// DataContext, which propagates down
// the element tree.
window.DataContext = viewModel;
window.Show();
}
Here is the current list:
public ObservableCollection<PersonData> SearchResults { get; set; }
As for my xaml, though, I am rather lost -- I've tried fiddling around with binding and ItemsSource, and really have no idea how to use them here. Also, I suppose using a DataTemplate will be necessary, as I need to let it know somehow that the columns need to display certain properties of the PersonData, such as first and last name, location, and title. Any help is appreciated.
EDIT
More generally -- how does one simply display a ViewModel list, collection, what have you, period?
Your basic DataGrid syntax should look something like this:
<DataGrid ItemsSource="{Binding SearchResults}"
AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="First Name" Binding="{Binding FirstName}"/>
<DataGridTextColumn Header="Last Name" Binding="{Binding LastName}" />
</DataGrid.Columns>
</DataGrid>
If you don't want to display your data in a DataGrid, you can use an ItemsControl.
The default ItemsControl will add all your items to a StackPanel, and draw each of them using a TextBlock bound to the .ToString() of the item. You can change how the ItemsControl displays the items by specifying your own ItemsPanelTemplate and ItemTemplate for it to use. For some examples, check out my blog post on WPF's ItemsControl.
Ach. Failure to pre-study is fatal -- I did not implement INotifyPropertyChanged. Found out how to here: http://msdn.microsoft.com/en-us/library/ms743695

How to select all the text when the edit textbox in a DataGridTemplateColumn receives focus?

I'm trying to get a DataGridTemplateColumn to behave identically to a TextColumn
when the cell goes into edit mode (Press F2), the user can immediately start typing in the new value
by default, existing text content is selected - so that you can set new values easily
Got the first one done ; however selecting all the text isn't working. As mentioned by a number of posts, tried hooking into the GotFocus event and selecting all the text in code-behind. This worked for a standalone textbox ; however for a Textbox which is the edit control for a TemplateColumn, this doesn't work.
Any ideas?
Code Sample:
<Window.Resources>
<Style x:Key="HighlightTextBoxStyle" TargetType="{x:Type TextBox}">
<EventSetter Event="GotFocus" Handler="SelectAllText"/>
<EventSetter Event="GotMouseCapture" Handler="SelectAllText"/>
<Setter Property="Background" Value="AliceBlue"/>
</Style>
<DataTemplate x:Key="DefaultTitleTemplate">
<TextBlock Text="{Binding Title}"/>
</DataTemplate>
<DataTemplate x:Key="EditTitleTemplate">
<TextBox x:Name="Fox"
FocusManager.FocusedElement="{Binding RelativeSource={RelativeSource Self}}"
Text="{Binding Path=Title, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource HighlightTextBoxStyle}">
</TextBox>
</DataTemplate>
</Window.Resources>
<DockPanel>
<TextBox DockPanel.Dock="Top" x:Name="Test" Text="{Binding Path=(FocusManager.FocusedElement).Name, ElementName=MyWindow}"
Style="{StaticResource HighlightTextBoxStyle}"/>
<toolkit:DataGrid ItemsSource="{Binding Items}" AutoGenerateColumns="False">
<toolkit:DataGrid.Columns>
<toolkit:DataGridTemplateColumn Header="Templated Title"
CellTemplate="{StaticResource DefaultTitleTemplate}"
CellEditingTemplate="{StaticResource EditTitleTemplate}" />
<toolkit:DataGridTextColumn Header="Title" Binding="{Binding Path=Title}" />
</toolkit:DataGrid.Columns>
</toolkit:DataGrid>
</DockPanel>
Missed updating the post with an answer...
The problem seems to be that for a custom data grid column (aka a DataGridTemplateColumn) the grid has no way of knowing the exact type of the editing control (which is specified via a DataTemplate and could be anything). For a DataGridTextColumn, the editing control type is known and hence the grid can find it and invoke a SelectAll() in it.
So to achieve the end-goal for a TemplateColumn, you need to provide an assist. I forgotten how I solved it the first time around.. but here is something that I searched-tweaked out today. Create a custom derivation of a TemplateColumn with an override of the PrepareCellForEdit method as shown below (Swap Textbox with your exact editing control).
public class MyCustomDataColumn : DataGridTemplateColumn
{
protected override object PrepareCellForEdit(FrameworkElement editingElement, RoutedEventArgs editingEventArgs)
{
var contentPresenter = editingElement as ContentPresenter;
var editingControl = FindVisualChild<TextBox>(contentPresenter);
if (editingControl == null)
return null;
editingControl.SelectAll();
return null;
}
private static childItem FindVisualChild<childItem>(DependencyObject obj)
}
Here's an implementation for FindVisualChild.
XAML:
<WPFTestBed:MyCustomDataColumn Header="CustomColumn"
CellTemplate="{StaticResource DefaultTitleTemplate}"
CellEditingTemplate="{StaticResource EditTitleTemplate}"/>
</DataGrid.Columns>
Lot of code for an annoying inconsistency.
I know this is way late but I took a different approach and creatively extended the TextBox class. I don't really like using the boolean to check if the text is already defined but the problem is that the selection events all fire before the text is set from the binding so SelectAll() doesn't have anything to select! This class is probably only useful as a editing template in something like a DataGridTemplateColumn. Every solution I found for this issue is pretty much a hack so I don't feel too bad about this one ... :)
class AutoSelectTextBox : TextBox
{
private bool _autoSelectAll= true;
protected override void OnInitialized(EventArgs e)
{
// This will cause the cursor to enter the text box ready to
// type even when there is no content.
Focus();
base.OnInitialized(e);
}
protected override OnKeyDown(System.Windows.Input.KeyEventArgs e)
{
// This is here to handle the case of an empty text box. If
// omitted then the first character would be auto selected when
// the user starts typing.
_autoSelectAll = false;
base.OnKeyDown(e);
}
protected override void OnTextChanged(TextChangedEventArgs e)
{
if (_autoSelectAll)
{
SelectAll();
Focus();
_autoSelectAll= false;
}
base.OnTextChanged(e);
}
}
Kinda VERY late...just putting this out here in case someone can use this
I had a similar need to DeSelect (or Select All) text in a DataGridTextColumn on editing
Just added the method to the PreparingCellForEdit event of the DataGrid
DataGrid.PreparingCellForEdit += DataGrid_PreparingCellForEdit;
Then assigned the (e.EditingElement as TextBox) and then set my options
private void DataGrid_PreparingCellForEdit(object sender, DataGridPreparingCellForEditEventArgs e)
{
var txtBox = e.EditingElement as TextBox;
txtBox.Select(txtBox.Text.Length, 0); //to DeSelect all and place cursor at end
txtBox.SelectAll(); // to selectall
}

Resources