将Feature/XP.Common和Feature/XP.Hardware分支合并至Develop/XP.forHardwareAndCommon,完善XPapp注册和相关硬件类库通用类库功能。
This commit is contained in:
@@ -0,0 +1,276 @@
|
||||
using System;
|
||||
using System.Drawing.Printing;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using Telerik.Windows.Controls;
|
||||
using Telerik.Windows.Documents.Fixed.FormatProviders.Pdf;
|
||||
using Telerik.Windows.Documents.Fixed.Print;
|
||||
using XP.Common.Logging.Interfaces;
|
||||
using XP.Common.PdfViewer.Exceptions;
|
||||
using XP.Common.PdfViewer.Interfaces;
|
||||
|
||||
namespace XP.Common.PdfViewer.Implementations
|
||||
{
|
||||
/// <summary>
|
||||
/// PDF 打印服务实现 | PDF print service implementation
|
||||
/// 基于 Telerik RadPdfViewer.Print() 实现 | Based on Telerik RadPdfViewer.Print()
|
||||
/// </summary>
|
||||
public class PdfPrintService : IPdfPrintService
|
||||
{
|
||||
private readonly ILoggerService _logger;
|
||||
|
||||
public PdfPrintService(ILoggerService logger)
|
||||
{
|
||||
_logger = logger?.ForModule<PdfPrintService>()
|
||||
?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用指定打印机打印 PDF 文件 | Print PDF file with specified printer
|
||||
/// </summary>
|
||||
public void Print(string filePath, string printerName, int? pageFrom = null, int? pageTo = null, int copies = 1)
|
||||
{
|
||||
// 1. 验证文件路径存在 | Validate file path exists
|
||||
ValidateFilePath(filePath);
|
||||
|
||||
// 2. 验证打印机名称有效 | Validate printer name is valid
|
||||
ValidatePrinterName(printerName);
|
||||
|
||||
RadPdfViewer? pdfViewer = null;
|
||||
try
|
||||
{
|
||||
// 3. 创建隐藏的 RadPdfViewer 并加载 PDF | Create hidden RadPdfViewer and load PDF
|
||||
pdfViewer = CreatePdfViewerWithDocument(filePath);
|
||||
|
||||
// 4. 创建 PrintDialog 并配置打印机名称 | Create PrintDialog and configure printer name
|
||||
var printDialog = new PrintDialog();
|
||||
printDialog.PrintQueue = new System.Printing.PrintQueue(
|
||||
new System.Printing.LocalPrintServer(), printerName);
|
||||
|
||||
// 5. 配置页面范围 | Configure page range
|
||||
if (pageFrom.HasValue || pageTo.HasValue)
|
||||
{
|
||||
printDialog.PageRangeSelection = PageRangeSelection.UserPages;
|
||||
printDialog.PageRange = new PageRange(
|
||||
pageFrom ?? 1,
|
||||
pageTo ?? int.MaxValue);
|
||||
}
|
||||
|
||||
// 6. 配置打印份数 | Configure copies
|
||||
if (copies > 1)
|
||||
{
|
||||
printDialog.PrintTicket.CopyCount = copies;
|
||||
}
|
||||
|
||||
// 7. 配置 Telerik PrintSettings | Configure Telerik PrintSettings
|
||||
var printSettings = new Telerik.Windows.Documents.Fixed.Print.PrintSettings
|
||||
{
|
||||
DocumentName = Path.GetFileName(filePath),
|
||||
PageMargins = new Thickness(0),
|
||||
UseDefaultPrinter = false
|
||||
};
|
||||
|
||||
// 8. 调用 RadPdfViewer.Print() 静默打印 | Call RadPdfViewer.Print() for silent printing
|
||||
pdfViewer.Print(printDialog, printSettings);
|
||||
|
||||
// 9. 记录 Info 日志 | Log info
|
||||
var pageRange = FormatPageRange(pageFrom, pageTo);
|
||||
_logger.Info("打印任务已提交 | Print job submitted: {FileName} → {PrinterName}, 页面范围 | Pages: {PageRange}, 份数 | Copies: {Copies}",
|
||||
Path.GetFileName(filePath), printerName, pageRange, copies);
|
||||
}
|
||||
catch (Exception ex) when (ex is not FileNotFoundException
|
||||
and not PrinterNotFoundException
|
||||
and not PdfLoadException)
|
||||
{
|
||||
_logger.Error(ex, "打印失败 | Print failed: {FileName} → {PrinterName}", Path.GetFileName(filePath), printerName);
|
||||
throw new PrintException($"打印失败 | Print failed: {Path.GetFileName(filePath)}", ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// 10. 释放资源 | Release resources
|
||||
DisposePdfViewer(pdfViewer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 打开打印设置对话框并打印 | Open print settings dialog and print
|
||||
/// </summary>
|
||||
public bool PrintWithDialog(string filePath)
|
||||
{
|
||||
// 1. 验证文件路径 | Validate file path
|
||||
ValidateFilePath(filePath);
|
||||
|
||||
RadPdfViewer? pdfViewer = null;
|
||||
try
|
||||
{
|
||||
// 2. 加载 PDF 到隐藏的 RadPdfViewer | Load PDF into hidden RadPdfViewer
|
||||
pdfViewer = CreatePdfViewerWithDocument(filePath);
|
||||
|
||||
// 3. 配置 PrintSettings | Configure PrintSettings
|
||||
var printSettings = new Telerik.Windows.Documents.Fixed.Print.PrintSettings
|
||||
{
|
||||
DocumentName = Path.GetFileName(filePath),
|
||||
PageMargins = new Thickness(0),
|
||||
UseDefaultPrinter = false
|
||||
};
|
||||
|
||||
// 4. 调用 RadPdfViewer.Print() 弹出 PrintDialog | Call RadPdfViewer.Print() to show PrintDialog
|
||||
pdfViewer.Print(printSettings);
|
||||
|
||||
_logger.Info("用户通过对话框打印 | User printed via dialog: {FileName}", Path.GetFileName(filePath));
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex) when (ex is not FileNotFoundException and not PdfLoadException)
|
||||
{
|
||||
_logger.Error(ex, "打印失败 | Print failed: {FileName}", Path.GetFileName(filePath));
|
||||
throw new PrintException($"打印失败 | Print failed: {Path.GetFileName(filePath)}", ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// 5. 释放资源 | Release resources
|
||||
DisposePdfViewer(pdfViewer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 打开打印预览对话框 | Open print preview dialog
|
||||
/// </summary>
|
||||
public void PrintPreview(string filePath)
|
||||
{
|
||||
// 1. 验证文件路径 | Validate file path
|
||||
ValidateFilePath(filePath);
|
||||
|
||||
RadPdfViewer? pdfViewer = null;
|
||||
try
|
||||
{
|
||||
// 2. 加载 PDF 到隐藏的 RadPdfViewer | Load PDF into hidden RadPdfViewer
|
||||
pdfViewer = CreatePdfViewerWithDocument(filePath);
|
||||
|
||||
// 3. 通过 PrintDialog 显示预览 | Show preview via PrintDialog
|
||||
var printDialog = new PrintDialog();
|
||||
|
||||
var printSettings = new Telerik.Windows.Documents.Fixed.Print.PrintSettings
|
||||
{
|
||||
DocumentName = Path.GetFileName(filePath),
|
||||
PageMargins = new Thickness(0),
|
||||
UseDefaultPrinter = false
|
||||
};
|
||||
|
||||
// 显示打印对话框(含预览功能)| Show print dialog (with preview capability)
|
||||
pdfViewer.Print(printDialog, printSettings);
|
||||
|
||||
_logger.Info("打印预览已显示 | Print preview shown: {FileName}", Path.GetFileName(filePath));
|
||||
}
|
||||
catch (Exception ex) when (ex is not FileNotFoundException and not PdfLoadException)
|
||||
{
|
||||
_logger.Error(ex, "打印预览失败 | Print preview failed: {FileName}", Path.GetFileName(filePath));
|
||||
throw new PrintException($"打印预览失败 | Print preview failed: {Path.GetFileName(filePath)}", ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// 4. 释放资源 | Release resources
|
||||
DisposePdfViewer(pdfViewer);
|
||||
}
|
||||
}
|
||||
|
||||
#region 私有辅助方法 | Private Helper Methods
|
||||
|
||||
/// <summary>
|
||||
/// 验证文件路径是否存在 | Validate file path exists
|
||||
/// </summary>
|
||||
private void ValidateFilePath(string filePath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(filePath))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(filePath), "文件路径不能为空 | File path cannot be null or empty");
|
||||
}
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
_logger.Error(new FileNotFoundException(filePath), "文件不存在 | File not found: {FilePath}", filePath);
|
||||
throw new FileNotFoundException($"文件不存在 | File not found: {filePath}", filePath);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证打印机名称是否有效 | Validate printer name is valid
|
||||
/// </summary>
|
||||
private void ValidatePrinterName(string printerName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(printerName))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(printerName), "打印机名称不能为空 | Printer name cannot be null or empty");
|
||||
}
|
||||
|
||||
// 通过 PrinterSettings.InstalledPrinters 查询系统已安装的打印机 | Query installed printers
|
||||
bool found = false;
|
||||
foreach (string installedPrinter in PrinterSettings.InstalledPrinters)
|
||||
{
|
||||
if (string.Equals(installedPrinter, printerName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
_logger.Error(new PrinterNotFoundException(printerName),
|
||||
"打印机未找到 | Printer not found: {PrinterName}", printerName);
|
||||
throw new PrinterNotFoundException(printerName);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建隐藏的 RadPdfViewer 并加载 PDF 文档 | Create hidden RadPdfViewer and load PDF document
|
||||
/// </summary>
|
||||
private RadPdfViewer CreatePdfViewerWithDocument(string filePath)
|
||||
{
|
||||
var pdfViewer = new RadPdfViewer();
|
||||
|
||||
try
|
||||
{
|
||||
var provider = new PdfFormatProvider();
|
||||
using var fileStream = File.OpenRead(filePath);
|
||||
pdfViewer.Document = provider.Import(fileStream);
|
||||
return pdfViewer;
|
||||
}
|
||||
catch (Exception ex) when (ex is not FileNotFoundException)
|
||||
{
|
||||
// 释放已创建的 viewer | Dispose created viewer
|
||||
DisposePdfViewer(pdfViewer);
|
||||
_logger.Error(ex, "PDF 文件加载失败 | PDF file load failed: {FilePath}", filePath);
|
||||
throw new PdfLoadException("PDF 文件加载失败 | PDF file load failed", filePath, ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放 RadPdfViewer 资源 | Dispose RadPdfViewer resources
|
||||
/// </summary>
|
||||
private static void DisposePdfViewer(RadPdfViewer? pdfViewer)
|
||||
{
|
||||
if (pdfViewer != null)
|
||||
{
|
||||
pdfViewer.Document = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 格式化页面范围字符串 | Format page range string
|
||||
/// </summary>
|
||||
private static string FormatPageRange(int? pageFrom, int? pageTo)
|
||||
{
|
||||
if (!pageFrom.HasValue && !pageTo.HasValue)
|
||||
{
|
||||
return "全部 | All";
|
||||
}
|
||||
|
||||
var from = pageFrom?.ToString() ?? "1";
|
||||
var to = pageTo?.ToString() ?? "末页 | Last";
|
||||
return $"{from}-{to}";
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using XP.Common.Localization;
|
||||
using XP.Common.Logging.Interfaces;
|
||||
using XP.Common.PdfViewer.Exceptions;
|
||||
using XP.Common.PdfViewer.Interfaces;
|
||||
using XP.Common.PdfViewer.ViewModels;
|
||||
using XP.Common.PdfViewer.Views;
|
||||
|
||||
namespace XP.Common.PdfViewer.Implementations
|
||||
{
|
||||
/// <summary>
|
||||
/// PDF 查看服务实现 | PDF viewer service implementation
|
||||
/// 基于 Telerik RadPdfViewer 实现 | Based on Telerik RadPdfViewer
|
||||
/// </summary>
|
||||
public class PdfViewerService : IPdfViewerService
|
||||
{
|
||||
private readonly ILoggerService _logger;
|
||||
private readonly IPdfPrintService _printService;
|
||||
private bool _disposed;
|
||||
|
||||
public PdfViewerService(ILoggerService logger, IPdfPrintService printService)
|
||||
{
|
||||
_logger = logger?.ForModule<PdfViewerService>()
|
||||
?? throw new ArgumentNullException(nameof(logger));
|
||||
_printService = printService
|
||||
?? throw new ArgumentNullException(nameof(printService));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通过文件路径打开 PDF 阅读器窗口 | Open PDF viewer window by file path
|
||||
/// </summary>
|
||||
/// <param name="filePath">PDF 文件路径 | PDF file path</param>
|
||||
/// <exception cref="FileNotFoundException">文件不存在 | File not found</exception>
|
||||
/// <exception cref="PdfLoadException">PDF 格式无效 | Invalid PDF format</exception>
|
||||
public void OpenViewer(string filePath)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
// 1. 验证文件路径存在 | Validate file path exists
|
||||
if (string.IsNullOrWhiteSpace(filePath))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(filePath),
|
||||
"文件路径不能为空 | File path cannot be null or empty");
|
||||
}
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
_logger.Error(new FileNotFoundException(filePath),
|
||||
"文件不存在 | File not found: {FilePath}", filePath);
|
||||
throw new FileNotFoundException(
|
||||
$"文件不存在 | File not found: {filePath}", filePath);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// 2. 创建 PdfViewerWindowViewModel | Create PdfViewerWindowViewModel
|
||||
var viewModel = new PdfViewerWindowViewModel(filePath, _printService, _logger);
|
||||
|
||||
// 3. 创建 PdfViewerWindow 并设置 DataContext | Create PdfViewerWindow and set DataContext
|
||||
var window = new PdfViewerWindow
|
||||
{
|
||||
DataContext = viewModel
|
||||
};
|
||||
|
||||
// 4. 记录 Info 日志 | Log info
|
||||
_logger.Info("打开 PDF 阅读器窗口 | Opening PDF viewer window: {FileName}",
|
||||
Path.GetFileName(filePath));
|
||||
|
||||
// 5. 显示窗口(非模态)| Show window (non-modal)
|
||||
window.Show();
|
||||
}
|
||||
catch (Exception ex) when (ex is not FileNotFoundException
|
||||
and not ArgumentNullException
|
||||
and not PdfLoadException)
|
||||
{
|
||||
_logger.Error(ex, "PDF 文件加载失败 | PDF file load failed: {FilePath}", filePath);
|
||||
throw new PdfLoadException(
|
||||
"PDF 文件加载失败 | PDF file load failed", filePath, ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通过文件流打开 PDF 阅读器窗口 | Open PDF viewer window by stream
|
||||
/// </summary>
|
||||
/// <param name="stream">PDF 文件流 | PDF file stream</param>
|
||||
/// <param name="title">窗口标题(可选)| Window title (optional)</param>
|
||||
/// <exception cref="ArgumentNullException">流为 null | Stream is null</exception>
|
||||
/// <exception cref="PdfLoadException">PDF 格式无效 | Invalid PDF format</exception>
|
||||
public void OpenViewer(Stream stream, string? title = null)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
// 1. 验证 stream 非 null | Validate stream is not null
|
||||
ArgumentNullException.ThrowIfNull(stream, nameof(stream));
|
||||
|
||||
try
|
||||
{
|
||||
// 2. 创建 ViewModel | Create ViewModel
|
||||
var viewModel = new PdfViewerWindowViewModel(stream, title, _printService, _logger);
|
||||
|
||||
// 3. 创建 PdfViewerWindow 并设置 DataContext | Create PdfViewerWindow and set DataContext
|
||||
var window = new PdfViewerWindow
|
||||
{
|
||||
DataContext = viewModel
|
||||
};
|
||||
|
||||
// 4. 记录 Info 日志 | Log info
|
||||
var displayTitle = title ?? LocalizationHelper.Get("PdfViewer_Title");
|
||||
_logger.Info("打开 PDF 阅读器窗口(流模式)| Opening PDF viewer window (stream mode): {Title}",
|
||||
displayTitle);
|
||||
|
||||
// 5. 显示窗口(非模态)| Show window (non-modal)
|
||||
window.Show();
|
||||
}
|
||||
catch (Exception ex) when (ex is not ArgumentNullException
|
||||
and not PdfLoadException)
|
||||
{
|
||||
_logger.Error(ex, "PDF 文件加载失败(流模式)| PDF file load failed (stream mode)");
|
||||
throw new PdfLoadException(
|
||||
"PDF 文件加载失败 | PDF file load failed", null, ex);
|
||||
}
|
||||
}
|
||||
|
||||
#region IDisposable 模式 | IDisposable Pattern
|
||||
|
||||
/// <summary>
|
||||
/// 释放资源 | Dispose resources
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放资源的核心方法 | Core dispose method
|
||||
/// </summary>
|
||||
/// <param name="disposing">是否由 Dispose() 调用 | Whether called by Dispose()</param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
// 释放托管资源 | Dispose managed resources
|
||||
_logger.Info("PdfViewerService 已释放 | PdfViewerService disposed");
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 终结器安全网 | Finalizer safety net
|
||||
/// 确保未显式释放时仍能清理非托管资源 | Ensures unmanaged resources are cleaned up if not explicitly disposed
|
||||
/// </summary>
|
||||
~PdfViewerService()
|
||||
{
|
||||
Dispose(disposing: false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user