Friday, December 13, 2024

A print service based on C#

   

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Printing;
using System.IO;
using System.Windows.Forms;

//
// Author:  Barret
//

namespace Infor.SunSystems.V4 {
    /// <summary>
    /// Print Service
    /// </summary>
    public class PrintService {
        /// <summary>
        /// Contruct method to register the print page event
        /// </summary>
        public PrintService() {
            _docToPrint.PrintPage += new PrintPageEventHandler(docToPrint_PrintPage);
        }

        /// <summary>
        /// Show the print dialog and execute print action if necessary
        /// </summary>
        /// <param name="printType"></param>
        /// <param name="font"></param>
        private void Print(PrintType printType, Font font) {
            _printType = printType;
            _printFont = font;
           
            // Allow the user to choose the page range he or she would
            // like to print.
            PrintDialog printDialog = new PrintDialog();

            printDialog.AllowSomePages = true;
           
            printDialog.UseEXDialog = false;

            // Show the help button.
            printDialog.ShowHelp = true;

            // Set the Document property to the PrintDocument for
            // which the PrintPage Event has been handled. To display the
            // dialog, either this property or the PrinterSettings property
            // must be set
            printDialog.Document = _docToPrint;
           
            DialogResult result = printDialog.ShowDialog();

            // If the result is OK then print the document.
            if (result == DialogResult.OK) {
                _docToPrint.Print();
            }
        }
       
        /// <summary>
        /// Set properties on the PrintDialog object and then display the dialog.
        /// </summary>
        /// <param name="printer"></param>
        /// <param name="pages"></param>
        /// <param name="font"></param>
        public void StartPrint(string printer, IList<string> pages, int linesPerPage, Font font) {
            _pagesToPrint = pages;
            _linesPerPage = linesPerPage;
            // Set the default printer
            if (!string.IsNullOrEmpty(printer)) {
                _docToPrint.PrinterSettings.PrinterName = printer;
            }

            // Initialize the page range
            if (_pagesToPrint.Count > 1) {
                _docToPrint.PrinterSettings.MinimumPage = MINMUM_PAGE;
                _docToPrint.PrinterSettings.MaximumPage = _pagesToPrint.Count;
                _docToPrint.PrinterSettings.FromPage = _docToPrint.PrinterSettings.MinimumPage;
                _docToPrint.PrinterSettings.ToPage = _docToPrint.PrinterSettings.MaximumPage;
            }

            Print(PrintType.TextPages, font);
        }

        /// <summary>
        /// Set properties on the PrintDialog object and then display the dialog.
        /// </summary>
        /// <param name="pages"></param>
        /// <param name="font"></param>
        public void StartPrint(IList<string> pages, int linesPerPage, Font font) {
            StartPrint(null, pages, linesPerPage, font);
        }
       
        /// <summary>
        /// Set properties on the PrintDialog object and then display the dialog.
        /// </summary>
        /// <param name="text"></param>
        /// <param name="font"></param>
        public void StartPrint(string text, Font font) {
            _textToPrint = text;

            Print(PrintType.Text, font);
        }
       
        /// <summary>
        /// Set properties on the PrintDialog object and then display the dialog.
        /// </summary>
        /// <param name="streamToPrint"></param>
        /// <param name="printType"></param>
        /// <param name="font"></param>
        public void StartPrint(Stream streamToPrint, PrintType printType, Font font) {
            _streamToPrint = streamToPrint;

            Print(printType, font);
        }
       
        /// <summary>
        /// Handling the document's PrintPage event. The PrintDialog will print the document by call back this method.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void docToPrint_PrintPage(object sender, PrintPageEventArgs e) {
            // Insert code to render the page here.
            // This code will be called when the control is drawn.

