wpf extending control with keyboard override - c#

I'm extending the control canvas and adding my own custom overrides for MouseEvents. I was curious to know why this basic override which is when the user presses any key on the keyboard it doesn't emit a signal. How can I make this override work in wpf c#?
namespace CanvasGraphDemo
{
public class CanvasGraph : Canvas
{
public CanvasGraph()
{
}
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if (e.Key == Key.Enter)
{
Console.WriteLine("context menu open");
e.Handled = true;
}
}
}
}

This will work with your specific example. As others noted, you have to make the Canvas focusable and actually focus it, so it will receive keyboard events.
public class CanvasGraph : Canvas
{
public CanvasGraph()
{
Focusable = true;
Loaded += OnCanvasGraphLoaded;
}
private void OnCanvasGraphLoaded(object sender, RoutedEventArgs routedEventArgs)
{
Focus();
Loaded -= OnCanvasGraphLoaded;
}
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if (e.Key == Key.Enter)
{
Console.WriteLine("context menu open");
e.Handled = true;
}
}
}

Related

How to read keystrokes in a custom Winforms control, regardless of focus

I have a custom component for WinForms, on which graphics are drawn.
Using the Ctrl+right/left mouse buttons, I can add or remove objects.
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if (e.KeyCode == Keys.ControlKey)
this.EditorMode = true;
}
protected override void OnKeyUp(KeyEventArgs e)
{
base.OnKeyUp(e);
if (e.KeyCode == Keys.ControlKey)
this.EditorMode = false;
}
protected override void OnMouseDown(MouseEventArgs e)
{
if (!this.EditorMode)
{
base.OnMouseDown(e);
return;
}
if (e.Button == MouseButtons.Left)
{
// adding new object
}
else if (e.Button == MouseButtons.Right)
{
// deleting object
}
}
Everything works fine until I add something else to the custom control.
The problem is that pressing the Ctrl key will no longer be handled by the controller, but by the element on which the focus is currently set.
And I need my keyboard shortcut to work regardless of which element the focus is on...
What is the best way to do this?
I tried to redefine Processcmdkey, but it does not allow me to know if the key was pressed or released
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
if (keyData == (Keys.ControlKey | Keys.Control))
{
MessageBox.Show("ctrl");
return true;
}
return base.ProcessCmdKey(ref msg, keyData);
}
what should I do to get the desired result: regardless of focusing on child controls, I can always add new objects to the drawing?
My suggestion is to implement IMessageFilter in your MainForm and intercept
the WM_KEYDOWN message for Control-Left and Control-Right. The example below is a guideline for a UserControl that will add and remove buttons based on Control-Right and Control-Left respectively.
Adding and Removing the MessageFilter
The message filter will be added on the OnHandleCreated override and removed in the Dispose method of MainForm.
public partial class MainForm : Form, IMessageFilter
{
public MainForm() => InitializeComponent();
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
if(!(DesignMode ) || _isHandleInitialized)
{
_isHandleInitialized = true; ;
Application.AddMessageFilter(this);
}
}
bool _isHandleInitialized = false;
// In MainForm.Designer.cs
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (components != null)
{
components.Dispose();
}
Application.RemoveMessageFilter(this);
}
base.Dispose(disposing);
}
}
Handling PreFilterMessage
The MainForm will provide three event hooks for ControlLeft, ControlRight, and NoCommand. These will be fired in the PreFilterMessage method.
public partial class MainForm : Form, IMessageFilter
{
const int WM_KEYDOWN = 0x100;
public bool PreFilterMessage(ref Message m)
{
if (Form.ActiveForm == this)
{
switch (m.Msg)
{
case WM_KEYDOWN:
var key = (Keys)m.WParam | ModifierKeys;
switch (key)
{
case Keys.Control | Keys.Left:
ControlLeft?.Invoke(this, EventArgs.Empty);
Text = "Control.Left";
break;
case Keys.Control | Keys.Right:
ControlRight?.Invoke(this, EventArgs.Empty);
Text = "Control.Right";
break;
default:
// Don't event if it's "just" the Control key
if(ModifierKeys == Keys.None)
{
Text = "Main Form";
NoCommand?.Invoke(this, EventArgs.Empty);
}
break;
}
break;
}
}
return false;
}
public event EventHandler ControlLeft;
public event EventHandler ControlRight;
public event EventHandler NoCommand;
}
Responding to events in the UserControl
The MainForm events will be subscribed to in the OnHandleCreated override of UserControlResponder.
public partial class UserControlResponder : UserControl
{
public UserControlResponder()
{
InitializeComponent();
}
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
if(!(DesignMode || _isHandleInitialized))
{
_isHandleInitialized = true;
var main = (MainForm)Parent;
main.ControlLeft += onControlLeft;
main.ControlRight += onControlRight;
main.NoCommand += onNoCommand;
}
}
private bool _isHandleInitialized = false;
char _tstCount = 'A';
private void onControlRight(object sender, EventArgs e)
{
BackColor = Color.LightBlue;
BorderStyle = BorderStyle.None;
var button = new Button
{
Text = $"Button {_tstCount++}",
Size = new Size(150, 50),
};
button.Click += onAnyButtonClick;
flowLayoutPanel.Controls.Add(button);
}
private void onControlLeft(object sender, EventArgs e)
{
BackColor = Color.LightGreen;
BorderStyle = BorderStyle.None;
if(flowLayoutPanel.Controls.Count != 0)
{
var remove = flowLayoutPanel.Controls[flowLayoutPanel.Controls.Count - 1];
if(remove is Button button)
{
button.Click -= onAnyButtonClick;
}
flowLayoutPanel.Controls.RemoveAt(flowLayoutPanel.Controls.Count - 1);
}
}
private void onNoCommand(object sender, EventArgs e)
{
BackColor = Color.Transparent;
BorderStyle = BorderStyle.FixedSingle;
}
private void onAnyButtonClick(object sender, EventArgs e)
{
((MainForm)Parent).Text = $"{((Button)sender).Text} Clicked";
}
}

