当前位置: 首页 > news >正文

WPF 打印报告图片大小的自适应(含完整示例与详解)

目标:在 FlowDocument 报告里,根据 1~6 张图片的数量, 自动选择 2 行 × 3 列 的最佳布局;在只有 1、2、4 张时保持“占满感”,打印清晰且不变形。

规则一览:

  • 1 张 → 占满 2×3(大图居中)

  • 2 张 → 1×2(只占一行)

  • 3 张 → 1×3(只占一行)

  • 4 张 → 2×2(视觉上仍居中于 2×3 区域)

  • 5–6 张 → 2×3

  

一、为什么要“自适应 + 占满感”?

打印报告的图片既要 等比缩放,又要在不同数量时 视觉均衡。常见坑:

  • 直接 UniformGrid 2×3 固定排布,1/2/4 张图会显得松散;

  • 源图 DPI 不一致导致打印缩放异常;

  • Stretch 使用不当造成拉伸变形或留白过多。

本文通过 ItemsControl + 动态 ItemsPanelTemplate 实现布局切换,并配合 DPI 统一、像素对齐 提升打印品质。

二、XAML(FlowDocument 内)

说明:预定义 5 套布局模板;其中 2×2 居中 模板用外层 2×3 容器包一层 2×2,使其在视觉上位于中间。

<FlowDocument.Resources><!-- 2×3 大面板,用于 1 张图占满整个区域(或作为通用容器) --><ItemsPanelTemplate x:Key="Panel1x1"><UniformGrid Rows="2" Columns="3"/></ItemsPanelTemplate><!-- 1 行 2 列:用于 2 张图 --><ItemsPanelTemplate x:Key="Panel1x2"><UniformGrid Rows="1" Columns="2"/></ItemsPanelTemplate><!-- 1 行 3 列:用于 3 张图 --><ItemsPanelTemplate x:Key="Panel1x3"><UniformGrid Rows="1" Columns="3"/></ItemsPanelTemplate><!-- 2×2 居中在 2×3 区域:用于 4 张图(让视觉更均衡) --><ItemsPanelTemplate x:Key="Panel2x2Centered"><Grid><!-- 外层 2×3 占位 --><Grid.RowDefinitions><RowDefinition/><RowDefinition/></Grid.RowDefinitions><Grid.ColumnDefinitions><ColumnDefinition/><ColumnDefinition/><ColumnDefinition/></Grid.ColumnDefinitions><!-- 内层 2×2 真正承载图片,放中间两列(或居中对齐) --><UniformGrid Rows="2" Columns="2"Grid.RowSpan="2" Grid.ColumnSpan="2"Grid.Row="0" Grid.Column="0"HorizontalAlignment="Center" VerticalAlignment="Center"/></Grid></ItemsPanelTemplate><!-- 2×3:用于 5~6 张图 --><ItemsPanelTemplate x:Key="Panel2x3"><UniformGrid Rows="2" Columns="3"/></ItemsPanelTemplate><!-- 单项模板:标题 + 图片(等比缩放、像素对齐) --><DataTemplate x:Key="PrintImageTemplate"><StackPanel Orientation="Vertical"><TextBlock Text="{Binding ImageInfo}"FontSize="12" Margin="5" TextWrapping="Wrap"/><!-- 外层 Grid 允许拉伸占满单元格,Image 使用 Uniform 保持比例 --><Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch"SnapsToDevicePixels="True" UseLayoutRounding="True"><Image Source="{Binding BitmapSource}"Stretch="Uniform"RenderOptions.BitmapScalingMode="HighQuality"RenderOptions.EdgeMode="Aliased"Margin="5"/></Grid></StackPanel></DataTemplate>
</FlowDocument.Resources><!-- 图像区域(2 行 3 列的总体设计) -->
<ItemsControl Name="ImagesItemsControl"Grid.Row="6" Grid.ColumnSpan="3" Margin="0,5,0,15"ItemTemplate="{StaticResource PrintImageTemplate}"><!-- 让每项容器撑满自身单元格,避免 Image 因容器未拉伸而显示偏小 --><ItemsControl.ItemContainerStyle><Style TargetType="ContentPresenter"><Setter Property="HorizontalAlignment" Value="Stretch"/><Setter Property="VerticalAlignment" Value="Stretch"/></Style></ItemsControl.ItemContainerStyle>
</ItemsControl>

