Serialization, the Clipboard, and Drag and Drop

Serialization, the Clipboard, and Drag and Drop

All the cool functionality that we ve been performing on ink won t do the user much good if he or she can t save or load data or have it interoperate with other applications via the clipboard or drag and drop. In this final section on the Ink Data Management API, we ll take a look at the platform s support for serialization, clipboard, and drag and drop functionality.

Serialization

Ink data is always saved at the Ink object level, and it s accomplished with the Ink class s Save method:

byte[] Ink.Save() byte[] Ink.Save(PersistenceFormat p) byte[] Ink.Save(PersistenceFormat p, CompressionMode c)

This method produces a byte array in one of several formats. By default, the Save method will output the data in the Ink Serialized Format (ISF), which is the Tablet PC Platform s native binary format for ink data.

If the optional PersistenceFormat parameter is given, alternative formats can be generated. Table 6-2 lists all the formats that version 1 of the Tablet PC Platform supports.

Table 6-2. The Values of the PersistenceFormat Enumeration

PersistenceFormat Value

Description

Base64Gif

The Graphics Interchange Format, which is then Base64-encoded, typically used for viewing ink via .mht files

Base64InkSerializedFormat

The ISF, which is then Base64-encoded, typically used for storing ink in XML

Gif

The Graphics Interchange Format, typically used for viewing ink in Web browsers

InkSerializedFormat

The Ink Serialized Format, typically used to save and load ink data

Notice that there are really only two formats. You can choose to encode to Base64 or not. Encoding to the Base64 format doesn t even really have to be provided by the Tablet PC Platform because the Microsoft .NET Framework s System.Convert.ToBase64String method will do the same work. I believe the Base64 formats are a result of the managed API being built on top of the COM automation layer where .NET Framework functionality is not available.

The Save method s optional CompressionMode value is used when saving in InkSerializedFormat. Table 6-3 lists its values.

Table 6-3. The Values of the CompressionMode Enumeration

CompressionMode Value

Description

Default

The default compression mode its value is Maximum

Maximum

The most compression possible (resulting in the smallest data size); also the most performance intensive

NoCompression

No compression occurs; the fastest performance

As already mentioned, when no CompressionMode is provided to the Save method, the CompressionMode.Default value is used.

Once ink data has been saved, we ll need to reconstitute it into an Ink object. This is done with the Ink class s Load method:

void Ink.Load(byte[] inkData)

There is only one version of the method because the format and compression are automatically determined. The method has no return value, so if the byte array has some bad data in it or a problem otherwise occurs, the Load method will throw an exception. When no exception is thrown, you can assume the function has succeeded.

You cannot call Load on an Ink object that has ever contained an ink stroke an exception will be thrown if you do! The reason is simple: when ink data is saved and loaded, stroke IDs are preserved. Recall from Chapter 5 that stroke IDs are unique only on a per Ink object basis. An Ink object that has contained one or more ink strokes can have one or more Strokes collections referencing those ink strokes, even if all ink strokes have since been deleted. If data is then loaded into that Ink object and one or more stroke IDs match the references in a Strokes collection, the Strokes collection will dereference the wrong Stroke objects, possibly causing great harm. The solution to this problem is to always create a new Ink object before loading data into it.

To help us keep track of when to save ink data, the Ink class s Dirty property is set whenever any of its data is modified. It is very useful to track a document s dirty flag, a bit that is used to determine whether the document needs to be saved. This Ink class s Dirty property is a read-write property that is typically set to clean (or false) on file load and is queried on file save; if it s dirty (or true), the document is saved and the property is then set to clean.

Persisting Strokes Collections

When ink data from an Ink object is saved, no existing Strokes collections will be saved by default. To persist a Strokes collection, it must be added to the Ink object s CustomStrokes property. This property is a collection of Strokes collections whose contents will be saved. It is useful for persisting relations of ink strokes, for example, layers, groups, and recognition data.

To keep track of which Strokes collection contains which data, Strokes collections are added to the CustomStrokes property along with a name of type string. To retrieve a Strokes collection, the name can be used as an argument to the CustomStrokes property s operator[]. Here s a hypothetical example showing the use of CustomStrokes:

// Make sure the strksTopLayer strokes collection is saved ink.CustomStrokes.Add("TopLayer", strksTopLayer); // Save the ink data byte[] arrData = Ink.Save(); ... // Load the ink data into a new ink object ink = new Ink(); ink.Load(arrData); // Assign the top layer strokes collection to strksTopLayer strksTopLayer = ink.CustomStrokes["TopLayer"]; // Remove it since it won't be used anymore ink.CustomStrokes.Remove("TopLayer");

