InkPicture

InkPicture

InkPicture is the second control available for use in the Tablet PC Platform SDK. Its intended use focuses narrowly around inking on images. In fact, the InkPicture control offers little value over the InkOverlay class, as we ll soon see. Unlike InkEdit, InkPicture supports only managed and ActiveX flavors it does not have a Win32 interface.

The InkPicture control is essentially a combination of the InkOverlay class and the .NET Framework s PictureBox control. If you do a method-by-method, event-by-event, property-by-property comparison of InkPicture and InkOverlay, you will find that they are similar. In addition, because InkPicture derives from the PictureBox, it includes and exposes all the functionality of the PictureBox control.

NOTE
Chapter 4 covered the InkOverlay class extensively. InkPicture retains almost 100 percent of the InkOverlay interface, so your knowledge of InkOverlay should transfer nicely to InkPicture. The PictureBox control is comprehensively covered in MSDN and books such as Programming Microsoft Windows with C#, so we won t delve into it too deeply.

Now let s dive right into an InkPicture sample. The following program lets a user ink over any image file by first loading it into the InkPicture control and then capturing the user s ink. When the user saves, we combine the image and the ink together in one file. Subsequently, the user can reload a saved file to continue working on it. Figure 8-2 shows the application in action.

figure 8-2 inkpictureapp supports inking on loaded images and saves image and ink together in isf files.

Figure 8-2. InkPictureApp supports inking on loaded images and saves image and ink together in ISF files.

InkPictureApp.cs

