问题描述
我在 WPF 应用程序中使用 .net framework 4.8,并且我在 RDLC 上有两种用法。第一个是完全获取的 ReportViewer,它使用来自 postgres 的数据表,第二个只是一个 LocalReport,其中有少量参数呈现为 EMF,并使用默认打印机直接打印。
它们似乎都存在渲染问题,但只是在建议缩放 (RS) > 100% 的显示器上。结果是垂直压缩字母并在它们之间添加一些额外的空间(我可以在再次访问客户端计算机后立即提供示例)。如果我只是在我的 100% RS 显示器上增加缩放比例,那么一切都打印得很好。如果我用 1080p 100% RS 显示器替换 >100% RS 显示器,同样,一切都可以正常打印。与我在 Windows 中设置的缩放比例无关,在显示器上的打印输出 > 100% RS 出来总是一团糟。只需使用 ReportViewer 中的“打印布局”视图即可快速重现问题,导出为 PDF 会产生相同的结果。
因为我有 ReportViewer 和 LocalReport 的直接打印输出,所以我可以尝试几种不同的方法:
- 使应用成为 DPIAware/notaware/true/PM 等(还包括 manifest、App.config 和 App.xaml 更改)
- 将 ReportViewer 放在 ViewBox 中
- 在 DeviceInfo 上使用 DpiX/Y 和 PrintDpiX/Y
- 有和没有 DeviceInfo 更改的 PrintPage 回调上的 ScaleTransform 和 DrawImageUnscaled
- Windows 中的无数打印机选项
客户端机器运行在最新的 Windows 10 或接近最新的 Windows 10 上,否则相当空。
它会响铃吗?有没有潜在修复的想法?
为了简化开发和使用,我很想在我的应用中使用 RDLC,但这些问题对于技术来说确实是不可行的。
代码
无预览打印
用于直接打印单个文档,无需参数预览。
class CytologiaPrinter : Idisposable
{
private static readonly ILog log = LogManager.GetLogger(typeof(CytologiaPrinter));
private int m_currentPageIndex;
private IList<Stream> m_streams;
private int WizytaID;
private CytologiaAnkieta Cytologia;
public CytologiaPrinter(int wizytaID)
{
WizytaID = wizytaID;
}
public CytologiaPrinter(CytologiaAnkieta cytologia)
{
Cytologia = cytologia;
}
public void Print()
{
try
{
CytologiaAnkieta cytologia;
if (Cytologia == null)
{
cytologia = DBCommunication.fetchCytologia(WizytaID);
}
else
{
cytologia = Cytologia;
}
if (cytologia != null && cytologia.AnkietaNumer != null && cytologia.AnkietaNumer.Length > 0)
{
LocalReport report = new LocalReport();
var cytologie = new List<CytologiaAnkieta>();
cytologie.Add(cytologia);
ReportDataSource reportDataSource = new ReportDataSource("DataSet1",cytologie);
report.DataSources.Add(reportDataSource);
report.ReportEmbeddedResource = "suplement.CytologiaAnkieta.rdlc";
var parameters = new List<ReportParameter>();
//parameters.Add(...); //setting all parameters omitted for demo
report.SetParameters(parameters);
m_currentPageIndex = 0;
Print(cytologia);
}
}
catch (Exception ex)
{
log.Error("Error (" + ex.Message + "),stack:" + ex.StackTrace);
}
}
private Stream CreateStream(string name,string fileNameExtension,Encoding encoding,string mimeType,bool willSeek)
{
Stream stream = new MemoryStream();
m_streams.Add(stream);
return stream;
}
private void Export(LocalReport report)
{
string deviceInfo =
@"<DeviceInfo>
<OutputFormat>EMF</OutputFormat>
<PageWidth>29.7cm</PageWidth>
<PageHeight>21cm</PageHeight>
<MarginTop>1cm</MarginTop>
<MarginLeft>1cm</MarginLeft>
<MarginRight>1cm</MarginRight>
<MarginBottom>1cm</MarginBottom>
</DeviceInfo>"; //printing in landscape
Warning[] warnings;
m_streams = new List<Stream>();
report.Render("Image",deviceInfo,CreateStream,out warnings);
if (warnings != null && warnings.Length > 0)
{
foreach (var warn in warnings)
{
log.Warn("Cytologia printing issues: " + warn.Message);
}
}
foreach (Stream stream in m_streams)
stream.Position = 0;
}
private void PrintPage(object sender,PrintPageEventArgs ev)
{
Metafile pageImage = new
Metafile(m_streams[m_currentPageIndex]);
Rectangle adjustedRect = new Rectangle(
ev.PageBounds.Left - (int)ev.PageSettings.HardMarginX,ev.PageBounds.Top - (int)ev.PageSettings.HardMarginY,ev.PageBounds.Width,ev.PageBounds.Height);
ev.Graphics.FillRectangle(Brushes.White,adjustedRect);
ev.Graphics.DrawImage(pageImage,adjustedRect);
m_currentPageIndex++;
ev.HasMorePages = m_currentPageIndex < m_streams.Count;
}
private void Print(CytologiaAnkieta cytologia)
{
if (m_streams == null || m_streams.Count == 0)
throw new Exception("Error: no stream to print.");
PrintDocument printDoc = new PrintDocument();
printDoc.DefaultPageSettings.Landscape = true;
if (!printDoc.PrinterSettings.IsValid)
{
throw new Exception("Error: cannot find the default printer.");
}
else
{
printDoc.PrintPage += new PrintPageEventHandler(PrintPage);
m_currentPageIndex = 0;
printDoc.Print();
}
}
public void dispose()
{
if (m_streams != null)
{
foreach (Stream stream in m_streams)
stream.Close();
m_streams = null;
}
}
}
预览 WinForms
Xaml
xmlns:rv="clr-namespace:Microsoft.Reporting.WinForms;assembly=Microsoft.ReportViewer.WinForms"
...
<windowsformshost DockPanel.Dock="Bottom" Margin="0 0 0 0" >
<rv:ReportViewer x:Name="RVDemo"/>
</windowsformshost>
C# 代码部分
private void RaportGenerate_Click(object sender,RoutedEventArgs e)
{
RVDemo.Reset();
ReportDataSource reportDataSource = new ReportDataSource("Ankiety",DBCommunication.fetchCytologiaAnkietyReport(...));
RVDemo.LocalReport.DataSources.Add(reportDataSource);
RVDemo.LocalReport.ReportEmbeddedResource = "suplement.Cytologie.rdlc";
var parameters = new List<ReportParameter>();
//parameters.Add(...); // omitted for demo
RVDemo.LocalReport.SetParameters(parameters);
RVDemo.RefreshReport();
}
解决方法
如果 WPF 应用程序上的 RDLC 缩放问题没有可用的修复程序。
一种可能的解决方法是将文件渲染部分迁移到 RDLC 的 Web 版本,这将忽略屏幕 DPI(据我所知)。
你需要额外的资源来开发这个。
但在大多数情况下,一些通用函数就足够了。
那么您的报告应该能够以一致性缩放呈现。
(如果库 Microsoft.ReportViewer.Common
版本可以被 WebForms 和 WinForms ReportViewer 使用,则您可能不需要 ReportViewer.WebForms 的附加项目库。)
以下是可能的解决方案:
1) 将库项目添加到您的 WPF 解决方案
解决方案应使用 .NET Framework 4+。它看起来像这样。
2) 通过 NuGet 将 WebForm 版本的 RDLC 下载到新库
查找 Microsoft 的 Microsoft.ReportViewer.WebForms
。
将为您安装正确版本的 Microsoft.ReportViewer.Common
作为依赖项。
3) 通过 RDLC 的 Web 版本创建渲染代码
为 WDF 项目创建一个静态类以供使用,这是一个非常简单的示例,您可以在继续之前测试它是否有效。
在“RLDCRendering”项目中复制这个类:
using Microsoft.Reporting.WebForms;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace RDLCRendering
{
public class RDLCRender
{
public static byte[] RenderReport(String reportPath,DataTable data)
{
Microsoft.Reporting.WebForms.ReportDataSource rdc1 =
new Microsoft.Reporting.WebForms.ReportDataSource("DataSet1",data);
Microsoft.Reporting.WebForms.ReportViewer v1 = new Microsoft.Reporting.WebForms.ReportViewer();
v1.LocalReport.DataSources.Clear();
v1.LocalReport.ReportPath = reportPath;
v1.LocalReport.DataSources.Add(rdc1);
return v1.LocalReport.Render(format: "PDF",deviceInfo: "");
}
}
}
项目看起来像这样:
4) 隐藏 WPF 版本的报表打印按钮
使用此示例代码隐藏打印/保存按钮,这样用户就不会使用有问题的渲染方法
ReportViewer.ShowPrintButton = false;
ReportViewer.ShowExportButton = false;
在您的 WDF 页面上添加打印按钮,具体操作由您决定。
添加按钮被点击时的回调,然后提供我们创建的库的所有需要的数据源、报表路径、输出路径。
以下是为您提供的示例代码:
string connString = "Server=someIP;Database=catalogName;User Id=uid;Password=pwd";
SqlConnection sqlConn = new SqlConnection(connString);
SqlDataAdapter sqlDA = new SqlDataAdapter("select top 100 * from samplesData",sqlConn);
DataTable dt= new DataTable();
sqlDA.Fill(dt);
//Important
Byte[] bytes = RDLCRendering.RDLCRender.RenderReport(@"the path to the report teamplate\Report1.rdlc",dt);
using (FileStream stream = new FileStream(@"C:\test\test.pdf",FileMode.Create))
{
stream.Write(bytes,bytes.Length);
}
5) 验证
您可以根据需要更改输出路径。单击该按钮时,应呈现 PDF 文件并将其保存在您指定的位置下。在我的示例中,它位于 C:\test\test.pdf 中。
如果此解决方案适合您,您可以继续向/其他渲染函数 byte[] RenderReport
添加参数等。
然后通过将返回的字节文件发送到打印机或保存到某个本地文件夹并使用其他应用程序打开来处理返回的字节文件。
,这个问题是众所周知的,它也会影响性能。原因很简单:
创建 RDLC 是为了提供简单的报告,例如收据或发票。除此之外的任何事情都会给你带来很多问题和很多头痛。
微软技术支持在网络上提供了简单的解决方案:
- 更改系统 DPI 设置
- 更改报告字体
- 使报告查看器自动适应更高分辨率。
但他们都忽略了一个简单的事实,RDLC 从来就不是用于大型报告或更高的分辨率。 仅适用于 invo 等文件 冰或收据不大,需要处理的细节很少
,在 DPI 缩放比例为 set > 100%
的情况下,无法从 MS reportviewer 打印标签,因为它会将 2" x 3"
标签的内容缩小到无法读取字体的位置。
需要修复,要么完全删除缩放,要么添加复选框以禁用报表查看器和 RDLC 报告本身的自动缩放。
更新 1:
在 DPI 比例因子高于 96 (100%) 的机器上使用 Windows Forms ReportViewer 时,Windows 会将应用程序位图拉伸到预期的物理大小。 这使得报表查看器和报表内容变得模糊。但是,当您尝试打印报表时,Windows 窗体查看器始终基于 96 DPI 进行布局计算,并且它将打印报表,因为它将以 96 DPI 预览。这是因为您在其中使用 Windows 窗体报告查看器的应用程序未声明为 DPI 感知。
默认情况下,WinForms 应用程序被声明为 DPI Unaware。因此,报告查看器和报告的物理大小无法自动处理 DPI 缩放,因此在许多常见使用场景中会呈现模糊或大小不正确。因此,在打印报告时,应用程序不会对更改的 DPI 做出响应(不知道机器更改的设置)并且不正确地缩放。
推荐的方法是在您的应用程序中设置 dpiAware 元素,以避免进一步的缩放、视觉或交互问题。
例如:
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3" >
<asmv3:application>
<asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
<dpiAware>True</dpiAware>
</asmv3:windowsSettings>
</asmv3:application>
</assembly>
更新 2