diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/BindingNavigator.cs b/src/System.Windows.Forms/src/System/Windows/Forms/BindingNavigator.cs
index 59c4a0029af..6a9a8dfd7d4 100644
--- a/src/System.Windows.Forms/src/System/Windows/Forms/BindingNavigator.cs
+++ b/src/System.Windows.Forms/src/System/Windows/Forms/BindingNavigator.cs
@@ -767,6 +767,14 @@ private void OnBindingSourceStateChanged(object sender, EventArgs e)
RefreshItemsInternal();
}
+ ///
+ /// Refresh tool strip items when the BindingSource is disposed.
+ ///
+ private void OnBindingSourceDisposed(object sender, EventArgs e)
+ {
+ BindingSource = null;
+ }
+
///
/// Refresh tool strip items when something changes in the BindingSource's list.
///
@@ -895,6 +903,7 @@ private void WireUpBindingSource(ref BindingSource oldBindingSource, BindingSour
oldBindingSource.DataSourceChanged -= new EventHandler(OnBindingSourceStateChanged);
oldBindingSource.DataMemberChanged -= new EventHandler(OnBindingSourceStateChanged);
oldBindingSource.ListChanged -= new ListChangedEventHandler(OnBindingSourceListChanged);
+ oldBindingSource.Disposed -= new EventHandler(OnBindingSourceDisposed);
}
if (newBindingSource != null)
@@ -905,6 +914,7 @@ private void WireUpBindingSource(ref BindingSource oldBindingSource, BindingSour
newBindingSource.DataSourceChanged += new EventHandler(OnBindingSourceStateChanged);
newBindingSource.DataMemberChanged += new EventHandler(OnBindingSourceStateChanged);
newBindingSource.ListChanged += new ListChangedEventHandler(OnBindingSourceListChanged);
+ newBindingSource.Disposed += new EventHandler(OnBindingSourceDisposed);
}
oldBindingSource = newBindingSource;
diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/DataGridView.Methods.cs b/src/System.Windows.Forms/src/System/Windows/Forms/DataGridView.Methods.cs
index 725b43104ff..53cd5eeaf4b 100644
--- a/src/System.Windows.Forms/src/System/Windows/Forms/DataGridView.Methods.cs
+++ b/src/System.Windows.Forms/src/System/Windows/Forms/DataGridView.Methods.cs
@@ -14903,6 +14903,14 @@ protected virtual void OnDataSourceChanged(EventArgs e)
}
}
+ ///
+ /// Refresh items when the DataSource is disposed.
+ ///
+ private void OnDataSourceDisposed(object sender, EventArgs e)
+ {
+ DataSource = null;
+ }
+
protected virtual void OnDefaultCellStyleChanged(EventArgs e)
{
if (e is DataGridViewCellStyleChangedEventArgs dgvcsce && !dgvcsce.ChangeAffectsPreferredSize)
diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/DataGridView.cs b/src/System.Windows.Forms/src/System/Windows/Forms/DataGridView.cs
index 435e0db6979..a3a01730fe6 100644
--- a/src/System.Windows.Forms/src/System/Windows/Forms/DataGridView.cs
+++ b/src/System.Windows.Forms/src/System/Windows/Forms/DataGridView.cs
@@ -2029,6 +2029,16 @@ public object DataSource
{
if (value != DataSource)
{
+ if (DataSource is Component oldDataSource)
+ {
+ oldDataSource.Disposed -= OnDataSourceDisposed;
+ }
+
+ if (value is Component newDataSource)
+ {
+ newDataSource.Disposed += OnDataSourceDisposed;
+ }
+
CurrentCell = null;
if (DataConnection is null)
{
diff --git a/src/System.Windows.Forms/tests/UnitTests/BindingNavigatorTests.cs b/src/System.Windows.Forms/tests/UnitTests/BindingNavigatorTests.cs
index bdebec7d348..f820bc7041d 100644
--- a/src/System.Windows.Forms/tests/UnitTests/BindingNavigatorTests.cs
+++ b/src/System.Windows.Forms/tests/UnitTests/BindingNavigatorTests.cs
@@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.ComponentModel;
+using System.Data;
using System.Diagnostics;
using Moq;
using Xunit;
@@ -129,5 +130,86 @@ public void BindingNavigator_ConstructorBool()
Assert.Equal(bn.AddNewItem, bn.Items[index++]);
Assert.Equal(bn.DeleteItem, bn.Items[index++]);
}
+
+ [WinFormsFact]
+ public void BindingNavigator_UpdatesItsItems_AfterDataSourceDisposing()
+ {
+ using BindingNavigator control = new BindingNavigator(true);
+ int rowsCount = 5;
+ BindingSource bindingSource = GetTestBindingSource(rowsCount);
+ control.BindingSource = bindingSource;
+
+ Assert.Equal("1", control.PositionItem.Text);
+ Assert.Equal($"of {rowsCount}", control.CountItem.Text);
+
+ bindingSource.Dispose();
+
+ // The BindingNavigator updates its PositionItem and CountItem values
+ // after its BindingSource is disposed
+ Assert.Equal("0", control.PositionItem.Text);
+ Assert.Equal("of 0", control.CountItem.Text);
+ }
+
+ [WinFormsFact]
+ public void BindingNavigator_BindingSource_IsNull_AfterDisposing()
+ {
+ using BindingNavigator control = new BindingNavigator();
+ BindingSource bindingSource = GetTestBindingSource(5);
+ control.BindingSource = bindingSource;
+
+ Assert.Equal(bindingSource, control.BindingSource);
+
+ bindingSource.Dispose();
+
+ Assert.Null(control.BindingSource);
+ }
+
+ [WinFormsFact]
+ public void BindingNavigator_BindingSource_IsActual_AfterOldOneIsDisposed()
+ {
+ using BindingNavigator control = new BindingNavigator(true);
+ int rowsCount1 = 3;
+ BindingSource bindingSource1 = GetTestBindingSource(rowsCount1);
+ int rowsCount2 = 5;
+ BindingSource bindingSource2 = GetTestBindingSource(rowsCount2);
+ control.BindingSource = bindingSource1;
+
+ Assert.Equal(bindingSource1, control.BindingSource);
+ Assert.Equal("1", control.PositionItem.Text);
+ Assert.Equal($"of {rowsCount1}", control.CountItem.Text);
+
+ control.BindingSource = bindingSource2;
+
+ Assert.Equal(bindingSource2, control.BindingSource);
+ Assert.Equal("1", control.PositionItem.Text);
+ Assert.Equal($"of {rowsCount2}", control.CountItem.Text);
+
+ bindingSource1.Dispose();
+
+ // bindingSource2 is actual for the BindingNavigator
+ // so it will contain correct PositionItem and CountItem values
+ // even after bindingSource1 is disposed.
+ // This test checks that Disposed events unsubscribed correctly
+ Assert.Equal(bindingSource2, control.BindingSource);
+ Assert.Equal("1", control.PositionItem.Text);
+ Assert.Equal($"of {rowsCount2}", control.CountItem.Text);
+ }
+
+ private BindingSource GetTestBindingSource(int rowsCount)
+ {
+ DataTable dt = new DataTable();
+ dt.Columns.Add("Name");
+ dt.Columns.Add("Age");
+
+ for (int i = 0; i < rowsCount; i++)
+ {
+ DataRow dr = dt.NewRow();
+ dr[0] = $"User{i}";
+ dr[1] = i * 3;
+ dt.Rows.Add(dr);
+ }
+
+ return new() { DataSource = dt };
+ }
}
}
diff --git a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/DataGridViewTests.cs b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/DataGridViewTests.cs
index 6168f1724f0..b7aaf9becfa 100644
--- a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/DataGridViewTests.cs
+++ b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/DataGridViewTests.cs
@@ -11,6 +11,7 @@
using System.Numerics;
using static System.Windows.Forms.Metafiles.DataHelpers;
using static Interop;
+using System.Data;
namespace System.Windows.Forms.Tests
{
@@ -2801,6 +2802,92 @@ public void DataGridView_OnRowHeadersWidthSizeModeChanged_NullE_ThrowsNullRefere
Assert.Throws(() => control.OnRowHeadersWidthSizeModeChanged(null));
}
+ [WinFormsFact]
+ public void DataGridView_UpdatesItsItems_AfterDataSourceDisposing()
+ {
+ using DataGridView control = new DataGridView();
+ int rowsCount = 5;
+ BindingSource bindingSource = GetTestBindingSource(rowsCount);
+ BindingContext context = new BindingContext();
+ context.Add(bindingSource, bindingSource.CurrencyManager);
+ control.BindingContext = context;
+ control.DataSource = bindingSource;
+
+ // The TestBindingSource table contains 2 columns
+ Assert.Equal(2, control.Columns.Count);
+ // The TestBindingSource table contains some rows + 1 new DGV row (because AllowUserToAddRows is true)
+ Assert.Equal(rowsCount + 1, control.Rows.Count);
+
+ bindingSource.Dispose();
+
+ // The DataGridView updates its Rows and Columns collections after its DataSource is disposed
+ Assert.Empty(control.Columns);
+ Assert.Empty(control.Rows);
+ }
+
+ [WinFormsFact]
+ public void DataGridView_DataSource_IsNull_AfterDisposing()
+ {
+ using DataGridView control = new DataGridView();
+ BindingSource bindingSource = GetTestBindingSource(5);
+ control.DataSource = bindingSource;
+
+ Assert.Equal(bindingSource, control.DataSource);
+
+ bindingSource.Dispose();
+
+ Assert.Null(control.DataSource);
+ }
+
+ [WinFormsFact]
+ public void DataGridView_DataSource_IsActual_AfterOldOneIsDisposed()
+ {
+ using DataGridView control = new DataGridView();
+ int rowsCount1 = 3;
+ BindingSource bindingSource1 = GetTestBindingSource(rowsCount1);
+ int rowsCount2 = 5;
+ BindingSource bindingSource2 = GetTestBindingSource(rowsCount2);
+ BindingContext context = new BindingContext();
+ context.Add(bindingSource1, bindingSource1.CurrencyManager);
+ control.BindingContext = context;
+ control.DataSource = bindingSource1;
+
+ Assert.Equal(bindingSource1, control.DataSource);
+ Assert.Equal(2, control.Columns.Count);
+ Assert.Equal(rowsCount1 + 1, control.Rows.Count); // + 1 is the new DGV row
+
+ control.DataSource = bindingSource2;
+
+ Assert.Equal(bindingSource2, control.DataSource);
+ Assert.Equal(2, control.Columns.Count);
+ Assert.Equal(rowsCount2 + 1, control.Rows.Count); // + 1 is the new DGV row
+
+ bindingSource1.Dispose();
+
+ // bindingSource2 is actual for the DataGridView so it will contain correct Rows and Columns counts
+ // even after bindingSource1 is disposed. This test checks that Disposed events unsubscribed correctly
+ Assert.Equal(bindingSource2, control.DataSource);
+ Assert.Equal(2, control.Columns.Count);
+ Assert.Equal(rowsCount2 + 1, control.Rows.Count); // + 1 is the new DGV row
+ }
+
+ private BindingSource GetTestBindingSource(int rowsCount)
+ {
+ DataTable dt = new DataTable();
+ dt.Columns.Add("Name");
+ dt.Columns.Add("Age");
+
+ for (int i = 0; i < rowsCount; i++)
+ {
+ DataRow dr = dt.NewRow();
+ dr[0] = $"User{i}";
+ dr[1] = i * 3;
+ dt.Rows.Add(dr);
+ }
+
+ return new() { DataSource = dt };
+ }
+
private class SubDataGridViewCell : DataGridViewCell
{
}