<?xml version="1.0" encoding="UTF-8"?>
<!-- generator="wordpress/2.3.1" -->
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	>

<channel>
	<title>VBA Tips</title>
	<link>http://vbatips.com</link>
	<description>Tips and Tricks for Office VBA</description>
	<pubDate>Mon, 10 Dec 2007 23:15:37 +0000</pubDate>
	<generator>http://wordpress.org/?v=2.3.1</generator>
	<language>en</language>
			<item>
		<title>How to Programatically Retrieve Data from a Database</title>
		<link>http://vbatips.com/2007/12/10/how-to-programatically-retrieve-data-from-a-database/</link>
		<comments>http://vbatips.com/2007/12/10/how-to-programatically-retrieve-data-from-a-database/#comments</comments>
		<pubDate>Mon, 10 Dec 2007 23:15:37 +0000</pubDate>
		<dc:creator>steve</dc:creator>
		
		<category><![CDATA[VBA]]></category>

		<guid isPermaLink="false">http://vbatips.com/2007/12/10/how-to-programatically-retrieve-data-from-a-database/</guid>
		<description><![CDATA[One of the most fundamental programming tasks is writing code that interacts with a database. This week I&#8217;ll demonstrate a basic method of retrieving data from an Access database and displaying it on an Excel worksheet. Being a programming column, of course, I&#8217;ll show how to do this programmatically. That said, if you haven&#8217;t already [...]]]></description>
			<content:encoded><![CDATA[<p>One of the most fundamental programming tasks is writing code that interacts with a database. This week I&#8217;ll demonstrate a basic method of retrieving data from an Access database and displaying it on an Excel worksheet. Being a programming column, of course, I&#8217;ll show how to do this programmatically. That said, if you haven&#8217;t already done so, be sure to look into Microsoft Query for bringing data into Microsoft Excel using the user interface.</p>
<p>Any time you interact with a database, you&#8217;ll need to use a special application programming interface (API) that allows you to communicate with the database. For most purposes, you can use one of the versions of ActiveX Data Objects, commonly referred to as ADO.</p>
<p>Prior to using ADO in your project however, you need to add a reference to it in the VBE. To do this, select Tools/References from the Visual Basic Editor menu and check the checkbox next to the latest version of ADO on your computer as illustrated in Figure 1.</p>
<p><center><br />
<a href='http://vbatips.com/wp-content/uploads/2007/12/n46eopfigure1.jpg' title='n46eopfigure1.jpg'><img src='http://vbatips.com/wp-content/uploads/2007/12/n46eopfigure1.thumbnail.jpg' alt='n46eopfigure1.jpg' /></a><br />
Figure 1 - Adding a reference to Microsoft ActiveX Data Objects<br />
Click to enlarge<br />
</center></p>
<p>Listing 1 shows how to connect to the Northwind sample database, execute a query, and copy the results on to a worksheet. Enter the following listing in the code module associated with Sheet1 (you could also insert a new module and enter it there).</p>
<p>Listing 1: This listing demonstrates basic data retrieval from an Access database.</p>
<p><code> 'NOTE: Set a reference in the VBE to the latest version of<br />
' the Microsoft ActiveX Data Objects Library on your computer.</p>
<p>Sub DatabaseExample()<br />
    Dim rst As ADODB.Recordset<br />
    Dim sConnection As String<br />
    Dim sSQL As String<br />
    Dim rg As Range</p>
<p>    On Error GoTo ErrHandler</p>
<p>    ' This is the range that will receive the data.</p>
<p>    Set rg = ThisWorkbook.Worksheets(1).Range("a1")</p>
<p>    ' The database connection string. Double-check the path<br />
    ' to the Northwind database on your computer.</p>
<p>    sConnection = "Provider=Microsoft.Jet.OLEDB.4.0;" &#038; _<br />
        "Data Source=C:\Program Files\Microsoft Office\" &#038; _<br />
        "OFFICE11\SAMPLES\northwind.mdb"</p>
<p>    ' The query to execute</p>
<p>    sSQL = "SELECT LastName, FirstName, Title FROM employees"</p>
<p>    ' Create &#038; Open the recordset</p>
<p>    Set rst = New ADODB.Recordset<br />
    rst.Open sSQL, sConnection</p>
<p>    ' Copy to the range</p>
<p>    rg.CopyFromRecordset rst<br />
    rg.CurrentRegion.Columns.AutoFit</p>
<p>    ' Close the recordset when you're done with it.</p>
<p>    rst.Close</p>
<p>ExitPoint:<br />
    Set rst = Nothing<br />
    Set rg = Nothing<br />
    Exit Sub</p>
<p>ErrHandler:<br />
    MsgBox "Sorry, an error occured. " &#038; Err.Description, vbOKOnly</p>
<p>    ' resume at the ExitPoint label to clean up object variables<br />
    Resume ExitPoint<br />
End Sub</code></p>
<p>Running listing 1 produces the results shown in Figure 2. </p>
<p><Center><br />
<a href='http://vbatips.com/wp-content/uploads/2007/12/n46eopfigure2.jpg' title='n46eopfigure2.jpg'><img src='http://vbatips.com/wp-content/uploads/2007/12/n46eopfigure2.thumbnail.jpg' alt='n46eopfigure2.jpg' /></a><br />
Figure 2: Success! - Click to enlarge<br />
</center></p>
<p>Querying a database programmatically using ADO generally involves four tasks. First, you need to define a connection string. A connection string is simply the details required by ADO to locate and connect to the desired database. While I&#8217;ve just defined a connection string in this example, it is also common to use the ADO Connection object to handle the details associated with connecting to a database.</p>
<p>Second, you need to build the SQL statement that will return the data that you&#8217;re interested in. ADO has a Command object that you can use to do this &#8212; or you could just define a SQL statement and store it in a string as I have in the example.</p>
<p>Third, you need to use the ADO Recordset object to execute the query (or command) against the connection and store the results.</p>
<p>Finally, once you have the results in a Recordset, you need to display them somehow. Using the CopyFromRecordset method associated with a Range object is the easiest way to go about this. In a future column I&#8217;ll demonstrate a technique that is a little more flexible.</p>
]]></content:encoded>
			<wfw:commentRss>http://vbatips.com/2007/12/10/how-to-programatically-retrieve-data-from-a-database/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Returning Information about a Word Document</title>
		<link>http://vbatips.com/2007/12/10/returning-information-about-a-word-document/</link>
		<comments>http://vbatips.com/2007/12/10/returning-information-about-a-word-document/#comments</comments>
		<pubDate>Mon, 10 Dec 2007 23:12:48 +0000</pubDate>
		<dc:creator>steve</dc:creator>
		
		<category><![CDATA[VBA]]></category>

		<guid isPermaLink="false">http://vbatips.com/2007/12/10/returning-information-about-a-word-document/</guid>
		<description><![CDATA[While my earlier tips have used Excel examples, the name of this column is NOT Essential Excel Programming, so I suppose it is time to take a look at some useful tips related to other Office applications. One of the most useful properties in the Word object model is the Information property of the Selection [...]]]></description>
			<content:encoded><![CDATA[<p>While my earlier tips have used Excel examples, the name of this column is NOT Essential Excel Programming, so I suppose it is time to take a look at some useful tips related to other Office applications. One of the most useful properties in the Word object model is the Information property of the Selection object. </p>
<p>The Selection object represents the current selection in a window or pane. It is useful when you are writing macros that interact with the user.</p>
<p>When programming in Word, you frequently need to know things such as which page the user is working on, the number of pages in the document, the line number of the insertion point, the column number of the insertion point, and on and on.</p>
<p>Surprisingly, you only need to know one property to determine the answer to these questions and many more: </p>
<p>   Selection.Information(wdInformation) </p>
<p>where wdInformation represents the information you want returned.</p>
<p>The Information property is unique, unlike other properties I have discussed in this column. The Information property is a parameterized property. Generally properties do not use parameters. The Information property, however, accepts a wdInformation enumeration consisting of 35 options, shown in Figure 1.</p>
<p><center><br />
<a href='http://vbatips.com/wp-content/uploads/2007/12/n44eopfigure1.jpg' title='n44eopfigure1.jpg'><img src='http://vbatips.com/wp-content/uploads/2007/12/n44eopfigure1.thumbnail.jpg' alt='n44eopfigure1.jpg' /></a><br />
Figure 1: Browsing Members of the wdInformation enumeration<br />
</center></p>
<p>As shown in Listing 1, using the Information property is very easy.</p>
<p>Listing 1: A simple demonstration of the Selection.Information property</p>
<p><code>Sub SelectionInformation()<br />
  Debug.Print "Selection starts at column: " &#038; _<br />
    Selection.Information(wdFirstCharacterColumnNumber)</p>
<p>  Debug.Print "Selection starts at line: " &#038; _<br />
    Selection.Information(wdFirstCharacterLineNumber)</p>
<p>  Debug.Print "The selection starts on page " &#038; _<br />
    Selection.Information(wdActiveEndPageNumber) _<br />
    &#038; ". There are " &#038; _<br />
    Selection.Information(wdNumberOfPagesInDocument) &#038; _<br />
    " page(s) in the document."<br />
End Sub</code></p>
<p>To test this listing, enter the text =Rand(12,12) and press Enter to easily add a bunch to test data into a document. Then, experiment with the listing by placing the cursor at different locations in the document and running the macro. A sample of the output is shown below.</p>
<p>Listing 2: Example output from the SelectionInformation procedure</p>
<p><code>Selection starts at column: 11<br />
Selection starts at line: 8<br />
The selection starts on page 2. There are 5 page(s) in the document.</code></p>
<p>For details regarding the types of operations you can perform with Selection.Information and the correct enumeration value for each operation, visit </p>
<p><a href="http://msdn2.microsoft.com/en-us/library/bb213848.aspx">http://msdn2.microsoft.com/en-us/library/bb213848.aspx</a></p>
]]></content:encoded>
			<wfw:commentRss>http://vbatips.com/2007/12/10/returning-information-about-a-word-document/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Scaling Numbers in Excel</title>
		<link>http://vbatips.com/2007/12/10/scaling-numbers-in-excel/</link>
		<comments>http://vbatips.com/2007/12/10/scaling-numbers-in-excel/#comments</comments>
		<pubDate>Mon, 10 Dec 2007 23:09:24 +0000</pubDate>
		<dc:creator>steve</dc:creator>
		
		<category><![CDATA[VBA]]></category>

		<guid isPermaLink="false">http://vbatips.com/2007/12/10/scaling-numbers-in-excel/</guid>
		<description><![CDATA[One time-consuming, error-prone technique commonly repeated is scaling numbers in Excel using formulas rather than number formats. For example, to scale a number by thousands, people use formulas such as =A1*.001 or =A1/1000. The problem with this practice is that it is time-consuming to setup or modify and it tends to be error-prone if the [...]]]></description>
			<content:encoded><![CDATA[<p>One time-consuming, error-prone technique commonly repeated is scaling numbers in Excel using formulas rather than number formats. For example, to scale a number by thousands, people use formulas such as =A1*.001 or =A1/1000. The problem with this practice is that it is time-consuming to setup or modify and it tends to be error-prone if the cell is involved in a chain of calculations.</p>
<p>A much simpler approach is to use custom number formats. For example, it is possible to display a number scaled by thousands using this number format: </p>
<p>   #,##0,</p>
<p>Using custom number formats does not change the underlying value, and the formats are trivial to modify.</p>
<p>To take this a step further, it is relatively simple to add a scaling feature using a combination of named ranges, a simple drop-down list using the data validation feature, and a pinch of VBA. As an example, consider the worksheet shown in Figure 1. (NOTE: Although these images show Excel 2007, the same results can be achieved using Excel 2003.)</p>
<p><center><a href='http://vbatips.com/wp-content/uploads/2007/12/n43eopfigure1.jpg' title='n43eopfigure1.jpg'><img src='http://vbatips.com/wp-content/uploads/2007/12/n43eopfigure1.thumbnail.jpg' alt='n43eopfigure1.jpg' /></a><br />
Figure 1: Step 1 - Name the range of numbers that should be scaled.<br />
</center></p>
<p>The first step is to create a named range consisting of all the cells that should be scaled. In Figure 1, I’ve named the range Scale_Range.</p>
<p><Center><br />
<a href='http://vbatips.com/wp-content/uploads/2007/12/n43eopfigure2.jpg' title='n43eopfigure2.jpg'><img src='http://vbatips.com/wp-content/uploads/2007/12/n43eopfigure2.thumbnail.jpg' alt='n43eopfigure2.jpg' /></a><br />
Figure 2: Step 2 - Create a named range for the cell that specifies the scaling factor.<br />
</center></p>
<p>The next step is to create a named range for the cell that contains the scaling factor options. In Figure 2, I’ve named this Scale_Factor. Additionally, I’ve added data validation to this cell to provide the list of options and the drop-down functionality.</p>
<p>The final step is to add the code shown in Listing 1 to the Sheet1 object in the Visual Basic Editor.</p>
<p>Listing 1: A method for providing scaling capabilities</p>
<p><code>Private Sub Worksheet_Change(ByVal Target As Range)<br />
    If Target = Me.Range("Scale_Factor") Then<br />
        ScaleNumbers<br />
    End If<br />
End Sub</p>
<p>Private Sub ScaleNumbers()<br />
    Select Case Me.Range("Scale_Factor").Value<br />
        Case Is = "(in $)"<br />
            Me.Range("Scale_Range").NumberFormat = "#,##0"<br />
        Case Is = "(in thousands of $)"<br />
            Me.Range("Scale_Range").NumberFormat = "#,##0,"<br />
        Case Is = "(in millions of $)"<br />
            Me.Range("Scale_Range").NumberFormat = "#,##0,,"<br />
        Case Else<br />
            Me.Range("Scale_Range").NumberFormat = "#,##0"<br />
    End Select<br />
End Sub</code></p>
<p>The Worksheet_Change procedure is an event handler that Excel calls automatically anytime something changes on Sheet1. The Target parameter represents the range object (i.e., cell) that changed. If the cell that changed is the cell containing the scaling choices, the ScaleNumbers procedure is called.</p>
<p>The ScaleNumbers procedure examines the value of the Scale_Factor named range and &#8212; depending on the desired scaling &#8212; uses the NumberFormat property to set a custom number format.</p>
<p>As you can see in Figure 3, the number appears scaled on the worksheet but the underlying value of the cell is unchanged. </p>
<p><center><br />
<a href='http://vbatips.com/wp-content/uploads/2007/12/n43eopfigure3.jpg' title='n43eopfigure3.jpg'><img src='http://vbatips.com/wp-content/uploads/2007/12/n43eopfigure3.thumbnail.jpg' alt='n43eopfigure3.jpg' /></a><br />
Figure 3<br />
</center></p>
]]></content:encoded>
			<wfw:commentRss>http://vbatips.com/2007/12/10/scaling-numbers-in-excel/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Error Handling for VBA Macros, Part II</title>
		<link>http://vbatips.com/2007/12/10/error-handling-for-vba-macros-part-ii/</link>
		<comments>http://vbatips.com/2007/12/10/error-handling-for-vba-macros-part-ii/#comments</comments>
		<pubDate>Mon, 10 Dec 2007 23:06:10 +0000</pubDate>
		<dc:creator>steve</dc:creator>
		
		<category><![CDATA[VBA]]></category>

		<guid isPermaLink="false">http://vbatips.com/2007/12/10/error-handling-for-vba-macros-part-ii/</guid>
		<description><![CDATA[Last week I discussed simple error handling techniques such as no error handling (the default experience), ignoring errors, and quit (gracefully) the first time an error occurs. This week I&#8217;m going to explain a more flexible technique that allows different actions to be taken depending on the type of error that occurs. 
The key to [...]]]></description>
			<content:encoded><![CDATA[<p>Last week I discussed simple error handling techniques such as no error handling (the default experience), ignoring errors, and quit (gracefully) the first time an error occurs. This week I&#8217;m going to explain a more flexible technique that allows different actions to be taken depending on the type of error that occurs. </p>
<p>The key to this technique is using the Err object to gather information about the type of error that occurred and then take an appropriate action. Nearly every type of error that can occur at runtime is assigned a number and description and this information is surfaced through the Err object. The Err object is an object that is automatically created by VBA whenever an error occurs. </p>
<p>As an example, consider Listing 1 below. </p>
<p>Listing 1: Flexible Error Handling </p>
<p><code>Sub FixErrorStrategy()<br />
    Dim x As Integer<br />
    Dim z As Integer</p>
<p>    ' If an error occurs, resume execution<br />
    ' with the statement following the<br />
    ' ErrHandler label.<br />
    On Error GoTo ErrHandler</p>
<p>    z = 0</p>
<p>    For x = 1 To 10<br />
        Debug.Print x / z<br />
    Next</p>
<p>    ' This line will only be reached<br />
    ' if no errors occur or if<br />
    ' recoverable errors occur.<br />
    MsgBox "Finished"<br />
    Exit Sub</p>
<p>ErrHandler:<br />
    If Err.Number = 11 Then<br />
        MsgBox "Division by zero error. " &#038; _<br />
            "Changing denominator to 1."<br />
        z = 1<br />
        ' Resume execution with the statement that<br />
        ' caused the error.<br />
        Resume<br />
    Else<br />
        MsgBox "Unrecoverable Error: " &#038; _<br />
            Err.Number &#038; " - " &#038; Err.Description<br />
    End If<br />
End Sub</code></p>
<p>Listing 1 loops through the numbers 1 through 10 and divides each number by the number stored in variable z (which is mistakenly set to 0 initially). When an error occurs, execution proceeds to the block of code after the ErrHandler label. This code examines the Err.Number and takes action if the error is error number 11 -– the error number associated with division by zero. For all other errors, the procedure will display information about the error and then exit the procedure. </p>
<p>The ErrHandler code block uses the Resume statement after fixing the condition that caused the error to resume execution at the statement that initially caused the error. When the division by zero error occurs, the procedure sets the value of z to 1, then the Resume statement causes execution to resume at the Debug.Print x/z statement. From that point on, the procedure runs as expected and produces the following output in the Immediate window.</p>
<p><code> 1<br />
 2<br />
 3<br />
 4<br />
 5<br />
 6<br />
 7<br />
 8<br />
 9<br />
 10</code>						  </p>
<p>This error handling pattern is very typical of VBA procedures that require anything more than the most elementary error handling. Stay tuned next week for a great tip on scaling Excel worksheet numbers programmatically. </p>
]]></content:encoded>
			<wfw:commentRss>http://vbatips.com/2007/12/10/error-handling-for-vba-macros-part-ii/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Error Handling for VBA Macos</title>
		<link>http://vbatips.com/2007/12/10/error-handling-for-vba-macos/</link>
		<comments>http://vbatips.com/2007/12/10/error-handling-for-vba-macos/#comments</comments>
		<pubDate>Mon, 10 Dec 2007 23:01:18 +0000</pubDate>
		<dc:creator>steve</dc:creator>
		
		<category><![CDATA[VBA]]></category>

		<guid isPermaLink="false">http://vbatips.com/2007/12/10/error-handling-for-vba-macos/</guid>
		<description><![CDATA[The focus of this week&#8217;s column is error handling. Learning basic error handling is essential knowledge for writing more robust VBA code. I&#8217;m going to demonstrate two simple strategies. These strategies assume that you are using the default Break on Unhandled Errors error trapping option. Check the setting for this option by selecting Tools/Options in [...]]]></description>
			<content:encoded><![CDATA[<p>The focus of this week&#8217;s column is error handling. Learning basic error handling is essential knowledge for writing more robust VBA code. I&#8217;m going to demonstrate two simple strategies. These strategies assume that you are using the default Break on Unhandled Errors error trapping option. Check the setting for this option by selecting Tools/Options in the Visual Basic Editor. This option is located on the General tab. </p>
<p><center><br />
<a href='http://vbatips.com/wp-content/uploads/2007/12/n41eopfigure1.jpg' title='n41eopfigure1.jpg'><img src='http://vbatips.com/wp-content/uploads/2007/12/n41eopfigure1.thumbnail.jpg' alt='n41eopfigure1.jpg' /></a><br />
Figure 1: The default runtime error experience<br />
</center></p>
<p>Before moving into basic error handling strategies, I&#8217;ll demonstrate the default strategy &#8212; no error handling. If there is no error handling present, VBA will display a runtime error when an error occurs and present the option to either End the macro or Debug it. Listing 1 shows an example listing that will generate a division by zero error. </p>
<p>Listing 1: A procedure that does not use error handling. </p>
<p><code>Sub NoErrorHandlingStratgy()<br />
    Dim x As Integer</p>
<p>    For x = 1 To 10<br />
        Debug.Print x / 0<br />
    Next</p>
<p>    ' This line will only be reached if no errors occur.<br />
    MsgBox "Finished - without errors"<br />
End Sub</code></p>
<p>Running Listing 1 produces the dialog box shown in Figure 1. </p>
<p>The error-handling strategy is simply to ignore any errors and continue executing code. As an example, consider Listing 2. This listing attempts to do the same thing as Listing 1 except it includes the statement On Error Resume Next near the beginning of the procedure. </p>
<p>Listing 2: A procedure that ignores all errors </p>
<p><code>Sub IgnoreEverythingStratgy()<br />
    Dim x As Integer</p>
<p>    ' If an error occurs, ignore it<br />
    ' and continue executing with the<br />
    ' next statement.<br />
    On Error Resume Next</p>
<p>    For x = 1 To 10<br />
        Debug.Print x / 0<br />
    Next</p>
<p>    ' This line will be reached no matter what.<br />
    MsgBox "Finished - with or without errors"<br />
End Sub</code></p>
<p>This strategy is helpful when you have procedures in which the individual statements don&#8217;t rely on previous statements. In other words, if one line doesn&#8217;t execute due to an error, there are no adverse affects to the remaining statements. </p>
<p>If there a procedure is performing more critical operations and there is a dependency between the various statements in a procedure, it might be better to gracefully quit or exit the procedure the first time that an error occurs. At first, this might sound exactly like what you get by not using any error handling. However, there are critical differences. </p>
<p>First, you get to control the user experience rather than the VBA execution engine. For example, you might not want to give the user the ability to debug your code. Or you might choose to not even notify the user that an error occurred. Finally, if you have other procedures that need to run after the procedure that caused the error, these other procedures can still run. Without error handling, all execution stops and any following procedures will not be run. </p>
<p>To use this strategy, use the On Error GoTo ErrHandler statement. ErrHandler is actually a line label - you can use a different label if you prefer. Listing 3 shows an example of this strategy. </p>
<p>Listing 3: A procedure that uses an error handler </p>
<p><code>Sub QuitOnFirstErrorStrategy()<br />
    Dim x As Integer</p>
<p>    ' If an error occurs, resume execution<br />
    ' with the statement following the<br />
    ' ErrHandler label.<br />
    On Error GoTo ErrHandler</p>
<p>    For x = 1 To 10<br />
        Debug.Print x / 0<br />
    Next</p>
<p>    ' This line will only be reached<br />
    ' if no errors occur.<br />
    MsgBox "Finished without errors"</p>
<p>    ' Use Exit Sub here to prevent<br />
    ' running into the error handling code.<br />
    Exit Sub</p>
<p>' The following code only runs if an<br />
' error occurs.<br />
ErrHandler:<br />
    MsgBox "Did not finish - error: " &#038; _<br />
        Err.Description<br />
End Sub</code></p>
<p>These two basic strategies can significantly improve your ability to write more user friendly and robust code. Next week I&#8217;ll demonstrate additional error handling strategies that provide even more flexibility to selectively handle some errors but not others. </p>
]]></content:encoded>
			<wfw:commentRss>http://vbatips.com/2007/12/10/error-handling-for-vba-macos/feed/</wfw:commentRss>
		</item>
		<item>
		<title>A Real World String Manipulation Example Part II</title>
		<link>http://vbatips.com/2007/12/10/a-real-world-string-manipulation-example-part-ii/</link>
		<comments>http://vbatips.com/2007/12/10/a-real-world-string-manipulation-example-part-ii/#comments</comments>
		<pubDate>Mon, 10 Dec 2007 22:59:08 +0000</pubDate>
		<dc:creator>steve</dc:creator>
		
		<category><![CDATA[VBA]]></category>

		<guid isPermaLink="false">http://vbatips.com/2007/12/10/a-real-world-string-manipulation-example-part-ii/</guid>
		<description><![CDATA[Last week I wrote about a string manipulation issue that a reader was having. This was a great way to illustrate some of the string manipulation techniques that I covered in previous columns. As I noted then, the string manipulation is only one part of the problem. Providing a way to search for all cells [...]]]></description>
			<content:encoded><![CDATA[<p>Last week I wrote about a string manipulation issue that a reader was having. This was a great way to illustrate some of the string manipulation techniques that I covered in previous columns. As I noted then, the string manipulation is only one part of the problem. Providing a way to search for all cells that need to be fixed is the second part of the issue. </p>
<p>While Excel provides Find and FindNext methods to search cells, using them correctly isn’t as easy as one might think. The reason that using Find is more difficult than it should be is that as long as it finds at least one match, calls to FindNext will never terminate because it will repeatedly find the same item. This happens because once Excel gets to the last cell in the range it wraps around and resumes searching with the first cell in the range. </p>
<p>The key to using Find and FindNext is saving the address of the first cell that is found and then comparing the address of subsequent finds with the saved address. When a subsequent address equals the saved address, the entire range has been searched. </p>
<p>Listing 1 provides and example illustrating this method. It looks a lot longer than it really is because of all the comments I’ve added. </p>
<p>Listing 1: Using Find and FindNext to search cells on a worksheet </p>
<p><code>Sub FindExample()<br />
    Dim rgFound As Range<br />
    Dim sFirstFoundAddress As String</p>
<p>    ' Be careful using ActiveSheet - you have to make sure<br />
    ' it is a worksheet if you'll be doing worksheet-specific<br />
    ' things to it.<br />
    If ActiveSheet.Type <> XlSheetType.xlWorksheet Then<br />
        MsgBox &#8220;The active sheet is not a worksheet.&#8221;, vbOKOnly<br />
        Exit Sub<br />
    End If</p>
<p>    &#8216; In the initial call to Find, it is a good idea to specify<br />
    &#8216; all the values. The values are &#8220;sticky&#8221; - they&#8217;ll last in<br />
    &#8216; between calls or uses from the user interface which is why<br />
    &#8216; you should explicitly specify the values you want rather<br />
    &#8216; then relying on &#8220;default&#8221; values.<br />
    Set rgFound = ActiveSheet.Cells.Find(What:=&#8221;NAR&#8221;, _<br />
        After:=ActiveCell, LookIn:=xlValues, _<br />
        LookAt:=xlPart, SearchDirection:=xlNext, _<br />
        MatchCase:=False)</p>
<p>    &#8216; If nothing is found, Find sets the range (rgFound in this<br />
    &#8216; case) to Nothing.<br />
    If Not rgFound Is Nothing Then</p>
<p>        &#8216; To prevent an endless loop, save the address of the<br />
        &#8216; first found item.<br />
        sFirstFoundAddress = rgFound.Address<br />
        MsgBox &#8220;Found match as cell &#8221; &#038; rgFound.Address</p>
<p>        &#8216; Continue finding subsequent items by calling out<br />
        &#8216; to FindNext.<br />
        Set rgFound = ActiveSheet.Cells.FindNext(rgFound)</p>
<p>        &#8216; Start a loop for finding all remaining items.<br />
        Do Until rgFound Is Nothing<br />
            &#8216; Once find gets to the end of a worksheet, it<br />
            &#8216; loops back to the first cell. To prevent endless<br />
            &#8216; loops, compare any found cells to the first found<br />
            &#8216; cell to see if you&#8217;ve already found it.<br />
            If rgFound.Address = sFirstFoundAddress Then<br />
                Exit Do<br />
            Else<br />
                MsgBox &#8220;Found match as cell &#8221; &#038; rgFound.Address<br />
            End If<br />
            Set rgFound = ActiveSheet.Cells.FindNext(rgFound)<br />
        Loop<br />
    End If</p>
<p>    MsgBox &#8220;The active sheet has been searched.&#8221;, _<br />
            vbOKOnly, &#8220;Search Complete&#8221;<br />
End Sub</code></p>
<p>One thought that I’d like to share relates to the use of ActiveSheet. Generally, ActiveSheet will resolve to a worksheet object. However, when using ActiveSheet, be sure to make sure that the active sheet is actually a worksheet. It could be a chart sheet for example which may result in runtime errors if you have assumed that ActiveSheet is a worksheet. It is easy to validate that ActiveSheet is a worksheet by examining the Type property as I’ve done near the beginning of Listing 1. </p>
<p>To test this procedure, simply populate several cells in a worksheet with the value “NAR”. With the worksheet active, run the FindExample macro. </p>
]]></content:encoded>
			<wfw:commentRss>http://vbatips.com/2007/12/10/a-real-world-string-manipulation-example-part-ii/feed/</wfw:commentRss>
		</item>
		<item>
		<title>A Real World String Manipulation Example</title>
		<link>http://vbatips.com/2007/12/10/a-real-world-string-manipulation-example/</link>
		<comments>http://vbatips.com/2007/12/10/a-real-world-string-manipulation-example/#comments</comments>
		<pubDate>Mon, 10 Dec 2007 22:57:22 +0000</pubDate>
		<dc:creator>steve</dc:creator>
		
		<category><![CDATA[VBA]]></category>

		<guid isPermaLink="false">http://vbatips.com/2007/12/10/a-real-world-string-manipulation-example/</guid>
		<description><![CDATA[A reader writes: 
One of our staff asked me if there was a way in Excel to take this information: 
   NAR123456 
and replace it with this 
   123-456 
In other words, the person wants to delete the &#8220;NAR&#8221; and place a hyphen after the first three numbers. Evidently, she has [...]]]></description>
			<content:encoded><![CDATA[<p>A reader writes: </p>
<p>One of our staff asked me if there was a way in Excel to take this information: </p>
<p>   NAR123456 </p>
<p>and replace it with this </p>
<p>   123-456 </p>
<p>In other words, the person wants to delete the &#8220;NAR&#8221; and place a hyphen after the first three numbers. Evidently, she has to do this frequently on large spreadsheets. This has got to be an easy thing to do, but I don&#8217;t have the skill set needed to come up with the solution. Can you help? Thanks. </p>
<p>Using the string manipulation functions that I covered in the previous two columns, it is possible to make quick work of this problem. However, depending on the layout of the spreadsheet in question, it may be even easier to handle to problem using formulas. An example of this is shown in Figure 1. </p>
<p><center><br />
<a href='http://vbatips.com/wp-content/uploads/2007/12/n39eopfigure1.jpg' title='n39eopfigure1.jpg'><img src='http://vbatips.com/wp-content/uploads/2007/12/n39eopfigure1.thumbnail.jpg' alt='n39eopfigure1.jpg' /></a><br />
<br />Figure 1: Using formulas to manipulate text<br />
</center></p>
<p>The difference between the &#8220;Variable&#8221; and the &#8220;Fixed&#8221; columns is that the formula in the variable column assumes that the number of digits after the &#8220;NAR&#8221; part is variable. The formula for the fixed column assumes that there are exactly 6 digits after the &#8220;NAR&#8221; part. </p>
<p>If the text can appear anywhere on the worksheet, using formulas isn&#8217;t an option because you don&#8217;t know ahead of time where the text will show up and it isn&#8217;t practical to re-create the formula everywhere it is needed. </p>
<p>The solution in VBA is twofold. First, build a procedure that will convert the text as desired. Second, provide a way to search all of the cells on the worksheet. I&#8217;ll only be covering the first issue this week. </p>
<p>Listing 1 shows a simple implementation of one of several approaches to this problem. This implementation is rather simple because it relies on the end user to perform the &#8220;searching&#8221; for cells that need to be fixed. Then, by assigning a shortcut key to the FixCell macro such as Ctrl + F, one can select individual cells that need to be fixed and press Ctrl + F to fix the cell. FixCell determines if the value in the cell is actually a value that needs to be fixed and, if so, calls the ConvertString function to perform the conversion. Finally, FixCell updates the active cell with the value returned from ConvertString. </p>
<p>Listing 1: A VBA function to convert the string.</p>
<p><code>' Very simplistic procedure to<br />
' try and fix a cell<br />
' Assigned to shortcut key: ctrl+f</p>
<p>Sub FixCell()<br />
    Dim sValue As String</p>
<p>    sValue = ActiveCell.value<br />
    If Left(sValue, 3) = "NAR" Then<br />
        ActiveCell.value = ConvertString(sValue)<br />
    End If<br />
End Sub</p>
<p>' Assumes s is a string in the format<br />
' NAR123456<br />
' Converts to 123-456<br />
Function ConvertString(s As String)<br />
    Dim sResult As String</p>
<p>    ' Strip off the "NAR" part<br />
    sResult = Right(s, Len(s) - 3)</p>
<p>    ' Add a hyphen after the third character<br />
    sResult = Left(sResult, 3) &#038; "-" &#038; _<br />
              Right(sResult, Len(sResult) - 3)</p>
<p>    ' Return the result<br />
    ConvertString = sResult<br />
End Function</code></p>
<p>Providing a way to search a worksheet for all possible cells that need to be fixed requires an entire column which I will save for another week.</p>
]]></content:encoded>
			<wfw:commentRss>http://vbatips.com/2007/12/10/a-real-world-string-manipulation-example/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Basic Text Manipulation with VBA</title>
		<link>http://vbatips.com/2007/12/10/basic-text-manipulation-with-vba/</link>
		<comments>http://vbatips.com/2007/12/10/basic-text-manipulation-with-vba/#comments</comments>
		<pubDate>Mon, 10 Dec 2007 22:46:20 +0000</pubDate>
		<dc:creator>steve</dc:creator>
		
		<category><![CDATA[VBA]]></category>

		<guid isPermaLink="false">http://vbatips.com/2007/12/10/basic-text-manipulation-with-vba/</guid>
		<description><![CDATA[Manipulating text is a frequent programming task. Fortunately, VBA has numerous functions that make this easy work. The purpose of this week’s column is to illustrate the use of some of the text functions that I tend to use the most. 
Text in VBA is usually stored in string variables. When discussing text manipulation, then, [...]]]></description>
			<content:encoded><![CDATA[<p>Manipulating text is a frequent programming task. Fortunately, VBA has numerous functions that make this easy work. The purpose of this week’s column is to illustrate the use of some of the text functions that I tend to use the most. </p>
<p>Text in VBA is usually stored in string variables. When discussing text manipulation, then, it is common to refer to text as a string. Perhaps the most common need when it comes to manipulating strings is to concatenate two strings together. For this, you don’t even need to use a function &#8212; use the ampersand (&#038;) character. Listing 1 demonstrates string concatenation. </p>
<p>Listing 1: A demonstration of string concatenation. </p>
<p><code>Sub StringConcatenation()<br />
    Dim sFirstName As String<br />
    Dim sLastName As String<br />
    Dim sFullName As String</p>
<p>    sFirstName = "Ronald"<br />
    sLastName = "McDonald"<br />
    Debug.Print "His name is " &#038; sFirstName &#038; " " &#038; sLastName</p>
<p>    sFullName = sFirstName &#038; " " &#038; sLastName<br />
    Debug.Print "His name is " &#038; sFullName<br />
End Sub</code><br />
&#8212; The output of this listing is: &#8212;<br />
His name is Ronald McDonald<br />
His name is Ronald McDonald </p>
<p>After concatenation, the other basic needs relate to simple string modification such as stripping characters from the beginning or end of a string. The following table documents the basic string modification functions. These functions are demonstrated in Listing 2. </p>
<p>Function Name and Purpose</p>
<p><code>Left (string, n) Returns the n leftmost characters from a string<br />
Right(string, n) Returns the n rightmost characters from a string<br />
Mid (string, starting at, n) Returns n characters from a string starting with the character in the "starting at" position<br />
LTrim (string), RTrim (string), Trim (string) Removes leading (LTrim), trailing (RTrim), or leading and trailing (Trim) white space<br />
LCase (string), UCase (string) Converts a string to lower case (LCase) or upper case (UCase)</code></p>
<p>Listing 2: A demonstration of string manipulation. </p>
<p><code>Sub StringManipulation()</p>
<p>    ' Using the Left function<br />
    Dim sZipCode As String<br />
    sZipCode = "55021-0000"<br />
    Debug.Print "The short zip code is: " &#038; Left(sZipCode, 5)</p>
<p>    ' Using the Mid and Right functions<br />
    Dim sPhoneNumber As String<br />
    sPhoneNumber = "123-555-1234"<br />
    Debug.Print "The local prefix is: " &#038; Mid(sPhoneNumber, 5, 3)<br />
    Debug.Print "The last four digits are: " &#038; Right(sPhoneNumber, 4)</p>
<p>    ' Using the Trim functions<br />
    Dim sPaddedString As String<br />
    sPaddedString = "   Interesting Text   "<br />
    Debug.Print "Using LTrim:***" &#038; LTrim(sPaddedString) &#038; "***"<br />
    Debug.Print "Using RTrim:***" &#038; RTrim(sPaddedString) &#038; "***"<br />
    Debug.Print "Using  Trim:***" &#038; Trim(sPaddedString) &#038; "***"</p>
<p>    ' Using LCase and UCase<br />
    Dim sMixedCase As String<br />
    sMixedCase = "New York, NY"<br />
    Debug.Print UCase(sMixedCase)<br />
    Debug.Print LCase(sMixedCase)<br />
End Sub</code></p>
<p>&#8212; The output of this listing is: &#8212;<br />
The short zip code is: 55021<br />
The local prefix is: 555<br />
The last four digits are: 1234<br />
Using LTrim:***Interesting Text ***<br />
Using RTrim:*** Interesting Text***<br />
Using Trim:***Interesting Text***<br />
NEW YORK, NY<br />
new york, ny </p>
<p>The ability to employ basic string manipulation opens up a world of possibilities beyond what you can achieve with simple macro recording. Next week I’ll continue this discussion by explaining other text manipulation functions and techniques. </p>
]]></content:encoded>
			<wfw:commentRss>http://vbatips.com/2007/12/10/basic-text-manipulation-with-vba/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Reporting Progress</title>
		<link>http://vbatips.com/2007/12/10/reporting-progress/</link>
		<comments>http://vbatips.com/2007/12/10/reporting-progress/#comments</comments>
		<pubDate>Mon, 10 Dec 2007 22:44:29 +0000</pubDate>
		<dc:creator>steve</dc:creator>
		
		<category><![CDATA[VBA]]></category>

		<guid isPermaLink="false">http://vbatips.com/2007/12/10/reporting-progress/</guid>
		<description><![CDATA[Mark Mudron offered this response to Steve Hansen’s programming article from last week’s issue: 
Referring to the Essential Office Programming section of your most recent news letter regarding Application.ScreenUpdating&#8230;
I discovered this handy command quite awhile ago while working on large spreadsheets. The performance gain, as was pointed out, was enormous. My problem is, how does [...]]]></description>
			<content:encoded><![CDATA[<p>Mark Mudron offered this response to Steve Hansen’s programming article from last week’s issue: </p>
<p>Referring to the Essential Office Programming section of your most recent news letter regarding Application.ScreenUpdating&#8230;<br />
I discovered this handy command quite awhile ago while working on large spreadsheets. The performance gain, as was pointed out, was enormous. My problem is, how does the user know that the macro is doing something? If the macro takes too long to run, the user may get impatient and try to terminate the macro or the application. </p>
<p>In the past, I have taken a bit of a performance hit to highlight the first cell of the row the macro is working on (or every nth row if you want to write an if/then statement). This allows the user to know something is happening without having to show every update. </p>
<p>Is there a better way to do this? Is there a way to display and update a progress box while still allowing the macro to run in the background? </p>
<p>You bet there is, Mark. Thanks for the question. This week Steve provides the answer. </p>
<p>- - - </p>
<p>A couple of weeks ago, I discussed the use of Application.ScreenUpdating in Excel to improve the performance of your macros and prevent screen flicker while your macro executes. When you turn screen updating off, it is helpful to provide some sort of feedback to the user so that they know that their computer isn’t locked up. </p>
<p>The method I prefer to use is using Application.StatusBar, which allows you to change the information appearing in the status bar. While the visibility of the status bar can be changed by the user, it is shown by default and in my experience, very few people ever hide it. It is a very non-intrusive way to display progress information. </p>
<p>While some programmers occasionally worry that “my users won’t notice it”, trust me -– they will. Based on solutions I’ve deployed, I’ve never had any feedback to the contrary. When it comes to progress information, subtle is good. </p>
<p>One of the reasons subtle is good when it comes to progress information is that providing progress information has a performance cost. If the reason you are turning screen updating off is to maximize performance, you probably don’t want to implement a progress information system that consumes all of the performance gain. The key is to find a refresh rate that provides reasonable progress information without a noticeable impact on performance. </p>
<p>Listing 1 provides an example of how to use Application.StatusBar in a common scenario –- providing progress information as you process individual rows in Excel. </p>
<p>Listing 1: A demonstration of Application.StatusBar </p>
<p><code>Sub StatusBarExample()<br />
    Dim lRow As Long<br />
    Dim ws As Worksheet<br />
    Dim lFrequency As Long</p>
<p>    Set ws = ThisWorkbook.Worksheets(1)</p>
<p>    lFrequency = 1000</p>
<p>    ' Turn screen updating off<br />
    Application.ScreenUpdating = False</p>
<p>    ' Excel 2007 ready - don't assume 65,536 rows anymore<br />
    ' get a row count from the worksheet instead.<br />
    For lRow = 1 To ws.Rows.Count<br />
        ' To experiment with the performance implications of<br />
        ' providing more or less frequent progress feedback<br />
        ' change the value of the lFrequency variable<br />
        If lRow Mod lFrequency = 0 Then<br />
            Application.StatusBar = "Processing row: " &#038; lRow<br />
        End If<br />
    Next</p>
<p>    ' Turn screen updating on and reset the status bar<br />
    Application.ScreenUpdating = True<br />
    Application.StatusBar = False<br />
    Set ws = Nothing<br />
End Sub</code></p>
<p>To experiment with the performance impact of using Application.StatusBar, run the StatusBarExample a few times with different values for lFrequency. For example try 10, 100, and 1000. This example uses the Mod function to determine if the status bar should be updated. Mod divides two numbers and returns the remainder of the division. </p>
<p>Since you can write arbitrary text to the StatusBar, you can use lots of other schemes besides rows. For example, a batch processing routine may use the filename of the file it is currently processing. A routine that harvests information from numerous worksheets may display the name of the current worksheet. A process that reads data from a database may display a primary key or other identifying attribute (perhaps combined with a frequency modifier such as every nth row). </p>
<p>In summary, Application.ScreenUpdating and Application.StatusBar are very complimentary when used together to maximize the performance of a macro while providing some basic progress information so that the computer doesn’t appear locked-up. </p>
]]></content:encoded>
			<wfw:commentRss>http://vbatips.com/2007/12/10/reporting-progress/feed/</wfw:commentRss>
		</item>
		<item>
		<title>A Primer on Procedure Arguments</title>
		<link>http://vbatips.com/2007/12/10/a-primer-on-procedure-arguments/</link>
		<comments>http://vbatips.com/2007/12/10/a-primer-on-procedure-arguments/#comments</comments>
		<pubDate>Mon, 10 Dec 2007 22:42:52 +0000</pubDate>
		<dc:creator>steve</dc:creator>
		
		<category><![CDATA[VBA]]></category>

		<guid isPermaLink="false">http://vbatips.com/2007/12/10/a-primer-on-procedure-arguments/</guid>
		<description><![CDATA[One key to becoming a more effective programmer is to develop an understanding of procedure arguments and how to use them. Once you develop an understanding of arguments, you will see programming tasks in a whole new light and dramatically increase your productivity as well as the quality of your code. 
As an example, consider [...]]]></description>
			<content:encoded><![CDATA[<p>One key to becoming a more effective programmer is to develop an understanding of procedure arguments and how to use them. Once you develop an understanding of arguments, you will see programming tasks in a whole new light and dramatically increase your productivity as well as the quality of your code. </p>
<p>As an example, consider Listing 1. This is a macro I recorded in Microsoft Word to insert a picture and add an orange border to it. If you always add the same picture with the same color borders, perhaps this macro would be useful. However, it would be much more useful and efficient if you modified this macro so that it could be used in more situations. For example, maybe you want to specify which file to add and what color the borders should be. </p>
<p>Listing 1: A simple macro to insert a picture in Word </p>
<p><code>Sub Macro1()<br />
'<br />
' Macro1 Macro<br />
' Macro recorded 2/21/2007 by Steve Hansen<br />
'<br />
' Note the reference to a hard-coded file name – you should update this<br />
    Selection.InlineShapes.AddPicture _<br />
      FileName:= "C:\DSC00007.JPG", _<br />
      LinkToFile:=False, _<br />
      SaveWithDocument:=True</p>
<p>    Selection.MoveLeft Unit:=wdCharacter, Count:=1, Extend:=wdExtend    </p>
<p>    With Selection.InlineShapes(1)<br />
        With .Borders(wdBorderLeft)<br />
            .LineStyle = wdLineStyleSingle<br />
            .LineWidth = wdLineWidth050pt<br />
            .Color = wdColorOrange<br />
        End With<br />
        With .Borders(wdBorderRight)<br />
            .LineStyle = wdLineStyleSingle<br />
            .LineWidth = wdLineWidth050pt<br />
            .Color = wdColorOrange<br />
        End With<br />
        With .Borders(wdBorderTop)<br />
            .LineStyle = wdLineStyleSingle<br />
            .LineWidth = wdLineWidth050pt<br />
            .Color = wdColorOrange<br />
        End With<br />
        With .Borders(wdBorderBottom)<br />
            .LineStyle = wdLineStyleSingle<br />
            .LineWidth = wdLineWidth050pt<br />
            .Color = wdColorOrange<br />
        End With<br />
        .Borders.Shadow = False<br />
    End With<br />
    With Options<br />
        .DefaultBorderLineStyle = wdLineStyleSingle<br />
        .DefaultBorderLineWidth = wdLineWidth050pt<br />
        .DefaultBorderColor = wdColorOrange<br />
    End With<br />
End Sub</code></p>
<p>You can think of arguments as variables that you define in the declaration of a procedure (the Sub or Function statement) with an added twist &#8212; other procedures that call the procedure can specify the value of these variables. </p>
<p>Listing 2 shows a modified version of Listing 1 that uses arguments to specify the filename of the image to add as well as the color of the borders. I also included a procedure named AddTestImage that demonstrates how to call another procedure that uses arguments. This procedure displays a dialog box that prompts the user to select an image to add. </p>
<p>Listing 2: A simple macro to insert a picture in Word with arguments<br />
<code><br />
Sub AddTestImage()<br />
    Dim dlgOpen As FileDialog</p>
<p>    ' Initialize the file open dialog box<br />
    Set dlgOpen = Application.FileDialog( _<br />
       FileDialogType:=msoFileDialogOpen)<br />
    dlgOpen.Filters.Clear<br />
    dlgOpen.Filters.Add "Images", "*.gif; *.jpg; *.jpeg", 1<br />
    dlgOpen.AllowMultiSelect = False<br />
    dlgOpen.Show</p>
<p>    ' If a file was selected, try and insert it.<br />
    If dlgOpen.SelectedItems.Count > 0 Then<br />
        AddImage dlgOpen.SelectedItems(1), wdColorRed<br />
    End If<br />
End Sub</p>
<p>Sub AddImage(sFileName As String, clrColor As WdColor)<br />
    Selection.InlineShapes.AddPicture _<br />
        FileName:=sFileName, _<br />
        LinkToFile:=False, SaveWithDocument:=True</p>
<p>    Selection.MoveLeft Unit:=wdCharacter, Count:=1, Extend:=wdExtend</p>
<p>    With Selection.InlineShapes(1)<br />
        With .Borders(wdBorderLeft)<br />
            .LineStyle = wdLineStyleSingle<br />
            .LineWidth = wdLineWidth050pt<br />
            .Color = clrColor<br />
        End With<br />
        With .Borders(wdBorderRight)<br />
            .LineStyle = wdLineStyleSingle<br />
            .LineWidth = wdLineWidth050pt<br />
            .Color = clrColor<br />
        End With<br />
        With .Borders(wdBorderTop)<br />
            .LineStyle = wdLineStyleSingle<br />
            .LineWidth = wdLineWidth050pt<br />
            .Color = clrColor<br />
        End With<br />
        With .Borders(wdBorderBottom)<br />
            .LineStyle = wdLineStyleSingle<br />
            .LineWidth = wdLineWidth050pt<br />
            .Color = clrColor<br />
        End With<br />
        .Borders.Shadow = False<br />
    End With<br />
    With Options<br />
        .DefaultBorderLineStyle = wdLineStyleSingle<br />
        .DefaultBorderLineWidth = wdLineWidth050pt<br />
        .DefaultBorderColor = clrColor<br />
    End With<br />
End Sub</code></p>
<p>Using arguments is an essential concept to learn in order to start building more elaborate solutions instead of what you create simply by recording macros. As you can see here, it is relatively simple to add arguments to a recorded macro so that you can use it in more situations. </p>
<p>I&#8217;ll leave it as an exercise for you to figure out how to simplify the AddImage procedure even more. Here are a couple of clues: you can break it up into multiple procedures that use arguments and the AddImage has extra &#8220;baggage&#8221; that you can get rid of. </p>
]]></content:encoded>
			<wfw:commentRss>http://vbatips.com/2007/12/10/a-primer-on-procedure-arguments/feed/</wfw:commentRss>
		</item>
	</channel>
</rss>
