Enables mouse event reception for WPF FrameworkElement using DrawingContext by drawing transparent backgrounds. Use when custom-drawn elements don't receive mouse events.
View on GitHubchristian289/dotnet-with-claudecode
wpf-dev-pack
January 23, 2026
Select agents to install to:
npx add-skill https://github.com/christian289/dotnet-with-claudecode/blob/main/wpf-dev-pack/skills/implementing-hit-testing/SKILL.md -a claude-code --skill implementing-hit-testingInstallation paths:
.claude/skills/implementing-hit-testing/# WPF FrameworkElement Hit Testing
An essential pattern for receiving mouse events when rendering directly with `OnRender(DrawingContext)` in a class that inherits from `FrameworkElement`.
## 1. Problem Scenario
### Symptoms
- Events like `MouseLeftButtonDown`, `MouseMove` don't fire on controls inheriting from `FrameworkElement`
- Nothing happens when clicking
### Cause
WPF 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.
---
## 2. Solution
### 2.1 Draw Transparent Background (Required)
```csharp
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
}
}
```
---
## 3. Why Transparent?
### Transparent vs null
| 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`.
---
## 4. Practical Example
### 4.1 Measurement Tool Overlay
```csharp
namespace MyApp.Controls;
using System.Windows;
using System.Windows.Media;
public sealed class RulerOverlay : FrameworkElement
{
private static readonly Pen LinePen;
private static readonly Brush TextBr