|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Services
Chapters
Feature Zones
|
![]() IntroductionI had originally planned on calling this application ClipSpy but about midway through the project I thought I had better Google it and see if there was anything else associated with this name, lo and behold there was an excellent article right here on The Code Project written by Michael Dunn entitled ClipSpy. After reviewing what Michael had presented I decided to go ahead with my project since; his was written in 2001 and the In the following sections, we will explore the inner workings of the
As always, I hope you get as much out of reading this article as I did writing it! UpdateThere were some major problems that needed to be addressed to make the application more stable and get data that we weren't able to get in the first version. An addition to the main screen is the button in the upper right corner with the start image, this brings up the Injector. In the Data viewer, I have added a button to the upper left corner to play a wave file if encountered. Clipboard 101The Clipboard Chaining and the Registering/Unregistering for Activity NotificationsWindows provides a hook for anyone interested in intercepting data from the To register ourselves as a listener we will have to resort to some interop. [DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern
IntPtr SetClipboardViewer(IntPtr hWnd);
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern
bool ChangeClipboardChain(
IntPtr hWndRemove,
//handle to window to remove
IntPtr hWndNewNext
//handle to next window
);
The private IntPtr ClipboardViewerNext;
/// <summary>
/// Add this control to the Clipboard chain to receive notification events|
/// </summary>
private void RegisterClipboardViewer()
{
ClipboardViewerNext = SetClipboardViewer(this.Handle);
}
The /// <summary>
/// Remove this form from the Clipboard Viewer list
/// </summary>
private void UnregisterClipboardViewer()
{
ChangeClipboardChain(this.Handle, ClipboardViewerNext);
}
Now to the heart of the matter! /// <summary>
/// Process window messages
/// <remarks>
/// This code was not entirely written by me but has been modified from
/// compiled from examples
/// found while researching the subject!
/// </remarks>
/// </summary>
/// <param name="m"></param>
protected override void WndProc(ref Message m)
{
switch ((Msgs)m.Msg)
{
case Msgs.WM_DRAWCLIPBOARD: //0x308
//Retrieve the data object and Process it
IDataObject data = Clipboard.GetDataObject();
ProcessClip(data);
//
// Each window that receives the WM_DRAWCLIPBOARD message
// must call the SendMessage function to pass the message
// on to the next window in the clipboard viewer chain.
//
SendMessage(ClipboardViewerNext, m.Msg, m.WParam, m.LParam);
break;
//
// The WM_CHANGECBCHAIN message is sent to the first window
// in the clipboard viewer chain when a window is being
// removed from the chain.
//
case Msgs.WM_CHANGECBCHAIN: //0x30D
// When a clipboard viewer window receives the WM_CHANGECBCHAIN message,
// it should call the SendMessage function to pass the message to the
// next window in the chain, unless the next window is the window
// being removed. In this case, the clipboard viewer should save
// the handle specified by the lParam parameter
// as the next window in the chain.
//
// wParam is the Handle to the window being removed from
// the clipboard viewer chain
//Param is the Handle to the next window in the chain
//following the window being removed.
if (m.WParam == ClipboardViewerNext)
{
//
// If wParam is the next clipboard viewer then it
// is being removed so update pointer to the next
// window in the clipboard chain
//
ClipboardViewerNext = m.LParam;
}
else
{
SendMessage(ClipboardViewerNext, m.Msg, m.WParam, m.LParam);
}
break;
default:
//
// Just pass the message on.
//
base.WndProc(ref m);
break;
}
The code should explain itself. Basic APIThe
I won't go into a lot of detail about these as they are well documented in the help files and MSDN. If you don't care about working with anything exotic, these will work just fine but I needed to view anything that was thrown at me and I wanted to present the data at a low level so I used the methods associated with the Clipboard 201The
The
Note: As I continue my research, I will update this list if I find any more but for now these are the pertinent formats that I check for. As an example, if we copy an RTF selection the following formats are involved:
As you can see, the information is given in several formats to accommodate the application that will potentially consume the information. When rendering the data to its raw form, I found that by going through the list of formats returned by the
The code given below is what I use to extract the raw data from the Note: I've tried copying a lot of different types of data from several applications, etc. and these are the only ones I've found so far. If I stumble on any more formats, I will update. public void ProcessRawData(IDataObject data, bool IsUnknowClip)
{
string[] strs = new string[20];
dataFormats = data.GetFormats(true);
try
{
int index = 0;
MemoryStream ms = null;
foreach (string s in dataFormats)
{
switch (data.GetData(s, true).GetType().ToString())
{
case "System.String[]":
strs = (string[])data.GetData(s, true);
rawDataBuffers[index++] = strs[0];
break;
case "System.IO.MemoryStream":
ms = (MemoryStream)data.GetData(s, true);
rawByteBuffers[index++] = ms.ToArray();
break;
case "System.String":
rawDataBuffers[index++] = (string)data.GetData(s, true);
break;
}
}
}
//The catchall, there was an error processing the data
catch
{
if (IsUnknowClip)|
title = "ERROR - Processing data";
}
}
The best way to learn about this is to use ClipSpy+ and check out what you have learned. This brings us to actually using the dang thing, so let's do that now! Injecting DataInjecting the data is the opposite of retrieving it. If we know how to retrieve data, and hopefully having read this far you do, we can visualize how we will pass the data to the NOTE: Any data that you may pass has to be [Serializable]
public class CustomFormatStruct : Object
{
public bool textActive = true;
public bool rtfActive = true;
public bool audioActive = true;
public bool imageActive = true;
public string data = string.Empty;
public string name = string.Empty;
public CustomFormatStruct(bool ta, bool ra, bool aa, bool ia, string d, string n)
{
textActive = ta;
rtfActive = ra;
audioActive = aa;
imageActive = ia;
data = d;
name = n;
}
public override string ToString()
{
StringBuilder sb = new StringBuilder();
sb.Append("This is an example of the kinds of data that can be passed
using the Clipboard\n\n");
sb.Append("<CustomFormatStruct>\n");
sb.Append("\t Text Data Sent: " + textActive.ToString() + "\n");
sb.Append("\t Rtf data sent: " + rtfActive.ToString() + "\n");
sb.Append("\tAudio data sent: " + audioActive.ToString() + "\n");
sb.Append("\tImage data sent: " + imageActive.ToString() + "\n");
sb.Append("</CustomFormatStruct>\n\n");
sb.Append("<" + name + ">\n");
sb.Append("\t" + data + "\n");
sb.Append("</" + name + ">");
return sb.ToString();
}
If you look at the code below, you will see how the different data is passed in various data formats depending on the type of data it is, i.e. If we are passing text we use the /// <summary>
/// Add data to the Clipboard
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button1_Click(object sender, EventArgs e)
{
if (richTextBox1.Text == string.Empty)
richTextBox1.Text = "Tried to fool me didn't ya? Enter some text and try again!";
DataObject data = new DataObject();
//If we are going to send TExt set it here
if (optionTextActive)
data.SetData(DataFormats.Text, richTextBox1.Text);
//Struct for our custom data
CustomFormatStruct cfs = new CustomFormatStruct(
optionTextActive,
optionRtfActive,
optionAudioActive,
optionImageActive,
richTextBox1.Text,
textBox1.Text);
if (optionCustomActive)
{
data.SetData("ClipInjectorPlus", "This format was generated byt ClipInjector+");
data.SetData(textBox1.Text, cfs.ToString());
}
//Rtf data here
if (optionRtfActive)
data.SetData(DataFormats.Rtf, richTextBox1.Rtf);
//Image data here
if (optionImageActive)
data.SetData(DataFormats.Bitmap, image);
//Audio data here
if (optionAudioActive)
data.SetData(DataFormats.WaveAudio, audioData);
//Do the deed!
Clipboard.Clear();
Clipboard.SetDataObject(data);
}
Using ClipSpy+As you may or may not know, I've been learning by myself GDI+ and graphics in general so the UI will illustrate some of what I've been learning along the way. I could have done more but then I wouldn't have any surprises for the next article. So having patted myself on the back and taken a break to realign my arm, I'm now ready to show you how to use this marvellous utility. Figure 1 and 2 show ClipSpy+ in the Full/Expanded mode and the Mini or Collapsed layouts respectively. ![]() Figure 1. ClipSpy+ Layout - Full Mode
Figure 2. ClipSpy+ Mini Mode
By default the application starts up with itself registered and the sound is on. Using ClipInjector+Using the injector is fairly straight forward. The available formats are in the upper pane and determine if the data will be set and what data will be set for the various data formats available. Once you have set the options that you are going to use, you click on the arrow button the lower left of the format area and this will set the clip to the
Well that's about it for this time, happy coding! References
Revision History
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||