Migrating Custom-Drawn Controls from Xamarin.Forms to Uno Platform
This guide explores how to migrate custom-drawn controls from Xamarin.Forms to Uno Platform. Custom-drawn controls use low-level graphics APIs to render custom visuals, and the good news is that the most common approach – using SkiaSharp – works seamlessly with Uno Platform.
Custom-Drawn Controls in Xamarin.Forms
While there are many ways to create custom-drawn controls, the main implementation you'll encounter in multi-platform Xamarin.Forms controls is SkiaSharp. SkiaSharp is a cross-platform .NET API based on the Skia 2D graphics library. Skia was created by Google but is available on all mainstream platforms, allowing you to create drawing code that works across devices regardless of their specific implementation.
SKCanvasView in Xamarin.Forms
When building a custom control for Xamarin.Forms, you use the SKCanvasView. This provides a view that contains the OnPaintSurface method, which you implement in your derived class to draw to the screen. The method arguments provide access to the SKSurface, which represents the raw surface you can draw to.
The SKCanvasView handles resizing and calls your OnPaintSurface method to redraw as required. Your code considers the size of the surface, as you should expect this to vary between devices or at runtime due to screen rotation or moveable UI elements.
SkiaSharp and Uno Platform
The great news is that the SkiaSharp project already has support for Uno Platform through the SkiaSharp.Views.Uno NuGet package.
SKXamlCanvas Control
The SKXamlCanvas control is the equivalent to the SKCanvasView from Xamarin.Forms, but it inherits from the WinUI Canvas class. This control:
- Handles lifecycle and size changes
- Contains an overridable
OnPaintSurfacemethod where you can draw to the surface - Works consistently across all Uno Platform targets
Because of this, you probably don't need to change any of the SkiaSharp drawing code from an existing control – you simply:
- Create a library with a class derived from
SKXamlCanvas - Hook up the drawing logic from your Xamarin.Forms control
Note
While SKXamlCanvas is the most straightforward option for migrating existing Xamarin.Forms controls (because its API closely matches SKCanvasView), the Uno Platform documentation recommends using SKCanvasElement on Skia-based targets when possible. SKCanvasElement is designed for Uno's Skia renderers, providing hardware acceleration and avoiding additional buffer copies, which can improve performance on platforms such as WebAssembly, Skia GTK, and Skia WPF.
Migration Example: Microcharts
Microcharts is a simple open-source charting library written in C# that utilizes SkiaSharp to draw a wide range of chart types. It had implementations for Xamarin.Forms, .NET MAUI, UWP, and others but initially had no Uno support.
The great thing about having UWP support is that the code was already written for the Windows version of SKCanvasView and could be used with minimal changes in an Uno library.
Creating the Uno Library
To add Uno support, a new project was created: Microcharts.Uno.WinUI. This is created as a .NET Standard library, so it has no platform dependencies itself. The required steps:
- Add a reference to
SkiaSharp.Views.Uno.WinUI - Add a reference to
Uno.WinUI - Link to the existing UWP
ChartView.csfile
Because the SkiaSharp.Views libraries depend on the base SkiaSharp library, you don't need to explicitly add it separately.
Code Changes for WinUI 3.0 Support
The only necessary changes in the code were to support WinUI 3.0 (and hence the Uno.WinUI implementation) by changing the using statements. This is handled using an #if directive so that the single file can be retained in multiple projects:
#if WINUI
using Microsoft.UI.Xaml;
using SkiaSharp.Views.Windows;
#else
using Windows.UI.Xaml;
using SkiaSharp.Views.UWP;
#endif
The OnPaintCanvas Method
As described above, the drawing is carried out by the OnPaintCanvas method:
private void OnPaintCanvas(object sender, SKPaintSurfaceEventArgs e)
{
if (this.chart != null)
{
this.chart.Draw(e.Surface.Canvas, e.Info.Width, e.Info.Height);
}
else
{
e.Surface.Canvas.Clear(SKColors.Transparent);
}
}
The chart object is an instance of Microcharts.Chart and is the same across all implementations of Microcharts. It contains the Draw method, which takes the Skia canvas and size and does all the drawing based on the properties set on it and the data source.
This means that on all platforms, you can access the Chart property via XAML or code and have the same API regardless of platform.
Creating Your Own Custom-Drawn Control
Step 1: Add SkiaSharp Package
Add the SkiaSharp.Views.Uno.WinUI NuGet package to your project:
<PackageReference Include="SkiaSharp.Views.Uno.WinUI" Version="2.88.0" />
Step 2: Create a Custom Control Class
Create a class that inherits from SKXamlCanvas:
using SkiaSharp;
using SkiaSharp.Views.Windows;
public class MyCustomControl : SKXamlCanvas
{
public MyCustomControl()
{
PaintSurface += OnPaintSurface;
}
private void OnPaintSurface(object sender, SKPaintSurfaceEventArgs e)
{
var canvas = e.Surface.Canvas;
var info = e.Info;
// Clear the canvas
canvas.Clear(SKColors.White);
// Your custom drawing code here
using (var paint = new SKPaint())
{
paint.Color = SKColors.Blue;
paint.IsAntialias = true;
paint.Style = SKPaintStyle.Fill;
// Example: Draw a circle in the center
var centerX = info.Width / 2f;
var centerY = info.Height / 2f;
var radius = Math.Min(info.Width, info.Height) / 4f;
canvas.DrawCircle(centerX, centerY, radius, paint);
}
}
}
Step 3: Use the Control in XAML
<Page x:Class="MyApp.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MyApp">
<Grid>
<local:MyCustomControl Width="400" Height="400"/>
</Grid>
</Page>
Adding Bindable Properties
To make your custom control more flexible, add dependency properties that can be data-bound:
public class MyCustomControl : SKXamlCanvas
{
public static readonly DependencyProperty FillColorProperty =
DependencyProperty.Register(
nameof(FillColor),
typeof(Color),
typeof(MyCustomControl),
new PropertyMetadata(Colors.Blue, OnPropertyChanged));
public Color FillColor
{
get => (Color)GetValue(FillColorProperty);
set => SetValue(FillColorProperty, value);
}
private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// Invalidate the control to trigger a redraw
if (d is MyCustomControl control)
{
control.Invalidate();
}
}
private void OnPaintSurface(object sender, SKPaintSurfaceEventArgs e)
{
var canvas = e.Surface.Canvas;
var info = e.Info;
canvas.Clear(SKColors.White);
using (var paint = new SKPaint())
{
// Convert WinUI Color to SKColor
var color = FillColor;
paint.Color = new SKColor(color.R, color.G, color.B, color.A);
paint.IsAntialias = true;
paint.Style = SKPaintStyle.Fill;
var centerX = info.Width / 2f;
var centerY = info.Height / 2f;
var radius = Math.Min(info.Width, info.Height) / 4f;
canvas.DrawCircle(centerX, centerY, radius, paint);
}
}
}
Now you can bind or set the color from XAML:
<local:MyCustomControl Width="400"
Height="400"
FillColor="Red"/>
Performance Considerations
Invalidation
Call Invalidate() on your control when you need to trigger a redraw. This is typically done in property change handlers.
Caching
For complex drawings that don't change often, consider caching rendered content:
private SKBitmap cachedBitmap;
private void OnPaintSurface(object sender, SKPaintSurfaceEventArgs e)
{
if (cachedBitmap == null || needsRedraw)
{
// Render to cached bitmap
cachedBitmap = new SKBitmap(info.Width, info.Height);
using (var canvas = new SKCanvas(cachedBitmap))
{
// Draw your complex graphics here
}
needsRedraw = false;
}
// Draw the cached bitmap to the surface
e.Surface.Canvas.DrawBitmap(cachedBitmap, 0, 0);
}
Hardware Acceleration
SkiaSharp automatically uses hardware acceleration when available. Ensure your drawing code is efficient:
- Reuse
SKPaintobjects when possible - Dispose of resources properly
- Avoid creating objects inside the paint loop
Common SkiaSharp Drawing Operations
Drawing Shapes
// Rectangle
canvas.DrawRect(x, y, width, height, paint);
// Circle
canvas.DrawCircle(centerX, centerY, radius, paint);
// Oval
canvas.DrawOval(new SKRect(left, top, right, bottom), paint);
// Line
canvas.DrawLine(x1, y1, x2, y2, paint);
// Path
using (var path = new SKPath())
{
path.MoveTo(x1, y1);
path.LineTo(x2, y2);
path.LineTo(x3, y3);
path.Close();
canvas.DrawPath(path, paint);
}
Drawing Text
using (var paint = new SKPaint())
{
paint.Color = SKColors.Black;
paint.TextSize = 24;
paint.IsAntialias = true;
paint.TextAlign = SKTextAlign.Center;
canvas.DrawText("Hello, Uno!", x, y, paint);
}
Drawing Images
using (var stream = Assembly.GetExecutingAssembly()
.GetManifestResourceStream("MyApp.Assets.image.png"))
using (var bitmap = SKBitmap.Decode(stream))
{
canvas.DrawBitmap(bitmap, x, y);
}
Applying Effects
using (var paint = new SKPaint())
{
paint.Color = SKColors.Blue;
// Shadow
paint.ImageFilter = SKImageFilter.CreateDropShadow(2, 2, 4, 4, SKColors.Black);
// Blur
paint.ImageFilter = SKImageFilter.CreateBlur(5, 5);
canvas.DrawCircle(centerX, centerY, radius, paint);
}
Using Microcharts in an Uno App
The Microcharts.Samples.Uno project demonstrates how to use Microcharts in an Uno Platform app. The project structure:
- Shared project: Contains XAML and code-behind for the app
- Platform projects: Windows, Mobile, Skia.GTK, WebAssembly
- Samples project: Contains example data used by all sample projects
This shows how easy it is to share or port code between different .NET platforms, as all the logic is platform-agnostic.
Migration Checklist
When migrating custom-drawn controls from Xamarin.Forms to Uno Platform:
- Identify all controls that use
SKCanvasView - Add
SkiaSharp.Views.Uno.WinUINuGet package to your Uno project - Change control base class from
SKCanvasViewtoSKXamlCanvas - Update using statements to include
SkiaSharp.Views.Windows - Rename
OnPaintSurfaceevent handler if needed - Convert
BindablePropertytoDependencyPropertyfor any custom properties - Update XAML namespaces from Xamarin.Forms to WinUI
- Test on all target platforms
- Verify performance is acceptable
Summary
Migrating custom-drawn controls from Xamarin.Forms to Uno Platform is straightforward when using SkiaSharp:
- Minimal code changes: Most SkiaSharp drawing code can be reused as-is
- Same API: The drawing API remains consistent across platforms
- SKXamlCanvas: Drop-in replacement for
SKCanvasView - Platform support: Works on all Uno Platform targets
- Shared code: Drawing logic can be completely platform-agnostic
The key difference is changing from SKCanvasView to SKXamlCanvas and updating namespaces – the actual drawing code remains the same.
Next Steps
- Continue with Migrating Custom Controls
- Return to Overview
Sample Code
The complete Microcharts code, including the Uno libraries and sample app, is available in the Microcharts GitHub repository. The Microcharts.Uno package is also available on NuGet for easy integration into your own app.