Extended Property Data

The CustomStrokes property is handy for storing Strokes collections, but what about other types of data? We could write out our own custom data separately and read it back in, but that can take a lot of effort and is prone to errors. Fortunately, the Tablet PC Platform allows some arbitrary data to be persisted along with the ink data by using extended properties. An extended property is simply a name-value pair, where the name is a Guid and the value is any built-in type such as int, string, and byte[ ].

The Ink, Stroke, and DrawingAttributes classes all possess an ExtendedProperties property that is a collection of ExtendedProperty objects. The ExtendedProperty class has two properties: Id, which is a Guid value, and Data, which is an object of some kind. The ExtendedProperties property is found in the Ink, Stroke, and DrawingAttributes classes to satisfy saving data at any of those scopes. Use Ink s ExtendedProperties for document-level data, Stroke s for stroke-level data, and DrawingAttributes for custom rendering settings.

Sample Application InkPersist

The InkPersist sample application shows just how easy it is to load and save ink data to a file. It supports loading, merging, and saving ink to an ISF file, as well as exporting ink data to a GIF file.

InkPersist.cs

//////////////////////////////////////////////////////////////////// // // InkPersist.cs // // (c) 2002 Microsoft Press // by Rob Jarrett // // This program demonstrates how to use serialization API functions // the TPG platform provides. // //////////////////////////////////////////////////////////////////// using System; using System.Drawing; using System.IO; using System.Windows.Forms; using Microsoft.Ink; using MSPress.BuildingTabletApps; public class frmMain : Form { private InkControl2 inkCtl; private MenuItem miNew; private MenuItem miOpen; private MenuItem miMerge; private MenuItem miSave; private MenuItem miExport; private MenuItem miExit; // Entry point of the program [STAThread] static void Main() { Application.Run(new frmMain()); } // Main form setup public frmMain() { SuspendLayout(); // Create the main menu Menu = new MainMenu(); MenuItem miFile = new MenuItem("&File"); Menu.MenuItems.Add(miFile); miNew = new MenuItem("&New", new EventHandler(miNew_Click)); Menu.MenuItems[0].MenuItems.Add(miNew); Menu.MenuItems[0].MenuItems.Add(new MenuItem("-")); miOpen = new MenuItem("&Open...", new EventHandler(miOpen_Click)); Menu.MenuItems[0].MenuItems.Add(miOpen); miMerge = new MenuItem("&Merge...", new EventHandler(miMerge_Click)); Menu.MenuItems[0].MenuItems.Add(miMerge); miSave = new MenuItem("Save &As...", new EventHandler(miSave_Click)); Menu.MenuItems[0].MenuItems.Add(miSave); Menu.MenuItems[0].MenuItems.Add(new MenuItem("-")); miExport = new MenuItem("&Export...", new EventHandler(miExport_Click)); Menu.MenuItems[0].MenuItems.Add(miExport); Menu.MenuItems[0].MenuItems.Add(new MenuItem("-")); miExit = new MenuItem("E&xit"); miExit.Click += new EventHandler(miExit_Click); Menu.MenuItems[0].MenuItems.Add(miExit); // Create and place all of our controls inkCtl = new InkControl2(); inkCtl.Location = new Point(8, 8); inkCtl.Size = new Size(352, 216); // Configure the form itself ClientSize = new Size(368, 232); Controls.AddRange(new Control[] { inkCtl }); FormBorderStyle = FormBorderStyle.FixedDialog; MaximizeBox = false; Text = "InkPersist"; ResumeLayout(false); // We're now set to go, so turn on tablet input inkCtl.InkOverlay.Enabled = true; } // Handle the "New" menu item being clicked private void miNew_Click(object sender, EventArgs e) { inkCtl.InkOverlay.Enabled = false; // Create a new ink object Ink newInk = new Ink(); inkCtl.InkOverlay.Ink = newInk; inkCtl.InkInputPanel.Invalidate(); // Show the new ink inkCtl.InkOverlay.Enabled = true; } // Handle the "Open..." menu item being clicked private void miOpen_Click(object sender, EventArgs e) { inkCtl.InkOverlay.Enabled = false; // Choose the file to open OpenFileDialog dlg = new OpenFileDialog(); dlg.DefaultExt = "isf"; dlg.Filter =  "ISF binary files (*.isf) *.isf All files (*.*) *.*"; if (dlg.ShowDialog(this) == DialogResult.OK) { // Load the file into a new ink object Stream s = dlg.OpenFile(); byte [] buf = new byte[s.Length]; s.Read(buf, 0, buf.Length); Ink newInk = new Ink(); newInk.Load(buf); inkCtl.InkOverlay.Ink = newInk; // Show the new ink inkCtl.InkInputPanel.Invalidate(); } inkCtl.InkOverlay.Enabled = true; } // Handle the "Merge..." menu item being clicked private void miMerge_Click(object sender, EventArgs e) { inkCtl.InkOverlay.Enabled = false; // Choose the file to merge OpenFileDialog dlg = new OpenFileDialog(); dlg.DefaultExt = "isf"; dlg.Filter =  "ISF binary files (*.isf) *.isf All files (*.*) *.*"; if (dlg.ShowDialog(this) == DialogResult.OK) { // Load the file into a new ink object Stream s = dlg.OpenFile(); byte [] buf = new byte[s.Length]; s.Read(buf, 0, buf.Length); Ink newInk = new Ink(); newInk.Load(buf); // Merge the ink into the current ink object inkCtl.InkOverlay.Ink.AddStrokesAtRectangle( newInk.Strokes, newInk.GetBoundingBox()); // Show the new ink inkCtl.InkInputPanel.Invalidate(); } inkCtl.InkOverlay.Enabled = true; } // Handle the "Save As..." menu item being clicked private void miSave_Click(object sender, EventArgs e) { inkCtl.InkOverlay.Enabled = false; // Choose the file to save SaveFileDialog dlg = new SaveFileDialog(); dlg.DefaultExt = "isf"; dlg.Filter =  "ISF binary files (*.isf) *.isf All files (*.*) *.*"; if (dlg.ShowDialog(this) == DialogResult.OK) { // Save the ink object to the file Stream s = dlg.OpenFile(); byte [] buf = inkCtl.InkOverlay.Ink.Save( PersistenceFormat.InkSerializedFormat); s.Write(buf, 0, buf.Length); } inkCtl.InkOverlay.Enabled = true; } // Handle the "Export..." menu item being clicked private void miExport_Click(object sender, EventArgs e) { inkCtl.InkOverlay.Enabled = false; // Choose the file to export SaveFileDialog dlg = new SaveFileDialog(); dlg.DefaultExt = "gif"; dlg.Filter =  "Graphics Interchange Format (GIF) files " +  "(*.gif) *.gif All files (*.*) *.*"; if (dlg.ShowDialog(this) == DialogResult.OK) { // Export the ink object as a GIF image Stream s = dlg.OpenFile(); byte [] buf = inkCtl.InkOverlay.Ink.Save(PersistenceFormat.Gif); s.Write(buf, 0, buf.Length); } inkCtl.InkOverlay.Enabled = true; } // Handle the "Exit" menu item being clicked private void miExit_Click(object sender, EventArgs e) { Application.Exit(); } }

