Chapter 11 (Performance and Graphics) Samples
Listing 11.1. Populating and Clearing a TreeView Control Using Alternative Strategies
'------------------------------------------------------------ 'Note #1: This sample uses the PerformanceSampling class ' defined earlier in this book. Make sure this class ' is included in your project. 'Note #2: This code need to be inserted into a Form class that ' has a TreeView control and buttons hooked up to the ' xxx_Click functions below. '------------------------------------------------------------ 'Number of items to place into the tree view Const NUMBER_ITEMS As Integer = 800 '-------------------------------------------- 'Code for: "Fill: Baseline" Button ' '"Unoptimized" Approach to filling a TreeView '-------------------------------------------- Private Sub UnOptimizedFill_Click(ByVal sender As _ System.Object, ByVal e As System.EventArgs) _ Handles UnOptimizedFill.Click 'To make sure we're testing the same thing, make sure 'the array is clear If (TreeView1.Nodes.Count > 0) Then TreeView1.BeginUpdate() TreeView1.Nodes.Clear() TreeView1.EndUpdate() TreeView1.Update() End If 'For more consistent measurement, collect the garbage 'before running. Do not do this in production code! System.GC.Collect() 'Start the test timer PerformanceSampling.StartSample(0, "TreeViewPopulate") 'Fill the TreeView Dim i As Integer For i = 1 To NUMBER_ITEMS TreeView1.Nodes.Add("TreeItem" + CStr(i)) Next 'Stop the test timer and show the results PerformanceSampling.StopSample(0) MsgBox(PerformanceSampling.GetSampleDurationText(0)) End Sub '-------------------------------------------- 'Code for: "Clear: Baseline" Button ' '"Unoptimized" Approach to filling a TreeView '-------------------------------------------- Private Sub UnOptimizedClear_Click(ByVal sender As _ System.Object, ByVal e As System.EventArgs) _ Handles UnOptimizedClear.Click 'For more consistent measurement, collect the garbage ' before running System.GC.Collect() 'Start the test timer PerformanceSampling.StartSample(1, "TreeViewClear") TreeView1.Nodes.Clear() PerformanceSampling.StopSample(1) MsgBox(PerformanceSampling.GetSampleDurationText(1)) End Sub '-------------------------------------------- 'Code for: "Fill: BeginUpdate" Button ' '"Using BeginUpdate()" Approach '-------------------------------------------- Private Sub UseBeginEndUpdateForFill_Click(ByVal sender As _ System.Object, ByVal e As System.EventArgs) _ Handles UseBeginEndUpdateForFill.Click 'To make sure we're testing the same thing, make sure 'the array is clear If (TreeView1.Nodes.Count > 0) Then TreeView1.BeginUpdate() TreeView1.Nodes.Clear() TreeView1.EndUpdate() TreeView1.Update() End If 'For more consistent measurement, collect the garbage ' before running. DO NOT DO THIS IN PRODUCTION CODE! System.GC.Collect() 'Start the test timer PerformanceSampling.StartSample(2, _ "Populate - Use BeginUpdate") 'Fill the TreeView TreeView1.BeginUpdate() Dim i As Integer For i = 1 To NUMBER_ITEMS TreeView1.Nodes.Add("TreeItem" + i.ToString()) Next TreeView1.EndUpdate() 'Stop the test timer and show the results PerformanceSampling.StopSample(2) MsgBox(PerformanceSampling.GetSampleDurationText(2)) End Sub '-------------------------------------------- 'Code for: "Clear: BeginUpdate" Button ' '"Using BeginUpdate()" Approach '-------------------------------------------- Private Sub UseBeginEndUpdateForClear_Click(ByVal sender As _ System.Object, ByVal e As System.EventArgs) _ Handles UseBeginEndUpdateForClear.Click 'For more consistent measurement, collect the garbage ' before running. DO NOT DO THIS IN PRODUCTION CODE! System.GC.Collect() 'Start the test timer PerformanceSampling.StartSample(3, "Clear - Use BeginUpdate") TreeView1.BeginUpdate() TreeView1.Nodes.Clear() TreeView1.EndUpdate() 'Stop the test timer and show the results PerformanceSampling.StopSample(3) MsgBox(PerformanceSampling.GetSampleDurationText(3)) End Sub '-------------------------------------------- 'Code for: "Fill: Use Array" Button ' '"Using Array" Approach '-------------------------------------------- Private Sub FillArrayBeforeAttachingToTree_Click(ByVal _ sender As System.Object, ByVal e As System.EventArgs) _ Handles FillArrayBeforeAttachingToTree.Click 'To make sure we're testing the same thing, make sure ' the array is clear If (TreeView1.Nodes.Count > 0) Then TreeView1.BeginUpdate() TreeView1.Nodes.Clear() TreeView1.EndUpdate() TreeView1.Update() End If 'For more consistent measurement, collect the garbage before 'running. DO NOT DO THIS IN PRODUCTION CODE! System.GC.Collect() 'Start the test timer PerformanceSampling.StartSample(4, "Populate - Use Array") 'Allocate space for our array of tree nodes Dim newTreeNodes() As System.Windows.Forms.TreeNode ReDim newTreeNodes(NUMBER_ITEMS - 1) 'Fill up the array Dim i As Integer For i = 0 To NUMBER_ITEMS - 1 newTreeNodes(i) = _ New System.Windows.Forms.TreeNode("TreeItem" + _ i.ToString()) Next 'Connect the array to the TreeView TreeView1.BeginUpdate() TreeView1.Nodes.AddRange(newTreeNodes) TreeView1.EndUpdate() 'Stop the test timer and show the results PerformanceSampling.StopSample(4) MsgBox(PerformanceSampling.GetSampleDurationText(4)) End Sub
Listing 11.2. Dynamic Population of a TreeView Control
'Dummy text to put in the placeholder child nodes Const dummy_node As String = "_dummynode" 'Tag we will use to indicate a node Const node_needToBePopulated As String = "_populateMe" 'Text we will use for our top-level nodes Const nodeText_Neighborhoods As String = "Neighborhoods" Const nodeText_Prices As String = "Prices" Const nodeText_HouseType As String = "HouseTypes" '------------------------------------------------------------- 'Click event handler for our button ' 'Sets up our TreeView to show incremental filling of the 'tree '------------------------------------------------------------- Private Sub Button1_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Button1.Click Dim tnNewNode As TreeNode 'Turn off UI updates before we fill in the tree TreeView1.BeginUpdate() 'Throw out any old data TreeView1.Nodes.Clear() '----------------------------- ''Neighborhoods' node '----------------------------- 'Add the top-level 'Neighborhoods' node. tnNewNode = TreeView1.Nodes.Add("Neighborhoods") 'Set a tag on the node that indicates that we will 'dynamically fill in the node tnNewNode.Tag = node_needToBePopulated 'This dummy child node only exists so that the node has 'at least one child node and therefore the tree node is 'expandable. tnNewNode.Nodes.Add(dummy_node) '----------------------------- ''Price' node '----------------------------- tnNewNode = TreeView1.Nodes.Add("Price") 'Set a tag on the node that indicates that we will 'dynamically fill in the node tnNewNode.Tag = node_needToBePopulated 'This dummy child node only exists so that the node has 'at least one child node and therefore the tree node is 'expandable. tnNewNode.Nodes.Add(dummy_node) '----------------------------- ''HouseType' node '----------------------------- tnNewNode = TreeView1.Nodes.Add("HouseType") 'Set a tag on the node that indicates that we will 'dynamically fill in the node tnNewNode.Tag = node_needToBePopulated 'This dummy child node only exists so that the node has 'at least one child node and therefore the tree node is 'expandable. tnNewNode.Nodes.Add(dummy_node) 'Resume the UI updates TreeView1.EndUpdate() End Sub ''------------------------------------------------------ ''BeforeExpand event handler for our TreeView ''NOTE: Unlike with C#, This event handler '' DOES NOT require you to tinker with the code in '' "InitializeComponent()" (don't do this!) '' You can just choose the event the regular way '' via the VB editors event drop-down list '' ''Called when a user asks to expand a node that has at least ''one child node. This is called before the node's children ''are shown and gives us a chance to dynamically populate the ''TreeView control. ''------------------------------------------------------ Private Sub TreeView1_BeforeExpand(ByVal sender As Object, _ ByVal e As System.Windows.Forms.TreeViewCancelEventArgs) _ Handles TreeView1.BeforeExpand 'Get the node that is about to be expanded Dim tnExpanding As System.Windows.Forms.TreeNode tnExpanding = e.Node 'If the node is not marked 'need to be populated' the 'node is fine 'as is.' If Not (tnExpanding.Tag Is node_needToBePopulated) Then Return 'Allow things to contine without hinderance End If '-------------------------------------------------------- 'Dynamic tree population required. 'We know the node needs to be populated, figure out which 'node it is '-------------------------------------------------------- If (tnExpanding.Text = nodeText_Neighborhoods) Then PopulateTreeViewNeighborhoods(tnExpanding) Return 'done adding items! Else 'Check other possibilities for tree nodes we need to add. MsgBox("UN-DONE: Add code to dynamically populate this node") 'Remove the tag from the node so we don't run this 'code again tnExpanding.Tag = " End If End Sub '------------------------------------------------------ 'This function is called to dynamically add child nodes 'To the "Neighborhood" node '------------------------------------------------------ Sub PopulateTreeViewNeighborhoods(ByVal tnAddTo As TreeNode) Dim tvControl As TreeView tvControl = tnAddTo.TreeView tvControl.BeginUpdate() 'Clear the dummy sub-node we have in there tnAddTo.Nodes.Clear() 'Declare four nodes we want to make children 'of the node that was passed in. Dim newNeighborhoodNodes() As TreeNode ReDim newNeighborhoodNodes(3) newNeighborhoodNodes(0) = New TreeNode("Capitol Hill") newNeighborhoodNodes(1) = New TreeNode("Chelsea") newNeighborhoodNodes(2) = New TreeNode("Downtown") newNeighborhoodNodes(3) = New TreeNode("South Bay") 'Add the child nodes to the tree view tnAddTo.Nodes.AddRange(newNeighborhoodNodes) tvControl.EndUpdate() End Sub
Listing 11.3. Programmatic TextBox Update Causes Event Code to Be Run
Private m_eventTriggerCount As Integer Private Sub Button1_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Button1.Click 'This triggers a TextChanged event 'same as if the user typed in text TextBox1.Text = "Hello World" End Sub Private Sub TextBox1_TextChanged(ByVal sender As _ System.Object, ByVal e As System.EventArgs) _ Handles TextBox1.TextChanged m_eventTriggerCount = m_eventTriggerCount + 1 'Update a label to show the number of events Label1.Text = "Events: #" + CStr(m_eventTriggerCount) 'List each of the events ListBox1.Items.Add(m_eventTriggerCount.ToString() + _ TextBox1.Text) End Sub
Listing 11.4. Using a State Model for Updates and Instrumentation to Better Understand and Control Event Processing
'------------------------------------------------------------- 'To enable event instrumentation: ' #Const EVENTINSTRUMENTATION = 1 ' 'To disable event instrumentation: ' #Const EVENTINSTRUMENTATION = 0 '------------------------------------------------------------- #Const EVENTINSTRUMENTATION = 1 '------------------------------------------------------------- 'A flag that tells control event handlers if they should 'exit without doing any work '------------------------------------------------------------- Private m_userInterfaceUpdateOccuring As Boolean 'Counters for event occurrences Private m_radioButton1ChangeEventCount As Integer Private m_textBox1ChangeEventCount As Integer '------------------------------------------------------------- 'Code we only want to include if we are running in an 'instrumented mode. This code has relatively high execution 'overhead and we only want to compile it in and run it if 'we are doing diagnostics. '------------------------------------------------------------- #If EVENTINSTRUMENTATION <> 0 Then Private m_instrumentedEventLog As System.Collections.ArrayList '------------------------------------------------------------- 'Logs the occurrence of an event into an array we can inspect ' 'Note: No attempt is made to keep the size of the ' logging array bounded, so the longer the application ' runs the larger this array will become '------------------------------------------------------------- Private Sub instrumented_logEventOccurrence(ByVal eventData _ As String) 'Create the event log if it has not already been created If (m_instrumentedEventLog Is Nothing) Then m_instrumentedEventLog = _ New System.Collections.ArrayList End If 'Log the event m_instrumentedEventLog.Add(eventData) End Sub '------------------------------------------------------------- 'Show the list of events that have occurred 'Note: This implementation is pretty crude. ' You may want instead to show the events ' list in a separate dialog that pops up for the ' purpose. '------------------------------------------------------------- Private Sub instrumentation_ShowEventLog() Dim listItems As _ System.Windows.Forms.ListBox.ObjectCollection listItems = listBoxEventLog.Items 'Clear the items in the list listItems.Clear() 'If there are no events, exit If (m_instrumentedEventLog Is Nothing) Then listItems.Add("0 Events") Return End If 'At the top of the list show the total of events we 'have counted listItems.Add(m_instrumentedEventLog.Count.ToString() + _ " Events") 'List the items in reverse order, so the most recent are 'displayed first Dim logItem As String Dim listIdx As Integer For listIdx = _ m_instrumentedEventLog.Count - 1 To 0 Step -1 logItem = CStr(m_instrumentedEventLog(listIdx)) listItems.Add(logItem) Next End Sub #End If '------------------------------------------------------------- 'RadioButton1 Changed event '------------------------------------------------------------- Private Sub RadioButton1_CheckedChanged(ByVal sender As _ System.Object, ByVal e As System.EventArgs) _ Handles RadioButton1.CheckedChanged 'If our application is updating the data in the 'user interface we do not want to treat this as 'a user triggered event. If this is the case 'exit and do nothing. If (m_userInterfaceUpdateOccuring = True) Then Return End If 'Count the number of times this event has been called m_radioButton1ChangeEventCount = _ m_radioButton1ChangeEventCount + 1 #If (EVENTINSTRUMENTATION <> 0) Then 'Log the occurrence of the event instrumented_logEventOccurrence("radioButton1.Change:" + _ m_radioButton1ChangeEventCount.ToString() + ":" + _ RadioButton1.Checked.ToString()) 'value #End If End Sub '------------------------------------------------------------- 'Button1 click event 'Simulates a case where code updates the user interface 'potentially causes event code to be run '------------------------------------------------------------- Private Sub Button1_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Button1.Click 'Indicate that we do not want the event handlers 'to process events right now because we are updating 'the user interface. ' 'm_userInterfaceUpdateOccuring = true RadioButton1.Checked = True TextBox1.Text = "Hello World" 'We are done updating the user interface m_userInterfaceUpdateOccuring = False End Sub '------------------------------------------------------------- 'TextBox changed event handler '------------------------------------------------------------- Private Sub TextBox1_TextChanged(ByVal sender As System.Object _ , ByVal e As System.EventArgs) Handles TextBox1.TextChanged 'If our application is updating the data in the 'user interface we do not want to treat this as 'a user triggered event. If this is the case 'exit and do nothing. If (m_userInterfaceUpdateOccuring = True) Then Return End If 'Count the number of times we execute this event m_textBox1ChangeEventCount = m_textBox1ChangeEventCount + 1 #If EVENTINSTRUMENTATION <> 0 Then 'Log the occurrence of the event instrumented_logEventOccurrence("textBox1.Change:" + _ m_textBox1ChangeEventCount.ToString() + ":" + _ TextBox1.Text.ToString()) 'Value #End If End Sub Private Sub buttonShowEventLog_Click(ByVal sender As _ System.Object, ByVal e As System.EventArgs) _ Handles buttonShowEventLog.Click #If EVENTINSTRUMENTATION <> 0 Then instrumentation_ShowEventLog() #End If End Sub
Listing 11.5. Calling a Controls Update() Method to Show Progress Text
'--------------------------------------------------- 'This code belongs in a Form containing a single 'Button (button1) and a Label (label1) '--------------------------------------------------- Private Sub Button1_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Button1.Click 'Show a wait cursor System.Windows.Forms.Cursor.Current = _ System.Windows.Forms.Cursors.WaitCursor Dim testString As String Dim loop3 As Integer For loop3 = 1 To 100 Step 10 Label1.Text = loop3.ToString() + "% Done..." '!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 'Uncomment the line below to show progress updates! '!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 'Label1.Update() testString = " Dim loop2 As Integer For loop2 = 1 To 1000 testString = testString + "test" Next Next Label1.Text = "Done!" 'Remove the wait cursor System.Windows.Forms.Cursor.Current = _ System.Windows.Forms.Cursors.Default End Sub
Listing 11.6. Drawing into an Off-Screen Bitmap and Sending It to a Picture Box
'------------------------------------------------------------- 'Draw into a bitmap. Send the bitmap to a PictureBox '------------------------------------------------------------- Private Sub Button1_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Button1.Click 'Create a new bitmap Dim myBitmap As System.Drawing.Bitmap myBitmap = New System.Drawing.Bitmap(PictureBox1.Width, _ PictureBox1.Height) '----------------------------------------------------- 'Create a graphics object so we can draw in the bitmap '----------------------------------------------------- Dim myGfx As System.Drawing.Graphics myGfx = System.Drawing.Graphics.FromImage(myBitmap) 'Paint our bitmap all yellow myGfx.Clear(System.Drawing.Color.Yellow) 'Create a pen Dim myPen As System.Drawing.Pen myPen = New System.Drawing.Pen(System.Drawing.Color.Blue) '----------------------------------------------------- 'Draw an ellipse '----------------------------------------------------- myGfx.DrawEllipse(myPen, 0, 0, myBitmap.Width - 1, _ myBitmap.Height - 1) 'Create a solid brush Dim myBrush As System.Drawing.Brush '----------------------------------------------------- 'Draw the text with the brush '----------------------------------------------------- myBrush = New System.Drawing.SolidBrush( _ System.Drawing.Color.Black) 'Note: We are useing the Font object from the Form myGfx.DrawString("Hello!", Me.Font, myBrush, 2, 10) '----------------------------------------------------- 'Important! Clean up after ourselves '----------------------------------------------------- myGfx.Dispose() myPen.Dispose() myBrush.Dispose() '----------------------------------------------------- 'Tell the picture box that it should display the 'bitmap we just created and drew on. '----------------------------------------------------- PictureBox1.Image = myBitmap End Sub
Listing 11.7. Creating a Graphics Object for a Form
'------------------------------------------------------------- 'Creates a Graphics object for a Form and draws onto it '------------------------------------------------------------- Private Sub Button1_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Button1.Click 'Create a Graphics object for the Form Dim myGfx As System.Drawing.Graphics myGfx = Me.CreateGraphics() 'Create a Brush Dim myBrush As System.Drawing.Brush myBrush = New System.Drawing.SolidBrush( _ System.Drawing.Color.DarkGreen) 'Fill the rectangle myGfx.FillRectangle(myBrush, 4, 2, 60, 20) '-------------------------------------- 'Important: Clean up! '-------------------------------------- myBrush.Dispose() myGfx.Dispose() End Sub
Listing 11.8. Hooking into the Paint Function for a Form
'Brushes we want to cache, so we don't need to create/dispose them 'all the time Private m_brushBlue As System.Drawing.Brush Private m_brushYellow As System.Drawing.Brush 'Just for fun, lets count the number of times we are called Private m_paintCount As Integer '-------------------------------------------------------------- 'We are overriding our base classes 'Paint' event. This means 'every time the Form gets called to paint itself, this 'function will get called. '-------------------------------------------------------------- Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs) 'Important: Call the base class and allow it to do its 'paint work MyBase.OnPaint(e) 'Up the count of the number of times we have been called m_paintCount = m_paintCount + 1 '------------------------------------------------------------ 'Important: 'Instead of creating a graphics object, we are being lent one 'for the duration of this call. This means that it is not 'our job to .Dispose() of the object '------------------------------------------------------------ Dim myGfx As System.Drawing.Graphics myGfx = e.Graphics '------------------------------------------------------------ 'Because this painting needs to occur quickly, let's cache the 'brushes so we don't need to create/dispose them every time 'we are called '------------------------------------------------------------ If (m_brushBlue Is Nothing) Then m_brushBlue = New System.Drawing.SolidBrush( _ System.Drawing.Color.Blue) End If If (m_brushYellow Is Nothing) Then m_brushYellow = New System.Drawing.SolidBrush( _ System.Drawing.Color.Yellow) End If '------------------------------------------------------------ 'Do the drawing '------------------------------------------------------------ myGfx.FillRectangle(m_brushBlue, 2, 2, 100, 100) myGfx.DrawString("PaintCount: " + CStr(m_paintCount), _ Me.Font, m_brushYellow, 3, 3) 'Exit: Nothing we want to call .Dispose() on. End Sub
Listing 11.9. A Simple Custom Control That Changes Colors and Fires a Custom Event
'A very simple custom control Public Class myButton Inherits System.Windows.Forms.Control '------------------------------------------------------- 'Objects we need for drawing '------------------------------------------------------- Private m_RectangleBrush As System.Drawing.Brush Private m_TextBrush As System.Drawing.Brush Private m_RectangleColor As System.Drawing.Color '------------------------------------------------------- 'The event we want to expose. This is a public delegate. '------------------------------------------------------- Public Event EventButtonTurningBlue(ByVal sender As Object, _ ByVal e As System.EventArgs) 'The constructor Public Sub New() MyBase.New() 'Note: We should write a "Dispose()" function and 'destructor that clean up these objects 'Create the brushes we will need m_RectangleColor = System.Drawing.Color.Black m_RectangleBrush = New System.Drawing.SolidBrush( _ m_RectangleColor) m_TextBrush = New System.Drawing.SolidBrush( _ System.Drawing.Color.White) End Sub '------------------------------------------------------- 'Internal response to being clicked is to cycle 'through three different button colors '------------------------------------------------------- Protected Overrides Sub OnClick(ByVal e As System.EventArgs) '------------------------------------------------ 'Important: Call the base implementation. This 'will allow any event handlers hooked up to this 'control to be called '------------------------------------------------ MyBase.OnClick(e) '-------------------------------------------------------- 'Select our new brush color based on the last brush color '-------------------------------------------------------- If (m_RectangleColor.Equals(System.Drawing.Color.Black)) Then m_RectangleColor = System.Drawing.Color.Blue '!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 'Trigger an event! '!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 'Call the event, pass no event arguments RaiseEvent EventButtonTurningBlue(Me, Nothing) ElseIf (m_RectangleColor.Equals(System.Drawing.Color.Blue)) _ Then m_RectangleColor = System.Drawing.Color.Red Else m_RectangleColor = System.Drawing.Color.Black End If '------------------------------------------------ 'Release the old brush '------------------------------------------------ m_RectangleBrush.Dispose() '--------------------------------------------------------- 'Create the new brush we want to draw the background with '--------------------------------------------------------- m_RectangleBrush = _ New System.Drawing.SolidBrush(m_RectangleColor) '--------------------------------------------------------- 'Tell the operating system that our control needs to be 'redrawn as soon as reasonable '--------------------------------------------------------- Me.Invalidate() End Sub '--------------------------------------------------------- 'Just for fun let's count how many times we get painted '--------------------------------------------------------- Private m_paintCount As Integer Protected Overrides Sub OnPaint( _ ByVal e As System.Windows.Forms.PaintEventArgs) '--------------------------------------------------------- 'Important: Call the base class and allow it to do its 'paint work '--------------------------------------------------------- MyBase.OnPaint(e) 'Up the count of the number of times we have been called m_paintCount = m_paintCount + 1 '------------------------------------------------------------ 'Important: 'Instead of creating a graphics object, we are being lent one 'for the duration of this call. This means that it is not 'our job to .Dispose() of the object '------------------------------------------------------------ Dim myGfx As System.Drawing.Graphics myGfx = e.Graphics 'Draw the rectangle myGfx.FillRectangle(m_RectangleBrush, 0, 0, _ Me.Width, Me.Height) 'Draw the text myGfx.DrawString("Button! Paint: " + m_paintCount.ToString(), _ Me.Parent.Font, m_TextBrush, 0, 0) End Sub End Class
Listing 11.10. Code to Place inside a Form to Create an Instance of the Custom Control
'Our new button Private m_newControl As myButton '--------------------------------------------------- 'This code will get hooked up as our event handler '--------------------------------------------------- Private Sub CallWhenButtonTurningBlue(ByVal sender As Object, _ ByVal e As System.EventArgs) MsgBox("Button is about to turn blue!") End Sub '--------------------------------------------------- 'This function is to be hooked up to the click event 'of Button1 '--------------------------------------------------- Private Sub Button1_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Button1.Click '-------------------------------------------------- 'To keep things simple, allow only one instance of 'the control. '-------------------------------------------------- If Not (m_newControl Is Nothing) Then Return 'Create an instance of our button m_newControl = New myButton 'Tell it where it should be located inside its parent m_newControl.Bounds = New Rectangle(10, 10, 150, 40) '-------------------------------------- 'Connect up an event handler '-------------------------------------- AddHandler m_newControl.EventButtonTurningBlue, _ AddressOf CallWhenButtonTurningBlue 'Add it to the list of controls in this Form. 'This will make it visible Me.Controls.Add(m_newControl) End Sub
Listing 11.11. Three Useful Ways to Cache Graphical Resources
Imports System Imports System.Drawing Friend Class GraphicsGlobals '========================================================= 'Approach 1: Create the resource on demand ' and keep cached afterward. ' 'External code gets access view the public properties, but 'the variable itself is internal to the class '========================================================= Private Shared s_bluePen As Pen Public Shared ReadOnly Property globalBluePen() As Pen Get 'If we have not created a If (s_bluePen Is Nothing) Then s_bluePen = New System.Drawing.Pen( _ System.Drawing.Color.Blue) End If Return s_bluePen End Get End Property '======================================================== 'Approach 2: 'Globally load and cache a bunch of commonly 'used Pens, ImageAttributes, Fonts, and brushes ' 'External code gets access to the public members, 'no accessors functions needed. '======================================================== Public Shared g_blackPen As Pen Public Shared g_whitePen As Pen Public Shared g_ImageAttribute As Imaging.ImageAttributes Private Shared s_alreadyInitialized As Boolean Public Shared g_boldFont As Font Public Shared g_smallTextFont As Font Public Shared g_greenBrush As Brush Public Shared g_yellowBrush As Brush Public Shared g_redBrush As Brush Public Shared g_blackBrush As Brush '=========================================================== 'Needs to be called before anyone accesses the globals above '=========================================================== Public Shared Sub InitializeGlobals() If (s_alreadyInitialized = True) Then Return g_blackPen = New System.Drawing.Pen(Color.Black) g_whitePen = New System.Drawing.Pen(Color.White) g_ImageAttribute = New _ System.Drawing.Imaging.ImageAttributes g_ImageAttribute.SetColorKey(Color.White, Color.White) g_boldFont = New Font(FontFamily.GenericSerif, _ 10, FontStyle.Bold) g_smallTextFont = New Font(FontFamily.GenericSansSerif, _ 8, FontStyle.Regular) g_blackBrush = New SolidBrush(System.Drawing.Color.Black) g_greenBrush = New SolidBrush( _ System.Drawing.Color.LightGreen) g_yellowBrush = New SolidBrush(System.Drawing.Color.Yellow) g_redBrush = New SolidBrush(System.Drawing.Color.Red) s_alreadyInitialized = True End Sub '======================================================== 'Approach 3: Return an array of related resources. ' Cache the resources locally so that multiple ' requests do not load duplicate (wasteful) ' versions ' '======================================================== Private Shared m_CaveMan_Bitmap1 As Bitmap Private Shared m_CaveMan_Bitmap2 As Bitmap Private Shared m_CaveMan_Bitmap3 As Bitmap Private Shared m_CaveMan_Bitmap4 As Bitmap Private Shared m_colCaveManBitmaps As _ System.Collections.ArrayList '---------------------------------------------------------- 'Create and load an array of images for a sprite '---------------------------------------------------------- Public Shared Function g_CaveManPictureCollection() As _ System.Collections.ArrayList 'Only load the bitmaps if we do not have them loaded yet If (m_CaveMan_Bitmap1 Is Nothing) Then '------------------------------------------------------- 'Load the bitmaps. These bitmaps are stored as embedded 'resources in our binary application ' 'Loading the images from external files would be similar 'but slightly simpler (we could just specify the file 'name in the bitmaps constructor). '------------------------------------------------------- 'Get a reference to our binary assembly dim thisAssembly as System.Reflection.Assembly = _ System.Reflection.Assembly.GetExecutingAssembly() 'Get the name of the assembly Dim thisAssemblyName As System.Reflection.AssemblyName = _ thisAssembly.GetName() Dim assemblyName As String = thisAssemblyName.Name 'Load the bitmaps as binary streams from our assembly m_CaveMan_Bitmap1 = New System.Drawing.Bitmap( _ thisAssembly.GetManifestResourceStream( _ assemblyName + ".Hank_RightRun1.bmp")) m_CaveMan_Bitmap2 = New System.Drawing.Bitmap( _ thisAssembly.GetManifestResourceStream( _ assemblyName + ".Hank_RightRun2.bmp")) m_CaveMan_Bitmap3 = New System.Drawing.Bitmap( _ thisAssembly.GetManifestResourceStream( _ assemblyName + ".Hank_LeftRun1.bmp")) m_CaveMan_Bitmap4 = New System.Drawing.Bitmap( _ thisAssembly.GetManifestResourceStream( _ assemblyName + ".Hank_LeftRun2.bmp")) 'Add them to the collection m_colCaveManBitmaps = New System.Collections.ArrayList m_colCaveManBitmaps.Add(m_CaveMan_Bitmap1) m_colCaveManBitmaps.Add(m_CaveMan_Bitmap2) m_colCaveManBitmaps.Add(m_CaveMan_Bitmap3) m_colCaveManBitmaps.Add(m_CaveMan_Bitmap4) End If 'Return the collection Return m_colCaveManBitmaps End Function End Class
|