Avalonia provides a comprehensive drawing API through the DrawingContext class and related drawing primitives. The drawing system supports shapes, text, images, and custom rendering.
DrawingContext
The primary interface for drawing operations. Obtained from Render method overrides or custom draw operations.
Drawing Methods
Draws a line between two points.public override void Render(DrawingContext context)
{
var pen = new Pen(Brushes.Black, 2);
context.DrawLine(pen, new Point(0, 0), new Point(100, 100));
}
Parameters:
pen - The pen defining stroke color and thickness
p1 - Start point
p2 - End point
Draws a rectangle.context.DrawRectangle(
brush: Brushes.LightBlue,
pen: new Pen(Brushes.Navy, 1),
rect: new Rect(10, 10, 100, 50),
radiusX: 5, // Optional corner radius
radiusY: 5
);
Draws an ellipse.context.DrawEllipse(
brush: Brushes.Red,
pen: new Pen(Brushes.DarkRed, 2),
center: new Point(50, 50),
radiusX: 40,
radiusY: 30
);
Draws a geometry (complex shape).var geometry = StreamGeometry.Parse("M 0,0 L 100,0 L 50,100 Z");
context.DrawGeometry(
brush: Brushes.Green,
pen: new Pen(Brushes.DarkGreen, 1),
geometry: geometry
);
Draws an image.// Draw entire image
context.DrawImage(image, new Rect(0, 0, 100, 100));
// Draw part of image
context.DrawImage(
source: image,
sourceRect: new Rect(0, 0, 50, 50), // Source area
destRect: new Rect(10, 10, 100, 100) // Destination area
);
Draws formatted text. Note: Use FormattedText or TextLayout for advanced text rendering.var text = new FormattedText(
"Hello, World!",
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
new Typeface("Arial"),
24,
Brushes.Black
);
context.DrawText(text, new Point(10, 10));
Applies a transformation matrix. Returns a disposable that restores the previous transform.using (context.PushTransform(Matrix.CreateRotation(Math.PI / 4)))
{
// Drawing operations here are rotated 45 degrees
context.DrawRectangle(Brushes.Blue, null, new Rect(0, 0, 50, 50));
}
// Transform automatically restored
Applies an opacity level to subsequent drawing operations.using (context.PushOpacity(0.5))
{
// Drawing operations here are 50% transparent
context.DrawEllipse(Brushes.Red, null, new Point(50, 50), 40, 40);
}
Clips subsequent drawing to a rectangular region.using (context.PushClip(new Rect(0, 0, 100, 100)))
{
// Only the part within the rectangle will be visible
context.DrawEllipse(Brushes.Blue, null, new Point(150, 50), 80, 80);
}
Clips subsequent drawing to a geometry shape.var clipGeometry = new EllipseGeometry(new Rect(0, 0, 100, 100));
using (context.PushGeometryClip(clipGeometry))
{
// Drawing is clipped to ellipse shape
context.DrawRectangle(Brushes.Green, null, new Rect(0, 0, 150, 150));
}
Pen
Defines how lines and shape outlines are drawn.
Properties
The brush used for the stroke color.
The width of the stroke in device-independent pixels.
The dash pattern for the stroke (solid, dashed, dotted, etc.).
The shape at the start and end of lines. Values: Flat, Round, Square.
How line segments join. Values: Miter, Bevel, Round.
The limit on the ratio of the miter length to half the thickness.
Usage Examples
// Solid pen
var solidPen = new Pen(Brushes.Black, 2);
// Dashed pen
var dashedPen = new Pen(Brushes.Blue, 1)
{
DashStyle = DashStyle.Dash
};
// Custom dash pattern
var customPen = new Pen(Brushes.Red, 3)
{
DashStyle = new DashStyle(new[] { 2.0, 1.0 }, 0), // 2 on, 1 off
LineCap = PenLineCap.Round,
LineJoin = PenLineJoin.Round
};
Geometry Classes
Define complex shapes for drawing.
StreamGeometry
Efficient geometry created from path data.
var geometry = StreamGeometry.Parse("M 0,0 L 100,0 L 100,100 L 0,100 Z");
context.DrawGeometry(Brushes.Yellow, new Pen(Brushes.Black), geometry);
// Or build programmatically
var streamGeometry = new StreamGeometry();
using (var ctx = streamGeometry.Open())
{
ctx.BeginFigure(new Point(0, 0), isFilled: true);
ctx.LineTo(new Point(100, 0));
ctx.LineTo(new Point(100, 100));
ctx.LineTo(new Point(0, 100));
ctx.EndFigure(isClosed: true);
}
Built-in Geometries
// Rectangle
var rect = new RectangleGeometry(new Rect(0, 0, 100, 50));
// Ellipse
var ellipse = new EllipseGeometry(new Rect(0, 0, 100, 100));
// Line
var line = new LineGeometry(new Point(0, 0), new Point(100, 100));
// Polyline
var polyline = new PolylineGeometry(
new[] { new Point(0, 0), new Point(50, 100), new Point(100, 0) },
isFilled: false
);
// Combined geometries
var combined = new GeometryGroup()
{
Children = new GeometryCollection { rect, ellipse },
FillRule = FillRule.EvenOdd
};
Drawing Class
Represents a drawing that can be rendered.
GeometryDrawing
Combines a geometry with a brush and/or pen.
var drawing = new GeometryDrawing
{
Geometry = new EllipseGeometry(new Rect(0, 0, 100, 100)),
Brush = Brushes.LightBlue,
Pen = new Pen(Brushes.Navy, 2)
};
// Use in DrawingImage
var image = new DrawingImage(drawing);
DrawingGroup
Groups multiple drawings together.
var group = new DrawingGroup
{
Children = new DrawingCollection
{
new GeometryDrawing { /* ... */ },
new ImageDrawing { /* ... */ },
new GlyphRunDrawing { /* ... */ }
},
Opacity = 0.8,
Transform = new RotateTransform(45)
};
Complete Drawing Examples
Custom Control with Drawing
public class CustomShapeControl : Control
{
public override void Render(DrawingContext context)
{
// Fill background
context.DrawRectangle(
Brushes.White,
null,
new Rect(Bounds.Size)
);
// Draw border
var borderPen = new Pen(Brushes.Gray, 1);
context.DrawRectangle(
null,
borderPen,
new Rect(Bounds.Size)
);
// Draw centered circle
var center = new Point(Bounds.Width / 2, Bounds.Height / 2);
var radius = Math.Min(Bounds.Width, Bounds.Height) / 3;
context.DrawEllipse(
Brushes.CornflowerBlue,
new Pen(Brushes.Navy, 2),
center,
radius,
radius
);
base.Render(context);
}
}
public override void Render(DrawingContext context)
{
var center = new Point(Bounds.Width / 2, Bounds.Height / 2);
// Draw rotated rectangles
for (int i = 0; i < 8; i++)
{
var angle = i * Math.PI / 4;
var matrix = Matrix.CreateTranslation(-center)
.Append(Matrix.CreateRotation(angle))
.Append(Matrix.CreateTranslation(center));
using (context.PushTransform(matrix))
{
var rect = new Rect(
center.X - 40,
center.Y - 10,
80,
20
);
context.DrawRectangle(
Brushes.Orange,
new Pen(Brushes.DarkOrange, 1),
rect
);
}
}
}
Drawing Complex Paths
public override void Render(DrawingContext context)
{
var geometry = new StreamGeometry();
using (var ctx = geometry.Open())
{
// Draw a star
var points = new Point[10];
var centerX = Bounds.Width / 2;
var centerY = Bounds.Height / 2;
var outerRadius = Math.Min(Bounds.Width, Bounds.Height) / 2 - 10;
var innerRadius = outerRadius * 0.4;
for (int i = 0; i < 10; i++)
{
var angle = i * Math.PI / 5 - Math.PI / 2;
var radius = i % 2 == 0 ? outerRadius : innerRadius;
points[i] = new Point(
centerX + radius * Math.Cos(angle),
centerY + radius * Math.Sin(angle)
);
}
ctx.BeginFigure(points[0], isFilled: true);
for (int i = 1; i < points.Length; i++)
{
ctx.LineTo(points[i]);
}
ctx.EndFigure(isClosed: true);
}
var gradient = new LinearGradientBrush
{
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
EndPoint = new RelativePoint(1, 1, RelativeUnit.Relative),
GradientStops =
{
new GradientStop { Color = Colors.Gold, Offset = 0 },
new GradientStop { Color = Colors.Orange, Offset = 1 }
}
};
context.DrawGeometry(gradient, new Pen(Brushes.DarkGoldenrod, 2), geometry);
}
Drawing with Clipping
public override void Render(DrawingContext context)
{
var clipRect = new Rect(10, 10, Bounds.Width - 20, Bounds.Height - 20);
using (context.PushClip(clipRect))
{
// Draw pattern that extends beyond clip region
for (int x = 0; x < Bounds.Width; x += 20)
{
for (int y = 0; y < Bounds.Height; y += 20)
{
context.DrawEllipse(
Brushes.LightBlue,
null,
new Point(x, y),
8,
8
);
}
}
}
// Draw clip boundary
context.DrawRectangle(
null,
new Pen(Brushes.Red, 2),
clipRect
);
}
Best Practices
- Use Push Methods: Always use
using statements with Push* methods to ensure state is restored
- Minimize DrawingContext Calls: Batch drawing operations when possible
- Cache Geometries: Reuse
Geometry objects rather than creating new ones each frame
- Use StreamGeometry: For complex paths,
StreamGeometry is more efficient than PathGeometry
- Invalidate Efficiently: Only call
InvalidateVisual() when necessary
- Consider Performance: Complex geometries and many draw calls can impact performance
- Use Immutable Brushes: Predefined brushes from
Brushes class are immutable and cached
- Dispose Resources: Dispose
Pen instances that use custom brushes
- Test DPI Scaling: Ensure drawing looks correct at different DPI settings