            switch (_printType) {
                case PrintType.TextPages:
                    if (e.PageSettings.PrinterSettings.PrintRange == PrintRange.SomePages && _currentPage == 0) {
                        _currentPage = e.PageSettings.PrinterSettings.FromPage - 1;
                    }

                    ScaleReport(e);

                    e.Graphics.DrawString(_pagesToPrint[_currentPage], _printFont, Brushes.Black, e.MarginBounds.X,
                                          e.MarginBounds.Y);

                    _currentPage += 1;

                    ProcessMorePages(e);
                    break;
                   
                case PrintType.Text:
                    e.Graphics.DrawString(_textToPrint, _printFont, Brushes.Black, e.MarginBounds.X,
                                          e.MarginBounds.Y);
                    break;
                   
                case PrintType.Image:
                    Image image = Image.FromStream(_streamToPrint);
                    int x = e.MarginBounds.X;
                    int y = e.MarginBounds.Y;
                    int width = image.Width;
                    int height = image.Height;
                    if ((width / e.MarginBounds.Width) > (height / e.MarginBounds.Height)) {
                        width = e.MarginBounds.Width;
                        height = image.Height * e.MarginBounds.Width / image.Width;
                    } else {
                        height = e.MarginBounds.Height;
                        width = image.Width * e.MarginBounds.Height / image.Height;
                    }
                   
                    Rectangle destRect = new Rectangle(x, y, width, height);
                    e.Graphics.DrawImage(image, destRect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel);

                    break;
                   
                default:
                    break;
            }
        }

        /// <summary>
        /// Set property HasMorePages for event argument when there is more page to print
        /// </summary>
        /// <param name="e"></param>
        private void ProcessMorePages(PrintPageEventArgs e) {
            bool anyMorePage = (e.PageSettings.PrinterSettings.PrintRange != PrintRange.SomePages &&
                                _currentPage < _pagesToPrint.Count) ||
                               (e.PageSettings.PrinterSettings.PrintRange == PrintRange.SomePages &&
                                _currentPage < e.PageSettings.PrinterSettings.ToPage);
                   
            // Set the true for e.HasMorePages when there any more page waiting for print. Otherwise, set false to it.
            if (anyMorePage) {
                e.HasMorePages = true;
            } else {
                _currentPage = 0;
                e.HasMorePages = false;
            }
        }

        /// <summary>
        /// Scale the report to adapt to the width of paper when it is not enough for the whole repot width
        /// </summary>
        /// <param name="e"></param>
        private void ScaleReport(PrintPageEventArgs e) {
            float widthScale = GetScale(e, true);

            float heightScale = GetScale(e, false);

            e.Graphics.ScaleTransform(widthScale, heightScale);
        }

        private float GetScale(PrintPageEventArgs e, bool isWidth) {
            float scale = 1;
            int paperValue;
            float contentValue;
           
            if (isWidth) {
                paperValue = e.MarginBounds.Width;
                contentValue = (int)e.Graphics.MeasureString(ONE_LINE_REPORT, _printFont).Width;
            } else {
                paperValue = e.MarginBounds.Height;
                contentValue = (int)e.Graphics.MeasureString(ONE_LINE_REPORT, _printFont).Height * _linesPerPage;
            }
           
            // Evaluate the scale
            if ((contentValue > paperValue) && (paperValue > 0)) {
                scale = paperValue / contentValue;

                // Paper value is 'int' and it is not exact. So here to just it advance
                if (scale < 1 - 0.046F) {
                    scale += 0.046F;
                }
            }
           
            return scale;
        }

        // Declare the PrintDocument object.
        private PrintDocument _docToPrint = new PrintDocument();
       
        private PrintType _printType;
        private Font _printFont;

        private int _currentPage = 0;
        private int _linesPerPage;
       
        private IList<string> _pagesToPrint;
        private string _textToPrint;
        private Stream _streamToPrint;

        private static string ONE_LINE_REPORT = "------------------------------------------------------------------------------------------------------------------------------------";
        private static int MINMUM_PAGE = 1;
    }

    public enum PrintType {
        TextPages,
        Text,
        Image
    }
}

  

Graphics.MeasureString & TextRenderer.MeasureText

 In C#, Graphics.MeasureString & TextRenderer.MeasureTex, Which do we should use?

