📜 ⬆️ ⬇️

We write virtual clipboard on C #

VirtualClipBoard A lot of work has to be done with textual data, such as code, articles, posts, etc. At the time when I lived under Linux, I used clipboard history managers , who remembered, what got into text into the buffer and by clicking on the tray I could return the desired value to the buffer without returning to the source.
Recently I had to spend most of my time on Windows, I did not find a satisfying alternative for such a simple application. Something in the found variants did not suit: not free software, a lot of unnecessary functionality (which just interfered) or worked inconveniently for me (for example: I had to open the program window to get the previous buffer value). Without thinking twice decided to do, as I wanted.

Since the application should work exclusively in Windows, it was decided to write it in C # - besides, I had never written anything on it before - there was reason to try.

Task



')


Interface




VirtualClipBoard

I will post the main parts of the code with some explanations. At the end, under the spoiler, a full listing of the program is posted, as well as a link to the complete project zipped archive + separately link to the compiled version of the program as an exe file.


Directly the code itself



I didn’t want to reinvent the bike for storing program settings, so I used Properties.SettingDefault for “history size” and “number of displayed items in the tray”:

//    private void history_size_ValueChanged(object sender, EventArgs e) { Properties.Settings.Default.history_size = (int)history_size.Value; Properties.Settings.Default.Save(); Console.WriteLine("  : " + Properties.Settings.Default.history_size); reload_list_clipboard(); //  ListBox } //       private void size_tray_ValueChanged(object sender, EventArgs e) { Properties.Settings.Default.size_tray = (int)size_tray.Value; Properties.Settings.Default.Save(); Console.WriteLine("    : " + Properties.Settings.Default.size_tray); reload_tray(); //   } 


Startup, the program, as previously agreed, is implemented through the registry.
  //      //   -      private void autoload_CheckedChanged(object sender, EventArgs e) { RegistryKey reg = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Run\", true); if (reg.GetValue(VirtualClipBoard_Name) != null) { try { reg.DeleteValue(VirtualClipBoard_Name); Console.WriteLine(" " + VirtualClipBoard_Name + "     "); } catch { Console.WriteLine("  " + VirtualClipBoard_Name + "    "); } } if(autoload.Checked) { reg.SetValue(VirtualClipBoard_Name, Application.ExecutablePath); Console.WriteLine(" " + VirtualClipBoard_Name + "     "); } reg.Close(); } 


When you close the window, the program should be minimized to tray, and not shut down
  //       protected override void OnClosing(CancelEventArgs e) { e.Cancel = true; // ShowInTaskbar = false; Hide(); } 


Method to close the program
  //        private void exit_Click(object sender, EventArgs e) { Application.Exit(); } 


Since, checking on the timer changes in the clipboard is a perversion + an unjustified waste of resources, we will use User32.dll . To do this, we need to add our window to the clipboard window chain and use WndProc to receive messages, more specifically WM_DRAWCLIPBOARD = 0x0308 , which will notify us of the change in the exchange buffer.

So, 1. We connect libraries:
  [DllImport("User32.dll")] protected static extern int SetClipboardViewer(int hWndNewViewer); [DllImport("User32.dll", CharSet = CharSet.Auto)] public static extern bool ChangeClipboardChain(IntPtr hWndRemove, IntPtr hWndNewNext); [DllImport("user32.dll", CharSet = CharSet.Auto)] public static extern int SendMessage(IntPtr hwnd, int wMsg, IntPtr wParam, IntPtr lParam); 


2. Implementation of the WndProc method of receiving messages and sending them further along the chain:
  //   private IntPtr nextClipboardViewer; //  public const int WM_DRAWCLIPBOARD = 0x0308; public const int WM_CHANGECBCHAIN = 0x030D; //         .. protected override void WndProc(ref Message m) { switch (m.Msg) { case WM_DRAWCLIPBOARD: { ClipboardChanged(); SendMessage(nextClipboardViewer, m.Msg, m.WParam, m.LParam); break; } case WM_CHANGECBCHAIN: { if (m.WParam == nextClipboardViewer) { nextClipboardViewer = m.LParam; } else { SendMessage(nextClipboardViewer, WM_CHANGECBCHAIN, m.WParam, m.LParam); } m.Result = IntPtr.Zero; break; } default: { base.WndProc(ref m); break; } } } 


3. In the form upload method, register our window:
 nextClipboardViewer = (IntPtr)SetClipboardViewer((int)this.Handle); 


4. Now we need to react to the change in the buffer and write them into history, for this we will create the ClipboardChanged method. History will be written to the Dictionary dictionary <int, string> VirtualClipBoard_History = new Dictionary <int, string> (); if the number of elements in the dictionary is larger than the size of the history, in the method we also introduce a clearing of older elements. Also, to be able to get the history of the previous session, we will write new elements to the file VirtualClipBoard_DAT .

  //     private void ClipboardChanged() { if (Clipboard.ContainsText() && Clipboard.GetText().Length > 0 && VirtualClipBoard_TARGET != Clipboard.GetText()) { VirtualClipBoard_TARGET = Clipboard.GetText(); //      VirtualClipBoard_History.Add((VirtualClipBoard_History.Last().Key + 1), VirtualClipBoard_TARGET); reload_tray(); //    reload_list_clipboard(); //  ListBox //      if (VirtualClipBoard_History.Count() > Properties.Settings.Default.history_size) { int clear_items_count = VirtualClipBoard_History.Count() - Properties.Settings.Default.history_size; var list = VirtualClipBoard_History.Keys.ToList(); list.Sort(); foreach (var key in list) { VirtualClipBoard_History.Remove(key); if (clear_items_count == 1) { break; } else { clear_items_count--; } } } //       StreamWriter writer = new StreamWriter(VirtualClipBoard_DAT, true, System.Text.Encoding.UTF8); writer.WriteLine(@"<item>" + VirtualClipBoard_TARGET.Replace(@"<", @"<").Replace(@">", @">") + @"</item>"); writer.Close(); Console.WriteLine("    : " + VirtualClipBoard_TARGET); } } 


To output elements to the tray, create the reload_tray method. We will use ContextMenuStrip . For each ToolStripMenuItem we will use the TAG , in which we will transfer the key in the dictionary, so that when selecting an item in the context menu, it is easy to pull the necessary item from the dictionary.
  //     private void reload_tray() { ContextMenuStrip contextMenu = new ContextMenuStrip(); ToolStripMenuItem menuItem; int free_slot_to_tray = Properties.Settings.Default.size_tray; var list = VirtualClipBoard_History.OrderByDescending(x => x.Key); foreach (var item in list) { menuItem = new ToolStripMenuItem(); menuItem.Tag = item.Key; if (item.Value.Length > 60) { menuItem.Text = item.Value.Replace("\n", "\t").Replace("\r", "\t").Substring(0, 60); } else { menuItem.Text = item.Value.Replace("\n", "\t").Replace("\r", "\t"); } menuItem.Click += new System.EventHandler(menu_item_click); contextMenu.Items.Add(menuItem); if (free_slot_to_tray == 1) { break; } else { free_slot_to_tray--; } } //  contextMenu.Items.Add(new ToolStripSeparator()); // / menuItem = new ToolStripMenuItem(); menuItem.Text = ""; menuItem.Click += new System.EventHandler(menu_item_config); contextMenu.Items.Add(menuItem); //    menuItem = new ToolStripMenuItem(); menuItem.Text = ""; menuItem.Click += new System.EventHandler(exit_Click); contextMenu.Items.Add(menuItem); _notifyIcon.ContextMenuStrip = contextMenu; } 


Click handling events in the tray context menu:
  //          private void menu_item_click(object sender, EventArgs e) { Clipboard.SetText(VirtualClipBoard_History[(int)(sender as ToolStripMenuItem).Tag]); } 


List generation for ListBox in the form of the program itself. For convenience, we will use the additional dictionary VirtualClipBoard_Index_ListBox = new Dictionary <int, int> (); to associate a ListBox with a element in the VirtualClipBoard_History history dictionary:
  //    ListBox private void reload_list_clipboard() { VirtualClipBoard_Index_ListBox = new Dictionary<int, int>(); int list_target_item = 0; //     ListBox list_clipboard.Items.Clear(); //   String string_name_ite; int free_slot_to_tray = Properties.Settings.Default.history_size; var list = VirtualClipBoard_History.OrderByDescending(x => x.Key); foreach (var item in list) { if (item.Value.Length > 150) { string_name_ite = item.Value.Replace("\n", "\t").Replace("\r", "\t").Substring(0, 60); } else { string_name_ite = item.Value.Replace("\n", "\t").Replace("\r", "\t"); } list_clipboard.Items.Add(string_name_ite); VirtualClipBoard_Index_ListBox.Add(list_target_item, item.Key); if (free_slot_to_tray == 1) { break; } else { free_slot_to_tray--; } list_target_item++; //      ListBox } } 