Loading in ink is quite straightforward the file is read into a byte array that is then passed to the Load method of a new Ink object. The Ink object is then assigned to the InkControl s InkOverlay:

// Load the file into a new ink object Stream s = dlg.OpenFile(); byte [] buf = new byte[s.Length]; s.Read(buf, 0, buf.Length); Ink newInk = new Ink(); newInk.Load(buf); inkCtl.InkOverlay.Ink = newInk;

Merging a file requires almost the same set of operations as loading one except that instead of replacing the InkOverlay s Ink object with the newly loaded ink, we use AddStrokesAtRectangle to copy the new ink into the existing Ink object:

// Load the file into a new ink object Stream s = dlg.OpenFile(); byte [] buf = new byte[s.Length]; s.Read(buf, 0, buf.Length); Ink newInk = new Ink(); newInk.Load(buf); // Merge the ink into the current ink object inkCtl.InkOverlay.Ink.AddStrokesAtRectangle( newInk.Strokes, newInk.GetBoundingBox());

Saving the ink data out to a file is even more straightforward than loading it in. The resulting byte array from the Save method is written to the target file:

// Save the ink object to the file Stream s = dlg.OpenFile(); byte [] buf = inkCtl.InkOverlay.Ink.Save( PersistenceFormat.InkSerializedFormat); s.Write(buf, 0, buf.Length);

