Укрощаем хоткеи системой плагинов на C#

в 17:05, , рубрики: .net, плагины, Программирование, хоткеи, метки: , , ,

Привет, дорогой читатель!

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

Вступление

Примерно полтора месяца назад пользователь vvzvlad натолкнул меня на идею программы, для перевода различного текста по нажатию хоткея. Я очень долго пользовался этой программкой, но вот пару дней назад мне пришла в голову идея улучшить её.
Я решил добавить возможность назначать различные хоткеи на разные действия. Для этого я использовал систему плагинов.
В итоге получилась программа HotKeyHelper, которую вы можете скачать здесь.

image

Файл проекта находится здесь.

Под катом вы увидите исходный код программы и пояснения к нему.

Программа

Как я уже написал выше, я решил использовать систему плагинов.

Для этого нам необходимо написать интерфейс плагинов:

    public delegate void PluginHandler(string text);

    /// <summary>
    /// Представляет собой интерфейс плагина
    /// </summary>
    public interface IPlugin
    {
        /// <summary>
        /// Название плагина
        /// </summary>
        string Name { get; }

        /// <summary>
        /// Нужно ли передавать плагину выделенный пользователем текст
        /// </summary>
        bool NeedSelectedText { get;}

        /// <summary>
        /// Основной метод плагина
        /// </summary>
        /// <param name="parametres">Входные параметры</param>
        /// <param name="text">Выделенный пользователем текст</param>
        void MainMethod(string parametres,string text);

        event PluginHandler ShowBaloonHandler;
        event PluginHandler ShowFormHandler;
        event PluginHandler PasteTextHandler;
    }

И написать метод для загрузки плагинов:

 private void LoadPlugins()
        {
            //Получаем путь до программы
            string sPath = System.IO.Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName); ;

            //Проходим циклом по всем файлом с расширением .dll
            foreach (string f in System.IO.Directory.GetFiles(sPath, "*.dll"))
            {
                System.Reflection.Assembly a = System.Reflection.Assembly.LoadFile(f);
                try
                {
                    foreach (Type t in a.GetTypes())
                    {
                        foreach (Type i in t.GetInterfaces())
                        {
                            //И если библиотека наследована от IPlugin
                            if (i.Equals(Type.GetType("HotKeyHelper.IPlugin")))
                            {
                                //Подписываемся на события плагина и добавляем его в список плагинов
                                IPlugin p = (IPlugin)Activator.CreateInstance(t);
                                p.ShowBaloonHandler += ShowBaloon;
                                p.ShowFormHandler += ShowForm;
                                p.PasteTextHandler += PasteText;
                                comboBox1.Items.Add(p.Name);
                                Plugins.Add(p);
                                break;
                            }
                        }
                    }

                }
                catch (Exception)
                {
                    continue;
                }
            }
        }

