Architect or Cobbler?
Good code starts with good design

Printing

Saturday, November 25, 2006

I decided to bite the bullet and just use the PrintPreviewDialog - so my app now has printing support.  You may recall from earlier that I used a Command attribute for the Print menus rather than a Click attribute.  There are several benefits to using the Command attribute:

  • In many of the WPF controls these commands are already coded fro you so you don't have to code it.
  • There is a standard mechanism for seeing if the command can be executed so the menu is automatically enabled.
  • The menus will automatically get Keyboard shortcuts added.
  • You can override the standard behaviour in a CommandBindings section

So as the Print and PrintPreview don't seem to be wired up, I need to wire up the commands using CommandBindings like this:

<Window.CommandBindings>
    <CommandBinding Command="Print" Executed="OnPrint" 
CanExecute="CanPrint"/> <CommandBinding Command="PrintPreview" Executed="OnPrintPreview"
CanExecute="CanPrint"/> </Window.CommandBindings>

Now I need to provide the Code for the CanPrint method, this method is a simple switch that indicates whether or not the menu can be enabled.  In my case, I've decided that if there are no active Scribble windows then you won't be able to print, and the code is self explanatory.

        void CanPrint(object sender, CanExecuteRoutedEventArgs  args)
        {
            args.CanExecute =  (ActiveCanvas != null);
        }

The Print functionality involves creating a PrintDialog, and allowing this to manage the printing, while the PrintPreview is a bit trickier.  I need to use a RenderTargetBitmap and a BmpBitmapEncoder to convert the Vector information into a raster bitmap that can be used in the Print Page handler for the PrintPreviewDialog.  There is one obvious disadvantage to this approach - the print quality will be lower going through the PrintPreview dialog compared to just printing directly.  The printing itself is just a matter of getting the Strokes collection from the active InkCanvas, and then invoking the Draw method for each Stroke in that collection.

        void OnPrint(object sender, RoutedEventArgs args)
        {
            PrintDialog pd = new PrintDialog();
            if (pd.ShowDialog().GetValueOrDefault())
            {
                DrawingVisual dv = RenderSinglePage();
                pd.PrintVisual(dv, "Scribble Canvas Print Job");

            }
        }

        private DrawingVisual RenderSinglePage()
        {
            DrawingVisual dv = new DrawingVisual();
            DrawingContext dc = dv.RenderOpen();
            ScribbleCanvas sc = ActiveCanvas;
            StrokeCollection strokes = sc.Strokes;
            foreach (Stroke stroke in strokes)
            {
                stroke.Draw(dc);
            }
            dc.Close();
            return dv;
        }

        void OnPrintPreview(object sender, RoutedEventArgs args)
        {
            PrintDocument pd = new PrintDocument();
            pd.PrintPage += new System.Drawing.Printing.PrintPageEventHandler(
this.ScribblePrintPage); System.Windows.Forms.PrintPreviewDialog prevDlg =
new System.Windows.Forms.PrintPreviewDialog(); prevDlg.Document = pd; prevDlg.ShowDialog(); } private void ScribblePrintPage(object sender, PrintPageEventArgs ev) { ScribbleCanvas sc = ActiveCanvas; RenderTargetBitmap rtb = new RenderTargetBitmap((int)sc.ActualWidth,
(int)sc.ActualHeight,
96d, 96d,
PixelFormats.Default); rtb.Render(sc); MemoryStream ms = new MemoryStream(); BmpBitmapEncoder encoder = new BmpBitmapEncoder(); encoder.Frames.Add(BitmapFrame.Create(rtb)); encoder.Save(ms); ev.Graphics.DrawImage(new System.Drawing.Bitmap(ms),
0f,0f,
(float)sc.ActualWidth,(float)sc.ActualHeight); ms.Close(); ev.HasMorePages = false; }

As usual you can download the source code here


# posted by James @ 4:23 PM   0 comments Comments: Post a Comment

<< Main blog page
This page is powered by Blogger. Isn't yours?