Skip to main content

Custom-drawn controls

If you need full control over the visual appearance of your custom control, create a new control class inheriting from Control. Override the Render method to draw the new class directly with a DrawingContext. You can even override MeasureOverride and ArrangeOverride to have the custom control participate in the layout pass.

Creating a custom-drawn control

The following example creates a simple circle control with a configurable Fill property.

using System;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Media;

namespace AvaloniaCCExample.CustomControls
{
public class CircleControl : Control
{
public static readonly StyledProperty<IBrush> FillProperty =
AvaloniaProperty.Register<CircleControl, IBrush>(nameof(Fill), Brushes.Blue);

public IBrush Fill
{
get => GetValue(FillProperty);
set => SetValue(FillProperty, value);
}

static CircleControl()
{
AffectsRender<CircleControl>(FillProperty);
}

public override void Render(DrawingContext context)
{
var radius = Math.Min(Bounds.Width, Bounds.Height) / 2;
var center = new Point(Bounds.Width / 2, Bounds.Height / 2);
context.DrawEllipse(Fill, null, center, radius, radius);
}
}
}

Notes:

  • FillProperty is a styled property, so it can be set in XAML, bound to data, and targeted by styles.
  • The static constructor calls AffectsRender, which tells Avalonia to redraw the control whenever Fill changes.
  • Render receives a DrawingContext that provides methods such as DrawEllipse, DrawRectangle, DrawLine, and DrawText.

Using in XAML

To use a custom control in XAML, add an XML namespace that maps to the CLR namespace where your control lives. Then, reference the control by its class name.

XAML
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cc="using:AvaloniaCCExample.CustomControls"
x:Class="AvaloniaCCExample.MainWindow"
Title="Avalonia Custom Control">
<cc:CircleControl Height="200" Width="200" Fill="Red" />
</Window>

The cc prefix is arbitrary. Its purpose is to map AvaloniaCCExample.CustomControls, so the XAML parser can resolve CircleControl.

info

If your control lives in a separate class library, see Custom Control Library for additional setup steps.

Invalidating rendering

Avalonia provides two mechanisms to tell the layout and rendering system that a control needs to be updated: The Affects helpers and manual invalidation.

AffectsRender, AffectsMeasure, and AffectsArrange

AffectsRender, AffectsMeasure, and AffectsArrange are static helpers. Call them in your control's static constructor to declare which properties trigger which stage of the rendering pipeline.

static CircleControl()
{
AffectsRender<CircleControl>(FillProperty);
AffectsMeasure<CircleControl>(SomeOtherProperty);
AffectsArrange<CircleControl>(YetAnotherProperty);
}
  • AffectsRender causes a repaint (i.e., calls Render again) when the property changes.
  • AffectsMeasure triggers a new measure pass, which is appropriate when a property changes the control's desired size.
  • AffectsArrange triggers a new arrange pass, which is appropriate when a property changes how the control positions its content.

Manual invalidation

To trigger a visual update of the control in response to something other than a property change (for example, a timer tick), you can call a manual invalidation on the control instance.

// Forces a repaint
InvalidateVisual();

// Forces a measure pass
InvalidateMeasure();

// Forces an arrange pass
InvalidateArrange();
tip

Use manual invalidation sparingly. Declaring property dependencies with AffectsRender is preferred because it keeps invalidation automatic and predictable.

DrawingContext methods

DrawingContext provides the following methods for rendering custom-drawn controls:

MethodDescription
DrawRectangleDraws a rectangle.
DrawEllipseDraws an ellipse.
DrawLineDraws a line between two points.
DrawGeometryDraws an arbitrary geometry path.
DrawTextDraws formatted text.
DrawImageDraws a bitmap image.
FillRectangleFills a rectangle (shorthand).

Drawing shapes

You can draw multiple shapes within a single Render override:

public override void Render(DrawingContext context)
{
var pen = new Pen(Brushes.Black, 2);

// Draw a filled rectangle
context.DrawRectangle(Brushes.LightBlue, pen, new Rect(10, 10, 100, 60));

// Draw an ellipse
context.DrawEllipse(Brushes.Orange, pen, new Point(200, 40), 50, 30);

// Draw a line
context.DrawLine(new Pen(Brushes.Red, 3), new Point(10, 100), new Point(290, 100));
}

Drawing text

You can draw formatted text using DrawText with a FormattedText object:

public override void Render(DrawingContext context)
{
var text = new FormattedText(
"Hello, Avalonia!",
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
new Typeface("Arial"),
24,
Brushes.Black);

context.DrawText(text, new Point(10, 10));
}

Clipping and transforming

Use PushClip and PushTransform to clip and/or transform regions. Both return disposable objects, so wrap them in using blocks to ensure the state is restored automatically:

public override void Render(DrawingContext context)
{
// Save and restore state with PushClip
using (context.PushClip(new Rect(0, 0, 100, 100)))
{
context.FillRectangle(Brushes.Blue, new Rect(0, 0, 200, 200));
// Only the portion within 100x100 is visible
}

// Apply a transform
using (context.PushTransform(Matrix.CreateRotation(Math.PI / 4)))
{
context.FillRectangle(Brushes.Green, new Rect(150, 50, 40, 40));
}
}

See also