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
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))