Since .NET 7, Microsoft has improved the binding capabilities of Windows Forms to bring it closer to WPF and other XAML technologies: Using Command Binding in Windows Forms apps to go Cross-Platform – .NET Blog
In WPF we can bind a ViewModel selected item property directly to a DataGrid.SelectdItem
property via XAML, but it seems that WinForms DataGridView does not have this feature:
DataGridView.SelectedRows
expects a collection of rows;DataGridView.CurrentRow
is read-only, so the binding fails;DataGridView.CurrentCell
is the way to set the current row of the grid, but it expects aDataGridViewCell
object, and I tried using it by formatting the expected value (I removed the checking code and left only what matters here):
var currentRowBinding = myDataGrid.DataBindings.Add("CurrentCell",
mViewModel.Items,
nameof(mViewModel.CurrentItem),
true);
// The Binding.Format event is fired when the value changes in the DataSource
// and needs to be displayed in the control, but needs formatting before that.
currentRowBinding.Format += CurrentRowBinding_Format;
// The Binding.Parse event is fired when the value changes in the control and needs
// to be updated in the DataSource, but it needs to be parsed/transformed before that.
currentRowBinding.Parse += CurrentRowBinding_Parse;
private void CurrentRowBinding_Format(object? sender, ConvertEventArgs e)
{
var itemIndex = mViewModel.Items.IndexOf(mViewModel.CurrentItem);
e.Value = myDataGrid[0, itemIndex];
myDataGrid.Rows[itemIndex].Selected = true;
}
private void CurrentRowBinding_Parse(object? sender, ConvertEventArgs e)
{
e.Value = myDataGrid.CurrentRow.DataBoundItem;
}
Doing this works, but the program starts to behave strangely. It seems that these events are fired multiple times each time a row is changed, and the program slow down and even some things stop working, like buttons that do nothing when clicked.
My solution was to listen to the form’s BindingContext CurrencyManager.CurrentChanged
event, to synchronize my ViewModel.CurrentItem
property with the current row selected in the grid; and create a CurrentItemChanged
event in my ViewModel, to synchronize any changes made from the ViewModel to the ViewModel.CurrentItem
property with the current row in the grid:
private void CurrencyManager_CurrentChanged(object? sender, EventArgs e)
{
if (mCurrencyManager.Current != mViewModel.CurrentItem)
{
mViewModel.CurrentItem = mCurrencyManager.Current as TViewModel;
}
}
private void ViewModel_CurrentItemChanged(object? sender, EventArgs e)
{
if (mViewModel.CurrentItem != mCurrencyManager.Current)
{
var itemIndex = mViewModel.Items.IndexOf(mViewModel.CurrentItem);
mCurrencyManager.Position = itemIndex;
}
}
Is there a better solution to this problem?