How to catch event when DataGrid's row was edited and get all values from it?
If I use RowEditEnding event, I can't get new values...
Thanks!
See the discussion here, and this solution:
private void OnRowEditEnding(object sender, DataGridRowEditEndingEventArgs e)
{
DataGrid dataGrid = sender as DataGrid;
if (e.EditAction == DataGridEditAction.Commit) {
ListCollectionView view = CollectionViewSource.GetDefaultView(dataGrid.ItemsSource) as ListCollectionView;
if (view.IsAddingNew || view.IsEditingItem) {
this.Dispatcher.BeginInvoke(new DispatcherOperationCallback(param =>
{
// This callback will be called after the CollectionView
// has pushed the changes back to the DataGrid.ItemSource.
// Write code here to save the data to the database.
return null;
}), DispatcherPriority.Background, new object[] { null });
}
}
}
Related
I'm starting out in wpf.
I have a TreeView in which each item has a checkbox.
I'm trying to create an animation in which The checkboxes are checked programmatically inside a loop.
After researching the topic for some time, I came up with the following method -
private void Traverse_Click(object sender, RoutedEventArgs e)
{
ItemCollection items = tvMain.Items;
Task.Factory.StartNew( ()=>
Dispatcher.Invoke( (Action)(() =>
{
foreach (TreeViewItem item in items)
{
UIElement elemnt = getCheckbox();
if (elemnt != null)
{
CheckBox chk = (CheckBox)elemnt;
chk.IsChecked = !chk.IsChecked;
tvMain.Items.Refresh();
tvMain.UpdateLayout();
Thread.Sleep(500);
}
}
})));
And yet despite all of my attempts the the tree doesn't update inside the loop, only at the end. so the checkboxes are all checked at once.
How can I make the tree update inside the loop?
Thanks
Replace Thread.Sleep with Task.Delay to "sleep" asynchronously:
private async void Traverse_Click(object sender, RoutedEventArgs e)
{
ItemCollection items = tvMain.Items;
foreach (TreeViewItem item in items)
{
UIElement elemnt = getCheckbox();
if (elemnt != null)
{
CheckBox chk = (CheckBox)elemnt;
chk.IsChecked = !chk.IsChecked;
await Task.Delay(500);
}
}
}
WPF Datagrid it is adding new row while pressing enter on first cell I want it to add new row once I press last cell of Datagrid.
Please check demo app is here:
WPFDemo
Thank you,
Jitendra Jadav
The DataGrid also adds a new row even when you don't even press ENTER. If you don't want this behaviour, you would probably be better of setting the CanUserAddRows property to false and add the items yourself to the source collection. Something like this:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
dataGrid.CanUserAddRows = false;
//add blank row
var itemsSource = dataGrid.ItemsSource as ObservableCollection<ItemModel>;
if (itemsSource != null)
itemsSource.Add(new ItemModel());
Loaded += MainWindow_Loaded;
dataGrid.PreviewKeyDown += DataGrid_PreviewKeyDown;
}
private void DataGrid_PreviewKeyDown(object sender, KeyEventArgs e)
{
if(e.Key == Key.Enter)
{
if (Keyboard.FocusedElement is UIElement elementWithFocus)
{
if (dataGrid.Columns.Count - 1 == dataGrid.CurrentCell.Column.DisplayIndex)
{
var itemsSource = dataGrid.ItemsSource as ObservableCollection<ItemModel>;
if (itemsSource != null)
{
var newItem = new ItemModel();
itemsSource.Add(newItem);
dataGrid.SelectedItem = newItem;
Dispatcher.BeginInvoke(new Action(()=>
{
DataGridRow row = dataGrid.ItemContainerGenerator.ContainerFromItem(newItem) as DataGridRow;
DataGridCell cell = Helper.GetCell(dataGrid, row, 0);
if (cell != null)
dataGrid.CurrentCell = new DataGridCellInfo(cell);
}), DispatcherPriority.Background);
}
}
else
{
elementWithFocus.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
e.Handled = true;
}
}
}
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
Helper.SelectRowByIndex(dataGrid, 0);
}
}
I know this question is asked before, but I couldnt find what I am looking for.
private void dataGrid1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (oOrdItem.ItemNo == 0)
{
e.Handled = true;
MessageBox.Show("Please save the order item", "Save");
return;
}
}
Even if I call e.Handled = true;
it will select the datagrid row. I dont want to call dataGrid1.SelectedIndex =-1; because it will trigger selectionchanged event again. I also tried dataGrid1.UnSelectAll();
Any other way to cancel the selectionchanged event?
I used a variety of methods to try to cancel the selection changed event, including the method from the selected answer, but none of them worked. This, however, worked great for me:
Using the PreviewMouseDown event-handler for the datagrid:
private void dataGrid_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
//get the item I am clicking on (replace MyDataClass with datatype in datagrid)
var myItem = (e.OriginalSource as FrameworkElement).DataContext as MyDataClass;
//check if item is different from currently selected item
if (myItem != dataGrid.SelectedItem)
{
//save message dialog
MessageBoxResult result = MessageBox.Show("Changes will be lost. Are you sure?", "Confirmation", MessageBoxButton.YesNo, MessageBoxImage.Question);
//if click no, then cancel the event
if (result == MessageBoxResult.No)
{
e.Handled = true;
}
else
{
//otherwise, reinvoke the click event
dataGrid.Dispatcher.BeginInvoke(
new Action(() =>
{
RoutedEventArgs args = new MouseButtonEventArgs(e.MouseDevice, 0, e.ChangedButton);
args.RoutedEvent = UIElement.MouseDownEvent;
(e.OriginalSource as UIElement).RaiseEvent(args);
}),
System.Windows.Threading.DispatcherPriority.Input);
}
}
}
}
This successfully keeps the current row selected if the user clicks "No", and if they click "Yes", then execution will continue as normal. Hopefully this helps someone in the future, because it took a long time to find something that would work for a seemingly simple problem.
Did you think about an alternative implementation? I'm thinking on Binding and a check method before changing the SelectedItem. An illustration:
<DataGrid ItemsSource="..." SelectedItem="{Binding SelectedEntry}" />
and the underlying VM could look like this:
public class SampleVm : ViewModelBase//assuming that you are using such a base class
{
private object _selectedEntry;
public object SelectedEntry
{
get { return _selectedEntry; }
set
{
if (!SavePrevItem())
return;
_selectedEntry = value;
RaisePropertyChanged("SelectedItem"); // or something similar
}
}
private bool SavePrevItem()
{
// your logic here
}
}
I have datagrid with another datagrid in the rowdetails and I am not able to sort the details view
I have tried the following but no success :(
The main datagrid is populated in the following manner:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
NamskeidinDomConTxt = new Namskeidin_DomainContext();
this.NamskeidinDomConTxt.Load(this.NamskeidinDomConTxt.GetNamskeidQuery(), LoadBehavior.RefreshCurrent, loadOperation =>
{
PagedCollectionView pcView = new PagedCollectionView(loadOperation.Entities);
pcView.SortDescriptions.Add(new SortDescription("Heiti", ListSortDirection.Ascending));
namskeidDataGrid.ItemsSource = pcView;
}, null);
}
The datagrid in the detailsrow of the main datagrid is populated in the following manner:
First I catch the following event and get the details datagrid.
private void namskeidsHlutarDataGrid_RowDetailsVisibilityChanged(object sender, DataGridRowDetailsEventArgs e)
{
verkefniDataGrid = (e.DetailsElement as FrameworkElement).FindName("VerkefniDataGrid") as DataGrid;
Verkefni_DomConTxt = new Verkefni_DomainContext();
}
and then the this event fiers so I can fill the details datagrid when I have got the id:
private void namskeidsHlutarDataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
DataGrid dataGrid = sender as DataGrid;
var item = dataGrid.SelectedItem;
if (item != null)
{
nHlutaId = ((Entity)item).GetIdentity().ToString();
Verkefni_DomConTxt.Load(Verkefni_DomConTxt.GetVerkefniQuery().Where(v => v.NamskeidsHluta_ID == nHlutaId),
LoadBehavior.RefreshCurrent, loadOperation =>
{
verkefniDataGrid.ItemsSource = loadOperation.Entities;
}, null);
}
}
private void GridName_RowDetailsVisibilityChanged(object sender, DataGridRowDetailsEventArgs e)
{
var dataGrid = (e.DetailsElement as FrameworkElement).FindName("detailsDataGrid") as DataGrid;
PagedCollectionView pcView = new PagedCollectionView(dataGrid.ItemsSource as IEnumerable);
pcView.GroupDescriptions.Clear();
pcView.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));
pcView.Refresh();
}
There are actualy three datagrids details/details
Can anyone help me with this?
You're creating a new PagedCollectionView but your DataGrid's ItemsSource is still bound to the plain IEnumerable. You'll need to bind the ItemsSource to your PagedCollectionView.
Updated: So based on your updated code you should just load the child grid the same way you load the parent grid. So instead of:
Verkefni_DomConTxt.Load(Verkefni_DomConTxt.GetVerkefniQuery().Where(v => v.NamskeidsHluta_ID == nHlutaId), LoadBehavior.RefreshCurrent,
loadOperation => { verkefniDataGrid.ItemsSource = loadOperation.Entities;}, null);
You could just create your data view here and set the items source to point to it:
Verkefni_DomConTxt.Load(Verkefni_DomConTxt.GetVerkefniQuery().Where(v => v.NamskeidsHluta_ID == nHlutaId), LoadBehavior.RefreshCurrent,
loadOperation => {
PagedCollectionView pcView = new PagedCollectionView(loadOperation.Entities);
pcView.GroupDescriptions.Clear();
pcView.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));
verkefniDataGrid.ItemsSource = pcView;
}, null);
And then get rid of your other event that tries to create the view.
I want to enable the user to highlight a row on the WPF DataGrid and press delete key to delete the row.
the functionality is already built into the UI of the grid, so to the user, the row disappears
I currently handle this on the SelectionChanged event (code below)
I loop through all the "e.RemovedItems" and delete them with LINQ
Problem is: even when you simply select a row and move off of it, selection change is fired and that row is in e.RemovedItems (which is odd, why would simply selecting something put it in a RemovedItems container?).
So I am looking for a DeleteKeyPressed event so I can simply handle it. What is that event called?
I am using the March 2009 toolkit.
XAML:
<Grid DockPanel.Dock="Bottom">
<toolkit:DataGrid x:Name="TheDataGrid"
SelectionChanged="TheDataGrid_SelectionChanged"
AutoGenerateColumns="True"
RowEditEnding="TheDataGrid_RowEditEnding"/>
code-behind:
private void TheDataGrid_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
if (e.RemovedItems.Count > 0)
{
Message.Text = "The following were removed: ";
foreach (object obj in e.RemovedItems)
{
Customer customer = obj as Customer;
Message.Text += customer.ContactName + ",";
_db.Order_Details.DeleteAllOnSubmit(
customer.Orders.SelectMany(o => o.Order_Details));
_db.Orders.DeleteAllOnSubmit(customer.Orders);
_db.Customers.DeleteOnSubmit(customer);
}
}
try
{
_db.SubmitChanges();
}
catch (Exception ex)
{
Message.Text = ex.Message;
}
}
ANSWER:
Thanks lnferis, that was exactly what I was looking for, here is my finished delete handling event for the datagrid, note the KeyDown event doesn't fire for some reason.
XAML:
<toolkit:DataGrid x:Name="TheDataGrid"
KeyDown="TheDataGrid_KeyDown"
PreviewKeyDown="TheDataGrid_PreviewKeyDown"
AutoGenerateColumns="True"
RowEditEnding="TheDataGrid_RowEditEnding"/>
code-behind
private void TheDataGrid_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Delete)
{
var grid = (DataGrid)sender;
if (grid.SelectedItems.Count > 0)
{
string checkMessage = "The following will be removed: ";
foreach (var row in grid.SelectedItems)
{
Customer customer = row as Customer;
checkMessage += customer.ContactName + ",";
}
checkMessage = Regex.Replace(checkMessage, ",$", "");
var result = MessageBox.Show(checkMessage, "Delete", MessageBoxButton.OKCancel);
if (result == MessageBoxResult.OK)
{
foreach (var row in grid.SelectedItems)
{
Customer customer = row as Customer;
_db.Order_Details.DeleteAllOnSubmit(
customer.Orders.SelectMany(o => o.Order_Details));
_db.Orders.DeleteAllOnSubmit(customer.Orders);
_db.Customers.DeleteOnSubmit(customer);
}
_db.SubmitChanges();
}
else
{
foreach (var row in grid.SelectedItems)
{
Customer customer = row as Customer;
LoadData();
_db.Refresh(System.Data.Linq.RefreshMode.OverwriteCurrentValues, customer); //TODO: this doesn't refresh the datagrid like the other instance in this code
}
}
}
}
}
private void TheDataGrid_KeyDown(object sender, KeyEventArgs e)
{
Console.WriteLine("never gets here for some reason");
}
The RemovedItems items reflects the items removed from the selection, and not from the grid.
Handle the PreviewKeyDown event, and use the SelectedItems property to delete the selected rows there:
private void PreviewKeyDownHandler(object sender, KeyEventArgs e) {
var grid = (DataGrid)sender;
if ( Key.Delete == e.Key ) {
foreach (var row in grid.SelectedItems) {
... // perform linq stuff to delete here
}
}
}
XAML
<DataGrid ItemsSource="{Binding}" CommandManager.PreviewCanExecute="Grid_PreviewCanExecute" />
Code behind
private void Grid_PreviewCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
DataGrid grid = (DataGrid)sender;
if (e.Command == DataGrid.DeleteCommand)
{
if (MessageBox.Show(String.Format("Would you like to delete {0}", (grid.SelectedItem as Person).FirstName), "Confirm Delete", MessageBoxButton.OKCancel) != MessageBoxResult.OK)
e.Handled = true;
}
}
What are you binding your DataGrid to?
Ideally, you should react to CollectionChanged events on the collection you are binding to. That way, your logic (deletion of removed items) will be separated from your UI.
You can build an Observable collection containing your objects and bind it to ItemsSource just for that purpose if the original collection does not have the necessary events.
It might not suit your specific setup, but that's how I usually do it.
Please follow the below code. I have succeeded with the below code.
Please let me know if changes are required.
private void grdEmployee_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Device.Target.GetType().Name == "DataGridCell")
{
if (e.Key == Key.Delete)
{
MessageBoxResult res = MessageBox.Show("Are you sure want to delete?", "Confirmation!", MessageBoxButton.YesNo,MessageBoxImage.Question);
e.Handled = (res == MessageBoxResult.No);
}
}
}
A little late to the party, but to get Inferis answer working:
Dim isEditing = False
AddHandler dg.BeginningEdit, Sub() isEditing = True
AddHandler dg.RowEditEnding, Sub() isEditing = False
AddHandler dg.PreviewKeyDown, Sub(obj, ev)
If e.Key = Key.Delete AndAlso Not isEditing Then ...
This fixes epalms comment: "if you're editing a cell and use the delete key to remove some characters in the cell, you'll end up deleting the whole row"
The cleanest solution is to use PreviewCanExecute like answered by flux, this is a completed solution to make it a bit more clear for anybody that overlooked his answer like I did:
private void Grid_PreviewCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
if (e.Command == DataGrid.DeleteCommand)
{
if (MessageBox.Show($"Delete something from something else?", "Confirm removal of something", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
// Do what ever needs to be done when someone deletes the row
}
else
{
e.Handled = true;
// Handled means.. no worries, I took care of it.. and it will not delete the row
}
}
}
No need to hook on to CommandManager.Executed after this.
You want to handle the KeyUp or KeyDown event and check the pressed Key for Delete.
private void OnKeyDown(object sender, KeyEventArgs e) {
if ( Key.Delete == e.Key ) {
// Delete pressed
}
}