![]() |
Platforms, Frameworks & Libraries »
.NET Framework »
General
Intermediate
Share the Clipboard Using .NET RemotingBy Douglas EarlUse .NET remoting to send the contents of your clipboard to another computer. |
C#, VC7.NET 1.0, Win2K, WinXP, Dev
|
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||

Do you have two computers at your desk? If so, have you ever wanted to Ctrl-C on one computer and Ctrl-V on the other? It's especially tempting to do this when the two computers share a common keyboard and monitor via a KVM switch. This application makes it easy to share the clipboard between two computers on a local network. The application is written in C# using .NET remoting. This article will explain the remoting implementation and the clipboard processing, as well as explaining the use of a delegate to get around a problem with threading.
ClipShare acts as both a client and server, so it must be running on both computers that will be sharing the clipboard. On the computer that has the clipboard data that you want to share (the client), specify the name of the computer you want to send to and then select the "Send Clipboard" button. The clipboard will be packaged up, sent over the LAN, and automatically placed on the clipboard of the computer at the receiving end (the server). For convenience, the application has an icon in the system tray so you can also select "send clipboard" from the icon's context menu. If you want to temporarily disallow other computers from sending their clipboard to your computer, deselect the "allow incoming" checkbox or context menu item.
This application uses .NET remoting in order to transfer the clipboard
contents. First we need to do some initialization to make the application
a remoting server. This code snippet (from the form's constructor)
enables remoting and then registers a class, RemoteClipboard (discussed
later), with the RemotingConfiguration so that it can be activated
remotely:
TcpChannel channel = new TcpChannel(4820);
hannelServices.RegisterChannel(channel);
RemotingConfiguration.RegisterWellKnownServiceType(typeof(RemoteClipboard),
"ClipShare",
WellKnownObjectMode.Singleton);
This application also serves as a remoting client. To send the clipboard
data, the client activates an instance of the RemoteClipboard
class on the server using the same port number and service name that was
registered above. We will use this instance to invoke the SendClipboard
method, so we save it in a member variable for use later:
private void InitRemoteObject()
{
try
{
string location = "tcp://" + computerName.Text + ":4820/ClipShare";
m_remoteClipboard = (RemoteClipboard) Activator.GetObject(
typeof(RemoteClipboard), location);
computerName.Modified = false;
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
The RemoteClipboard class is activated remotely and used to transfer the
clipboard contents from one computer to the other. It must inherit from
MarshalByRefObject so that it can be activated remotely. It
has one method, SendClipboard, that takes an ArrayList
containing the clipboard data passed in from the client, and places it on the
server's clipboard.
Unfortunately, the SendClipboard method isn't allowed to place
data on the clipboard. The method from the .NET framework used to set the
contents of the clipboard, Clipboard.SetDataObject(), can only be
run in a Single Threaded Apartment (STA), but since the RemoteClipboard
object is activated remotely, it's running in a Multi Threaded Apartment
(MTA). If you try to call Clipboard.SetDataObject() directly
from SendClipboard, an exception is thrown.
In order to get around this problem, we need to have the form place the data on
the clipboard instead of the RemoteClipboard object. Since
Form.Main() is declared with the [STAThread] attribute,
it can call SetDataObject without any problem. To get the
form to process the data in its own thread, we need to call the Invoke
method on the form, passing in a delegate to one of the form's
methods (AddToClip). The RemoteClipboard class
has a static method, SetOnClipReceive, called once during
initialization, to give it the form and delegate to use when calling Invoke.
Here's a picture to help describe what's going on conceptually, followed
by the RemoteClipboard class in its entirety:

public delegate void ClipEventHandler(ArrayList clipData);
public class RemoteClipboard : MarshalByRefObject
{
private static ClipEventHandler m_OnClipReceive;
private static Form m_receiverForm;
// set the object and method that gets a callback when a clipboard is sent.
public static void SetOnClipReceive(Form receiver, ClipEventHandler theCallback)
{
m_receiverForm = receiver;
m_OnClipReceive = theCallback;
}
// this is the method that will be invoked remotely by the other computer.
public void SendClipboard(ArrayList clipData)
{
object[] clipObjects = {clipData};
m_receiverForm.Invoke(m_OnClipReceive, clipObjects);
}
}
The SetOnClipReceive function is called from the form's constructor:
RemoteClipboard.SetOnClipReceive(this, new ClipEventHandler(this.AddToClip));
Special note to Forms developers: I included the RemoteClipboard
class in the same source file as the form since it is small and convenient to
do so. I originally had the RemoteClipboard class defined above
the form in the source file. As soon as I did this (although I never made
that connection), the form could no longer access its resources, such as the
icon for the system tray. It took me quite awhile to figure out that
the Form must be defined first in the source file. According
to Microsoft, this is by design (see
Q318603).
To access the clipboard data, use Clipboard.GetDataObject(), which
returns an instance of the IDataObject interface.
Unfortunately, this object is not serializable, so it can't be passed as a
parameter to the SendClipboard method. Instead, we iterate through
each format on the clipboard, and if it is serlializable, put the clipboard
data item in an array list, paired with its format string. The ArrayList
is then passed to the RemoteClipboard object using the SendClipboard
method.
private void SendClipboardToRemote()
{
try
{
...
ArrayList dataObjects = new ArrayList();
IDataObject clipboardData = Clipboard.GetDataObject();
string[] formats = clipboardData.GetFormats();
for (int i=0; i < formats.Length; i++)
{
object clipboardItem = clipboardData.GetData(formats[i]);
if (clipboardItem != null && clipboardItem.GetType().IsSerializable)
{
Console.WriteLine("sending {0}", formats[i]);
dataObjects.Add(formats[i]);
dataObjects.Add(clipboardItem);
}
else
Console.WriteLine("ignoring {0}", formats[i]);
}
if (dataObjects.Count > 0)
{
Cursor.Current = Cursors.WaitCursor;
m_remoteClipboard.SendClipboard(dataObjects);
Cursor.Current = Cursors.Default;
}
else
MessageBox.Show(this, "Nothing on clipboard, or contents not supported",
"ClipShare");
}
catch (Exception ex)
{
string message = String.Format("Unable to send data: {0}", ex.Message);
MessageBox.Show(this, message, "ClipShare");
}
}
On the receive end, we iterate through the array list and add each clipboard
data item to a new DataObject, which then gets placed on the
clipboard via Clipboard.SetDataObject. The AddToClip
method shown here is the delegate that gets invoked by the SendClipboard
method (see above):
public void AddToClip(ArrayList theData)
{
if (!allowIncomingCB.Checked)
throw new Exception("Remote computer has disabled clipboard sharing");
try
{
DataObject dataObj = new DataObject();
for (int i = 0; i < theData.Count; i++)
{
string format = (string)theData[i++];
dataObj.SetData(format, theData[i]);
}
Clipboard.SetDataObject(dataObj, true);
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
You may have noticed that the call to SendClipboard by the client
is in a try/catch block, so the exception that is thrown in AddToClip
on the server: ("Remote computer has disabled clipboard sharing") will get
propagated back to the client and shown in a message box. Also
interesting to note, but not surprising, is that if AddToClip
is invoked asynchronously with BeginInvoke instead of Invoke,
the exception will not get propagated and you will get an unhandled exception
error.
Unfortunately, not all formats retrieved from the IDataObject are
serializable. For example, the windows metafile format is not, so
transferring to or from drawing programs is limited to bitmap formats.
Also, if you copy a file or directory, the location placed on the clipboard
uses drive letters instead of UNC so they can't be pasted on the remote
computer. I imagine that it wouldn't be hard to add pre-processing to
change the path to use UNC before the clipboard is sent. Finally, I made
a half-hearted attempt at getting a left mouse click on the system tray icon to
show the context menu in addition to a right mouse click (this is
commented out in the source file if you download it). This doesn't seem
to be directly supported by the NotifyIcon class, so is not easily
done.
When I started writing this application, I thought it would serve as a quick introduction to remoting, but as is often the case, especially when coming up to speed on a new programming environment, it turned out to take longer than I expected. But running into problems isn't all bad since solving them is part of the learning process. (Next time I'll know not to define a class above my form!)
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 14 Oct 2002 Editor: Andrew Peace |
Copyright 2002 by Douglas Earl Everything else Copyright © CodeProject, 1999-2009 Web17 | Advertise on the Code Project |