Exporting ink to a .gif file is identical to saving except that it requires the Save method s PersistenceFormat value to be changed to PersistenceFormat.Gif.

NOTE
This sample doesn t use any exception handling, an omission I do not recommend, particularly when it comes to something as error prone as persistence code. Because the Tablet PC Platform doesn t introduce any issues that otherwise wouldn t occur in other persistence code, I chose to omit the exception handling for clarity. Please use exception handling in your own application.

Using the Clipboard

Copying and pasting ink is almost a necessity if your ink application supports selection of some kind. Now that we know how to persist ink data, we can certainly write our own clipboard support we put the byte array returned from the Save method into a data object and place the data object on the clipboard. However, this has a couple of drawbacks: the first is that we have to write all the code to perform the clipboard operations, and the second is that interoperating with other applications will be difficult if not impossible because the clipboard formats will have to match exactly.

Again, the Tablet PC Platform comes to the rescue by providing all the clipboard functionality we need. It has methods to cut, copy, paste, and query the clipboard for supported formats. Let s first take a look at how to cut and copy ink:

IDataObject Ink.ClipboardCopy(InkClipboardFormats formats, InkClipboardModes mo des) IDataObject Ink.ClipboardCopy(Strokes strokes, InkClipboardFormats formats, Ink ClipboardModes modes) IDataObject Ink.ClipboardCopy(Rectangle rectangle, InkClipboardFormats formats,    InkClipboardModes modes)

The ClipboardCopy method can cut or copy ink data from an Ink object to the clipboard in many different formats. The InkClipboardFormats parameter indicates which formats should be created, and its values are found in Table 6-4.

Table 6-4. The Values of the InkClipboardFormats Enumeration

InkClipboardFormats Value

Description

Bitmap

Equivalent to CF_BITMAP clipboard type; cannot be used to paste ink data

CopyMask

Equivalent to Bitmap, EnhancedMetafile, InkSerializedFormat, Metafile, SketchInk, and TextInk

Default

Equivalent to CopyMask

EnhancedMetafile

Equivalent to CF_ENHMETAFILE clipboard type; cannot be used to paste ink data

InkSerializedFormat

The Tablet PC Platform s binary format for ink persistence; can be used to paste ink data

Metafile

Equivalent to CF_METAFILE clipboard type; cannot be used to paste ink data

None

No clipboard formats

PasteMask

Equivalent to InkSerializedFormat, SketchInk, and TextInk

SketchInk

Microsoft Office sketch ink ink-drawing format; can be used to paste ink data

TextInk

Microsoft Office text ink ink-word format; can be used to paste ink data

The InkClipboardFormats.Bitmap, InkClipboardModes.EnhancedMetafile, InkClipboardModes.InkSerializedFormat, InkClipboardModes.Metafile, InkClipboardModes.SketchInk, and InkClipboardModes.TextInk can be bitwise-ORed to produce various combinations of data formats on the clipboard. Common formats are found in the InkClipboardModes.CopyMask and InkClipboardModes.PasteMask values.

The InkClipboardModes parameter specifies how the ink data and IDataObject should be used. Table 6-5 lists its values.

Table 6-5. The Values of the InkClipboardModes Enumeration

InkClipboardModes Value

Description

Copy

Puts the desired formats on the clipboard; leaves the ink data alone

Cut

Puts the desired formats on the clipboard; deletes the ink data from the Ink object

Default

Equivalent to Copy

DelayedCopy

Results in not executing the code to fill the IDataObject with data until it s first requested

ExtractOnly

Results in the Windows clipboard not being touched; instead just an IDataObject with the ink data is returned (for drag and drop)

The InkClipboardModes.Copy, InkClipboardModes.Cut, and InkClipboardModes.DelayedCopy values are exclusive to the InkClipboardModes.ExtractOnly value. That is, you can bitwise-OR the InkClipboardModes.ExtractOnly value with the others. The InkClipboardModes.ExtractOnly value is used when performing a drag and drop operation. In the next section, we ll learn how to perform drag and drop.

Pasting ink from the clipboard or a data object is performed by the Ink class s ClipboardPaste method:

Strokes Ink.ClipboardPaste() Strokes Ink.ClipboardPaste(Point point) Strokes Ink.ClipboardPaste(Point point, object dataObject)

