From fe414dc4cdffff46a47dd5e407b63c4b457c2ed1 Mon Sep 17 00:00:00 2001
From: Abinesh p <120440951+abineshPalanisamy@users.noreply.github.com>
Date: Thu, 4 Jun 2026 13:42:23 +0530
Subject: [PATCH 1/4] Added fix for the Fix_Issue_3260
Added fix for the Fix_Issue_3260
---
.../Forms/Application.ComponentThreadContext.cs | 16 ++++++++++------
.../Windows/Forms/Application.ThreadContext.cs | 17 +++++++++++++++++
.../System/Windows/Forms/ApplicationContext.cs | 8 ++++++++
3 files changed, 35 insertions(+), 6 deletions(-)
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Application.ComponentThreadContext.cs b/src/System.Windows.Forms/System/Windows/Forms/Application.ComponentThreadContext.cs
index ea2a1961b02..c0dc3525f00 100644
--- a/src/System.Windows.Forms/System/Windows/Forms/Application.ComponentThreadContext.cs
+++ b/src/System.Windows.Forms/System/Windows/Forms/Application.ComponentThreadContext.cs
@@ -260,11 +260,11 @@ protected override bool RunMessageLoop(msoloop reason, bool fullModal)
}
else if (reason is msoloop.DoEvents or msoloop.DoEventsModal)
{
- result = LocalModalMessageLoop(null);
+ result = LocalModalMessageLoop(form: null, resolveMainFormDynamically: false);
}
else
{
- result = LocalModalMessageLoop(CurrentForm);
+ result = LocalModalMessageLoop(form: null, resolveMainFormDynamically: true);
}
return result;
@@ -279,7 +279,7 @@ protected override void EndOuterMessageLoop()
}
}
- private bool LocalModalMessageLoop(Form? form)
+ private bool LocalModalMessageLoop(Form? form, bool resolveMainFormDynamically = false)
{
try
{
@@ -289,6 +289,10 @@ private bool LocalModalMessageLoop(Form? form)
while (continueLoop)
{
+ Form? currentForm = resolveMainFormDynamically
+ ? ApplicationContext?.MainForm ?? CurrentForm
+ : form;
+
if (PInvoke.GetMessage(&msg, HWND.Null, 0, 0))
{
if (!PreTranslateMessage(ref msg))
@@ -297,12 +301,12 @@ private bool LocalModalMessageLoop(Form? form)
PInvoke.DispatchMessage(&msg);
}
- if (form is not null)
+ if (currentForm is not null)
{
- continueLoop = !form.CheckCloseDialog(false);
+ continueLoop = !currentForm.CheckCloseDialog(false);
}
}
- else if (form is null)
+ else if (currentForm is null)
{
break;
}
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Application.ThreadContext.cs b/src/System.Windows.Forms/System/Windows/Forms/Application.ThreadContext.cs
index 45d2a615bd5..93f82d5a45e 100644
--- a/src/System.Windows.Forms/System/Windows/Forms/Application.ThreadContext.cs
+++ b/src/System.Windows.Forms/System/Windows/Forms/Application.ThreadContext.cs
@@ -95,6 +95,23 @@ public virtual void EnsureReadyForIdle() { }
internal bool CustomThreadExceptionHandlerAttached => _threadExceptionHandler is not null;
+ internal static ThreadContext? Current => t_currentThreadContext;
+
+ internal void OnMainFormChanged(ApplicationContext applicationContext, Form? oldMainForm, Form? newMainForm)
+ {
+ if (!ReferenceEquals(ApplicationContext, applicationContext))
+ {
+ return;
+ }
+
+ // Only update the cached form if it is still tracking the previous MainForm.
+ // This avoids clobbering unrelated state such as a current modal form.
+ if (ReferenceEquals(CurrentForm, oldMainForm))
+ {
+ CurrentForm = newMainForm;
+ }
+ }
+
///
/// Retrieves the actual parking form. This will demand create the parking window if it needs to.
///
diff --git a/src/System.Windows.Forms/System/Windows/Forms/ApplicationContext.cs b/src/System.Windows.Forms/System/Windows/Forms/ApplicationContext.cs
index 31be20c2f0c..28873206a43 100644
--- a/src/System.Windows.Forms/System/Windows/Forms/ApplicationContext.cs
+++ b/src/System.Windows.Forms/System/Windows/Forms/ApplicationContext.cs
@@ -54,10 +54,18 @@ public Form? MainForm
get => _mainForm;
set
{
+ if (ReferenceEquals(_mainForm, value))
+ {
+ return;
+ }
+
+ Form? oldMainForm = _mainForm;
EventHandler onClose = OnMainFormDestroy;
_mainForm?.HandleDestroyed -= onClose;
_mainForm = value;
_mainForm?.HandleDestroyed += onClose;
+
+ Application.ThreadContext.Current?.OnMainFormChanged(this, oldMainForm, value);
}
}
From 143812575d03426476c8fde5af364c07f83f23f5 Mon Sep 17 00:00:00 2001
From: Abinesh p <120440951+abineshPalanisamy@users.noreply.github.com>
Date: Thu, 9 Jul 2026 17:46:29 +0530
Subject: [PATCH 2/4] Addressed the review correction and updated the test
cases
Addressed the review correction and updated the test cases
---
.../Application.ComponentThreadContext.cs | 16 ++++------
.../Forms/Application.ThreadContext.cs | 32 +++++++------------
.../Windows/Forms/ApplicationContext.cs | 8 -----
3 files changed, 17 insertions(+), 39 deletions(-)
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Application.ComponentThreadContext.cs b/src/System.Windows.Forms/System/Windows/Forms/Application.ComponentThreadContext.cs
index c0dc3525f00..e583df9b43a 100644
--- a/src/System.Windows.Forms/System/Windows/Forms/Application.ComponentThreadContext.cs
+++ b/src/System.Windows.Forms/System/Windows/Forms/Application.ComponentThreadContext.cs
@@ -260,11 +260,11 @@ protected override bool RunMessageLoop(msoloop reason, bool fullModal)
}
else if (reason is msoloop.DoEvents or msoloop.DoEventsModal)
{
- result = LocalModalMessageLoop(form: null, resolveMainFormDynamically: false);
+ result = LocalModalMessageLoop(null);
}
else
{
- result = LocalModalMessageLoop(form: null, resolveMainFormDynamically: true);
+ result = LocalModalMessageLoop(CurrentForm);
}
return result;
@@ -279,7 +279,7 @@ protected override void EndOuterMessageLoop()
}
}
- private bool LocalModalMessageLoop(Form? form, bool resolveMainFormDynamically = false)
+ private bool LocalModalMessageLoop(Form? form)
{
try
{
@@ -289,10 +289,6 @@ private bool LocalModalMessageLoop(Form? form, bool resolveMainFormDynamically =
while (continueLoop)
{
- Form? currentForm = resolveMainFormDynamically
- ? ApplicationContext?.MainForm ?? CurrentForm
- : form;
-
if (PInvoke.GetMessage(&msg, HWND.Null, 0, 0))
{
if (!PreTranslateMessage(ref msg))
@@ -301,12 +297,12 @@ private bool LocalModalMessageLoop(Form? form, bool resolveMainFormDynamically =
PInvoke.DispatchMessage(&msg);
}
- if (currentForm is not null)
+ if (form is not null)
{
- continueLoop = !currentForm.CheckCloseDialog(false);
+ continueLoop = !form .CheckCloseDialog(false);
}
}
- else if (currentForm is null)
+ else if (form is null)
{
break;
}
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Application.ThreadContext.cs b/src/System.Windows.Forms/System/Windows/Forms/Application.ThreadContext.cs
index 93f82d5a45e..09130a6db5b 100644
--- a/src/System.Windows.Forms/System/Windows/Forms/Application.ThreadContext.cs
+++ b/src/System.Windows.Forms/System/Windows/Forms/Application.ThreadContext.cs
@@ -62,7 +62,14 @@ internal abstract unsafe partial class ThreadContext : MarshalByRefObject, IHand
// A private field on Application that stores the callback delegate
private MessageLoopCallback? _messageLoopCallback;
- protected Form? CurrentForm { get; private set; }
+ private Form? _currentFormOverride;
+
+ protected Form? CurrentForm
+ {
+ get => _currentFormOverride ?? ApplicationContext?.MainForm;
+ private set => _currentFormOverride = value;
+ }
+
protected bool PostedQuit { get; private set; }
///
@@ -95,23 +102,6 @@ public virtual void EnsureReadyForIdle() { }
internal bool CustomThreadExceptionHandlerAttached => _threadExceptionHandler is not null;
- internal static ThreadContext? Current => t_currentThreadContext;
-
- internal void OnMainFormChanged(ApplicationContext applicationContext, Form? oldMainForm, Form? newMainForm)
- {
- if (!ReferenceEquals(ApplicationContext, applicationContext))
- {
- return;
- }
-
- // Only update the cached form if it is still tracking the previous MainForm.
- // This avoids clobbering unrelated state such as a current modal form.
- if (ReferenceEquals(CurrentForm, oldMainForm))
- {
- CurrentForm = newMainForm;
- }
- }
-
///
/// Retrieves the actual parking form. This will demand create the parking window if it needs to.
///
@@ -736,8 +726,8 @@ private void RunMessageLoopInner(msoloop reason, ApplicationContext? context)
ApplicationContext.MainForm?.Visible = true;
}
- Form? oldForm = CurrentForm;
- if (context is not null)
+ Form? oldFormOverride = _currentFormOverride;
+ if (context is not null && reason != msoloop.Main)
{
CurrentForm = context.MainForm;
}
@@ -821,7 +811,7 @@ private void RunMessageLoopInner(msoloop reason, ApplicationContext? context)
}
}
- CurrentForm = oldForm;
+ _currentFormOverride = oldFormOverride;
s_totalMessageLoopCount--;
_messageLoopCount--;
diff --git a/src/System.Windows.Forms/System/Windows/Forms/ApplicationContext.cs b/src/System.Windows.Forms/System/Windows/Forms/ApplicationContext.cs
index 28873206a43..31be20c2f0c 100644
--- a/src/System.Windows.Forms/System/Windows/Forms/ApplicationContext.cs
+++ b/src/System.Windows.Forms/System/Windows/Forms/ApplicationContext.cs
@@ -54,18 +54,10 @@ public Form? MainForm
get => _mainForm;
set
{
- if (ReferenceEquals(_mainForm, value))
- {
- return;
- }
-
- Form? oldMainForm = _mainForm;
EventHandler onClose = OnMainFormDestroy;
_mainForm?.HandleDestroyed -= onClose;
_mainForm = value;
_mainForm?.HandleDestroyed += onClose;
-
- Application.ThreadContext.Current?.OnMainFormChanged(this, oldMainForm, value);
}
}
From 612966ec42b750c5f08394ef1aa297a17e4240b4 Mon Sep 17 00:00:00 2001
From: Abinesh p <120440951+abineshPalanisamy@users.noreply.github.com>
Date: Thu, 9 Jul 2026 17:47:43 +0530
Subject: [PATCH 3/4] Updated the changes for Fix_Issue_3260
Updated the changes for Fix_Issue_3260
---
.../System/Windows/Forms/Application.ComponentThreadContext.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Application.ComponentThreadContext.cs b/src/System.Windows.Forms/System/Windows/Forms/Application.ComponentThreadContext.cs
index e583df9b43a..ea2a1961b02 100644
--- a/src/System.Windows.Forms/System/Windows/Forms/Application.ComponentThreadContext.cs
+++ b/src/System.Windows.Forms/System/Windows/Forms/Application.ComponentThreadContext.cs
@@ -299,7 +299,7 @@ private bool LocalModalMessageLoop(Form? form)
if (form is not null)
{
- continueLoop = !form .CheckCloseDialog(false);
+ continueLoop = !form.CheckCloseDialog(false);
}
}
else if (form is null)
From 3938ac39a81bd6f8c7d1559c6038f4cf544e76f6 Mon Sep 17 00:00:00 2001
From: Abinesh p <120440951+abineshPalanisamy@users.noreply.github.com>
Date: Thu, 9 Jul 2026 17:50:52 +0530
Subject: [PATCH 4/4] Added the unit test cases for Fix_Issue_3260
Added the unit test cases for Fix_Issue_3260
---
.../ThreadContextTests.cs | 153 ++++++++++++++++++
1 file changed, 153 insertions(+)
diff --git a/src/test/unit/System.Windows.Forms/ThreadContextTests.cs b/src/test/unit/System.Windows.Forms/ThreadContextTests.cs
index fe00f88a411..ae0d722b863 100644
--- a/src/test/unit/System.Windows.Forms/ThreadContextTests.cs
+++ b/src/test/unit/System.Windows.Forms/ThreadContextTests.cs
@@ -110,4 +110,157 @@ public void ThreadContext_MultipleProcessFiltersProcesses()
mockContext2.Verify(c => c.PreFilterMessage(ref It.Ref.IsAny), Times.Exactly(3));
mockContext3.Verify(c => c.PreFilterMessage(ref It.Ref.IsAny), Times.Exactly(2));
}
+
+ [WinFormsFact]
+ public void ThreadContext_CurrentForm_ReflectsChangedMainForm()
+ {
+ RunOnStaThread(() =>
+ {
+ using Form form1 = new()
+ {
+ Text = "Form1"
+ };
+
+ using Form form2 = new()
+ {
+ Text = "Form2"
+ };
+
+ using ApplicationContext context = new(form1);
+
+ Form currentFormBeforeChange = null;
+ Form currentFormAfterChange = null;
+
+ form1.Shown += (_, _) =>
+ {
+ currentFormBeforeChange = GetThreadContextCurrentForm();
+
+ context.MainForm = form2;
+
+ currentFormAfterChange = GetThreadContextCurrentForm();
+
+ context.ExitThread();
+ };
+
+ Application.Run(context);
+
+ Assert.Same(form1, currentFormBeforeChange);
+ Assert.Same(form2, currentFormAfterChange);
+ });
+ }
+
+ [WinFormsFact]
+ public void ThreadContext_CurrentForm_ReflectsNullMainForm()
+ {
+ RunOnStaThread(() =>
+ {
+ using Form form = new()
+ {
+ Text = "Form1"
+ };
+
+ using ApplicationContext context = new(form);
+
+ Form currentFormBeforeChange = null;
+ Form currentFormAfterChange = null;
+
+ form.Shown += (_, _) =>
+ {
+ currentFormBeforeChange = GetThreadContextCurrentForm();
+
+ context.MainForm = null;
+
+ currentFormAfterChange = GetThreadContextCurrentForm();
+
+ context.ExitThread();
+ };
+
+ Application.Run(context);
+
+ Assert.Same(form, currentFormBeforeChange);
+ Assert.Null(currentFormAfterChange);
+ });
+ }
+
+ [WinFormsFact]
+ public void ThreadContext_CurrentForm_ReflectsMultipleMainFormChanges()
+ {
+ RunOnStaThread(() =>
+ {
+ using Form form1 = new()
+ {
+ Text = "Form1"
+ };
+
+ using Form form2 = new()
+ {
+ Text = "Form2"
+ };
+
+ using Form form3 = new()
+ {
+ Text = "Form3"
+ };
+
+ using ApplicationContext context = new(form1);
+
+ Form currentFormAfterFirstChange = null;
+ Form currentFormAfterSecondChange = null;
+
+ form1.Shown += (_, _) =>
+ {
+ context.MainForm = form2;
+ currentFormAfterFirstChange = GetThreadContextCurrentForm();
+
+ context.MainForm = form3;
+ currentFormAfterSecondChange = GetThreadContextCurrentForm();
+
+ context.ExitThread();
+ };
+
+ Application.Run(context);
+
+ Assert.Same(form2, currentFormAfterFirstChange);
+ Assert.Same(form3, currentFormAfterSecondChange);
+ });
+ }
+
+ private static void RunOnStaThread(Action action)
+ {
+ Exception exception = null;
+
+ Thread thread = new(() =>
+ {
+ try
+ {
+ action();
+ }
+ catch (Exception ex)
+ {
+ exception = ex;
+ }
+ });
+
+ thread.SetApartmentState(ApartmentState.STA);
+ thread.IsBackground = true;
+ thread.Start();
+
+ Assert.True(thread.Join(TimeSpan.FromSeconds(30)), "The STA test thread timed out.");
+
+ if (exception is not null)
+ {
+ throw exception;
+ }
+ }
+
+ private static Form GetThreadContextCurrentForm()
+ {
+ Application.ThreadContext threadContext = Application.ThreadContext.FromCurrent();
+
+ PropertyInfo currentFormProperty = typeof(Application.ThreadContext).GetProperty(
+ "CurrentForm",
+ BindingFlags.Instance | BindingFlags.NonPublic);
+
+ return (Form)currentFormProperty.GetValue(threadContext);
+ }
}