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.pswp.cn/pingmian/93677.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/93677.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【AI大模型前沿】百度飞桨PaddleOCR 3.0开源发布,支持多语言、手写体识别,赋能智能文档处理

系列篇章&#x1f4a5; No.文章1【AI大模型前沿】深度剖析瑞智病理大模型 RuiPath&#xff1a;如何革新癌症病理诊断技术2【AI大模型前沿】清华大学 CLAMP-3&#xff1a;多模态技术引领音乐检索新潮流3【AI大模型前沿】浙大携手阿里推出HealthGPT&#xff1a;医学视觉语言大模…

迅为RK3588开发板Android12 制作使用系统签名

在 Android 源码 build/make/target/product/security/下存放着签名文件&#xff0c;如下所示&#xff1a;将北京迅为提供的 keytool 工具拷贝到 ubuntu 中&#xff0c;然后将 Android11 或 Android12 源码build/make/target/product/security/下的 platform.pk8 platform.x509…

Day08 Go语言学习

1.安装Go和Goland 2.新建demo项目实践语法并使用git实践版本控制操作 2.1 Goland配置 路径**&#xff1a;** GOPATH workspace GOROOT golang 文件夹&#xff1a; bin 编译后的可执行文件 pkg 编译后的包文件 src 源文件 遇到问题1&#xff1a;运行 ‘go build awesomeProject…

Linux-文件创建拷贝删除剪切

文章目录Linux文件相关命令ls通配符含义touch 创建文件命令示例cp 拷贝文件rm 删除文件mv剪切文件Linux文件相关命令 ls ls是英文单词list的简写&#xff0c;其功能为列出目录的内容&#xff0c;是用户最常用的命令之一&#xff0c;它类似于DOS下的dir命令。 Linux文件或者目…

RabbitMQ:交换机(Exchange)

目录一、概述二、Direct Exchange &#xff08;直连型交换机&#xff09;三、Fanout Exchange&#xff08;扇型交换机&#xff09;四、Topic Exchange&#xff08;主题交换机&#xff09;五、Header Exchange&#xff08;头交换机&#xff09;六、Default Exchange&#xff08;…

【实时Linux实战系列】基于实时Linux的物联网系统设计

随着物联网&#xff08;IoT&#xff09;技术的飞速发展&#xff0c;越来越多的设备被连接到互联网&#xff0c;形成了一个庞大而复杂的网络。这些设备从简单的传感器到复杂的工业控制系统&#xff0c;都在实时地产生和交换数据。实时Linux作为一种强大的操作系统&#xff0c;为…

第五天~提取Arxml中描述信息New_CanCluster--Expert

🔍 ARXML描述信息提取:挖掘汽车电子设计的"知识宝藏" 在AUTOSAR工程中,描述信息如同埋藏在ARXML文件中的金矿,而New_CanCluster--Expert正是打开这座宝藏的密钥。本文将带您深度探索ARXML描述信息的提取艺术,解锁汽车电子设计的核心知识资产! 💎 为什么描述…

开源 C++ QT Widget 开发(一)工程文件结构

文章的目的为了记录使用C 进行QT Widget 开发学习的经历。临时学习&#xff0c;完成app的开发。开发流程和要点有些记忆模糊&#xff0c;赶紧记录&#xff0c;防止忘记。 相关链接&#xff1a; 开源 C QT Widget 开发&#xff08;一&#xff09;工程文件结构-CSDN博客 开源 C…

手写C++ string类实现详解