//////////////////////////////////////////////////////////////////// // InkPictureApp.cs // (c) 2002 Microsoft Press, by Philip Su // This program uses InkPicture to collect ink over an image. It // supports saving and loading of ink along with the image. //////////////////////////////////////////////////////////////////// using System; using System.Drawing; using System.Windows.Forms; using System.Collections; using System.IO; using Microsoft.Ink; using System.Runtime.Serialization.Formatters.Binary; public class InkPictureApp : Form { // File filters for load / save dialogs private const string strFileFilter = "ISF files (*.isf) *.isf " +  "All files (*.*) *.*"; private const string strImageFilter = "Image files " +  "(*.bmp; *.gif; *.jpg; *.png) *.bmp;*.gif;*.jpg;*.png " +  "All files (*.*) *.*"; // Store images in the ink object using a custom property // with this guid private static Guid guidImage = new Guid("{B3BF218B-2923-4d8a-9675-D36FA38EF17D}"); private InkPicture m_inkpict; private Button m_btnLoadImage; private Button m_btnLoad; private Button m_btnSave; private Button m_btnColor; // Entry point of the program static void Main() { Application.Run(new InkPictureApp()); } // Main form setup public InkPictureApp() { CreateUI(); // Stretch images to fit m_inkpict.SizeMode = PictureBoxSizeMode.StretchImage; } // Load Image button handler -- loads an image private void OnLoadImage(object sender, EventArgs e) { OpenFileDialog opendlg = new OpenFileDialog(); // Show the Open File dialog opendlg.Filter = strImageFilter; if (opendlg.ShowDialog() == DialogResult.OK) { try { m_inkpict.Image = Image.FromFile(opendlg.FileName); } catch (Exception) { MessageBox.Show(this, "Unable to load the image!",  "Load error"); } } } // Load button handler -- uses the system Load dialog private void OnLoad(object sender, EventArgs e) { OpenFileDialog opendlg = new OpenFileDialog(); // Show the Open File dialog opendlg.Filter = strFileFilter; if (opendlg.ShowDialog() == DialogResult.OK) { Stream stream = opendlg.OpenFile(); if (stream != null && stream.Length > 0) { byte[] rgb = new byte[stream.Length]; int cbRead = 0; // Read in the ISF into rgb cbRead = stream.Read(rgb, 0, rgb.Length); if (cbRead == stream.Length) { Ink ink = new Ink(); ink.Load(rgb); // Load the ISF // Set the loaded ink into m_inkpict. To do // so, we must disable inking in m_inkpict // before setting its Ink property. m_inkpict.InkEnabled = false; m_inkpict.Ink = ink; m_inkpict.InkEnabled = true; // Load an image, if it exists, into the // control. m_inkpict.Image = LoadImageFromInk(ink); // Update our UI m_inkpict.Invalidate(); } stream.Close(); } } } private Image LoadImageFromInk(Ink ink) { if (ink.ExtendedProperties.Contains(guidImage)) { byte[] rgbImage = (byte[])ink.ExtendedProperties[guidImage].Data; MemoryStream ms = new MemoryStream(rgbImage); BinaryFormatter bf = new BinaryFormatter(); // Convert the memory stream of bytes into an image return (Image)bf.Deserialize(ms); } else { return null; } } // Save button handler -- uses the system Save dialog private void OnSave(object sender, EventArgs e) { SaveFileDialog savedlg = new SaveFileDialog(); // Initialize and show the Save dialog savedlg.Filter = strFileFilter; if (savedlg.ShowDialog() == DialogResult.OK) { Stream stream = savedlg.OpenFile(); // Prepare to write the ISF. if (stream != null) { byte[] rgbISF; // Save the image as an extended property if (m_inkpict.Image != null) { SaveImageInInk(m_inkpict.Image, m_inkpict.Ink); } // Now save it all rgbISF = m_inkpict.Ink.Save(); stream.Write(rgbISF, 0, rgbISF.Length); stream.Close(); } } } private void SaveImageInInk(Image image, Ink ink) { MemoryStream ms = new MemoryStream(); BinaryFormatter bf = new BinaryFormatter(); byte[] rgbImage; // Convert the image to a memory stream of bytes bf.Serialize(ms, image); rgbImage = ms.GetBuffer(); // Add the image's data as an extended property ink.ExtendedProperties.Add(guidImage, rgbImage); } // Color button handler -- uses system color dialog private void OnColor(object sender, EventArgs e) { ColorDialog colordlg = new ColorDialog(); if (colordlg.ShowDialog() == DialogResult.OK) { DrawingAttributes drawattrs; drawattrs = m_inkpict.DefaultDrawingAttributes; drawattrs.Color = colordlg.Color; } } private const int cSpacing = 8; // Space between controls private void CreateUI() { int x = cSpacing; int y = cSpacing; SuspendLayout(); // Create and place all of our controls m_inkpict = new InkPicture(); m_inkpict.BorderStyle = BorderStyle.Fixed3D; m_inkpict.Location = new Point(x, y); m_inkpict.Size = new Size(500, 400); m_inkpict.BackColor = Color.White; x += m_inkpict.Bounds.Width + cSpacing; y = CreateButton(out m_btnLoadImage, x, y, 100, 24,  "Load &Image", new EventHandler(OnLoadImage)); y = CreateButton(out m_btnLoad, x, y, 100, 24, "&Load File", new EventHandler(OnLoad)); y = CreateButton(out m_btnSave, x, y, 100, 24, "&Save File", new EventHandler(OnSave)); y = CreateButton(out m_btnColor, x, y, 100, 24, "&Pen Color", new EventHandler(OnColor)); // Configure the form itself x += m_btnColor.Bounds.Width + cSpacing; y = m_inkpict.Bounds.Height + (2 * cSpacing); AutoScaleBaseSize = new Size(5, 13); ClientSize = new Size(x, y); Controls.AddRange(new Control[] { m_inkpict, m_btnLoadImage, m_btnLoad, m_btnSave, m_btnColor }); FormBorderStyle = FormBorderStyle.FixedDialog; MaximizeBox = false; Text = "InkPicture Application"; ResumeLayout(false); } // Creates a button and suggests the next y value to use private int CreateButton(out Button btn, int x, int y, int cWidth, int cHeight, string strName, EventHandler eh) { btn = new Button(); btn.Location = new Point(x, y); btn.Size = new Size(cWidth, cHeight); btn.Text = strName; btn.Click += eh; return y + btn.Bounds.Height + cSpacing; // Suggest next y } }

CreateUI (called from the constructor) creates an InkPicture control just as you would create any other control. It sets a border around the InkPicture control and also makes its background white:

 m_inkpict = new InkPicture(); m_inkpict.BorderStyle = BorderStyle.Fixed3D; m_inkpict.Location = new Point(x, y); m_inkpict.Size = new Size(500, 400); m_inkpict.BackColor = Color.White;

