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
英文地址,請注明出處:http://en.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…