A clipboard is a systemwide place for users to store data temporarily. The clipboard enables your user to move data within a single application or to exchange data among applications. Typically, a user selects data in the application using the mouse or keyboard, then initiates a cut or copy operation on the selected data. The clipboard can hold an entire object or part of that object, and it can hold any kind of object. For example, the clipboard can hold a single line of text or an entire database, a single line segment or an entire graphic.
When the user selects the paste operation, the data is transferred to the application from the clipboard.
Note: | Only a single item of data can be stored in the clipboard at a time. Therefore, do not use the clipboard to store data unless a user requests it because you can overlay the user's data stored there. This is important: the user must always control access to the clipboard. |
While you can only store a single item of data in the clipboard, you can store this item in multiple formats. This allows an application to choose the format it supports that gives it the most information about the data. For example, a graphics application might copy a picture into the clipboard as both a metafile and a bitmap. This allows applications that support both metafiles and bitmaps to retrieve the picture as a metafile if it needs to modify the picture or as a bitmap if it only needs to display the picture.
IClipboard predefines several system clipboard formats. In addition, any application can create and register additional private formats.
Before you can write any data to, or read any data from, the clipboard, you must first open it. Only a single application at a time can open the clipboard. If an application tries to open the clipboard but another application already has it open, it waits until the clipboard is available. The default behavior of the clipboard classes minimizes the time the clipboard is open.
If you use the default behavior of IClipboard, the clipboard functions that require an open clipboard will open it when needed and close it when finished. You turn off the default behavior of IClipboard when you explicitly open the clipboard by calling IClipboard::open. If you open the clipboard in this manner, functions in IClipboard will not close the clipboard when complete. If you explicitly open the clipboard, you must close the clipboard by calling IClipboard::close. You can turn off the default behavior of IClipboard to place different formats of your data on the clipboard without opening and closing it to write each format.
All clipboard operations must be associated with a window. You provide thiswindow on the IClipboard constructor. If necessary, IClipboard makes this window the owner of the clipboard. The clipboard owner is the window responsible for the data put on the clipboard. It is also the window that the operating system sends messages to for events relating to the clipboard. The IClipboard object establishes this window as the system clipboard owner when you call IClipboard::empty. If you call IClipboard::owner before calling empty, your window will not be returned because it is not yet the system clipboard owner.
The clipboard classes support an advanced concept called delayed rendering. Delayed rendering allows you to wait until another application requests the data before you put the data on the clipboard. You activate delayed rendering by supplying 0 for the data when you call the clipboard functions to place data on the clipboard.
You process clipboard events by creating and attaching an IClipboardHandler object to your clipboard owner window. In particular, if you use delayed rendering, you must attach an IClipboardHandler object to your clipboard's window (the owner window). The window dispatcher calls this handler when a request is made to the clipboard for data that has not been placed there yet.
Because the clipboard should only be kept open for a short time, create IClipboard objects as temporary objects with a short lifetime. This helps ensure that the clipboard is only open for the time necessary.
The IClipboard destructor always closes the clipboard if it is still open.
You provide the clipboard owner on the IClipboard constructor and allow the functions needing an open clipboard to open the clipboard and close it when finished.
The following example uses an IClipboard object to copy text from an MLE into the clipboard and then to paste the data from the clipboard back into the MLE:
bool CommandHandler::command ( ICommandEvent& event) { switch(event.commandId()) { case MI_COPY : { IClipboard clipboard(event.window()->handle()); clipboard.empty(); clipboard.setText(edit.selectedText()); return true; } case MI_PASTE : { IClipboard clipboard(event.window()->handle()); if (clipboard.hasText()) edit.add(clipboard.text()); return true; } } return false; }
Note: | An application can put only one item of one format into the clipboard. You can only put multiple items into the clipboard if each has a different format. Adding multiple items with the same format results in replacing the data. |
Use the classes described below to create and manage a clipboard for your application:
Refer to IClipboard in the Open Class Library Reference for more information about these classes.
To clear the contents of the clipboard, use the IClipboard::empty member function. This empties the contents of the clipboard and establishes the owner provided on open as the real clipboard owner. This function opens the clipboard if it is not already open and closes it after use unless you have explicitly opened the clipboard by calling open.
Use the IClipboard::isOpen function to query the clipboard status. It returns true if the clipboard is open.
You can use the following IClipboard class member functions to move data to and from the clipboard:
This function always leaves the clipboard open. The caller must copy the data, if necessary, before closing the clipboard. Access to the data is lost after the clipboard is closed.
You can request delayed rendering of data by using the setData member function with the appropriate format and a 0 data pointer. Delayed rendering requires that you create an IClipboard handler to process requests to render the data when needed.
While all three environments support delayed rendering by allowing an application to pass a zero pointer for the data, Motif differs in that it passes back a data identifier that needs to be used when the data is later placed on the clipboard. Further, actually putting the data on the clipboard requires a different api (XmClipboardCopyByName) then putting the data on the clipboard normally (XmClipboardCopy). OS/2 and Windows use the same APIs in both scenarios. This API is handled in Open Class Library with a call to IClipboard::setData.
Open Class now has the member IClipboardHandler::setDelayedData. On Motif, implement this member by calling IClipboardImp::setDelayedData. The Motif version of this implementation function, IMotifClipboardImp::setDelayedData, calls XmClipboardCopyByName. The OS/2 and Windows versions of setDelayedData calls setData.
Code that needs to be portable to Motif must change the override of IClipboardHandler::renderFormat from a call to IClipboard::setData to IClipboardHandler::setDelayedData.
When you copy data to the clipboard in response to a delayed rendering message on OS/2 and Windows, the data is actually placed on the clipboard. Repeated attempts to access the data, return the data on the clipboard. No further action is necessary from the owner of the data. On Motif, every time delayed-rendered data is requested, it is acquired from the owner's callback routine. It acts like the data never ends up on the clipboard. This is a problem in scenarios where an object is cut from an application because the object is deleted at the time of the cut and is therefore unavailable at the time the application is asked to provide the data in a delayed-rendering scenario. This is a Motif restriction and your code will have to be different for Cut than it is in Copy.
IClipboardHandler processes the clipboard event by creating an IEventobject and routing it to the appropriate virtual function. The virtual function allows you to supply your own specialized processing of the event. The return values from the virtual function specify whether the paint event is passed on to another handler object to be processed.
The dispatchHandlerEvent member function evaluates the event to determine if it is appropriate for this handler object to process. If it is, this function calls the virtual function used to process the event.
The following sample is taken from the clipbrd.hpp and clipbrd.cpp files found in the sample directory \samples\ioc\clipbrd. This sample demonstrates how you can add clipboard support to a control, such as a container, that does not have built-in clipboard support. It also demonstrates how to use delayed rendering to put the data on the clipboard only when a user requests it during a paste operation.
In the .hpp file we do the following:
#ifndef _CLIPBRD_ #define _CLIPBRD #include <icliphdr.hpp> #include <icmdhdr.hpp> #include <istring.hpp> #include <icnr.hpp> //************************************************************************** // Class: Department * // * // Purpose: Defines the data stored in the container for a Department. * // * //************************************************************************** class Department : public IContainerObject { public: Department ( const IString& name=IString(), const IString& address=IString()) : IContainerObject (name), strAddress(address) {} // Add functions to query and set the data. virtual IString name ( ) const, address ( ) const; virtual Department &setName ( const IString& name), &setAddress ( const IString& address); // Define the functions to render an object both as a // private format and as a normal text string, and to // re-construct the object from the private format. IString asString ( ) const, text ( ) const; Department &initializeFromString ( const IString& renderedString); // Define the separator character (a tilde) that separates // the fields of the object in its string format. static const IString separator, renderedFormat; // Define a function to return the offset of the Address field. static unsigned long offsetOfAddress ( ) { return offsetof(Department, strAddress); } private: IString strAddress; }; class ICnrObjectSet; //************************************************************************** // Class: ContainerCutPasteHandler * // * // Purpose: Adds Clipboard support to the container for a Department * // object. This includes: * // 1) A container menu handler to show a popup menu with * // cut, copy, and paste choices. * // 2) A command handler to process the cut, copy, and paste * // requests. * // 3) A clipboard handler to process requests from the clipboard * // to render data not yet on the clipboard. * //************************************************************************** class ContainerCutPasteHandler : public ICommandHandler, public ICnrMenuHandler, public IClipboardHandler { public: ContainerCutPasteHandler (IContainerControl& container); IContainerControl &container ( ) { return cnr; } protected: // Define the command handler callback. virtual bool command ( ICommandEvent& event); // Define the popup menu callback. bool makePopUpMenu(IMenuEvent& cnEvt); // Define the callbacks to render data on the // clipboard. virtual bool clipboardEmptied ( IEvent& event ), renderFormat ( IEvent& event, const IString& format), renderAllFormats ( IEvent& event); // Define a string object to use as a separator between fields // for the private format. static const IString separator; IString selectedData ( ); private: IContainerControl &cnr; ICnrObjectSet *objectList; }; #endif
To support copying our Department object to the clipboard, we have devised a design to render a Department object as a data string and to create and initialize a new Developer object from data stored in such a data string. This data string contains fields separated by a character that we know does not exist in the data of our Department object. We define this separator character and initialize it to a tilde character later in clipbrd.cpp.
The ContainerCutPasteHandler utilizes a very similar design to store a series of container objects on the clipboard. The ContainerCutPasteHandler must use a different separator to distinguish the parts of its data. In the .cpp, we define the caret (^) character as the separator between objects on the clipboard. When our handler receives a request to render the data to the clipboard in our private format, it creates a string with the following layout:
countn |
separator (^) | object1 |
separator (^) | objectn |
The number of objects in the string is stored as a text number in the firstseparator delimited field of the string. Also in the string, we put Department object1 through objectn in their own separator delimited format with the following layout:
Department name | separator (~) | Department address |
For example, if the name field of a Department object is "Accounting" and the address field is "Building 4000," then the format string using the tilde character (~) as the separator is:
"Accounting~Building 4000"
If in addition to the Accounting department object above, we stored a"Sales" Department object with an address of "Building 5000," they would collectively appear on the clipboard as the string:
"2^Accounting~Building 4000^Sales~Building 5000"
Rather than copying our objects to the clipboard during the cut or copy operation, we have added support for delayed rendering. The design entails maintaining a collection of the objects cut or copied to the clipboard. When a user requests the data on the clipboard, our ClipboardCutPasteHandler's renderFormat routine is called to put the data on the clipboard. It iterates the objects in the collection and writes their data to the clipboard in the string format previously described.
A user that copies data to the clipboard and later pastes it into an application usually expects that the data will be the same as it was when it was first copied. To ensure this, any time the data of one of the objects in our collection changes or the object is removed from the container, we must force the delayed rendering mechanism to put the objects on the clipboard first. This support does not exist in our current clipboard sample.
This demonstrates that while delayed rendering has the potential for improving the performance of your application, it also increases its complexity. We therefore recommend that you first determine that you need to improve performance before deciding to add delayed rendering support to your application.
In the .cpp file we do the following:
The line numbers correspond to the line numbers in the actual sample.
#include <iframe.hpp> #include <ipopmenu.hpp> #include <iclipbrd.hpp> #include <icliphdr.hpp> #include <icmdhdr.hpp> #include <icnr.hpp> #include <icnrolst.hpp> #include <istring.hpp> #include <istparse.hpp> #include <iexcept.hpp> #include <itrace.hpp> #include <ireslib.hpp> #include <icoordsy.hpp> #include "clipbrd.h" #include "clipbrd.hpp" // Define our static Separator objects: // The first separates fields in the Department object string; // the second separates multiple Department object strings. const IString Department::separator("~"); const IString ContainerCutPasteHandler::separator("^"); // Define the private format of our Department object. const IString Department::renderedFormat("Department_rendered"); //************************************************************************* // main - Application entry point * //************************************************************************* int main() { ICoordinateSystem::setApplicationOrientation( ICoordinateSystem::kOriginLowerLeft ); IResourceLibrary reslib; IFrameWindow frame(reslib.loadString(WND_MAIN), WND_MAIN); // Create the details view container with columns that // displays the Department name and address. IContainerControl container(0x8008, &frame, &frame); IContainerColumn name(IContainerColumn::isIconViewText); IContainerColumn address( Department::offsetOfAddress()); name.setHeadingText( STR_HEAD_1 ); address.setHeadingText( STR_HEAD_2 ); container.addColumn(&name); container.addColumn(&address); // Create the Department objects and add them to // the container. Department* department; department = new Department(reslib.loadString(STR_ITEM_1_1), reslib.loadString(STR_ITEM_1_2)); container.addObject(department); department = new Department(reslib.loadString(STR_ITEM_2_1), reslib.loadString(STR_ITEM_2_2)); container.addObject(department); // Create the cut and paste handler (the handlers are // added in the constructor). ContainerCutPasteHandler handler(container); // Enable extended selection, switch to details view, and // give the container the focus. container.setExtendedSelection(); container.showDetailsView(); container.setFocus(); // Add an icon to the frame, put the container in the // client, and show the frame window. frame.setIcon( frame.id() ); frame.setClient(&container); frame.show(); // Start processing events. IApplication::current().run(); return 0; } /*------------------------------------------------------------------------------ | ContainerCutPasteHandler::ContainerCutPasteHandler | | | | Construct the handlers, register our private clipboard format, and | | attach the handlers to the container. | ------------------------------------------------------------------------------*/ ContainerCutPasteHandler :: ContainerCutPasteHandler ( IContainerControl& container) : cnr(container), objectList(new ICnrObjectSet()) { // Enable the command, menu, and clipboard handlers. ICommandHandler::handleEventsFor(&container); ICnrMenuHandler::handleEventsFor(&container); IClipboardHandler::handleEventsFor(&container); // Register the Department object's private format. IClipboard::registerFormat( Department::renderedFormat); } /*------------------------------------------------------------------------------ | ContainerCutPasteHandler::command | | | | Handle the command events associated with the clipboard (Cut, Copy, | | and Paste). | ------------------------------------------------------------------------------*/ bool ContainerCutPasteHandler::command ( ICommandEvent& event) { switch(event.commandId()) { case MI_CUT : case MI_COPY : { // Create a clipboard object. IClipboard clipboard(event.controlWindow()->handle()); // Find the cursored object in the container. Department* cursoredObject = (Department*)(container().cursoredObject()); // If a Copy request, utilize delayed rendering to put the data of // of the private format on the clipboard. If a Cut request, put // the data directly on the clipboard since the object won't be // around later when the renderFormat code needs it. // We maintain an "objectList" collection to keep track of // the objects copied onto the clipboard so that renderFormat // knows which objects to render. // We also store text data for the objects so that applications // that don't support the private format can paste the data into a // text editor. // Clear the collection used to keep track of clipboard objects. objectList->removeAll(); // If the cursored object is selected, loop through all other // selected objects and store the objects in our set. if (container().isSelected(cursoredObject)) { unsigned long count = 0; IString objectsAsText("\0"); IContainerControl::ObjectCursor cursor(container(), IContainerObject::selected); for (cursor.setToFirst(); cursor.isValid(); cursor.setToNext()) { count++; Department* selectedObject = (Department*)(container().objectAt(cursor)); objectList->add(selectedObject); objectsAsText = objectsAsText + selectedObject->text(); } // Empty the clipboard to establish ownership clipboard.empty(); // Put the data on the clipboard. We put our private // format first since it has the most information. // We use 0 for the data on a Copy request because // we want to delay the rendering until // the paste operation. // If this is a Cut, put the data on the clipboard // instead of using delayed rendering because we delete // the object. if (event.commandId() == MI_CUT) { IString string = selectedData(); clipboard.setData(Department::renderedFormat, (const char*)string, string.length()+1); objectList->removeAll(); container().deleteSelectedObjects(); } else clipboard.setData(Department::renderedFormat, 0, 0); clipboard.setText(objectsAsText); } else { // If the object is not selected, repeat the above procedure // for the cursored object. objectList->add(cursoredObject); // Empty the clipboard to establish ownership clipboard.empty(); // Put the data on the clipboard (using // delayed rendering for a Copy. Delete the object // if a Cut request. IString objectAsText = cursoredObject->text(); if (event.commandId() == MI_CUT) { IString string = selectedData(); clipboard.setData(Department::renderedFormat, (const char*)string, string.length()+1); objectList->removeAll(); container().removeObject(cursoredObject); delete cursoredObject; } else clipboard.setData(Department::renderedFormat, 0,0); clipboard.setText(objectAsText); } return true; } case MI_PASTE : { IClipboard clipboard(event.controlWindow()->handle()); // If the clipboard has data of the private format, // get the data and build objects from it. // Note: To see the text format of the data, paste // from the clipboard using any text editor that // supports the clipboard. if (clipboard.hasData(Department::renderedFormat)) { IString strCount, strObject, objectsAsString; // Query the data on the clipboard. char* data = (char*)clipboard.data(Department::renderedFormat); // Parse the string into our data fields. data >> strCount >> separator >> objectsAsString; // Extract the number of objects stored in the string. unsigned long count = strCount.asUnsigned(); // Turn refresh off to eliminate multiple painting. container().setRefreshOff(); // Construct new objects from the data. for( int i=0; i> strObject >> separator >> objectsAsString; Department* department = new Department(); department->initializeFromString(strObject); container().addObject(department); } // Enable refresh and refresh the container. container().setRefreshOn(); container().refresh(); } return true; } } return false; } /*------------------------------------------------------------------------------ | ContainerCutPasteHandler::makePopUpMenu | | | | Create a pop-up menu with the clipboard actions. | ------------------------------------------------------------------------------*/ bool ContainerCutPasteHandler::makePopUpMenu(IMenuEvent& event) { IPopUpMenu* popUpMenu = new IPopUpMenu(CNR_POPUPMENU, event.controlWindow()); // Enable the allowable menu items in the pop-up menu. if (popupMenuObject()) { ((IContainerControl*)event.controlWindow())->setCursor(popupMenuObject()); popUpMenu->disableItem(MI_PASTE); } else { popUpMenu->disableItem(MI_CUT); popUpMenu->disableItem(MI_COPY); } // Show the menu. popUpMenu->setAutoDeleteObject(); popUpMenu->show(event.mousePosition()); return true; } /*------------------------------------------------------------------------------ | ContainerCutPasteHandler::clipboardEmptied | | | | This function is responsible for cleaning up data when the clipboard is | | emptied and has nothing to do for now. | ------------------------------------------------------------------------------*/ bool ContainerCutPasteHandler::clipboardEmptied ( IEvent& ) { return true; } /*------------------------------------------------------------------------------ | ContainerCutPasteHandler::renderFormat | | | | Put our private format data on the clipboard. | ------------------------------------------------------------------------------*/ bool ContainerCutPasteHandler::renderFormat( IEvent& event, const IString& format) { // Use the handler's collection to find the Department objects // whose data we need to put on the clipboard. IClipboard clipboard(event.controlWindow()->handle()); if (format == Department::renderedFormat) { IString objectsAsString = selectedData(); setDelayedData(event, Department::renderedFormat, (const char*)objectsAsString, objectsAsString.size()); return true; } return false; } /*------------------------------------------------------------------------------ | ContainerCutPasteHandler::selectedData | | | | Return an IString containing the data to put on the clipboard. | ------------------------------------------------------------------------------*/ IString ContainerCutPasteHandler::selectedData( ) { // Use the handler's collection to find the Department objects // whose data we need to put on the clipboard. // Cursor the objects and build the string. ICnrObjectSet::Cursor cursor(*objectList); IString objectsAsString("\0"); unsigned long count = 0; // Loop through our collection and query the object for // its data. for( cursor.setToFirst(); cursor.isValid(); cursor.setToNext()) { count++; Department* department = (Department*)objectList->elementAt(cursor); // Document that no support is present for rendering objects // after they are removed from the container. IASSERTSTATE(container().containsObject(department)); objectsAsString = objectsAsString + separator + department->asString(); } // Build the final string for the clipboard and return it. objectsAsString = IString(count) + objectsAsString; return objectsAsString; } /*------------------------------------------------------------------------------ | ContainerCutPasteHandler::renderAllFormats | | | | Pass this on to our function to render a single format since we only use | | one format with delayed rendering. | ------------------------------------------------------------------------------*/ bool ContainerCutPasteHandler::renderAllFormats( IEvent& event) { return renderFormat(event, Department::renderedFormat); } /*------------------------------------------------------------------------------ | Department::name | | | | Return the name of the Department object. | ------------------------------------------------------------------------------*/ IString Department::name( ) const { return iconText(); } /*------------------------------------------------------------------------------ | Department::address | | | | Return the address of the Department object. | ------------------------------------------------------------------------------*/ IString Department::address( ) const { return strAddress; } /*------------------------------------------------------------------------------ | Department::setName | | | | Set the name of the Department object. | ------------------------------------------------------------------------------*/ Department& Department::setName( const IString& name) { setIconText(name); return *this; } /*------------------------------------------------------------------------------ | Department::setAddress | | | | Set the address of the Department object. | ------------------------------------------------------------------------------*/ Department& Department::setAddress( const IString& address) { strAddress = address; return *this; } /*------------------------------------------------------------------------------ | Department::asString | | | | Render the Department object as a String that we can later use to | | reconstruct a Department object. | ------------------------------------------------------------------------------*/ IString Department::asString ( ) const { IString strObject = name() + separator + address(); return strObject; } /*------------------------------------------------------------------------------ | Department::text | | | | Render the Department object as a text string with Name formatted to | | 30 characters, and address formatted to 50 characters with both | | followed by a new line character. Use this format for storing a plain | | text format of our object on the clipboard. | ------------------------------------------------------------------------------*/ IString Department::text ( ) const { IString strObject(name().subString(1,30) + address().subString(1,50) + "\n"); return strObject; } /*------------------------------------------------------------------------------ | Department::initializeFromString | | | | Set the fields of the object by parsing the passed string. | ------------------------------------------------------------------------------*/ Department& Department::initializeFromString ( const IString& renderedString ) { IString strName, strAddress; renderedString >> strName >> separator >> strAddress; setName (strName); setAddress(strAddress); return *this; }
Using Default Direct Manipulation
IClipboard
IClipboard::Cursor
IClipboardHandler