In a project I have experenced, there are many values as below:

    .
    .
    .

    SCCCCCCCCCCCCCCCCCCCCC
    SSCCCCCCCCCCCCCCCCCCCC
    SSSCCCCCCCCCCCCCCCCCCC
    SSSSCCCCCCCCCCCCCCCCCC
    SSSSSCCCCCCCCCCCCCCCCC
    SSSSSSCCCCCCCCCCCCCCCC
    .
    .
    .
S: Space
C: Character

The summary for spaces and characters in the same line is the same between every two lines.
I need evaluate the start position of characters according to the lenth of spaces, in order to all lines align in right. Then paint them on the panel.

Firstly, we evaluate the length of spaces in this way:
    private float GetSpaceWidth(int spaceNum, Font font) {            
        string temp = new string(' ', spaceNum);
        Size proposedSize = new Size(int.MaxValue, int.MaxValue);            
        Size s = TextRenderer.MeasureText(temp, font, proposedSize, TextFormatFlags.NoPrefix);
            
        return s.Width;
    }

Unluckily, I can not align them in right exactly. There allways exists some small difference.

After some analyze, I see that the TextRenderer.MeasureText just return Size struct. In Size struct, the type for both x and y is int. This is not exactly enough for paint align in right. Graphics.MeasureString return SizeF struct. In SizeF struct, the type for x and y is float.
So I have to update code as below:
    private float GetSpaceWidth(PaintEventArgs e, int spaceNum, Font font) {
        Graphics g = e.Graphics;
        string temp = new string(' ', spaceNum);            
        StringFormat format = new StringFormat(StringFormat.GenericDefault);
        format.FormatFlags = StringFormatFlags.MeasureTrailingSpaces;
        SizeF size = g.MeasureString(temp, font, int.MaxValue, format);
        return size.Width;
    }

As excepted, all lines align in right this time.

 

Conclution: If we can get the Graphics object(OnPaint mehtod), we should use its interface
MeasureString, because it is more exact than TextRenderer.MeasureText. When no other option, use TextRenderer.MeasureText instead.

Implement the functions of shortcut keys Ctrl+S and Ctrl+Esc using JavaScript

 The following is the specific code: 83 is the key value of the S key, and 27 is the key value of the Esc key

document.onkeydown = fnPortfolioKeyCheck;

function fnPortfolioKeyCheck(evt)

{

    evt = (evt) ? evt : window.event;

    if (evt.ctrlKey)

    {   

        if (83 == evt.keyCode)

        {

            goToPortfolioSaverPage();

        }

    }

    else if (27 == evt.keyCode)

    {

        if ("" == document.getElementById("showCompanyLookupId").style.display)

        {

            document.getElementById("showCompanyLookupId").style.display = "none";

            document.getElementById('divMask').style.display='none';

        }

        if ("" == document.getElementById("showPortfolioBrowserId").style.display)

        {

            document.getElementById("showPortfolioBrowserId").style.display = "none";

            document.getElementById('divMask').style.display='none';

        }

    }

}

How to search for DLL references in. net

 Commandline for compile C# code: csc.exe /out:Program.exe /t:exe /r:MSCorLib.dll Program.cs

When you use the /reference compiler switch to reference an assembly, you can
specify a complete path to a particular file. However, if you do not specify a path, the compiler
will search for the file in the following places (in the order listed):


1. Working directory.
2. The directory that contains the CSC.exe file itself. MSCorLib.dll is always obtained from
this directory. The path looks something like this: %SystemRoot%\Microsoft.NET\
Framework\v2.0.50727.
3. Any directories specified using the /lib compiler switch.
4. Any directories specified using the LIB environment variable.

Detect and avoid memory and resources leaks in .NET applications

 1 Introduction


Despite what a lot of people believe, it's easy to introduce memory and resources leaks in .NET applications. The Garbage Collector, or GC for close friends, is not a magician who would completely relieve you from taking care of your memory and resources consumption. It's important to understand leaks and how to avoid them, especially some 'key points'. You should be careful with these 'key points' when coding, because leaks are not the kind of things that is easy to detect automatically. They usually cost too much and may influence your project progress.

 

