It’s been a while since I have posted, so I thought I would whip out a quick nugget while I work on a longer technical post. I have been hacking away at a side project and experimenting with Prism 2 and MVC/MVP/MVVM/et al design patterns and came upon the need to print some of the output from that was already on the screen. This seemed like a somewhat common occurrence for Composite style applications – think “print THAT widget”. The screenshot shows an application I have been working on in my spare time and the red area shows the portion of the screen I would like to print. I quick, and admittedly cursory, examination of the SDK provided lots of examples on generating custom output to the WPF XPS infrastructure, but it was all about dynamically generating the print content. I already had the content I wanted to print, why did I need to go through a lot of hoops and additional code just to print something I had already created?
What I was really looking for was a way to print WYSIWYG style. I wanted to take just the chart portion of the UI and send it to the printer. A web search got me close - “Printing WPF window(visual) to printer and fit on a page”. The problem was that Pankaj’s solution was printing the entire window, and not just a smaller part of the overall visual tree. Fortunately, the solution is pretty straightforward but I thought I would post it anyway. So standing on the shoulder of giants, let me show you what I did.
First, Horse.NET is built on a flavor of MVP, so I have a button on the screen (actually the View for the SummaryPresenter) that initiates the printing process. Here is the XAML for that button.
1: <Button Content="Print" Command="{Binding Path=PrintCommand}" CommandParameter="{Binding ElementName=ReportPanel}"></Button>
There are two important things to note here. First, I am using a WPF command to start the printing process. You don’t have to do it this way, but it lets me tie the presenter to the UI pretty cleanly. The second thing is the CommandParameter. It is passing in a reference to the the ReportPanel. ReportPanel is just a WPF Grid control that wraps the title TextBlock and a Listbox that contains the actual charts. The simplified XAML is below:
1: <Grid x:Name="ReportPanel" >
2: <Grid.RowDefinitions>
3: <RowDefinition Height="Auto" />
4: <RowDefinition Height="*" />
5: </Grid.RowDefinitions>
6: <TextBlock />
7: <ListBox/>>
8: </Grid>
With that UI established, lets jump to the code. When the user clicks the Print button, the following WPF command is executed:
1: this.PrintCommand = new SimpleCommand<Grid>
2: {
3: CanExecuteDelegate = execute => true,
4: ExecuteDelegate = grid =>
5: {
6: PrintCharts(grid);
7: }
8: };
This is pretty simple stuff. SimpleCommand implements the ICommand interface and lets me pass in some lambda expressions defining the code I want to run when this command is fired. Clearly, the magic happens in the PrintCharts(grid) call. The code shown below is basically the same code you would find in Pankaj’s article with a couple of modification highlighted in red.
1: private void PrintCharts(Grid grid)
2: {
3: PrintDialog print = new PrintDialog();
4: if (print.ShowDialog() == true)
5: {
6: PrintCapabilities capabilities = print.PrintQueue.GetPrintCapabilities(print.PrintTicket);
7:
8: double scale = Math.Min(capabilities.PageImageableArea.ExtentWidth / grid.ActualWidth,
9: capabilities.PageImageableArea.ExtentHeight / grid.ActualHeight);
10:
11: Transform oldTransform = grid.LayoutTransform;
12:
13: grid.LayoutTransform = new ScaleTransform(scale, scale);
14:
15: Size oldSize = new Size(grid.ActualWidth, grid.ActualHeight);
16: Size sz = new Size(capabilities.PageImageableArea.ExtentWidth, capabilities.PageImageableArea.ExtentHeight);
17: grid.Measure(sz);
18: ((UIElement)grid).Arrange(new Rect(new Point(capabilities.PageImageableArea.OriginWidth, capabilities.PageImageableArea.OriginHeight),
19: sz));
20:
21: print.PrintVisual(grid, "Print Results");
22: grid.LayoutTransform = oldTransform;
23: grid.Measure(oldSize);
24:
25: ((UIElement)grid).Arrange(new Rect(new Point(0, 0),
26: oldSize));
27: }
28: }
All right, what are these modifications? The most obvious is that I am replacing the use of the original this object (which represented the entire application window in the original code) with the Grid control that was passed in as part of the Command. So all of the measurements and transforms are executed using the Grid. The other change is that I have save the original Transform and Size of the Grid as well. The reason is that when you transform the Grid to fit to the printing page, it causes the actual application UI to change as well. This doesn’t look so good on your screen, so after sending the Grid to the printer, I transform it back to its original screen layout.
There you have it.