Client Printing

Download Samples Repository

Description

This sample demonstrates how to print a map using client-side capabilities of the PrintDialog class.

"Desktop" Available for Desktop

Sample Code

<UserControl x:Class="ArcGISRuntime.Samples.Desktop.ClientPrinting"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:sys="clr-namespace:System;assembly=mscorlib"
             xmlns:esri="http://schemas.esri.com/arcgis/runtime/2013"
             xmlns:local="clr-namespace:ArcGISRuntime.Samples.Desktop">
    <Grid x:Name="layoutGrid" Background="LightGray">
        <Grid.Resources>
            <ResourceDictionary>
                <BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>

                <!--  Collection of preview sizes (Note: real size will be set during the print process depending on printer properties)  -->
                <local:PreviewSizes x:Key="previewSizes">
                    <local:PreviewSize Width="780" Height="1100" Id="A4 Portrait" />
                    <local:PreviewSize Width="1100" Height="780" Id="A4 Landscape" />
                    <local:PreviewSize Width="1100" Height="1560" Id="A3 Portrait" />
                    <local:PreviewSize Width="1560" Height="1100" Id="A3 Landscape" />
                    <local:PreviewSize Width="2200" Height="1560" Id="A2 Landscape" />
                    <local:PreviewSize Width="3120" Height="2200" Id="A1 Landscape" />
                    <local:PreviewSize Width="925" Height="1343" Id="B4 Portrait" />
                    <local:PreviewSize Width="1343" Height="925" Id="B4 Landscape" />
                    <local:PreviewSize Width="687" Height="971" Id="B5 Portrait" />
                    <local:PreviewSize Width="971" Height="687" Id="B5 Landscape" />
                    <local:PreviewSize Width="760" Height="1000" Id="Letter Portrait" />
                    <local:PreviewSize Width="976" Height="760" Id="Letter Landscape" />
                    <local:PreviewSize Width="665" Height="977" Id="Executive" />
                    <local:PreviewSize Width="689" Height="415" Id="Envelope" />
                </local:PreviewSizes>

                <!--  Dictionary with all available Print Templates  -->
                <ResourceDictionary x:Key="PrintTemplates">
                    <!--  Basic style with the map only  -->
                    <ControlTemplate x:Key="Map Only">
                        <esri:MapView x:Name="PrintMapView" Background="White" />
                    </ControlTemplate>

                    <!--  Print map with header and footer  -->
                    <ControlTemplate x:Key="Header And Footer">
                        <Grid Background="White">
                            <Grid Margin="10">
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="Auto" />
                                    <RowDefinition />
                                    <RowDefinition Height="Auto" />
                                </Grid.RowDefinitions>

                                <!--  Title  -->
                                <TextBlock HorizontalAlignment="Center" FontSize="14" Text="{Binding Title}" TextWrapping="Wrap" />

                                <!--  Map with attribution  -->
                                <Border Grid.Row="1" Background="Transparent"
                                    BorderBrush="Black" BorderThickness="1">
                                    <Grid>
                                        <esri:MapView x:Name="PrintMapView" Background="White" />

                                        <!-- Attribution -->
                                        <ItemsControl Grid.Row="1" Margin="10,10,300,10" HorizontalAlignment="Left" VerticalAlignment="Bottom"
                                                      ItemsSource="{Binding AttributionItems}">
                                            <ItemsControl.ItemTemplate>
                                                <DataTemplate>
                                                    <TextBlock Text="{Binding}" TextWrapping="Wrap" />
                                                </DataTemplate>
                                            </ItemsControl.ItemTemplate>
                                        </ItemsControl>
                                        
                                        <!-- Scale -->
                                        <TextBlock Margin="0,0,10,10" HorizontalAlignment="Right" VerticalAlignment="Bottom" FontWeight="Bold" 
                                                   Text="{Binding ElementName=PrintMapView, Path=Scale, StringFormat='Scale: 1 : {0:0.000}'}" />
                                    </Grid>
                                </Border>

                                <!--  Date as Footer  -->
                                <TextBlock Grid.Row="2" HorizontalAlignment="Left" VerticalAlignment="Center"
                                       Text="{Binding Now, StringFormat='Printed {0:D} '}" />
                            </Grid>
                        </Grid>
                    </ControlTemplate>

                    <!--  Standard style with Title, Map, Scale and Attribution  -->
                    <ControlTemplate x:Key="Standard">
                        <Border Background="White">
                            <Grid Margin="10">
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="Auto" />
                                    <RowDefinition Height="*" />
                                    <RowDefinition Height="Auto" />
                                </Grid.RowDefinitions>

                                <!--  Header  -->
                                <TextBlock Margin="10" HorizontalAlignment="Center" FontSize="22" 
                                       Text="{Binding Title}" TextWrapping="Wrap" />

                                <!--  Map  -->
                                <Border Grid.Row="1" BorderBrush="Black" BorderThickness="1">
                                    <esri:MapView x:Name="PrintMapView" />
                                </Border>

                                <!--  Footer  -->
                                <Grid Grid.Row="2" Margin="0,10">
                                    <Grid.RowDefinitions>
                                        <RowDefinition Height="Auto" />
                                        <RowDefinition Height="Auto" />
                                        <RowDefinition Height="Auto" />
                                    </Grid.RowDefinitions>
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition Width="2*" />
                                        <ColumnDefinition Width="3*" />
                                    </Grid.ColumnDefinitions>

                                    <TextBlock HorizontalAlignment="Left" VerticalAlignment="Center"
                                               FontSize="16"
                                               Text="{Binding Now, StringFormat='Printed {0:d} '}" />
                                    
                                    <TextBlock Grid.Column="1" HorizontalAlignment="Left" VerticalAlignment="Center" FontSize="14"
                                               Text="{Binding Scale, ElementName=PrintMapView, StringFormat='Scale: 1 : {0:0.000}'}" />

                                    <!-- Attribution -->
                                    <ItemsControl Grid.Row="2" Grid.Column="1" ItemsSource="{Binding AttributionItems}">
                                        <ItemsControl.ItemTemplate>
                                            <DataTemplate>
                                                <TextBlock Text="{Binding}" TextWrapping="Wrap" />
                                            </DataTemplate>
                                        </ItemsControl.ItemTemplate>
                                    </ItemsControl>
                                </Grid>
                            </Grid>
                        </Border>
                    </ControlTemplate>
                </ResourceDictionary>
            </ResourceDictionary>
        </Grid.Resources>

        <!--  Map Control  -->
        <esri:MapView x:Name="MyMapView" WrapAround="True" MinScale="2446">
			<esri:Map InitialViewpoint="-9813505,5127140,-9812977,5127509,3857">
                <esri:ArcGISTiledMapServiceLayer ID="Basemap" ShowLegend="False" 
                    ServiceUri="http://services.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer" />

                <esri:ArcGISDynamicMapServiceLayer ID="Water Network" 
                    ServiceUri="http://sampleserver6.arcgisonline.com/arcgis/rest/services/Water_Network/MapServer" />
            </esri:Map>
        </esri:MapView>

		<Border Background="White" BorderBrush="Black" BorderThickness="1"
				HorizontalAlignment="Right" VerticalAlignment="Top"
				Margin="30" Padding="20">
			<Border.Effect>
				<DropShadowEffect/>
			</Border.Effect>
			<Button Content="Print" 
					Width="150" Height="75"
					FontSize="18"
					Click="ActivatePrintPreview"
					ToolTipService.ToolTip="Show Print Preview Dialog" />
		</Border>
		

        <!--  Print Preview Dialog (overlaps the map in this sample)  -->
        <Grid x:Name="previewPanel" Background="LightGray" Visibility="Collapsed">
            <Grid.ColumnDefinitions>
                <!--  Print parameters  -->
                <ColumnDefinition Width="Auto" />
                <!--  Separator  -->
                <ColumnDefinition Width="Auto" />
                <!--  MapPrinter preview  -->
                <ColumnDefinition />
            </Grid.ColumnDefinitions>

            <!--  Print parameters  -->
            <Grid Grid.Column="0" MaxWidth="350" Margin="3">
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="*" />
                </Grid.RowDefinitions>

                <!--  Print properties row 0  -->
                <Grid Grid.Row="0" Margin="10">
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto" />
                            <RowDefinition Height="Auto" />
                            <RowDefinition Height="Auto" />
                            <RowDefinition Height="Auto" />
                        </Grid.RowDefinitions>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="Auto" />
                            <ColumnDefinition Width="*" />
                        </Grid.ColumnDefinitions>

                        <DockPanel Grid.ColumnSpan="2" HorizontalAlignment="Stretch">
                            <TextBlock FontSize="16" FontWeight="Bold" Text="Print properties " />
                            <Separator Margin="0" Background="DarkGray" />
                        </DockPanel>

                        <!--  Title  -->
                        <TextBlock Grid.Row="1" Margin="5" VerticalAlignment="Center" Text="Title: " />
                        <TextBox Grid.Row="1" Grid.Column="1" Margin="5" VerticalAlignment="Center"
                                 HorizontalScrollBarVisibility="Visible" 
                                 Text="{Binding Title, Mode=TwoWay, ElementName=printControl}" TextWrapping="Wrap" />

                        <!--  Rotate map  -->
                        <TextBlock Grid.Row="2" Margin="5" VerticalAlignment="Center" Text="Rotate Map: " />
                        <CheckBox Grid.Row="2" Grid.Column="1" Margin="5" VerticalAlignment="Center"
                                  IsChecked="{Binding RotateMap, Mode=TwoWay, ElementName=printControl}" />

                        <!--  Style selector from the PrintTemplates resource dictionary  -->
                        <TextBlock Grid.Row="3" Margin="5"
                                   VerticalAlignment="Center" Text="Print Style: " />
                        <ComboBox x:Name="comboPrintTemplate" Grid.Row="3" Grid.Column="1" Width="150" Margin="5" 
                                  VerticalAlignment="Center" DisplayMemberPath="Key" SelectedValuePath="Value"
                                  ItemsSource="{StaticResource PrintTemplates}" SelectedIndex="0">
                        </ComboBox>
                    </Grid>
                </Grid>

                <!--  Print Menu  row 1  -->
                <Grid Grid.Row="1" Margin="10">
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition />
                            <RowDefinition />
                            <RowDefinition />
                        </Grid.RowDefinitions>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="Auto" />
                            <ColumnDefinition Width="*" />
                        </Grid.ColumnDefinitions>
                        <DockPanel Grid.ColumnSpan="2">
                            <TextBlock FontSize="16" FontWeight="Bold" Text="Print menu " />
                            <Separator Margin="0" Background="DarkGray" />
                        </DockPanel>

                        <!--  Preview size selector  -->
                        <TextBlock Grid.Row="1" Margin="5"
                                   VerticalAlignment="Center" Text="Preview size: " />
                        <ComboBox x:Name="comboPreviewSize" Grid.Row="1" Grid.Column="1" Width="150" Margin="5"
                                  VerticalAlignment="Center" DisplayMemberPath="Id"
                                  ItemsSource="{StaticResource previewSizes}" SelectedIndex="0" />

                        <!--  Print Button  -->
                        <Button x:Name="PrintButton" Grid.Row="2" Grid.ColumnSpan="2" Margin="5"
                                HorizontalAlignment="Stretch" Click="OnPrint" ToolTipService.ToolTip="Print Map"
                                Content="Print" />
                    </Grid>
                </Grid>

                <!--  Busy indicator over all parameters  -->
                <Grid Grid.Row="0" Grid.RowSpan="2" Background="#80FFFFFF"
                      Visibility="{Binding IsPrinting, ElementName=printControl, Converter={StaticResource BooleanToVisibilityConverter}}">
                    <Grid HorizontalAlignment="Center" VerticalAlignment="Center">
                        <Border Background="White" BorderBrush="Black" BorderThickness="1">
                            <StackPanel Margin="10" Orientation="Horizontal">
                                <Grid x:Name="BusyIndicator" Margin="3,0"
                                      HorizontalAlignment="Center" VerticalAlignment="Center"
                                      Background="Transparent" RenderTransformOrigin="0.5,0.5">
                                    <Grid.Triggers>
                                        <EventTrigger RoutedEvent="Grid.Loaded">
                                            <EventTrigger.Actions>
                                                <BeginStoryboard>
                                                    <Storyboard>
                                                        <DoubleAnimation Duration="0:0:1" RepeatBehavior="Forever" Storyboard.TargetName="BusyIndicator"
                                                                         Storyboard.TargetProperty="(UIElement.RenderTransform).(RotateTransform.Angle)"
                                                                         To="360" />
                                                    </Storyboard>
                                                </BeginStoryboard>
                                            </EventTrigger.Actions>
                                        </EventTrigger>
                                    </Grid.Triggers>

                                    <Grid.RenderTransform>
                                        <RotateTransform />
                                    </Grid.RenderTransform>
                                    <Ellipse Width="2" Height="2"
                                             Margin="11,2,11,20" Fill="#1E525252" />
                                    <Ellipse Width="3" Height="3"
                                             Margin="0,4,5,0" HorizontalAlignment="Right"
                                             VerticalAlignment="Top" Fill="#3F525252" />
                                    <Ellipse Width="4" Height="4"
                                             Margin="0,9,1,0" HorizontalAlignment="Right"
                                             VerticalAlignment="Top" Fill="#7F525252" />
                                    <Ellipse Width="5" Height="5"
                                             Margin="0,0,3,3" HorizontalAlignment="Right"
                                             VerticalAlignment="Bottom" Fill="#BF525252" />
                                    <Ellipse Width="6" Height="6"
                                             Margin="9,0" VerticalAlignment="Bottom"
                                             Fill="#FF525252" />
                                </Grid>
                                <TextBlock FontSize="22" Text="Printing..." />
                            </StackPanel>
                        </Border>
                    </Grid>
                </Grid>

                <!--  Footer with Done/Cancel button row 2  -->
                <StackPanel Grid.Row="2" VerticalAlignment="Bottom">
                    <Separator Margin="5,0" Background="DarkGray" />
                    <Button Margin="5" HorizontalAlignment="Right" Click="DeactivatePrintPreview" FontSize="14">
                        <Button.Style>
                            <Style TargetType="{x:Type Button}">
                                <Style.Triggers>
                                    <DataTrigger Binding="{Binding IsPrinting, ElementName=printControl}" Value="True">
                                        <Setter Property="Content" Value="Cancel" />
                                    </DataTrigger>
                                    <DataTrigger Binding="{Binding IsPrinting, ElementName=printControl}" Value="False">
                                        <Setter Property="Content" Value="Done" />
                                    </DataTrigger>
                                </Style.Triggers>
                            </Style>
                        </Button.Style>
                    </Button>
                </StackPanel>
            </Grid>

            <!--  Separator  column 1  -->
            <Border Grid.Column="1" HorizontalAlignment="Stretch" BorderBrush="Black" BorderThickness="1" />

            <!--  Preview Map column 2  -->
            <Grid Grid.Column="2">
                <!--  Viewbox allows the whole mapprinter to be rendered in the allocate size  -->
                <Border Margin="4" HorizontalAlignment="Center" VerticalAlignment="Center" 
                        BorderBrush="Black" BorderThickness="1">
                    <Border.Effect>
                        <DropShadowEffect Direction="-45" Opacity="0.5" ShadowDepth="4" />
                    </Border.Effect>
                    <Viewbox>
                        <local:MapPrinter x:Name="printControl" Title="Water Network"
                                          BaseMapView="{Binding ElementName=MyMapView}"
                                          Width="{Binding ElementName=comboPreviewSize, Path=SelectedItem.Width}"
                                          Height="{Binding ElementName=comboPreviewSize, Path=SelectedItem.Height}"
                                          Template="{Binding SelectedValue, ElementName=comboPrintTemplate}" />
                    </Viewbox>
                </Border>
            </Grid>
        </Grid>
    </Grid>