2 Prepare

 

2.1 Memory leaks

 

In computer science, a memory leak is a particular type of unintentional memory consumption by a computer program where the program fails to release memory when no longer needed. This condition is normally the result of a bug in a program that prevents it from freeing up memory that it no longer needs.

 

You should take it in heart that languages those provide automatic memory management, like Java, C#, VB.NET or LISP, are not immune to memory leaks.

 

2.2 Handles and resources

 

Memory is not the only resource to keep an eye on. When your .NET application runs on Windows, it consumes a whole set of system resources. Microsoft defines three categories of system objects: user, graphics device interface (GDI), and kernel.

 

User objects support window management: Accelerator tables, Carets, Cursors, DDE conversation(Dynamic Data Exchange Management Library), Hooks, Icons, Menus, Windows and Window position.

GDI objects support graphics: Bitmaps, Brushes, Device Contexts (DC), Enhanced metafile, Enhanced-metafile DC, Fonts, Memory DCs, Metafiles, Metafile DC, Palettes, Pen and extended pen, Regions, etc.

Kernel objects support memory management, process execution, and inter-process communications (IPC): Access to, Change notification, Communications device, Console input, Console screen buffer, Desktop, Event and Eventlog, Files and File mapping, Heap, Job, Mailslot, Module, Mutex, Pipe, Processes, Semaphore, Socket, Thread, Timer and Timer queue, Timer-queue timer, update resource, Window station, etc.

 

In addition to system objects, you'll encounter handles. Applications cannot directly access object data or the system resource that an object represents. Instead, an application must obtain an object handle, which it can use to examine or modify the system resource.

In .NET however, this will be transparent most of the time because system objects and handles are represented directly or indirectly by .NET classes.

 

2.3 Unmanaged resources

 

Resources such as system objects are not a problem in themselves, however the operating systems such as Windows have limits on the number of sockets, files, etc. that can be open simultaneously. That's why it's important that you pay attention to the quantity of system objects you application uses.

 

Windows has quotas for the number of User and GDI objects that a process can use at a given time. The default values are 10,000 for GDI objects and 10,000 for User objects. If you need to read the values set on your machine, you can use the following registry keys, found in HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows: GDIProcessHandleQuota and USERProcessHandleQuota.

 

Given that these values can be customized, you may think that a solution to break the default limits is to raise the quotas. I think that this is a bad idea and the real solution should be that consume less resources anyway.

 

Quotas exist for a reason: your application is not alone on the system and it should share the system resources with the other processes running on the machine.

If you change them on your machine, they may be different on another one. You have to make sure that the change is done on all the machines your application will run on, which doesn't come without issues from a system administration point of view.

The default quotas are more than enough most of the time. If you find the quotas are not enough for your application, then you probably have some cleaning to do.

 

2.4 Desktop Heap

 

all the graphical applications of an interactive user session execute in what is named a "desktop". Every desktop object has a single desktop heap associated with it. The resources allocated to such a desktop are limited (but configurable).

 

 

Here is some suggestion to avoid desktop heap exhausted:

 

  • We use TabControls and we create the content of each tab on the fly, when it becomes visible;
  • We use expandable/collapsible regions, and again fill them with controls and data only when needed;
  • We release resources as soon as possible (using the Dispose method). When a region is collapsed, it's possible to clear it's child controls. The same for a tab when it becomes hidden;
  • We use the MVP design pattern, which helps in making the above possible because it separates data from views;

 

  • We use layout engines, the standard FlowLayoutPanel and TableLayoutPanel ones, or custom ones, instead of creating deep hierarchies of nested panels, GroupBoxes and Splitters (an empty splitter itself consumes three window handles...).

 

3 Experence

 