类定义cppnamespace ym {class string {private:char* _str; // 字符串数据size_t _size; // 当前字符串长度size_t _capacity; // 当前分配的内存容量static const size_t npos -1; // 特殊值&#xff0c;表示最大可能位置public:// 构造函数和析构函数string(…

C++信息学奥赛一本通-第一部分-基础一-第3章-第2节

C信息学奥赛一本通-第一部分-基础一-第3章-第2节 2057 星期几 #include <iostream>using namespace std;int main() {int day; cin >> day;switch (day) {case 1:cout << "Monday";break;case 2:cout << "Tuesday";break;case 3:c…

【leetcode 3】最长连续序列 (Longest Consecutive Sequence) - 解题思路 + Golang实现

最长连续序列 (Longest Consecutive Sequence) - LeetCode 题解 题目描述 给定一个未排序的整数数组 nums&#xff0c;找出数字连续的最长序列&#xff08;不要求序列元素在原数组中连续&#xff09;的长度。要求设计并实现时间复杂度为 O(n) 的算法解决此问题。 示例 1&#x…

矿物分类系统开发笔记(一):数据预处理

目录 一、数据基础与预处理目标 二、具体预处理步骤及代码解析 2.1 数据加载与初步清洗 2.2 标签编码 2.3 缺失值处理 &#xff08;1&#xff09;删除含缺失值的样本 &#xff08;2&#xff09;按类别均值填充 &#xff08;3&#xff09;按类别中位数填充 &#xff08;…

《UE5_C++多人TPS完整教程》学习笔记43 ——《P44 奔跑混合空间(Running Blending Space)》

本文为B站系列教学视频 《UE5_C多人TPS完整教程》 —— 《P44 奔跑混合空间&#xff08;Running Blending Space&#xff09;》 的学习笔记&#xff0c;该系列教学视频为计算机工程师、程序员、游戏开发者、作家&#xff08;Engineer, Programmer, Game Developer, Author&…

TensorRT-LLM.V1.1.0rc1:Dockerfile.multi文件解读

一、TensorRT-LLM有三种安装方式&#xff0c;从简单到难 1.NGC上的预构建发布容器进行部署,见《tensorrt-llm0.20.0离线部署DeepSeek-R1-Distill-Qwen-32B》。 2.通过pip进行部署。 3.从源头构建再部署&#xff0c;《TensorRT-LLM.V1.1.0rc0:在无 GitHub 访问权限的服务器上编…

UniApp 实现pdf上传和预览

一、上传1、html<template><button click"takeFile">pdf上传</button> </template>2、JStakeFile() {// #ifdef H5// H5端使用input方式选择文件const input document.createElement(input);input.type file;input.accept .pdf;input.onc…

《用Proxy解构前端壁垒:跨框架状态共享库的从零到优之路》

一个项目中同时出现React的函数式组件、Vue的模板语法、Angular的依赖注入时,数据在不同框架体系间的流转便成了开发者不得不面对的难题—状态管理,这个本就复杂的命题,在跨框架场景下更显棘手。而Proxy,作为JavaScript语言赋予开发者的“元编程利器”,正为打破这道壁垒提…

MOESI FSM的全路径测试用例

MOESI FSM的全路径测试用例摘要&#xff1a;本文首先提供一个UVM版本的测试序列&#xff08;基于SystemVerilog和UVM框架&#xff09;&#xff0c;设计为覆盖MOESI FSM的全路径&#xff1b;其次详细解释如何使用覆盖组&#xff08;covergroup&#xff09;来量化测试的覆盖率&am…

git仓库和分支的关系

1️⃣ 仓库分支&#xff08;Repository Branch&#xff09;每个 Git 仓库都有自己的分支结构。分支决定你当前仓库看到的代码版本。示例&#xff1a;仓库分支只是局部修改&#xff0c;项目分支才是全局管理所有仓库分支的概念。wifi_camera 仓库&#xff1a; - main - dev - fe…

Linux的基本操作

Linux 系统基础操作完整指南一、文件与目录操作1. 导航与查看pwd (Print Working Directory)作用&#xff1a;显示当前所在目录的完整路径示例&#xff1a;pwd → 输出 /home/user/documents使用场景&#xff1a;当你在多层目录中迷失时快速定位当前位置ls (List)常用选项&…

npm设置了镜像 pnpm还需要设置镜像吗

npm配置镜像后是否需要为pnpm单独设置镜像&#xff1f; 是的&#xff0c;即使您已经为npm设置了镜像源&#xff08;如淘宝镜像&#xff09;&#xff0c;仍然需要单独为pnpm配置镜像源。这是因为npm和pnpm是两个独立的包管理工具&#xff0c;它们的配置系统和环境变量是分离的&a…