Буфера для буферов или пишем виртуальный буфер обмена на C# не в 30 строк кода

от автора

Так случилось, что в такую мрачную погоду, обложив себя таблетками и препаратами от простуды я решил от нечего делать поделиться с хабра-сообществом инструментом, который я сделал для себя и уже почти что месяц им успешно пользуюсь. Речь идет о 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.1×64) и на рабочем компьютере (Win8x64), то разрабатываемое приложение должно работать на трех осях Windows 7×64, 8×64, 8.1×64 с установленным .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%:

Заключение

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

ссылка на оригинал статьи http://habrahabr.ru/post/211653/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *