Creating Your Own Advanced Clipboard in .NET

Introduction

The Windows clipboard is probably the tool that gets used the most. And, it is probably the tool that is loved the most. But, it has its limitations. Nothing is perfect.

Today, you will learn how to make a clipboard that can hold multiple objects at the same time, as well as how to connect it to a global hot key. A global hot key is a key, such as Ctrl + C, from anywhere, and it functions accordingly by copying the selected item to the clipboard. We will replicate this behaviour, but we will incorporate different channels for the clipboard.

Let’s play!

Create a new Windows Forms project in either C# or VB.NET. After the project has loaded, add the following fields.

C#

   private int intChannel = 1;
   private object[] objData = new object[5];
   private ClipboardDataType[] cdtType = new ClipboardDataType[5];

   private enum ClipboardDataType
   {

      Empty,
      Text,
      Image,
      Audio,
      FileList
   }

VB.NET

   Private intChannel As Integer = 1
   Private objData(4) As Object
   Private cdtType(4) As ClipboardDataType

   Private Enum ClipboardDataType

      Empty
      Text
      Image
      Audio
      FileList

   End Enum

Here, we created a channel object. We will use this to keep track of the number of channels being used. Then, we created two arrays: one for the data to be placed on and retrieved from the clipboard, and one for the data type which depends on the enumeration type.

Add the following Windows APIs.

C#

   [System.Runtime.InteropServices.DllImport("user32")]
   private static extern int RegisterHotKey(IntPtr hwnd, int id,
      int fsModifiers, int vk);
   [System.Runtime.InteropServices.DllImport("user32")]
   private static extern int UnregisterHotKey(IntPtr hwnd, int id);
   [System.Runtime.InteropServices.DllImport("kernel32")]
   private static extern short GlobalAddAtom(string lpString);
   [System.Runtime.InteropServices.DllImport("kernel32")]
   private static extern short GlobalDeleteAtom(short nAtom);

   private const int MOD_CONTROL = 2;

   private short[] stKeyID = new short[5];

   private Keys[] kKeyValue = new[] { Keys.D1, Keys.D2, Keys.D3,
      Keys.D4, Keys.D5 };

VB.NET

   Private Declare Function RegisterHotKey Lib "user32" _
      (ByVal hwnd As IntPtr, ByVal id As Integer,
      ByVal fsModifiers As Integer, ByVal vk As Integer) As Integer
   Private Declare Function UnregisterHotKey Lib "user32" _
      (ByVal hwnd As IntPtr, ByVal id As Integer) As Integer
   Private Declare Function GlobalAddAtom Lib "kernel32" _
      Alias "GlobalAddAtomA" (ByVal lpString As String) As Short
   Private Declare Function GlobalDeleteAtom Lib "kernel32" _
      (ByVal nAtom As Short) As Short

   Private Const MOD_CONTROL As Integer = 2

   Dim stKeyID(4) As Short
   Dim kKeyValue() As Keys = {Keys.D1, Keys.D2, Keys.D3, Keys.D4, _
      Keys.D5}

The preceding Windows APIs create and dispose a global hot key. It must be registered through a Windows API; else, all other applications will not know about it and override the supposed hot key. When the application closes, UnregisterHotKey releases the designated hot keys from use, thus freeing them up and allowing the other applications to make use of them. The keys being assigned hot keys are Ctrl + 1, Ctrl + 2, Ctrl + 3, Ctrl + 4, and Ctrl + 5. Add the Hot key functions below:

C#

   public void RegisterGlobalHotKey(int intID)
   {

      try
      {

         Keys kKey = kKeyValue[intID];
         int intModifier = MOD_CONTROL;

         string strName = Thread.CurrentThread.ManagedThreadId
            .ToString("X8") + this.Name + intID;
         stKeyID[intID] = GlobalAddAtom(strName);

         if (stKeyID[intID] == 0)
            throw new Exception("Error");

         if (RegisterHotKey(this.Handle, stKeyID[intID],
               intModifier, System.Convert.ToInt32(kKey)) == 0)
            throw new Exception("Error");

      }
      catch (Exception ex)
      {
         UnregisterGlobalHotKey();
      }

   }

   public void UnregisterGlobalHotKey()
   {

      for (int i = 0; i <= stKeyID.Length - 1; i++)
      {

         if (stKeyID[i] != 0)
         {
            UnregisterHotKey(this.Handle, stKeyID[i]);
            GlobalDeleteAtom(stKeyID[i]);
            stKeyID[i] = 0;
         }

      }

   }

VB.NET

   Sub RegisterGlobalHotKey(ByVal intID As Integer)

      Try

         Dim kKey As Keys = kKeyValue(intID)
         Dim intModifier As Integer = MOD_CONTROL

         Dim strName As String = Thread.CurrentThread.Managed _
            ThreadId.ToString("X8") & Me.Name & intID
         stKeyID(intID) = GlobalAddAtom(strName)

         If stKeyID(intID) = 0 Then

            Throw New Exception("Error")

         End If

         If RegisterHotKey(Me.Handle, stKeyID(intID), _
               intModifier, CInt(kKey)) = 0 Then

            Throw New Exception("Error")

         End If

      Catch ex As Exception

         UnregisterGlobalHotKey()

      End Try

   End Sub

   Sub UnregisterGlobalHotKey()

      For i As Integer = 0 To stKeyID.Length - 1

         If stKeyID(i) <> 0 Then

            UnregisterHotKey(Me.Handle, stKeyID(i))
            GlobalDeleteAtom(stKeyID(i))
            stKeyID(i) = 0
         End If
      Next
   End Sub

Add the Overriden WndProc procedure to accept the hotkeys, identify which ones have been pressed, and then determine what type of data needs to be copied or pasted.

C#

   protected override void WndProc(ref Message mes)
   {

      base.WndProc(ref mes);

      const int WM_HOTKEY = 0x312;

      if (mes.Msg == WM_HOTKEY)
      {

         for (int i = 0; i <= stKeyID.Length - 1; i++)
         {

            if (mes.WParam.Equals(stKeyID[i]))
               Swap(i);
         }

      }
   }
   private void Swap(int iChannel)
   {

      if (Clipboard.ContainsText())
      {
         objData[intChannel] = Clipboard.GetText();
         cdtType[intChannel] = ClipboardDataType.Text;
      }

      else if (Clipboard.ContainsImage())
      {
         objData[intChannel] = Clipboard.GetImage();
         cdtType[intChannel] = ClipboardDataType.Image;
      }

      else if (Clipboard.ContainsAudio())
      {
         objData[intChannel] = Clipboard.GetAudioStream();
         cdtType[intChannel] = ClipboardDataType.Audio;
      }

      else if (Clipboard.ContainsFileDropList())
      {
         objData[intChannel] = Clipboard.GetFileDropList();
         cdtType[intChannel] = ClipboardDataType.FileList;
      }

      else
      {
         objData[intChannel] = null;
         cdtType[intChannel] = ClipboardDataType.Empty;
      }

      intChannel = iChannel;

      switch (cdtType[intChannel])
      {

         case ClipboardDataType.Text:
            {
               Clipboard.SetText((string)objData[intChannel]);
               break;
            }

         case ClipboardDataType.Image:
            {
               Clipboard.SetImage((Image)objData[intChannel]);
               break;
            }

         case ClipboardDataType.FileList:
            {
               Clipboard.SetFileDropList((System.Collections
                  .Specialized.StringCollection)objData
                  [intChannel]);
               break;
            }

         case ClipboardDataType.Audio:
            {
               Clipboard.SetAudio((System.IO.Stream)objData
                  [intChannel]);
               break;
            }

         case ClipboardDataType.Empty:
            {
               Clipboard.Clear();
               break;
            }

      }
   }

   public void RegisterAllKeys()
   {

      for (int i = 0; i <= stKeyID.Length - 1; i++)

         RegisterGlobalHotKey(i);

   }

   private void Form1_Load(object sender, EventArgs e)
   {

      RegisterAllKeys();

   }

   private void Form1_FormClosed(object sender,
      FormClosedEventArgs e)
   {

      UnregisterGlobalHotKey();

   }

VB.NET

   Protected Overrides Sub WndProc(ByRef mes As Message)

      MyBase.WndProc(mes)

      Const WM_HOTKEY As Integer = &H312

      If mes.Msg = WM_HOTKEY Then

         For i As Integer = 0 To stKeyID.Length - 1

            If mes.WParam.ToInt32 = stKeyID(i) Then

               Swap(i)

            End If

         Next

      End If

   End Sub

   Private Sub Swap(ByVal iChannel As Integer)

      If Clipboard.ContainsText Then


         objData(intChannel) = Clipboard.GetText
         cdtType(intChannel) = ClipboardDataType.Text

      ElseIf Clipboard.ContainsImage Then

         objData(intChannel) = Clipboard.GetImage
         cdtType(intChannel) = ClipboardDataType.Image

      ElseIf Clipboard.ContainsAudio Then

         objData(intChannel) = Clipboard.GetAudioStream
         cdtType(intChannel) = ClipboardDataType.Audio

      ElseIf Clipboard.ContainsFileDropList Then

         objData(intChannel) = Clipboard.GetFileDropList
         cdtType(intChannel) = ClipboardDataType.FileList

      Else

         objData(intChannel) = Nothing
         cdtType(intChannel) = ClipboardDataType.Empty

      End If

      intChannel = iChannel

      Select Case cdtType(intChannel)

         Case ClipboardDataType.Text

            Clipboard.SetText(DirectCast(objData(intChannel), _
               String))

         Case ClipboardDataType.Image

            Clipboard.SetImage(DirectCast(objData(intChannel), _
               Image))

         Case ClipboardDataType.FileList

            Clipboard.SetFileDropList(DirectCast(objData _
               (intChannel), Specialized.StringCollection))

         Case ClipboardDataType.Audio

            Clipboard.SetAudio(DirectCast(objData(intChannel), _
               IO.Stream))

         Case ClipboardDataType.Empty

            Clipboard.Clear()

      End Select

   End Sub

   Public Sub RegisterAllKeys()

      For i As Integer = 0 To stKeyID.Length - 1

         RegisterGlobalHotKey(i)

      Next

   End Sub

   Private Sub Form1_FormClosed(sender As Object, e As _
         FormClosedEventArgs) Handles MyBase.FormClosed

      UnregisterGlobalHotKey()

   End Sub

   Private Sub Form1_Load(sender As Object, e As EventArgs) _
         Handles MyBase.Load

      RegisterAllKeys()

   End Sub

When  run (obviously, there is no user interface), you can press Ctrl + 1, then Ctrl + C, to add an item to the first channel of the clipboard. Press Ctrl + 2, then Ctrl + C to add to the second channel—and so on. To paste, follow the same process except for substituting Ctrl + C with Ctrl + V.

Conclusion

It is always fun to reinvent the wheel. In this article, you have learned not only about the clipboard, but more importantly, you have learned about global hot keys which are system wide and affect all programs. Happy copying, and happy coding!

Hannes DuPreez
Hannes DuPreez
Ockert J. du Preez is a passionate coder and always willing to learn. He has written hundreds of developer articles over the years detailing his programming quests and adventures. He has written the following books: Visual Studio 2019 In-Depth (BpB Publications) JavaScript for Gurus (BpB Publications) He was the Technical Editor for Professional C++, 5th Edition (Wiley) He was a Microsoft Most Valuable Professional for .NET (2008–2017).

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read