From wpf-dev-pack
Enables mouse events on WPF FrameworkElements rendered via OnRender(DrawingContext) by drawing transparent backgrounds. Use when custom-drawn areas ignore clicks.
npx claudepluginhub christian289/dotnet-with-claudecode --plugin wpf-dev-packThis skill uses the workspace's default tool permissions.
An essential pattern for receiving mouse events when rendering directly with `OnRender(DrawingContext)` in a class that inherits from `FrameworkElement`.
Generates design tokens/docs from CSS/Tailwind/styled-components codebases, audits visual consistency across 10 dimensions, detects AI slop in UI.
Records polished WebM UI demo videos of web apps using Playwright with cursor overlay, natural pacing, and three-phase scripting. Activates for demo, walkthrough, screen recording, or tutorial requests.
Delivers idiomatic Kotlin patterns for null safety, immutability, sealed classes, coroutines, Flows, extensions, DSL builders, and Gradle DSL. Use when writing, reviewing, refactoring, or designing Kotlin code.
An essential pattern for receiving mouse events when rendering directly with OnRender(DrawingContext) in a class that inherits from FrameworkElement.
MouseLeftButtonDown, MouseMove don't fire on controls inheriting from FrameworkElementWPF Hit Testing is performed based on rendered pixels. If nothing is drawn in OnRender() or there's no background, that area is considered "empty" and mouse events won't be delivered.
namespace MyApp.Controls;
using System.Windows;
using System.Windows.Media;
public sealed class MyOverlay : FrameworkElement
{
protected override void OnRender(DrawingContext dc)
{
base.OnRender(dc);
// ⚠️ Required: Draw transparent background (for mouse event reception)
dc.DrawRectangle(
Brushes.Transparent,
null,
new Rect(0, 0, ActualWidth, ActualHeight));
// Actual rendering logic follows
DrawContent(dc);
}
private void DrawContent(DrawingContext dc)
{
// Draw actual content
}
}
| Setting | Hit Test Result | Visual Result |
|---|---|---|
Brushes.Transparent | ✅ Success | Not visible |
null | ❌ Failure | Not visible |
new SolidColorBrush(Color.FromArgb(0, 0, 0, 0)) | ✅ Success | Not visible |
Transparent is an "existing" brush with Alpha channel of 0. WPF Hit Testing checks if a brush exists, so it behaves differently from null.
namespace MyApp.Controls;
using System.Windows;
using System.Windows.Media;
public sealed class RulerOverlay : FrameworkElement
{
private static readonly Pen LinePen;
private static readonly Brush TextBrush;
static RulerOverlay()
{
// Frozen resources (performance optimization)
LinePen = new Pen(Brushes.Yellow, 2);
LinePen.Freeze();
TextBrush = Brushes.Yellow;
}
public Point StartPoint { get; set; }
public Point EndPoint { get; set; }
public bool IsDrawing { get; set; }
protected override void OnRender(DrawingContext dc)
{
base.OnRender(dc);
// 1. Transparent background (required for hit testing)
dc.DrawRectangle(
Brushes.Transparent,
null,
new Rect(0, 0, ActualWidth, ActualHeight));
// 2. Draw actual measurement line
if (IsDrawing)
{
dc.DrawLine(LinePen, StartPoint, EndPoint);
}
}
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonDown(e);
// Now events are received normally
StartPoint = e.GetPosition(this);
IsDrawing = true;
CaptureMouse();
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (IsDrawing)
{
EndPoint = e.GetPosition(this);
InvalidateVisual(); // Redraw
}
}
protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonUp(e);
if (IsDrawing)
{
IsDrawing = false;
ReleaseMouseCapture();
}
}
}
The same principle applies when connecting events in XAML:
<controls:RulerOverlay x:Name="RulerOverlay"
MouseLeftButtonDown="RulerOverlay_MouseLeftButtonDown"
MouseMove="RulerOverlay_MouseMove"
MouseLeftButtonUp="RulerOverlay_MouseLeftButtonUp" />
// Code-behind
private void RulerOverlay_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (sender is RulerOverlay overlay)
{
// Without transparent background, this event won't fire!
var point = e.GetPosition(overlay);
// ...
}
}
<!-- IsHitTestVisible="False" blocks events regardless of transparent background -->
<controls:MyOverlay IsHitTestVisible="False" />
| Setting | Transparent Background | Hit Test Result |
|---|---|---|
IsHitTestVisible="True" (default) | Yes | ✅ Success |
IsHitTestVisible="True" | No | ❌ Failure |
IsHitTestVisible="False" | Yes | ❌ Failure |
IsHitTestVisible="False" | No | ❌ Failure |
Brushes.Transparent in OnRender()IsHitTestVisible is True (default)Freeze() to Pen, Brush (performance optimization)protected override void OnRender(DrawingContext dc)
{
base.OnRender(dc);
// Draw content without background
dc.DrawLine(LinePen, StartPoint, EndPoint); // Hit Test succeeds only on the line
}
protected override void OnRender(DrawingContext dc)
{
base.OnRender(dc);
// 1. Transparent background first
dc.DrawRectangle(Brushes.Transparent, null,
new Rect(0, 0, ActualWidth, ActualHeight));
// 2. Then content
dc.DrawLine(LinePen, StartPoint, EndPoint);
}