Привет, дорогой читатель!
В этой статье я хочу показать, как я делал программу для ускорения и упрощения некоторых действий с помощью хоткеев.
Вступление
Примерно полтора месяца назад пользователь vvzvlad натолкнул меня на идею программы, для перевода различного текста по нажатию хоткея. Я очень долго пользовался этой программкой, но вот пару дней назад мне пришла в голову идея улучшить её.
Я решил добавить возможность назначать различные хоткеи на разные действия. Для этого я использовал систему плагинов.
В итоге получилась программа HotKeyHelper, которую вы можете скачать здесь.
Файл проекта находится здесь.
Под катом вы увидите исходный код программы и пояснения к нему.
Программа
Как я уже написал выше, я решил использовать систему плагинов.
Для этого нам необходимо написать интерфейс плагинов:
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