Обработка сочетаний клавиш в программном обеспечении C #

Изменить: я добавил исправленный код в ответ на этот вопрос.


В настоящее время я обрабатываю быстрые клавиши для своих приложений одним огромным методом, который выглядит так:

    protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
    {
        // Next issue (validate the fix)
        if (keyData == (Keys.Control | Keys.Enter) || 
            keyData == (Keys.Shift | Keys.Enter))
        {
            mark_as_fixed();
            return true;
        }
        // Skip to next issue without validating or changing anything
        if (keyData == (Keys.Alt | Keys.Down))
        {
            next_issue();
        }
        // Previous issue
        if (keyData == (Keys.Alt | Keys.Up))
        {
            previous_issue();
            return true;
        }
        ... [130 lines of this]

Он работает отлично, его не особенно трудно читать или поддерживать, но имея метод 130 строк длинный и постоянно растущий, просто чувствует себя не так.

Кроме того, я могу захотеть внедрить контекстную настройку в будущем, и нет простого способа узнать, какие ключи сопоставлены с каким методом, поэтому его нужно будет переписать.

Так как у всех нетривиальных приложений есть gazillion ярлыки, я предполагаю, что это решена проблема, но я не мог найти хорошее объяснение того, как приложения обрабатывают свои ярлыки.

11 голосов | спросил Sylverdrag 14 J000000Thursday16 2016, 21:37:16

5 ответов


5

Повторное использование идеи Мат Маг о базовом классе Command: как насчет использования словаря для отображения?

Таким образом, вы разделяете идеи команды и ярлыка (возможно, теряете потенциальную возможность отражения для программной реализации ваших команд), но вы можете легко позволить своим пользователям сопоставлять команды по мере необходимости.

ответил bassfault 15 J000000Friday16 2016, 01:46:17
15
  

Кроме того, я могу захотеть реализовать контекстную настройку в будущем, и нет простого способа узнать, какие ключи сопоставлены с каким методом, поэтому его нужно будет переписать.

Остановитесь здесь! Извлеките класс для каждой команды (например, вместо метода mark_as_fixed, у вас будет MarkAsFixedCommand). Если вы используете WPF, вы можете реализовать интерфейс ICommand и nevermind, вы используя WinForms . Тем не менее, извлеките классы команд - ICommand WPF - это действительно простой интерфейс, который просто предоставляет bool CanExcute(object) и void Execute(object) (вам не нужно выполнять object, если вам это не понадобится) - извлекая логику в классы команд, вы можете подготовить почву к тому, что вы измените свой интерфейс с WPF (который вы должен!).

  

нет простого способа узнать, какие ключи сопоставлены с каким методом

Сделайте одно! Пусть ваши классы команд получены из абстрактного класса CommandBase, который предоставляет для него метод:

public abstract bool IsShortcutKey(Keys keys);

Теперь для каждого производного командного класса используется для реализации этого метода IsShortcutKey. Реализация для MarkAsFixedCommand может выглядеть так:

public override bool IsShortcutKey(Keys keys)
{ 
    return keys == (Keys.Control | Keys.Enter)
        || keys == (Keys.Shift | Keys.Enter); 
}

Более чистый способ состоял в том, чтобы выставить некоторый атрибут свойства Keys ShortcutKey { get; }, но это не сыграло бы неплохую работу с несколькими ярлыками для того же команда.

В любом случае теперь, когда каждая реализация команды отвечает за логику быстрого доступа, вы можете легко получить конфигурацию и вернуть этот метод true когда keys соответствует любому значению Keys, которое у вас есть в вашей конфигурации.

Затем тот, кто запускает этот метод ProcessCmdKey, просто должен знать обо всех доступных командах - считать их как IEnumerable<CommandBase> и пусть тот, кто вызывает этот конструктор, имеет дело с предоставлением ему экземпляров команд (которые вы приобретете с некоторым кодом отражения).

Теперь ProcessCmdKey может быть таким же простым, как это:

// assume only 1 command returns true for specified keyData value:
var command = _commands.SingleOrDefault(cmd => cmd.IsShortcutKey(keyData));
if (command != null && command.CanExecute())
{
    command.Execute();
    return true;
}

return base.ProcessCmdKey(ref msg, keyData);

PS - имена методов должны быть PascalCase, not lower_snake_case

ответил Mathieu Guindon 14 J000000Thursday16 2016, 22:59:13
3

Я использую ваш метод, за исключением того, что я хотел бы предложить использовать инструкцию case для удобства чтения.

Я все еще новичок-программист с менее чем года опыта, поэтому могут быть лучшие методы, чем это.

protected override bool ProcessCmdKey(ref Message msg, Keys keyData) 
    {
        switch (keyData)
        {
            case Keys.F1: 
                //Stuff
                return true;
            case Keys.F2:
                //Stuff
                return true;
            case Keys.Alt | Keys.Down:
                //Stuff
                return true;
        }
        return base.ProcessCmdKey(ref msg, keyData);
    }

Также не забывайте, что ваш return base.ProcessCmdKey(ref msg, keyData); Таким образом, вы не отключите все остальные ключи, которые не указаны.

ответил Timmy 14 J000000Thursday16 2016, 22:02:17
1

Я предпочел бы использовать более гибкий дизайн, где вы или пользователь можете настроить ярлыки.

В этом проекте каждая операция будет ICommand с двумя членами:

public interface ICommand
{
    Keys Keys { get; }
    void Execute();
}

Абстрактный класс CommandBase реализует Keys:

public abstract class CommandBase : ICommand
{
    protected CommandBase(Keys keys) { Keys = keys; }
    public Keys Keys { get; }
    public abstract void Execute();
}

и конкретных типов метод Execute:

public class LoadReportCommand : CommandBase
{
    public LoadReportCommand(Keys keys) : base(keys) { }
    public override void Execute() { }
}
public class MarkAsFixedCommand : CommandBase
{
    public MarkAsFixedCommand(Keys keys) : base(keys) { }
    public override void Execute() { }
}

Затем я создавал ярлыки, настраиваемые с помощью app.config или любой другой конфигурации. Ключ будет именем команды и значением ключей:

private static IEnumerable<ICommand> _commands;

void Main()
{
    // this would come from a configuration
    var commandShortCuts = new Dictionary<string, string>
    {
        ["LoadReport"] = "Control+O",
        ["MarkAsFixed"] = "Shift+Enter",
    };

    // get all commands in this assembly
    var commandTypes =
        Assembly.GetExecutingAssembly()
        .GetTypes()
        .Where(t => t.BaseType == typeof(CommandBase))
        .ToList();

    // initialize commands and their shortcuts
    _commands = commandShortCuts.Select(x =>
    {
        var keys = x.Value.Split('+').Aggregate(
            Keys.None, 
            (result, next) => 
                result |= (Keys)Enum.Parse(typeof(Keys), next));
        var commandType = commandTypes.SingleOrDefault(t => t.Name.Equals(x.Key + "Command"));
        return (ICommand)Activator.CreateInstance(commandType, keys);
    })
    .ToList();
}

Затем вы можете выполнить команду следующим образом:

protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
    var command = _commands.SingleOrDefault(c => c.Keys == keyData);
    command?.Execute();
}
ответил t3chb0t 22 J000000Friday16 2016, 09:20:21
0

