Так случилось, что в такую мрачную погоду, обложив себя таблетками и препаратами от простуды я решил от нечего делать поделиться с хабра-сообществом инструментом, который я сделал для себя и уже почти что месяц им успешно пользуюсь. Речь идет о windows-программе, которая перехватывает копирование текста в буфер обмена и позволяет вставить из раннее скопированного текста любой фрагмент.
С чего всё началось
Как любому программисту, мне постоянно приходится работать с кодом. Помимо написания кода на C#, мне постоянно приходится писать sql-запросы. Причем достаточно сложные запросы: запросы с подзапросами, с вложенными запросами, с кучей LEFT JOIN и RIGHT JOIN-ов. Производя декомпозицию таких запросов на отдельные его части легко запутаться что к чему и зачем/для чего всё это писалось.Типичный пример одного sql-запроса (даже не пытайтесь понять что он делает)
SELECT IFNULL(SUM(t_step3.half_step3 - IFNULL(t_step1.bonus, 0) - IFNULL(t_step2.bonus, 0)), 0) AS 'bonus'
FROM (
SELECT t_orders2cat.order_id AS 'id',
(t_o.balance_rub - SUM(t_orders2cat.dprice_rub - t_orders2cat.delivery_cost_rub) - IFNULL(t_gurkin.half_step3, 0) - IFNULL(t_o2p.cost_expenses_rub, 0)) *
(
IF(base_on_tender = 0, IFNULL(t_category_percent.percent, 1),
IF(base_on_tender = 1,
IFNULL(t_category_percent.percent, 1) - 0.1,
IFNULL(t_category_percent.percent, 1) - 0.5)
) -
IF(t_orders.tech_helper_id != 0, t_users.tech_bonus_percent, 0)
) / 100 AS 'half_step3'
FROM t_orders2cat,
t_orders LEFT JOIN t_category_percent ON t_orders.category_percent_id = t_category_percent.id
LEFT JOIN t_users ON t_orders.tech_helper_id = t_users.id
LEFT JOIN (SELECT order_id, SUM(cost_expenses_rub) AS 'cost_expenses_rub'
FROM t_orders2pnr
GROUP BY t_orders2pnr.order_id
HAVING SUM(cost_expenses_rub) > 0) AS t_o2p ON t_orders.id = t_o2p.order_id
LEFT JOIN (
SELECT t_orders.id AS 'order_id',
SUM(deg_discount(deg_convert_money( t_orders.rate_eur,
t_orders.rate_usd,
t_orders.rate_jpy,
t_orders2cat.price,
t_orders2cat.currency_id,
t_orders.currency_id),
t_orders2cat.discount) * t_orders2cat.count -
t_orders2cat.dprice_rub -
t_orders2cat.delivery_cost_rub -
IFNULL(t_orders2pnr.cost_expenses_rub, 0)
) * t_p.balance_rub / t_orders.balance AS 'half_step3'
FROM t_orders LEFT JOIN t_orders2pnr ON t_orders.id = t_orders2pnr.order_id,
t_orders2cat,
t_cat,
t_vendors,
(
SELECT order_id,
SUM(summa_rub) AS 'balance_rub'
FROM t_payments
GROUP BY order_id
) AS t_p,
(
SELECT t_o.id,
GREATEST(IFNULL(t_o.date_shipment, t_o.date_pnr_finish), IFNULL(t_o.date_pnr_finish, t_o.date_shipment), MAX(t_payments.date_payment)) AS 'date_closed',
SUM(t_payments.summa_rub) AS 'balance_rub'
FROM t_payments,
(
SELECT t_orders.id,
COUNT(t_orders2cat.id) AS 'orders2cat_count',
MAX(t_orders2cat.date_from) AS 'date_shipment',
MAX(t_orders2cat.status_id) AS 'orders2cat_status',
COUNT(t_orders2pnr.id) AS 'pnr_count',
MAX(t_orders2pnr.date) AS 'date_pnr_finish',
MAX(t_orders2pnr.status_id) AS 'pnr_status'
FROM t_orders LEFT JOIN t_orders2cat ON t_orders.id = t_orders2cat.order_id
LEFT JOIN t_orders2pnr ON t_orders.id = t_orders2pnr.order_id
WHERE t_orders.user_id = in_user_id AND
t_orders.items_finished >= t_orders.items_count AND t_orders.summa - t_orders.balance < 2
GROUP BY t_orders.id
) AS t_o
WHERE t_payments.order_id = t_o.id
GROUP BY t_payments.order_id
HAVING EXTRACT(YEAR_MONTH FROM date_closed) = EXTRACT(YEAR_MONTH FROM in_date)
) AS t_o
WHERE t_orders.id = t_o.id AND
t_orders.id = t_p.order_id AND
t_orders.id = t_orders2cat.order_id AND
t_orders2cat.cat_id = t_cat.id AND
t_orders.user_id = in_user_id AND
t_cat.vendor_id = t_vendors.id AND
t_vendors.user_id = 158
GROUP BY t_orders.id
) AS t_gurkin ON t_orders.id = t_gurkin.order_id,
(
SELECT t_o.id,
GREATEST(IFNULL(t_o.date_shipment, t_o.date_pnr_finish), IFNULL(t_o.date_pnr_finish, t_o.date_shipment), MAX(t_payments.date_payment)) AS 'date_closed',
SUM(t_payments.summa_rub) AS 'balance_rub'
FROM t_payments,
(
SELECT t_orders.id,
COUNT(t_orders2cat.id) AS 'orders2cat_count',
MAX(t_orders2cat.date_from) AS 'date_shipment',
MAX(t_orders2cat.status_id) AS 'orders2cat_status',
COUNT(t_orders2pnr.id) AS 'pnr_count',
MAX(t_orders2pnr.date) AS 'date_pnr_finish',
MAX(t_orders2pnr.status_id) AS 'pnr_status'
FROM t_orders LEFT JOIN t_orders2cat ON t_orders.id = t_orders2cat.order_id
LEFT JOIN t_orders2pnr ON t_orders.id = t_orders2pnr.order_id
WHERE t_orders.user_id = in_user_id AND
t_orders.items_finished >= t_orders.items_count AND t_orders.summa - t_orders.balance < 2
GROUP BY t_orders.id
) AS t_o
WHERE t_payments.order_id = t_o.id
GROUP BY t_payments.order_id
HAVING EXTRACT(YEAR_MONTH FROM date_closed) = EXTRACT(YEAR_MONTH FROM in_date)
) AS t_o
WHERE t_orders2cat.order_id = t_o.id AND
t_orders2cat.order_id = t_orders.id
GROUP BY t_orders2cat.order_id
) AS t_step3
LEFT JOIN
(
SELECT t_orders.id,
t_o.average_curs * (t_orders.summa - IFNULL(t_gurkin.step1, 0)) *
(
IF(base_on_tender = 0, IFNULL(t_category_percent.percent, 1),
IF(base_on_tender = 1,
IFNULL(t_category_percent.percent, 1) - 0.1,
IFNULL(t_category_percent.percent, 1) - 0.5
)
) -
IF(t_orders.tech_helper_id != 0, t_users.tech_bonus_percent, 0)
) /
100 * 0.3 AS 'bonus'
FROM t_orders LEFT JOIN t_category_percent ON t_orders.category_percent_id = t_category_percent.id
LEFT JOIN t_users ON t_orders.tech_helper_id = t_users.id
LEFT JOIN (
SELECT t_orders.id AS 'order_id',
SUM(deg_discount(deg_convert_money( t_orders.rate_eur,
t_orders.rate_usd,
t_orders.rate_jpy,
t_orders2cat.price,
t_orders2cat.currency_id,
t_orders.currency_id),
t_orders2cat.discount) * t_orders2cat.count) * t_o.payment_rub / t_o.payment_currency AS 'step1'
FROM t_orders,
t_orders2cat,
t_cat,
t_vendors,
(
SELECT t_orders.id,
SUM(t_payments.summa_rub) AS 'payment_rub',
SUM(deg_convert_money(t_rates.eur, t_rates.usd, t_rates.jpy, t_payments.summa_rub, 1, t_orders.currency_id)) AS 'payment_currency',
t_orders.summa
FROM t_orders,
t_payments,
t_rates
WHERE t_orders.id = t_payments.order_id AND
t_payments.date_rates = t_rates.date AND
EXTRACT(YEAR_MONTH FROM t_orders.date_firstpay) = EXTRACT(YEAR_MONTH FROM t_payments.date_payment)
GROUP BY t_payments.order_id
HAVING payment_currency / t_orders.summa >= 0.3
) AS t_o
WHERE t_orders.id = t_orders2cat.order_id AND
t_orders2cat.order_id = t_o.id AND
t_orders.user_id = in_user_id AND
t_orders2cat.cat_id = t_cat.id AND
t_vendors.user_id = 158 AND
t_cat.vendor_id = t_vendors.id
GROUP BY t_o.id
) AS t_gurkin ON t_orders.id = t_gurkin.order_id,
(
SELECT t_orders.id,
SUM(t_payments.summa_rub) / SUM(deg_convert_money(t_rates.eur, t_rates.usd, t_rates.jpy, t_payments.summa_rub, 1, t_orders.currency_id)) AS 'average_curs'
FROM t_payments,
t_rates,
t_orders,
(
SELECT t_payments.order_id,
MIN(t_payments.date_payment) AS 'date_payment'
FROM t_payments
WHERE 0.3 <= (
SELECT SUM(deg_convert_money(t_rates.eur, t_rates.usd, t_rates.jpy, t_p.summa_rub, 1, t_o.currency_id)) / t_o.summa AS 'summa_cur'
FROM t_payments AS t_p,
t_orders AS t_o,
t_rates
WHERE t_p.order_id = t_o.id AND
t_p.date_rates = t_rates.date AND
t_p.date_payment <= t_payments.date_payment AND
t_o.id = t_payments.order_id
)
GROUP BY t_payments.order_id
) AS t_o
WHERE t_payments.order_id = t_o.order_id AND
t_payments.order_id = t_orders.id AND
t_payments.date_rates = t_rates.date AND
EXTRACT(YEAR_MONTH FROM t_payments.date_payment) <= EXTRACT(YEAR_MONTH FROM t_o.date_payment) AND
t_orders.user_id = in_user_id
GROUP BY t_orders.id
) AS t_o
WHERE t_orders.id = t_o.id AND
t_orders.user_id = in_user_id
) AS t_step1
ON t_step3.id = t_step1.id
LEFT JOIN
(
SELECT t_orders.id,
SUM(t_payments.summa_rub) / SUM(deg_convert_money(t_rates.eur, t_rates.usd, t_rates.jpy, t_payments.summa_rub, 1, t_orders.currency_id)) * (t_orders.summa - IFNULL(t_gurkin.step2, 0)) *
(
IF(base_on_tender = 0, IFNULL(t_category_percent.percent, 1),
IF(base_on_tender = 1,
IFNULL(t_category_percent.percent, 1) - 0.1,
IFNULL(t_category_percent.percent, 1) - 0.5
)
) -
IF(t_orders.tech_helper_id != 0, t_users.tech_bonus_percent, 0)
) / 100 * 0.3 AS 'bonus'
FROM t_payments,
t_rates,
t_orders LEFT JOIN t_category_percent ON t_orders.category_percent_id = t_category_percent.id
LEFT JOIN t_users ON t_orders.tech_helper_id = t_users.id
LEFT JOIN (
SELECT t_orders.id AS 'order_id',
SUM(deg_discount(deg_convert_money( t_orders.rate_eur,
t_orders.rate_usd,
t_orders.rate_jpy,
t_orders2cat.price,
t_orders2cat.currency_id,
t_orders.currency_id),
t_orders2cat.discount) * t_orders2cat.count) * t_o.payment_rub / t_o.payment_currency AS 'step2'
FROM t_orders,
t_orders2cat,
t_cat,
t_vendors,
(
SELECT t_orders.id,
SUM(t_payments.summa_rub) AS 'payment_rub',
SUM(deg_convert_money(t_rates.eur, t_rates.usd, t_rates.jpy, t_payments.summa_rub, 1, t_orders.currency_id)) AS 'payment_currency'
FROM t_payments,
t_rates,
t_orders,
(
SELECT t_o.id,
GREATEST(date_exit, date_payment) AS 'date_payment_bonus'
FROM (
SELECT t_o.id AS 'id',
MAX(t_containers.date_port_exit) AS 'date_exit'
FROM t_orders2cat,
t_invoices2orders2cat,
t_containers2invoices,
t_containers,
(
SELECT t_orders.id
FROM t_orders,
t_orders2cat
WHERE t_orders.id = t_orders2cat.order_id AND
t_orders.user_id = in_user_id AND
4 <= ALL (SELECT t_o2c.status_id FROM t_orders2cat AS t_o2c WHERE t_o2c.order_id = t_orders.id)
GROUP BY t_orders.id
) AS t_o
WHERE t_orders2cat.id = t_invoices2orders2cat.order2cat_id AND
t_invoices2orders2cat.invoice_id = t_containers2invoices.invoice_id AND
t_containers2invoices.container_id = t_containers.id AND
t_orders2cat.order_id = t_o.id
GROUP BY t_o.id
) AS t_o,
(
SELECT t_payments.order_id,
MIN(t_payments.date_payment) AS 'date_payment'
FROM t_payments
WHERE 0.8 <= (
SELECT SUM(deg_convert_money(t_rates.eur, t_rates.usd, t_rates.jpy, t_p.summa_rub, 1, t_o.currency_id)) / t_o.summa AS 'summa_cur'
FROM t_payments AS t_p,
t_orders AS t_o,
t_rates
WHERE t_p.order_id = t_o.id AND
t_o.user_id = in_user_id AND
t_p.date_rates = t_rates.date AND
t_p.date_payment <= t_payments.date_payment AND
t_o.id = t_payments.order_id
)
GROUP BY t_payments.order_id
) t_p
WHERE t_o.id = t_p.order_id
) AS t_o
WHERE t_payments.order_id = t_o.id AND
t_payments.date_rates = t_rates.date AND
t_payments.order_id = t_orders.id AND
EXTRACT(YEAR_MONTH FROM t_payments.date_payment) <= EXTRACT(YEAR_MONTH FROM t_o.date_payment_bonus)
GROUP BY t_orders.id
) AS t_o
WHERE t_orders.id = t_orders2cat.order_id AND
t_orders2cat.order_id = t_o.id AND
t_orders2cat.cat_id = t_cat.id AND
t_orders.user_id = in_user_id AND
t_vendors.user_id = 158 AND
t_cat.vendor_id = t_vendors.id
GROUP BY t_o.id
) AS t_gurkin ON t_orders.id = t_gurkin.order_id,
(
SELECT t_o.id,
GREATEST(date_exit, date_payment) AS 'date_payment_bonus'
FROM (
SELECT t_o.id AS 'id',
MAX(t_containers.date_port_exit) AS 'date_exit'
FROM t_orders2cat,
t_invoices2orders2cat,
t_containers2invoices,
t_containers,
(
SELECT t_orders.id
FROM t_orders,
t_orders2cat
WHERE t_orders.id = t_orders2cat.order_id AND
t_orders.user_id = in_user_id AND
4 <= ALL (SELECT t_o2c.status_id FROM t_orders2cat AS t_o2c WHERE t_o2c.order_id = t_orders.id) AND
t_orders.user_id = in_user_id
GROUP BY t_orders.id
) AS t_o
WHERE t_orders2cat.id = t_invoices2orders2cat.order2cat_id AND
t_invoices2orders2cat.invoice_id = t_containers2invoices.invoice_id AND
t_containers2invoices.container_id = t_containers.id AND
t_orders2cat.order_id = t_o.id
GROUP BY t_o.id
) AS t_o,
(
SELECT t_payments.order_id,
MIN(t_payments.date_payment) AS 'date_payment'
FROM t_payments
WHERE 0.8 <= (
SELECT SUM(deg_convert_money(t_rates.eur, t_rates.usd, t_rates.jpy, t_p.summa_rub, 1, t_o.currency_id)) / t_o.summa AS 'summa_cur'
FROM t_payments AS t_p,
t_orders AS t_o,
t_rates
WHERE t_p.order_id = t_o.id AND
t_o.user_id = in_user_id AND
t_p.date_rates = t_rates.date AND
t_p.date_payment <= t_payments.date_payment AND
t_o.id = t_payments.order_id
)
GROUP BY t_payments.order_id
) t_p
WHERE t_o.id = t_p.order_id
) AS t_o
WHERE t_o.id = t_payments.order_id AND
t_orders.id = t_o.id AND
t_orders.user_id = in_user_id AND
t_payments.date_rates = t_rates.date AND
EXTRACT(YEAR_MONTH FROM t_payments.date_payment) <= EXTRACT(YEAR_MONTH FROM t_o.date_payment_bonus)
GROUP BY t_orders.id
) AS t_step2
ON t_step3.id = t_step2.id;
Почитав статью Пишем виртуальный буфер обмена на C#, решил попробовать в действии то, что предложил нам yanzlatov, но его вариант для меня был неприемлем: во многом неудобство к быстрому доступу скопированных в буфер буферов, глючность…
Требования
Сформулировав для себя четкие требования того, что мне необходимо, было принято решение заново написать велосипед.- Так как я работаю на ноуте (Win7x64), на домашнем компьютере (Win8.1x64) и на рабочем компьютере (Win8x64), то разрабатываемое приложение должно работать на трех осях Windows 7x64, 8x64, 8.1x64 с установленным .Net Framework версии 4 и выше.
- Приложение должно втихаря загружаться в момент загрузки ОС
- При копировании текста в буфер обмена приложение должно сохранять этот текст в памяти
- При вставки текста (например вызовом клавиш ctrl + v или соответствующим пунктом меню) должен вставляться последний отправленный в буфер текст
- При нажатии ctrl + alt + v должно появиться окно с возможностью выбора буфера
- История буферов должна сохраняться даже после перезагрузки
- Крайне необходима полноценная поддержка горячих клавиш
- Так как количество сохраненных элементов может быть огромным необходим удобный поиск по всем элементам
Код
Исходник проекта вы можете скачать здесь. Exe-файл можно забрать здесь.
Я не буду заострять внимание на всём коде. Остановлюсь на некоторых моментах.
В самом начале запуска делается при помощи мьютекса проверка на то, чтобы не был запущен еще один экземпляр приложения:
static void Main()
{
bool createdNew;
Mutex mutex = new Mutex(true, "MY_UNIQUE_MUTEX_ClipboardToClipboard", out createdNew);
if (!createdNew)
{
MessageBox.Show("В памяти компьютера уже загружена один экземпляр данного приложения", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Warning);
Process.GetCurrentProcess().Kill();
}
...
...
...
mutex.ReleaseMutex();
}
Сам процесс перехвата нажатия комбинации ctrl + alt + v
public static EventCtrlAltVHandler EventPressCtrlAltV;
private static Keys lastKey = Keys.FinalMode;
private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
if ((nCode >= 0) && (wParam == (IntPtr)WM_KEYDOWN))
{
Keys key = (Keys)Marshal.ReadInt32(lParam);
if (lastKey == Keys.LMenu && key == Keys.V)
EventPressCtrlAltV();
lastKey = key;
}
return CallNextHookEx(_hookID, nCode, wParam, lParam);
}
В MainForm.cs имеется функция, которая имитирует нажатие ctrl + {клавиша}. В моем случае это было ctrl + v на выбранном из списка элементе
private static void SendCtrlhotKey(char key)
{
keybd_event(VK_CONTROL, 0, 0, 0);
keybd_event((byte)key, 0, 0, 0);
keybd_event((byte)key, 0, KEYEVENTF_KEYUP, 0);
keybd_event(VK_CONTROL, 0, KEYEVENTF_KEYUP, 0);
}
В принципе там ничего такого особенного нету. История хранится в xml-файле, настройки — прям в свойствах приложения… Кому интересно постарайтесь сами посмотреть, разобраться. Если будут вопросы, могу на них ответить.
Как на практике это всё выглядит и работает
Тут всё очень просто. Запускаем exe-шник. Выделяете фрагмент текста, жмакаете ctrl + c и в системном трее появляется уведомление:Казалось бы, что эта фича бесполезна, но за ней скрывается более крутая фича — если на всплывающей подсказке кликнуть мышью, то откроется окно с возможностью добавления комментария:
Что делать с этим комментарием я расскажу попозже.
Теперь предположим, что нам понадобилось вставить текст, который фиг знает когда был скопирован в буфер обмена. Нет ничего проще — жмём ctrl + alt + v:
Мы тут же можем, перемещаясь по списку клавишами вверх или вниз, выбрать нужный нам текст. При этом, при перемещении в списке по элементам, появляется всплывающая подсказка, которая может быть удобна в том случае, если текст — многострочный. Кстати говоря, вот тут и отображается комментарий, который вы могли указать раннее:
Согласитесь, очень удобная фича при редактировании индуского кода)
Итак, вы выбрали нужный в списке элемент. Теперь просто нажимаете Enter и текст вставится в ту позицию вашего редактора, где установлен курсор.
В этом главном окне вы можете как поэлементно удалить выделенный фрагмент при помощи клавиши Del, так и полностью очистить весь буфер буферов; вы можете выделенному элементу задать комментарий при помощи комбинации ctrl + R.
Нажимая escape, окно прячется в трей.
Отдельного внимая заслуживает поиск. Если вам надо что-то найти в среди буферов, то можно быстро переключиться на поиск: ctrl + alt + v -> ctrl + f. В этом случае фокус устанавливается в строке поиска.
Вводим несколько ключевых букв, нажимает Enter -> теперь фокус будет на списке, по которому можно будет опять же перемещаться клавишами вверх или вниз. Если в строке поиска фокус и имеется текст, то при нажатии escape текстовое поле очищается. Повторное нажатие escape скрывает окно.
В настройках ничего особенного. Как-то реализовал уровень прозрачности, думал, что это будет полезная фича. На деле оказалось, что толку от нее мало. Оставил ее, но для себя прозрачность оставил 100%: