From wpf-dev-pack
Syncs WPF Keyboard.Modifiers with ScottPlot's internal keyboard state before MouseWheel events. Fixes Ctrl+Wheel zoom requiring control focus in ScottPlot WPF controls.
How this skill is triggered — by the user, by Claude, or both
Slash command
/wpf-dev-pack:scottplot-syncing-modifier-keys-for-mousewheelhaikuThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
ScottPlot manages its own internal keyboard state via KeyDown/KeyUp events. Since these events require keyboard focus, modifier-dependent MouseWheel interactions (e.g., Ctrl+Wheel zoom) fail until the user clicks the control.
ScottPlot manages its own internal keyboard state via KeyDown/KeyUp events. Since these events require keyboard focus, modifier-dependent MouseWheel interactions (e.g., Ctrl+Wheel zoom) fail until the user clicks the control.
WPF routes mouse events and keyboard events differently:
| Event | Routing Basis | Requires Keyboard Focus |
|---|---|---|
MouseWheel | Mouse position (hit-test) | No |
MouseMove, MouseDown | Mouse position (hit-test) | No |
KeyDown, KeyUp | Keyboard focus | Yes |
ScottPlot tracks modifier keys through KeyDown/KeyUp into its internal KeyboardState. Without keyboard focus, KeyDown never fires, so the internal state never records Ctrl as pressed. The MouseWheel event arrives, but the zoom handler checks keys.IsPressed(Control) against the empty internal state and does nothing.
System.Windows.Input.Keyboard.Modifiers reads the OS-level key state globally, regardless of which element has keyboard focus.
Inject the current modifier state into the control's input processor before processing each MouseWheel event:
using System.Windows;
using System.Windows.Input;
public static void ProcessMouseWheel(
this UserInputProcessor processor,
FrameworkElement fe,
MouseWheelEventArgs e)
{
// Sync modifier keys from WPF global state before processing
SyncModifierKeys(processor);
Pixel pixel = e.ToPixel(fe);
IUserAction action = e.Delta > 0
? new MouseWheelUp(pixel)
: new MouseWheelDown(pixel);
processor.Process(action);
}
private static void SyncModifierKeys(UserInputProcessor processor)
{
IUserAction action = (Keyboard.Modifiers & ModifierKeys.Control) != 0
? new KeyDown(StandardKeys.Control)
: new KeyUp(StandardKeys.Control);
processor.Process(action);
}
Keyboard.Modifiers is focus-independent (OS-level global query)KeyDown/KeyUp to the input processor to update its internal keyboard stateAn alternative approach is to call Keyboard.Focus(this) on MouseEnter:
// Alternative approach - has side effects
protected override void OnMouseEnter(MouseEventArgs e)
{
Keyboard.Focus(this);
base.OnMouseEnter(e);
}
| Approach | Pros | Cons |
|---|---|---|
| Sync Keyboard.Modifiers | No side effects, no focus stealing | Must be called on each wheel event |
| Focus on MouseEnter | Simple | Steals focus from TextBox/other inputs, disrupts Tab navigation |
Prefer Keyboard.Modifiers sync - it solves the problem without affecting focus behavior.
// Wrong: sync once and cache
private static bool _synced = false;
private static void SyncModifierKeys(UserInputProcessor processor)
{
if (_synced) return; // Ctrl state can change between wheel events!
_synced = true;
// ...
}
Modifier state must be synced every time before processing MouseWheel, because the user may press or release Ctrl between wheel events.
// Wrong: sync after processing
processor.Process(action);
SyncModifierKeys(processor); // Too late - wheel already processed with stale state
The sync must happen before processor.Process(wheelAction).
UserInputProcessor)Keyboard.Modifiers sync in the MouseWheel processing pathnpx claudepluginhub christian289/dotnet-with-claudecode --plugin wpf-dev-packFixes FlaUI mouse click/drag and keyboard input failures in cross-process WPF UI automation. Addresses stuck keys, SendInput timing, xUnit parallel execution, and drag coordinate issues.
Guides implementation of keyboard-accessible interactive elements, focus management, and WAI-ARIA patterns for modals, menus, tabs, and custom widgets.
Building WPF on .NET 8+. Host builder, MVVM Toolkit, Fluent theme, performance, modern C# patterns.