Создание горячих клавиш на C# Windows.Forms (примерно как режим рации в Discord)
Всем доброго времени суток. Столкнулась с проблемой того, что пыталась написать приложение и там есть две функции которые связаны с тем, чтобы пользователь мог юзать горячие клавиши(бинды) для использования некоторого функционала. KeyPreview = true; применяла конечно. Не понимаю почему не получается забиндить комбинации клавиш и клавиши модификаторы по типу Ctrl + G или отдельно Ctrl/Shift etc. Вот пример того, что у меня получилось, но работает косо криво:
private string bind1;
private string bind2;
private InputSimulator inputSimulator = new InputSimulator();
public RebinderForm()
{
InitializeComponent();
this.Load += RebinderForm_Load;
this.KeyPreview = true;
this.bindBox.KeyPress += bindBox_KeyPress;
this.actionBox.KeyPress += actionBox_KeyPress;
}
private void RebinderForm_Load(object sender, EventArgs e)
{
}
private void bindBox_KeyPress(object sender, KeyPressEventArgs e)
{
char keyChar = e.KeyChar;
if (!char.IsControl(keyChar))
{
bindBox.Text = keyChar.ToString();
bind1 = keyChar.ToString();
}
e.Handled = true;
}
private void actionBox_KeyPress(object sender, KeyPressEventArgs e)
{
char keyChar = e.KeyChar;
if (!char.IsControl(keyChar))
{
actionBox.Text = keyChar.ToString();
bind2 = keyChar.ToString();
}
e.Handled = true;
}
private void ExecuteAction()
{
inputSimulator.Keyboard.ModifiedKeyStroke(
WindowsInput.Native.VirtualKeyCode.CONTROL,
WindowsInput.Native.VirtualKeyCode.VK_A);
}
}
}
Ответы (2 шт):
Создание сочетания клавиш и привязка их в пользовательском интерфейсе.
Для всех форм где используется отлов горячих клавиш устанавливаем
KeyPreview = true;
Всё остальное я привязал через конструктор, все события и т.п
Для TextBox(сов) ставим у ReadOnly значение false чтобы запретить ввод пользователю и меняем BackColor цвет фона по своему желанию, курсор (Cursor) я установил значением Hand
Создаем первую форму где будем проверять комбинации клавиш

