Skip to content

Commit 367520b

Browse files
RedthCopilot
andcommitted
Add platform-neutral handler contracts for external platform backends
Every per-control handler interface in Microsoft.Maui.Core declares PlatformView through a `using PlatformView = ...` alias that resolves to a different native type per target framework. That bakes MAUI's own native types into the compiled contract, so a platform backend shipping outside dotnet/maui - which owns its own native types - cannot implement them: - CS0738 on a platform TFM, where the alias is one of MAUI's native types. - CS9333 (plus CS8766, an error under TreatWarningsAsErrors) on the neutral net11.0 TFM, where the alias is a non-nullable System.Object. Both were reproduced against Microsoft.Maui.Core 11.0.0-preview.7.26406.9. Adds three additive interfaces that carry the same strong typing without the alias: - Microsoft.Maui.IElementHandler<out TVirtualView, out TPlatformView> - Microsoft.Maui.IViewHandler<out TVirtualView, out TPlatformView> - Microsoft.Maui.ILayoutHandler<out TPlatformView> ElementHandler<,> and ViewHandler<,> implement the matching interface. Their existing public VirtualView/PlatformView members already have the right signatures, so no handler - in the box or out of it - needed a new member, and all 46 aliased handlers are covered at once. TPlatformView is covariant, so `IViewHandler<ILabel, object>` matches any label handler from any backend. ILayoutHandler<TPlatformView> carries the child-management behavior that only ILayoutHandler had. It is deliberately unrelated to ILayoutHandler by inheritance: relating them would force member hiding and break explicit interface implementations. LayoutHandler declares both. Nothing was moved, renamed or removed. Binary compatibility was verified by compiling derived and explicit-interface-implementation handlers against 11.0.0-preview.7.26406.9 and loading them against this build. Adds src/Core/tests/ExternalBackend, a separate assembly that is absent from Core's InternalsVisibleTo and builds with TreatWarningsAsErrors, so everything it compiles is provably public and warning-free for an external consumer. Tests cover contract satisfaction, covariance, handler registration, mapper composition off ViewHandler.ViewMapper, Controls' Layout command routing into an external layout handler, and that the aliased interfaces are unchanged. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent bedd1b1 commit 367520b

25 files changed

Lines changed: 1231 additions & 3 deletions

Microsoft.Maui-mac.slnf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
"src\\Core\\tests\\Benchmarks\\Core.Benchmarks.csproj",
4040
"src\\Core\\tests\\DeviceTests.Shared\\Core.DeviceTests.Shared.csproj",
4141
"src\\Core\\tests\\DeviceTests\\Core.DeviceTests.csproj",
42+
"src\\Core\\tests\\ExternalBackend\\Core.ExternalBackend.csproj",
4243
"src\\Core\\tests\\UnitTests\\Core.UnitTests.csproj",
4344
"src\\Essentials\\samples\\Sample.Server.WebAuthenticator\\Essentials.Sample.Server.WebAuthenticator.csproj",
4445
"src\\Essentials\\samples\\Samples.Server.Passkeys\\Essentials.Samples.Server.Passkeys.csproj",

Microsoft.Maui-windows.slnf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
"src\\Core\\tests\\Benchmarks\\Core.Benchmarks.csproj",
4747
"src\\Core\\tests\\DeviceTests.Shared\\Core.DeviceTests.Shared.csproj",
4848
"src\\Core\\tests\\DeviceTests\\Core.DeviceTests.csproj",
49+
"src\\Core\\tests\\ExternalBackend\\Core.ExternalBackend.csproj",
4950
"src\\Core\\tests\\UnitTests\\Core.UnitTests.csproj",
5051
"src\\Essentials\\samples\\Sample.Server.WebAuthenticator\\Essentials.Sample.Server.WebAuthenticator.csproj",
5152
"src\\Essentials\\samples\\Samples.Server.Passkeys\\Essentials.Samples.Server.Passkeys.csproj",

