printing quality winform

后端 未结 4 2030
囚心锁ツ
囚心锁ツ 2020-12-06 06:02

I have 2 problems while trying to print from a WinForms application. The first is a very very bad quality no matter what I try. The second is that I have a big page margin f

相关标签:
4条回答
  • 2020-12-06 06:30

    I spent days looking for a way to print a panel and its contents in high quality. This didnt work, and I tried other peoples codes and they were all either wrong or just bad quality, until I found this:

    http://rkinfopedia.blogspot.com/2008/07/printing-contents-of-panel-control.html

    just put the eventhandler inside the print button click event handler, and include the print method in it too, like this:

    private void button3_Click(object sender, EventArgs e)
    {
        printdoc1.PrintPage += new PrintPageEventHandler(printdoc1_PrintPage);
        Print(panel1);
    }
    

    and place an if statement inside the override OnPaint method, like this:

    protected override void OnPaint(PaintEventArgs e)
    {
        if (MemoryImage != null)
        {
            e.Graphics.DrawImage(MemoryImage, 0, 0);
            base.OnPaint(e);
        }
    }
    

    rest is fine as is, and youll finally get almost perfect print quality

    Just wanted to share this gem, you are welcome internet stranger!

    Thank you Mr. Rakesh!

    0 讨论(0)
  • 2020-12-06 06:31

    You can actually print sharper controls by applying scaling to them on a "vector" level instead of bitmap level.

    This snapshot shows the result of the following technique (please don't mind my Win2000-ish UI :-) ):

    Composite

    What we do is to iterate through the control's ControlCollection in a very similar fashion as rene shows in his answer.

    But - in addition we apply scale to location, size and font to the control itself before we draw its bitmap to a preset size bitmap which in this case is 5 times bigger (4 times would represent about 300 DPI which is the effective print resolution on most printers).

    The reason for this is to keep the thin line on the control sharp on print, or we could just scale the bitmap itself which wouldn't give us any benefit resolution-wise. By scaling font we reduce the anti-alias effect and can provide a better print quality.

    To do so you can first in the button's click event setup the following:

    //this will produce 5x "sharper" print
    MemoryImage = new Bitmap((Panel1.Width * 5), (Panel1.Height * 5));
    
    Using Graphics g = Graphics.FromImage(MemoryImage) {
        ScaleControls(Panel1, g, 5);
    };
    
    PrintPreviewDialog1.Document = printdoc1;
    PrintPreviewDialog1.ShowDialog();
    

    Now in the ScaleControls function, which is recursive, we scale location, size and font in order to make each control itself in higher resolution before we draw them to the bitmap:

    private void ScaleControls(Control c, ref Graphics g, double s)
    {
        //To detach controls for panels, groupboxes etc.
        List<Control> hold = null;
    
        foreach (Control ctrl in c.Controls) {
            if (ctrl is GroupBox || ctrl is Panel) {
                //backup reference to controls
                hold = new List<Control>();
                foreach (Control gctrl in ctrl.Controls) {
                    hold.Add(gctrl);
                }
                ctrl.Controls.Clear();
            }
    
            //backup old location, size and font (see explanation)
            Point oldLoc = ctrl.Location;
            Size oldSize = ctrl.Size;
            Font oldFont = ctrl.Font;
    
            //calc scaled location, size and font
            ctrl.Location = new Point(ctrl.Location.X * s, ctrl.Location.Y * s);
            ctrl.Size = new Size(ctrl.Size.Width * s, ctrl.Height * s);
            ctrl.Font = new Font(ctrl.Font.FontFamily, ctrl.Font.Size * 5,
                                 ctrl.Font.Style, ctrl.Font.Unit);
    
            //draw this scaled control to hi-res bitmap
            using (Bitmap bmp = new Bitmap(ctrl.Size.Width, ctrl.Size.Height)) {
                ctrl.DrawToBitmap(bmp, ctrl.ClientRectangle);
                g.DrawImage(bmp, ctrl.Location);
            }
    
            //restore control's geo
            ctrl.Location = oldLoc;
            ctrl.Size = oldSize;
            ctrl.Font = oldFont;
    
            //recursive for panel, groupbox and other controls
            if (ctrl is GroupBox || ctrl is Panel) {
                foreach (Control gctrl in hold) {
                    ctrl.Controls.Add(gctrl);
                }
    
                ScaleControls(ctrl, g, s);
            }
        }
    }
    

    and finally in the event handler for printing:

    double scale = MemoryImage.Width / e.PageBounds.Width;
    
    e.Graphics.DrawImage(MemoryImage, 0, 0,
              Convert.ToInt32(MemoryImage.Width / scale),
              Convert.ToInt32(MemoryImage.Height / scale));
    

    Now, in this example we scale the controls in-place. This is not ideal of course as they will appear to live their own life while we do a print preview.

    Ideally we would clone each control as we iterated and discard it after we drew to bitmap. This would also eliminate the need to backup geometries. But for the example of principle I left it as-is. I'll leave it to you to clone etc.

    The reason for detaching controls is that if we don't (as the code is now - this can surely be changed by providing another method of iterating, ie. pre-scale cloned controls) the non-scaled in the f.ex. GroupBox control will be printed first, then the scaled ones will show on top of those when they are iterated. This because we DrawToBitmap the GroupBox before scaling its controls. This is something I'll leave for you to handle.

    The bitmap we're working on will not necessary fit the resolution of the print that the user end up with from setting up the print dialog, but we get a higher resolution to work with which in turn yield a better result than the poor screen bitmap resolution we have initially.

    You would of course need to add support for special cases for other controls than Panel and GroupBox that can hold other controls, image controls and so forth.

    0 讨论(0)
  • 2020-12-06 06:33

    This comes up over and over again. There just is no magic solution, although eventually the problem is likely to disappear. The emergence of "retina" displays is pivotal.

    The core issue is that monitors have a resolution that's drastically worse than printers. A typical printer has a resolution of 600 dots per inch. Which makes it capable of printing 6600 x 5100 individual pixels on a piece of paper. Much, much more than what a monitor can display, a full HD monitor tops out at 1920 x 1080 pixels. Roughly a factor of 5 worse, give or take.

    This works out poorly when you print what shows up on a monitor on a piece of paper and try to keep it the same size. Inevitably, because of the lack of pixels on the monitor, each one pixel from the monitor is printed as a 5x5 blob on paper. If you try to keep the pixel mapping one-to-one, you will get a razor-sharp copy on paper. But it has turned into a postage-stamp.

    Inevitably, the printout looks very grainy due those pixel blobs. What looks especially poor is text. Operating systems use lots of tricks to make text look good on monitors with poor resolution. Anti-aliasing is standard, tricks like ClearType were designed to take advantage of monitor physics that can help increase the perceived resolution. This no longer works when the text is printed, those anti-aliasing pixels turn into blobs and become very visible, completely ruining the effect. Especially bad for ClearType text on a color printer, the red and blue color fringes now can be clearly seen.

    The only decent approach is to render to a printer using the actual resolution and not the monitor resolution. Like using the PrintDocument class in .NET. Using a report generator can help avoid having to write the code for it.

    0 讨论(0)
  • 2020-12-06 06:45

    You should draw yourself on the Graphics object you get when the PrintDocument prints. That gives you all the control you need. Still everything Hans Passant said applies here as well... Keep in mind that this is the simplest implementation that merely demo's what can be achieved, I'm not claiming that this is the easiest/best/most productive way... my code doesn't take multiple pages, controls contained in contaimers or controls not of the type Label and PictureBox.

    I used the Draw... methods from System.Drawing.Graphics

    sligthly adapted from the code above to get this working:

    public void GetPrintArea(Panel pnl, Graphics gr)
    {
        // scale to fit on width of page...
        if (pnl.Width > 0)
        {
          gr.PageScale = gr.VisibleClipBounds.Width/pnl.Width;
        }
        // this should recurse...
        // just for demo so kept it simple
        foreach (var ctl in pnl.Controls)
        {
            // for every control type
            // come up with a way to Draw its
            // contents
            if (ctl is Label)
            {
                var lbl = (Label)ctl;
                gr.DrawString(
                    lbl.Text,
                    lbl.Font,
                    new SolidBrush(lbl.ForeColor),
                    lbl.Location.X,  // simple based on the position in the panel
                    lbl.Location.Y);
            }
            if (ctl is PictureBox)
            {
                var pic = (PictureBox)ctl;
                gr.DrawImageUnscaledAndClipped(
                    pic.Image,
                    new Rectangle(
                        pic.Location.X,
                        pic.Location.Y,
                        pic.Width,
                        pic.Height));
            }
        }
    }
    
    void printdoc1_PrintPage(object sender, PrintPageEventArgs e)
    {
        e.Graphics.SmoothingMode = Drawing2D.SmoothingMode.HighQuality;
        e.Graphics.InterpolationMode =Drawing2D.InterpolationMode.HighQualityBilinear;
        e.Graphics.PixelOffsetMode = Drawing2D.PixelOffsetMode.HighQuality;
        GetPrintArea(panel1, e.Graphics);
    }
    
    0 讨论(0)
提交回复
热议问题