├── .github └── FUNDING.yml ├── .gitignore ├── preview.png ├── PrintDialogX ├── Resources │ ├── Effects │ │ ├── Grayscale.ps │ │ ├── Monochrome.ps │ │ ├── Grayscale.fx │ │ └── Monochrome.fx │ └── Languages │ │ ├── zh-CN.xaml │ │ └── en-US.xaml ├── Global.xaml ├── PrintDialogWindow.xaml ├── PrintDialogX.csproj ├── PrintDocument.cs ├── IPrintDialogHost.cs ├── PrintDialogWindow.xaml.cs ├── PrintDialog.cs ├── InterfaceSettings.cs ├── ValueHelpers.cs ├── ValueMappings.cs └── PrintDialogControl.xaml ├── PrintDialogX.Test ├── App.xaml.cs ├── App.xaml ├── PrintDialogX.Test.csproj └── MainWindow.xaml ├── LICENSE.txt ├── PrintDialogX.sln └── README.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [Fei-Sheng-Wu] 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vs/ 2 | 3 | bin/ 4 | obj/ 5 | 6 | *.user 7 | *.nupkg 8 | *.snupkg -------------------------------------------------------------------------------- /preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fei-Sheng-Wu/PrintDialogX/HEAD/preview.png -------------------------------------------------------------------------------- /PrintDialogX/Resources/Effects/Grayscale.ps: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fei-Sheng-Wu/PrintDialogX/HEAD/PrintDialogX/Resources/Effects/Grayscale.ps -------------------------------------------------------------------------------- /PrintDialogX/Resources/Effects/Monochrome.ps: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fei-Sheng-Wu/PrintDialogX/HEAD/PrintDialogX/Resources/Effects/Monochrome.ps -------------------------------------------------------------------------------- /PrintDialogX.Test/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | namespace PrintDialogX.Test 4 | { 5 | public partial class App : Application 6 | { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /PrintDialogX/Resources/Effects/Grayscale.fx: -------------------------------------------------------------------------------- 1 | sampler2D input : register(s0); 2 | 3 | float4 main(float2 uv : TEXCOORD) : COLOR 4 | { 5 | float4 color = tex2D(input, uv); 6 | color.rgb = dot(color.rgb, float3(0.3, 0.59, 0.11)); 7 | 8 | return color; 9 | } -------------------------------------------------------------------------------- /PrintDialogX.Test/App.xaml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /PrintDialogX.Test/PrintDialogX.Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WinExe 5 | net6.0-windows 6 | true 7 | enable 8 | latest 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /PrintDialogX/Resources/Effects/Monochrome.fx: -------------------------------------------------------------------------------- 1 | sampler2D input : register(s0); 2 | 3 | float left : register(c0); 4 | float top : register(c1); 5 | float width : register(c2); 6 | float height : register(c3); 7 | 8 | float4 main(float2 uv : TEXCOORD) : COLOR 9 | { 10 | float4 color = tex2D(input, uv); 11 | float2 position = frac((uv * float2(width, height) + float2(left, top)) / 256.0); 12 | float dither = frac(sin(dot(floor(position * 2048.0), float2(12.34, 45.67))) * 4321.1234); 13 | float grayscale = dot(color.rgb, float3(0.3, 0.59, 0.11)); 14 | color.rgb = 1.0 - smoothstep(grayscale - 0.001, grayscale + 0.01, dither); 15 | 16 | return color; 17 | } -------------------------------------------------------------------------------- /PrintDialogX/Global.xaml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright © 2025 FeiShengWu 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. 22 | -------------------------------------------------------------------------------- /PrintDialogX.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.4.33205.214 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PrintDialogX", "PrintDialogX\PrintDialogX.csproj", "{10A50135-1CAB-49DF-9AAA-C970DDF46F63}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PrintDialogX.Test", "PrintDialogX.Test\PrintDialogX.Test.csproj", "{E98DEC5C-3EEA-4F69-A232-A309CFB91471}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {10A50135-1CAB-49DF-9AAA-C970DDF46F63}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {10A50135-1CAB-49DF-9AAA-C970DDF46F63}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {10A50135-1CAB-49DF-9AAA-C970DDF46F63}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {10A50135-1CAB-49DF-9AAA-C970DDF46F63}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {E98DEC5C-3EEA-4F69-A232-A309CFB91471}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {E98DEC5C-3EEA-4F69-A232-A309CFB91471}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {E98DEC5C-3EEA-4F69-A232-A309CFB91471}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {E98DEC5C-3EEA-4F69-A232-A309CFB91471}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {0ADCEB1B-0618-4D8A-9759-3F4C2AC95324} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /PrintDialogX/PrintDialogWindow.xaml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /PrintDialogX/PrintDialogX.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Library 5 | net472;net6.0-windows 6 | true 7 | enable 8 | 14.0 9 | 10 | 11 | 12 | PrintDialogX 13 | 3.1.0 14 | FeiShengWu 15 | A custom WPF print dialog with lightning-fast real-time preview. Support a full scope of print settings for modern demands, with the flexibility for complete customization. Provide the ability to dynamically adjust documents according to changes in print settings. Empowers the user experience with a responsive, elegant, and configurable interface. 16 | Copyright © 2025 FeiShengWu 17 | https://github.com/Fei-Sheng-Wu/PrintDialogX 18 | MIT 19 | C#;WPF;PrintDialog;PrinterDialog;Dialog;Printer;Document;Print;Printing;Preview;Previewer;DocumentPreview;DocumentPrinting 20 | 21 | 22 | 23 | true 24 | true 25 | snupkg 26 | portable 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 52 | 53 | -------------------------------------------------------------------------------- /PrintDialogX/PrintDocument.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Windows; 4 | using System.Windows.Media; 5 | using System.Windows.Threading; 6 | 7 | namespace PrintDialogX 8 | { 9 | /// 10 | /// Initializes a new instance of the class. 11 | /// 12 | public class PrintPage 13 | { 14 | /// 15 | /// Gets or sets the content of the page. 16 | /// 17 | public FrameworkElement? Content 18 | { 19 | get; 20 | set => field = value == null || VisualTreeHelper.GetParent(value) == null ? value : throw new PrintDocumentException(value, "The value is already the child of another element."); 21 | } = null; 22 | } 23 | 24 | /// 25 | /// Initializes a new instance of the class. 26 | /// 27 | public class PrintDocument() 28 | { 29 | /// 30 | /// Occurs when the print settings have changed. 31 | /// 32 | public event EventHandler? PrintSettingsChanged = null; 33 | 34 | /// 35 | /// Gets or sets the name of the document. 36 | /// 37 | public string DocumentName { get; set; } = string.Empty; 38 | 39 | /// 40 | /// Gets or sets the size of the document. If set to , the document automatically adapts to the sizes calculated from the print settings. 41 | /// 42 | public Enums.Size? DocumentSize { get; set; } = null; 43 | 44 | /// 45 | /// Gets or sets the default margin of the document in pixels. 46 | /// 47 | public double DocumentMargin 48 | { 49 | get; 50 | set => field = value >= 0 ? value : throw new ArgumentOutOfRangeException(nameof(DocumentMargin), "The value cannot be negative."); 51 | } = 60; 52 | 53 | /// 54 | /// Gets or sets the collection of pages in the document. 55 | /// 56 | public ICollection Pages { get; set; } = []; 57 | 58 | /// 59 | /// Gets the number of pages in the document. 60 | /// 61 | public int PageCount { get => Pages.Count; } 62 | 63 | /// 64 | /// Gets or sets the computed size of the available space for the content of the document, excluding the margin. 65 | /// 66 | public Size MeasuredSize { get; set; } = Size.Empty; 67 | 68 | /// 69 | /// Raises the event. 70 | /// 71 | /// The instance to be used to invoke the handler. 72 | /// The instance of the new print settings. 73 | public void OnPrintSettingsChanged(Dispatcher dispatcher, PrintSettingsEventArgs settings) 74 | { 75 | dispatcher.Invoke(() => PrintSettingsChanged?.Invoke(this, settings)); 76 | } 77 | } 78 | 79 | /// 80 | /// Initializes a new instance of the class. 81 | /// 82 | /// The instance that caused the error. 83 | /// The message that describes the error. 84 | public class PrintDocumentException(FrameworkElement content, string message) : Exception(message) 85 | { 86 | /// 87 | /// Gets or sets the instance that caused the error. 88 | /// 89 | public FrameworkElement Content { get; set; } = content; 90 | } 91 | } 92 | 93 | -------------------------------------------------------------------------------- /PrintDialogX/IPrintDialogHost.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using System.Windows; 4 | using System.Windows.Input; 5 | 6 | namespace PrintDialogX 7 | { 8 | /// 9 | /// Initializes a new instance of the struct. 10 | /// 11 | public struct PrintDialogResult() 12 | { 13 | /// 14 | /// Gets or sets whether the document was successfully printed or the operation was cancelled. 15 | /// 16 | public bool IsSuccess { get; set; } 17 | 18 | /// 19 | /// Gets or sets the number of papers calculated to be used. 20 | /// 21 | public int PaperCount { get; set; } 22 | } 23 | 24 | /// 25 | /// Represents a host that can contain the actual control for the print operation. 26 | /// 27 | public interface IPrintDialogHost 28 | { 29 | /// 30 | /// Specifies the state of the print job progress. 31 | /// 32 | public enum PrintDialogProgressState 33 | { 34 | /// 35 | /// There is no available print job. 36 | /// 37 | None, 38 | 39 | /// 40 | /// The print job is initializing. 41 | /// 42 | Indeterminate, 43 | 44 | /// 45 | /// The print job is in progress. 46 | /// 47 | Normal, 48 | 49 | /// 50 | /// The print job failed or is cancelled. 51 | /// 52 | Error 53 | } 54 | 55 | /// 56 | /// Initializes a new instance of the struct. 57 | /// 58 | public struct PrintDialogProgress() 59 | { 60 | /// 61 | /// Gets or sets the state of the print job progress. 62 | /// 63 | public PrintDialogProgressState State { get; set; } 64 | 65 | /// 66 | /// Gets or sets the percentage value of the print job progress, from 0 to 100. 67 | /// 68 | public double Value { get; set; } 69 | } 70 | 71 | /// 72 | /// Starts the host with the specified settings and a callback to attach the actual control for the print operation. 73 | /// 74 | /// The instance with the specified settings. 75 | /// to start the host in a dialog style and return only when the operation is finished; otherwise, . 76 | /// The callback function to be invoked to attain the actual control for the print operation to be attached to the host. 77 | public void Start(PrintDialog dialog, bool isDialog, Func> callback); 78 | 79 | /// 80 | /// Gets the result of the print operation. 81 | /// 82 | /// 83 | public PrintDialogResult GetResult(); 84 | 85 | /// 86 | /// Sets the result of the print operation. 87 | /// 88 | /// The result to be set to. 89 | public void SetResult(PrintDialogResult result); 90 | 91 | /// 92 | /// Sets the progress of the current print job. 93 | /// 94 | /// The progress to be set to. 95 | public void SetProgress(PrintDialogProgress progress); 96 | 97 | /// 98 | /// Sets the event handler that handles keyboard shortcuts. 99 | /// 100 | /// The event handler to be used. 101 | public void SetShortcutHandler(KeyEventHandler handler); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /PrintDialogX/PrintDialogWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Threading.Tasks; 4 | using System.Windows; 5 | using System.Windows.Input; 6 | using System.Windows.Media; 7 | using System.Windows.Shell; 8 | using System.Windows.Controls; 9 | 10 | namespace PrintDialogX 11 | { 12 | internal partial class PrintDialogWindow : Wpf.Ui.Controls.FluentWindow, IPrintDialogHost 13 | { 14 | private bool isAvailable = true; 15 | private Func>? loader = null; 16 | private KeyEventHandler? handler = null; 17 | private PrintDialogResult result = new(); 18 | 19 | public PrintDialogWindow() 20 | { 21 | InitializeComponent(); 22 | } 23 | 24 | private async void AttachControl(object sender, EventArgs e) 25 | { 26 | if (loader == null) 27 | { 28 | return; 29 | } 30 | 31 | content.Child = await loader(); 32 | } 33 | 34 | private void HandleShortcuts(object sender, KeyEventArgs e) 35 | { 36 | handler?.Invoke(sender, e); 37 | } 38 | 39 | public void Start(PrintDialog dialog, bool isDialog, Func> callback) 40 | { 41 | if (!isAvailable) 42 | { 43 | throw new InvalidOperationException("The print dialog has already been used."); 44 | } 45 | 46 | isAvailable = false; 47 | loader = callback; 48 | 49 | InterfaceToContentConverter.ApplyLanguage(Resources, dialog.InterfaceSettings.DisplayLanguage); 50 | title.Header = new TextBlock() 51 | { 52 | Margin = new(dialog.InterfaceSettings.Icon == null ? 16 : 0, 10, 0, 10), 53 | FontSize = title.FontSize, 54 | Text = dialog.InterfaceSettings.Title ?? Title 55 | }; 56 | title.Icon = dialog.InterfaceSettings.Icon; 57 | 58 | Wpf.Ui.Appearance.ApplicationThemeManager.Apply(this); 59 | Wpf.Ui.Appearance.ApplicationThemeManager.Changed += UpdateTheme; 60 | Wpf.Ui.Appearance.SystemThemeWatcher.Watch(this); 61 | 62 | if (isDialog) 63 | { 64 | ShowDialog(); 65 | } 66 | else 67 | { 68 | Show(); 69 | } 70 | } 71 | 72 | public PrintDialogResult GetResult() 73 | { 74 | return result; 75 | } 76 | 77 | public void SetResult(PrintDialogResult value) 78 | { 79 | result = value; 80 | Close(); 81 | } 82 | 83 | public void SetProgress(IPrintDialogHost.PrintDialogProgress progress) 84 | { 85 | TaskbarItemInfo.ProgressState = progress.State switch 86 | { 87 | IPrintDialogHost.PrintDialogProgressState.Indeterminate => TaskbarItemProgressState.Indeterminate, 88 | IPrintDialogHost.PrintDialogProgressState.Normal => TaskbarItemProgressState.Normal, 89 | IPrintDialogHost.PrintDialogProgressState.Error => TaskbarItemProgressState.Error, 90 | _ => TaskbarItemProgressState.None 91 | }; 92 | TaskbarItemInfo.ProgressValue = progress.Value / 100; 93 | } 94 | 95 | public void SetShortcutHandler(KeyEventHandler value) 96 | { 97 | handler = value; 98 | } 99 | 100 | public void UpdateTheme(Wpf.Ui.Appearance.ApplicationTheme theme, Color accent) 101 | { 102 | Wpf.Ui.Appearance.ApplicationThemeManager.Apply(this); 103 | 104 | if (content.Child is FrameworkElement element) 105 | { 106 | Wpf.Ui.Appearance.ApplicationThemeManager.Apply(element); 107 | } 108 | } 109 | 110 | protected override void OnClosing(CancelEventArgs e) 111 | { 112 | Wpf.Ui.Appearance.ApplicationThemeManager.Changed -= UpdateTheme; 113 | Wpf.Ui.Appearance.SystemThemeWatcher.UnWatch(this); 114 | 115 | base.OnClosing(e); 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /PrintDialogX.Test/MainWindow.xaml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 11 | 15 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 |