Код Form1
using System.Diagnostics;
namespace Binding_HotKey_Exemple
{
public partial class Form1 : Form
{
public static List<HotKeysArrange> HotKeysLis = new(); //Список со всевозможными комбинациями, сделан статическим чтобы можно было задать его из других форм
public List<Keys?> KeysListTemp = new(); //Временный список нажатых клавиш, нужен для проверки что было нажато.
public Form1()
{
InitializeComponent();
KeyPreview = true;
}
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
Debug.WriteLine(e.KeyCode);
if (!KeysListTemp.Contains(e.KeyCode) && HotKeysLis.Any(hotKey => hotKey.OneKey == e.KeyCode)) //Если первая нажатая клавиша равна первой клавиши любой комбинации, добавляем её
KeysListTemp.Add(e.KeyCode);
if (!KeysListTemp.Contains(e.KeyCode) && HotKeysLis.Any(hotKey => hotKey.TwoKey == e.KeyCode)) //Если вторая нажатая клавиша равна второй клавиши любой комбинации, добавляем её
KeysListTemp.Add(e.KeyCode);
if (!KeysListTemp.Contains(e.KeyCode) && HotKeysLis.Any(hotKey => hotKey.FreeKey == e.KeyCode)) //Если третья нажатая клавиша равна третьей клавиши любой комбинации, добавляем её
KeysListTemp.Add(e.KeyCode);
#region Проверка нажатых клавиш.
if (KeysListTemp.Count == 1) // Проверяем что в нажатых клавишах для отлова находится одна клавиша
{
var temp = HotKeysLis.Where(hotKey => KeysListTemp.Contains(hotKey.OneKey) && hotKey.TwoKey == null && hotKey.FreeKey == null).FirstOrDefault();
if (temp != null)
{
temp.Action(); //Вызываем наш привязанный метод
KeysListTemp.Clear();
}
}
if (KeysListTemp.Count == 2) // Проверяем что в нажатых клавишах для отлова находятся две клавиши
{
var temp = HotKeysLis.Where(hotKey => KeysListTemp.Contains(hotKey.OneKey) && KeysListTemp.Contains(hotKey.TwoKey) && hotKey.FreeKey == null).FirstOrDefault();
if (temp != null)
{
temp.Action(); //Вызываем наш привязанный метод
KeysListTemp.Clear();
}
}
if (KeysListTemp.Count == 3) // Проверяем что в нажатых клавишах для отлова находятся три клавиши
{
var temp = HotKeysLis.Where(hotKey => KeysListTemp.Contains(hotKey.OneKey) && KeysListTemp.Contains(hotKey.TwoKey) && KeysListTemp.Contains(hotKey.FreeKey)).FirstOrDefault();
if (temp != null)
{
temp.Action(); //Вызываем наш привязанный метод
KeysListTemp.Clear();
}
}
#endregion
}
private void Form1_KeyUp(object sender, KeyEventArgs e) => KeysListTemp.Clear(); //Если пользователь отпустил клавишу, удаляем все данные из списка.
#region Методы для которых будут задаваться комбинации клавиш
public void OneMessage() => MessageBox.Show(label1.Text);
public void TwoMessage() => MessageBox.Show(label2.Text);
public void FreeMessage() => MessageBox.Show(label3.Text);
#endregion
#region Передача методов для привязки их к горячим клавишам
private void textBox1_Click(object sender, EventArgs e)
{
BindingHotkeyForm form = new(OneMessage, textBox1);
form.Show();
}
private void textBox2_Click(object sender, EventArgs e)
{
BindingHotkeyForm form = new(TwoMessage, textBox2);
form.Show();
}
private void textBox3_Click(object sender, EventArgs e)
{
BindingHotkeyForm form = new(FreeMessage, textBox3);
form.Show();
}
#endregion
}
/// <summary>
/// Модель для горячих клавиш, я ограничился тремя сочетаниями комбинаций
/// </summary>
public class HotKeysArrange
{
public Action Action { get; set; } //Наш метод для привязки к комбинации клавиш
public Keys? OneKey { get; set; }
public Keys? TwoKey { get; set; }
public Keys? FreeKey { get; set; }
}
}
Создаём вторую форму BindingHotkeyForm