The default version of ClipboardPaste will try to read the supported data formats off the clipboard and merge it into the Ink object the method is called on, at location (0,0). An alternative position for pasting is specified by providing the Point argument. Performing a paste operation from a data object rather than from the Windows clipboard is accomplished by providing the dataObject parameter. The ClipboardPaste method returns a Strokes collection referencing the newly pasted ink data.

To determine whether a paste operation can occur, or if a data object supports any pasteable formats, the CanPaste method is used:

bool Ink.CanPaste() bool Ink.CanPaste(object dataObject)

The CanPaste method returns true if pasting can occur and false if it can t. This method is useful for updating a Paste menu item or providing drop effect feedback during a drag and drop operation.

Sample Application InkClippy

This sample application shows how cut/copy/paste functionality can be implemented. It is named InkClippy to avoid confusion with the Tablet PC Platform SDK s sample named InkClipboard.

InkClippy.cs

//////////////////////////////////////////////////////////////////// // // InkClippy.cs // // (c) 2002 Microsoft Press // by Rob Jarrett // // This program demonstrates how to use clipboard API functions the // TPG platform provides. // //////////////////////////////////////////////////////////////////// using System; using System.Drawing; using System.Windows.Forms; using Microsoft.Ink; using MSPress.BuildingTabletApps; public class frmMain : Form { private InkControl2 inkCtl; private MenuItem miCut; private MenuItem miCopy; private MenuItem miPaste; // Entry point of the program [STAThread] static void Main() { Application.Run(new frmMain()); } // Main form setup public frmMain() { SuspendLayout(); // Create the main menu Menu = new MainMenu(); MenuItem miFile = new MenuItem("&File"); Menu.MenuItems.Add(miFile); MenuItem miExit = new MenuItem("E&xit"); miExit.Click += new EventHandler(miExit_Click); Menu.MenuItems[0].MenuItems.Add(miExit); MenuItem miEdit = new MenuItem("&Edit"); miEdit.Popup += new EventHandler(miEdit_Popup); Menu.MenuItems.Add(miEdit); miCut = new MenuItem("Cu&t"); miCut.Click += new EventHandler(miCut_Click); Menu.MenuItems[1].MenuItems.Add(miCut); miCopy = new MenuItem("&Copy"); miCopy.Click += new EventHandler(miCopy_Click); Menu.MenuItems[1].MenuItems.Add(miCopy); miPaste = new MenuItem("&Paste"); miPaste.Click += new EventHandler(miPaste_Click); Menu.MenuItems[1].MenuItems.Add(miPaste); // Create and place all of our controls inkCtl = new InkControl2(); inkCtl.Location = new Point(8, 8); inkCtl.Size = new Size(352, 216); // Configure the form itself ClientSize = new Size(368, 232); Controls.AddRange(new Control[] { inkCtl }); FormBorderStyle = FormBorderStyle.FixedDialog; MaximizeBox = false; Text = "InkClippy"; ResumeLayout(false); // We're now set to go, so turn on tablet input inkCtl.InkOverlay.Enabled = true; } // Handle the "Exit" menu item being clicked private void miExit_Click(object sender, EventArgs e) { Application.Exit(); } // Handle the "Edit" submenu popping up private void miEdit_Popup(object sender, EventArgs e) { bool fSelectMode = (inkCtl.InkOverlay.EditingMode == InkOverlayEditingMode.Select); // Enable or disable the various menu items miCut.Enabled = fSelectMode && (inkCtl.InkOverlay.Selection.Count > 0); miCopy.Enabled = miCut.Enabled; miPaste.Enabled = fSelectMode && inkCtl.InkOverlay.Ink.CanPaste(); } // Handle the "Cut" menu item being clicked private void miCut_Click(object sender, EventArgs e) { if (inkCtl.InkOverlay.Selection.Count > 0) { // Cut the selected ink to the clipboard inkCtl.InkOverlay.Ink.ClipboardCopy( inkCtl.InkOverlay.Selection, InkClipboardFormats.CopyMask, InkClipboardModes.Cut); // Clear the selection inkCtl.InkOverlay.Selection = inkCtl.InkOverlay.Ink.CreateStrokes(); // Update the display inkCtl.InkInputPanel.Invalidate(); } } // Handle the "Copy" menu item being clicked private void miCopy_Click(object sender, EventArgs e) { if (inkCtl.InkOverlay.Selection.Count > 0) { // Copy the selected ink to the clipboard inkCtl.InkOverlay.Ink.ClipboardCopy( inkCtl.InkOverlay.Selection, InkClipboardFormats.CopyMask, InkClipboardModes.Copy); } } // Handle the "Paste" menu item being clicked private void miPaste_Click(object sender, EventArgs e) { if (inkCtl.InkOverlay.Ink.CanPaste()) { Point ptOffset = new Point(0,0); // Do we need to replace any current selection? if (inkCtl.InkOverlay.Selection.Count > 0) { // Remember the offset of the selection so we can paste // the new data there ptOffset = inkCtl.InkOverlay.Selection.GetBoundingBox().Location; // Delete the selection from the InkOverlay's Ink object inkCtl.InkOverlay.Ink.DeleteStrokes( inkCtl.InkOverlay.Selection); // Clear the selection inkCtl.InkOverlay.Selection = inkCtl.InkOverlay.Ink.CreateStrokes(); } // Paste the data from the clipboard and select it inkCtl.InkOverlay.Selection = inkCtl.InkOverlay.Ink.ClipboardPaste(ptOffset); // Update the display inkCtl.InkInputPanel.Invalidate(); } } }

