RDLC issues on monitors with higher recommended scaling
Asked Answered
F

2

8

I'm on .net framework 4.8 in my WPF app and I have two usages on RDLC. 1st is a fully fetched ReportViewer that uses a DataTable from postgres, 2nd is just a LocalReport with small number of parameters rendered as EMF and printed directly with use of default printer.

They both have what would seem to be rendering issues, but just on monitors that have recommended scaling (RS) >100%. The outcome is squashing of letters vertically and adding some extra space in between (I can provide samples as soon as I get access to client machine again). If I just increase scaling on my 100% RS monitor, everything prints out just fine. If I replace the >100% RS monitor with a 1080p 100% RS one, again, everything prints out fine. Printouts on machines with monitors with >100% RS come out always messed up irrelevant of the scaling I set in Windows. Issues can be quickly reproduced with just 'Print Layout' view in ReportViewer, exporting to PDF produces same results.

Since I have ReportViewer and a direct printout of LocalReport I was able to try out several different approaches:

  • Making the app DPIAware / not aware / true/PM etc. (also included manifest, App.config and App.xaml changes)
  • Putting the ReportViewer in ViewBox
  • Using DpiX/Y and PrintDpiX/Y on DeviceInfo
  • ScaleTransform and DrawImageUnscaled on PrintPage callback with and without the DeviceInfo changes
  • countless printer options in Windows

Client machines run on either latest Windows 10 or close to latest and are rather empty otherwise.

Does it ring any bells? Any idea for potential fix?

I would love to use RDLC in my app, for the simplicity of development and usage, but those issues are really a no go for the technology.

Code

No preview printout

Used to print a single document directly without preview from parameters.

    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;
            }
        }
    }

Preview 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# code part


        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();
        }
Fob answered 18/7, 2021 at 10:52 Comment(9)
Can this resolve your issue? (This is for WinForm though.) Report Viewer - scaling issue with system DPI changeButtons
Thanks for your answer @HardcoreGamer, I've already tried that.Fob
I built a testing project but not able replicate the result that is significant enough like you mentioned, can you include and specify any particular fonts, language and words in your question that can reproduce this issue?Buttons
@Buttons sounds exciting, I will need some time to provide that. What's the recommended scaling of your monitor? On my client machine setup it's 200%, so I guess the differences might be higher with higher RS. As for fonts I've used Arial, Calibri, both on size 7. I also have C39HrP24DmTt for barcode (this one is fragile when it come to rendering issues). Language is Polish, but the effect happens on words with and without special characters, example words could be: Badanie, PESEL, Pacjent, ROZPOZNANIE, Komórki nabłonka gruczołowego.Fob
Tried on 1920 x 1080 monitor with 100% - 175% scale with Arial, 10pt, Font. My test result isn't big enough for me to see the issue. Thanks for the samples. I'd test this when I have time.Buttons
I think you would need a monitor with bigger windows recommended scaling to observe the issue :( Let me see if this could be emulated somehowFob
I tried on a 2560 monitor and scale up to 225%. Is this what you meant? i.e. The space between letter "w" and "e". But the exported PDF seems fine though..Buttons
I'll provide some samples today. Recommended scaling example - in display settings does your monitor have higher recommended scaling than 100%?Fob
Let us continue this discussion in chat.Buttons
B
4

If there are no fixes available for RDLC scaling issue on WPF app.

One possible workaround would be migrating the file rendering part to Web version of RDLC, which would ignore screen DPI (as far as I know).

You'll need extra resource to develop this.

But a few generic functions would be enough, in most cases.

Then your reports should be able to rendered with consistence scaling.


(You may not need addtional project library for ReportViewer.WebForms if the library Microsoft.ReportViewer.Common version can be used by both WebForms and WinForms ReportViewer.)

Here's the possible solution:

1) Add a Library Project to your WPF solution

The solution should use .NET Framework 4+. It would look something like this.

enter image description here

2) Download the WebForm version of RDLC to the new library through NuGet

Look for Microsoft.ReportViewer.WebForms by Microsoft.

Correct version of Microsoft.ReportViewer.Common will be installed for you as dependence.

enter image description here

3) Create the code for Rendering through Web Version of RDLC

Create a static class for WDF project to use, this is a very simple sample for you to test if it works, before you continuing on.

Copy this class in the "RLDCRendering" Project:

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: "");
        }
    }
}

The project would looks like this:

enter image description here

4) Hide the WPF version's report print button

Hide the Print /Save button with this example code so users would not use the faulted rendering method

ReportViewer.ShowPrintButton = false;
ReportViewer.ShowExportButton = false;

Add a print button on your WDF page, how you do it is up to you.

The end result is something like this: enter image description here

Add a callback when the button is clicked, then provide all the needed data source, report path, output path to the library we created.

The follow is a sample code for you:

            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) Validate

You may change the output path as you need. When the button is clicked, a PDF file should be rendered and saved under the location you specified. In my sample, it is in C:\test\test.pdf.

If this solution works for you, you may continue to add parameters and etc. to the/other rendering function byte[] RenderReport.

Then handle the returned byte file by sending it to printer or save to some local folder and open with other applications.

Buttons answered 26/7, 2021 at 4:20 Comment(4)
Hello @Hardcore Gamer, can you please show your xaml for using WebForms in C# wpf? I have serious issues thereFob
@Fob Do not use WebForm for C# WPF, this is only meant to be used for rendering files for printing. The reason I load the WebForm version of ReportViewer into different project is because WebForm and WinForm version won't match. You likely loaded a incompatible version of ReportViewer.Common library in your WPF project? Sample xaml, FYIButtons
Damn, this works, wow. Thank you for that. One simplification to this approach is that you don't need the other project, it's enough to use Microsoft.ReportViewer.WebForms for rendering instead. I still have to handle printing the pdf from byte array but just after saving it, everything is fine. I guess we are still far from the root cause or we don't quite know the perfect solution, but I'm happy. Thank you!Fob
You're welcome, great to hear your result and glad I can help! Maybe one day WinForms will go open source and we may know : ). I will update my answer later for helping others with similar issues. For now, like you said you still have to handle different way to print the files, so good luck!Buttons
B
0

The problem is well known one, it affects performance as well. The reason is simple:

RDLC was created to deliver simple reports such as receipt or invoice. anything more then that is going to cause you alot of issues and alot of headache.

There are simple solution offered by Microsoft technical support across the web:

  • Change the system DPI settings
  • Change the font on report
  • Make the report viewer adapt to higher resolution automatically.

But all of them just ignore the simple fact, RDLC was never meant for big reports or higher resolutions. Only for documents such as invo ice or receipt which are modest and with small amount of details to address

Brooking answered 21/7, 2021 at 4:30 Comment(5)
Hello @Barr, by system dpi setting you mean scaling? I've tried that one already. As for the font on the report, I haven't tried this one yet, let me have a go on it. The report view seems to be irrelevant as the Local Report works without it and any possible solution should cover it as well.Fob
Figured as much, as I said if the font won't work than RDLC is no the proper solution for you.Brooking
I'm using Calibri font (and some barcode one), which font would you recommend as a safe test instead of Calibri?Fob
Ariel is always a safe bet for testingBrooking
Then as I said, RDLC is not the solution for you.Brooking

© 2022 - 2024 — McMap. All rights reserved.