From bc0dbf944deef8209d0394dce959696593bc5135 Mon Sep 17 00:00:00 2001 From: Derek Dong Date: Fri, 12 Jun 2026 15:32:19 +0800 Subject: [PATCH 1/4] WI01087620: Add reflection-free protected static API for ZBindingContext net10 compatibility ZBindingContext (CargoWise) rewires how related CurrencyManagers subscribe to their parent's change events, driving children from CurrentChanged with CargoWise-specific policy instead of the default CurrentItemChanged handler. On .NET Framework it did this by reflecting over RelatedCurrencyManager internals. On .NET 10 those internals changed (field renamed, flags refactored) so reflection no longer works. Add a set of reflection-free protected static helpers on BindingContext so that the subclass in the external assembly can reach the necessary internal state: BindingContext: - GetParentCurrencyManager: returns the CurrencyManager driving a RelatedCurrencyManager child - DetachRelatedManagerFromParentItemChanged: detaches the default CurrentItemChanged subscription - RefreshRelatedManagerFromParent: triggers the child to refresh from the parent's current row - ResetRelatedManagerToEmptyList(IList): resets child to a safe empty placeholder list and raises position/current events, bypassing the AddNew/CancelCurrentEdit appcompat path that executes AddNew on a real business-object collection when the parent is empty - RaiseCurrentItemChangedSuppressingPushData: raises CurrentItemChanged with PushData suppressed; replaces reflection of the removed suspendPushDataInCurrentChanged field RelatedCurrencyManager (internal helpers backing the above): - ParentCurrencyManager property - DetachParentItemChangedHandler() - RefreshFromParentCurrent() - ResetToEmptyList(IList) CurrencyManager: - RaiseCurrentItemChangedSuppressingPushData(EventArgs): toggles _state flag directly Remove the now-superseded RewireParentChangeHandler / RewireRelatedCurrencyManagerParent pair. Update PublicAPI.Unshipped.txt accordingly. Companion change in WiseTechGlobal/CargoWise consumes these helpers. --- .../PublicAPI.Unshipped.txt | 6 +- .../Forms/DataBinding/BindingContext.cs | 55 ++++++++++++++----- .../Forms/DataBinding/CurrencyManager.cs | 19 +++++++ .../DataBinding/RelatedCurrencyManager.cs | 55 +++++++++++++++---- 4 files changed, 110 insertions(+), 25 deletions(-) diff --git a/src/System.Windows.Forms/PublicAPI.Unshipped.txt b/src/System.Windows.Forms/PublicAPI.Unshipped.txt index 9a731e8d912..7a4f8ce09c2 100644 --- a/src/System.Windows.Forms/PublicAPI.Unshipped.txt +++ b/src/System.Windows.Forms/PublicAPI.Unshipped.txt @@ -22,7 +22,11 @@ System.Windows.Forms.SystemColorMode.Classic = 0 -> System.Windows.Forms.SystemC System.Windows.Forms.SystemColorMode.Dark = 2 -> System.Windows.Forms.SystemColorMode System.Windows.Forms.SystemColorMode.System = 1 -> System.Windows.Forms.SystemColorMode System.Windows.Forms.VisualStyles.ComboBoxState.Focused = 5 -> System.Windows.Forms.VisualStyles.ComboBoxState -static System.Windows.Forms.BindingContext.RewireRelatedCurrencyManagerParent(System.Windows.Forms.BindingManagerBase! bindingManagerBase) -> void +static System.Windows.Forms.BindingContext.DetachRelatedManagerFromParentItemChanged(System.Windows.Forms.BindingManagerBase! bindingManagerBase) -> void +static System.Windows.Forms.BindingContext.GetParentCurrencyManager(System.Windows.Forms.BindingManagerBase! bindingManagerBase) -> System.Windows.Forms.CurrencyManager? +static System.Windows.Forms.BindingContext.RaiseCurrentItemChangedSuppressingPushData(System.Windows.Forms.CurrencyManager! currencyManager, System.EventArgs! e) -> void +static System.Windows.Forms.BindingContext.RefreshRelatedManagerFromParent(System.Windows.Forms.BindingManagerBase! bindingManagerBase) -> void +static System.Windows.Forms.BindingContext.ResetRelatedManagerToEmptyList(System.Windows.Forms.BindingManagerBase! bindingManagerBase, System.Collections.IList! emptyList) -> void virtual System.Windows.Forms.BindingContext.OnListManagerAdded(System.Windows.Forms.BindingManagerBase! bindingManagerBase) -> void virtual System.Windows.Forms.Form.OnFormBorderColorChanged(System.EventArgs! e) -> void virtual System.Windows.Forms.Form.OnFormCaptionBackColorChanged(System.EventArgs! e) -> void diff --git a/src/System.Windows.Forms/System/Windows/Forms/DataBinding/BindingContext.cs b/src/System.Windows.Forms/System/Windows/Forms/DataBinding/BindingContext.cs index a3a04cafd24..260ae557830 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/DataBinding/BindingContext.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/DataBinding/BindingContext.cs @@ -305,20 +305,49 @@ protected virtual void OnListManagerAdded(BindingManagerBase bindingManagerBase) } /// - /// Helper for subclasses overriding : if the supplied manager is a - /// child currency manager, detaches its parent's - /// subscription and re-attaches it to , then primes the child - /// once with the parent's current state. No-op for managers that are not - /// instances. Exposed because is internal and cannot be referenced from - /// subclasses in other assemblies. + /// Helper for subclasses overriding : returns the parent + /// that drives the supplied related (child) currency manager, or + /// when the manager is top-level or is not a child currency manager. Lets subclasses + /// re-wire parent/child change notifications without reflecting over the internal + /// type. /// - protected static void RewireRelatedCurrencyManagerParent(BindingManagerBase bindingManagerBase) - { - if (bindingManagerBase is RelatedCurrencyManager relatedCurrencyManager) - { - relatedCurrencyManager.RewireParentChangeHandler(); - } - } + protected static CurrencyManager? GetParentCurrencyManager(BindingManagerBase bindingManagerBase) + => (bindingManagerBase as RelatedCurrencyManager)?.ParentCurrencyManager; + + /// + /// Helper for subclasses overriding : detaches the default + /// subscription that a child currency manager wires onto + /// its parent, so the subclass can drive the child from the parent's + /// event instead. No-op for managers that are not + /// instances. + /// + protected static void DetachRelatedManagerFromParentItemChanged(BindingManagerBase bindingManagerBase) + => (bindingManagerBase as RelatedCurrencyManager)?.DetachParentItemChangedHandler(); + + /// + /// Helper for subclasses overriding : refreshes a child currency manager's + /// list from its parent's current row, exactly as the default CurrentItemChanged handler would. No-op for + /// managers that are not instances. + /// + protected static void RefreshRelatedManagerFromParent(BindingManagerBase bindingManagerBase) + => (bindingManagerBase as RelatedCurrencyManager)?.RefreshFromParentCurrent(); + + /// + /// Helper for subclasses overriding : resets a child currency manager to the + /// supplied empty placeholder list and raises position/current change events, without running the + /// AddNew/CancelCurrentEdit appcompat path that the default empty-parent handling uses. No-op for managers + /// that are not instances. + /// + protected static void ResetRelatedManagerToEmptyList(BindingManagerBase bindingManagerBase, IList emptyList) + => (bindingManagerBase as RelatedCurrencyManager)?.ResetToEmptyList(emptyList); + + /// + /// Helper for subclasses: raises on a currency manager + /// while suppressing the push of data back into bound controls, so the subclass can notify bindings that the + /// current item has gone (position -1) without WinForms trying to push data into a row that no longer exists. + /// + protected static void RaiseCurrentItemChangedSuppressingPushData(CurrencyManager currencyManager, EventArgs e) + => currencyManager.RaiseCurrentItemChangedSuppressingPushData(e); private static void CheckPropertyBindingCycles(BindingContext newBindingContext, Binding propBinding) { diff --git a/src/System.Windows.Forms/System/Windows/Forms/DataBinding/CurrencyManager.cs b/src/System.Windows.Forms/System/Windows/Forms/DataBinding/CurrencyManager.cs index ca55380fdeb..4e566062de4 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/DataBinding/CurrencyManager.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/DataBinding/CurrencyManager.cs @@ -880,6 +880,25 @@ protected internal override void OnCurrentItemChanged(EventArgs e) _onCurrentItemChangedHandler?.Invoke(this, e); } + // WiseTech: raises CurrentItemChanged while suppressing the push of data back into bound controls, so callers + // can notify bindings that the current item has gone (position -1) without WinForms trying to push data into a + // row that no longer exists. Preserves the previous suspension flag. Used by ZBindingContext on .NET 10 in + // place of the .NET Framework reflection that toggled the removed suspendPushDataInCurrentChanged field. + internal void RaiseCurrentItemChangedSuppressingPushData(EventArgs e) + { + bool previouslySuppressed = _state.HasFlag(CurrencyManagerStates.SuspendPushDataInCurrentChanged); + _state.ChangeFlags(CurrencyManagerStates.SuspendPushDataInCurrentChanged, true); + + try + { + OnCurrentItemChanged(e); + } + finally + { + _state.ChangeFlags(CurrencyManagerStates.SuspendPushDataInCurrentChanged, previouslySuppressed); + } + } + protected virtual void OnItemChanged(ItemChangedEventArgs e) { // It is possible that CurrencyManager_PushData will change the position diff --git a/src/System.Windows.Forms/System/Windows/Forms/DataBinding/RelatedCurrencyManager.cs b/src/System.Windows.Forms/System/Windows/Forms/DataBinding/RelatedCurrencyManager.cs index ffc115dfcb5..0c96d279eda 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/DataBinding/RelatedCurrencyManager.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/DataBinding/RelatedCurrencyManager.cs @@ -138,22 +138,55 @@ private void ParentManager_MetaDataChanged(object? sender, EventArgs e) // WiseTech: RelatedCurrencyManager normally refreshes its child list whenever the parent manager raises // CurrentItemChanged. In CargoWise that can be too broad: item-change notifications may be raised while // bindings/business collections are already reacting to the same edit or AddNew flow, and refreshing the - // child list from that path can re-enter the same notification chain until the stack overflows. ZBindingContext - // avoided this on .NET Framework by intercepting BindingContextHashtable.Add, removing the default - // CurrentItemChanged subscription, and refreshing the child only when the parent's CurrentChanged event fires. - // BindingContext uses a Dictionary on .NET 10, so this helper gives subclasses the same event swap without - // reflecting over RelatedCurrencyManager internals. - internal void RewireParentChangeHandler() + // child list from that path can re-enter the same notification chain until the stack overflows. Worse, the + // default empty-parent branch of ParentManager_CurrentItemChanged runs an AddNew/CancelCurrentEdit appcompat + // dance which, against a CargoWise business-object collection, actually adds and then deletes a real business + // object. ZBindingContext avoided this on .NET Framework by intercepting BindingContextHashtable.Add, removing + // the default CurrentItemChanged subscription, and driving the child from the parent's CurrentChanged event + // with CargoWise-owned policy (skip redundant refreshes; reset to a safe empty list instead of AddNew/Cancel). + // BindingContext uses a Dictionary on .NET 10, so the members below give that subclass the same mechanism + // (parent lookup, handler detach, refresh, safe empty reset) without reflecting over RelatedCurrencyManager + // internals. The when-to-refresh policy stays in ZBindingContext because it depends on CargoWise types. + + /// + /// The parent currency manager that drives this child, or when the parent is not a + /// . + /// + internal CurrencyManager? ParentCurrencyManager => _parentManager as CurrencyManager; + + /// + /// Detaches the default subscription that + /// wired onto the parent. Callers that drive the child from the parent's CurrentChanged event use this to + /// avoid double refreshing. + /// + internal void DetachParentItemChangedHandler() { - if (_parentManager is CurrencyManager parentCurrencyManager) + if (_parentManager is not null) { - parentCurrencyManager.CurrentItemChanged -= ParentManager_CurrentItemChanged; - parentCurrencyManager.CurrentChanged -= ParentManager_CurrentItemChanged; - parentCurrencyManager.CurrentChanged += ParentManager_CurrentItemChanged; - ParentManager_CurrentItemChanged(parentCurrencyManager, EventArgs.Empty); + _parentManager.CurrentItemChanged -= ParentManager_CurrentItemChanged; } } + /// + /// Refreshes the child list from the parent's current row, exactly as the default CurrentItemChanged handler + /// does. + /// + internal void RefreshFromParentCurrent() => ParentManager_CurrentItemChanged(_parentManager, EventArgs.Empty); + + /// + /// Resets this child to the supplied empty placeholder list and raises position/current change notifications, + /// bypassing the AddNew/CancelCurrentEdit appcompat path that + /// runs when the parent is empty. + /// + internal void ResetToEmptyList(IList emptyList) + { + SetDataSource(emptyList); + listposition = -1; + OnPositionChanged(EventArgs.Empty); + OnCurrentChanged(EventArgs.Empty); + OnCurrentItemChanged(EventArgs.Empty); + } + private void ParentManager_CurrentItemChanged(object? sender, EventArgs e) { if (IgnoreItemChangedTable.Contains(_parentManager)) From db1d7f010979b1653a9b2587c98b289b5ce31478 Mon Sep 17 00:00:00 2001 From: Derek Dong Date: Mon, 15 Jun 2026 17:23:36 +0800 Subject: [PATCH 2/4] revert NPR changes --- .../PublicAPI.Unshipped.txt | 3 --- .../Forms/DataBinding/BindingContext.cs | 25 ------------------- .../Forms/DataBinding/CurrencyManager.cs | 19 -------------- .../DataBinding/RelatedCurrencyManager.cs | 25 +------------------ 4 files changed, 1 insertion(+), 71 deletions(-) diff --git a/src/System.Windows.Forms/PublicAPI.Unshipped.txt b/src/System.Windows.Forms/PublicAPI.Unshipped.txt index 7a4f8ce09c2..b4a9ed5bb7b 100644 --- a/src/System.Windows.Forms/PublicAPI.Unshipped.txt +++ b/src/System.Windows.Forms/PublicAPI.Unshipped.txt @@ -24,9 +24,6 @@ System.Windows.Forms.SystemColorMode.System = 1 -> System.Windows.Forms.SystemCo System.Windows.Forms.VisualStyles.ComboBoxState.Focused = 5 -> System.Windows.Forms.VisualStyles.ComboBoxState static System.Windows.Forms.BindingContext.DetachRelatedManagerFromParentItemChanged(System.Windows.Forms.BindingManagerBase! bindingManagerBase) -> void static System.Windows.Forms.BindingContext.GetParentCurrencyManager(System.Windows.Forms.BindingManagerBase! bindingManagerBase) -> System.Windows.Forms.CurrencyManager? -static System.Windows.Forms.BindingContext.RaiseCurrentItemChangedSuppressingPushData(System.Windows.Forms.CurrencyManager! currencyManager, System.EventArgs! e) -> void -static System.Windows.Forms.BindingContext.RefreshRelatedManagerFromParent(System.Windows.Forms.BindingManagerBase! bindingManagerBase) -> void -static System.Windows.Forms.BindingContext.ResetRelatedManagerToEmptyList(System.Windows.Forms.BindingManagerBase! bindingManagerBase, System.Collections.IList! emptyList) -> void virtual System.Windows.Forms.BindingContext.OnListManagerAdded(System.Windows.Forms.BindingManagerBase! bindingManagerBase) -> void virtual System.Windows.Forms.Form.OnFormBorderColorChanged(System.EventArgs! e) -> void virtual System.Windows.Forms.Form.OnFormCaptionBackColorChanged(System.EventArgs! e) -> void diff --git a/src/System.Windows.Forms/System/Windows/Forms/DataBinding/BindingContext.cs b/src/System.Windows.Forms/System/Windows/Forms/DataBinding/BindingContext.cs index 260ae557830..cd76459445a 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/DataBinding/BindingContext.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/DataBinding/BindingContext.cs @@ -324,31 +324,6 @@ protected virtual void OnListManagerAdded(BindingManagerBase bindingManagerBase) protected static void DetachRelatedManagerFromParentItemChanged(BindingManagerBase bindingManagerBase) => (bindingManagerBase as RelatedCurrencyManager)?.DetachParentItemChangedHandler(); - /// - /// Helper for subclasses overriding : refreshes a child currency manager's - /// list from its parent's current row, exactly as the default CurrentItemChanged handler would. No-op for - /// managers that are not instances. - /// - protected static void RefreshRelatedManagerFromParent(BindingManagerBase bindingManagerBase) - => (bindingManagerBase as RelatedCurrencyManager)?.RefreshFromParentCurrent(); - - /// - /// Helper for subclasses overriding : resets a child currency manager to the - /// supplied empty placeholder list and raises position/current change events, without running the - /// AddNew/CancelCurrentEdit appcompat path that the default empty-parent handling uses. No-op for managers - /// that are not instances. - /// - protected static void ResetRelatedManagerToEmptyList(BindingManagerBase bindingManagerBase, IList emptyList) - => (bindingManagerBase as RelatedCurrencyManager)?.ResetToEmptyList(emptyList); - - /// - /// Helper for subclasses: raises on a currency manager - /// while suppressing the push of data back into bound controls, so the subclass can notify bindings that the - /// current item has gone (position -1) without WinForms trying to push data into a row that no longer exists. - /// - protected static void RaiseCurrentItemChangedSuppressingPushData(CurrencyManager currencyManager, EventArgs e) - => currencyManager.RaiseCurrentItemChangedSuppressingPushData(e); - private static void CheckPropertyBindingCycles(BindingContext newBindingContext, Binding propBinding) { Debug.Assert(newBindingContext is not null, "Always called with a non-null BindingContext"); diff --git a/src/System.Windows.Forms/System/Windows/Forms/DataBinding/CurrencyManager.cs b/src/System.Windows.Forms/System/Windows/Forms/DataBinding/CurrencyManager.cs index 4e566062de4..ca55380fdeb 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/DataBinding/CurrencyManager.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/DataBinding/CurrencyManager.cs @@ -880,25 +880,6 @@ protected internal override void OnCurrentItemChanged(EventArgs e) _onCurrentItemChangedHandler?.Invoke(this, e); } - // WiseTech: raises CurrentItemChanged while suppressing the push of data back into bound controls, so callers - // can notify bindings that the current item has gone (position -1) without WinForms trying to push data into a - // row that no longer exists. Preserves the previous suspension flag. Used by ZBindingContext on .NET 10 in - // place of the .NET Framework reflection that toggled the removed suspendPushDataInCurrentChanged field. - internal void RaiseCurrentItemChangedSuppressingPushData(EventArgs e) - { - bool previouslySuppressed = _state.HasFlag(CurrencyManagerStates.SuspendPushDataInCurrentChanged); - _state.ChangeFlags(CurrencyManagerStates.SuspendPushDataInCurrentChanged, true); - - try - { - OnCurrentItemChanged(e); - } - finally - { - _state.ChangeFlags(CurrencyManagerStates.SuspendPushDataInCurrentChanged, previouslySuppressed); - } - } - protected virtual void OnItemChanged(ItemChangedEventArgs e) { // It is possible that CurrencyManager_PushData will change the position diff --git a/src/System.Windows.Forms/System/Windows/Forms/DataBinding/RelatedCurrencyManager.cs b/src/System.Windows.Forms/System/Windows/Forms/DataBinding/RelatedCurrencyManager.cs index 0c96d279eda..ec519dce793 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/DataBinding/RelatedCurrencyManager.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/DataBinding/RelatedCurrencyManager.cs @@ -161,30 +161,7 @@ private void ParentManager_MetaDataChanged(object? sender, EventArgs e) /// internal void DetachParentItemChangedHandler() { - if (_parentManager is not null) - { - _parentManager.CurrentItemChanged -= ParentManager_CurrentItemChanged; - } - } - - /// - /// Refreshes the child list from the parent's current row, exactly as the default CurrentItemChanged handler - /// does. - /// - internal void RefreshFromParentCurrent() => ParentManager_CurrentItemChanged(_parentManager, EventArgs.Empty); - - /// - /// Resets this child to the supplied empty placeholder list and raises position/current change notifications, - /// bypassing the AddNew/CancelCurrentEdit appcompat path that - /// runs when the parent is empty. - /// - internal void ResetToEmptyList(IList emptyList) - { - SetDataSource(emptyList); - listposition = -1; - OnPositionChanged(EventArgs.Empty); - OnCurrentChanged(EventArgs.Empty); - OnCurrentItemChanged(EventArgs.Empty); + _parentManager?.CurrentItemChanged -= ParentManager_CurrentItemChanged; } private void ParentManager_CurrentItemChanged(object? sender, EventArgs e) From 37e0885fdad95d5fd8d899ebfb2fabba2a02dacf Mon Sep 17 00:00:00 2001 From: Derek Dong Date: Tue, 16 Jun 2026 09:28:34 +0800 Subject: [PATCH 3/4] Revert "revert NPR changes" This reverts commit db1d7f010979b1653a9b2587c98b289b5ce31478. --- .../PublicAPI.Unshipped.txt | 3 +++ .../Forms/DataBinding/BindingContext.cs | 25 +++++++++++++++++++ .../Forms/DataBinding/CurrencyManager.cs | 19 ++++++++++++++ .../DataBinding/RelatedCurrencyManager.cs | 25 ++++++++++++++++++- 4 files changed, 71 insertions(+), 1 deletion(-) diff --git a/src/System.Windows.Forms/PublicAPI.Unshipped.txt b/src/System.Windows.Forms/PublicAPI.Unshipped.txt index b4a9ed5bb7b..7a4f8ce09c2 100644 --- a/src/System.Windows.Forms/PublicAPI.Unshipped.txt +++ b/src/System.Windows.Forms/PublicAPI.Unshipped.txt @@ -24,6 +24,9 @@ System.Windows.Forms.SystemColorMode.System = 1 -> System.Windows.Forms.SystemCo System.Windows.Forms.VisualStyles.ComboBoxState.Focused = 5 -> System.Windows.Forms.VisualStyles.ComboBoxState static System.Windows.Forms.BindingContext.DetachRelatedManagerFromParentItemChanged(System.Windows.Forms.BindingManagerBase! bindingManagerBase) -> void static System.Windows.Forms.BindingContext.GetParentCurrencyManager(System.Windows.Forms.BindingManagerBase! bindingManagerBase) -> System.Windows.Forms.CurrencyManager? +static System.Windows.Forms.BindingContext.RaiseCurrentItemChangedSuppressingPushData(System.Windows.Forms.CurrencyManager! currencyManager, System.EventArgs! e) -> void +static System.Windows.Forms.BindingContext.RefreshRelatedManagerFromParent(System.Windows.Forms.BindingManagerBase! bindingManagerBase) -> void +static System.Windows.Forms.BindingContext.ResetRelatedManagerToEmptyList(System.Windows.Forms.BindingManagerBase! bindingManagerBase, System.Collections.IList! emptyList) -> void virtual System.Windows.Forms.BindingContext.OnListManagerAdded(System.Windows.Forms.BindingManagerBase! bindingManagerBase) -> void virtual System.Windows.Forms.Form.OnFormBorderColorChanged(System.EventArgs! e) -> void virtual System.Windows.Forms.Form.OnFormCaptionBackColorChanged(System.EventArgs! e) -> void diff --git a/src/System.Windows.Forms/System/Windows/Forms/DataBinding/BindingContext.cs b/src/System.Windows.Forms/System/Windows/Forms/DataBinding/BindingContext.cs index cd76459445a..260ae557830 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/DataBinding/BindingContext.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/DataBinding/BindingContext.cs @@ -324,6 +324,31 @@ protected virtual void OnListManagerAdded(BindingManagerBase bindingManagerBase) protected static void DetachRelatedManagerFromParentItemChanged(BindingManagerBase bindingManagerBase) => (bindingManagerBase as RelatedCurrencyManager)?.DetachParentItemChangedHandler(); + /// + /// Helper for subclasses overriding : refreshes a child currency manager's + /// list from its parent's current row, exactly as the default CurrentItemChanged handler would. No-op for + /// managers that are not instances. + /// + protected static void RefreshRelatedManagerFromParent(BindingManagerBase bindingManagerBase) + => (bindingManagerBase as RelatedCurrencyManager)?.RefreshFromParentCurrent(); + + /// + /// Helper for subclasses overriding : resets a child currency manager to the + /// supplied empty placeholder list and raises position/current change events, without running the + /// AddNew/CancelCurrentEdit appcompat path that the default empty-parent handling uses. No-op for managers + /// that are not instances. + /// + protected static void ResetRelatedManagerToEmptyList(BindingManagerBase bindingManagerBase, IList emptyList) + => (bindingManagerBase as RelatedCurrencyManager)?.ResetToEmptyList(emptyList); + + /// + /// Helper for subclasses: raises on a currency manager + /// while suppressing the push of data back into bound controls, so the subclass can notify bindings that the + /// current item has gone (position -1) without WinForms trying to push data into a row that no longer exists. + /// + protected static void RaiseCurrentItemChangedSuppressingPushData(CurrencyManager currencyManager, EventArgs e) + => currencyManager.RaiseCurrentItemChangedSuppressingPushData(e); + private static void CheckPropertyBindingCycles(BindingContext newBindingContext, Binding propBinding) { Debug.Assert(newBindingContext is not null, "Always called with a non-null BindingContext"); diff --git a/src/System.Windows.Forms/System/Windows/Forms/DataBinding/CurrencyManager.cs b/src/System.Windows.Forms/System/Windows/Forms/DataBinding/CurrencyManager.cs index ca55380fdeb..4e566062de4 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/DataBinding/CurrencyManager.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/DataBinding/CurrencyManager.cs @@ -880,6 +880,25 @@ protected internal override void OnCurrentItemChanged(EventArgs e) _onCurrentItemChangedHandler?.Invoke(this, e); } + // WiseTech: raises CurrentItemChanged while suppressing the push of data back into bound controls, so callers + // can notify bindings that the current item has gone (position -1) without WinForms trying to push data into a + // row that no longer exists. Preserves the previous suspension flag. Used by ZBindingContext on .NET 10 in + // place of the .NET Framework reflection that toggled the removed suspendPushDataInCurrentChanged field. + internal void RaiseCurrentItemChangedSuppressingPushData(EventArgs e) + { + bool previouslySuppressed = _state.HasFlag(CurrencyManagerStates.SuspendPushDataInCurrentChanged); + _state.ChangeFlags(CurrencyManagerStates.SuspendPushDataInCurrentChanged, true); + + try + { + OnCurrentItemChanged(e); + } + finally + { + _state.ChangeFlags(CurrencyManagerStates.SuspendPushDataInCurrentChanged, previouslySuppressed); + } + } + protected virtual void OnItemChanged(ItemChangedEventArgs e) { // It is possible that CurrencyManager_PushData will change the position diff --git a/src/System.Windows.Forms/System/Windows/Forms/DataBinding/RelatedCurrencyManager.cs b/src/System.Windows.Forms/System/Windows/Forms/DataBinding/RelatedCurrencyManager.cs index ec519dce793..0c96d279eda 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/DataBinding/RelatedCurrencyManager.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/DataBinding/RelatedCurrencyManager.cs @@ -161,7 +161,30 @@ private void ParentManager_MetaDataChanged(object? sender, EventArgs e) /// internal void DetachParentItemChangedHandler() { - _parentManager?.CurrentItemChanged -= ParentManager_CurrentItemChanged; + if (_parentManager is not null) + { + _parentManager.CurrentItemChanged -= ParentManager_CurrentItemChanged; + } + } + + /// + /// Refreshes the child list from the parent's current row, exactly as the default CurrentItemChanged handler + /// does. + /// + internal void RefreshFromParentCurrent() => ParentManager_CurrentItemChanged(_parentManager, EventArgs.Empty); + + /// + /// Resets this child to the supplied empty placeholder list and raises position/current change notifications, + /// bypassing the AddNew/CancelCurrentEdit appcompat path that + /// runs when the parent is empty. + /// + internal void ResetToEmptyList(IList emptyList) + { + SetDataSource(emptyList); + listposition = -1; + OnPositionChanged(EventArgs.Empty); + OnCurrentChanged(EventArgs.Empty); + OnCurrentItemChanged(EventArgs.Empty); } private void ParentManager_CurrentItemChanged(object? sender, EventArgs e) From d3c42ff0d309f0f4754575975181d95a2e883fe4 Mon Sep 17 00:00:00 2001 From: Derek Dong Date: Tue, 16 Jun 2026 11:47:07 +0800 Subject: [PATCH 4/4] add some descriptions --- .../System.Windows.Forms.Package.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/System.Windows.Forms.Legacy/System.Windows.Forms.Package/System.Windows.Forms.Package.csproj b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Package/System.Windows.Forms.Package.csproj index 3bc006f413f..fbd8cf4e9c9 100644 --- a/src/System.Windows.Forms.Legacy/System.Windows.Forms.Package/System.Windows.Forms.Package.csproj +++ b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Package/System.Windows.Forms.Package.csproj @@ -10,6 +10,7 @@ 0.1.0 $(WtgPackageVersionPrefix)-dev WiseTech Global forked version of System.Windows.Forms that includes controls that were deprecated in .NET Core 3.1 and .NET 5, like DataGrid, Menu, ToolBar and StatusBar. + HotFix - Fix ZBindingContext related-manager binding behavior on .NET 10 WiseTech Global © WiseTech Global Limited. All rights reserved. README.md