The application allows cutting and copying if there is a current selection, and it allows pasting by deferring the decision to the value returned from CanPaste. The menu items enabled state is updated in the miEdit_Popup event handler.

The cut operation uses the ClipboardCopy method to put the formats specified by InkClipboardFormats.CopyMask on the clipboard and results in the ink data being removed from the Ink object because of InkClipboardModes.Cut being used:

// Cut the selected ink to the clipboard inkCtl.InkOverlay.Ink.ClipboardCopy( inkCtl.InkOverlay.Selection, InkClipboardFormats.CopyMask, InkClipboardModes.Cut); // Clear the selection inkCtl.InkOverlay.Selection = inkCtl.InkOverlay.Ink.CreateStrokes();

Once the ink data is on the clipboard, the selection is cleared because the ink strokes referenced by the selection don t exist anymore.

The copy operation is almost the same as cut except that InkClipboardModes.Copy is used and there is no need to clear the selection:

// Copy the selected ink to the clipboard inkCtl.InkOverlay.Ink.ClipboardCopy( inkCtl.InkOverlay.Selection, InkClipboardFormats.CopyMask, InkClipboardModes.Copy);

Pasting is performed with the ClipboardPaste method, using a paste location computed to be the upper left of any current selection, or (0,0) otherwise:

// Paste the data from the clipboard and select it inkCtl.InkOverlay.Selection = inkCtl.InkOverlay.Ink.ClipboardPaste(ptOffset);

If there is any existing selection, it is overwritten by the newly pasted data. This functionality isn t required, and you may not want to implement it in order to enable repeated pasting operations of the same data. Once the data has been pasted, it is also selected by utilizing the returned Strokes collection from the ClipboardPaste method.

Implementing Drag and Drop

Drag and drop functionality is provided by the InkOverlay class in its select editing mode. However, it does not support dragging ink from one application to another the user is only able to move ink around the inking area it was created in. The .NET Framework and Tablet PC Platform provide some great support for OLE drag and drop, which is the Windows standard way of drag and drop occurring between applications.

Recall that the ClipboardCopy, CanPaste, and ClipboardPaste methods provide variants to work only with data objects and not with the clipboard. It is this functionality that enables us to perform OLE drag and drop effectively and easily in an inking application.

Sample Application InkDragDrop

Let s take a look at a simple implementation of OLE drag and drop.

InkDragDrop.cs