Recently, I have been working on a .NET project named 'Navigator'. The runtime is running correctly when you use it manually. But it crashes when running script file automatically. With the help of the tool procexp.exe, I observes that the GDI handles and USER handles increasing fast with the system running. And finally crashed the system.

 

3.1 First 'key points' (ToolTip

 

After research and analyze, the code snap below comes into my eyes.

 

    public class SomeClass {

        .

        .

        .

        public void SomeMethod() {

                        .

            .

            .

              ToolTip toolTip = new ToolTip();

              toolTip.SetToolTip(label, label.Text);

                        .

            .

            .

        }

            .

        .

        .

    }

 

Every time, system creates an instance for ToolTip when SomeMethod is called. This is the reason that cause lots of GDI handles. I update code above to this:

 

    public class SomeClass {

        .

        .

        .

        public void SomeMethod() {

                        .

            .

            .

              toolTip.SetToolTip(label, label.Text);

                        .

            .

            .

        }

            .

        .

        .

            ToolTip toolTip = new ToolTip();

    }

 

This time, GDI hanles issue is eradicated. But the USER handles still increase as before. I have to update the code again.

 

 

    public class SomeClass {

        .

        .

        .

        public void SomeMethod() {

                        .

            .

            .

              toolTip.RemoveAll();

              toolTip.SetToolTip(label, label.Text);

                        .

            .

            .

        }

            .

        .

        .

            ToolTip toolTip = new ToolTip();

    }

 

 

The USER handles increase slow down. But the issue still is there.

 

This is our first 'key point'. The Microsoft Inc. should update method SetToolTip above to static to avoid mistake. It is easy to induce people to create a new instance when call it every time. Finally, you should not forget call method RemoveAll to remove the reference on the labels to release their USER hanldles.

 

 

3.2 Second 'key points' (delegate | event

 

I feeling there must be some problem in other place. After some time, I take notice of code snap below

 

    public class SomeClass {

        .

        .

        .

        public void SomeMethod() {

                        .

            .

            .

              A a = new A(b)

                        .

            .

            .

              Controls.Clear();

              Controls.Add(a);

        }

            .

        .

        .

    }

 

    public class A {       

        public A(B b) {

                        .

            .

            .

              b.ElementsChanged += OnElementsChanged;

        }

            .

        .

        .

        private int OnElementsChanged(object sender, MenuModelEventArgs e) {

                .

            .

            .

        }

    }

 

In SomeMethod, it create a new instance of A to replace old one every time. But the old instance can not be collected by GC(garbage collector), since its method OnElementsChanged is regestered to the event ElementsChanged in object b. And b keeps alive through the whole life time of runtime. The problem is that the subscription to the ElementsChanged event creates a implicit reference from the b to the old panel.

Microsoft Inc. should fix this issue. No explicit reference means no useful. .Net platform should unregester the binded event and allow GC to collect it. So this is our second 'key point'. I must remove the binding event from old panel before replace it.

 

    public class SomeClass {

        .

        .

        .

        public void SomeMethod() {

                        .

            .

            .

              A a = new A(b)

                        .

            .

            .

              if (Controls.Count > 0) {

                A holdA = Controls[0] as A;

                holdA.RemoveElementsChangeEvent();

              }

              Controls.Clear();

              Controls.Add(a);

        }

            .

        .

        .

    }

 

    public class A {       

        public A(B b) {

                        .

            .

            .

              b.ElementsChanged += OnElementsChanged;

        }

            .

        .

        .

        private int OnElementsChanged(object sender, MenuModelEventArgs e) {

                .

            .

            .

        }

 

        public void RemoveElementsChangeEvent() {

            _groupItem.MenuModel.ElementsChanged -= _groupItem_Updated;

        }

    }

 

This time, the USER handle number is slow down advance. But the issue still exists. It is so unwieldy.

 

3.3 Third 'key points' (ContextMenuStrip | MenuStrip

 

It seems that code snap below is the cause.

 

    public class SomeClass {

        .

        .

        .

        public static void PopulateContextMenuStrip(ContextMenuStrip menuStrip) {

            menuStrip.Items.Clear();

            .

            .

            .

        }

        public static void PopulateMenuStrip(MenuStrip menuStrip) {

            menuStrip.Items.Clear();

            .

            .

            .

        }

            .

        .

        .

    } 

I suspect method Clear can not realease all USER handles when the menu level is more than 2(this is our case). So the code need some updates. 

    public class SomeClass {

        .

        .

        .

        public static void PopulateContextMenuStrip(ContextMenuStrip menuStrip) {

            ClearMenuStrip(menuStrip.Items);

            .

            .

            .

        }

        public static void PopulateMenuStrip(MenuStrip menuStrip) {

            ClearMenuStrip(menuStrip.Items);

            .

            .

            .

        } 

        private static void ClearMenuStrip(ToolStripItemCollection menus) {

            foreach (ToolStripItem item in menus) {

                ToolStripMenuItem menu = item as ToolStripMenuItem;

                if (menu != null) {

                    ClearMenuStrip(menu.DropDownItems);

                }

            }

            menus.Clear();

        }

            .

        .

        .

    } 

I add a new method ClearMenuStrip to remove all controls by recursion. Thanks the god, the leaks issue do not present any more. This is another bug for Microsoft Inc? I am not sure.

This is our third key point.

 

4 Common key points 

  • Static references
  • Event with missing unsubscription
  • Static event with missing unsubscription
  • Dispose method not invoked
  • Incomplete Dispose method  

In addition to these classical traps, here are other more specific problem sources: 

  • Windows Forms: BindingSource misused
  • CAB (Composite UI Application Block) : missing Remove call on WorkItem  

5 How to detect leaks and find the leaking resources

 

Even a little leak can bring down the system if it occurs many times. This is similar to what happens with leaking water. A drop of water is not a big issue. But drop by drop, a leak can become a major problem. 

  • There are usually three steps in leak eradication:
  • Detect a leak
  • Find the leaking resource
  • Decide where and when the resource should be released in the source code  

The most direct way to "detect" leaks is to suffer from them. You won't likely see your computer run out of memory. "Out of memory" messages are quite rare. This is because when operating systems run out of RAM, they use hard disk space to extend the memory workspace (this is called virtual memory).

 

What you're more likely to see happen are "out of handles" exceptions in your Windows graphical applications. The exact exception is either a System.ComponentModel.Win32Exception or a System.OutOfMemoryException with the following message: "Error creating window handle". This happens when two many resources are consumed at the same time, most likely because of objects not being released while they should.

 

Another thing you may see even more often is your application or the whole computer getting slower and slower. This can happen because your machine is simply getting out of resources.

 

If you suspect that objects are lingering in memory while they should have been released, the first thing you need to do is to find what these objects are.

This may seem obvious, but what's not so obvious is how to find these objects.

What I suggest is that you look for unexpected and lingering high level objects or root containers with your favorite memory profiler.

 

The next step is to find why these objects are being kept in memory while they shouldn't be. This is where debuggers and profilers really help. They can show you how objects are linked together.

 

It's by looking at the incoming references to the zombie object you have identified that you'll be able to find the root cause of the problem.Your goal should be to find the root reference. Don't stop at the first object you'll find, but ask yourself why this object is kept in memory.

 

6 Others

 

6.1 for Bitmap objects call DestroyIcon to release them

 

[DllImport("user32", EntryPoint = "DestroyIcon")]

private static extern void DestroyIcon(IntPtr handle);

using (System.IO.MemoryStream mstream = new System.IO.MemoryStream())

{

     bmp.Save(mstream, System.Drawing.Imaging.ImageFormat.Png);

 intP = new Bitmap(mstream).GetHicon();

 this.notifyIcon.Icon = Icon.FromHandle(intP);

     DestroyIcon(intP);

}

 

6.2 Call GC to collect memory forcely

 

// Force garbage collection.

GC.Collect();

 

// Wait for all finalizers to complete

GC.WaitForPendingFinalizers();


// Force garbage collection again.

GC.Collect();