Это то, что у меня есть сейчас, на основе ответов Мата и Бел8z:

Dictionary<Keys, Func<Keys, bool>> dic_shortcut_mngr = new Dictionary<Keys, Func<Keys, bool>>();
private void init_shortcuts()
{
    //Files
    dic_shortcut_mngr.Add((Keys.Control | Keys.O), open);
    dic_shortcut_mngr.Add((Keys.Control | Keys.L), load_report);
    // Operations
    dic_shortcut_mngr.Add((Keys.Control | Keys.Enter), mark_as_fixed);
    dic_shortcut_mngr.Add((Keys.Shift | Keys.Enter), mark_as_fixed);
    dic_shortcut_mngr.Add((Keys.Alt | Keys.Down), next_issue);
    dic_shortcut_mngr.Add((Keys.Alt | Keys.Up), previous_issue);
    dic_shortcut_mngr.Add((Keys.Insert), copy);
    //Copy items
    dic_shortcut_mngr.Add((Keys.Control | Keys.NumPad0), copy_item);
    dic_shortcut_mngr.Add((Keys.Control | Keys.NumPad1), copy_item);
    dic_shortcut_mngr.Add((Keys.Control | Keys.NumPad2), copy_item);
    ...
}

protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
    if (dic_shortcut_mngr.ContainsKey(keyData))
    {
        return dic_shortcut_mngr[keyData].Invoke(keyData);
    }
    return base.ProcessCmdKey(ref msg, keyData);
}
private bool copy_item(Keys key)
{
    //NumKeys start at index 96. Removing 96 gets the numkey's value 
    int i = (int)key - 96;
    if (dgv.Rows.Count > i && dgv.Rows[i].Cells["item"] != null)
    {
        string item = dgv.Rows[i].Cells["item"].Value.ToString();
        Clipboard.SetText(item);
        rtb_fix_target.Paste();
    }
    return true;
}

Я решил использовать Func<Keys, bool> вместо Action, чтобы иметь возможность возвращать «true» и передавать ключевые данные методам (что полезно для обработки ярлыков numpad, поскольку они определяют подлежащий копированию элемент)

ответил Sylverdrag 15 J000000Friday16 2016, 22:33:49

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132