</UserControl>
using Esri.ArcGISRuntime.Controls;
using Esri.ArcGISRuntime.Layers;
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;

namespace ArcGISRuntime.Samples.Desktop
{
    /// <summary>
    /// This sample demonstrates how to print a map using client-side capabilities of the PrintDialog class.
    /// </summary>
    /// <title>Client Printing</title>
    /// <category>Printing</category>
    public partial class ClientPrinting : UserControl
    {
        /// <summary>Construct Client Printing sample control</summary>
        public ClientPrinting()
        {
            InitializeComponent();
        }

        private void ActivatePrintPreview(object sender, RoutedEventArgs e)
        {
            previewPanel.Visibility = Visibility.Visible;
        }

        private void DeactivatePrintPreview(object sender, RoutedEventArgs e)
        {
            previewPanel.Visibility = Visibility.Collapsed;
        }

        private void OnPrint(object sender, RoutedEventArgs e)
        {
            printControl.Print();
        }
    }


    /// <summary>Collection of PreviewSize (creatable in XAML)</summary>
    public class PreviewSizes : ObservableCollection<PreviewSize> { }

    /// <summary>Represents a Preview Size (creatable in XAML)</summary>
    public class PreviewSize
    {
        /// <summary>ID or Name of the preview size</summary>
        public string Id { get; set; }
        /// <summary>Width of the preview size</summary>
        public double Width { get; set; }
        /// <summary>Height of the preview size</summary>
        public double Height { get; set; }
    }