InkPicture inherits a SizeMode setting from PictureBox, which determines how a loaded image is sized relative to the control. We decided that it would be nice to have images stretched to fit the control.

 m_inkpict.SizeMode = PictureBoxSizeMode.StretchImage;

Loading the image is also straightforward. After using the standard OpenFileDialog to choose an image, we load it using Image s FromFile. Then we assign the resulting Image to the Image property on InkPicture:

 m_inkpict.Image = Image.FromFile(opendlg.FileName);

If we stopped right here, we would already have an application that is capable of inking on top of loaded images. But to increase the fun, we added two more features: ink color and load/save capability. For color, we rely on the standard color dialog to let the user choose a color, and then set the chosen color into InkPicture s DefaultDrawingAtttributes:

 drawattrs = m_inkpict.DefaultDrawingAttributes; drawattrs.Color = colordlg.Color;

Now for the interesting part loading and saving the image along with the ink. InkPicture will not save them together automatically, so you ll need a strategy of your own if you d like the image and ink saved together. The strategy in our sample application is simple: we save the image as an extended property in the Ink object, which is then streamed out with the ink into Ink Serialized Format (ISF). Let s take a look at SaveImageInInk first:

 MemoryStream ms = new MemoryStream(); BinaryFormatter bf = new BinaryFormatter(); byte[] rgbImage; // Convert the image to a memory stream of bytes bf.Serialize(ms, image); rgbImage = ms.GetBuffer(); // Add the image's data as an extended property ink.ExtendedProperties.Add(guidImage, rgbImage);

Because Image supports ISerializable, BinaryFormatter does all the magic of converting it into a stream of bytes. We add the array of bytes to the ExtendedProperties collection, associating it with a Guid we chose for all such embedded images. This effectively assures that the Image will remain with the ink when we save and load, since all extended properties are automatically saved and loaded from ISF. Using the ExtendedProperties collection is just one of many ways in which ink can be tied together in a file format with an image. We chose this as our strategy for its relative ease and clarity.

You might be wondering why we need to go through all the trouble of using BinaryFormatter to save the ink. After all, shouldn t the following work?

 ink.ExtendedProperties.Add(guidImage, image); // Throws!

Unfortunately, this code will throw an exception. The ExtendedProperties collection does not accept just any object that implements ISerializable. Instead, it is quite limited in being able to store only built-in types (such as int, float, double, bool, byte[], or string). So if you need to store an extended property that is not a built-in type, serialize it into a byte array prior to calling Add.

In LoadImageFromInk, we search for an extended property with the same Guid as when we saved. Using BinaryFormatter, we convert the stream of bytes back into the original image:

 if (ink.ExtendedProperties.Contains(guidImage)) { byte[] rgbImage = (byte[])ink.ExtendedProperties[guidImage].Data; MemoryStream ms = new MemoryStream(rgbImage); BinaryFormatter bf = new BinaryFormatter(); // Convert the memory stream of bytes into an image return (Image)bf.Deserialize(ms); }

Up to this point, nothing we ve done with InkPicture should be new. All the functionality that we ve been using so far in InkPicture is exactly the same as what s available in the related InkOverlay and PictureBox classes.

However, InkPicture has an InkEnabled property that is not available in either related class. InkPicture s InkEnabled property is analogous to InkOverlay s Enabled property. It was merely renamed to distinguish it from the PictureBox control s Enabled property (inherited from System.Windows.Forms.Control). In InkPicture, changing the Enabled property will enable or disable the control as a whole. However, changing the InkEnabled property turns ink capture on and off, just as it does in InkOverlay.

It s worth mentioning again that the Tablet PC Platform SDK requires that you turn off ink capture before changing the Ink property. This is true for InkOverlay, and it remains true for InkPicture. After loading an image from the ISF, we carefully set the image into InkPicture:

 m_inkpict.InkEnabled = false; m_inkpict.Ink = ink; m_inkpict.InkEnabled = true;

It s important to set InkEnabled to false prior to assigning a new Ink object into InkPicture. Given the tremendous similarity between InkOverlay and InkPicture, it is all too easy to do the following by habit:

 m_inkpict.Enabled = false; // Wrong! m_inkpict.Ink = ink; m_inkpict.Enabled = true; // Wrong!

This code compiles, but it will throw a mysterious exception when run. Remembering this subtle difference between InkPicture and InkOverlay will save you precious debugging time!



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