Sample Printed Output of Application Screen
Introduction
I was working on a project and found myself trying to replicate the data on the screen into a report for the user. After giving it some thought, I decided to look into printing the screen information. I wanted to produce a professional looking report, not just a simple screen print or snip-it image that the user had to manipulate.
The following code shows an easy way to add printing, along with a title or description on top of the page. It also allows the program to capture your program screen as an image that can be used in documentation, emails, troubleshooting, help files or even to use it as a tooltip!
Sample Screen Image with Screen Image Tooltip
Code Construction
The code is comprised of 2 main classes (and 2 corresponding interface classes). One to create an image file of your screen and another to create the printout of the screen.
Each of the following topics:
- Save screen (
FrameworkElement
) to an image file - Print screen (
FrameworkElement
) as a report
is explained below.
This code could be compiled to a class library and added to your project or it could be added to your MVVM framework so you can add this functionality to all your MVVM projects. After explaining the code in detail, I will use both in a small MVVM application to show how to implement it into your programs.
1. Save Screen to an Image File
Application Screen with Menu Options to Print or Create Page Image
The code to create an image file of the "screen"/FrameworkElement starts with an Enum
of the various image types that are available to save the image as. The 5 available image types are listed below.
ImageType Enums
Private Enum GWSImageType
BMPImage
GIFImage
JPGImage
PNGImage
TIFImage
End Enum
The Enum
s are used behind the scenes to differentiate each method call. BMPImage
will create a ". bmp" bitmap file. JPGImage
will create a ".jpg" JPEG graphics file, etc...
Each method call uses the same signature (a framework element to create the image from, an optional double value used to scale the size of the image).
A default scale value of "1
" means to create the image as the actual size with no scaling. A value of "2
" would create an image twice as large and a value of ".5
" would create the image half the size.
I created an interface for the public
methods in order to use a service provider to locate the available methods, more on that later (MVVM stuff).
The function call to create the BitmapSource file was also made public
so that this function could be used to obtain a BitmapSource
of the screen/FrameworkElement. You could then use this BitmapSource
in an image control to display it. An example of using this function is shown later on when it is used to create an image of the screen and then uses that image in a tooltip. Sample of this screen appears above.
SaveImage Interface
Public Interface IGWSSaveImage
Sub CreateImageFileBMP(ByVal objControl As FrameworkElement, Optional ByVal Scale As Double = 1)
Sub CreateImageFileGIF(ByVal objControl As FrameworkElement, Optional ByVal Scale As Double = 1)
...
Function CreateBitmapSource(ByVal source As FrameworkElement, _
Optional ByVal scale As Double = 1) As BitmapSource
End Interface
The code for each image type is the same except for the encoder that is used to save the bitmap image and of course the filename extension. Each method calls the same procedures with "XXX
" replaced with the image type.
CreateImageFileXXX
Public Sub CreateImageFileXXX(ByVal source As FrameworkElement, Optional ByVal scale As Double = 1)
Dim _bm As BitmapSource = CreateBitmapSource(source, scale)
Dim _bmArray As Byte() = CreateImageByteArray(_bm, GWSImageType.XXXImage)
SaveImageFile(_bmArray, GWSImageType.XXXImage)
End Sub
The three steps in the method above perform the following functions:
- Turn
FrameworkElement
into a BitmapSource
- Convert the
BitmapSource
into an encoded byte array - Save the byte array to a file
1. Turn FrameworkElement into a BitmapSource
The basic code to turn a framework element into a bitmap source is right from Microsoft documentation. I added the scaling to obtain a smaller or larger image.
UIElement.Rendersize.Width
is the same as FrameworkElement.ActualWidth
(and also height), so if you wanted to use a UIElement
as your source object, the code would still work. ActualWidth
and ActualHeight
are only found on FrameworkElements
and not UIElements
.
The code uses a VisualBrush
to "paint" the source into a DrawingContext Rectangle
.
CreateBitmapSource Function
Public Function CreateBitmapSource(ByVal source As FrameworkElement, _
Optional ByVal scale As Double = 1) As BitmapSource Implements IGWSSaveImage.CreateBitmapSource
Dim actualWidth As Double = _
source.RenderSize.Width
Dim actualHeight As Double = _
source.RenderSize.Heigt
Dim renderWidth As Double = actualWidth * scale
Dim renderHeight As Double = actualHeight * scale
Dim renderTarget As New RenderTargetBitmap(CInt(renderWidth), _
CInt(renderHeight), 96, 96, PixelFormats.[Default])
Dim sourceBrush As New VisualBrush(source)
Dim drawingVisual As New System.Windows.Media.DrawingVisual()
Dim drawingContext As DrawingContext = drawingVisual.RenderOpen()
Using drawingContext
drawingContext.PushTransform(New ScaleTransform(scale, scale))
drawingContext.DrawRectangle(sourceBrush, Nothing, _
New Rect(New Point(0, 0), New Point(actualWidth, actualHeight)))
End Using
renderTarget.Render(drawingVisual)
Return renderTarget
End Function
2. Convert the BitmapSource into an Encoded Byte Array
Once we have our BitmapSource
object, we can then convert it into a byte array using the proper encoder based on the file type we wish to receive.
A select case
statement chooses the proper encoder based on the ImageType
parameter of the function call. Once we have the proper encoder, we can create the byte array file.
CreateImageByteArray Function
Private Shared Function CreateImageByteArray_
(ByRef source As BitmapSource, ByVal imageType As GWSImageType) As Byte()
Dim _imageArray As Byte() = Nothing
Dim ImageEncoder As BitmapEncoder = Nothing
Select Case imageType
Case GWSImageType.BMPImage
ImageEncoder = New BmpBitmapEncoder()
...
ImageEncoder.Frames.Add(BitmapFrame.Create(source))
Using outputStream As New MemoryStream()
ImageEncoder.Save(outputStream)
_imageArray = outputStream.ToArray()
End Using
Return _imageArray
End Function
3. Save the byte array to a file
Now that we have our byte array encoded for the file type we desire, the next step is to save the file to disk. The code below uses a SaveFileDialog
to get a path & filename to save the file. The SaveFileDialog
would usually be part of your framework helper methods, but I included it here in this class for completeness.
Save Image File Stream
Private Shared Sub SaveImageFile(ByVal byteData As Byte(), ByRef imageType As GWSImageType)
Dim fileName As String
fileName = GetSaveImageDialog(imageType)
If String.IsNullOrEmpty(fileName) OrElse fileName.Trim() = String.Empty Then
Else
Try
Dim oFileStream As System.IO.FileStream
oFileStream = New System.IO.FileStream(fileName, System.IO.FileMode.Create)
oFileStream.Write(byteData, 0, byteData.Length)
oFileStream.Close()
MessageBox.Show("Window Image Saved. " & fileName, "Save Data", _
MessageBoxButton.OK, MessageBoxImage.Information)
Catch ex As Exception
MessageBox.Show("Error Saving Image to disk.", "Disk Error", _
MessageBoxButton.OK, MessageBoxImage.Stop)
End Try
End If
End Sub
We now have an image of our screen saved to a file of the image type that we selected. Further below, we will demonstrate using this code in a MVVM type application.
2. Print Screen as a Report
Application Screen with Menu Options to Print Page
The code to create a report of our screen is similar to the code to create the screen image. The print code takes an image of the screen as a visual brush (like we did above) and puts it in a Viewbox
, that is then manipulated and printed. The code allows you to select a title for the report as well as setting print options such as paper size, orientation, font size and the ability to stretch the report size.
Once again, I created an Interface for access to this class in order to use a service provider to locate the available methods, again more on that later (MVVM stuff).
In addition, the PrintControl
class contains public
properties that can be set to format your printed report. Here is the interface code and the properties that can be set.
PrintControl Interface
Public Interface IGWSPrintControl
Sub PrintUIElement(ByVal objPrint As UIElement)
Property PrintDescription As String
Property PrintTitle As String
Property PrintMargin As Double
Property PrintFontSize As Double
Property PrintFontWeight As FontWeight
Property PrintForeground As Media.Color
Property PrintStretch As Windows.Media.Stretch
End Interface
The properties are self explanatory as to their purpose. The PrintControl
class starts with setting default values for all of the public
properties. The PrintDescription
is the description of the report that appears in the print queue when you print it.
The PrintTitle
is the title that prints as part of the report. It is initially set to an empty string
which means no title will appear. The font size, font weight and media color only apply to the print title while the print margin and media stretch apply to the whole report.
Default Public Property Values
Private _printDescription As String = "GWS Framework Print"
Private _printTitle As String = String.Empty
Private _printMargin As Double = 100
Private _printFontSize As Double = 36
Private _printFontWeight As FontWeight = FontWeights.Bold
Private _printForeground As Media.Color = Colors.Maroon
Private _printStretch As Windows.Media.Stretch = Stretch.Uniform
In the main method call of the Print Control class, I will pass in a UIElement
instead of a Framework Element to show that either can be used.
The main method first declares a PrintDialog
which will pop up the print dialog window and allow you to make a printer selection, paper size, orientation, etc. Once selected, the PrintDialog
will contain the properties we need to set the properties for our report.
Here is an outline of the steps to print the report:
- Create a
PrintDialog
object. - Create a main grid and set its size to the
PrintableArea
. - Add a
Viewbox
to the main grid and set its alignment. - Add a new grid to the
Viewbox
with 2 rows. - Add your screen as a
VisualBrush
to the bottom row. - Add your title text to the top row and set its properties.
- Print the main grid.
First, you can see that a PrintDialog
object is declared and PrtDialog.ShowDialog
will pop up the dialog for the user. If the user selects the print button, ShowDialog
will return true
and the PrintDialog
object will be populated with their selections. We can obtain the PrintableAreaWidth
and PrintableAreaHeight
from the PrintDialog
and assign it to the size of our main grid (PageGrid
).
Next, we create a Viewbox
and add it to our grid. The key to the Viewbox
is the stretch setting which will allow you to change the size of the screen report. Further below in the MVVM sample program, I will add a menu item where the user can change the stretch parameter before printing.
CreateUIElement Setup and Parameters
Private Sub CreateUIElement(ByVal objPrint As UIElement)
Dim PrtDialog As New PrintDialog
If PrtDialog.ShowDialog = True Then
Dim PageGrid As New Grid
PageGrid.Width = PrtDialog.PrintableAreaWidth
PageGrid.Height = PrtDialog.PrintableAreaHeight
Dim PageViewBox As New Viewbox
PageGrid.Children.Add(PageViewBox)
With PageViewBox
.HorizontalAlignment = Windows.HorizontalAlignment.Center
.VerticalAlignment = Windows.VerticalAlignment.Center
.Margin = New Thickness(Me.PrintMargin)
.Stretch = Me.PrintStretch
End With
...
We create a rectangle and set it to the size of our screen object. We are going to put this rectangle into a Viewbox
so it will be adjusted if needed. A grid is created with 2 rows. The bottom row is set a height of "*" so it will take all the room it needs to display the VisualBrush
image.
Create Grid and Assign VisualBrush
...
Dim PrintRectangle As New Rectangle
PrintRectangle.Height = objPrint.RenderSize.Width
PrintRectangle.Width = objPrint.RenderSize.Width
Dim PrintGrid As New Grid
Dim PrintRow1 As New RowDefinition
Dim PrintRow2 As New RowDefinition
PrintRow1.Height = GridLength.Auto
PrintRow2.Height = New GridLength(1.0, GridUnitType.Star)
PrintGrid.RowDefinitions.Add(PrintRow1)
PrintGrid.RowDefinitions.Add(PrintRow2)
Dim PrintVisualBrush As New VisualBrush(objPrint)
PrintRectangle.Fill = PrintVisualBrush
...
We create a TextBlock
to hold our report title and set its properties from the public
properties for this class. You can easily change the values of each property before you print the report. This will also be demonstrated in the sample MVVM app to follow.
Create Title Text
...
Dim PrintTextBlock As New TextBlock
With PrintTextBlock
.Text = Me.PrintTitle
.FontSize = Me.PrintFontSize
.FontWeight = Me.PrintFontWeight
.Foreground = New SolidColorBrush(Me.PrintForeground)
.VerticalAlignment = VerticalAlignment.Top
.HorizontalAlignment = HorizontalAlignment.Center
End With
Grid.SetRow(PrintTextBlock, 0)
Grid.SetRow(PrintRectangle, 1)
PrintGrid.Children.Add(PrintRectangle)
If Me.PrintTitle <> String.Empty Then
PrintGrid.Children.Add(PrintTextBlock)
End If
PageViewBox.Child = PrintGrid
...
Measure
and Arrange
must be called on our grid or the report will print out a blank page. Once that has occurred, we just need to send our grid to the PrintDialog.PrintVisual
method. This is where we use the print description for the report.
Measure and Arrange and Print
...
PageGrid.Measure(New Size(Double.PositiveInfinity, Double.PositiveInfinity))
PageGrid.Arrange(New Rect(0, 0, PrtDialog.PrintableAreaWidth, PrtDialog.PrintableAreaHeight))
Try
PrtDialog.PrintVisual(PageGrid, Me.PrintDescription)
Catch ex As Exception
Throw ex
End Try
...
There you have it. You can now print or create an image of your screens.
To make this article complete, I will now incorporate this code into a sample MVVM project to illustrate one way to implement this code.
MVVM Sample Project
Sample WPF/MVVM Screen
Overview
In order to see the code in action, I created a small MVVM style project to show how to implement both utilities into your programs. This is not an explanation of WPF or MVVM. I will just explain the code that relates to implementing the screen printing/image capture. The full source code is included in the attached download files if you would like to look at all the functionality.
As you can see in the image above, the print menu will let the user select the stretch setting before printing. You could also add a menu option to allow the user to enter a report title. I used the ViewModel
Switching pattern to navigate screens in the program. I explain the ViewModel
Switching in detail in my previous article here on Code Project.
Laying the Groundwork
This is the same simple MVVM framework as shown in my previous article. It is repeated it here for completeness and to acknowledge the people who wrote the original code.
First - The Plumbing
If you are creating any kind of WPF application (an enterprise wide solution to a small standalone app), it makes sense to use the Model-View-ViewModel pattern. This is not an article to explain MVVM, there are plenty of them on Code Project that you can reference. When using MVVM, it pays to use a framework for the program infrastructure. There are also numerous frameworks available to choose from.
I have created a simple MVVM framework module to provide the basic "plumbing" for the application. The framework contains the following items:
- RelayCommand.vb
- Messenger.vb
- ServiceContainer.vb
- IMessageBoxService.vb
- MessageBoxService.vb
- ServiceInjector.vb
- IScreen.vb
- BaseViewModel.vb
- IShowDialogService.vb
- ShowDialogService.vb
The relay-command, messenger, IScreen
and service provider are from articles from Josh Smith and the BaseViewModel
class complete with INotifyPropertyChanged
logic is from Karl Shifflett's articles, all here on Code Project. All the code for this framework is included in the project source code download file.
Bringing It All Together
In a few easy steps, Printing
and ImageSave
is added to all screens in my program. Since I use ViewModel
switching in all my WPF/MVVM programs, my main window contains a content control to display the current ViewModel
. First, all I need to do is add a name to the ContentControl (x:name = "MyView")
so I can pass it to the Print
and SaveImage
methods.
MainWindow XAML ContentControl
<ContentControl Content="{Binding Path=CurrentPageViewModel}"
Grid.Row="1"
x:Name="MyView"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" />
Then, I create two menu items on the MainWindow
. I link each menu item to a command and pass the ContentControl
as the command parameter.
MainWindow Xaml Menu Items
<MenuItem Header="_Print Page"
Command="{Binding Path=PrintCommand}"
CommandParameter="{Binding ElementName=MyView}">
...
<MenuItem Header="_Create Page Image"
Command="{Binding Path=SaveImageCommand}"
CommandParameter="{Binding ElementName=MyView}">
Then in the application file, I use the ServiceContainer
from my MVVM Framework and set up a reference to each method. This is why I created an interface for both classes as explained in the details above.
Application.Xaml.VB File
Public Sub New()
...
ServiceContainer.Instance.AddService(Of IGWSPrintControl)(New GWSPrintControl)
ServiceContainer.Instance.AddService(Of IGWSSaveImage)(New GWSSaveImage)
...
End Sub
Then each menu item will call the ICommand
on the ViewModel
. I put this code in the DisplayViewModel
that is inherited by each ViewModel
. Each ICommand
uses the standard RelayCommand
from Josh Smith's articles on Code Project.
Each command uses the MVVM Framework ServiceContainer
to find the proper method. Each method is passed the ContentControl
as a parameter and is printed or saved using the default properties.
DisplayViewModel ICommands
Public Overridable ReadOnly Property PrintCommand() As ICommand
Get
Return New RelayCommand(Of UIElement)(AddressOf Me.Print)
End Get
End Property
Protected Overridable Sub Print(ByVal obj As UIElement)
Dim GWSPrintControl = MyBase.GetService(Of IGWSPrintControl)()
GWSPrintControl.PrintUIElement(obj)
End Sub
Public Overridable ReadOnly Property SaveImageCommand() As ICommand
Get
Return New RelayCommand(Of FrameworkElement)(AddressOf Me.ImageExecute)
End Get
End Property
Protected Overridable Sub ImageExecute(ByVal obj As FrameworkElement)
Dim GWSSaveImage = MyBase.GetService(Of IGWSSaveImage)()
GWSSaveImage.CreateImageFileJPG(obj)
End Sub
That's it! The program now has the functionality to Print
or SaveImage
of each screen in the program.
Now for a Few Bells and Whistles
Let's change the title, margin and stretch on the BoxGridViewModel Screen report.
For each ViewModel, you can override the ICommand and set the properties for that ViewModel. This will let you change the description of each screen and change the font size, color and margins.
Since each ICommand
on the DisplayViewModel
is marked as Overridable
, we can simply Override
the ICommand
in the CurrentPageViewModel
. Then, we create the PrintControl
object and set any properties we wish for this ViewModel
.
BoxGridViewModel with Overrides
Public Overrides ReadOnly Property PrintCommand() As ICommand
Get
Return New RelayCommand(Of UIElement)(AddressOf Me.Print)
End Get
End Property
Protected Overrides Sub Print(ByVal obj As UIElement)
Dim GWSPrintControl = MyBase.GetService(Of IGWSPrintControl)()
With GWSPrintControl
.PrintDescription = "Print Demo Description"
.PrintTitle = My.Application.Info.Description
.PrintStretch = Stretch.Fill
.PrintMargin = 50
End With
GWSPrintControl.PrintUIElement(obj)
End Sub
Let's change the SaveImage file type and hide the 4 buttons on the edit screen.
Some information on the screen you may not wish to show on the screen image or printout. You can change the screen anyway you like before creating the image and then restore it.
First, we declare the messenger from the MVVM Framework in our application file and set up 2 constant string
s to be used as our messages.
Application.Xaml.vb Declarations
Shared ReadOnly _messenger As New GwsMvvmFramework.Messenger
...
Friend Const MSG_CLEANUP_COMMAND As String = "Clean up"
Friend Const MSG_CLEANUP_RESET_COMMAND As String = "Clean up Reset"
...
Friend Shared ReadOnly Property Messenger() As Messenger
Get
Return _messenger
End Get
End Property
Now, when we do our override, we can send a message beforehand and tell the "listener" to perform some action. (hide the buttons). Then we can call the CreateImageFilePNG
method to produce a .png outputfile. Then, we send another message to tell the "listener" to perform another action (unhide the buttons).
EditRoundViewModel Override
Protected Overrides Sub ImageExecute(ByVal obj As FrameworkElement)
Application.Messenger.NotifyColleagues(Application.MSG_CLEANUP_COMMAND)
Dim GWSSaveImage = MyBase.GetService(Of IGWSSaveImage)()
GWSSaveImage.CreateImageFilePNG(obj)
Application.Messenger.NotifyColleagues(Application.MSG_CLEANUP_RESET_COMMAND)
End Sub
In the EditRoundView
code behind file, we register to receive both messages and assign an action to perform for each one. The first message will hide the buttons and the second message will restore them. The buttons must have a name property in the XAML so they can be accessed from the codebehind. All of this code behind code is View related and called by the messenger.
EditRoundView.Xaml.VB Code Behind File
Partial Public Class EditRoundView
Public Sub New()
InitializeComponent()
Application.Messenger.Register_
(Application.MSG_CLEANUP_COMMAND, New Action(AddressOf Me.CleanUpScreenImage))
Application.Messenger.Register_
(Application.MSG_CLEANUP_RESET_COMMAND, New Action(AddressOf Me.CleanUpResetScreenImage))
End Sub
Private Sub CleanUpScreenImage()
AcceptButton.Visibility = Windows.Visibility.Hidden
CancelButton.Visibility = Windows.Visibility.Hidden
ApplyButton.Visibility = Windows.Visibility.Hidden
GetRnd.Visibility = Windows.Visibility.Hidden
End Sub
Private Sub CleanUpResetScreenImage()
AcceptButton.Visibility = Windows.Visibility.Visible
CancelButton.Visibility = Windows.Visibility.Visible
ApplyButton.Visibility = Windows.Visibility.Visible
GetRnd.Visibility = Windows.Visibility.Visible
End Sub
End Class
Let's create a bitmap image of the screen and use it in a tooltip.
This might not seem as the most useful thing, but it is demonstrating how to obtain a bitmap of the screen and use it in your program.
On our EditRoundViewModel
, we need to add property get/set to hold the bitmap source.
EditRoundViewModel Property
Public Property BitmapScreen As BitmapSource
Get
Return _BitmapScreen
End Get
Set(value As BitmapSource)
MyBase.SetPropertyValue("BitmapScreen", _BitmapScreen, value)
End Set
End Property
Recall from the top of the article, we declared the method to CreateBitmapSource
as public
so we could access it later. So here we are later. Assign the bitmap source created to our new BitmapScreen
property and we now have an image of the screen sitting in our ViewModel
. You can hide any controls you like before creating the image. Notice I set the scaling in the CreateBitmapSource
to 0.35
to create a small image.
EditRoundViewModel ImageExecute Override
Protected Overrides Sub ImageExecute(ByVal obj As FrameworkElement)
Application.Messenger.NotifyColleagues(Application.MSG_CLEANUP_COMMAND)
Dim GWSSaveImage = MyBase.GetService(Of IGWSSaveImage)()
GWSSaveImage.CreateImageFilePNG(obj)
BitmapScreen = GWSSaveImage.CreateBitmapSource(obj, 0.35)
Application.Messenger.NotifyColleagues(Application.MSG_CLEANUP_RESET_COMMAND)
End Sub
Now, we just create the tooltip in the XAML file and set the source to the BitmapScreen
property in the ViewModel
holding the screen image. Then, assign the tooltip to a style and use it wherever you like.
EditRoundViewModel XAML Tooltip
<Grid.Resources>
<ToolTip x:Key="tt">
<StackPanel Orientation="Vertical">
<TextBlock Text="Tool Tip with Image"></TextBlock>
<Image Source="{Binding BitmapScreen}"/>
</StackPanel>
</ToolTip>
...
<Style x:Key="TextBoxStyle1" TargetType="TextBox">
<Setter Property="TextBox.Margin" Value="5" />
<Setter Property="TextBox.TextAlignment" Value="Right" />
<Setter Property="ToolTip" Value="{StaticResource tt}"/>
</Style>
...
Let's allow the user to select the print stretch setting from a menu item.
This will show how to let the user select property values to adjust the screen image. Although this will explain how to let the user select stretch values, the same logic could be followed to allow the user to select other properties. (Font size, color, etc.)
On the DisplayViewModel
, we need to setup an ICommand
to link to the Stretch Menu item we will create in a moment. We also need a PrintStretch
property to hold our user selected Stretch Value. We need to make sure that PrintControl
object points to Print Stretch property for its value.
DisplayViewModel Stretch ICommand and Property
...
Public ReadOnly Property PrintStretchCommand() As ICommand
Get
Return New RelayCommand(Of Stretch)(AddressOf Me.PrintStretch)
End Get
End Property
Private Sub PrintStretch(ByVal obj As Stretch)
Print_Stretch = obj
End Sub
Public Property Print_Stretch() As Stretch
Get
Return _Print_Stretch
End Get
Set(value As Stretch)
_Print_Stretch = value
End Set
End Property
...
Protected Overridable Sub Print(ByVal obj As UIElement)
Dim GWSPrintControl = MyBase.GetService(Of IGWSPrintControl)()
With GWSPrintControl
.PrintDescription = "Print Demo Description"
.PrintTitle = "Control Printout"
.PrintStretch = Me.Print_Stretch
.PrintMargin = 50
End With
GWSPrintControl.PrintUIElement(obj)
End Sub
...
Stretch
is an Microsoft supplied Enum
. There are 4 available values (Fill
, None
, Uniform
, UniformtoFill
). Since it is an Enum
, we can use it to automatically create our menu items in XAML. We need to make sure we set a reference Mscorlib
and PresentationCore
assemblies in our XAML. Then, we can set up an ObjectDataProvider
to give us the stretch values.
MainWindowView XAML ObjectDataProvider
<Window x:Class="MainWindowView" Name="MyWin"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PrintSaveDemo"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:prt="clr-namespace:System.Windows.Media;assembly=PresentationCore"
...
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/MainResources.xaml" />
</ResourceDictionary.MergedDictionaries>
<ObjectDataProvider x:Key="dataFromEnum1"
MethodName="GetValues"
ObjectType="{x:Type sys:Enum}">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="prt:Stretch" />
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
...
Now, we can create the menu item for the Stretch
values and set the ItemSource
to ObjectDataProvider (x:Key=dataFromEnum1)
. To select the value from the menu item, I am using a mutually exclusive set of Radio Buttons. A StyleSelector
is used to select a style to show a RadioButton
that is checked or unchecked.
MainWindowView XAML Stretch Menu and Styles
<MenuItem.Resources>
<RadioButton x:Key="RadioMenu"
GroupName="RadioEnum"
x:Shared="False"
HorizontalAlignment="Center"
IsHitTestVisible="False"/>
<RadioButton x:Key="RadioMenuChecked"
GroupName="RadioEnum"
x:Shared="False"
HorizontalAlignment="Center"
IsHitTestVisible="False"
IsChecked="True"/>
<Style TargetType="MenuItem" x:Key="RadioUnCheckedStyle">
<Setter Property="Command"
Value="{Binding Path=DataContext.PrintStretchCommand,
RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
<Setter Property="CommandParameter" Value="{Binding}" />
<EventSetter Event="Click"
Handler="RadioEnum_Click" />
<Setter Property="Icon"
Value="{StaticResource RadioMenu}"/>
<Setter Property="StaysOpenOnClick" Value="True"/>
</Style>
<Style TargetType="MenuItem" x:Key="RadioCheckedStyle"
BasedOn="{StaticResource RadioUnCheckedStyle}">
<Setter Property="Icon"
Value="{StaticResource RadioMenuChecked}"/>
</Style>
</MenuItem.Resources>
<MenuItem Header="Print S_tretch"
Name="StretchEnum"
DataContext="{Binding}"
ItemsSource="
{Binding Source={StaticResource dataFromEnum1}}" >
...
<MenuItem.ItemContainerStyleSelector>
<local:RadioStyleSelector UnCheckedStyle="{StaticResource RadioUnCheckedStyle}"
CheckedStyle="{StaticResource RadioCheckedStyle}" />
</MenuItem.ItemContainerStyleSelector>
</MenuItem>
In order to make sure you can click on the MenuItem
and not just the RadioButton
, the following code needs to be added to the Codebehind file. Again, this is all View related code.
MainWindow.Xaml.Vb Click Event
Partial Public Class MainWindowView
Public Sub New()
InitializeComponent()
End Sub
...
#Region "Methods"
Private Sub RadioEnum_Click(sender As Object, e As System.Windows.RoutedEventArgs)
Dim mitem As MenuItem = TryCast(sender, MenuItem)
If mitem IsNot Nothing Then
Dim rbutton As RadioButton = TryCast(mitem.Icon, RadioButton)
If rbutton IsNot Nothing Then
rbutton.IsChecked = True
End If
End If
End Sub
#End Region
End Class
There you have it. A fully functional WPF/MVVM program with the ability to print or capture each screen.
*Cartoon Images from Xamalot.com free clipart.