I have a WPF DataGrid. The user enters some data and clicks a button to process the data. If a cell is still being edited, all of the row values are null. Is there a way to force the editing to be complete after the user presses the button so that i get the values the user has entered?
I have tried to create a minimum version. If you run it, click in the first row, first column, enter some data, click in the second column, enter some data and leave the cursor flashing there, and click the Export button, both cells show as null. I want to be able to complete the editing but I can't work out how.
MainWindow.xaml
<Window x:Class="WpfCellEdit1.MainWindow"
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:WpfCellEdit1"
mc:Ignorable="d"
Title="MainWindow" Height="300" Width="600">
<DockPanel>
<DockPanel.DataContext>
<local:MainWindowViewModel x:Name="viewModel"/>
</DockPanel.DataContext>
<ToolBarTray DockPanel.Dock="Top">
<ToolBar>
<Button x:Name="export" Content="Export" Margin="5,0" Click="export_Click"/>
</ToolBar>
</ToolBarTray>
<DataGrid x:Name="dataGrid" AutoGenerateColumns="False"
ColumnHeaderHeight="0"
SelectionUnit="Cell" SelectionMode="Single"
CanUserAddRows="False" CanUserSortColumns="False" CanUserDeleteRows="False" CanUserReorderColumns="False"
EnableColumnVirtualization="True" EnableRowVirtualization="True"/>
</DockPanel>
</Window>
MainWindow.xaml.cs
using System;
using System.Diagnostics;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace WpfCellEdit1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var col_count = viewModel.CellData.Select(ws => ws.Count).Max();
for (var col = 0; col < col_count; col++)
{
var dataGridColumn = new DataGridTextColumn()
{
Binding = new Binding(String.Format("[{0}].Value", col)) { Mode = BindingMode.TwoWay },
Header = "Column" + (col + 1).ToString(),
};
dataGrid.Columns.Add(dataGridColumn);
}
dataGrid.ItemsSource = viewModel.CellData;
}
private void export_Click(object sender, RoutedEventArgs e)
{
for (int i = 0; i < viewModel.CellData.Count; i++)
{
for (int j = 0; j < viewModel.CellData[i].Count; j++)
{
if (null == viewModel.CellData[i][j].Value)
Debug.WriteLine("cell value at ({0},{1}) is null", i, j);
else
Debug.WriteLine("cell value at ({0},{1}) is {2}", i, j, viewModel.CellData[i][j].Value);
}
}
}
}
}
MainWindowViewModel.cs
using System.Collections.Generic;
using System.ComponentModel;
namespace WpfCellEdit1
{
internal class MainWindowViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyValue)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyValue));
}
private BindingList<List<Cell>> _cellData = new BindingList<List<Cell>>()
{
new List <Cell>
{
new Cell(),
new Cell(),
},
new List <Cell>
{
new Cell(),
new Cell(),
},
};
public BindingList<List<Cell>> CellData {
get { return _cellData; }
set {
if (value != _cellData)
{
_cellData = value;
OnPropertyChanged("CellData");
}
}
}
}
}
Cell.cs
using System.ComponentModel;
namespace WpfCellEdit1
{
internal class Cell : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private object _value = null;
public object Value {
get { return _value; }
set {
if (_value != value)
{
_value = value;
OnPropertyChanged("Value");
}
}
}
}
}
This is in .Net 4.8.
Do this to commit changes to the DataGrid when a button is clicked
dataGrid.CommitEdit();