Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 140 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
140
Dung lượng
2,76 MB
Nội dung
Of course, the results that you get will depend on the fonts you have installed on your computer. For this sample we have as usual created a standard C# Windows Application, EnumFontFamilies. We start off by adding an extra namespace to be searched. We will be using the InstalledFontCollection class, which is defined in System.Drawing.Text. using System; using System.Drawing; using System.Drawing.Text; We then add the following constant to the Form1 class: private const int margin = 10; margin is the size of the left and top margin between the text and the edge of the document—it stops the text from appearing right at the edge of the client area. This is designed as a quick-and-easy way of showing off font families; therefore the code is crude and in many instances doesn’t do things the way you ought to in a real application. For example, here we hard- code an estimated value for the document size of (200,1500) and set the AutoScrollMinSize property to this value using the Visual Studio .NET Properties window. Normally you would have to examine the text to be displayed to work out the document size. We do that in the next section. Here is the OnPaint() method: protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); int verticalCoordinate = margin; Point topLeftCorner; InstalledFontCollection insFont = new InstalledFontCollection(); FontFamily [] families = insFont.Families; e.Graphics.TranslateTransform(AutoScrollPosition.X, AutoScrollPosition.Y); foreach (FontFamily family in families) { if (family.IsStyleAvailable(FontStyle.Regular)) { Font f = new Font(family.Name, 12); topLeftCorner = new Point(margin, verticalCoordinate); verticalCoordinate += f.Height; e.Graphics.DrawString (family.Name, f, Brushes.Black,topLeftCorner); f.Dispose(); } } } In this code we start off by using an InstalledFontCollection object to obtain an array that contains details of all the available font families. For each family, we instantiate a 12-point Font. We use a simple constructor for Font—there are many more that allow additional options to be specified. The construc- tor we’ve picked takes two parameters, the name of the family and the size of the font: 660 Chapter 20 24 557599 Ch20.qxd 4/29/04 11:39 AM Page 660 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Font f = new Font(family.Name, 12); This constructor builds a font that has the regular style. To be on the safe side, however, we first check that this style is available for each font family before attempting to display anything using that font. This is done using the FontFamily.IsStyleAvailable() method. This check is important, because not all fonts are available in all styles: if (family.IsStyleAvailable(FontStyle.Regular)) FontFamily.IsStyleAvailable() takes one parameter, a FontStyle enumeration. This enumeration contains a number of flags that might be combined with the bitwise OR operator. The possible flags are Bold, Italic, Regular, Strikeout, and Underline. Finally, note that we use a property of the Font class, Height, which returns the height needed to display text of that font, in order to work out the line spacing: Font f = new Font(family.Name, 12); topLeftCorner = new Point(margin, verticalCoordinate); verticalCoordinate += f.Height; Again, to keep things simple, our version of OnPaint() reveals some bad programming practices. For a start, we haven’t bothered to check what area of the document actually needs drawing—we just try to dis- play everything. Also, instantiating a Font is, as remarked earlier, a computationally intensive process, so we really ought to save the fonts rather than instantiating new copies every time OnPaint() is called. As a result of the way the code has been designed, you might note that this example actually takes a noticeable time to paint itself. In order to try to conserve memory and help the garbage collector out we do, however, call Dispose() on each font instance after we have finished with it. If we didn’t, then after 10 or 20 paint operations, there’d be a lot of wasted memory storing fonts that are no longer needed. Editing a Text Document: The CapsEditor Sample We now come to the extended example in this chapter. The CapsEditor example is designed to demonstrate how the principles of drawing that we’ve learned so far have to be applied in a more realistic context. The CapsEditor example does not require any new material, apart from responding to user input via the mouse, but it shows how to manage the drawing of text so that the application maintains performance while ensuring that the contents of the client area of the main window are always kept up to date. The CapsEditor program is functionally quite simple. It allows the user to read in a text file, which is then displayed line by line in the client area. If the user double-clicks any line, that line will be changed to all uppercase. That’s literally all the sample does. Even with this limited set of features, we’ll find that the work involved in making sure everything is displayed in the right place while considering perfor- mance issues is quite complex. In particular, we have a new element here: the contents of the document can change—either when the user selects the menu option to read a new file, or when she double-clicks to capitalize a line. In the first case we need to update the document size so the scroll bars still work cor- rectly, and we have to redisplay everything. In the second case, we need to check carefully whether the document size has changed, and what text needs to be redisplayed. 661 Graphics with GDI+ 24 557599 Ch20.qxd 4/29/04 11:39 AM Page 661 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com We’ll start by reviewing the appearance of CapsEditor. When the application is first run, it has no docu- ment loaded and resembles Figure 20-17. Figure 20-17 The File menu has two options: Open, which evokes OpenFileDialog when selected and reads in whatever file the user clicks, and Exit, which closes the application when clicked. Figure 20-18 shows CapsEditor displaying its own source file, Form1.cs. (We’ve also double-clicked a couple of lines to con- vert them to uppercase.) Figure 20-18 The sizes of the horizontal and vertical scrollbars are correct. The client area will scroll just enough to view the entire document. CapsEditor doesn’t try to wrap lines of text—the example is already compli- cated enough as is. It just displays each line of the file exactly as it is read in. There are no limits to the size of the file, but we are assuming it is a text file and doesn’t contain any non-printable characters. 662 Chapter 20 24 557599 Ch20.qxd 4/29/04 11:39 AM Page 662 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Let’s begin by adding a using command: using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; using System.IO; This is because we’ll be using the StreamReader class, which is in the System.IO namespace. Next we’ll add some fields to the Form1 class: #region Constant fields private const string standardTitle = “CapsEditor”; // default text in titlebar private const uint margin = 10; // horizontal and vertical margin in client area #endregion #region Member fields private ArrayList documentLines = new ArrayList(); // the ‘document’ private uint lineHeight; // height in pixels of one line private Size documentSize; // how big a client area is needed to // display document private uint nLines; // number of lines in document private Font mainFont; // font used to display all lines private Font emptyDocumentFont; // font used to display empty message private Brush mainBrush = Brushes.Blue; // brush used to display document text private Brush emptyDocumentBrush = Brushes.Red; // brush used to display empty document message private Point mouseDoubleClickPosition; // location mouse is pointing to when double-clicked private OpenFileDialog fileOpenDialog = new OpenFileDialog(); // standard open file dialog private bool documentHasData = false; // set to true if document has some data in it #endregion Most of these fields should be self-explanatory. The documentLines field is an ArrayList that contains the actual text of the file that has been read in. In a real sense, this is the field that contains the data in the document” Each element of documentLines contains information for one line of text that has been read in. Since it’s an ArrayList, rather than a plain array, we can dynamically add elements to it as we read in a file. Note also that we’ve used #region preprocessor directives to block bits of the program to make it easier to edit. As previously mentioned, each documentLines element contains information about a line of text. This information is actually an instance of another class, TextLineInformation: 663 Graphics with GDI+ 24 557599 Ch20.qxd 4/29/04 11:39 AM Page 663 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com class TextLineInformation { public string Text; public uint Width; } TextLineInformation looks like a classic case where you’d normally use a struct rather than a class since it’s just there to group a couple of fields. However, its instances are always accessed as elements of an ArrayList, which expects its elements to be stored as reference types, so declaring TextLineInformation as a class makes things more efficient by saving a lot of boxing and unboxing operations. Each TextLineInformation instance stores a line of text—and that can be thought of as the smallest item that is displayed as a single item. In general, for each similar item in a GDI+ application, you’d probably want to store the text of the item, as well as the world coordinates of where it should be dis- played and its size (the page coordinates will change frequently, whenever the user scrolls, whereas world coordinates will normally only change when other parts of the document are modified in some way). In this case we’ve only stored the Width of the item. The reason is because the height in this case is just the height of whatever our selected font is. It’s the same for all lines of text so there’s no point stor- ing it separately for each one; we store it once, in the Form1.lineHeight field. As for the position well in this case the x coordinate is just equal to the margin, and the y coordinate is easily calculated as: margin + lineHeight*(however many lines are above this one) If we’d been trying to display and manipulate, say, individual words instead of complete lines, then the x position of each word would have to be calculated using the widths of all the previous words on that line of text, but I wanted to keep it simple here, which is why we’re treating each line of text as one sin- gle item. Let’s turn to the main menu now. This part of the application is more the realm of Windows Forms (see Chapter 19) than of GDI+. Add the menu options using the design view in Visual Studio .NET, but rename them as menuFile, menuFileOpen, and menuFileExit. Next add event handlers for the File Open and File Exit menu options using the Visual Studio .NET Properties window. The event handlers have their Visual Studio .NET–generated names of menuFileOpen_Click() and menuFileExit_Click(). Add some extra initialization code in the Form1() constructor: public Form1() { InitializeComponent(); CreateFonts(); fileOpenDialog.FileOk += new System.ComponentModel.CancelEventHandler( this.OpenFileDialog_FileOk); fileOpenDialog.Filter = “Text files (*.txt)|*.txt|C# source files (*.cs)|*.cs”; } The event handler added here is for when the user clicks OK in the File Open dialog box. We have also set the filter for the Open File dialog box, so that we can only load text files—we’ve opted for .txt files as well as C# source files, so we can use the application to examine the source code for our samples. 664 Chapter 20 24 557599 Ch20.qxd 4/29/04 11:39 AM Page 664 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com CreateFonts() is a helper method that sorts out the fonts we intend to use: private void CreateFonts() { mainFont = new Font(“Arial”, 10); lineHeight = (uint)mainFont.Height; emptyDocumentFont = new Font(“Verdana”, 13, FontStyle.Bold); } The actual definitions of the handlers are pretty standard stuff: protected void OpenFileDialog_FileOk(object Sender, CancelEventArgs e) { this.LoadFile(fileOpenDialog.FileName); } protected void menuFileOpen_Click(object sender, EventArgs e) { fileOpenDialog.ShowDialog(); } protected void menuFileExit_Click(object sender, EventArgs e) { this.Close(); } Next, we’ll examine the LoadFile() method. It’s the method that handles the opening and reading of a file (as well as ensuring a Paint event is raised to force a repaint with the new file): private void LoadFile(string FileName) { StreamReader sr = new StreamReader(FileName); string nextLine; documentLines.Clear(); nLines = 0; TextLineInformation nextLineInfo; while ( (nextLine = sr.ReadLine()) != null) { nextLineInfo = new TextLineInformation(); nextLineInfo.Text = nextLine; documentLines.Add(nextLineInfo); ++nLines; } sr.Close(); documentHasData = (nLines>0) ? true : false; CalculateLineWidths(); CalculateDocumentSize(); this.Text = standardTitle + “ - “ + FileName; this.Invalidate(); } 665 Graphics with GDI+ 24 557599 Ch20.qxd 4/29/04 11:39 AM Page 665 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Most of this function is just standard file-reading stuff (see Chapter 30). Note that as the file is read, we progressively add lines to documentLines ArrayList, so this array ends up containing information for each of the lines in order. After we’ve read in the file, we set the documentHasData flag, which indicates whether there is actually anything to display. Our next task is to work out where everything is to be dis- played, and, having done that, how much client area we need to display the file—the document size that will be used to set the scroll bars. Finally, we set the title bar text and call Invalidate(). Invalidate() is an important method supplied by Microsoft, so we’ll discuss its use first, before we examine the code for the CalculateLineWidths() and CalculateDocumentSize() methods. The Invalidate() Method Invalidate() is a member of System.Windows.Forms.Form. It marks an area of the client window as invalid and, therefore, in need of repainting, and then makes sure a Paint event is raised. There are a couple of overrides to Invalidate(): you can pass it a rectangle that specifies (in page coordinates) precisely which area of the window needs repainting, or if you don’t pass any parameters it’ll just mark the entire client area as invalid. You might wonder why we are doing it this way. If we know that something needs painting, why don’t we just call OnPaint() or some other method to do the painting directly? The answer is that in general, calling painting routines directly is regarded as bad programming practice—if your code decides it wants some painting done, you should call Invalidate(). Here’s why: ❑ Drawing is almost always the most processor-intensive task a GDI+ application will carry out, so doing it in the middle of other work holds up the other work. With our example, if we’d directly called a method to do the drawing from the LoadFile() method, then the LoadFile() method wouldn’t return until that drawing task was complete. During that time, our applica- tion can’t respond to any other events. On the other hand, by calling Invalidate() we are sim- ply getting Windows to raise a Paint event before immediately returning from LoadFile(). Windows is then free to examine the events that are in line to be handled. How this works inter- nally is that the events sit as what are known as messages in a message queue. Windows periodi- cally examines the queue, and if there are events in it, it picks one and calls the corresponding event handler. Although the Paint event might be the only one sitting in the queue (so OnPaint() gets called immediately anyway), in a more complex application there might be other events that ought to get priority over our Paint event. In particular, if the user has decided to quit the application, this will be marked by a message known as WM_QUIT. ❑ Related to the previous point, if you have a more complicated, multithreaded, application, you’ll probably want just one thread to handle all the drawing. Using Invalidate() to route all drawing through the message queue provides a good way of ensuring that the same thread (whatever thread is responsible for the message queue, this will be the thread that called Application.Run()) does all the drawing, no matter what other thread requested the drawing operation. ❑ There’s an additional performance-related reason. Suppose at about the same time a couple of different requests to draw part of the screen come in. Maybe your code has just modified the document and wants to ensure the updated document is displayed, while at the same time the user has just moved another window that was covering part of the client area out of the way. By calling Invalidate(), you are giving windows a chance to notice that this has occurred. Windows can then merge the Paint events if appropriate, combining the invalidated areas, so that the painting is only done once. 666 Chapter 20 24 557599 Ch20.qxd 4/29/04 11:39 AM Page 666 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com ❑ Finally, the code to do the painting is probably going to be one of the most complex parts of the code in your application, especially if you have a very sophisticated user interface. The guys who have to maintain your code in a couple of years time will thank you for having kept your painting code all in one place and as simple as you reasonably can—something that’s easier to do if you don’t have too many pathways into it from other parts of the program. The bottom line from all this is that it is good practice to keep all your painting in the OnPaint() routine, or in other methods called from that method. However, you have to strike a balance; if you want to replace just one character on the screen and you know perfectly well that it won’t affect anything else that you’ve drawn, then you might decide that it’s not worth the overhead of going through Invalidate(), and just write a separate drawing routine. In a very complicated application, you might even write a full class that takes responsibility for drawing to the screen. A few years ago when MFC was the standard technology for GDI-intensive applications, MFC followed this model, with a C++ class, C<ApplicationName>View that was responsible for paint- ing. However, even in this case, this class had one member function, OnDraw(), which was designed to be the entry point for most drawing requests. Calculating Item Sizes and Document Size We’ll return to the CapsEditor example now and examine the CalculateLineWidths() and CalculateDocumentSize() methods that are called from LoadFile(): private void CalculateLineWidths() { Graphics dc = this.CreateGraphics(); foreach (TextLineInformation nextLine in documentLines) { nextLine.Width = (uint)dc.MeasureString(nextLine.Text, mainFont).Width; } } This method simply runs through each line that has been read in and uses the Graphics.MeasureString() method to work out and store how much horizontal screen space the string requires. We store the value, because MeasureString() is computationally intensive. If we hadn’t made the CapsEditor sample so sim- ple that we can easily work out the height and location of each item, this method would almost certainly have needed to be implemented in such a way as to compute all those quantities too. Now we know how big each item on the screen is, and we can calculate where each item goes, we are in a position to work out the actual document size. The height is basically the number of lines multiplied by the height of each line. The width will need to be worked out by iterating through the lines to find the longest. For both height and width, we will also want to make an allowance for a small margin around the displayed document, to make the application look more attractive. Here’s the method that calculates the document size: private void CalculateDocumentSize() { if (!documentHasData) 667 Graphics with GDI+ 24 557599 Ch20.qxd 4/29/04 11:39 AM Page 667 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com { documentSize = new Size(100, 200); } else { documentSize.Height = (int)(nLines*lineHeight) + 2*(int)margin; uint maxLineLength = 0; foreach (TextLineInformation nextWord in documentLines) { uint tempLineLength = nextWord.Width + 2*margin; if (tempLineLength > maxLineLength) maxLineLength = tempLineLength; } documentSize.Width = (int)maxLineLength; } this.AutoScrollMinSize = documentSize; } This method first checks whether there is any data to be displayed. If there isn’t we cheat a bit and use a hard-coded document size, which is big enough to display the big red <Empty Document> warning. If we’d wanted to really do it properly, we’d have used MeasureString() to check how big that warning actually is. Once we’ve worked out the document size, we tell the Form instance what the size is by setting the Form.AutoScrollMinSize property. When we do this, something interesting happens behind the scenes. In the process of setting this property, the client area is invalidated and a Paint event is raised, for the very sensible reason that changing the size of the document means scroll bars will need to be added or modified and the entire client area will almost certainly be repainted. Why is that interesting? If you look back at the code for LoadFile() you’ll realize that our call to Invalidate() in that method is actually redundant. The client area will be invalidated anyway when we set the document size. I left the explicit call to Invalidate() in the LoadFile() implementation to illustrate how in general you should nor- mally do things. In fact in this case, all calling Invalidate() again will do is needlessly request a dupli- cate Paint event. However, this in turn illustrates what I was saying about how Invalidate() gives Windows the chance to optimize performance. The second Paint event won’t in fact get raised: Windows will see that there’s a Paint event already sitting in the queue and will compare the requested invalidated regions to see if it needs to do anything to merge them. In this case, both Paint events will specify the entire client area, so nothing needs to be done, and Windows will quietly drop the second Paint request. Of course, going through that process will take up a little bit of processor time, but it’ll be a negligible amount of time compared to how long it takes to actually do some painting. OnPaint() Now we’ve seen how CapsEditor loads the file, it’s time to look at how the painting is done: protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); Graphics dc = e.Graphics; int scrollPositionX = this.AutoScrollPosition.X; int scrollPositionY = this.AutoScrollPosition.Y; dc.TranslateTransform(scrollPositionX, scrollPositionY); 668 Chapter 20 24 557599 Ch20.qxd 4/29/04 11:39 AM Page 668 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com if (!documentHasData) { dc.DrawString(“<Empty document>”, emptyDocumentFont, emptyDocumentBrush, new Point(20,20)); base.OnPaint(e); return; } // work out which lines are in clipping rectangle int minLineInClipRegion = WorldYCoordinateToLineIndex(e.ClipRectangle.Top – scrollPositionY); if (minLineInClipRegion == -1) minLineInClipRegion = 0; int maxLineInClipRegion = WorldYCoordinateToLineIndex(e.ClipRectangle.Bottom – scrollPositionY); if (maxLineInClipRegion >= this.documentLines.Count || maxLineInClipRegion == -1) maxLineInClipRegion = this.documentLines.Count-1; TextLineInformation nextLine; for (int i=minLineInClipRegion; i<=maxLineInClipRegion ; i++) { nextLine = (TextLineInformation)documentLines[i]; dc.DrawString(nextLine.Text, mainFont, mainBrush, this.LineIndexToWorldCoordinates(i)); } } At the heart of this OnPaint() override is a loop that goes through each line of the document, calling Graphics.DrawString() to paint each one. The rest of this code is mostly to do with optimizing the painting—the usual stuff about figuring out what exactly needs painting instead of rushing in and telling the graphics instance to redraw everything. We begin by checking if there is any data in the document. If there isn’t, we draw a quick message say- ing so, call the base class’s OnPaint() implementation, and exit. If there is data, then we start looking at the clipping rectangle. The way we do this is by calling another method that we’ve written, WorldY- CoordinateToLineIndex() . We’ll examine this method next, but essentially it takes a given y position relative to the top of the document, and works out what line of the document is being displayed at that point. The first time we call the WorldYCoordinateToLineIndex() method, we pass it the coordinate value (e.ClipRectangle.Top - scrollPositionY). This is just the top of the clipping region, converted to world coordinates. If the return value is –1, then we’ll play safe and assume we need to start at the beginning of the document (this is the case if the top of the clipping region is within the top margin). Once we’ve done all that, we essentially repeat the same process for the bottom of the clipping rectangle, in order to find the last line of the document that is inside the clipping region. The indices of the first and last lines are respectively stored in minLineInClipRegion and maxLineInClipRegion, so then we can just run a for loop between these values to do our painting. Inside the painting loop, we actually need to do roughly the reverse transformation to the one performed by WorldYCoordinateToLineIndex(). We 669 Graphics with GDI+ 24 557599 Ch20.qxd 4/29/04 11:39 AM Page 669 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com [...]... TextLineInformation(); nextLineInfo.Text = nextLine; documentLines.Add(nextLineInfo); ++nLines; } sr.Close(); if (nLines > 0) { documentHasData = true; menuFilePrint.Enabled = true; menuFilePrintPreview.Enabled = true; 67 6 Graphics with GDI+ Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com } else { documentHasData = false; menuFilePrint.Enabled = false; menuFilePrintPreview.Enabled = false; } CalculateLineWidths();... remains is to compile the project and check that the code works Figure 20-19 shows what happens when you run CapsEdit, load a text document (as before, we’ve picked the C# source file for the project), and select Print Preview Figure 20-19 67 9 Chapter 20 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com In Figure 20-19, we have scrolled to page 5 of the document, and set the preview... following classes are contained in the System.Data namespace: ❑ DataSet—This object is designed for disconnected use and can contain a set of DataTables and include relationships between these tables ❑ 68 6 DataTable—A container of data that consists of one or more DataColumns and, when populated, will have one or more DataRows containing data Data Access with NET Simpo PDF Merge and Split Unregistered... let’s look at our OnDoubleClick() override There’s quite a bit more work to do here: protected override void OnDoubleClick(EventArgs e) { int i = PageCoordinatesToLineIndex(this.mouseDoubleClickPosition); 67 3 Chapter 20 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com if (i >= 0) { TextLineInformation lineToBeChanged = (TextLineInformation)documentLines[i]; lineToBeChanged.Text =... ensuring that just that line of text will be repainted That’s precisely what the previous code does Our call to Invalidate() initiates a call to OnPaint() when the mouse event handler finally returns 67 4 Graphics with GDI+ Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Keeping in mind our earlier comments about the difficulty in setting a break point in OnPaint(), if you run... page each part of the document needs to be written to Despite the above complications, the process of printing is quite simple Programmatically, the steps you need to go through look roughly like this: 67 5 Chapter 20 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com ❑ Printing You instantiate a PrintDocument object, and call its Print() method This method signals the PrintPage... if (position.Y < margin || position.X < margin) return -1; int index = (int)(position.Y-margin)/(int)this.lineHeight; // check position isn’t below document if (index >= documentLines.Count) return -1; 67 0 Graphics with GDI+ Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com // now check that horizontal position is within this line TextLineInformation theLine = (TextLineInformation)documentLines[index];... PrintDocument object to a PrintPreviewDialog and call the preview dialog box object’s ShowDialog() method The real work to the PrintPage event is done in the event handler Here is what this handler looks like: 67 7 Chapter 20 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com private void pd_PrintPage(object sender, PrintPageEventArgs e) { float yPos = 0; float leftMargin = e.MarginBounds.Left;... all the lines of text in the document, or when we find that we have printed all the lines that will fit on this page, whichever condition occurs first Finally, we check whether there is any more of the 67 8 Graphics with GDI+ Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com document to be printed, and set the HasMorePages property of our PrintPageEventArgs accordingly, and also... relevant event handler, in much the same way that OnPaint() is called when a Paint event is raised The following table lists the methods you might want to override when the user clicks or moves the mouse 67 1 Chapter 20 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Method Called when OnClick(EventArgs e) Mouse is clicked OnDoubleClick(EventArgs e) Mouse is double-clicked OnMouseDown(MouseEventArgs . combining the invalidated areas, so that the painting is only done once. 66 6 Chapter 20 24 557599 Ch20.qxd 4/29/04 11:39 AM Page 66 6 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com ❑. .txt files as well as C# source files, so we can use the application to examine the source code for our samples. 66 4 Chapter 20 24 557599 Ch20.qxd 4/29/04 11:39 AM Page 66 4 Simpo PDF Merge and. it is a text file and doesn’t contain any non-printable characters. 66 2 Chapter 20 24 557599 Ch20.qxd 4/29/04 11:39 AM Page 66 2 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Let’s