KeyDown and KeyUp Events inside "Custom Control"

I have a custom control derived from control. The mouse events, such as OnMouseLeftButtonDown (MouseButtonEventArgs e), can be used without any problems. Now I would like a combination of e.g. left-ctrl and left mouse click. But I just can't manage to respond to keyboard events in my "custom control".
Can someone give me a hint how to implement this?
This overriden method works fine.
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonDown(e);
if (isKeyCtrlDown)
{
Debug.WriteLine("Test");
}
}
And this overriden methods will not fired.
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if (e.Key == Key.LeftCtrl)
{
isKeyCtrlDown = true;
}
}
protected override void OnKeyUp(KeyEventArgs e)
{
base.OnKeyUp(e);
if (Keyboard.IsKeyUp(Key.LeftCtrl))
{
isKeyCtrlDown = false;
}
}
Thanks for any help.

How to distinguish whether Enter event was raised by keyboard or mouse click?

Is there a way to distinguish whether the Enter event on a control was raised by keyboard (Tab, Shift+Tab) or by direct mouse click?
I need to perform an action only when the user is moving to the control using Tab, but not when the user directly clicks on the control. I have tried to intercept the mouse click directly, but it seems the Enter event is raised before Click.
Instead of tracking the Tab key, you can use the WM_MOUSEACTIVATE message to detect activation of the control with the mouse. You could either sub-class each control type you use and override the WndProc method or use a NativeWindow listener class like the one presented below. Depending on how many types of controls you use, it may be less work and clutter to just sub-class those controls to provide a property that indicates that the control was selected using the mouse. It is your decision to make, but the pattern will be the same.
This code is a slight modification of the example shown in the MS documentation.
public class MouseActivateListener : NativeWindow
{
private Control parent;
public MouseActivateListener(Control parent)
{
parent.HandleCreated += this.OnHandleCreated;
parent.HandleDestroyed += this.OnHandleDestroyed;
parent.Leave += Parent_Leave;
this.parent = parent;
if (parent.IsHandleCreated)
{
AssignHandle(parent.Handle);
}
}
private void Parent_Leave(object sender, EventArgs e)
{
MouseActivated = false;
}
private void OnHandleCreated(object sender, EventArgs e)
{
AssignHandle(((Form)sender).Handle);
}
private void OnHandleDestroyed(object sender, EventArgs e)
{
ReleaseHandle();
}
public bool MouseActivated { get; set; }
[System.Security.Permissions.PermissionSetAttribute(System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")]
protected override void WndProc(ref Message m)
{
const Int32 WM_MouseActivate = 0x21;
base.WndProc(ref m);
if (m.Msg == WM_MouseActivate && m.Result.ToInt32() < 3)
{
MouseActivated = true;
}
}
}
Example Usage:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private MouseActivateListener textBox1Listener;
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
textBox1Listener = new MouseActivateListener(textBox1);
}
private void textBox1_Enter(object sender, EventArgs e)
{
if (textBox1Listener.MouseActivated)
{
MessageBox.Show("Mouse Enter");
}
else
{
MessageBox.Show("Tab Enter");
}
}
}
You can use the Form.KeyPreview event and store the last key press in a variable. Then in your control's Enter event, check the value of the key that was pressed last. If this is a tab, do whatever you need to:
private Keys lastKeyCode;
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
this.lastKeyCode = e.KeyCode;
}
Then in the Enter event, check it:
if (lastKeyCode == Keys.Tab)
{
// Whatever...
}
Intercepting WM_KEYUP and WM_KEYDOWN directly with a message filter to retrieve the state of the Tab key worked. This seems excessive for such a seemingly straightforward task, but apparently the Tab key is suppressed from most windows forms events.
Would be happy to take a cleaner answer, but for now, this is it:
class TabMessageFilter : IMessageFilter
{
public bool TabState { get; set; }
public bool PreFilterMessage(ref Message m)
{
const int WM_KEYUP = 0x101;
const int WM_KEYDOWN = 0x100;
switch (m.Msg)
{
case WM_KEYDOWN:
if ((Keys)m.WParam == Keys.Tab) TabState = true;
break;
case WM_KEYUP:
if ((Keys)m.WParam == Keys.Tab) TabState = false;
break;
}
return false;
}
}
class MainForm : Form
{
TabMessageFilter tabFilter;
public MainForm()
{
tabFilter = new TabMessageFilter();
Application.AddMessageFilter(tabFilter);
}
protected override void OnFormClosed(FormClosedEventArgs e)
{
Application.RemoveMessageFilter(tabFilter);
base.OnFormClosed(e);
}
void control_Enter(object sender, EventArgs e)
{
if (tabFilter.TabState) // do something
else // do domething else
}
}

