Multiple Document Interfaces (MDI) and Graphics (GDI+)
Visual Basic .NET Professional Skills Development 22-1
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
Multiple Document Interfaces (MDI) and Graphics (GDI+) Objectives Create MDI Windows applications. Manage menus in MDI forms. Use Toolbar and StatusBar controls, and ImageList components. Take control of the display of combo box lists and menus. Draw shapes on Windows forms. Create transparency effects and non-rectangular forms. Multiple Document Interfaces (MDI) and Graphics (GDI+) 22-2 Visual Basic .NET Professional Skills Development Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
Creating MDI Applications Since the earliest versions of Windows, Microsoft has supported a style of application that is referred to as Multiple Document Interface, or MDI. In an MDI application, there is one parent window that creates a shell environment for the application, with other child windows contained within it. The child windows in an MDI application always have certain characteristics: They never move outside the borders of the parent window. They are resizable. When one child is maximized, all child windows are maximized. When maximized, the title of the active child window is appended to the title of the parent. When minimized, child windows are displayed as icons within the parent window. Any menus in the child windows appear as menus of the parent, when that child is active. MDI Settings in .NET Windows Forms Windows Forms provide very flexible and full-featured support for building MDI applications. You can designate any form as an MDI parent, simply by setting its IsMdiContainer property to True. To make a form into an MDI child, you set its MdiParent property to point to an MDI container form. These properties can even be modified at runtime, and you can designate more than one form in an application as an MDI container. Try It Out! Follow these steps to see how you can turn .NET Windows forms into an MDI parent and child forms by setting their IsMdiContainer and MdiParent properties. 1. Open the sample application in Visual Studio by double-clicking MDI-GDI+.sln in the sample folder named MDI-GDI+. Press F5 to run the application and bring up the switchboard form named frmMain. Creating MDI Applications Visual Basic .NET Professional Skills Development 22-3 Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
2. Click the MDI Settings button on the switchboard form. This opens three sample forms named Form1, Form2 and Form3. Each of these forms has a check box that enables you to set the forms IsMdiContainer property and a combo box to set the MdiParent property. The combo box is enabled only when the check box is checked. 3. Click the IsMdiContainer check box in Form1 and Form3. Youll see the appearance of these forms change. Notice that these MDI parent forms still contain their controls, but the backcolor of the form has changed to the Windows system color named AppWorkspace, which is a dark gray color in the standard color scheme. 4. Use the combo box in Form2 to set its MdiParent property to Form1. Form2 becomes an MDI child form, contained inside Form1, as shown in Figure 1. (Youll need to resize Form1 to see Form2.)
Figure 1. MDI properties can be set dynamically at runtime. 5. Maximize Form2, and the title of Form1 changes to Form1 [Form2]. 6. Restore Form2 and set its MdiParent property to Form3. The form moves to become a child of Form3. 7. Clear the IsMdiContainer check box on Form3 and on Form1. All three forms return to being non-MDI forms. You can close the forms individually or you can close the switchboard form and the others will close automatically. Multiple Document Interfaces (MDI) and Graphics (GDI+) 22-4 Visual Basic .NET Professional Skills Development Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
WARNING! Although it is interesting and instructive to play with changing the MDI properties of forms during runtime, it is not something you normally would (or should) do in a production application. In fact, you will find that if you experiment with the sample forms, it isnt hard to find combinations of steps that produce errors, or that leave a form in a state where it cant be closed without closing the startup switchboard form. We recommend that you set the IsMdiContainer property when you design your applications. If you find a need to change this property at runtime, be sure to test the application thoroughly. Additional MDI Form Properties In addition to the IsMdiContainer and MdiParent properties, there are two other useful properties that relate to a forms status in an MDI application. The IsMdiChild property is a read-only Boolean property that provides an alternative to checking a forms MdiParent property. This is useful if you just need to determine whether the form is an MDI child or not. For example, the event handler for the CheckedChanged event of chkIsMdiContainer in the sample forms determines whether the form is an MDI child before attempting to make it into an MDI parent. If the form is an MDI child, then it sets the MdiParent property to Nothing before moving on to setting the IsMdiContainer property to True: If chkIsMdiContainer.Checked Then If Me.IsMdiChild Then ' You must make the form ' a top-level form, ' before making it a parent. Me.MdiParent = Nothing End If
So what is the difference between checking the value of IsMdiChild and simply checking whether the forms MdiParent property is set to Nothing? There is no difference. The read-only IsMdiChild property is added for your convenience, and perhaps to provide some backwards compatibility with Visual Basic 6 applications, where forms had a Boolean MDIChild property and applications could only have one MDI parent form. When the CheckBox is cleared, the code uses the MdiChildren property to find all the forms contained by that MDI parent form. The code sets the See chkI sMdiContainer _CheckedChanged in Form1 Creating MDI Applications Visual Basic .NET Professional Skills Development 22-5 Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
MdiParent property of these child forms to Nothing before setting its own IsMdiContainer property to False:
Else ' the check box was cleared Dim frm As Form For Each frm In Me.MdiChildren frm.MdiParent = Nothing Next Me.IsMdiContainer = False Me.lblMdiParent.Enabled = True Me.cboMdiParent.Enabled = True End If End Sub
Creating Multiple Document Windows The sample application includes a pair of forms named frmMDIParent and frmMDIChild. These forms comprise a simple MDI application that allows you to create and display multiple child windows. Each child window, based on the frmMDIChild form, contains a RichTextBox control, and you can individually format the text in each window. To test this example, click the MDI Sample button on the switchboard to open the parent form with one child window. Choose File|New to create a second child window, as shown in Figure 2. Use the Format menu to control the font in each window. Note that the name of the active document is displayed in the status bar control that is docked to the bottom of the parent form.
Figure 2. Creating multiple child documents. See frmMDI Parent and frmMDI Child Multiple Document Interfaces (MDI) and Graphics (GDI+) 22-6 Visual Basic .NET Professional Skills Development Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
The AddNewDocument method of frmMDIParent is called from the mnuFileNew.Click event handler. This code shows how multiple form instances can be created from one form class. A counter is incremented to give each form a unique document number, which appears in the title bar and as the initial text in the RichTextBox control. The MdiParent property is set for each form before the form is displayed.
Private Sub AddNewDocument() mintChildCount += 1 'Create the form Dim frmNew As New frmMDIChild( _ "Document " & mintChildCount.ToString()) frmNew.MdiParent = Me frmNew.Show() End Sub
The frmMDIChild forms code contains an overloaded constructor that takes a single string parameter and displays that string in the title bar and in the RichTextBox control.
Public Sub New(ByVal name As String) MyBase.New() 'This call is required by the Windows Form Designer. InitializeComponent() Me.Text = name RichTextBox1.Text = name
The code in the overloaded constructor also initializes the font used in the RichTextBox control to be the one currently selected in the forms Format menu. That font is stored in a private variable named currentFontFamily and its size is stored in a variable named sngFontSize.
RichTextBox1.Font = _ New Font(currentFontFamily, sngFontSize)
Finally, the constructor calls a subroutine that dynamically creates the Format menu items for the form and hooks up their event handlers. See AddNewDocument in frmMDI Parent See frmMDI Child Creating MDI Applications Visual Basic .NET Professional Skills Development 22-7 Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
Call AddFormatMenus() End Sub
Detecting Which Child is Active In the sample application, the status bar shows the text assigned to the active child form. To accomplish this, the application handles a special event that is raised only in MDI parent forms, the MdiChildActivate event. In the MdiChildActivate event handler (or in any code in the parent form) you can detect which child form currently has the focus by checking the value of the parent forms ActiveMdiChild property.
Private Sub frmMDIParent_MdiChildActivate( _ ByVal sender As Object, ByVal e As System.EventArgs) _ Handles MyBase.MdiChildActivate If (Me.ActiveMdiChild Is Nothing) Then StatusBar1.Text = "" Else StatusBar1.Text = Me.ActiveMdiChild.Text End If End Sub
Managing Menus in MDI Applications One tricky aspect of developing an MDI application is planning and controlling the behavior of menus. There will usually be some menu items that are global to the application and that therefore belong in the parent container form. Each child form, however, may have its own menus, which can replace or merge with those of the parent container. MergeOrder You set the MergeOrder property of a menu item to determine its position in relation to other menu items. For example, frmMDIParent has three top-level menu items with the MergeOrder settings shown in Table 1. See frmMDI Parent_ MdiChildActivate Multiple Document Interfaces (MDI) and Graphics (GDI+) 22-8 Visual Basic .NET Professional Skills Development Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
frmMDIParent Menus MergeOrder File 0 Edit 1 Window 10 Table 1. MergeOrder settings for the top-level menu items in frmMDIParent. The MergeOrder settings determine the order in which these menu items will appear on the menu bar of the form. If you change the MergeOrder value of Edit to 11, it will appear after the Window menu. The frmMDIChild form also contains a menu. Its menu items have the MergeOrder values shown in Table 2. frmMDIChild Menus MergeOrder File 0 Format 5 Table 2. MergeOrder settings for the top-level menu items in frmMDIChild. When you display these forms, you only see menus in the parent form. MDI child forms never display menus. However, the menus from frmMDIChild arent simply eliminated. They appear in the parent form. The Format menu appears between the Edit and Window menus, because its MergeOrder value (5) is between the values assigned to Edit (1) and Window (10). MergeType In the example application, the File menus in the parent and child forms have the same name and the same MergeOrder value. What determines the behavior when these menus are merged at runtime? This behavior is determined by the MergeType property, which provides several useful options. Figure 3 shows the MergeType property values that are available and Table 3 describes the effect of each one. Creating MDI Applications Visual Basic .NET Professional Skills Development 22-9 Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
Figure 3. Setting the MergeType property. MergeType Setting Effect Add Add the child menu item to the parents menu, along with the parent item that has the same MergeOrder value. Replace Replace the parent menu item with the one in the child form. MergeItems Blend the sub-items from the parent and child menus. Remove Use the parent menu item and ignore the one defined in the child. Table 3. The MergeType property determines how menu conflicts between parent and child MDI forms are handled. Because the mnuFile MenuItem in frmMDIChild has its MergeType set to MergeItems, the sub-items under this File menu are merged with the sub-items of the File menu in frmMDIParent. The order in which the sub-items appear in the merged File menu is determined by the MergeOrder values of the sub- items. The mnuFile menu item in frmMDIChild has one sub-item, mnuFileSave, which has a MergeOrder of 105. The mnuFile menu item in frmMDIParent has three sub-items, mnuFileNew (100), mnuFileSep1 (108), and mnuFileExit (110). Because of these settings, the Save item (105) appears between the New item (100) and the separator line (108) in the merged menu that is shown in Figure 4. Multiple Document Interfaces (MDI) and Graphics (GDI+) 22-10 Visual Basic .NET Professional Skills Development Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
Figure 4. The Save item, with a MergeOrder of 105 is displayed between New (100) and the separator (108) in the merged File menu. Listing and Arranging MDI Child Windows To have a menu automatically list all the child windows in an MDI application, all you need to do is set the MdiList property of that menu item to True. The list is automatically maintained and the item corresponding to the active window is automatically checked. Selecting an item from this menu list makes the selected window become active. In the sample application, the MdiList property of mnuWindow is set to True. A list of child windows is shown in Figure 5. Calling the LayoutMdi method of an MDI parent form enables you to reposition the child windows into a neatly-arranged cascading or tiled pattern. You pass the appropriate member of the MdiLayout enumeration to the method call. For example, here is code that horizontally tiles the child windows, as shown in Figure 5.
Private Sub mnuWindowHorizontal_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles mnuWindowHorizontal.Click Me.LayoutMdi(MdiLayout.TileHorizontal) End Sub
See frmMDI Parent Creating MDI Applications Visual Basic .NET Professional Skills Development 22-11 Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
Figure 5. List child windows by setting a menu items MdiList property, and arrange the windows by calling the forms LayoutMdi method. Editing Documents Using the Clipboard The sample MDI application also shows you two different ways to cut/copy/paste values to or from the Windows Clipboard. The .NET Framework includes a Clipboard object with methods you can call, but the RichTextBox and TextBox controls also have their own built-in methods for working with the Clipboard. You can even use methods of the controls to Undo or Redo successive user actions. You can experiment with these capabilities by testing the menu commands under the Edit menu in the sample form, as shown in Figure 6. Note that these commands have also been assigned the standard Windows shortcuts.
Figure 6. Adding standard editing functionality. The mnuEditCopy.Click event handler sets references to the active child form and its RichTextBox control. The code passes the value of the SelectedText property of the RichTextBox to the SetDataObject method of the Clipboard See mnuEditCopy_ Click Multiple Document Interfaces (MDI) and Graphics (GDI+) 22-12 Visual Basic .NET Professional Skills Development Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
object. A comment in the code shows how you can accomplish the same effect by calling the Copy method of the control.
' Get the active child form. Dim frmActive As Form = Me.ActiveMdiChild
' Get the active RichTextBox control ' on the active form, if there is one. If (Not frmActive Is Nothing) Then Try Dim rtb As RichTextBox = _ CType(frmActive.ActiveControl, RichTextBox) If (Not rtb Is Nothing) Then Clipboard.SetDataObject(rtb.SelectedText) ' You can also use this: 'rtb.Copy() End If Catch MessageBox.Show("No text is selected.") End Try End If
The code for pasting calls the Clipboards GetDataObject method, and it also checks the data to ensure that it is in text format. This is a useful validation to perform, because the Clipboard can hold many different types of data. A comment in the code shows how this can also be done by calling methods of the RichTextBox control. See mnuEditPaste_ Click Creating MDI Applications Visual Basic .NET Professional Skills Development 22-13 Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
Private Sub mnuEditPaste_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles mnuEditPaste.Click ' Get the active child form. Dim frmActive As Form = Me.ActiveMdiChild ' Get the active RichTextBox control ' on the active form, if there is one. If (Not frmActive Is Nothing) Then Try Dim rtb As RichTextBox = _ CType(frmActive.ActiveControl, RichTextBox) If (Not rtb Is Nothing) Then ' Get the clipboard data. Dim ido As IDataObject = Clipboard.GetDataObject() ' If the data is text, then paste. If (ido.GetDataPresent(DataFormats.Text)) Then rtb.SelectedText = _ ido.GetData(DataFormats.Text).ToString() End If
' This can also be done using methods ' of the RichTextBox control: 'If rtb.CanPaste( _ ' DataFormats.GetFormat(DataFormats.Text)) Then ' rtb.Paste() 'End If End If Catch MessageBox.Show("No insertion point is selected.") End Try End If End Sub
Implementing Undo and Redo is very easy. You only need to call the appropriate method of the control. Multiple Document Interfaces (MDI) and Graphics (GDI+) 22-14 Visual Basic .NET Professional Skills Development Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
Dim rtb As RichTextBox = _ CType(frmActive.ActiveControl, RichTextBox) If (Not rtb Is Nothing) Then rtb.Undo() End If
Dim rtb As RichTextBox = _ CType(frmActive.ActiveControl, RichTextBox) If (Not rtb Is Nothing) Then rtb.Redo() End If
The Scribble Application Visual Basic .NET Professional Skills Development 22-15 Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
The Scribble Application The Scribble application is an interesting MDI example provided by Microsoft. You can find it in the following directory, which includes several other useful samples: \Program Files\Microsoft Visual Studio .NET\Vb7\VB Samples\ A copy of Scribble is included in the MDI-GDI+.sln sample solution for this chapter. To test it, set Scribble as the startup project for the solution. When you run the application, the mouse cursor acts as a pen, and you can scribble on the child forms, as shown in Figure 7.
Figure 7. Running the Scribble application. The ToolBar Control The Scribble.vb MDI parent form includes a ToolBar control. Select the ToolBar control on the form and bring up the Properties window. Click on the ellipsis next to the Buttons property. This loads the ToolBarButton Collection Editor, as shown in Figure 8. See Scribble Multiple Document Interfaces (MDI) and Graphics (GDI+) 22-16 Visual Basic .NET Professional Skills Development Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
Figure 8. Adding Toolbar buttons and setting their properties, using the ToolBarButton Collection Editor. The ToolBarButton Collection Editor dialog box makes it very easy to add or remove buttons, to change their order of appearance in the ToolBar, and to set their properties. The ImageIndex property of each button is used to select the image to be displayed for each button. The images shown in the drop-down list for this property are the images contained in the Images collection of the ImageList1 component of the form, shown in Figure 9. The Scribble Application Visual Basic .NET Professional Skills Development 22-17 Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
Figure 9. ImageList1 is a component containing a collection of images that can be used by controls on the form. The ToolBar button images are contained in this collection. The ImageList Component The ImageList component provides an efficient way of storing and working with a collection of images to be used by controls on the form. In the Scribble sample form, the images displayed on the ToolBar buttons are taken from the collection of images stored in the ImageList1 component. Multiple Document Interfaces (MDI) and Graphics (GDI+) 22-18 Visual Basic .NET Professional Skills Development Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
To add, remove, or reorder images, bring up the Image Collection Editor dialog box, shown in Figure 10, by clicking the ellipsis next to the Images property of the component.
Figure 10. Adding, removing, or reordering images of the ImageList control with the Image Collection Editor. Responding to ToolBar ButtonClick Events The ToolBar control has a single ButtonClick event that fires when any of the buttons on the ToolBar are clicked. The ToolBarButtonClickEventArgs object has a Button property that allows you to determine which button was clicked: The Scribble Application Visual Basic .NET Professional Skills Development 22-19 Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
Private Sub ToolBar1_ButtonClick( _ ByVal Sender As System.Object, ByVal e As _ System.Windows.Forms.ToolBarButtonClickEventArgs) _ Handles toolBar1.ButtonClick If e.Button Is NewButton Then NewDoc() ElseIf e.Button Is OpenButton Then Open() ElseIf e.Button Is SaveButton Then Save() ElseIf e.Button Is PreviewButton Then PrintPreview() ElseIf e.Button Is PrintButton Then Print() ElseIf e.Button Is HelpButton1 Then ShowHelpTopics() End If End Sub
Displaying Help The Scribble.vb form also includes a HelpProvider component. This component is similar to the ToolTip component in the way that it adds properties to the controls on the form for providing context-sensitive Help when the F1 key is pressed. Figure 11 shows the HelpNamespace property of the HelpProvider1 component, which specifies the location of the scribble.chm Help file.
Figure 11. The HelpProvider properties window. Multiple Document Interfaces (MDI) and Graphics (GDI+) 22-20 Visual Basic .NET Professional Skills Development Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
If the user selects Help from the menu, then the following code runs:
Private Sub ShowHelpTopics() Help.ShowHelp(Me, "..\help\scribble.chm") End Sub
ShowHelp is a shared method of the Help class that allows you to specify a parent control and the URL to a Help file. In this case the parent control is the form itself. How the Scribble Application Works Without going through all the details of exactly how the Scribble application works, it is instructive to look at some of the major architectural strategies behind the application The Stroke and ScribbleDoc Classes The ScribbleDoc.vb file contains code that defines two classes in the Scribble Namespace, Stroke, and ScribbleDoc. The Stroke class holds an ArrayList of points, and the ScribbleDoc class holds an ArrayList of Stroke objects. The ScribbleView Form As you draw on an instance of the ScribbleView form, the mouse event handlers of the form add points to a Stroke object and add Stroke objects to a ScribbleDoc object for that form. In the MouseDown event a new Stroke object is created. In the MouseMove event, points are added to the Stroke object, and in the MouseUp event the completed Stroke object is added to the ScribbleDoc object for that form. In the Paint event, graphics objects and methods are used to draw shapes based on the points that were collected in the mouse events. Saving and Loading Scribbles One of the most interesting aspects of the Scribble application is the way that it saves drawings to disk and reloads them. The drawings are not stored as bitmaps. Instead, the application uses serialization to save a binary representation of the array of Stroke objects that comprise each drawing. The Scribble Application Visual Basic .NET Professional Skills Development 22-21 Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
The Stroke class is marked with an attribute that identifies it as serializable:
<Serializable()> Public Class Stroke
This serializable attribute is a powerful .NET feature. It allows you to save binary representations of objects with very little work. The SaveDocument method of the ScribbleDoc class, which is defined in ScribbleDoc.vb, runs after the user has specified a file name for saving the drawing. The SaveDocument code only requires four lines of code to save a scribble to disk. The code creates a FileStream object, using the FileName selected by the user, and specifies a FileMode of Create, indicating that a new file will be created. The code then creates a BinaryFormatter object and calls its Serialize method, passing in the FileStream and an ArrayList of Stroke objects. The .NET Framework does all the hard work of serializing the stroke objects and saving them to the FilesStream, which the code then closes.
Public Sub SaveDocument(ByVal FileName As String) Try Dim FileStream As Stream = _ File.Open(FileName, FileMode.Create) Dim FileFormatter As New BinaryFormatter() FileFormatter.Serialize(FileStream, StrokeList) FileStream.Close() IsDirty = False Catch Ex As Exception MessageBox.Show(Ex.ToString()) End Try End Sub
Loading a scribble by deserializing a saved array of Stroke objects is just as easy. This time the code creates a FileStream with a FileMode of Open. The code again uses a BinaryFormatter object, calling its Deserialize method to convert the FileStream back into an ArrayList of Stroke objects. See ScribbleDoc. SaveDocument in ScribbleDoc.vb Multiple Document Interfaces (MDI) and Graphics (GDI+) 22-22 Visual Basic .NET Professional Skills Development Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
Public Sub OpenDocument(ByVal FileName As String) Try Dim FileStream As Stream = _ File.Open(FileName, FileMode.Open) Dim FileFormatter As New BinaryFormatter() StrokeList = CType( _ FileFormatter.Deserialize(FileStream), ArrayList) FileStream.Close() Catch Ex As Exception MessageBox.Show(Ex.ToString()) End Try End Sub
Drawing the Scribbles The code that actually draws the scribble shapes on the forms is very simple. It cycles through the ArrayList of points stored in a Stroke object and uses a Pen object to draw lines between each successive pair of points. This method is called from the Paint event handler of a ScribbleView form, which passes a Graphics object to the DrawStroke method. Youll learn about the Graphics and Pen objects used in this method in the next section of this chapter.
Public Sub DrawStroke(ByVal NewGraphic As Graphics) Try Dim MyPen As New Pen(Color.Black, PenWidth) Dim i As Integer For i = 1 To PointArray.Count - 1 NewGraphic.DrawLine( _ MyPen, CType(PointArray((i - 1)), Point), _ CType(PointArray(i), Point)) Next i MyPen.Dispose() Catch Ex As Exception MessageBox.Show(Ex.ToString()) End Try End Sub 'DrawStroke
See Stroke.DrawStroke in ScribbleDoc.vb Drawing on Forms Visual Basic .NET Professional Skills Development 22-23 Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
Drawing on Forms The .NET Framework includes a set of namespaces under System.Drawing that are devoted to graphics. The classes in these namespaces are object- oriented wrappers around the graphics capabilities built into Windows. The previous technology for calling Windows graphics functions was called the graphics device interface (GDI). The new set of managed classes for creating and manipulating graphics is called GDI+. The MDI-GDI+ project includes a sample form that demonstrates several techniques for drawing on Windows forms. If you have been playing with the Scribble project in the sample solution, set the MDI-GDI+ project to be the startup project. To open the sample form, named frmDrawing, run the project and click the switchboard button labeled Drawing on Forms. When you run frmDrawing, a dialog box pops up that lets you choose an Object, Border, and Fill. When you click the Draw button at the bottom of the form, the specified shape is drawn on the form, as shown in Figure 12.
Figure 12. Drawing on a form. OwnerDraw Controls One way to use GDI+ in Windows forms is to take control of drawing the items in combo boxes, list boxes, and menus. When you do this, the controls See frmDrawing Multiple Document Interfaces (MDI) and Graphics (GDI+) 22-24 Visual Basic .NET Professional Skills Development Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
are referred to as OwnerDraw controlsyou are the owner and you are doing the drawing yourself in code rather than leaving the task to the system. The frmDrawing sample form contains several combo boxes for selecting colors that are OwnerDraw controls. These combo boxes display a rectangle showing the color for each item, as shown in Figure 13.
Figure 13. OwnerDraw controls, such as the combo boxes on this form for picking colors, enable you to write code to define each item in the list. DrawMode To create an OwnerDraw combo box (or list box), you must set the DrawMode property of the control to either OwnerDrawFixed or OwnerDrawVariable. The default value for this property is Normal. The difference between using a DrawMode setting of OwnerDrawFixed and OwnerDrawVariable is whether or not you can vary the height of the items in the list. Events to Handle To use either OwnerDrawFixed or OwnerDrawVariable as the DrawMode of a control, you need to handle the controls DrawItem event. This event is raised every time the control creates a new item for display and it provides you with a DrawItemEventArgs object that you use to create the graphics for the current item. Drawing on Forms Visual Basic .NET Professional Skills Development 22-25 Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
To take advantage of the OwnerDrawVariable setting, you must also handle the controls MeasureItem event, which is raised before DrawItem and enables you to adjust the size of the item. All the OwnerDraw combo boxes in frmDrawing have their DrawMode property set to OwnerDrawFixed, so that form does not include any MeasureItem event handler. However, later in the chapter youll see an example of creating an OwnerDraw menu, which always requires you to handle the MeasureItem event of the menu item. MenuItems do not have a DrawMode propertyonly a Boolean OwnerDraw property. Creating an OwnerDraw Color Combo Box The first step in creating an OwnerDraw combo box is to set the DrawMode property of the control. In frmDrawing, the DrawMode for all the color combo boxes is set to OwnerDrawFixed. The list of colors for each combo box is stored in a class-level private ArrayList variable.
Private malBorderColors As New ArrayList() Private malSolidColors As New ArrayList() Private malFromColors As New ArrayList() Private malToColors As New ArrayList() Private malForeColors As New ArrayList() Private malBackColors As New ArrayList()
The form creates a separate ArrayList variable for each of the combo boxes. If you bind more than one combo box to the same array, then every time you make a selection in one of the controls the selection in the other will automatically change to match the first. In other words, all the combo boxes bound to the same ArrayList will stay in sync with each other. Binding each control to its own ArrayList prevents this. See frmDrawing.vb Multiple Document Interfaces (MDI) and Graphics (GDI+) 22-26 Visual Basic .NET Professional Skills Development Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
The Load event handler of the form calls code that populates the ArrayLists and binds the combo boxes to them.
Private Sub frmDrawing_Load( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load ' Open the blank canvas form mfrmCanvas.Show()
' Fill the color arrays. Call FillColorArray(malBorderColors) Call FillColorArray(malSolidColors) Call FillColorArray(malFromColors) Call FillColorArray(malToColors) Call FillColorArray(malForeColors) Call FillColorArray(malBackColors)
The FillColorArray subroutine relies on the fact that the Color class has shared properties that return all the named colors. The code uses Reflection to get all the properties of the Color class, and it filters out all the ones that arent colors. It also filters out the Transparent color which wouldnt work in this context. The code extracts the name from each color object found and adds the color names to the ArrayList that was passed into the subroutine. Drawing on Forms Visual Basic .NET Professional Skills Development 22-27 Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
Sub FillColorArray(ByVal colorNames As ArrayList) Dim pi As System.Reflection.PropertyInfo Dim aPropInfo() As System.Reflection.PropertyInfo ' Get an array of PropertyInfo objects ' from System.Drawing.Color. aPropInfo = GetType(System.Drawing.Color).GetProperties For Each pi In aPropInfo If pi.PropertyType.Name = "Color" _ And pi.Name <> "Transparent" Then colorNames.Add(pi.Name) End If Next End Su
The only task remaining is to handle the DrawItem event of the combo boxes and to use the DrawItemEventArgs object to draw each item. One event handler is used for all the color combo boxes.
Private Sub DrawColorItemEventHandler( _ ByVal sender As Object, _ ByVal e As DrawItemEventArgs) _ Handles cboBorderColor.DrawItem, _ cboSolidColor.DrawItem, _ cboFromColor.DrawItem, _ cboToColor.DrawItem, _ cboForeColor.DrawItem, _ cboBackColor.DrawItem Call DrawColorListItem( _ CType(sender, ListControl), e) End Sub
The event handler calls a generic subroutine, DrawColorListItem, passing it references to the control that raised the event and to the DrawItemEventArgs object. The DrawColorListItem subroutine uses GDI+ objects to draw a colored rectangle and text for each item in the combo box list. Multiple Document Interfaces (MDI) and Graphics (GDI+) 22-28 Visual Basic .NET Professional Skills Development Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
Drawing Shapes and Text These are the primary types of objects used to draw with GDI+: Graphics: A Graphics object is always needed when you use GDI+. It represents the abstract surface that you are drawing on. It is roughly analogous to the device context that was used in older versions of GDI. All drawing is done in relation to a Graphics object. To create shapes and text, you call methods of the Graphics class, such as DrawLine, DrawRectangle, DrawEllipse, DrawArc, DrawPie, and DrawText. There are corresponding Fill methods that use Brush objects to fill in the shapes that are created with the Draw methods. Pen: Pen objects are used to define the characteristics of the borders created around your graphics shapes. Pens are used to draw lines and outlines. Brush: Brush objects are used to fill in areas. This includes filling the space inside an ellipse or a rectangle, as well as filling the shapes defined by fonts when you draw text. Rectangle: In addition to being able to create rectangles by calling the DrawRectangle method of a Graphics object, you also use the separate Rectangle class to define areas within your graphics surface. Region: Irregularly shaped areas are defined by using Region objects, which are composed of Rectangles and Paths. The DrawColorListItem procedure that is called from DrawColorItemEventHandler in frmDrawing illustrates most of the fundamental GDI+ techniques used to draw shapes and text. The code begins by declaring a brush object that is used to fill in the text in the list item, and a Pen object that is used to draw the rectangle that appears to the left of the text.
Private Sub DrawColorListItem( _ ByVal sender As ListControl, _ ByVal e As DrawItemEventArgs) Dim brText As Brush Dim penOutline As New Pen(Color.Black)
Next, the code creates an IList object, which is used to obtain the ArrayList that the list control is bound to. See DrawColorListI tem in frmDrawing Drawing on Forms Visual Basic .NET Professional Skills Development 22-29 Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
Dim ilColors As IList = _ CType(sender.BindingContext(sender.DataSource), _ CurrencyManager).List
A Rectangle is created to define the size and position of the color rectangle that will be drawn. This code uses the DrawItemEventArgs object (e) that was passed into the procedure from the event handler. The rectangle is offset 5 pixels from the left edge of list item, and is given a size of 15 by 10 pixels.
Dim rct As New Rectangle( _ e.Bounds.X + 5, e.Bounds.Y + 5, _ 15, 10)
The DrawItemEventArgs object (e) has an Index property that identifies which item is currently being drawn. The code uses the index to find the corresponding color name in the ArrayList that is stored in the IList object, ilColors. Each item in the list is stored as an Object, so the code converts it to a String to get the name of color for the current item.
Dim strColor As String = _ CType(ilColors.Item(e.Index), String)
The code then uses that color name to create the brush that will be used to fill in the rectangle. The Color class has a shared FromName method that converts a valid color name into a Color object.
Dim brColor As New SolidBrush( _ Color.FromName(strColor))
The color used for the text will vary depending on whether or not the item being drawn is currently selected (highlighted) or not. Unselected text will be filled in with a black brush, but selected items need to use a white brush. The DrawItemEventArgs object provides a bit field State property that the code uses to detect whether the current item is selected. Multiple Document Interfaces (MDI) and Graphics (GDI+) 22-30 Visual Basic .NET Professional Skills Development Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
If (e.State And DrawItemState.Selected) = _ DrawItemState.Selected Then brText = Brushes.White Else brText = Brushes.Black End If
Finally, the code is ready to do the actual drawing. It first calls a method of the DrawItemEventArgs object to have Windows draw the background of the list item according to the current Windows settings.
e.DrawBackground()
Next, the code uses the Graphics property of the DrawItemEventArgs object to get a Graphics object that it can use to draw the text showing the color name and the rectangle filled with a brush of that color. The code uses the Font of the control as the font for the list item, and it offsets the text 5 pixels from the rectangle.
The last step is to have Windows draw the dotted focus rectangle that appears around the active controls on a form.
e.DrawFocusRectangle() End Sub
The SelectedIndexChanged event handler of each combo box calls a procedure that sets the Backcolor of the PictureBox to the right of the combo box to match the selected color. Drawing on Forms Visual Basic .NET Professional Skills Development 22-31 Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
Private Sub SyncPictureBoxToColorList( _ ByVal pic As PictureBox, ByVal ctl As ListControl) If ctl.SelectedIndex > -1 Then pic.BackColor = _ Color.FromName(ctl.Text) Else pic.BackColor = _ Color.FromName("White") End If End Sub
Working with Hatch Styles The frmDrawing form also contains a combo box, cboHatch, that allows the use to select a Hatch style used to fill the shape. This combo box is an OwnerDraw control that displays a rectangle showing the hatch style that will be applied. In addition to providing a variety of fill patterns, hatch styles also allow you to specify the background and foreground colors used when drawing the hatch pattern. The code in frmDrawing that implements the combo box for hash styles is very similar to the code used for the OwnerDraw color combo boxes. One difference is the way that the code populates the control with a list of hatch styles. The code calls the shared GetNames method of the Enum class to get an array of all the names used in the HatchStyle enumeration.
Dim mastrHatchStyleNames() As String = _ System.Enum.GetNames( _ GetType(System.Drawing.Drawing2D.HatchStyle))
See frmDrawing.vb Multiple Document Interfaces (MDI) and Graphics (GDI+) 22-32 Visual Basic .NET Professional Skills Development Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
To populate the list, the code cycles through that array and adds items to the cboHatch combo box in the Load event handler.
Dim i As Integer For i = 0 To mastrHatchStyleNames.Length - 1 cboHatch.Items.Add(mastrHatchStyleNames(i)) Next
The cboHatch_DrawItem event handler calls a procedure named DrawHatchListItem, which is very similar to the DrawColorListItem procedure that you examined in the previous section. The code to extract the selected hatch style name is a bit simpler, but more complex code is required to convert the name to a HatchStyle enumeration object, because there is no FromName method as there is for colors. Also, the code creates a HatchBrush, instead of a plain Brush object, and it sets the ForeColor and BackColor properties of the HatchBrush in the constructor, getting those values from the appropriate combo boxes.
Dim strHatch As String = _ mastrHatchStyleNames(e.Index)
Dim brHatch As New System.Drawing.Drawing2D.HatchBrush _ (hatchstyle:=CType(System.Enum.Parse( _ GetType(Drawing2D.HatchStyle), _ strHatch), Drawing2D.HatchStyle), _ ForeColor:=Color.FromName(cboForeColor.Text), _ BackColor:=Color.FromName(cboBackColor.Text))
When drawing the rectangle, the code can pass the HatchBrush object to the DrawRectangle method even though the method is defined to accept a Brush object, because HatchBrush is derived from Brush. This is a good example of inheritance-based polymorphism at work.
e.Graphics.FillRectangle(brHatch, rct)
Drawing on Forms Visual Basic .NET Professional Skills Development 22-33 Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
Drawing and Filling Shapes Once you understand the code that is used to draw rectangles in the OwnerDraw combo box lists, you are well on your way to understanding the remainder of the code in the form that draws shapes on the frmCanvas form. The btnDraw.Click event handler calls GetSettingsAndDraw, passing it a Graphics object obtained from the frmCanvas form. GetSettingsAndDraw does all the work of gathering up the values that the user has selected on the form. Based on those selections, GetSettingsAndDraw calls the DrawLine, DrawEllipse, or DrawRectangle method, passing in a brush of the correct type to fill the shape, if a fill selection was specified. Handling gradient fills is very similar to handling hatch fills, except that you use a LinearGradientMode, instead of a HatchStyle, and a beginning and ending color, instead of a forecolor and backcolor.
Private Sub btnDraw_Click( _ ByVal sender As Object, _ ByVal e As System.EventArgs) Handles btnDraw.Click Try GetSettingsAndDraw(mfrmCanvas.CreateGraphics) Catch ex As System.ObjectDisposedException 'This exception occurs if ' frmCanvas was closed. mfrmCanvas = New frmCanvas() mfrmCanvas.Show() GetSettingsAndDraw(mfrmCanvas.CreateGraphics) End Try End Sub
GetSettingsAndDraw does all the work of gathering up the values that the user has selected on the form, and it calls the Draw procedure, which actually does the drawing. The Draw procedure evaluates the parameter values and calls the DrawLine, DrawEllipse, or DrawRectangle method, passing in a brush of the correct type to fill the shape, if a fill selection was specified. Handling gradient fills is very similar to handling hatch fills, except you have to use a LinearGradientMode, instead of a HatchStyle, and a beginning and ending color, instead of a forecolor and backcolor. The form only works with linear gradient fills, but GDI+ also supports path gradient fills. See btnDraw.Click in frmDrawing Multiple Document Interfaces (MDI) and Graphics (GDI+) 22-34 Visual Basic .NET Professional Skills Development Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
Overriding OnPaint There is one serious problem with the way that frmDrawing works. If you move a window on top of the drawing and then move it away, the portion of the image that was covered does not get repainted, as shown in Figure 14. Thats because there is no code to tell the form to repaint that image.
Figure 14. Moving an object on top of the form and then away causes covered parts of the image to vanish. Fortunately, this problem is easy to correct. You just need to override the OnPaint method in frmCanvas, so that when the form repaints itself it has a way of finding out what to paint. The one line of code that you need is already in place, but it is commented out. Try uncommenting that line and testing the form again.
Protected Overrides Sub OnPaint( _ ByVal e As System.Windows.Forms.PaintEventArgs) MyBase.OnPaint(e) ' Uncomment this line to ' make the last drawing persist. StartUp.frmDrawing.GetSettingsAndDraw(e.Graphics) End Sub
See OnPaint in frmCanvas Drawing on Forms Visual Basic .NET Professional Skills Development 22-35 Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
Youll find that with that line of code running, the last drawing you create will persist even after it has been covered up. In most applications, your graphics code would go in this procedure so that the repainting would happen automatically. NOTE In this sample form, youll find that after uncommenting the line of code shown above, a shape gets drawn using the default settings in frmDrawing when frmCanvas opens. This is because the OnPaint code is running even before you click the Draw button. If you ever need to trigger the Paint event from your code, you can do so by calling the Invalidate method of the formthis causes the form to immediately be repainted. Disposing of Graphics Objects The Common Language Runtime (CLR) does a very good job of managing memory resources that are consumed by objects. Garbage collection automatically releases this memory when it is no longer being used. However, some resources arent managed efficiently by the CLR. Database connections, for example, usually need to be released well before garbage collection occurs. GDI+ objects also fall into the category of objects that you need to clean up yourself. Most graphics objects, including Brush objects, Pen objects, and the Graphics object itself, have a Dispose method. A good rule of thumb is that when an object has a dispose method, you should call it when you are finished with the object. Multiple Document Interfaces (MDI) and Graphics (GDI+) 22-36 Visual Basic .NET Professional Skills Development Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
Creating Transparent Areas in Forms The Opacity property of a form enables you to add degrees of transparency to the overall image of a form. But what if you just want to make a particular section of a form transparent? There is a property of the form that you can use for this purpose. TransparencyKey Windows Forms have a property named TransparancyKey, which you can set to any color. Any portion of the form that has a background color equivalent to the selected TransparencyKey color will be transparent when the form is displayed. To see an illustration of this, click the Transparency button on the switchboard form. Select a color from the Transparent Color menu, as shown in Figure 15. The shapes on the form that were previously displayed with that color will become transparent
Figure 15. The Transparency form allows you to select a color that will appear to be transparent.
See frmTransparency Creating Transparent Areas in Forms Visual Basic .NET Professional Skills Development 22-37 Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
The event handler for the forms menu item click events sets the forms TransparencyKey property by using the FromName shared method of the Color class to convert a color name into a color object. Private Sub Menu_ClickHandler( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles mnuRed.Click, _ mnuBlue.Click, _ mnuYellow.Click, _ mnuWhite.Click Me.TransparencyKey = _ Color.FromName(CType(sender, MenuItem).Text) End Sub
You can also write code that sets the TransparencyKey to equal the backcolor of the form or of a control on the form. WARNING! Transparent effects are not supported in all versions of Windows. Only Windows 2000 or later (including Windows XP) supports these effects. The TransparencyKey property will have no effect on machines running Windows 98 or Windows ME. Most machines that do support transparency will allow the user to click through to objects that appear behind the transparent areas. However, some users have reported that this click-through behavior does not occur on their systems. Creating OwnerDraw Menus In addition to illustrating the use of the TransparencyKey property, frmTransparency also demonstrates how to create an OwnerDraw menu. The OwnerDraw property of each of the color MenuItems is set to True. Also, the forms code includes event handlers for the MeasureItem and DrawItem events of each of the color MenuItems. Both these events must be handled to create and OwnerDraw menu. See Menu_ ClickHandler in frmTransparency Multiple Document Interfaces (MDI) and Graphics (GDI+) 22-38 Visual Basic .NET Professional Skills Development Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
Handling the MeasureItem Event Here is the procedure that handles the MeasureItem event for all of the color MenuItems on the form.
Private Sub MenuMeasureItemHandler( _ ByVal sender As Object, _ ByVal e As MeasureItemEventArgs) _ Handles mnuRed.MeasureItem, _ mnuBlue.MeasureItem, _ mnuYellow.MeasureItem, _ mnuWhite.MeasureItem e.ItemHeight = 20 e.ItemWidth = 175 End Sub
Your responsibility in writing this event handler is very limited. You only need to set the height and width of the item, using the ItemHeight and ItemWidth properties of the MeasureItemEventArgs object. TIP: In the example, the ItemHeight and ItemWidth values are hard-coded, but you could calculate them, for example, based on the dimensions of your text. For more information, see the Help topic titled Obtaining Font Metrics. Handling the DrawItem Event The code that handles the DrawItem event of the MenuItem objects is very similar to the code you examined for the color combo boxes in frmDrawing. This code is simpler, because it is not necessary to create an exhaustive list of all the named colors. See MenuDraw I temHandler in frmTransparency Creating Transparent Areas in Forms Visual Basic .NET Professional Skills Development 22-39 Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
Private Sub MenuDrawItemHandler( _ ByVal sender As Object, _ ByVal e As DrawItemEventArgs) _ Handles mnuRed.DrawItem, _ mnuBlue.DrawItem, _ mnuYellow.DrawItem, _ mnuWhite.DrawItem
' This brush is used to fill in ' the text shown for each item. Dim brText As Brush ' This pen is used to draw a rectangle ' that will be filled with the color. Dim penOutline As New Pen(Color.Black)
' Create a 15 by 10 pixel rectangle, ' slightly offset from the list item. Dim rct As New Rectangle( _ e.Bounds.X + 5, e.Bounds.Y + 2, _ 15, 10)
' In this case, the menu item ' text is the color name, so no ' other data structure is required. Dim strColor As String = _ CType(sender, MenuItem).Text
' Create a brush of that color ' to fill in a rectangle to the ' left of the text. Dim brColor As New SolidBrush( _ Color.FromName(strColor))
Multiple Document Interfaces (MDI) and Graphics (GDI+) 22-40 Visual Basic .NET Professional Skills Development Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
' Use White text for selected items ' and black for the others. If (e.State And DrawItemState.Selected) = _ DrawItemState.Selected Then brText = Brushes.White Else brText = Brushes.Black End If
' Have Windows draw the background. e.DrawBackground() ' Draw and fill the rectangle for the color. e.Graphics.DrawRectangle(penOutline, rct) e.Graphics.FillRectangle(brColor, rct) ' Draw the text next to the rectangle. e.Graphics.DrawString( _ strColor, _ Me.Font, brText, _ rct.Left + rct.Width + 5, e.Bounds.Y) ' There is no DrawFocusRectangle ' method for menus.
' Clean up brColor.Dispose() penOutline.Dispose() e.Graphics.Dispose() End Sub Creating Shaped Forms Visual Basic .NET Professional Skills Development 22-41 Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
Creating Shaped Forms Every Windows Form has a Region property that defaults to being the rectangle defined by the dimension properties of the form. However, you can set the property to any GDI+ Region object. Region is a GDI+ class used for describing complex shapes. Figure 16 shows frmShape in the designer. When you run the project, click the Shaping Forms button on the switchboard form to open frmShape. Click the Shape button and the form will be displayed with a shape that is a combination of an ellipse and the text entered in the text box.
Figure 16. frmShape will be transformed into a non-rectangular form when you click the Shape button. The code that runs in the click event handler for btnShape uses a GraphicsPath object to create a Region. The code calls the AddString and AddText methods of the GraphicsPath class to build up a complex shape that is then applied to the form. See btnShape_Click in frmShape Multiple Document Interfaces (MDI) and Graphics (GDI+) 22-42 Visual Basic .NET Professional Skills Development Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
Private Sub btnShape_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles btnShape.Click
Dim gp As New GraphicsPath() gp.AddString( _ txtString.Text, New FontFamily("Arial"), _ FontStyle.Bold, 150, New PointF(97, 50), _ StringFormat.GenericDefault) gp.AddEllipse(New Rectangle(0, 0, 100, 300)) Me.Region = New Region(gp) End Sub
As an added effect, when the form closes, it fades away. The code in the btnClose.Click event handler activates a timer. Each Tick event of the timer decrements the Opacity property of the form by .05. When the opacity of the form reaches 0, the code calls the forms Close method.
Private Sub btnClose_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles btnClose.Click Timer1.Enabled = True End Sub
Private Sub Timer1_Tick( _ ByVal sender As Object, _ ByVal e As System.EventArgs) _ Handles Timer1.Tick If Me.Opacity = 0 Then Me.Close() Else Me.Opacity -= 0.05 End If End Sub
See btnClose_Click Creating Shaped Forms Visual Basic .NET Professional Skills Development 22-43 Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
Summary An MDI application has a parent window that creates a shell environment for the application, with other child windows contained within it. You can designate any form as an MDI parent, simply by setting its IsMdiContainer property to True. Child forms may have their own menus, which can replace or merge with those of the parent container. The .NET Framework includes a Clipboard object to cut, copy, or paste values to or from the Windows Clipboard. The .NET Framework includes a set of namespaces under System.Drawing that are devoted to graphics and that serve as object- oriented wrappers around the graphics capabilities built into Windows. You can use OwnerDraw controls to take control of drawing the items in combo boxes, list boxes, and menus. A Graphics object in GDI+ represents the abstract surface that you draw on. Pen objects are used to define the characteristics of lines and of the borders created around your graphics shapes. Brush objects are used to fill in areas. You use the Rectangle class to define areas within a graphics surface. Region objects can be composed of complex shapes. You should call the Dispose method when you are finished using graphics objects. The Opacity property of a form enables you to add degrees of transparency to the overall image of a form. Any portion of the form that has a background color equivalent to the TransparencyKey color will be transparent when the form is displayed. To create non-rectangular forms, set the Region property of the form. Multiple Document Interfaces (MDI) and Graphics (GDI+) 22-44 Visual Basic .NET Professional Skills Development Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
(Review questions and answers on the following pages.) Creating Shaped Forms Visual Basic .NET Professional Skills Development 22-45 Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
Questions 1. How do you designate a form as an MDI parent? 2. Which object does the .NET Framework provide that enables you to cut, copy, and paste data? 3. How can you take control of drawing items in ComboBox controls? 4. Which object in GDI+ represents the abstract surface that you are drawing on? 5. Which method should you call when you are done using graphics objects? 6. What property should you set in order to make a portion of the form transparent when the form is displayed?
Multiple Document Interfaces (MDI) and Graphics (GDI+) 22-46 Visual Basic .NET Professional Skills Development Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
Answers 1. How do you designate a form as an MDI parent? Set its IsMdiContainer property to True 2. Which object does the .NET Framework provide that allows you to cut, copy and paste data? The Clipboard object, plus use methods of TextBox and RichTextBox controls 3. How can you take control of drawing items in ComboBox controls? By setting the DrawMode property and handling the DrawItem (and possibly the MeasureItem) events. 4. Which object in GDI+ represents the abstract surface that you are drawing on? A Graphics object 5. Which method should you call when you are done using graphics objects? The Dispose method 6. What property should you set in order to make a portion of the form transparent when the form is displayed? Set TransparencyKey to the backcolor of the object you want to be transparent. Creating Shaped Forms Visual Basic .NET Professional Skills Development 22-47 Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
Lab 22: Multiple Document Interfaces (MDI) and Graphics (GDI+) TIP: Because this lab includes code that you must type in, weve tried to make it simpler for you. Youll find all the code in MDI-GDILab.txt, in the same directory as the sample project. To avoid typing the code, you can copy/paste it from the text file instead. Lab 22: Multiple Document Interfaces (MDI) and Graphics (GDI+) 22-48 Visual Basic .NET Professional Skills Development Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
Lab 22 Overview In this lab youll work with MDI parent and child forms, youll create an OwnerDraw combo box, and youll draw shapes on a form. To complete this lab, youll need to work through three exercises: MDI Forms Creating an OwnerDraw ComboBox Drawing on Forms Each exercise includes an Objective section that describes the purpose of the exercise. You are encouraged to try to complete the exercise from the information given in the Objective section. If you require more information to complete the exercise, the Objective section is followed by detailed step-by- step instructions. The files for this lab are in the MDI-GDILab directory, and a completed version is in the MDI-GDILabCompleted directory. The sample project includes a startup switchboard form named frmMain that you can use to open the other sample forms when you run the project. MDI Forms Visual Basic .NET Professional Skills Development 22-49 Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
MDI Forms Objective In this exercise, youll change frmShell to be an MDI parent form. Youll write code to open a new instance of frmDocument when a user selects New from the File menu in frmShell, and to make that instance of frmDocument be a child of frmShell. Youll adjust the properties of the File menu items in frmShell and frmDocument, so that the Close item, which is in frmDocument, displays between the New and Exit items in frmShell. Things to Consider Which properties must you change in frmShell and frmDocument? Does the code to open frmDocument need to do anything special to make it a child of frmShell? Which properties do you adjust to ensure that the menus merge properly? Step-by-Step Instructions 1. Open frmShell in Design view and set its IsMdiContainer property to True. 2. Expand the File menu in the frmShell Design view and double-click the New menu item. Add this code to the event handler:
Private Sub mnuFileNew_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles mnuFileNew.Click Dim frm As New frmDocument() frm.MdiParent = Me frm.Show() End Sub
Lab 22: Multiple Document Interfaces (MDI) and Graphics (GDI+) 22-50 Visual Basic .NET Professional Skills Development Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
3. Test the forms. Run the project by pressing F5. Click the MDI button to open frmShell. Select File|New to create a new frmDocument MDI child window. Notice that two separate File menus are displayed in the parent window. Select Close from the second File menu to close the document window. Select Exit from the first File menu to close the parent window. Close the switchboard window. 4. In Visual Studio, open frmDocument in Design view and select the mnuFile MenuItem. Set its MergeType property to MergeItems. 5. Select the mnuFileClose MenuItem in frmDocument. Set its MergeOrder property to 2. 6. Open frmShell in Design view. Select the mnuFile MenuItem in frmShell and set its MergeType property to MergeItems. 7. Select the mnuFileNew MenuItem in frmShell. Set its MergeOrder property to 1. 8. Select the mnuFileExit MenuItem in frmShell. Set its MergeOrder property to 3. 9. Test the application by repeating Step 3. After opening a new frmDocument window, the menus are now properly merged, as shown in Figure 17.
Figure 17. After setting the MergeType and MergeOrder properties, the menus merge correctly. Creating an OwnerDraw ComboBox Visual Basic .NET Professional Skills Development 22-51 Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
Creating an OwnerDraw ComboBox Objective In this exercise, youll change the cboGradient ComboBox control in frmDrawing to be an OwnerDraw ComboBox that displays a rectangle showing the gradient fill matching each item and using the colors selected in the cboFromColor and cboToColor combo boxes. Youll also write code to display the selected gradient in the PictureBox control to the right of the combo box. Things to Consider Do you need to change any properties of cboGradient? Where do you place the code that draws each item in the combo box? Is there another combo box on the form that you can use as a model for the changes to cboGradient? What kind of object will you need to use to fill the rectangle with a linear gradient pattern? Is there code in the Draw procedure that you can use to see how to create the gradient effect? Step-by-Step Instructions 1. Open frmDrawing in Design view. Select cboGradient and set its DrawMode property to OwnerDrawFixed. 2. Right-click on the form and select View Code. In the code window, select cboGradient in the upper-left drop-down list. Select the DrawItem event in the drop-down list on the right. Add this code to the event handler: Lab 22: Multiple Document Interfaces (MDI) and Graphics (GDI+) 22-52 Visual Basic .NET Professional Skills Development Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
Private Sub cboGradient_DrawItem(ByVal sender As Object, _ ByVal e As System.Windows.Forms.DrawItemEventArgs) _ Handles cboGradient.DrawItem Call DrawGradientListItem( _ CType(sender, ListControl), e) End Sub
3. Add this DrawGradientListItem procedure to draw each item. (The code follows the pattern in DrawHatchListItem, and it employs the drawing technique found in the Draw procedure to create and use a LinearGradientBrush.) Creating an OwnerDraw ComboBox Visual Basic .NET Professional Skills Development 22-53 Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
Private Sub DrawGradientListItem( _ ByVal sender As ListControl, _ ByVal e As DrawItemEventArgs) ' Call from the DrawList event, ' passing in the event parameters.
' This brush is used to fill in ' the text shown for each item. Dim brText As Brush ' This pen is used to draw a rectangle ' that will be filled with the hatch style. Dim penOutline As New Pen(Color.Black)
' Create a 15 by 10 pixel rectangle, ' slightly offset from the list item. Dim rct As New Rectangle( _ e.Bounds.X + 5, e.Bounds.Y + 5, _ 15, 10)
Dim strGradient As String = _ mastrGradientNames(e.Index)
' Create a GradientBrush, based on the ' LinearGradientMode, FromColor, and ToColor. Dim brGradient As New _ System.Drawing.Drawing2D.LinearGradientBrush _ (rect:=rct, _ color1:=Color.FromName(cboFromColor.Text), _ color2:=Color.FromName(cboToColor.Text), _ linearGradientMode:=CType(System.Enum.Parse( _ GetType(Drawing2D.HatchStyle), _ strGradient), Drawing2D.LinearGradientMode))
Lab 22: Multiple Document Interfaces (MDI) and Graphics (GDI+) 22-54 Visual Basic .NET Professional Skills Development Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
' Use White text for selected items ' and black for the others. If (e.State And DrawItemState.Selected) = _ DrawItemState.Selected Then brText = Brushes.White Else brText = Brushes.Black End If
' Have Windows draw the background. e.DrawBackground() ' Draw and fill the rectangle for the color. e.Graphics.DrawRectangle(penOutline, rct) e.Graphics.FillRectangle(brGradient, rct) ' Draw the text next to the rectangle. e.Graphics.DrawString( _ strGradient, _ CType(sender, Control).Font, brText, _ rct.Left + rct.Width + 5, e.Bounds.Y) ' Have Windows draw the dotted focus rectangle. e.DrawFocusRectangle()
' Clean up brGradient.Dispose() penOutline.Dispose() e.Graphics.Dispose() End Sub
4. Create a procedure that updates the piccboGradient PictureBox control, based on the selection in cboGradient, cboFromColor, and cboToColor. Creating an OwnerDraw ComboBox Visual Basic .NET Professional Skills Development 22-55 Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
Private Sub SyncPictureBoxToGradientList()
If cboGradient.SelectedIndex > -1 Then ' Create a LinearGradientBrush, based on the selected ' LinearGradientMode, FromColor, and ToColor. Dim brGradient As New _ System.Drawing.Drawing2D.LinearGradientBrush _ (rect:=piccboGradient.DisplayRectangle, _ color1:=Color.FromName(cboFromColor.Text), _ color2:=Color.FromName(cboToColor.Text), _ linearGradientMode:=CType(System.Enum.Parse( _ GetType(Drawing2D.HatchStyle), _ cboGradient.Text), Drawing2D.LinearGradientMode))
Dim g As Graphics = piccboGradient.CreateGraphics g.FillRectangle(brGradient, piccboGradient.DisplayRectangle)
Else piccboGradient.BackColor = _ Color.FromName("White") End If End Sub
5. Add an event handler for the SelectedIndexChanged event of cboGradient to call SyncPictureBoxToGradientList.
Private Sub cboGradient_SelectedIndexChanged( _ ByVal sender As Object, ByVal e As System.EventArgs) _ Handles cboGradient.SelectedIndexChanged Call SyncPictureBoxToGradientList() End Sub
6. Add code to the existing event handlers for the SelectedIndexChanged events of cboFromColor and cboToColor, to call SyncPictureBoxToGradientList. Lab 22: Multiple Document Interfaces (MDI) and Graphics (GDI+) 22-56 Visual Basic .NET Professional Skills Development Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
Private Sub cboFromColor_SelectedIndexChanged( _ ByVal sender As Object, ByVal e As System.EventArgs) _ Handles cboFromColor.SelectedIndexChanged Call SyncPictureBoxToColorList( _ piccboFromColor, _ CType(sender, ListControl)) ' Update the Gradient PictureBox Call SyncPictureBoxToGradientList() End Sub
Private Sub cboToColor_SelectedIndexChanged( _ ByVal sender As Object, ByVal e As System.EventArgs) _ Handles cboToColor.SelectedIndexChanged Call SyncPictureBoxToColorList( _ piccboToColor, _ CType(sender, ListControl)) ' Update the Gradient PictureBox Call SyncPictureBoxToGradientList() End Sub
7. Now, run the application, click the OwnerDraw button, and test that the gradient shows in the drop-down list and in the PictureBox control when you select a gradient fill. Drawing on Forms Visual Basic .NET Professional Skills Development 22-57 Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
Drawing on Forms Objective In this exercise, youll first work with frmRectangle1, which contains a Button control. Add code that runs when a user clicks the button and that draws a rectangle centered on the Button. The rectangle should be 20 pixels larger than the button, both horizontally and vertically, and it should have a black border with a thickness of 2 pixels. The rectangle your code creates in frmRectangle1 will disappear if you move the form so that it is covered and then uncovered. In frmRectangle2, write code that creates the same rectangle and displays it when the button is clicked, but that keeps the rectangle visible even after it has been covered and uncovered. Things to Consider Which properties do you use to retrieve the dimensions of a Button control? Which objects do you use to draw a rectangle on a form? Where should your code go if you want the rectangle to show after being covered and uncovered? Step-by-Step Instructions 1. Open frmRectangle1 in the designer and double-click the Draw Rectangle Button control to create and event handler for btnDrawRectangle. 2. Declare four variables to retrieve the desired position and dimensions of the new rectangle, based on the properties of btnDrawRectangle. Subtract 10 from the Left and Top, and add 20 to the Width and Height to create a rectangle that is centered on the button and 20 pixels larger in both directions.
Dim intX As Integer = btnDrawRectangle.Left - 10 Dim intY As Integer = btnDrawRectangle.Top - 10 Dim intWidth As Integer = btnDrawRectangle.Width + 20 Dim intHeight As Integer = btnDrawRectangle.Height + 20
Lab 22: Multiple Document Interfaces (MDI) and Graphics (GDI+) 22-58 Visual Basic .NET Professional Skills Development Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
3. Declare Pen and Rectangle variables to use when drawing the rectangle.
Dim pen As Pen Dim rct As Rectangle
4. Instantiate the pen variable, setting the Color to black and the thickness to 2.
pen = New Pen(Color.FromName("black"), 2)
5. Instantiate the rct variable, setting the x, y, Width, and Height properties to the values computed from the position and size of the Button.
rct = New Rectangle(x:=intX, y:=intY, _ Width:=intWidth, Height:=intHeight)
6. Create a Graphics object and draw the rectangle.
Dim gr As Graphics gr = Me.CreateGraphics gr.DrawRectangle(pen:=pen, rect:=rct)
7. Run cleanup code to dispose of the Graphics object.
gr.Dispose()
8. Press F5 to run the project. Test the form by clicking the Rectangle 1 button on the startup switchboard form and then clicking the Draw Rectangle button. The form should look like Figure 18. Drawing on Forms Visual Basic .NET Professional Skills Development 22-59 Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
Figure 18. Drawing a rectangle around a Button control. 9. Move the form so that part of the rectangle is off the screen and then move it back. The part of the rectangle that was covered will vanish. Close the form and the switchboard form. 10. Open frmRectangle2 in Visual Studio. Right-click and select View Code. Create a Boolean field that you will use to keep track of when the button on the form has been clicked.
Private mblnDrawRectangle As Boolean
11. In the upper-left drop-down list in the code window, select (Overrides). In the list on the right, select OnPaint. Add code to this procedure to draw the rectangle. Other than its placement in the OnPaint procedure, this code is identical to the code used in frmRectangle1, except where it instantiates the gr variable, which now is created using the PaintEventArgs parameter, e.
Protected Overrides Sub OnPaint( _ ByVal e As System.Windows.Forms.PaintEventArgs) ' Run this code ' only after the button ' has been clicked. If mblnDrawRectangle Then ' Get the rectangle position ' and dimensions. Dim intX As Integer = btnDrawRectangle.Left - 10 Dim intY As Integer = btnDrawRectangle.Top - 10 Dim intWidth As Integer = btnDrawRectangle.Width + 20 Lab 22: Multiple Document Interfaces (MDI) and Graphics (GDI+) 22-60 Visual Basic .NET Professional Skills Development Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.
Dim intHeight As Integer = btnDrawRectangle.Height + 20
' Set up the rectangle Dim pen As Pen Dim rct As Rectangle pen = New Pen(Color.FromName("black"), 2) rct = New Rectangle(x:=intX, y:=intY, _ Width:=intWidth, Height:=intHeight)
' Draw the rectangle Dim gr As Graphics gr = e.Graphics gr.DrawRectangle(pen:=pen, rect:=rct)
' Cleanup gr.Dispose() End If End Sub
12. Write code in the Click event handler of btnDrawRectangle to set the Boolean variable to True, so that the code in the OnPaint procedure will run. Call the forms Invalidate method to force a repaint now.
Private Sub btnDrawRectangle_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles btnDrawRectangle.Click mblnDrawRectangle = True ' Force the form to ' be repainted. Me.Invalidate() End Sub
13. Test the form by moving it offscreen and back. The rectangle should remain visible.