    // Main print control that displays the map using the print template
    internal class MapPrinter : Control, INotifyPropertyChanged
    {
        public MapPrinter()
        {
            _isPrinting = false;
            _attributionItems = new ObservableCollection<string>();
            DataContext = this; // simplify binding in print styles

            HorizontalContentAlignment = HorizontalAlignment.Stretch;
            VerticalContentAlignment = VerticalAlignment.Stretch;
        }

        // Executed when the print template changed
        public override async void OnApplyTemplate()
        {
            var extent = (PrintMapView != null) ? PrintMapView.Extent : null;

            base.OnApplyTemplate();

            PrintMapView = GetTemplateChild("PrintMapView") as MapView;
            if (PrintMapView.Extent == null)
            {
                PrintMapView.MaximumExtent = BaseMapView.MaximumExtent;
                PrintMapView.MaxScale = BaseMapView.MaxScale;
                PrintMapView.MinScale = BaseMapView.MinScale;
				PrintMapView.SetRotation(BaseMapView.Rotation);
                PrintMapView.WrapAround = BaseMapView.WrapAround;
                PrintMapView.Map = BaseMapView.Map;

                await PrintMapView.LayersLoadedAsync();
            }

            if (extent != null)
                PrintMapView.SetView(extent);
            else if (BaseMapView != null)
				PrintMapView.SetView(BaseMapView.Extent ?? (BaseMapView.Map.InitialViewpoint == null ? null : BaseMapView.Map.InitialViewpoint.TargetGeometry));

            AttributionItems = new ObservableCollection<string>(
                PrintMapView.Map.Layers.Where(layer => layer.IsVisible)
                .Select(CopyrightText).Where(cp => !string.IsNullOrEmpty(cp))
                .Select(cp => cp.Trim()).Distinct());
        }