Код BindingHotkeyForm
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Binding_HotKey_Exemple
{
public partial class BindingHotkeyForm : Form
{
public Action action;
HotKeysArrange TempHotKeysArrange = new HotKeysArrange(); //Временная модель для установки горячих клавиш
private TextBox textBox; //Передаём textBox из главной формы чтобы отобразить в нем комбинацию клавиш.
public BindingHotkeyForm(Action action, TextBox textBox)
{
InitializeComponent();
KeyPreview = true;
this.action = action;
TempHotKeysArrange.Action = action; //Устанавливаем метод который будет работать с заданной комбинацией клавиш
this.textBox = textBox;
}
private void BindingHotkeyForm_KeyDown(object sender, KeyEventArgs e)
{
if (TempHotKeysArrange.OneKey == null)
{
TempHotKeysArrange.OneKey = e.KeyCode;
textBox1.Text += e.KeyCode.ToString();
textBox1.Select(textBox1.Text.Length, 0);
return;
}
if (TempHotKeysArrange.TwoKey == null)
{
TempHotKeysArrange.TwoKey = e.KeyCode;
textBox1.Text += $" + {e.KeyCode}";
textBox1.Select(textBox1.Text.Length, 0);
return;
}
if (TempHotKeysArrange.FreeKey == null)
{
TempHotKeysArrange.FreeKey = e.KeyCode;
textBox1.Text += $" + {e.KeyCode}";
textBox1.Select(textBox1.Text.Length, 0); //Устанавливаем курсор в конец строки, не красиво выглядит когда курсов мигает при каждом изменение в начале....
return;
}
}
private void button2_Click(object sender, EventArgs e) => Close(); // В случае отмены закрываем форму
private void button1_Click(object sender, EventArgs e)
{
textBox.Text = $"{(TempHotKeysArrange.OneKey != null ? TempHotKeysArrange.OneKey : "")}" +
$"{(TempHotKeysArrange.TwoKey != null ? " + " + TempHotKeysArrange.TwoKey : "")}" +
$"{(TempHotKeysArrange.FreeKey != null ? " + " + TempHotKeysArrange.FreeKey : "")}";
textBox.Select(textBox.Text.Length, 0);
Form1.HotKeysLis.RemoveAll(x => x.Action == action); //Удаляем старые комбинации клавиш для данной привязки
Form1.HotKeysLis.Add(TempHotKeysArrange); // Добавляем новое сочетание клавиш
Close();
}
}
}
Проект можно изучить по ссылке https://github.com/xellans/exemple/tree/main/Binding_HotKey-Exemple
Что-то в ответе ранее куча лишнего и довольно костыльного, когда все это сводится к нескольким строкам кода. Частично это дубликат ответа (ну а как без них, основано все на одном ведь) от xellan, но вместо написания кучи комментариев думаю, а дай напишу по своему)
Для начала давайте сделаем класс, который будет иметь в себе название и действие кнопки. Так, как действие у нас нечто абстрактное, то мы смело можем брать
Action. В итоге получаем к примеру такое:public record HotKeyInfo(string Name, Action? Action);Далее нам нужно место, где мы будем хранить все наши хоткеи. Обычно на одну горячую клавишу вешается один обработчик, а это значит, что нам отлично подойдет
Dictionary, ключом которого будет служить стандартныйKeys, который является простымEnum, да еще и помечем как флаг, что позволяет делать битовые операции. В итоге получаем такое:public Dictionary<Keys, HotKeyInfo> HotKeys { get; private set; } = [];Дальше нам нужно получить текущие нажатые клавиши формы, а именно событие
OnKeyDown. Пишем в классе формыoverride, жмем пробел и выбираем из предложенного спискаOnKeyDown, готово, у нас появился такой код, где вe.KeyDataбудут наши кнопки:protected override void OnKeyDown(KeyEventArgs e) { base.OnKeyDown(e); }Теперь наша цель взять из словаря описание хоткея (в случае наличия такого там) и вызвать его
Action. Делается это элементарно:protected override void OnKeyDown(KeyEventArgs e) { base.OnKeyDown(e); if (HotKeys.TryGetValue(e.KeyData, out var hotKeyInfo)) { hotKeyInfo.Action?.Invoke(); } }Поздравляю, осталось лишь заполнить словарь данными. Например, напишем такое в конструкторе окна:
HotKeys.Add(Keys.Control | Keys.A, new("Тестовая команда 1", ()=> { Debug.WriteLine($"Мы нажали { Name }"); })); HotKeys.Add(Keys.Control | Keys.Shift | Keys.D, new("Тестовая команда 2", ()=> { Debug.WriteLine("Мм, а это команда 2"); }));Запускаем, жмем клавиши и видим как в окне отладки появляется то, что мы указали.
Собственно вот вам универсальный и довольно простой способ сделать команды в вашем приложении. Дальше уже подключайте фантазию, развивайте эту идею как хотите. Хотите делать интерфейс, пожалуйста, сохраните последнее сочетание клавиш все через тот-же OnKeyDown и сделайте HotKeys.TryAdd(e.KeyData, ...);. Хотите добавить описание, вперед, редактируйте HotKeyInfo под себя и используйте где надо. Ну и так далее.
И да, я надеюсь вы поняли, что Action это условно говоря метод, то есть вы можете там указать лямбду как я()=> что-то;, а можете создать отдельный метод void и уже прописать там просто его имя (.Add(..., new("Команда", OnHotKey1Pressed));). Ну и также вы можете это все кастомизировать под свои нужды, передавая параметры если надо, получая может быть какие либо данные, ну и др.
В общем, вот вам самый простейший пример, изучайте, дорабатывайте, пробуйте. Удачи!
Весь код:
public record HotKeyInfo(string Name, Action? Action);
public partial class Form1 : Form
{
public Dictionary<Keys, HotKeyInfo> HotKeys { get; private set; } = [];
public Form1()
{
InitializeComponent();
HotKeys.Add(Keys.Control | Keys.A, new("Тестовая команда 1", ()=> { Debug.WriteLine($"Мы нажали { Name }"); }));
HotKeys.Add(Keys.Control | Keys.Shift | Keys.D, new("Тестовая команда 2", ()=> { Debug.WriteLine("Мм, а это команда 2"); }));
}
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if (HotKeys.TryGetValue(e.KeyData, out var hotKeyInfo))
{
hotKeyInfo.Action?.Invoke();
}
}
}
Замечания в комментариях показали некий недостаток кода, а именно то, что использование KeyData не очень надежно, ибо при некоторых сочетаниях оно дает одно и тоже значение, игнорируя некоторые клавиши. По этой причине, лучше писать свою реализацию, сохраняя клавиши поочередно в коллекцию. Тогда код измениться на нечто такое:
public record HotKeyInfo(string Name, Action? Action);
public partial class Form1 : Form
{
public Dictionary<int, HotKeyInfo> HotKeys { get; private set; } = [];
public Form1()
{
InitializeComponent();
HotKeys.Add(ToKeyValues(Keys.ControlKey, Keys.A), new("Тестовая команда 1", ()=> { Debug.WriteLine($"Мы нажали { Name }"); }));
HotKeys.Add(ToKeyValues(Keys.ShiftKey, Keys.D, Keys.F), new("Тестовая команда 2", ()=> { Debug.WriteLine("Мм, а это команда 2"); }));
}
private int ToKeyValues(params Keys[] keys)
=> keys.Sum(x => (int)x);
private HashSet<Keys> _pressedKeys = [];
private HashSet<Keys> _allKeys = [];
protected override void OnKeyDown(KeyEventArgs e)
{
_allKeys.Add(e.KeyCode);
_pressedKeys.Add(e.KeyCode);
}
protected override void OnKeyUp(KeyEventArgs e)
{
_allKeys.Remove(e.KeyCode);
if (_allKeys.Count == 0)
{
var hotKeyCode = ToKeyValues([.. _pressedKeys]);
if (HotKeys.TryGetValue(hotKeyCode, out var hotKeyInfo))
{
hotKeyInfo.Action?.Invoke();
}
_pressedKeys.Clear();
}
}
}
Поясняю:
Словарь теперь хранит числовое значение кнопок, их сумму. Если мы зайдем в документацию, то увидим, что к примеру клавиша
Ctrlбудет иметь код17, а допустимAбудет иметь65, их сочетание будет 17+65 =82, это и будет уникальный код нашей комбинации. Такая реализация позволит нажимать клавиши в любой последовательности, да и с любым кол-вом клавиш.ToKeyValues- это вспомогательный метод, который и суммирует код клавиш, отдавая результат.HashSet<Keys> _pressedKeysиHashSet<Keys> _allKeys- Так, как клавиша не может быть нажата дважды, нам нужна коллекция, которая хранит в себе только уникальные значения, ну а это именноHashSet. В_pressedKeysмы будем хранить все нажатые кнопки пользователем до тех пор, пока нажата хоть одна клавиша, в конце это и будет нужное сочетание клавиш. Ну а_allKeysбудет неким индикатором, что нажато в данный момент, мы в нее будем добавлять при нажатии и сразу удалять из нее при отжатии клавиши.OnKeyDown- Переопределяем событие, которое происходит в момент зажатия клавиши. В нем мы добавляем нажатую кнопку в наши коллекции.OnKeyUp- ОбратноеOnKeyDown, выполняется в момент отжатия клавиши. В нем мы удаляем клавишу из_allKeys, и если эта коллекция пуста (все клавиши отжаты) начинаем сверять комбинацию клавиш с нашим списком.Ну а остальное, думаю и так понятно, ибо описывал выше. Мы формируем код нашей комбинации клавиш, ищем его в словаре, если есть такой, то вызываем, ну а если нету, проходим мимо.
Собственно, теперь думаю все проблемы тут решены. Код остался вроде такой-же простой и понятный, без каких либо дубликатов и хардкора на три клавиши) Есть правда одно НО, мы потеряли возможность зажимать комбинацию клавиш, ибо триггер срабатывания у нас "если все клавиши отжаты". Так что, если вам нужно именно "зажимать", то код нужно будет немного переделать, а именно в момент зажатия клавиши подсчитывать код комбинации, искать если есть и выполнять пока клавиша зажата, но это уже пусть будет в виде домашнего задания.

