InkPadJunior

InkPadJunior

We ll bring this chapter to a close by presenting InkPadJunior, a simple inking application that you can use to generate files with which to run your own tests, as shown in Figure 9-4. It supports loading and saving ISF files, as well as using various colors of ink. As a special bonus, it also supports a simple undo/redo mechanism based on cloning ink objects whenever they change. The undo functionality is included as a starting point to explore the many possibilities of how you might implement undo in your own application.

figure 9-4 inkpadjunior supports loading, saving, capturing, and undoing ink.

Figure 9-4. InkPadJunior supports loading, saving, capturing, and undoing ink.

InkPadJunior.cs

//////////////////////////////////////////////////////////////////// // InkPadJunior.cs // // (c) 2002 Microsoft Press, by Philip Su // // This program collects, loads, and saves ink. It also calculates // the number of strokes and average points per stroke. Finally, it // supports undoing and redoing of strokes. //////////////////////////////////////////////////////////////////// using System; using System.Drawing; using System.Windows.Forms; using System.Collections; using System.IO; using Microsoft.Ink; public class InkPadJunior : Form { private Panel m_pnlPad; private Button m_btnLoad; private Button m_btnSave; private Button m_btnClear; private Button m_btnColor; private Button m_btnUndo; private Button m_btnRedo; private Label m_labelCount; // Stroke count private Label m_labelAvgPts; // Avg. points / stroke private InkCollector m_inkcoll; private Stack m_stackUndo; // Undo stack of ink private Stack m_stackRedo; // Redo stack const string strFileFilter = "ISF files (*.isf) *.isf " +  "All files (*.*) *.*"; // Entry point of the program static void Main() { Application.Run(new InkPadJunior()); } // Main form setup public InkPadJunior() { m_stackUndo = new Stack(); m_stackRedo = new Stack(); CreateUI(); // Set up m_inkcoll to collect from m_pnlPad and // also to send Stroke events to OnStroke m_inkcoll = new InkCollector(m_pnlPad.Handle); m_inkcoll.Enabled = true; m_inkcoll.Stroke += new InkCollectorStrokeEventHandler( OnStroke); UpdateUI(); } // 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_inkcoll. To do // so, we must disable m_inkcoll before // setting its Ink property. m_inkcoll.Enabled = false; m_inkcoll.Ink = ink; m_inkcoll.Enabled = true; // Our undo/redo stacks should be cleared m_stackUndo.Clear(); m_stackRedo.Clear(); // Update our UI m_pnlPad.Invalidate(); OnStroke(null, null); } stream.Close(); } } } // 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(); // Now write the ISF stream to the file if (stream != null) { byte[] rgb = m_inkcoll.Ink.Save(); stream.Write(rgb, 0, rgb.Length); stream.Close(); } } } // Clear button handler -- deletes all strokes private void OnClear(object sender, EventArgs e) { // Have to invalidate for the changes to be shown m_inkcoll.Ink.DeleteStrokes(); m_pnlPad.Invalidate(); m_stackUndo.Clear(); // Clear our stacks as well m_stackRedo.Clear(); UpdateUI(); } // 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_inkcoll.DefaultDrawingAttributes; drawattrs.Color = colordlg.Color; } } // Undo button handler -- Restores a previous ink object private void OnUndo(object sender, EventArgs e) { Ink inkCurrent = ReplaceInk((Ink)m_stackUndo.Pop()); // Now that we've instituted the previous ink object, // store the original "current" one in the redo stack m_stackRedo.Push(inkCurrent); UpdateUI(); } // Redo button handler private void OnRedo(object sender, EventArgs e) { Ink inkUndo = ReplaceInk((Ink)m_stackRedo.Pop()); // Now that we've redone the previous ink object, // store the replaced ink into the undo stack m_stackUndo.Push(inkUndo); UpdateUI(); } // Replaces m_inkcoll.Ink with inkNew, returning the // original ink private Ink ReplaceInk(Ink inkNew) { Ink inkOld = m_inkcoll.Ink; // Save the old ink to return m_inkcoll.Enabled = false; // Must disable to replace ink m_inkcoll.Ink = inkNew; m_inkcoll.Enabled = true; // Re-enable collection m_pnlPad.Invalidate(); // Have to see the new ink return inkOld; } // Handles the Stroke event from m_inkcoll when a new // stroke has just been added to m_inkcoll.Ink private void OnStroke(object sender, InkCollectorStrokeEventArgs e) { Ink inkUndo = m_inkcoll.Ink.Clone(); int cStrokes = m_inkcoll.Ink.Strokes.Count; // Arrange our undo stack and clear the redo. // We need to remove the most recently added stroke // to have to ink we should add to the undo stack. inkUndo.DeleteStroke(inkUndo.Strokes[cStrokes - 1]); m_stackUndo.Push(inkUndo); m_stackRedo.Clear(); // User action means no redo! UpdateUI(); } 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_pnlPad = new Panel(); m_pnlPad.BorderStyle = BorderStyle.Fixed3D; m_pnlPad.Location = new Point(x, y); m_pnlPad.Size = new Size(500, 400); m_pnlPad.BackColor = Color.White; x += m_pnlPad.Bounds.Width + cSpacing; y = CreateButton(out m_btnLoad, x, y, 100, 24, "&Load", new EventHandler(OnLoad)); y = CreateButton(out m_btnSave, x, y, 100, 24, "&Save", new EventHandler(OnSave)); y = CreateButton(out m_btnClear, x, y, 100, 24, "&Clear", new EventHandler(OnClear)); y = CreateButton(out m_btnColor, x, y, 100, 24, "&Pen Color", new EventHandler(OnColor)); CreateButton(out m_btnUndo, x, y, 46, 24, "&Undo", new EventHandler(OnUndo)); CreateButton(out m_btnRedo, x + m_btnUndo.Bounds.Width + cSpacing, y, 46, 24, "&Redo", new EventHandler(OnRedo)); x = m_btnUndo.Bounds.X; y += m_btnRedo.Bounds.Height + (2 * cSpacing); m_labelCount = new Label(); m_labelCount.Location = new Point(x, y); m_labelCount.Size = new Size(100, 24); y += m_labelCount.Bounds.Height + cSpacing; m_labelAvgPts = new Label(); m_labelAvgPts.Location = new Point(x, y); m_labelAvgPts.Size = new Size(100, 24); y += m_labelAvgPts.Bounds.Height + cSpacing; // Configure the form itself x += m_labelAvgPts.Bounds.Width + cSpacing; y = m_pnlPad.Bounds.Height + (2 * cSpacing); AutoScaleBaseSize = new Size(5, 13); ClientSize = new Size(x, y); Controls.AddRange(new Control[] { m_pnlPad, m_btnLoad, m_btnSave, m_btnClear, m_btnColor, m_btnUndo, m_btnRedo, m_labelCount, m_labelAvgPts }); FormBorderStyle = FormBorderStyle.FixedDialog; MaximizeBox = false; Text = "InkPad Jr."; 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 } // Update our stroke statistics as well as the Undo/Redo // button states according to m_inkcoll.Ink private void UpdateUI() { int cStrokes = m_inkcoll.Ink.Strokes.Count; int cTotalPoints = 0; int cAvgPtsPerStroke = 0; // Calculate the average by counting all the points // and dividing by the number of strokes if (cStrokes > 0) { foreach (Stroke s in m_inkcoll.Ink.Strokes) { cTotalPoints += s.PacketCount; } cAvgPtsPerStroke = cTotalPoints / cStrokes; } m_labelCount.Text = "Strokes: " + cStrokes; m_labelAvgPts.Text = "Points/stroke: " + cAvgPtsPerStroke; // These buttons are enabled/disabled depending on // whether their stacks are empty m_btnUndo.Enabled = m_stackUndo.Count > 0; m_btnRedo.Enabled = m_stackRedo.Count > 0; } }

InkPadJunior is an example of a basic ink-aware application. The most complicated thing it does is implement an undo stack and redo stack by cloning ink objects. The basic idea behind undo and redo stacks is that every change in the document adds an entry to one of the stacks. If the action was a normal user action, you add its entry to the undo stack. If, however, the action was an undo, you add its entry to the redo stack. When the user does an undo, you pull the top entry off the undo stack, undo the action, and place its entry on the redo stack. Similarly, when you redo, you pull the top entry off the redo stack, redo the action, and place its entry on the undo stack again. When all is said and done, the user has unlimited undo and redo functionality. The only caveat is that you should always clear the redo stack whenever a normal user action occurs; this prevents user errors such as inking followed by redo, which makes no sense.

Other than undo, InkPadJunior doesn t exhibit much fancy behavior. It loads and saves data in much the same way we always have, taking care to clear the undo and redo stacks when a new file is loaded. It also responds to new strokes by adding them to the undo stack, clearing the redo stack, and finally calculating some statistics during UpdateUI. The amount of functionality you can get out of the Tablet PC Platform SDK with such little work is amazing!



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