目標:在 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
嵌套。
SnapsToDevicePixels
與UseLayoutRounding
有助于邊界像素對齊,減少細紋模糊。
三、模型定義(數據與視圖綁定)
public class PrintImageModel
{public BitmapSource BitmapSource { get; set; }public string ImageInfo { get; set; } // 標題/說明(拍攝時間、層號等)
}
四、代碼綁定(含 DPI 統一與布局選擇)
說明:
先對圖片按深度/序號排序,只取前 6 張;
將位圖統一到 96 DPI,避免打印引擎因 DPI 不一致而縮放異常;
根據數量選擇 ItemsPanel;
綁定到
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()
,以防打印時跨線程訪問異常。
五、打印清晰度與縮放的關鍵點
統一 DPI(推薦 96):WPF 視覺樹的度量與渲染以 96 DPI 為基準。若源圖為 300 DPI,但尺寸以像素為準,打印時仍以像素為依據,可能出現縮放計算偏差,統一 DPI 可減少不可控因素。
像素對齊:啟用
SnapsToDevicePixels
與UseLayoutRounding
,避免邊界半像素導致的灰邊。高質量縮放:
RenderOptions.BitmapScalingMode="HighQuality"
能在縮小圖片時明顯改善清晰度。避免多層縮放:不要在
Image
外再套ViewBox
,否則縮放疊乘影響清晰度。打印管線:如對分頁/邊距有嚴格控制,考慮使用
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、橫豎版自動切換等)。