Также нам будет необходимо вызывать WinApi методы, для этого импортируем их:

        [DllImport("User32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);

        [DllImport("User32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool UnregisterHotKey(IntPtr hWnd, int id);

        [DllImport("user32.dll", SetLastError = true)]
        public static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, int dwExtraInfo);

И напишем 2 метода, необходимых для выполнения функционала некоторых плагинов

        /// <summary>
        /// Эмулирует нажатие Ctrl+C
        /// </summary>
        void CtrlCEmul()
        {
            keybd_event(0x11, 0, 0, 0);
            keybd_event((byte)'C', 0, 0, 0);
            keybd_event((byte)'C', 0, 0x2, 0);
            keybd_event(0x11, 0, 0x2, 0);
        }

        /// <summary>
        /// Эмулирует нажатие Ctrl+V
        /// </summary>
        void CtrlVEmul()
        {
            keybd_event(0x11, 0, 0, 0);
            keybd_event((byte)'V', 0, 0, 0);
            keybd_event((byte)'V', 0, 0x2, 0);
            keybd_event(0x11, 0, 0x2, 0);
        }

Также напишем класс для хранения пользовательских хоткеев:

public class HotKey
    {
        /// <summary>
        /// Название плагина
        /// </summary>
        public string Plugin { get; set; }

        /// <summary>
        /// Параметры выполнения функции плагина
        /// </summary>
        public string Parametres { get; set; }

        /// <summary>
        /// Название клавиши
        /// </summary>
        public string Key { get; set; }

        /// <summary>
        /// Код клавиши
        /// </summary>
        public uint KeyCode { get; set; }

        /// <summary>
        /// Название модификаторов
        /// </summary>
        public string Modificators { get; set; }

        /// <summary>
        /// Код модификаторов
        /// </summary>
        public uint ModificatorsCode { get; set; }

        /// <summary>
        /// Загружает хоткеи
        /// </summary>
        /// <param name="path">Путь до файла сохранения</param>
        /// <returns>Список хоткеев</returns>
        public static List<HotKey> LoadHotKeys(string path)
        {
            List<HotKey> hotKeys = new List<HotKey>();
            if (File.Exists(path))
                foreach (string line in File.ReadAllLines(path))
                {
                    string[] parametres = line.Split(new string[] { "<!>" }, StringSplitOptions.None);
                    hotKeys.Add(new HotKey()
                                    {
                                        Plugin = parametres[0],
                                        Parametres = parametres[1],
                                        Key = parametres[2],
                                        KeyCode = UInt32.Parse(parametres[3]),
                                        Modificators = parametres[4],
                                        ModificatorsCode = UInt32.Parse(parametres[5])
                                    });
                }
            return hotKeys;
        }

        /// <summary>
        /// Сохраняет хоткеи
        /// </summary>
        /// <param name="hotKeys">Список хоткеев</param>
        /// <param name="path">Путь до файла сохранения</param>
        public static void SaveHotKeys(List<HotKey> hotKeys, string path)
        {
            List<string> parametres = new List<string>();
            foreach (HotKey hotKey in hotKeys)
            {
                parametres.Add(String.Format("{0}<!>{1}<!>{2}<!>{3}<!>{4}<!>{5}",
                    new object[]{hotKey.Plugin,hotKey.Parametres,hotKey.Key,hotKey.KeyCode,
                        hotKey.Modificators,hotKey.ModificatorsCode}));
            }
            File.WriteAllLines(path, parametres);
        }
    }

Теперь после загрузки плагинов мы можем загрузить пользовательские хоткеи и подписаться на них:

 HotKeys = HotKey.LoadHotKeys("HotKeys.txt");

            //Проходим по списку хоткеев и регистрируем их
            for (int i = 0; i < HotKeys.Count; i++)
            {
                if (!RegisterHotKey(this.Handle, i, HotKeys[i].ModificatorsCode, HotKeys[i].KeyCode))
                {
                    notifyIcon1.BalloonTipText = String.Format("Комбинация {0} {1} не зарегестрировалась", HotKeys[i].Modificators, HotKeys[i].Key);
                    notifyIcon1.ShowBalloonTip(10000);
                }
            }

После того, как мы подписались на хоткей, нашему окну будет посылаться сообщение WM_HOTKEY. Чтобы узнать, когда пользователь нажал необходимую комбинацию, мы должны переписать метод:

         protected override void WndProc(ref Message m)
        {
            //Если сработал WM_HOTKEY
            if (m.Msg == 0x0312)
            {
                //Получаем идентификатор комбинации и выбираем необходимый плагин
                int id = (int)m.WParam;
                IPlugin plugin = Plugins.SingleOrDefault(pl => pl.Name == HotKeys[id].Plugin);

                if (plugin == null)
                {
                    notifyIcon1.Text = "Такого плагина не существует";
                    notifyIcon1.ShowBalloonTip(10000);
                    return;
                }

                string text = "";

                if(plugin.NeedSelectedText)
                {
                    //Эмулируем нажатие Ctrl+C
                    CtrlCEmul();

                    //Ждём, пока выполнятся операции с буфером обмена
                    Thread.Sleep(150);

                    //Т.к. буфер обмена может быть занят другим приложением, пытаемся получить его несколько раз
                    for (int i = 0; i < 10; i++)
                    {
                        try
                        {
                            //Получаем содержимое буфера обмена
                            text = Clipboard.GetText();
                            break;
                        }
                        catch (ExternalException)
                        { }
                        Thread.Sleep(100);
                    }
                }

                //Выполняем функцию плагина
                try
                {
                    plugin.MainMethod(HotKeys[id].Parametres,text);
                }
                catch (Exception ex)
                {
                    notifyIcon1.Text = "В плагине произошла ошибка "+ex.Message;
                    notifyIcon1.ShowBalloonTip(10000);
                }
                return;

            }
            base.WndProc(ref m);
        }

Теперь нам осталось добавить более-мение удобный интерфейс для создания хоткеев. В этом, я думаю, нет ничего интересного, однако я покажу, как я сделал систему для записи нажатой комбинации.
Для этого нам нужно подписаться на события формы KeyDown и KeyUp:

   private void button1_Click(object sender, EventArgs e)
        {
            if (button1.Enabled)
            {
                button1.Enabled = false;
                isRecord = true;
            }
        }

        //Если была нажата любая клавиша
        private void Form1_KeyDown(object sender, KeyEventArgs e)
        {
            //И если была нажата клавиша "Записать"
            if (isRecord)
            {
                //Получаем значения нажатой клавиши
                modKeys = e.Modifiers;
                key = e.KeyCode;

                //И получаем значения нажатых модификаторов
                if (e.Alt)
                    modifiers = 1;
                if (e.Control)
                    modifiers = 2;
                if (e.Shift)
                    modifiers = 4;
                if (e.Alt && e.Control)
                    modifiers = 3;
                if (e.Alt && e.Shift)
                    modifiers = 5;
                if (e.Control && e.Shift)
                    modifiers = 6;
                if (e.Alt && e.Control && e.Shift)
                    modifiers = 7;

                label1.Text = modKeys.ToString();
                label2.Text = key.ToString();

            }
        }

        //Когда кнопка была отпущена
        private void Form1_KeyUp(object sender, KeyEventArgs e)
        {
            if (isRecord)
            {
                //Выводим диалог с подтверждением
                DialogResult result = MessageBox.Show(String.Format("Подтвердите клавиши: {0} {1}",label1.Text,label2.Text),
                                                      "Подтвержение",
                                                      MessageBoxButtons.YesNo);
                if (result == DialogResult.Yes)
                    isRecord = false;
            }
        }

Ну всё. Программа готова. Осталось только написать какой-либо плагин, чтобы начать пользоваться.

Плагин

Чтобы мы могли использовать плагин в программе, мы должны унаследовать основной класс от IPlugin.
Для этого нам нужно добавить ссылку на нашу программу. Теперь мы можем написать реализацию простого плагина. Для примера я написал плагин, который будет размещать выделенный текст на pastebin.com/:

  public class PastebinSender : IPlugin
    {
        public string Name
        {
            get { return "PastebinSender"; }
        }

        public bool NeedSelectedText
        {
            get { return true; }
        }

        public event PluginHandler ShowBaloonHandler;
        public event PluginHandler ShowFormHandler;
        public event PluginHandler PasteTextHandler;

        /// <summary>
        /// Основной метод
        /// </summary>
        /// <param name="parametres">Формат входных данных "выводить ли ссылку в отдельной форме:код подсветки синтаксиса:время истечения срока"</param>
        /// <param name="text">Выбранный пользователем текст</param>
        public void MainMethod(string parametres, string text)
        {
           

            string[] param = parametres.Split(':');
            bool useMessage = param[0] == "1";
            string format = param[1];
            string expire = param[2];

            using (HttpRequest request = new HttpRequest())
            {

                MultipartDataCollection reqParams = new MultipartDataCollection();

                //Задаём необходимые параметры веб-запроса
                request.UserAgent = HttpHelper.RandomChromeUserAgent();

                string cont = request.Get("http://pastebin.com").ToText();
                string postKey = cont.Substring("post_key" value="", """);

                reqParams.AddData("post_key", postKey);
                reqParams.AddData("submit_hidden", "submit_hidden");
                reqParams.AddData("paste_code", text);
                reqParams.AddData("paste_format", format);
                reqParams.AddData("paste_expire_date", expire);
                reqParams.AddData("paste_private", "1");
                reqParams.AddData("paste_name", "");

                //Получаем ссылку на размещённый текст
                string link = request.Post("http://pastebin.com/post.php", reqParams).Address.AbsoluteUri;

                if (useMessage)
                {
                    ShowFormHandler(link);
                }
                else
                {
                    ShowBaloonHandler(link);
                }
            }
        }
    }

В этих плагинах для запросов по http я использовал xNet

Также я написал плагин для перевода выделенного текста:

 public class GoogleTranslator : IPlugin
    {
        public string Name
        {
            get { return "GoogleTranslator"; }
        }

        public bool NeedSelectedText
        {
            get { return true; }
        }

        public event PluginHandler ShowBaloonHandler;
        public event PluginHandler ShowFormHandler;
        public event PluginHandler PasteTextHandler;

        /// <summary>
        /// Основной метод
        /// </summary>
        /// <param name="parametres">Формат входных данных "на какой язык переводить:выводить ли ответ в отдельной форме"</param>
        /// <param name="text">Выбранный пользователем текст</param>
        public void MainMethod(string parametres, string text)
        {
            //Задаём необходимые параметры
            string[] param = parametres.Split(':');

            string ToLang = param[0];

            bool useMessage = param[1] == "1";

            //Если сообщение НЕ имеет формат язык::Предложение, то переводим предложение на заданный язык
            string[] message = text.Split(new string[] { "::" }, StringSplitOptions.None);

            if (message.Length != 2)
            {
                //Если предложение содержит русские буквы
                if (isRussian(text))
                {
                    //Получаем перевод на стандартный язык
                    string translate = GetTranslate(text, ToLang);

                    PasteTextHandler(translate);
                }

                //Если предложение НЕ содержит русские буквы
                else
                {
                    //Переводим его на русский
                    string translate = GetTranslate(text, "ru");

                    //И выводим его согласно настройкам
                    if (useMessage)
                    {
                        ShowFormHandler(translate);

                    }
                    else
                    {
                        ShowBaloonHandler(translate);
                    }
                }
            }

           //Если сообщение имеет формат язык::Предложение, то переводим предложение на заданный язык
            else
            {

                string mess = message[1];
                string toLang = message[0];
                //Переводим его с заданными параметрами
                string translate = GetTranslate(mess, toLang);

                PasteTextHandler(translate);
            }
        }

        private string GetTranslate(string message, string toLang)
        {
            //Создаём необходимые объекты для работы с веб-запросами
            using (HttpRequest request = new HttpRequest())
            {
                StringDictionary reqParams = new StringDictionary();

                //Задаём необходимые параметры веб-запроса
                request.UserAgent = HttpHelper.RandomChromeUserAgent();

                reqParams["tl"] = toLang;
                reqParams["sl"] = "auto";
                reqParams["client"] = "x";

                string translate = "";

                ShowBaloonHandler("Переводим...");

                reqParams["text"] = message;
                //Получаем ответ от сервера
                string s = request.Get(
                    "http://translate.google.ru/translate_a/t", reqParams).ToText();

                //Выдираем из него перевод каждого предложения
                string[] ts = s.Substrings("trans":"", "",");

                foreach (string t in ts)
                {

                    string tr = t;

                    if (tr.Contains('"'))
                    {
                        tr = tr.Replace("\", "");
                    }

                    //Склеиваем предложения
                    if (translate != "")
                    {
                        translate = translate + " " + tr;
                    }
                    else
                    {
                        translate = tr;
                    }
                }
                return translate;
            }
        }

        bool isRussian(string text)
        {
            foreach (char c in text)
            {
                if ((c >= 'А' && c <= 'я'))
                    return true;
            }
            return false;
        }
    }

После того, как мы написали и скомпилировали плагин, мы должны поместить его в 1 папку с программой, и при следующем запуске вы сможете «забиндить» на определённый хоткей выполнение данного плагина.

Автор: RoboNET


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js