小贴士

  • 打印走 FlowDocument 时,尽量保持控件树简单,避免过多 ViewBox 嵌套。

  • SnapsToDevicePixelsUseLayoutRounding 有助于边界像素对齐,减少细纹模糊。

三、模型定义(数据与视图绑定)

public class PrintImageModel
{public BitmapSource BitmapSource { get; set; }public string ImageInfo { get; set; }  // 标题/说明(拍摄时间、层号等)
}

四、代码绑定(含 DPI 统一与布局选择)

说明:

  1. 先对图片按深度/序号排序,只取前 6 张;

  2. 将位图统一到 96 DPI,避免打印引擎因 DPI 不一致而缩放异常;

  3. 根据数量选择 ItemsPanel;

  4. 绑定到 ItemsControl

三、模型定义(数据与视图绑定)public class PrintImageModel
{public BitmapSource BitmapSource { get; set; }public string ImageInfo { get; set; }  // 标题/说明(拍摄时间、层号等)
}// 根据数量选择 ItemsPanelTemplate
private static ItemsPanelTemplate GetPanelForCount(ResourceDictionary res, int count)
{ItemsPanelTemplate Panel(string key) => res[key] as ItemsPanelTemplate;return count switch{<= 0 => Panel("Panel2x3"),        // 回退1    => Panel("Panel1x1"),         // 单图占满 2×32    => Panel("Panel1x2"),3    => Panel("Panel1x3"),4    => Panel("Panel2x2Centered"), // 2×2 居中_    => Panel("Panel2x3"),         // 5~6};
}// 将 BitmapSource 统一到 96 DPI,打印更稳定
private static BitmapSource EnsureDpi96(BitmapSource src)
{if (src == null) return null;const double dpi = 96.0;if (Math.Abs(src.DpiX - dpi) < 0.1 && Math.Abs(src.DpiY - dpi) < 0.1)return src; // 已经是 96 DPI// 复制像素数据到新的 96 DPI 位图var format = src.Format; // 保留原像素格式int stride = (src.PixelWidth * format.BitsPerPixel + 7) / 8;byte[] buffer = new byte[stride * src.PixelHeight];src.CopyPixels(buffer, stride, 0);var normalized = BitmapSource.Create(src.PixelWidth, src.PixelHeight, dpi, dpi,format, src.Palette, buffer, stride);normalized.Freeze();return normalized;
}public void BindReportImages(FlowDocument doc,IEnumerable<(int Depth, Mat Mat, object Meta)> tempList,string timeStr)
{if (doc == null) return;var imagesItemsControl = doc.FindName("ImagesItemsControl") as ItemsControl;if (imagesItemsControl == null) return;// 1) 排序并取前 6 张var list = tempList?.OrderBy(s => s.Depth).Take(6).ToList() ?? new();// 2) 组装绑定模型,并统一 DPIvar imageModelList = new List<PrintImageModel>(list.Count);foreach (var item in list){if (item.Mat == null) continue;var src = BitmapUtils.ToBitmapSource2(item.Mat); // 你现有的 Mat → BitmapSourcevar fixedDpi = EnsureDpi96(src);imageModelList.Add(new PrintImageModel{BitmapSource = fixedDpi,ImageInfo = GetImageInfo(item.Meta, timeStr, item.Depth)});}// 3) 选择合适的 ItemsPanelimagesItemsControl.ItemsPanel = GetPanelForCount(doc.Resources, imageModelList.Count);// 4) 绑定数据imagesItemsControl.ItemsSource = imageModelList;
}

关于 BitmapUtils.ToBitmapSource2:如果它内部使用了 MemoryStream 临时对象,请务必在 BitmapImage 完成初始化后 Freeze(),以防打印时跨线程访问异常。


五、打印清晰度与缩放的关键点

  1. 统一 DPI(推荐 96):WPF 视觉树的度量与渲染以 96 DPI 为基准。若源图为 300 DPI,但尺寸以像素为准,打印时仍以像素为依据,可能出现缩放计算偏差,统一 DPI 可减少不可控因素。

  2. 像素对齐:启用 SnapsToDevicePixelsUseLayoutRounding,避免边界半像素导致的灰边。

  3. 高质量缩放RenderOptions.BitmapScalingMode="HighQuality" 能在缩小图片时明显改善清晰度。

  4. 避免多层缩放:不要在 Image 外再套 ViewBox,否则缩放叠乘影响清晰度。

  5. 打印管线:如对分页/边距有严格控制,考虑使用 DocumentPaginator 或 FixedDocument,避免 Flow 文档自动分页带来的不可控换行。


六、常见问题(FAQ)

Q1:为什么 4 张图不用 2×3?
A:2×2 更大更聚焦。通过“2×2 居中到 2×3”视觉上仍与其他布局保持一致的占位比例。

Q2:只有 1 张图为何不用 1×1?
A:使用 2×3 的容器能与 2×3 总体版式对齐,且大图居中更美观,留白更合理。

Q3:源图是 16 位灰度(例如医学影像)怎么办?
A:先在内存中转换为 8 位或 24 位 BGR 的 BitmapSource,再参与绑定与 DPI 统一,避免打印时的像素格式兼容性问题。

Q4:图片很大(4K/8K)会卡顿?
A:打印前对像素尺寸进行约束(如最长边不超过 A4/A3 目标像素),并在后台线程解码,主线程只做绑定。

七、结语

通过 动态 ItemsPanel + DPI 统一 + 像素对齐,我们实现了打印报告中 1~6 张图片的自适应与高质量输出。你可以直接把本文的 XAML 与 C# 片段粘到你的项目里使用,或在此基础上扩展更多版式(如 3×3、横竖版自动切换等)。

http://www.dtcms.com/a/338336.html

相关文章:

  • 初识CNN04——经典网络认识
  • 驱动开发系列64 - glCompileShader实现-GLSL 精度优化pass
  • 3.1 结构化输出(大模型的封闭与开放)
  • Windows系统上使用GIT
  • CMake指令:查找文件(find_file)、查找目录(find_path)、查找库文件(find_library)
  • Life:Internship in OnSea Day 57
  • 【Kubernetes】在 K8s 上部署 Prometheus
  • 1-Flask相关知识点
  • 恒创科技:日本服务器 ping 不通?从排查到解决的实用指南
  • 朝阳区24小时图书馆“焕新计划”启幕 文化讲座点亮夜间阅读之光
  • ST05跟踪MRP的运行(MD01)过程
  • 使用chmod 命令修改文件权限
  • 【完整源码+数据集+部署教程】空中目标检测系统源码和数据集:改进yolo11-UniRepLKNetBlock
  • mac 电脑安装类似 nvm 的工具,node 版本管理工具
  • 【机器人-基础知识】ROS2常用命令
  • Vue3 全新特性 defineModel 深度解析
  • CentOS Linux 7 (Core)上部署Oracle 11g、19C RAC详细图文教程
  • 【MySQL】超详细入门学习
  • vue3 + antd modal弹窗拖拽全局封装 使用useDraggable
  • LeetCode100 -- Day1
  • 嵌入式工程师常去的网址
  • 缺陷检测最新综述:针对现实世界工业缺陷检测的综合调查:挑战、方法与展望
  • C++对象的内存布局
  • 拓扑排序详解:从力扣 207 题看有向图环检测
  • 2025年最新美区Apple ID共享账号免费分享(持续更新)
  • 决策树(1)
  • 2025年秋招Java后端面试场景题+八股文题目
  • pandas基本数据
  • 开疆智能Profient转EtherCAT网关连接伦茨变频器配置案例
  • DeepSeek辅助编写的将ET格式文件转换为xls和xlsb格式程序