When selecting an item in ListBox , the list_clipboard_SelectedIndexChanged event:
  //    ListBox private void list_clipboard_SelectedIndexChanged(object sender, EventArgs e) { Clipboard.SetText(VirtualClipBoard_History[VirtualClipBoard_Index_ListBox[list_clipboard.SelectedIndex]]); } 


While the program is loading, we read the history from the VirtualClipBoard_DAT file and parse the data as XML.
Clipboard history elements cannot be stored separately in each line in our case, because the elements may have carriage-transfer characters (\ n) that may damage our data integrity when reading.
If you use serialize, you can lose on the program speed with each change of the buffer.
Of course, it would be more correct to save the history to a file when the program was closed, but I didn’t like this option in this case, so I organized it in this way (I can hook someone on this - I apologize right away).
I also did not want to encode the data, I wanted to leave the original view of the data so that if necessary I could read the history file through notepad.
  //     String XMLString = ""; XMLString += @"<items>"; if (File.Exists(VirtualClipBoard_DAT)) { StreamReader stream = new StreamReader(VirtualClipBoard_DAT); while (stream.Peek() > -1) { XMLString += stream.ReadLine() + "\n"; } stream.Close(); XMLString += @"</items>"; int index_new_history = 2; XDocument doc = XDocument.Parse(XMLString); var items = doc.Element("items").Elements("item"); foreach (XElement item in items) { VirtualClipBoard_History.Add(index_new_history, item.Value); index_new_history++; //     } } 


All project code