        // BaseMapView to print (Dependency Property)
        public MapView BaseMapView
        {
            get { return (MapView)GetValue(MapProperty); }
            set { SetValue(MapProperty, value); }
        }

        public static readonly DependencyProperty MapProperty = DependencyProperty.Register("BaseMapView", typeof(MapView), typeof(MapPrinter), new PropertyMetadata(null, OnBaseMapViewChanged));

        private static void OnBaseMapViewChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var mapPrinter = d as MapPrinter;
            var newBaseMapView = e.NewValue as MapView;
            if (mapPrinter != null)
            {
                if (newBaseMapView != null && mapPrinter.PrintMapView != null)
					mapPrinter.PrintMapView.SetView(newBaseMapView.Extent ?? (newBaseMapView.Map.InitialViewpoint == null ? null : newBaseMapView.Map.InitialViewpoint.TargetGeometry));
                if (newBaseMapView == null && mapPrinter.IsPrinting)
                    mapPrinter.IsCancelingPrint = true;
            }
        }

        // Title of the print document (Dependency Property)
        public string Title
        {
            get { return (string)GetValue(TitleProperty); }
            set { SetValue(TitleProperty, value); }
        }

        public static readonly DependencyProperty TitleProperty = DependencyProperty.Register("Title", typeof(string), typeof(MapPrinter), new PropertyMetadata("Map Document"));