Windows Forms: set Tab as shortcut

There is no reaction on Tab shortcut in my app:
public class SomeWindow : Form {
// ...
private void someWindow_Load(object sender, EventArgs e) {
this.KeyPreview = true;
this.KeyDown += new KeyEventHandler(SomeWindow_KeyDown);
}
void SomeWindow_KeyDown(object sender, KeyEventArgs e) {
if (e.Control && e.KeyCode == Keys.B) {
// some actions
}
else if (e.KeyCode == Keys.Tab) {
// no reaction
}
}
}
I learned that Tab and direction keys are required some overrides, but all examples that I saw was related to textboxes. Any solutions for Tas as shortcut?

Add semi-transparent button to ListView

I have listview control. I need to add semi-transparent button with image to listview. Something like this:
I found several projects that use semi-transparent buttons on the form. But when I transfer them to the ListView, they do not work.
Necessary to use .net 2.0 framework.
I found some solution.
Making Transparent Controls - No Flickering
I inherit my TransparentToggleButton class from TranspControl class:
public class TransparentToggleButton : TranspControl
{
private Image _normalState;
private Image _mouseUpState;
private Image _activateState;
private bool _state;
private bool _mouseUnder;
public event EventHandler StateChanged;
public bool ToggleState
{
get { return _state; }
set
{
_state = value;
SetImage();
}
}
public void SetImages(Image normalState, Image mouseUpState, Image activateState)
{
BackImage = normalState;
_normalState = normalState;
_mouseUpState = mouseUpState;
_activateState = activateState;
}
protected override void OnMouseClick(MouseEventArgs e)
{
base.OnMouseClick(e);
if (e.Button == MouseButtons.Left)
{
_state = !_state;
if (StateChanged != null)
StateChanged(this, e);
SetImage();
}
}
protected override void OnMouseEnter(EventArgs e)
{
base.OnMouseEnter(e);
_mouseUnder = true;
SetImage();
}
protected override void OnMouseLeave(EventArgs e)
{
base.OnMouseLeave(e);
_mouseUnder = false;
SetImage();
}
private void SetImage()
{
try
{
if (_state)
BackImage = _activateState;
else
BackImage = _mouseUnder ? _mouseUpState : _normalState;
}
catch (Exception)
{
}
}
}
Function SetImages loads the 3 images that used for normal state, normal state when cursor over the button, activate state.
Besides need catch listview scroll event and Invalidate() TransparentToggleButton.

Categories

Resources