All project code
 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using Microsoft.Win32; using System.Runtime.InteropServices; using System.Data.Sql; using System.IO; using System.Text.RegularExpressions; using System.Xml; using System.Xml.Linq; using System.Windows; namespace VirtualClipBoard { public partial class VirtualClipBoard : Form { String VirtualClipBoard_Name = "VirtualClipBoard"; //   public String VirtualClipBoard_TARGET; //     public String VirtualClipBoard_DAT; //     Dictionary<int, string> VirtualClipBoard_History = new Dictionary<int, string>(); //    Dictionary<int, int> VirtualClipBoard_Index_ListBox; //         //   WIN [DllImport("User32.dll", CharSet = CharSet.Auto)] public static extern IntPtr SetClipboardViewer(IntPtr hWndNewViewer); [DllImport("User32.dll", CharSet = CharSet.Auto)] public static extern bool ChangeClipboardChain(IntPtr hWndRemove, IntPtr hWndNewNext); [DllImport("user32.dll", CharSet = CharSet.Auto)] public static extern int SendMessage(IntPtr hwnd, int wMsg, IntPtr wParam, IntPtr lParam); //  Form public VirtualClipBoard() { InitializeComponent(); load_configs(); nextClipboardViewer = (IntPtr)SetClipboardViewer((IntPtr)this.Handle); reload_tray(); //    reload_list_clipboard(); //  ListBox _notifyIcon.Text = VirtualClipBoard_Name; _notifyIcon.MouseDoubleClick += new MouseEventHandler(_notifyIcon_MouseDoubleClick); } //    ListBox private void reload_list_clipboard() { VirtualClipBoard_Index_ListBox = new Dictionary<int, int>(); int list_target_item = 0; //     ListBox list_clipboard.Items.Clear(); //   String string_name_ite; int free_slot_to_tray = Properties.Settings.Default.history_size; var list = VirtualClipBoard_History.OrderByDescending(x => x.Key); foreach (var item in list) { if (item.Value.Length > 150) { string_name_ite = item.Value.Replace("\n", "\t").Replace("\r", "\t").Substring(0, 60); } else { string_name_ite = item.Value.Replace("\n", "\t").Replace("\r", "\t"); } list_clipboard.Items.Add(string_name_ite); VirtualClipBoard_Index_ListBox.Add(list_target_item, item.Key); if (free_slot_to_tray == 1) { break; } else { free_slot_to_tray--; } list_target_item++; //      ListBox } } //    ListBox private void list_clipboard_SelectedIndexChanged(object sender, EventArgs e) { Clipboard.SetText(VirtualClipBoard_History[VirtualClipBoard_Index_ListBox[list_clipboard.SelectedIndex]]); } //     private void reload_tray() { ContextMenuStrip contextMenu = new ContextMenuStrip(); ToolStripMenuItem menuItem; int free_slot_to_tray = Properties.Settings.Default.size_tray; var list = VirtualClipBoard_History.OrderByDescending(x => x.Key); foreach (var item in list) { menuItem = new ToolStripMenuItem(); menuItem.Tag = item.Key; if (item.Value.Length > 60) { menuItem.Text = item.Value.Replace("\n", "\t").Replace("\r", "\t").Substring(0, 60); } else { menuItem.Text = item.Value.Replace("\n", "\t").Replace("\r", "\t"); } menuItem.Click += new System.EventHandler(menu_item_click); contextMenu.Items.Add(menuItem); if (free_slot_to_tray == 1) { break; } else { free_slot_to_tray--; } } //  contextMenu.Items.Add(new ToolStripSeparator()); // / menuItem = new ToolStripMenuItem(); menuItem.Text = ""; menuItem.Click += new System.EventHandler(menu_item_config); contextMenu.Items.Add(menuItem); //    menuItem = new ToolStripMenuItem(); menuItem.Text = ""; menuItem.Click += new System.EventHandler(exit_Click); contextMenu.Items.Add(menuItem); _notifyIcon.ContextMenuStrip = contextMenu; } //    private void menu_item_config(object sender, EventArgs e) { // ShowInTaskbar = true; Show(); WindowState = FormWindowState.Normal; } //          private void menu_item_click(object sender, EventArgs e) { // Console.WriteLine((int)(sender as ToolStripMenuItem).Tag); Clipboard.SetText(VirtualClipBoard_History[(int)(sender as ToolStripMenuItem).Tag]); } //         private void _notifyIcon_MouseDoubleClick(object sender, MouseEventArgs e) { Console.WriteLine(WindowState); if (WindowState == FormWindowState.Normal || WindowState == FormWindowState.Maximized) { // ShowInTaskbar = false; Hide(); WindowState = FormWindowState.Minimized; } else { // ShowInTaskbar = true; Show(); WindowState = FormWindowState.Normal; } } //        private void load_configs() { VirtualClipBoard_DAT = Application.UserAppDataPath + "\\history.dat"; Console.WriteLine(" : " + VirtualClipBoard_DAT); history_size.Value = Properties.Settings.Default.history_size; Console.WriteLine("    : " + Properties.Settings.Default.history_size); size_tray.Value = Properties.Settings.Default.size_tray; Console.WriteLine("      : " + Properties.Settings.Default.size_tray); RegistryKey reg = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Run\", true); if (reg.GetValue(VirtualClipBoard_Name) != null){ autoload.Checked = true; Console.WriteLine("   . (   Checked = true)"); } reg.Close(); //     String XMLString = ""; XMLString += @"<items>"; if (File.Exists(VirtualClipBoard_DAT)) { StreamReader stream = new StreamReader(VirtualClipBoard_DAT); while (stream.Peek() > -1) { XMLString += stream.ReadLine() + "\n"; } stream.Close(); XMLString += @"</items>"; int index_new_history = 2; XDocument doc = XDocument.Parse(XMLString); var items = doc.Element("items").Elements("item"); foreach (XElement item in items) { VirtualClipBoard_History.Add(index_new_history, item.Value); index_new_history++; //     } } //    if (VirtualClipBoard_History.Count() > Properties.Settings.Default.history_size) { int clear_items_count = VirtualClipBoard_History.Count() - Properties.Settings.Default.history_size; var list = VirtualClipBoard_History.Keys.ToList(); list.Sort(); foreach (var key in list) { VirtualClipBoard_History.Remove(key); if (clear_items_count == 1) { break; } else { clear_items_count--; } } } //    StreamWriter writer = new StreamWriter(VirtualClipBoard_DAT, false, System.Text.Encoding.UTF8); var new_list = VirtualClipBoard_History.Keys.ToList(); new_list.Sort(); foreach (var key in new_list) { writer.WriteLine(@"<item>" + VirtualClipBoard_History[key].Replace(@"<", @"<").Replace(@">", @">") + @"</item>"); } writer.Close(); //   ,    Console.WriteLine(VirtualClipBoard_History.Count()); if (VirtualClipBoard_History.Count() == 0) { VirtualClipBoard_TARGET = Clipboard.GetText(); VirtualClipBoard_History.Add(1, VirtualClipBoard_TARGET); } VirtualClipBoard_TARGET = VirtualClipBoard_History.Last().Value; } //      //   -      private void autoload_CheckedChanged(object sender, EventArgs e) { RegistryKey reg = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Run\", true); if (reg.GetValue(VirtualClipBoard_Name) != null) { try { reg.DeleteValue(VirtualClipBoard_Name); Console.WriteLine(" " + VirtualClipBoard_Name + "     "); } catch { Console.WriteLine("  " + VirtualClipBoard_Name + "    "); } } if(autoload.Checked) { reg.SetValue(VirtualClipBoard_Name, Application.ExecutablePath); Console.WriteLine(" " + VirtualClipBoard_Name + "     "); } reg.Close(); } //        private void exit_Click(object sender, EventArgs e) { Application.Exit(); } //    private void history_size_ValueChanged(object sender, EventArgs e) { Properties.Settings.Default.history_size = (int)history_size.Value; Properties.Settings.Default.Save(); Console.WriteLine("  : " + Properties.Settings.Default.history_size); reload_list_clipboard(); //  ListBox } //       private void size_tray_ValueChanged(object sender, EventArgs e) { Properties.Settings.Default.size_tray = (int)size_tray.Value; Properties.Settings.Default.Save(); Console.WriteLine("    : " + Properties.Settings.Default.size_tray); reload_tray(); //   } //     private void ClipboardChanged() { if (Clipboard.ContainsText() && Clipboard.GetText().Length > 0 && VirtualClipBoard_TARGET != Clipboard.GetText()) { VirtualClipBoard_TARGET = Clipboard.GetText(); //      VirtualClipBoard_History.Add((VirtualClipBoard_History.Last().Key + 1), VirtualClipBoard_TARGET); reload_tray(); //    reload_list_clipboard(); //  ListBox //      if (VirtualClipBoard_History.Count() > Properties.Settings.Default.history_size) { int clear_items_count = VirtualClipBoard_History.Count() - Properties.Settings.Default.history_size; var list = VirtualClipBoard_History.Keys.ToList(); list.Sort(); foreach (var key in list) { VirtualClipBoard_History.Remove(key); if (clear_items_count == 1) { break; } else { clear_items_count--; } } } //       StreamWriter writer = new StreamWriter(VirtualClipBoard_DAT, true, System.Text.Encoding.UTF8); writer.WriteLine(@"<item>" + VirtualClipBoard_TARGET.Replace(@"<", @"<").Replace(@">", @">") + @"</item>"); writer.Close(); Console.WriteLine("    : " + VirtualClipBoard_TARGET); } } //    private void clear_Click(object sender, EventArgs e) { StreamWriter writer = new StreamWriter(VirtualClipBoard_DAT, false, System.Text.Encoding.Default); writer.Write(""); writer.Close(); VirtualClipBoard_History = new Dictionary<int, string>(); VirtualClipBoard_TARGET = Clipboard.GetText(); VirtualClipBoard_History.Add(1, VirtualClipBoard_TARGET); reload_tray(); //    reload_list_clipboard(); //  ListBox } //       protected override void OnClosing(CancelEventArgs e) { e.Cancel = true; //ShowInTaskbar = false; Hide(); WindowState = FormWindowState.Minimized; } //   private IntPtr nextClipboardViewer; //  public const int WM_DRAWCLIPBOARD = 0x308; public const int WM_CHANGECBCHAIN = 0x030D; //         .. protected override void WndProc(ref Message m) { // Console.WriteLine("WndProc"); switch (m.Msg) { case WM_DRAWCLIPBOARD: { ClipboardChanged(); Console.WriteLine("WM_DRAWCLIPBOARD ClipboardChanged();"); SendMessage(nextClipboardViewer, WM_DRAWCLIPBOARD, m.WParam, m.LParam); break; } case WM_CHANGECBCHAIN: { if (m.WParam == nextClipboardViewer) { nextClipboardViewer = m.LParam; } else { SendMessage(nextClipboardViewer, WM_CHANGECBCHAIN, m.WParam, m.LParam); } m.Result = IntPtr.Zero; break; } default: { base.WndProc(ref m); break; } } } } } 


Bibliography:


Properties.Settings.Default - saving custom application settings
Registry.LocalMachine - working with the registry
SetClipboardViewer - we add a defined window to the chain of clipboard view windows.
class StreamWriter - write to file
ContextMenuStrip class - context menu
class Dictionary - Represents a collection of keys and values.
Clipboard class - Provides methods for putting data into the system clipboard and retrieving data from the system clipboard.

Links to the project:


Download the source of the entire project for VS in zip (133KB)
Download compiled EXE file (225KB)

UPDATE 1: Filled source on github: https://github.com/yanzlatov/ClipBoard

UPDATE 2: Corrected the code a bit, because I noticed the feature if you set ShowInTaskbar = false ( Gets or sets a value indicating whether the form is displayed in the Windows taskbar. ) Our window falls off the clipboard update listening chain and does not receive an update later .

UPDATE 3: Replaced Microsoft.Win32.Registry.LocalMachine with Microsoft.Win32.Registry.CurrentUser . (Thanks xaizek ).

Source: https://habr.com/ru/post/208314/


All Articles