Microsoft.Maui.sln

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestUtils", "src\TestUtils\
3737
EndProject
3838
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Core.UnitTests", "src\Core\tests\UnitTests\Core.UnitTests.csproj", "{92644F6F-5946-48FC-A21A-A3D6EE24E8B3}"
3939
EndProject
40+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Core.ExternalBackend", "src\Core\tests\ExternalBackend\Core.ExternalBackend.csproj", "{7B2E1C4D-9A5F-4E38-8D6B-2C0F1A73E9D4}"
41+
EndProject
4042
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{E8AD265B-3C67-4640-AC58-A522F9FB3361}"
4143
EndProject
4244
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{C564DDD6-DE79-45CD-88EA-3F690481572A}"
@@ -298,6 +300,10 @@ Global
298300
{92644F6F-5946-48FC-A21A-A3D6EE24E8B3}.Debug|Any CPU.Build.0 = Debug|Any CPU
299301
{92644F6F-5946-48FC-A21A-A3D6EE24E8B3}.Release|Any CPU.ActiveCfg = Release|Any CPU
300302
{92644F6F-5946-48FC-A21A-A3D6EE24E8B3}.Release|Any CPU.Build.0 = Release|Any CPU
303+
{7B2E1C4D-9A5F-4E38-8D6B-2C0F1A73E9D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
304+
{7B2E1C4D-9A5F-4E38-8D6B-2C0F1A73E9D4}.Debug|Any CPU.Build.0 = Debug|Any CPU
305+
{7B2E1C4D-9A5F-4E38-8D6B-2C0F1A73E9D4}.Release|Any CPU.ActiveCfg = Release|Any CPU
306+
{7B2E1C4D-9A5F-4E38-8D6B-2C0F1A73E9D4}.Release|Any CPU.Build.0 = Release|Any CPU
301307
{F7F2B379-52CE-4802-9EC9-0D7967B6BFB7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
302308
{F7F2B379-52CE-4802-9EC9-0D7967B6BFB7}.Debug|Any CPU.Build.0 = Debug|Any CPU
303309
{F7F2B379-52CE-4802-9EC9-0D7967B6BFB7}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -660,6 +666,7 @@ Global
660666
{DAAC2822-63B6-4DE0-83AE-04873CD2F364} = {72397ADB-40A8-4B8E-8E08-2DBE2803C845}
661667
{FBB3270F-1924-4A72-845E-A6DF39C402F6} = {7AC28763-9C68-4BF9-A1BA-25CBFFD2D15C}
662668
{92644F6F-5946-48FC-A21A-A3D6EE24E8B3} = {C564DDD6-DE79-45CD-88EA-3F690481572A}
669+
{7B2E1C4D-9A5F-4E38-8D6B-2C0F1A73E9D4} = {C564DDD6-DE79-45CD-88EA-3F690481572A}
663670
{E8AD265B-3C67-4640-AC58-A522F9FB3361} = {09C264E9-E3F3-4586-9151-DCBB1F6DA7AB}
664671
{C564DDD6-DE79-45CD-88EA-3F690481572A} = {09C264E9-E3F3-4586-9151-DCBB1F6DA7AB}
665672
{50C758FE-4E10-409A-94F5-A75480960864} = {459BF674-83CB-46F6-881F-A2D2117DBF4D}
Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
# External platform backends and handler contracts
2+
3+
This document describes the platform-neutral handler contracts added in .NET 11, why they exist, and
4+
how an out-of-tree platform backend adopts them.
5+
6+
## The problem
7+
8+
Almost every per-control handler interface in `Microsoft.Maui.Core` declares `PlatformView` through a
9+
compile-time type alias that changes per target framework. `ILabelHandler` is representative:
10+
11+
```csharp
12+
#if __IOS__ || MACCATALYST
13+
using PlatformView = Microsoft.Maui.Platform.MauiLabel;
14+
#elif MONOANDROID
15+
using PlatformView = AndroidX.AppCompat.Widget.AppCompatTextView;
16+
#elif WINDOWS
17+
using PlatformView = Microsoft.UI.Xaml.Controls.TextBlock;
18+
#elif TIZEN
19+
using PlatformView = Tizen.UIExtensions.NUI.Label;
20+
#elif (NETSTANDARD || !PLATFORM) || (NET6_0_OR_GREATER && !IOS && !TIZEN)
21+
using PlatformView = System.Object;
22+
#endif
23+
24+
public partial interface ILabelHandler : IViewHandler
25+
{
26+
new ILabel VirtualView { get; }
27+
new PlatformView PlatformView { get; }
28+
}
29+
```
30+
31+
The alias is baked into the compiled contract. That means the interface can only ever be implemented by
32+
a handler whose native view type is *exactly* the type .NET MAUI picked for that target framework.
33+
34+
An external backend — a platform implementation that ships outside dotnet/maui, such as a standalone
35+
Tizen backend — owns its own native types, so it cannot satisfy the contract:
36+
37+
| Target framework the backend consumes | Compiled `PlatformView` type | Error |
38+
| --- | --- | --- |
39+
| A platform TFM MAUI ships (`-android`, `-ios`, `-windows`, historically `-tizen`) | MAUI's own native type | `CS0738` — the implementing member's return type does not match |
40+
| The platform-neutral TFM (`net11.0`) | `System.Object` (non-nullable) | `CS9333` — "type must be `object` to match implemented member", plus `CS8766` because `ViewHandler.PlatformView` is `object?` |
41+
42+
Both failures were reproduced against the shipped
43+
`Microsoft.Maui.Core 11.0.0-preview.7.26406.9` package with an ordinary `net11.0` class library:
44+
45+
```csharp
46+
public class MyNativeLabel { }
47+
48+
public class MyLabelHandler : ViewHandler<ILabel, MyNativeLabel>, ILabelHandler
49+
{
50+
public MyLabelHandler() : base(LabelHandler.Mapper) { }
51+
protected override MyNativeLabel CreatePlatformView() => new();
52+
ILabel ILabelHandler.VirtualView => VirtualView;
53+
MyNativeLabel ILabelHandler.PlatformView => PlatformView; // CS9333
54+
}
55+
```
56+
57+
```text
58+
error CS9333: 'MyLabelHandler.PlatformView': type must be 'object' to match implemented member 'ILabelHandler.PlatformView'
59+
error CS8766: Nullability of reference types in return type of 'object? ViewHandler.PlatformView.get'
60+
doesn't match implicitly implemented member 'object ILabelHandler.PlatformView.get'
61+
```
62+
63+
`CS8766` is a warning, but external backends that build with `TreatWarningsAsErrors` (the .NET MAUI
64+
default under `ContinuousIntegrationBuild`) see it as an error.
65+
66+
46 handler interfaces in `Microsoft.Maui.Core` use the alias pattern. `ILabelHandler`,
67+
`IContentViewHandler`, `ILayoutHandler` and `IWindowHandler` are the ones a minimal backend hits first,
68+
because they are needed for the application → window → page → content → layout → label vertical slice.
69+
70+
`ILayoutHandler` is the most damaging of the four, because it is the only one of them that carries real
71+
behavior — `Add`, `Remove`, `Clear`, `Insert`, `Update` and `UpdateZIndex` — rather than just typed
72+
accessors.
73+
74+
## The solution
75+
76+
Three new public interfaces. Nothing was moved, renamed, or removed, so the change is source and
77+
binary compatible.
78+
79+
### `IElementHandler<out TVirtualView, out TPlatformView>`
80+
81+
```csharp
82+
namespace Microsoft.Maui;
83+
84+
public interface IElementHandler<out TVirtualView, out TPlatformView> : IElementHandler
85+
where TVirtualView : IElement
86+
where TPlatformView : class
87+
{
88+
new TVirtualView VirtualView { get; }
89+
new TPlatformView PlatformView { get; }
90+
}
91+
```
92+
93+
### `IViewHandler<out TVirtualView, out TPlatformView>`
94+
95+
```csharp
96+
namespace Microsoft.Maui;
97+
98+
public interface IViewHandler<out TVirtualView, out TPlatformView>
99+
: IElementHandler<TVirtualView, TPlatformView>, IViewHandler
100+
where TVirtualView : IView
101+
where TPlatformView : class
102+
{
103+
new TVirtualView VirtualView { get; }
104+
}
105+
```
106+
107+
### `ILayoutHandler<out TPlatformView>`
108+
109+
```csharp
110+
namespace Microsoft.Maui;
111+
112+
public interface ILayoutHandler<out TPlatformView> : IViewHandler<ILayout, TPlatformView>
113+
where TPlatformView : class
114+
{
115+
void Add(IView view);
116+
void Remove(IView view);
117+
void Clear();
118+
void Insert(int index, IView view);
119+
void Update(int index, IView view);
120+
void UpdateZIndex(IView view);
121+
}
122+
```
123+
124+
### Why this is the smallest change that covers everything
125+
126+
`ElementHandler<TVirtualView, TPlatformView>` and `ViewHandler<TVirtualView, TPlatformView>` now
127+
implement the matching new interface. Their existing public `VirtualView` and `PlatformView` members
128+
already have exactly the right signatures, so **no handler anywhere needed a new member** — in the box
129+
or out of it. Every one of the 46 aliased handlers, and every third-party handler derived from
130+
`ViewHandler<,>`, satisfies the new contracts automatically.
131+
132+
`TPlatformView` is covariant, so `object` acts as the platform-neutral wildcard:
133+
134+
```csharp
135+
// Matches the in-box LabelHandler on every platform AND any external backend's label handler.
136+
if (label.Handler is IViewHandler<ILabel, object> labelHandler)
137+
{
138+
ILabel virtualView = labelHandler.VirtualView;
139+
object nativeLabel = labelHandler.PlatformView;
140+
}
141+
```
142+
143+
`ILayoutHandler<TPlatformView>` is intentionally **not** related to the existing `ILayoutHandler` by
144+
inheritance. Relating them would force `ILayoutHandler` to hide the inherited members, which would break
145+
any type that implements `Add`/`Remove`/... as *explicit* interface implementations. Keeping them
146+
unrelated means a handler that implements those members as ordinary public methods — as the in-box
147+
`LayoutHandler` does — satisfies both interfaces at once. `LayoutHandler` therefore declares both.
148+
149+
### What deliberately did not change
150+
151+
* No member was moved off `ILabelHandler`, `IContentViewHandler`, `ILayoutHandler` or `IWindowHandler`.
152+
* No aliased interface gained or lost a base interface.
153+
* Property mappers, command mappers, handler registration and `ToHandler`/`ToPlatform` are untouched.
154+
* Existing casts such as `Handler as ILayoutHandler` in the Controls compatibility layer keep working
155+
against in-box handlers exactly as before.
156+
157+
### Consuming the contracts
158+
159+
Because the neutral contracts come from `ElementHandler<,>`/`ViewHandler<,>`, any handler derived from
160+
those bases — including handlers compiled against an older .NET MAUI — satisfies
161+
`IElementHandler<TVirtualView, object>` / `IViewHandler<TVirtualView, object>` automatically.
162+
163+
`ILayoutHandler<TPlatformView>` is the exception: it declares members, so a type only satisfies it if it
164+
lists the interface. In-box `LayoutHandler` does. A pre-existing third-party type that implements
165+
`ILayoutHandler` with *explicit* interface implementations does not. Framework and consumer code that
166+
needs to cover both should test for both:
167+
168+
```csharp
169+
if (handler is ILayoutHandler<object> neutral)
170+
{
171+
neutral.Add(view);
172+
}
173+
else if (handler is ILayoutHandler legacy)
174+
{
175+
legacy.Add(view);
176+
}
177+
```
178+
179+
Binary compatibility was verified by compiling handlers against
180+
`Microsoft.Maui.Core 11.0.0-preview.7.26406.9` and loading them against a build that includes this
181+
change: derived handlers, explicit-interface-implementation handlers, and all existing `is`/cast
182+
relationships continue to work with no `TypeLoadException`.
183+
184+
## Adopting this from an external backend
185+
186+
An external backend derives from the public generic handler base classes and stops trying to implement
187+
the aliased interfaces.
188+
189+
```csharp
190+
using Microsoft.Maui;
191+
using Microsoft.Maui.Handlers;
192+
193+
// The backend's own native type. MAUI knows nothing about it.
194+
public class MyNativeLabel
195+
{
196+
public string? Text { get; set; }
197+
}
198+
199+
public class MyLabelHandler : ViewHandler<ILabel, MyNativeLabel>
200+
{
201+
public static IPropertyMapper<ILabel, MyLabelHandler> Mapper =
202+
new PropertyMapper<ILabel, MyLabelHandler>(ViewMapper)
203+
{
204+
[nameof(ILabel.Text)] = MapText,
205+
};
206+
207+
public MyLabelHandler() : base(Mapper) { }
208+
209+
protected override MyNativeLabel CreatePlatformView() => new MyNativeLabel();
210+
211+
public static void MapText(MyLabelHandler handler, ILabel label) =>
212+
handler.PlatformView.Text = label.Text;
213+
}
214+
215+
// No extra members required — this already holds:
216+
IViewHandler<ILabel, MyNativeLabel> typed = new MyLabelHandler();
217+
IViewHandler<ILabel, object> neutral = typed;
218+
```
219+
220+
A layout handler additionally declares `ILayoutHandler<TPlatformView>` so that consumers can drive child
221+
management without knowing the backend:
222+
223+
```csharp
224+
public class MyLayoutHandler : ViewHandler<ILayout, MyNativeLayout>, ILayoutHandler<MyNativeLayout>
225+
{
226+
public static CommandMapper<ILayout, MyLayoutHandler> CommandMapper =
227+
new CommandMapper<ILayout, MyLayoutHandler>(ViewCommandMapper)
228+
{
229+
// Referencing ILayoutHandler for its member *names* is always allowed;
230+
// only implementing it is blocked. These are the keys Controls' Layout raises.
231+
[nameof(ILayoutHandler.Add)] = MapAdd,
232+
[nameof(ILayoutHandler.Remove)] = MapRemove,
233+
[nameof(ILayoutHandler.Clear)] = MapClear,
234+
[nameof(ILayoutHandler.Insert)] = MapInsert,
235+
[nameof(ILayoutHandler.Update)] = MapUpdate,
236+
[nameof(ILayoutHandler.UpdateZIndex)] = MapUpdateZIndex,
237+
};
238+
239+
public void Add(IView view) { /* ... */ }
240+
public void Remove(IView view) { /* ... */ }
241+
public void Clear() { /* ... */ }
242+
public void Insert(int index, IView view) { /* ... */ }
243+
public void Update(int index, IView view) { /* ... */ }
244+
public void UpdateZIndex(IView view) { /* ... */ }
245+
246+
// ...
247+
}
248+
```
249+
250+
Handler registration is unchanged:
251+
252+
```csharp
253+
builder.ConfigureMauiHandlers(handlers =>
254+
{
255+
handlers.AddHandler<Label, MyLabelHandler>();
256+
handlers.AddHandler<VerticalStackLayout, MyLayoutHandler>();
257+
});
258+
```
259+
260+
## Tests
261+
262+
`src/Core/tests/ExternalBackend` is a separate assembly
263+
(`Microsoft.Maui.Core.ExternalBackend.TestAssembly`) that is deliberately **not** in Core's
264+
`InternalsVisibleTo` list and builds with `TreatWarningsAsErrors`. It contains fake native view types and
265+
handlers for label, content view, layout and window, so everything it compiles is provably public API and
266+
provably warning-free for an external consumer.
267+
268+
`src/Core/tests/UnitTests/Handlers/ExternalPlatformBackendTests.cs` exercises it: contract satisfaction,
269+
covariance, handler registration, property-mapper composition off `ViewHandler.ViewMapper`, Controls'
270+
`Layout` command routing reaching an external layout handler, and backward-compatibility assertions that
271+
the aliased interfaces are unchanged.
272+
273+
## Follow-up coverage
274+
275+
The generic contracts cover every handler automatically, so no per-control follow-up is required for
276+
recognition or typed access. Two areas remain for a fully self-contained external backend and are
277+
tracked separately:
278+
279+
1. **Behavior-carrying handler interfaces.** `ILayoutHandler` is the only one of the four required
280+
interfaces with behavior, and it now has a neutral counterpart. Other interfaces that carry behavior
281+
rather than just accessors (for example `IScrollViewHandler`) can get the same treatment on demand,
282+
following the `ILayoutHandler<TPlatformView>` pattern.
283+
2. **Handler infrastructure that is still `internal` or TFM-locked.** `ViewHandler.ContainerView`'s
284+
setter, `MauiContext.AddSpecific`, `LifecycleEventServiceExtensions.InvokeLifecycleEvents`,
285+
`Microsoft.Maui.Handlers.LayoutExtensions` and `HandlerNotFoundException` are not reachable from an
286+
external assembly. Those are separate from the interface-shape problem this document addresses.

src/Core/src/Handlers/Element/ElementHandlerOfT.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace Microsoft.Maui.Handlers
44
{
5-
public abstract partial class ElementHandler<TVirtualView, TPlatformView> : ElementHandler, IElementHandler
5+
public abstract partial class ElementHandler<TVirtualView, TPlatformView> : ElementHandler, IElementHandler, IElementHandler<TVirtualView, TPlatformView>
66
where TVirtualView : class, IElement
77
where TPlatformView : class
88
{
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
namespace Microsoft.Maui
2+
{
3+
/// <summary>
4+
/// Defines a strongly typed, platform-neutral element handler contract.
5+
/// </summary>
6+
/// <typeparam name="TVirtualView">The cross-platform (virtual view) type this handler manages.</typeparam>
7+
/// <typeparam name="TPlatformView">The native type this handler creates.</typeparam>
8+
/// <remarks>
9+
/// <para>
10+
/// The per-control handler interfaces such as <see cref="Handlers.IWindowHandler"/> declare
11+
/// <c>PlatformView</c> using a <c>using PlatformView = ...</c> alias that resolves to a different
12+
/// native type for every target framework. That makes them impossible to implement from an
13+
/// external platform backend whose native types are not the ones .NET MAUI ships in the box:
14+
/// the compiler reports <c>CS0738</c> (or <c>CS9333</c> on the platform-neutral target framework)
15+
/// because the implementing member's return type does not match the alias exactly.
16+
/// </para>
17+
/// <para>
18+
/// This interface has no such alias. Both type parameters are covariant, so a handler can be
19+
/// matched platform-neutrally by testing for <c>IElementHandler&lt;IWindow, object&gt;</c>
20+
/// regardless of which native type it actually creates.
21+
/// </para>
22+
/// <para>
23+
/// <see cref="Handlers.ElementHandler{TVirtualView, TPlatformView}"/> implements this interface,
24+
/// so any handler derived from it — in the box or in an external backend — satisfies the contract
25+
/// without writing any additional members.
26+
/// </para>
27+
/// </remarks>
28+
/// <example>
29+
/// <code lang="csharp"><![CDATA[
30+
/// // In an external platform backend, with a native type MAUI knows nothing about:
31+
/// public class MyWindowHandler : ElementHandler<IWindow, MyNativeWindow>
32+
/// {
33+
/// public MyWindowHandler() : base(WindowHandler.Mapper) { }
34+
/// protected override MyNativeWindow CreatePlatformElement() => new MyNativeWindow();
35+
/// }
36+
///
37+
/// // Consumers match it platform-neutrally:
38+
/// if (window.Handler is IElementHandler<IWindow, object> windowHandler)
39+
/// {
40+
/// IWindow virtualView = windowHandler.VirtualView;
41+
/// object nativeWindow = windowHandler.PlatformView;
42+
/// }
43+
/// ]]></code>
44+
/// </example>
45+
public interface IElementHandler<out TVirtualView, out TPlatformView> : IElementHandler
46+
where TVirtualView : IElement
47+
where TPlatformView : class
48+
{
49+
/// <summary>
50+
/// Gets the cross-platform virtual view associated with the handler.
51+
/// </summary>
52+
new TVirtualView VirtualView { get; }
53+
54+
/// <summary>
55+
/// Gets the native view associated with the handler.
56+
/// </summary>
57+
new TPlatformView PlatformView { get; }
58+
}
59+
}

0 commit comments

Comments
 (0)