//////////////////////////////////////////////////////////////////// // // InkDragDrop.cs // // (c) 2002 Microsoft Press // by Rob Jarrett // // This program demonstrates how to do OLE drag-drop using an Ink // object as the source. // //////////////////////////////////////////////////////////////////// using System; using System.Drawing; using System.Windows.Forms; using Microsoft.Ink; using MSPress.BuildingTabletApps; public class frmMain : Form { // User interface private InkInputPanel pnlInput; private CheckBox cbAllowDrag; private InkCollector inkCollector; // Entry point of the program [STAThread] static void Main() { // Turn on OLE usage Application.OleRequired(); // Start up using frmMain Application.Run(new frmMain()); } // Main form setup public frmMain() { SuspendLayout(); // Create and place all of our controls pnlInput = new InkInputPanel(); pnlInput.BackColor = Color.White; pnlInput.BorderStyle = BorderStyle.Fixed3D; pnlInput.Location = new Point(8, 8); pnlInput.Size = new Size(352, 192); cbAllowDrag = new CheckBox(); cbAllowDrag.Location = new Point(8, 204); cbAllowDrag.Size = new Size(96, 20); cbAllowDrag.Text = "Allow drag"; cbAllowDrag.CheckedChanged += new System.EventHandler(cbAllowDrag_CheckedChanged); // Configure the form itself ClientSize = new Size(368, 236); Controls.AddRange(new Control[] { pnlInput, cbAllowDrag}); FormBorderStyle = FormBorderStyle.FixedDialog; MaximizeBox = false; Text = "InkDragDrop"; ResumeLayout(false); // Hook up to mouse events so we can start drag-n-drop operation pnlInput.MouseDown += new MouseEventHandler(pnlInput_MouseDown); // Turn on drag-n-drop for the input area and hook up to events pnlInput.AllowDrop = true; pnlInput.DragEnter += new DragEventHandler(pnlInput_DragEnter); pnlInput.DragDrop += new DragEventHandler(pnlInput_DragDrop); // Create a new InkCollector, using pnlInput for the collection // area inkCollector = new InkCollector(pnlInput.Handle); // Update the UI with the settings cbAllowDrag.Checked = false; // We're now set to go, so turn on tablet input inkCollector.Enabled = true; } // Handle the click of the allow drag checkbox private void cbAllowDrag_CheckedChanged(object sender, EventArgs e) { // Enable/disable tablet input inkCollector.Enabled = !cbAllowDrag.Checked; } // Handle mouse down in the input panel private void pnlInput_MouseDown(object sender, MouseEventArgs e) { if (!inkCollector.Enabled) { // Start up drag-n-drop using a DataObject obtained from // the ClipboardCopy method pnlInput.DoDragDrop( inkCollector.Ink.ClipboardCopy( InkClipboardFormats.Default, InkClipboardModes.ExtractOnly), DragDropEffects.Copy); } } // Handle the drag enter of some data private void pnlInput_DragEnter(object sender, DragEventArgs e) { // Make sure the ink object supports the format if (inkCollector.Ink.CanPaste(e.Data)) { // Additive only e.Effect = DragDropEffects.Copy; } else { // Format unsupported, reject the data e.Effect = DragDropEffects.None; } } // Handle the drag-drop event - data has been dropped! private void pnlInput_DragDrop(object sender, DragEventArgs e) { // Make sure the ink object supports the format if (inkCollector.Ink.CanPaste(e.Data)) { // Convert the drop-point into ink coordinates Point pt = pnlInput.PointToClient(new Point(e.X, e.Y)); Graphics g = pnlInput.CreateGraphics(); inkCollector.Renderer.PixelToInkSpace(g, ref pt); g.Dispose(); // Paste the data at the drop-point inkCollector.Ink.ClipboardPaste(pt, e.Data); e.Effect = DragDropEffects.Copy; // Update the ink area with the new ink pnlInput.Invalidate(); } else { // Format unsupported, reject the data e.Effect = DragDropEffects.None; } } }

The application provides a crude UI that enables the user to switch into drag mode, where any pen tap will start a drag and drop operation of all the ink in the inking area:

// Start up drag-n-drop using a DataObject obtained from // the ClipboardCopy method pnlInput.DoDragDrop( inkCollector.Ink.ClipboardCopy( InkClipboardFormats.Default, InkClipboardModes.ExtractOnly), DragDropEffects.Copy);

To facilitate dropping the ink in the same sample application, the AllowDrop property of pnlInput is set to true, and its DragEnter and DragDrop events have handlers installed to deal with data being dragged over the input area.

The DragEnter handler checks whether the data object of the drag operation supports any ink data formats by using the CanPaste method:

// Make sure the ink object supports the format if (inkCollector.Ink.CanPaste(e.Data)) { // Additive only e.Effect = DragDropEffects.Copy; } else { // Format unsupported, reject the data e.Effect = DragDropEffects.None; }

The DragDrop handler results in the data object being used in a call to ClipboardPaste:

// Paste the data at the drop-point inkCollector.Ink.ClipboardPaste(pt, e.Data);

Once the data has been dropped, the inking area is invalidated to reflect the new ink.



Building Tablet PC Applications
Building Tablet PC Applications (Pro-Developer)
ISBN: 0735617236
EAN: 2147483647
Year: 2001
Pages: 73

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net