        // Flag indicating that the map must be rotated 90°
        public bool RotateMap
        {
            get { return (bool)GetValue(RotateMapProperty); }
            set { SetValue(RotateMapProperty, value); }
        }

        public static readonly DependencyProperty RotateMapProperty =
                DependencyProperty.Register("RotateMap", typeof(bool), typeof(MapPrinter), new PropertyMetadata(false, OnRotateMapChanged));

        private static void OnRotateMapChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var mapPrinter = d as MapPrinter;
            if (mapPrinter != null)
                mapPrinter.PrintMapView.SetRotation((bool)e.NewValue ? -90 : 0);
        }

        private bool _isPrinting;
        // Indicates if a print task is going on.
        public bool IsPrinting
        {
            get { return _isPrinting; }
            private set
            {
                if (value != _isPrinting)
                {
                    _isPrinting = value;
                    NotifyPropertyChanged();
                }
            }
        }

        // Attribution Items
        private ObservableCollection<string> _attributionItems;
        public ObservableCollection<string> AttributionItems
        {
            get { return _attributionItems; }
            private set
            {
                if (value != _attributionItems)
                {
                    _attributionItems = value;
                    NotifyPropertyChanged();
                }
            }
        }

        // The print map (defined in the print template)
        public MapView PrintMapView { get; private set; }

        // Gets the current date/time.
        public DateTime Now
        {
            get { return DateTime.Now; }
        }

        // Start the print process (by delegating either to the Silverlight print engine or to the WPF print engine)
        public void Print()
        {
            if (IsPrinting)
                return;

            try
            {
                var printDialog = new PrintDialog();
                if (printDialog.ShowDialog() != true)
                    return;

                BeginPrint();

                if (!IsCancelingPrint)
                    printDialog.PrintVisual(this, Title);

                EndPrint(null);
            }
            catch (Exception e)
            {
                EndPrint(e);
            }
        }

        // INotifyPropertyChanged implementation
        public event PropertyChangedEventHandler PropertyChanged;

        // Notifies the property changed.
        protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
        {
            var handler = PropertyChanged;
            if (handler != null)
                handler(this, new PropertyChangedEventArgs(propertyName));
        }

        // Internal methods/properties
        internal bool IsCancelingPrint { get; set; }

        internal void BeginPrint()
        {
            IsCancelingPrint = false;
            IsPrinting = true;
            NotifyPropertyChanged("Now"); // in case time is displayed
        }

        internal void EndPrint(Exception error)
        {
            if (error != null && !IsCancelingPrint)
                MessageBox.Show(string.Format("Error during print: {0}", error.Message));

            IsPrinting = false;
            IsCancelingPrint = false;
        }

        private string CopyrightText(Layer lyr)
        {
            if ((lyr is IQueryCopyright) && (PrintMapView != null))
            {
                return ((IQueryCopyright)lyr).QueryCopyright(PrintMapView.Extent, PrintMapView.Scale);
            }
            else if (lyr is ICopyright)
            {
                return ((ICopyright)lyr).CopyrightText;
            }
            else
                return null;
        }
    }
}
Feedback on this topic?