具有更高推荐比例的监视器上的 RDLC 问题
RDLC issues on monitors with higher recommended scaling
我在我的 WPF 应用程序中使用 .net framework 4.8,我在 RDLC 上有两种用法。第一个是完全获取的 ReportViewer,它使用来自 postgres 的 DataTable,第二个只是一个 LocalReport,其中包含少量参数,呈现为 EMF 并使用默认打印机直接打印。
它们似乎都有渲染问题,但只是在建议缩放 (RS) >100% 的显示器上。结果是垂直挤压字母并在其间添加一些额外的 space(我可以在再次访问客户端计算机后立即提供示例)。如果我只是增加我的 100% RS 显示器的缩放比例,一切都打印出来就好了。如果我用 1080p 100% RS 显示器替换 >100% RS 显示器,一切都可以正常打印。与我在 Windows 中设置的缩放比例无关,带有 >100% RS 显示器的机器上的打印输出总是乱七八糟。只需 'Print Layout' 在 ReportViewer 中查看即可快速重现问题,导出为 PDF 会产生相同的结果。
因为我有 ReportViewer 和 LocalReport 的直接打印输出,所以我能够尝试几种不同的方法:
- 使应用程序成为 DPIAware/不知道/true/PM 等(还包括清单、App.config 和 App.xaml 更改)
- 将 ReportViewer 放入 ViewBox
- 在 DeviceInfo
上使用 DpiX/Y 和打印DpiX/Y
- PrintPage 回调中的 ScaleTransform 和 DrawImageUnscaled,有和没有 DeviceInfo 更改
- Windows
中的无数打印机选项
客户机 运行 最新 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();
}
这个问题是众所周知的,它也会影响性能。原因很简单:
RDLC 的创建是为了提供简单的报告,例如收据或发票。任何更多的事情都会给你带来很多问题和很多头痛。
Microsoft 技术支持在网络上提供了简单的解决方案:
- 更改系统 DPI 设置
- 更改报告的字体
- 使报表查看器自动适应更高的分辨率。
但他们都忽略了一个简单的事实,RDLC 从来就不是为大报告或更高分辨率而设计的。
仅针对 invo 等文件
ice 或 receipt 是适度的,需要处理的细节很少
如果 WPF 应用程序上的 RDLC 缩放问题没有可用的修复程序。
一种可能的解决方法是将文件呈现部分迁移到 RDLC 的 Web 版本,这将忽略屏幕 DPI(据我所知)。
你需要额外的资源来开发它。
但在大多数情况下,一些通用函数就足够了。
然后您的报告应该能够使用一致性缩放呈现。
(如果 WebForms 和 WinForms ReportViewer 都可以使用 Microsoft.ReportViewer.Common
版本的库,您可能不需要 ReportViewer.WebForms 的附加项目库。)
以下是可能的解决方案:
1) 将库项目添加到您的 WPF 解决方案
解决方案应使用 .NET Framework 4+。它看起来像这样。
2)通过NuGet下载WebForm版本的RDLC到新库
寻找 Microsoft 的 Microsoft.ReportViewer.WebForms
。
将为您安装 Microsoft.ReportViewer.Common
的正确版本作为依赖项。
3) 创建通过 Web 版本的 RDLC 渲染的代码
创建静态 class 供 WDF 项目使用,这是一个非常简单的示例,供您在继续之前测试它是否有效。
在“RLDCRendering”项目中复制此class:
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, 0, bytes.Length);
}
5) 验证
您可以根据需要更改输出路径。单击该按钮时,应呈现一个 PDF 文件并将其保存在您指定的位置下。在我的示例中,它位于 C:\test\test.pdf.
如果此解决方案适合您,您可以继续向 the/other 渲染函数添加参数等 byte[] RenderReport
。
然后通过将返回的字节文件发送到打印机或保存到某个本地文件夹并用其他应用程序打开来处理返回的字节文件。
我在我的 WPF 应用程序中使用 .net framework 4.8,我在 RDLC 上有两种用法。第一个是完全获取的 ReportViewer,它使用来自 postgres 的 DataTable,第二个只是一个 LocalReport,其中包含少量参数,呈现为 EMF 并使用默认打印机直接打印。
它们似乎都有渲染问题,但只是在建议缩放 (RS) >100% 的显示器上。结果是垂直挤压字母并在其间添加一些额外的 space(我可以在再次访问客户端计算机后立即提供示例)。如果我只是增加我的 100% RS 显示器的缩放比例,一切都打印出来就好了。如果我用 1080p 100% RS 显示器替换 >100% RS 显示器,一切都可以正常打印。与我在 Windows 中设置的缩放比例无关,带有 >100% RS 显示器的机器上的打印输出总是乱七八糟。只需 'Print Layout' 在 ReportViewer 中查看即可快速重现问题,导出为 PDF 会产生相同的结果。
因为我有 ReportViewer 和 LocalReport 的直接打印输出,所以我能够尝试几种不同的方法:
- 使应用程序成为 DPIAware/不知道/true/PM 等(还包括清单、App.config 和 App.xaml 更改)
- 将 ReportViewer 放入 ViewBox
- 在 DeviceInfo 上使用 DpiX/Y 和打印DpiX/Y
- PrintPage 回调中的 ScaleTransform 和 DrawImageUnscaled,有和没有 DeviceInfo 更改
- Windows 中的无数打印机选项
客户机 运行 最新 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();
}
这个问题是众所周知的,它也会影响性能。原因很简单:
RDLC 的创建是为了提供简单的报告,例如收据或发票。任何更多的事情都会给你带来很多问题和很多头痛。
Microsoft 技术支持在网络上提供了简单的解决方案:
- 更改系统 DPI 设置
- 更改报告的字体
- 使报表查看器自动适应更高的分辨率。
但他们都忽略了一个简单的事实,RDLC 从来就不是为大报告或更高分辨率而设计的。 仅针对 invo 等文件 ice 或 receipt 是适度的,需要处理的细节很少
如果 WPF 应用程序上的 RDLC 缩放问题没有可用的修复程序。
一种可能的解决方法是将文件呈现部分迁移到 RDLC 的 Web 版本,这将忽略屏幕 DPI(据我所知)。
你需要额外的资源来开发它。
但在大多数情况下,一些通用函数就足够了。
然后您的报告应该能够使用一致性缩放呈现。
(如果 WebForms 和 WinForms ReportViewer 都可以使用 Microsoft.ReportViewer.Common
版本的库,您可能不需要 ReportViewer.WebForms 的附加项目库。)
以下是可能的解决方案:
1) 将库项目添加到您的 WPF 解决方案
解决方案应使用 .NET Framework 4+。它看起来像这样。
2)通过NuGet下载WebForm版本的RDLC到新库
寻找 Microsoft 的 Microsoft.ReportViewer.WebForms
。
将为您安装 Microsoft.ReportViewer.Common
的正确版本作为依赖项。
3) 创建通过 Web 版本的 RDLC 渲染的代码
创建静态 class 供 WDF 项目使用,这是一个非常简单的示例,供您在继续之前测试它是否有效。
在“RLDCRendering”项目中复制此class:
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, 0, bytes.Length);
}
5) 验证
您可以根据需要更改输出路径。单击该按钮时,应呈现一个 PDF 文件并将其保存在您指定的位置下。在我的示例中,它位于 C:\test\test.pdf.
如果此解决方案适合您,您可以继续向 the/other 渲染函数添加参数等 byte[] RenderReport
。
然后通过将返回的字节文件发送到打印机或保存到某个本地文件夹并用其他应用程序打开来处理返回的字节文件。