├── .gitattributes ├── .gitignore ├── Example ├── App.config ├── App.xaml ├── App.xaml.cs ├── Assets │ └── pinterest_board_photo.png ├── Invoice.csproj ├── MainWindow.xaml ├── MainWindow.xaml.cs ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ ├── Resources.resx │ ├── Settings.Designer.cs │ └── Settings.settings └── packages.config ├── Files ├── input.png ├── output.pdf ├── output1.png └── output2.png ├── LICENSE.md ├── README.md ├── SimpleWPFReporting.sln └── SimpleWPFReporting ├── Properties └── AssemblyInfo.cs ├── Report.cs ├── ReportOrientation.cs ├── ReportPage.cs ├── SimpleWPFReporting.csproj └── packages.config /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | [Xx]64/ 19 | [Xx]86/ 20 | [Bb]uild/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | 85 | # Visual Studio profiler 86 | *.psess 87 | *.vsp 88 | *.vspx 89 | *.sap 90 | 91 | # TFS 2012 Local Workspace 92 | $tf/ 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | *.DotSettings.user 101 | 102 | # JustCode is a .NET coding add-in 103 | .JustCode 104 | 105 | # TeamCity is a build add-in 106 | _TeamCity* 107 | 108 | # DotCover is a Code Coverage Tool 109 | *.dotCover 110 | 111 | # NCrunch 112 | _NCrunch_* 113 | .*crunch*.local.xml 114 | nCrunchTemp_* 115 | 116 | # MightyMoose 117 | *.mm.* 118 | AutoTest.Net/ 119 | 120 | # Web workbench (sass) 121 | .sass-cache/ 122 | 123 | # Installshield output folder 124 | [Ee]xpress/ 125 | 126 | # DocProject is a documentation generator add-in 127 | DocProject/buildhelp/ 128 | DocProject/Help/*.HxT 129 | DocProject/Help/*.HxC 130 | DocProject/Help/*.hhc 131 | DocProject/Help/*.hhk 132 | DocProject/Help/*.hhp 133 | DocProject/Help/Html2 134 | DocProject/Help/html 135 | 136 | # Click-Once directory 137 | publish/ 138 | 139 | # Publish Web Output 140 | *.[Pp]ublish.xml 141 | *.azurePubxml 142 | 143 | # TODO: Un-comment the next line if you do not want to checkin 144 | # your web deploy settings because they may include unencrypted 145 | # passwords 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # NuGet Packages 150 | *.nupkg 151 | # The packages folder can be ignored because of Package Restore 152 | **/packages/* 153 | # except build/, which is used as an MSBuild target. 154 | !**/packages/build/ 155 | # Uncomment if necessary however generally it will be regenerated when needed 156 | #!**/packages/repositories.config 157 | # NuGet v3's project.json files produces more ignoreable files 158 | *.nuget.props 159 | *.nuget.targets 160 | 161 | # Microsoft Azure Build Output 162 | csx/ 163 | *.build.csdef 164 | 165 | # Microsoft Azure Emulator 166 | ecf/ 167 | rcf/ 168 | 169 | # Windows Store app package directory 170 | AppPackages/ 171 | BundleArtifacts/ 172 | 173 | # Visual Studio cache files 174 | # files ending in .cache can be ignored 175 | *.[Cc]ache 176 | # but keep track of directories ending in .cache 177 | !*.[Cc]ache/ 178 | 179 | # Others 180 | ClientBin/ 181 | [Ss]tyle[Cc]op.* 182 | ~$* 183 | *~ 184 | *.dbmdl 185 | *.dbproj.schemaview 186 | *.pfx 187 | *.publishsettings 188 | node_modules/ 189 | orleans.codegen.cs 190 | 191 | # RIA/Silverlight projects 192 | Generated_Code/ 193 | 194 | # Backup & report files from converting an old project file 195 | # to a newer Visual Studio version. Backup files are not needed, 196 | # because we have git ;-) 197 | _UpgradeReport_Files/ 198 | Backup*/ 199 | UpgradeLog*.XML 200 | UpgradeLog*.htm 201 | 202 | # SQL Server files 203 | *.mdf 204 | *.ldf 205 | 206 | # Business Intelligence projects 207 | *.rdl.data 208 | *.bim.layout 209 | *.bim_*.settings 210 | 211 | # Microsoft Fakes 212 | FakesAssemblies/ 213 | 214 | # GhostDoc plugin setting file 215 | *.GhostDoc.xml 216 | 217 | # Node.js Tools for Visual Studio 218 | .ntvs_analysis.dat 219 | 220 | # Visual Studio 6 build log 221 | *.plg 222 | 223 | # Visual Studio 6 workspace options file 224 | *.opt 225 | 226 | # Visual Studio LightSwitch build output 227 | **/*.HTMLClient/GeneratedArtifacts 228 | **/*.DesktopClient/GeneratedArtifacts 229 | **/*.DesktopClient/ModelManifest.xml 230 | **/*.Server/GeneratedArtifacts 231 | **/*.Server/ModelManifest.xml 232 | _Pvt_Extensions 233 | 234 | # LightSwitch generated files 235 | GeneratedArtifacts/ 236 | ModelManifest.xml 237 | 238 | # Paket dependency manager 239 | .paket/paket.exe 240 | 241 | # FAKE - F# Make 242 | .fake/ 243 | -------------------------------------------------------------------------------- /Example/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Example/App.xaml: -------------------------------------------------------------------------------- 1 |  6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Example/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.Data; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using System.Windows; 8 | 9 | namespace Invoice 10 | { 11 | /// 12 | /// Interaction logic for App.xaml 13 | /// 14 | public partial class App : Application 15 | { 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Example/Assets/pinterest_board_photo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maximcus/SimpleWPFReporting/3c9bd334f9c210f0caf5549a6309894033226e5a/Example/Assets/pinterest_board_photo.png -------------------------------------------------------------------------------- /Example/Invoice.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {DA07A6F7-D067-4E08-9ACB-621672357E2E} 8 | WinExe 9 | Invoice 10 | Invoice 11 | v4.6 12 | 512 13 | {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 14 | 4 15 | true 16 | true 17 | 18 | 19 | AnyCPU 20 | true 21 | full 22 | false 23 | bin\Debug\ 24 | DEBUG;TRACE 25 | prompt 26 | 4 27 | 28 | 29 | AnyCPU 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | 37 | 38 | 39 | ..\packages\MaterialDesignColors.1.1.2\lib\net45\MaterialDesignColors.dll 40 | 41 | 42 | ..\packages\MaterialDesignThemes.2.5.1\lib\net45\MaterialDesignThemes.Wpf.dll 43 | 44 | 45 | ..\packages\PdfSharp-WPF.SimpleWPFReporting.1.0.0\lib\net45\PdfSharp-WPF.dll 46 | 47 | 48 | ..\packages\PdfSharp.Xps.SimpleWPFReporting.1.0.0\lib\net45\PdfSharp.Xps.dll 49 | 50 | 51 | ..\packages\SimpleWPFReporting.1.1.0\lib\net45\SimpleWPFReporting.dll 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 4.0 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | MSBuild:Compile 71 | Designer 72 | 73 | 74 | MSBuild:Compile 75 | Designer 76 | 77 | 78 | App.xaml 79 | Code 80 | 81 | 82 | MainWindow.xaml 83 | Code 84 | 85 | 86 | 87 | 88 | Code 89 | 90 | 91 | True 92 | True 93 | Resources.resx 94 | 95 | 96 | True 97 | Settings.settings 98 | True 99 | 100 | 101 | ResXFileCodeGenerator 102 | Resources.Designer.cs 103 | 104 | 105 | 106 | SettingsSingleFileGenerator 107 | Settings.Designer.cs 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /Example/MainWindow.xaml: -------------------------------------------------------------------------------- 1 |  10 | 11 | 12 | 13 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | -------------------------------------------------------------------------------- /Example/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Windows; 7 | using System.Windows.Controls; 8 | using System.Windows.Data; 9 | using System.Windows.Documents; 10 | using System.Windows.Input; 11 | using System.Windows.Media; 12 | using System.Windows.Media.Imaging; 13 | using System.Windows.Navigation; 14 | using System.Windows.Shapes; 15 | using SimpleWPFReporting; 16 | 17 | namespace Invoice 18 | { 19 | /// 20 | /// Interaction logic for MainWindow.xaml 21 | /// 22 | public partial class MainWindow : Window 23 | { 24 | public MainWindow() 25 | { 26 | InitializeComponent(); 27 | } 28 | 29 | private void Button_Click(object sender, RoutedEventArgs e) 30 | { 31 | try 32 | { 33 | Report.ExportVisualAsPdf(print); 34 | 35 | } 36 | finally 37 | { 38 | this.IsEnabled = true; 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Example/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Resources; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | using System.Windows; 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [assembly: AssemblyTitle("Invoice")] 11 | [assembly: AssemblyDescription("")] 12 | [assembly: AssemblyConfiguration("")] 13 | [assembly: AssemblyCompany("")] 14 | [assembly: AssemblyProduct("Invoice")] 15 | [assembly: AssemblyCopyright("Copyright © 2019")] 16 | [assembly: AssemblyTrademark("")] 17 | [assembly: AssemblyCulture("")] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [assembly: ComVisible(false)] 23 | 24 | //In order to begin building localizable applications, set 25 | //CultureYouAreCodingWith in your .csproj file 26 | //inside a . For example, if you are using US english 27 | //in your source files, set the to en-US. Then uncomment 28 | //the NeutralResourceLanguage attribute below. Update the "en-US" in 29 | //the line below to match the UICulture setting in the project file. 30 | 31 | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] 32 | 33 | 34 | [assembly: ThemeInfo( 35 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 36 | //(used if a resource is not found in the page, 37 | // or application resource dictionaries) 38 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 39 | //(used if a resource is not found in the page, 40 | // app, or any theme specific resource dictionaries) 41 | )] 42 | 43 | 44 | // Version information for an assembly consists of the following four values: 45 | // 46 | // Major Version 47 | // Minor Version 48 | // Build Number 49 | // Revision 50 | // 51 | // You can specify all the values or you can default the Build and Revision Numbers 52 | // by using the '*' as shown below: 53 | // [assembly: AssemblyVersion("1.0.*")] 54 | [assembly: AssemblyVersion("1.0.0.0")] 55 | [assembly: AssemblyFileVersion("1.0.0.0")] 56 | -------------------------------------------------------------------------------- /Example/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Invoice.Properties 12 | { 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources 26 | { 27 | 28 | private static global::System.Resources.ResourceManager resourceMan; 29 | 30 | private static global::System.Globalization.CultureInfo resourceCulture; 31 | 32 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 33 | internal Resources() 34 | { 35 | } 36 | 37 | /// 38 | /// Returns the cached ResourceManager instance used by this class. 39 | /// 40 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 41 | internal static global::System.Resources.ResourceManager ResourceManager 42 | { 43 | get 44 | { 45 | if ((resourceMan == null)) 46 | { 47 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Invoice.Properties.Resources", typeof(Resources).Assembly); 48 | resourceMan = temp; 49 | } 50 | return resourceMan; 51 | } 52 | } 53 | 54 | /// 55 | /// Overrides the current thread's CurrentUICulture property for all 56 | /// resource lookups using this strongly typed resource class. 57 | /// 58 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 59 | internal static global::System.Globalization.CultureInfo Culture 60 | { 61 | get 62 | { 63 | return resourceCulture; 64 | } 65 | set 66 | { 67 | resourceCulture = value; 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Example/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | text/microsoft-resx 107 | 108 | 109 | 2.0 110 | 111 | 112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 113 | 114 | 115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | -------------------------------------------------------------------------------- /Example/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Invoice.Properties 12 | { 13 | 14 | 15 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 16 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] 17 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase 18 | { 19 | 20 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 21 | 22 | public static Settings Default 23 | { 24 | get 25 | { 26 | return defaultInstance; 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Example/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /Example/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Files/input.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maximcus/SimpleWPFReporting/3c9bd334f9c210f0caf5549a6309894033226e5a/Files/input.png -------------------------------------------------------------------------------- /Files/output.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maximcus/SimpleWPFReporting/3c9bd334f9c210f0caf5549a6309894033226e5a/Files/output.pdf -------------------------------------------------------------------------------- /Files/output1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maximcus/SimpleWPFReporting/3c9bd334f9c210f0caf5549a6309894033226e5a/Files/output1.png -------------------------------------------------------------------------------- /Files/output2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maximcus/SimpleWPFReporting/3c9bd334f9c210f0caf5549a6309894033226e5a/Files/output2.png -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Maxim Fedotov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SimpleWPFReporting 2 | 3 | Reporting in WPF (XAML) made easy 4 | 5 | ## How to get it 6 | 7 | SimpleWPFReporting package is available on [NuGet](https://www.nuget.org/packages/SimpleWPFReporting/) 8 | 9 | You can use Package Manager Console to get it: 10 | 11 | ~~~powershell 12 | PM> Install-Package SimpleWPFReporting 13 | ~~~ 14 | 15 | ## How does it work 16 | 17 | This project gives you the ability to create any report with the full power of WPF XAML. SimpleWPFReporting will take care of exporting it as a PDF or printing it. The important point is that SimpleWPFReporting will not rasterize your report before exporting or printing it. 18 | 19 | In order to be able to divide any report into pages this project expects that you will use a vertical `StackPanel` as your report body. You should put your report elements into this `StackPanel`. SimpleWPFReporting accepts `StackPanel` as a parameter and automatically divides report elements into pages. 20 | 21 | ## How to use it 22 | 23 | The main API of SimpleWPFReporting consists of `Report` static class, `ExportReportAsPdf` method, and `PrintReport` method. Both of the methods accept the same arguments. 24 | 25 | ### Required arguments 26 | 27 | `StackPanel reportContainer` is your report body containing report elements. 28 | 29 | `object dataContext` is the data context of your report. 30 | 31 | `Thickness margin` is the margin applied to every report page. Both of the methods have overloads without the margin argument. They use `Thickness(25)` as a default margin. 32 | 33 | `ReportOrientation orientation` is the orientation (`Portrait` or `Landscape`) of your report. 34 | 35 | ### Optional arguments 36 | 37 | `ResourceDictionary resourceDictionary` provides all the resources used in the report. 38 | 39 | `Brush backgroundBrush` is the background brush of the report. 40 | 41 | `DataTemplate reportHeaderDataTemplate` is the `DataTemplate` used to create a header for every page of the report. 42 | 43 | `bool headerOnlyOnTheFirstPage` allows you to use a header only on the first page of the report. (default is false) 44 | 45 | `DataTemplate reportFooterDataTemplate` is the `DataTemplate` used to create a footer for every page of the report. 46 | 47 | `bool footerStartsFromTheSecondPage` allows you to not use a footer on the first page of the report. (default is false) 48 | 49 | ### Page number 50 | 51 | Every header and footer will be supplied with the `PageNumber` dynamic resource. You can use it as you wish. For example, this is the simplest possible footer DataTemplate: 52 | 53 | ```XML 54 | 55 | 56 | 57 | ``` 58 | 59 | ## Example 60 | 61 | ### Report view: 62 | ![Alt text](Files/input.png "Report View") 63 | 64 | ### Output PDF: 65 | ![Alt text](Files/output1.png "Output PDF page 1") 66 | ![Alt text](Files/output2.png "Output PDF page 2") 67 | 68 | As you can see there is no difference between input (XAML) and output (PDF) except for paging. The subtle difference in the font weight and the width of the lines is due to artifacts of scaling of the screenshots. 69 | PDF is not rasterized, as you can see for yourself: [pdf](Files/output.pdf). 70 | -------------------------------------------------------------------------------- /SimpleWPFReporting.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29728.190 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimpleWPFReporting", "SimpleWPFReporting\SimpleWPFReporting.csproj", "{9846B28B-D22B-4B99-BFC1-420535730CE4}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{1E03FFEC-44A5-4DF1-8CD6-7A5FBFECE9BA}" 9 | ProjectSection(SolutionItems) = preProject 10 | LICENSE.md = LICENSE.md 11 | README.md = README.md 12 | EndProjectSection 13 | EndProject 14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Files", "Files", "{0E44BB82-F259-4F13-BA10-AB0A86A689B9}" 15 | ProjectSection(SolutionItems) = preProject 16 | Files\output.pdf = Files\output.pdf 17 | EndProjectSection 18 | EndProject 19 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Images", "Images", "{AD970D11-F40F-43A0-BA1A-4BE4B38D45B2}" 20 | ProjectSection(SolutionItems) = preProject 21 | Files\input.png = Files\input.png 22 | Files\output1.png = Files\output1.png 23 | Files\output2.png = Files\output2.png 24 | EndProjectSection 25 | EndProject 26 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Example", "Example", "{D89D75DC-4007-4F15-B1DA-F91E6C72CF72}" 27 | EndProject 28 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Invoice", "Example\Invoice.csproj", "{DA07A6F7-D067-4E08-9ACB-621672357E2E}" 29 | EndProject 30 | Global 31 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 32 | Debug|Any CPU = Debug|Any CPU 33 | Release|Any CPU = Release|Any CPU 34 | EndGlobalSection 35 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 36 | {9846B28B-D22B-4B99-BFC1-420535730CE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {9846B28B-D22B-4B99-BFC1-420535730CE4}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {9846B28B-D22B-4B99-BFC1-420535730CE4}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {9846B28B-D22B-4B99-BFC1-420535730CE4}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {DA07A6F7-D067-4E08-9ACB-621672357E2E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {DA07A6F7-D067-4E08-9ACB-621672357E2E}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {DA07A6F7-D067-4E08-9ACB-621672357E2E}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {DA07A6F7-D067-4E08-9ACB-621672357E2E}.Release|Any CPU.Build.0 = Release|Any CPU 44 | EndGlobalSection 45 | GlobalSection(SolutionProperties) = preSolution 46 | HideSolutionNode = FALSE 47 | EndGlobalSection 48 | GlobalSection(NestedProjects) = preSolution 49 | {AD970D11-F40F-43A0-BA1A-4BE4B38D45B2} = {0E44BB82-F259-4F13-BA10-AB0A86A689B9} 50 | {DA07A6F7-D067-4E08-9ACB-621672357E2E} = {D89D75DC-4007-4F15-B1DA-F91E6C72CF72} 51 | EndGlobalSection 52 | GlobalSection(ExtensibilityGlobals) = postSolution 53 | SolutionGuid = {C14426A7-1436-4D4D-8AC9-6D9D4F6DC212} 54 | EndGlobalSection 55 | EndGlobal 56 | -------------------------------------------------------------------------------- /SimpleWPFReporting/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("SimpleWPFReporting")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("SimpleWPFReporting")] 13 | [assembly: AssemblyCopyright("Copyright © 2017")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("9846b28b-d22b-4b99-bfc1-420535730ce4")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /SimpleWPFReporting/Report.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Printing; 6 | using System.Reflection; 7 | using System.Windows; 8 | using System.Windows.Controls; 9 | using System.Windows.Documents; 10 | using System.Windows.Markup; 11 | using System.Windows.Media; 12 | using System.Windows.Media.Imaging; 13 | using System.Windows.Xps; 14 | using System.Windows.Xps.Packaging; 15 | using Microsoft.Win32; 16 | using PdfSharp.Xps; 17 | 18 | #pragma warning disable 1591 19 | namespace SimpleWPFReporting 20 | { 21 | public static class Report 22 | { 23 | const int DIUPerInch = 96; 24 | private static readonly Thickness defaultMargin = new Thickness(25); 25 | 26 | public static void ExportVisualAsXps(Visual visual) 27 | { 28 | SaveFileDialog saveFileDialog = new SaveFileDialog 29 | { 30 | DefaultExt = ".xps", 31 | Filter = "XPS Documents (.xps)|*.xps" 32 | }; 33 | 34 | bool? result = saveFileDialog.ShowDialog(); 35 | 36 | if (result != true) return; 37 | 38 | XpsDocument xpsDocument = new XpsDocument(saveFileDialog.FileName, FileAccess.Write); 39 | XpsDocumentWriter xpsDocumentWriter = XpsDocument.CreateXpsDocumentWriter(xpsDocument); 40 | 41 | xpsDocumentWriter.Write(visual); 42 | xpsDocument.Close(); 43 | } 44 | 45 | public static void ExportVisualAsPdf(Visual visual) 46 | { 47 | SaveFileDialog saveFileDialog = new SaveFileDialog 48 | { 49 | DefaultExt = ".pdf", 50 | Filter = "PDF Documents (.pdf)|*.pdf" 51 | }; 52 | 53 | bool? result = saveFileDialog.ShowDialog(); 54 | 55 | if (result != true) return; 56 | 57 | using (MemoryStream memoryStream = new MemoryStream()) 58 | { 59 | System.IO.Packaging.Package package = System.IO.Packaging.Package.Open(memoryStream, FileMode.Create); 60 | XpsDocument xpsDocument = new XpsDocument(package); 61 | XpsDocumentWriter xpsDocumentWriter = XpsDocument.CreateXpsDocumentWriter(xpsDocument); 62 | 63 | xpsDocumentWriter.Write(visual); 64 | xpsDocument.Close(); 65 | package.Close(); 66 | 67 | var pdfXpsDoc = PdfSharp.Xps.XpsModel.XpsDocument.Open(memoryStream); 68 | XpsConverter.Convert(pdfXpsDoc, saveFileDialog.FileName, 0); 69 | } 70 | } 71 | 72 | private static readonly Lazy dpiX = new Lazy(() => 73 | { 74 | PropertyInfo dpiXProperty = typeof(SystemParameters).GetProperty("DpiX", BindingFlags.NonPublic | BindingFlags.Static); 75 | return (int)dpiXProperty.GetValue(null, null); 76 | }); 77 | 78 | private static readonly Lazy dpiY = new Lazy(() => 79 | { 80 | var dpiYProperty = typeof(SystemParameters).GetProperty("Dpi", BindingFlags.NonPublic | BindingFlags.Static); 81 | return (int)dpiYProperty.GetValue(null, null); 82 | }); 83 | 84 | private static void SaveFrameworkElementAsImage(FrameworkElement element, string filePath, BitmapEncoder bitmapEncoder) 85 | { 86 | RenderTargetBitmap bitmap = new RenderTargetBitmap( 87 | pixelWidth: Convert.ToInt32((element.ActualWidth / DIUPerInch) * dpiX.Value), 88 | pixelHeight: Convert.ToInt32((element.ActualHeight / DIUPerInch) * dpiY.Value), 89 | dpiX: dpiX.Value, 90 | dpiY: dpiY.Value, 91 | pixelFormat: PixelFormats.Pbgra32); 92 | bitmap.Render(element); 93 | 94 | bitmapEncoder.Frames.Add(BitmapFrame.Create(bitmap)); 95 | using (Stream fs = File.Create(filePath)) 96 | { 97 | bitmapEncoder.Save(fs); 98 | } 99 | } 100 | 101 | public static void ExportFrameworkElementAsJpg(FrameworkElement element) 102 | { 103 | SaveFileDialog saveFileDialog = new SaveFileDialog 104 | { 105 | DefaultExt = ".jpg", 106 | Filter = "JPG Images (.jpg)|*.jpg" 107 | }; 108 | 109 | bool? result = saveFileDialog.ShowDialog(); 110 | 111 | if (result != true) return; 112 | 113 | SaveFrameworkElementAsImage(element, saveFileDialog.FileName, new JpegBitmapEncoder {QualityLevel = 100}); 114 | } 115 | 116 | public static void ExportFrameworkElementAsBmp(FrameworkElement element) 117 | { 118 | SaveFileDialog saveFileDialog = new SaveFileDialog 119 | { 120 | DefaultExt = ".bmp", 121 | Filter = "BMP Images (.bmp)|*.bmp" 122 | }; 123 | 124 | bool? result = saveFileDialog.ShowDialog(); 125 | 126 | if (result != true) return; 127 | 128 | SaveFrameworkElementAsImage(element, saveFileDialog.FileName, new BmpBitmapEncoder()); 129 | } 130 | 131 | public static void ExportFrameworkElementAsPng(FrameworkElement element) 132 | { 133 | SaveFileDialog saveFileDialog = new SaveFileDialog 134 | { 135 | DefaultExt = ".png", 136 | Filter = "PNG Images (.png)|*.png" 137 | }; 138 | 139 | bool? result = saveFileDialog.ShowDialog(); 140 | 141 | if (result != true) return; 142 | 143 | SaveFrameworkElementAsImage(element, saveFileDialog.FileName, new PngBitmapEncoder()); 144 | } 145 | 146 | /// 147 | /// Divides elements of reportContainer into pages and prints them 148 | /// 149 | /// StackPanel containing report elements 150 | /// Data Context used in the report 151 | /// Margin of a report page 152 | /// Landscape or Portrait orientation 153 | /// Resources used in report 154 | /// Brush that will be used as background for report page 155 | /// 156 | /// Optional header for each page 157 | /// Note: You can use DynamicResource PageNumber in this template to display page number 158 | /// 159 | /// Use header only on the first page (default is false) 160 | /// 161 | /// Optional footer for each page 162 | /// Note: You can use DynamicResource PageNumber in this template to display page number 163 | /// 164 | /// Do not use footer on the first page (default is false) 165 | public static void PrintReport( 166 | StackPanel reportContainer, 167 | object dataContext, 168 | Thickness margin, 169 | ReportOrientation orientation, 170 | ResourceDictionary resourceDictionary = null, 171 | Brush backgroundBrush = null, 172 | DataTemplate reportHeaderDataTemplate = null, 173 | bool headerOnlyOnTheFirstPage = false, 174 | DataTemplate reportFooterDataTemplate = null, 175 | bool footerStartsFromTheSecondPage = false) 176 | { 177 | PrintDialog printDialog = new PrintDialog(); 178 | 179 | bool? result = printDialog.ShowDialog(); 180 | 181 | if (result != true) return; 182 | 183 | Size reportSize = GetReportSize(reportContainer, margin, orientation, printDialog); 184 | 185 | List ReportElements = new List(reportContainer.Children.Cast()); 186 | reportContainer.Children.Clear(); //to avoid exception "Specified element is already the logical child of another element." 187 | 188 | List ReportPages = 189 | GetReportPages( 190 | resourceDictionary, 191 | backgroundBrush, 192 | ReportElements, 193 | dataContext, 194 | margin, 195 | reportSize, 196 | reportHeaderDataTemplate, 197 | headerOnlyOnTheFirstPage, 198 | reportFooterDataTemplate, 199 | footerStartsFromTheSecondPage); 200 | 201 | try 202 | { 203 | ReportPages.ForEach(reportPage => reportPage.Scale(reportSize, printDialog)); 204 | ReportPages.ForEach((reportPage, index) => printDialog.PrintVisual(reportPage.LayoutRoot, $"Карточка Точки {index + 1}")); 205 | } 206 | finally 207 | { 208 | ReportPages.ForEach(reportPage => reportPage.ClearChildren()); 209 | ReportElements.ForEach(elm => reportContainer.Children.Add(elm)); 210 | reportContainer.UpdateLayout(); 211 | } 212 | } 213 | 214 | /// 215 | /// Divides elements of reportContainer into pages and prints them 216 | /// NOTE: this overload uses default margin of 25 217 | /// 218 | /// StackPanel containing report elements 219 | /// Data Context used in the report 220 | /// Landscape or Portrait orientation 221 | /// Resources used in report 222 | /// Brush that will be used as background for report page 223 | /// 224 | /// Optional header for each page 225 | /// Note: You can use DynamicResource PageNumber in this template to display page number 226 | /// 227 | /// Use header only on the first page (default is false) 228 | /// 229 | /// Optional footer for each page 230 | /// Note: You can use DynamicResource PageNumber in this template to display page number 231 | /// 232 | /// Do not use footer on the first page (default is false) 233 | public static void PrintReport( 234 | StackPanel reportContainer, 235 | object dataContext, 236 | ReportOrientation orientation, 237 | ResourceDictionary resourceDictionary = null, 238 | Brush backgroundBrush = null, 239 | DataTemplate reportHeaderDataTemplate = null, 240 | bool headerOnlyOnTheFirstPage = false, 241 | DataTemplate reportFooterDataTemplate = null, 242 | bool footerStartsFromTheSecondPage = false) 243 | { 244 | PrintReport( 245 | reportContainer, 246 | dataContext, 247 | defaultMargin, 248 | orientation, 249 | resourceDictionary, 250 | backgroundBrush, 251 | reportHeaderDataTemplate, 252 | headerOnlyOnTheFirstPage, 253 | reportFooterDataTemplate, 254 | footerStartsFromTheSecondPage); 255 | } 256 | 257 | /// 258 | /// Divides elements of reportContainer into pages and exports them as PDF 259 | /// 260 | /// StackPanel containing report elements 261 | /// Data Context used in the report 262 | /// Margin of a report page 263 | /// Landscape or Portrait orientation 264 | /// Resources used in report 265 | /// Brush that will be used as background for report page 266 | /// 267 | /// Optional header for each page 268 | /// Note: You can use DynamicResource PageNumber in this template to display page number 269 | /// 270 | /// Use header only on the first page (default is false) 271 | /// 272 | /// Optional footer for each page 273 | /// Note: You can use DynamicResource PageNumber in this template to display page number 274 | /// 275 | /// Do not use footer on the first page (default is false) 276 | public static void ExportReportAsPdf( 277 | StackPanel reportContainer, 278 | object dataContext, 279 | Thickness margin, 280 | ReportOrientation orientation, 281 | ResourceDictionary resourceDictionary = null, 282 | Brush backgroundBrush = null, 283 | DataTemplate reportHeaderDataTemplate = null, 284 | bool headerOnlyOnTheFirstPage = false, 285 | DataTemplate reportFooterDataTemplate = null, 286 | bool footerStartsFromTheSecondPage = false) 287 | { 288 | SaveFileDialog saveFileDialog = new SaveFileDialog 289 | { 290 | DefaultExt = ".pdf", 291 | Filter = "PDF Documents (.pdf)|*.pdf" 292 | }; 293 | 294 | bool? result = saveFileDialog.ShowDialog(); 295 | 296 | if (result != true) return; 297 | 298 | Size reportSize = GetReportSize(reportContainer, margin, orientation); 299 | 300 | List ReportElements = new List(reportContainer.Children.Cast()); 301 | reportContainer.Children.Clear(); //to avoid exception "Specified element is already the logical child of another element." 302 | 303 | List ReportPages = 304 | GetReportPages( 305 | resourceDictionary, 306 | backgroundBrush, 307 | ReportElements, 308 | dataContext, 309 | margin, 310 | reportSize, 311 | reportHeaderDataTemplate, 312 | headerOnlyOnTheFirstPage, 313 | reportFooterDataTemplate, 314 | footerStartsFromTheSecondPage); 315 | 316 | FixedDocument fixedDocument = new FixedDocument(); 317 | 318 | try 319 | { 320 | using (MemoryStream memoryStream = new MemoryStream()) 321 | { 322 | System.IO.Packaging.Package package = System.IO.Packaging.Package.Open(memoryStream, FileMode.Create); 323 | XpsDocument xpsDocument = new XpsDocument(package); 324 | XpsDocumentWriter xpsDocumentWriter = XpsDocument.CreateXpsDocumentWriter(xpsDocument); 325 | 326 | foreach (Grid reportPage in ReportPages.Select(reportPage => reportPage.LayoutRoot)) 327 | { 328 | reportPage.Width = reportPage.ActualWidth; 329 | reportPage.Height = reportPage.ActualHeight; 330 | 331 | FixedPage newFixedPage = new FixedPage(); 332 | newFixedPage.Children.Add(reportPage); 333 | newFixedPage.Measure(reportSize); 334 | newFixedPage.Arrange(new Rect(reportSize)); 335 | newFixedPage.Width = newFixedPage.ActualWidth; 336 | newFixedPage.Height = newFixedPage.ActualHeight; 337 | newFixedPage.Background = backgroundBrush; 338 | newFixedPage.UpdateLayout(); 339 | 340 | PageContent pageContent = new PageContent(); 341 | ((IAddChild)pageContent).AddChild(newFixedPage); 342 | 343 | fixedDocument.Pages.Add(pageContent); 344 | } 345 | 346 | xpsDocumentWriter.Write(fixedDocument); 347 | xpsDocument.Close(); 348 | package.Close(); 349 | 350 | var pdfXpsDoc = PdfSharp.Xps.XpsModel.XpsDocument.Open(memoryStream); 351 | XpsConverter.Convert(pdfXpsDoc, saveFileDialog.FileName, 0); 352 | } 353 | } 354 | finally 355 | { 356 | ReportPages.ForEach(reportPage => reportPage.ClearChildren()); 357 | ReportElements.ForEach(elm => reportContainer.Children.Add(elm)); 358 | reportContainer.UpdateLayout(); 359 | } 360 | } 361 | 362 | /// 363 | /// Divides elements of reportContainer into pages and exports them as PDF 364 | /// NOTE: this overload uses default margin of 25 365 | /// 366 | /// StackPanel containing report elements 367 | /// Data Context used in the report 368 | /// Landscape or Portrait orientation 369 | /// Resources used in report 370 | /// Brush that will be used as background for report page 371 | /// 372 | /// Optional header for each page 373 | /// Note: You can use DynamicResource PageNumber in this template to display page number 374 | /// 375 | /// Use header only on the first page (default is false) 376 | /// 377 | /// Optional footer for each page 378 | /// Note: You can use DynamicResource PageNumber in this template to display page number 379 | /// 380 | /// Do not use footer on the first page (default is false) 381 | public static void ExportReportAsPdf( 382 | StackPanel reportContainer, 383 | object dataContext, 384 | ReportOrientation orientation, 385 | ResourceDictionary resourceDictionary = null, 386 | Brush backgroundBrush = null, 387 | DataTemplate reportHeaderDataTemplate = null, 388 | bool headerOnlyOnTheFirstPage = false, 389 | DataTemplate reportFooterDataTemplate = null, 390 | bool footerStartsFromTheSecondPage = false) 391 | { 392 | ExportReportAsPdf( 393 | reportContainer, 394 | dataContext, 395 | defaultMargin, 396 | orientation, 397 | resourceDictionary, 398 | backgroundBrush, 399 | reportHeaderDataTemplate, 400 | headerOnlyOnTheFirstPage, 401 | reportFooterDataTemplate, 402 | footerStartsFromTheSecondPage); 403 | } 404 | 405 | private static List GetReportPages( 406 | ResourceDictionary resourceDictionary, 407 | Brush backgroundBrush, 408 | List ReportElements, 409 | object dataContext, 410 | Thickness margin, 411 | Size reportSize, 412 | DataTemplate reportHeaderDataTemplate, 413 | bool headerOnlyOnTheFirstPage, 414 | DataTemplate reportFooterDataTemplate, 415 | bool footerStartsFromTheSecondPage) 416 | { 417 | int pageNumber = 1; 418 | 419 | List ReportPages = 420 | new List 421 | { 422 | new ReportPage( 423 | reportSize, 424 | backgroundBrush, 425 | margin, 426 | dataContext, 427 | resourceDictionary, 428 | reportHeaderDataTemplate, 429 | (footerStartsFromTheSecondPage) ? null : reportFooterDataTemplate, 430 | pageNumber) 431 | }; 432 | 433 | foreach (FrameworkElement reportVisualElement in ReportElements) 434 | { 435 | if (ReportPages.Last().GetChildrenActualHeight() + GetActualHeightPlusMargin(reportVisualElement) > reportSize.Height - margin.Top - margin.Bottom) 436 | { 437 | pageNumber++; 438 | 439 | ReportPages.Add( 440 | new ReportPage( 441 | reportSize, 442 | backgroundBrush, 443 | margin, 444 | dataContext, 445 | resourceDictionary, 446 | (headerOnlyOnTheFirstPage) ? null : reportHeaderDataTemplate, 447 | reportFooterDataTemplate, 448 | pageNumber)); 449 | } 450 | 451 | ReportPages.Last().AddElement(reportVisualElement); 452 | } 453 | 454 | foreach (ReportPage reportPage in ReportPages) 455 | { 456 | reportPage.LayoutRoot.Measure(reportSize); 457 | reportPage.LayoutRoot.Arrange(new Rect(reportSize)); 458 | reportPage.LayoutRoot.UpdateLayout(); 459 | } 460 | 461 | return ReportPages; 462 | } 463 | 464 | private static double GetActualHeightPlusMargin(FrameworkElement elm) 465 | { 466 | return elm.ActualHeight + elm.Margin.Top + elm.Margin.Bottom; 467 | } 468 | 469 | private static Size GetReportSize(StackPanel reportContainer, Thickness margin, ReportOrientation orientation, PrintDialog printDialog = null) 470 | { 471 | if (printDialog == null) 472 | printDialog = new PrintDialog(); 473 | 474 | double reportWidth = reportContainer.ActualWidth + margin.Left + margin.Right; 475 | 476 | if (orientation == ReportOrientation.Landscape) 477 | printDialog.PrintTicket.PageOrientation = PageOrientation.Landscape; 478 | 479 | double reportHeight = (reportWidth / printDialog.PrintableAreaWidth) * printDialog.PrintableAreaHeight; 480 | 481 | return new Size(reportWidth, reportHeight); 482 | } 483 | 484 | private static void ForEach(this IEnumerable enumeration, Action action) 485 | { 486 | if (enumeration == null) 487 | return; 488 | 489 | int index = 0; 490 | foreach (T item in enumeration) 491 | { 492 | action(item, index); 493 | index++; 494 | } 495 | } 496 | } 497 | } 498 | #pragma warning restore 1591 499 | 500 | 501 | -------------------------------------------------------------------------------- /SimpleWPFReporting/ReportOrientation.cs: -------------------------------------------------------------------------------- 1 | namespace SimpleWPFReporting 2 | { 3 | #pragma warning disable 1591 4 | public enum ReportOrientation 5 | { 6 | Portrait, 7 | Landscape 8 | } 9 | 10 | #pragma warning restore 1591 11 | } 12 | -------------------------------------------------------------------------------- /SimpleWPFReporting/ReportPage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Windows; 4 | using System.Windows.Controls; 5 | using System.Windows.Media; 6 | 7 | namespace SimpleWPFReporting 8 | { 9 | internal class ReportPage 10 | { 11 | private readonly Grid layoutRootGrid; 12 | private readonly StackPanel stackPanel = new StackPanel(); 13 | private readonly FrameworkElement reportHeader; 14 | private readonly FrameworkElement reportFooter; 15 | 16 | internal ReportPage( 17 | Size reportSize, 18 | Brush backgroundBrush, 19 | Thickness margin, 20 | object dataContext, 21 | ResourceDictionary resourceDictionary, 22 | DataTemplate reportHeaderDataTemplate, 23 | DataTemplate reportFooterDataTemplate, 24 | int pageNumber) 25 | { 26 | layoutRootGrid = new Grid 27 | { 28 | Margin = margin, 29 | Background = backgroundBrush, 30 | Resources = resourceDictionary, 31 | DataContext = dataContext, 32 | HorizontalAlignment = HorizontalAlignment.Stretch, 33 | VerticalAlignment = VerticalAlignment.Stretch 34 | }; 35 | 36 | 37 | if (reportHeaderDataTemplate != null) 38 | { 39 | reportHeader = reportHeaderDataTemplate.LoadContent() as FrameworkElement; 40 | 41 | if (reportHeader == null) 42 | throw new Exception($"Couldn't cast content of {nameof(reportHeaderDataTemplate)} to {nameof(FrameworkElement)}"); 43 | 44 | layoutRootGrid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto }); 45 | Grid.SetRow(reportHeader, 0); 46 | 47 | AddPageNumberResource(reportHeader, pageNumber); 48 | layoutRootGrid.Children.Add(reportHeader); 49 | } 50 | 51 | layoutRootGrid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }); 52 | Grid.SetRow(stackPanel, reportHeader == null ? 0 : 1); 53 | layoutRootGrid.Children.Add(stackPanel); 54 | 55 | if (reportFooterDataTemplate != null) 56 | { 57 | reportFooter = reportFooterDataTemplate.LoadContent() as FrameworkElement; 58 | 59 | if (reportFooter == null) 60 | throw new Exception($"Couldn't cast content of {nameof(reportFooterDataTemplate)} to {nameof(FrameworkElement)}"); 61 | 62 | layoutRootGrid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto }); 63 | Grid.SetRow(reportFooter, reportHeader == null ? 1 : 2); 64 | 65 | AddPageNumberResource(reportFooter, pageNumber); 66 | layoutRootGrid.Children.Add(reportFooter); 67 | } 68 | 69 | layoutRootGrid.Measure(reportSize); 70 | layoutRootGrid.Arrange(new Rect(reportSize)); 71 | layoutRootGrid.UpdateLayout(); 72 | } 73 | 74 | internal void AddElement(UIElement element) => stackPanel.Children.Add(element); 75 | 76 | internal UIElementCollection Children => layoutRootGrid.Children; 77 | 78 | private static void AddPageNumberResource(FrameworkElement element, int pageNumber) 79 | { 80 | if (element.Resources.Contains("PageNumber")) 81 | element.Resources["PageNumber"] = pageNumber.ToString(); 82 | else 83 | element.Resources.Add("PageNumber", pageNumber.ToString()); 84 | } 85 | 86 | internal Grid LayoutRoot => layoutRootGrid; 87 | 88 | internal void ClearChildren() 89 | { 90 | stackPanel.Children.Clear(); 91 | layoutRootGrid.Children.Clear(); 92 | } 93 | 94 | internal double GetChildrenActualHeight() 95 | { 96 | double childrenActualHeight = 97 | stackPanel.Children 98 | .Cast() 99 | .Sum(elm => GetActualHeightPlusMargin(elm)); 100 | 101 | if (reportHeader != null) 102 | childrenActualHeight += GetActualHeightPlusMargin(reportHeader); 103 | 104 | if (reportFooter != null) 105 | childrenActualHeight += GetActualHeightPlusMargin(reportFooter); 106 | 107 | return childrenActualHeight; 108 | } 109 | 110 | private static double GetActualHeightPlusMargin(FrameworkElement elm) 111 | { 112 | return elm.ActualHeight + elm.Margin.Top + elm.Margin.Bottom; 113 | } 114 | 115 | internal void Scale(Size reportSize, PrintDialog printDialog) 116 | { 117 | double reportScale = GetReportScale(reportSize, printDialog); 118 | layoutRootGrid.LayoutTransform = new ScaleTransform(reportScale, reportScale); 119 | Size scaledReportSize = new Size(printDialog.PrintableAreaWidth, printDialog.PrintableAreaHeight); 120 | 121 | layoutRootGrid.Measure(scaledReportSize); 122 | layoutRootGrid.Arrange(new Rect(scaledReportSize)); 123 | layoutRootGrid.UpdateLayout(); 124 | } 125 | 126 | private static double GetReportScale(Size reportSize, PrintDialog printDialog) 127 | { 128 | if (reportSize.Width < printDialog.PrintableAreaWidth && reportSize.Height < printDialog.PrintableAreaHeight) 129 | return 1; 130 | 131 | double scale = Math.Min(printDialog.PrintableAreaWidth / reportSize.Width, printDialog.PrintableAreaHeight / reportSize.Height); 132 | 133 | scale = scale - 0.015; // To make sure the report will fit to the page 134 | 135 | return scale; 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /SimpleWPFReporting/SimpleWPFReporting.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {9846B28B-D22B-4B99-BFC1-420535730CE4} 8 | Library 9 | Properties 10 | SimpleWPFReporting 11 | SimpleWPFReporting 12 | v4.5 13 | 512 14 | 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | bin\Debug\SimpleWPFReporting.XML 25 | 26 | 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | bin\Release\SimpleWPFReporting.XML 34 | 35 | 36 | 37 | ..\packages\PdfSharp-WPF.SimpleWPFReporting.1.0.0\lib\net45\PdfSharp-WPF.dll 38 | True 39 | 40 | 41 | ..\packages\PdfSharp.Xps.SimpleWPFReporting.1.0.0\lib\net45\PdfSharp.Xps.dll 42 | True 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 76 | -------------------------------------------------------------------------------- /SimpleWPFReporting/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | --------------------------------------------------------------------------------