You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add ViewHandler.SetContainerView for external platform backends
ViewHandler.SetupContainer() and RemoveContainer() are protected
extensibility points, but ContainerView's setter is `private protected`.
A handler deriving from ViewHandler<TVirtualView, TPlatformView> in
another assembly - for example a platform backend that ships outside of
dotnet/maui - therefore has no way to publish the wrapper it created, so
it has to report NeedsContainer => false and loses gradient/image
backgrounds, clip and shadow.
Add a narrow `protected void SetContainerView(PlatformView?)` to
ViewHandler instead of widening the property setter. It only records the
container view: it never re-parents anything and it does not change
HasContainer, so SetupContainer/RemoveContainer stay in charge of the
lifecycle and all existing built-in handlers are untouched.
Platform type safety is preserved through a `private protected virtual`
ValidateContainerView hook. iOS/MacCatalyst and Tizen shadow
ContainerView with a WrapperView-typed property that hard-casts the base
value, so those overrides reject non-WrapperView containers with a clear
ArgumentException instead of failing later with an InvalidCastException.
Tests live in a new Core.ExternalBackend project whose assembly is
deliberately not an InternalsVisibleTo friend of Microsoft.Maui, so the
fact that it compiles proves the API is reachable externally. The new
unit tests cover setup/remove re-parenting and ordering, NeedsContainer
transitions driven through MapContainerView, repeated round trips,
no-op HasContainer assignments, direct SetContainerView install/clear,
and handler disconnect.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copy file name to clipboardExpand all lines: src/Core/src/Handlers/View/ViewHandler.cs
+81-2Lines changed: 81 additions & 2 deletions
Original file line number
Diff line number
Diff line change
@@ -144,13 +144,15 @@ public virtual bool NeedsContainer
144
144
/// <summary>
145
145
/// Constructs the <see cref="ContainerView"/> and adds <see cref="PlatformView"/> to a container.
146
146
/// </summary>
147
-
/// <remarks>This method is called when <see cref="HasContainer"/> is set to <see langword="true"/>.</remarks>
147
+
/// <remarks>This method is called when <see cref="HasContainer"/> is set to <see langword="true"/>.
148
+
/// Overrides should call <see cref="SetContainerView(PlatformView?)"/> to publish the container they created.</remarks>
148
149
protectedabstractvoidSetupContainer();
149
150
150
151
/// <summary>
151
152
/// Deconstructs the <see cref="ContainerView"/> and removes <see cref="PlatformView"/> from its container.
152
153
/// </summary>
153
-
/// <remarks>This method is called when <see cref="HasContainer"/> is set to <see langword="false"/>.</remarks>
154
+
/// <remarks>This method is called when <see cref="HasContainer"/> is set to <see langword="false"/>.
155
+
/// Overrides should call <see cref="SetContainerView(PlatformView?)"/> with <see langword="null"/> to clear the container they removed.</remarks>
154
156
protectedabstractvoidRemoveContainer();
155
157
156
158
/// <summary>
@@ -159,6 +161,83 @@ public virtual bool NeedsContainer
159
161
/// <remarks>Note that this can be <see langword="null"/>. Especially when <see cref="HasContainer"/> is set to <see langword="false"/> this value might not be set.</remarks>
/// Sets or clears the view returned by <see cref="ContainerView"/>.
166
+
/// </summary>
167
+
/// <param name="containerView">The platform view that wraps <see cref="PlatformView"/>, or <see langword="null"/> to clear the current container view.</param>
168
+
/// <remarks>
169
+
/// <para>This is the supported way for a handler that lives in another assembly - for example a platform backend that
170
+
/// ships outside of .NET MAUI - to participate in the container view lifecycle. Call it from
171
+
/// <see cref="SetupContainer"/> after the container has been created and the <see cref="PlatformView"/> has been
172
+
/// re-parented into it, and call it with <see langword="null"/> from <see cref="RemoveContainer"/> after the
173
+
/// <see cref="PlatformView"/> has been moved back to the original parent.</para>
174
+
/// <para>This method only records the container view; it never re-parents views, and it does not change
175
+
/// <see cref="HasContainer"/>. Attaching and detaching the platform views remains the responsibility of the
176
+
/// <see cref="SetupContainer"/> and <see cref="RemoveContainer"/> overrides, which .NET MAUI invokes when
177
+
/// <see cref="HasContainer"/> changes.</para>
178
+
/// <example>
179
+
/// A handler in an external backend assembly:
180
+
/// <code language="csharp">
181
+
/// public class MyBackendViewHandler<TVirtualView, TPlatformView> : ViewHandler<TVirtualView, TPlatformView>
182
+
/// where TVirtualView : class, IView
183
+
/// where TPlatformView : MyPlatformView
184
+
/// {
185
+
/// public override bool NeedsContainer =>
186
+
/// VirtualView?.Background is not null ||
187
+
/// VirtualView?.Clip is not null ||
188
+
/// VirtualView?.Shadow is not null ||
189
+
/// base.NeedsContainer;
190
+
///
191
+
/// protected override void SetupContainer()
192
+
/// {
193
+
/// if (PlatformView is null || ContainerView is not null)
194
+
/// return;
195
+
///
196
+
/// var wrapper = new MyWrapperView();
197
+
/// var parent = PlatformView.Parent;
198
+
/// parent?.Remove(PlatformView);
199
+
/// wrapper.Content = PlatformView;
200
+
/// parent?.Add(wrapper);
201
+
///
202
+
/// SetContainerView(wrapper);
203
+
/// }
204
+
///
205
+
/// protected override void RemoveContainer()
206
+
/// {
207
+
/// if (ContainerView is not MyWrapperView wrapper)
208
+
/// {
209
+
/// SetContainerView(null);
210
+
/// return;
211
+
/// }
212
+
///
213
+
/// var parent = wrapper.Parent;
214
+
/// parent?.Remove(wrapper);
215
+
/// wrapper.Content = null;
216
+
/// parent?.Add(PlatformView);
217
+
///
218
+
/// SetContainerView(null);
219
+
/// }
220
+
/// }
221
+
/// </code>
222
+
/// </example>
223
+
/// </remarks>
224
+
/// <exception cref="System.ArgumentException">Thrown when <paramref name="containerView"/> is not a container type that this handler supports.</exception>
0 commit comments