Sei sulla pagina 1di 9

5/1/2014

Excel tips and other stuff

DailyDose of Excel

April 30, 2014, 10:01 pm by Jeff Weir

Question One

You want to calculate a running (i.e. cumulative) total of the Data column. Which of these formulas should you put in B2 and drag down, and why?

=SUM(A\$2:A2)

=SUM(A2,B1)

Question Two

You want to calculate a running (i.e. cumulative) total of the Data column, and subtract 1 from it. Which of these formulas should you put in B2 and drag down, and why?

=SUM(A\$2:A2)-1

=SUM(A2,B1)-1

—edit— If you’re ambivalent as to the approach you would take, whip up a sample file. Then you won’t be ambivalent.

For Question one, Imagine you’ve got a really long list. Say a file with =row() in cells A1 to A20000.

For Question two, list length is unimportant, but be sure to do both approaches side by side.

In fact, let’s assume a more realistic data set, where instead of subtracting 1 from the cumulative total, you want to subtract a varying list of cumulative expenses, so you can work out the cumulative net profit:

April 30, 2014, 6:30 am by Dick Kusleika

5/1/2014

DailyDose of Excel

Last month I posted some metrics on the keyboard shortcuts I use. One of the pieces of code that I could not link to (because I’ve never posted it) is CopySum. I don’t remember what prompted me to write this little procedure, but it has been surprisingly useful. It sums the selected cells and puts that sum into the clipboard. That’s all it does.

Sub CopySum()

Dim doClip As MSForms.DataObject

On Error Resume Next

Set doClip = New MSForms.DataObject

If TypeName(Selection) = "Range" Then doClip.SetText Application.WorksheetFunction.Sum(Selection) doClip.PutInClipboard End If

End Sub

If I want to get a one-off sum of something and use it in another program, this comes in handy. I could SUM in a cell, copy that cell, paste it, and delete it. If I paste into Notepad, it’s fine, but if I try to paste into Outlook or even Gmail those programs try to get fancy and make an HTML table. Sometimes I just want the text.

One shortcoming of this procedure is that it doesn’t do well with filtered cells. The Selection includes both visible and hidden cells, but I probably only want visible. I’m changing the code to

doClip.SetText Application.WorksheetFunction.Subtotal(9, Selection)

so it works with filtered data.

April 29, 2014, 6:30 am by Dick Kusleika

Of course I use my sample data generator when I need some sample data. But sometimes I want some real data. I looked high and low for data sources to include in the upcoming 101 Ready to Use Excel Formulas book that Mike and I are writing. If you like data as much as I do, there might be a few sources you haven’t found on my pinboard page.

If you have a good one that’s not on that page, leave it in the comments.

April 28, 2014, 9:18 am by Dick Kusleika

How’s that title for Google-ability. Pretty saucy, I’d say. Here’s an affiliate link if you’re in the market for one of these label printers.

Previously I didn’t need anything fancy formatting-wise. I could just push some text at a DymoLabels object and print it. I’m not so lucky this time around. I really needed some underlines for visual separation of data. I don’t see any way to underline stuff through the Dymo object model. But what I did discover was that the .label files created by their software are just XML files. Woot!

Rather than trying to navigate a klunky object model, I can just write to a text file. Here’s what the new labels look like.

5/1/2014

DailyDose of Excel

And here’s a piece of the XML file.

<Element> <String>Int BOL Ext BOL

</String>

<Attributes>

Gallons

Price

Total

<Font Family="Courier New" Size="10" Bold="True" Italic="False" Underline="True" Strikeout="False" /> <ForeColor Alpha="255" Red="0" Green="0" Blue="0" /> </Attributes> </Element> <Element>

 22753 6,418 2.9775 19,109.60 18318 24094018 830 3.3361 2,768.93 18318 0

The XML isn’t terribly pretty mostly because there are line feeds in there. Without the line feeds, I think it would be indented properly. Each Element tag has a String tag and an Attributes tag. It looks like every font attribute is listed whether you specifically set it or not. I’m also padding my text with spaces to line up columns. I could use multiple text boxes, but there is no grid control. For now, this works. The only downside to using one big textbox and space padding is that I have to use a fixed-width font – Courier New in this case. If I want to use a nicer font, I’ll have to go the multiple textbox route.

I have this form for data input and a button to print the label. The button kicks off the code below. I don’t need the mdyLabel variable any more because I’m not using that part of the object model. Instead of mdyLabel, I’m manipulating the XML file directly giving me more control.

Sub PrintBlendCalc()

Dim vaPrinters As Variant Dim i As Long Dim sFile As String

If mdyAddin Is Nothing Then CreateDymoObjects End If

If Not mdyAddin Is Nothing Then vaPrinters = Split(mdyAddin.GetDymoPrinters, "|") For i = LBound(vaPrinters) To UBound(vaPrinters)

5/1/2014

DailyDose of Excel

UpdateLabelFile sFile mdyAddin.Open sFile mdyAddin.Print2 1, True, 1 Else MsgBox sMSGNODYMO, vbOKOnly End If

End Sub

Instead of using the SetField method (like in the previous example), I call a new procedure called UpdateLabel file to create a new XML file. Then I open the file and print it.

Public Sub UpdateLabelFile(ByRef sFile As String)

Dim xDoc As MSXML2.DOMDocument Dim xStrings As MSXML2.IXMLDOMSelection Dim vaData As Variant

'Get the label data in an array vaData = wshBlendCalc.Range("B2").Resize(7, 5).Value

'Create a new XML Doc and load the template label Set xDoc = New MSXML2.DOMDocument xDoc.Load msLABELPATH & "BlendCalc.label"

'Get all the "String" elements (there are 4) Set xStrings = xDoc.getElementsByTagName("String")

'Save the XML file sFile = msLABELPATH & "NewOrders\" & vaData(2, 2) & "_blend.label" xDoc.Save sFile

'Save the data to a text file for those people who don't read XML that well Open msLABELPATH & "NewOrders\" & vaData(2, 2) & "_blend.txt" For Output As #1 Print #1, FormatLabelText(vaData, 1) & _ FormatLabelText(vaData, 2) & _ FormatLabelText(vaData, 3) & _ FormatLabelText(vaData, 4) & _ FormatLabelText(vaData, 5) & _ FormatLabelText(vaData, 6) & _ FormatLabelText(vaData, 7) Close #1

End Sub

There are four elements in my XML file. The first label line is underlined, so that gets its own element. The second and third lines and a portion of the fourth line have the same attributes, so they’re grouped into one element. The portion of the fourth line that is underlined is in the third element. And all the rest of the text goes into the fourth element. The elements are set and the attributes are just the way I want them, so all that is left is to update the text.

I pass the array into a FormatLabelText function which extracts the data I want and pads the spaces so the columns line up. The FormatLabelText function is shown below.

I pass the sFile variable ByRef into UpdateLabelFile. The sFile variable is set and retains that value back in the calling procedure so the calling procedure knows which file to open and print.

The FormatLabelText function takes the array and whichever row we’re interested in formatting. It starts by defining three arrays:

vaBuffer defines how much padding for each of the five columns; vaAlignLeft defines if the columns are aligned right or left; and vaFormat defines the number format for each of the five column (@ is the format for general text).

Public Function FormatLabelText(ByRef vaData As Variant, ByVal lIndex As Long) As String

Dim sReturn As String Dim vaBuffer As Variant Dim vaAlignLeft As Variant Dim vaFormat As Variant Dim sData As String Dim j As Long

vaBuffer = Array(9, 11, 11, 8, 10)

5/1/2014

DailyDose of Excel

vaAlignLeft = Array(True, True, False, False, False) vaFormat = Array("@", "@", "#,##0", "0.0000", "###,##0.00")

For j = LBound(vaData, 2) To UBound(vaData, 2) sData = Format(vaData(lIndex, j), vaFormat(j - 1)) If Len(sData) = 0 Then sData = Space(vaBuffer(j - 1)) ElseIf Len(sData) > vaBuffer(j - 1) Then sData = Left\$(sData, vaBuffer(j - 1) - 1) & Chr\$(133) ElseIf Len(sData) < vaBuffer(j - 1) Then If vaAlignLeft(j - 1) Then sData = sData & Space(vaBuffer(j - 1) - Len(sData)) Else sData = Space(vaBuffer(j - 1) - Len(sData)) & sData End If End If sReturn = sReturn & sData Next j

FormatLabelText = sReturn & vbNewLine

End Function

First the data is formatted using the Format function and the vaFormat array. Next, the big If to pad the data with spaces.

ElseIf

EndIf

block determines how

If the data is empty (Len = 0) then write spaces for the whole buffer.

If the data length is greater than the buffer, truncate it and add an ellipse to the end.

If the data length is less than the buffer, add spaces to the end (or the beginning if it’s aligned right) to fill it out.

It would be nice (but hard) to create a more general purpose function that created a label from a range. But this works for now.

April 25, 2014, 6:30 am by Dick Kusleika

I’ve been using this recent files userform for quite a while now and I like it. I haven’t added Eric’s comment yet (and for no good reason), but I’m going to in the next version. I don’t know if that solves the Sharepoint problem or just the ChDrive problem as neither are problems for me.

Here are my two problems:

Not enough recent files. I’m shocked – SHOCKED – at how often 50 recent files is not enough. It’s usually when I have to open a whole bunch of very similar files that I will never open again, but that clean out my recent files list. I decided to, sort of, maintain my own list. Because I use class modules *ahem* changing things to maintain my own list was pretty easy. I had to change how I fill the CRcntFiles class, but everything that consumes that class downstream just works. Here’s the new Fill method in CRnctFiles.

Public Sub Fill()

Dim rf As RecentFile Dim clsRcntFile As CRcntFile Dim sFile As String, lFile As Long Dim vaFiles As Variant Dim i As Long

For Each rf In Application.RecentFiles Set clsRcntFile = New CRcntFile clsRcntFile.FullName = rf.Path Me.Add clsRcntFile Next rf

sFile = ThisWorkbook.Path & Application.PathSeparator & msMRUFILE lFile = FreeFile Open sFile For Input As lFile

vaFiles = Split(Input\$(LOF(lFile), lFile), vbNewLine)

Close lFile

For i = LBound(vaFiles) To UBound(vaFiles) If Len(vaFiles(i)) > 0 Then Set clsRcntFile = Nothing Set clsRcntFile = Me.RcntFileByFullName(vaFiles(i))

If clsRcntFile Is Nothing Then Set clsRcntFile = New CRcntFile clsRcntFile.FullName = vaFiles(i) Me.Add clsRcntFile End If End If

5/1/2014

DailyDose of Excel

Next i

End Sub

First, I read in the 50 Excel most recently used files. Then I read in the 1,000 most recently used files that I store in a text file, weeding out the duplicates as I go. The advantage of continuing to use the Excel MRU is that I can leverage its pinning feature. I don’t have to write my own pinning bullshit – if you want to pin something, do it via Excel and it will always be in the MRU. Awesome.

Why 1,000 files? I don’t know. We’ll see how the performance holds up. I’ve been using it for three days and my text file is only up to 58 files – the 50 Excel stores plus eight additional. I guess it will take a bit longer to get to 1,000 than I thought, but I think it will be clear when their are too many and I can pare it down.

Next I need a way to write the files back to the text file. When the userform closes, the CRcntFiles.WriteToDisk method is called.

Public Sub WriteToDisk()

Dim sFile As String Dim lFile As Long Dim clsRcntFile As CRcntFile Dim aFiles(1 To 1000) As String Dim lCnt As Long

lCnt = 0 For Each clsRcntFile In Me lCnt = lCnt + 1 If lCnt > UBound(aFiles) Then Exit For aFiles(lCnt) = clsRcntFile.FullName Next clsRcntFile

sFile = ThisWorkbook.Path & Application.PathSeparator & msMRUFILE lFile = FreeFile

Open sFile For Output As lFile Print #lFile, Join(aFiles, vbNewLine) Close lFile

End Sub

I lazily write 1,000 lines to the disk even if I don’t have that many. I mean efficiently, not lazily. The text file is 6KB, so I’m not losing sleep over it. I would be pretty trivial to Redim Preserve that after I’ve filled it up, so I supposed I’ll do that after the alpha test.

And other than a few minor tweaks, that’s the only changes I had to make. If that’s not a case for using class modules, I don’t know what is. My userform consumes a CRcntFiles class. It doesn’t care how that class gets filled up or where the list comes from. I could change to storing those recent files in the registry, in an XML file, or tattooed to my back. As long as I can get them into a CRcntFiles instance, the rest of the code is happy.

Save As is jealous of Open. My next problem is that while I can quickly open a recent file, I can’t quickly save a file to a recent place. This is primarily a problem when I open attachments in Outlook. It stores an opened attachment in the Temp folder and when I choose Save As, that’s the folder it starts me in. Nuts to that. If you download this add-in, you’ll also see that I’ve hooked up a SaveAs userform to Ctrl+Shift+S. It’s got a few problems too (it prompts to replace a file twice), but you can try it if you like.

April 23, 2014, 6:30 am by Dick Kusleika

I’ve probably said everything I need to on the subject, so no long diatribes here. However, I decided that I was going to experiment with coding without Hungarian and see what I thought. A little experimentation can’t be bad. I have a choice. I can be stubborn and not learn anything or I can force myself to try something different and maybe I’ll be the better for it. There was a time when I never coded custom class modules. Now you can’t get me to shut about them.

Here is the first procedure I coded in my experiment:

Public Sub ReformatDrivers()

Dim Cell As Range Dim Found As Range

For Each Cell In Sheet1.Range("A2:A133").Cells Set Found = Sheet2.ListObjects(1).DataBodyRange.Find(Cell.Value, , xlValues, xlWhole) If Not Found Is Nothing Then Cell.Offset(0, 1).Value = Sheet2.Cells(Found.Row, 2).Value Cell.Offset(0, 2).Value = "'" & Sheet2.Cells(Found.Row, 1).Value

5/1/2014

DailyDose of Excel

Cell.Offset(0, 3).Value = Found.Offset(0, 1).Value End If Next Cell

End Sub

Not exactly a barn burner, I’m sure you’ll agree. I hated every minute of it. I hate reading it right now. I’ve struggled to pinpoint why it displeases me so, but I have a theory.

It’s hard to tell the difference between keywords and variables. ForEachCellare all one syllable words with the first letter capitalized. The color distinction actually shows up better on this blog than it does in the IDE. I could change my color options in VBA so it stands out a little better.

There’s no requirement to make my variables proper case and thus hard to distinguish. I could code ForEachcelland make my variables stand out because they’re lower case. But there is a substantial advantage to using capital letters – the IDE fixes your caps and tells you if you have a typo. So I want to have at least one capital letter to get that benefit. I could use camel case for two syllable words, like fileName. Do I have to always avoid one syllable words?

As I’ve mentioned in the past, I don’t use data type prefixes in other languages. But like this experiment, I don’t really like the variables I use when I code Ruby and I think it’s for the same reason. The difference is that my Ruby IDE doesn’t fix caps and, maybe more importantly, everything about Ruby is new and novel and foreign so on the scale of strangeness, all lower-case variables don’t really rate.

Another advantage of data type prefixing is being able to use reserved words. For my experiments, if I want to use a reserve word I’m going to tack on an underscore. When I want to code DimlEndAsLong, I will instead use DimEnd_AsLong.

I haven’t made a userform yet, but there is a problem that I’m not sure how to solve. Most of my controls have labels and any control with a label is named the same as the label. The textbox tbxSearch has lblSearch. The combobox cbxCustomer has lblCustomer. There’s real value in that and I’m not sure how to get away from it. Another problem with userforms are class properties. When I start typing Me.tbxI know I’m getting a textbox. But if my textbox is called CustomerName and I have a property of the userform class to hold a customer name, how do I distinguish them without the tbx? That’s not a rhetorical question, I really want to know how people do it.

I’ll keep writing new code without data type prefixing until I can’t take it anymore. And, of course, I’ll keep bitching about right here.

April 21, 2014, 6:30 am by Dick Kusleika

In the comments to Error Handling Via an Error Class, Peter found that the problem with this method is the absence of the Stop and Resume in the error handler than let you debug at the line that caused the error. Yeah, that stinks. Then Jase got me thinking that I just wouldn’t create the class in debug mode. Well, that wasn’t quite right. What needed to happen was that the error handler should not be set in debug mode. Here’s a rewrite of the entry point procedure.

Sub EntryPoint()

Dim clsError As CError

gbDebugMode = False

If Not gbDebugMode Then On Error GoTo ErrHandler

Set clsError = New CError: clsError.SetLoc "Module1", "EntryPoint"

SubProc1

ErrExit:

Exit Sub

ErrHandler:

Set clsError = Nothing MsgBox Err.Description

5/1/2014

DailyDose of Excel

Resume ErrExit

End Sub

When gbDebugMode is False, the error handler is set and it works as described in the original post. That is, the user gets a message box and the code exits gracefully. When gbDebugMode is True, the error handler is not set. It’s like you don’t have an error handler at all – because you don’t. When in debug mode, you get kicked to the line that caused the error.

Is that that last hurdle?

Category: Classes, Events | 3 Comments

April 19, 2014, 5:51 pm by Dick Kusleika

I just submitted the last chapter of a super-awesome new book project I’m doing with Mike Alexander. Submitting the last chapter is always a great feeling. I have no idea when it will be available, but I’ll be pimping it right here when I know. It’s a formulas book, and did I mention that it’s super-awesome?

Last month was the 10 year anniversary of DDoE. Wow, ten years feels like a long time. The only way to see the first post from March 2004 is in the wayback machine. It was lost in the great data loss event of a couple years ago. I had designs to restore all the lost posts, but it has not happened. I still have hope that will happen.

That first post was about summing between two dates. It was the old trick where you sum less than the greater date and subtract the sum of less than the lesser date. Now that Excel has SUMIFS, that trick is obsolete. That may be why I’m not so eager to get it back online.

Did you know that DDoE was originally at dicks-blog.com? John Walkenbach convinced me to start a blog during a trip to Seattle back in the day. We came up with the URL at the bar. I think he was trying to make hyphens more socially acceptable in URLs. The early aughts were a strange time.

If you follow DDoE, you know we’ve had some trouble here of late. Something has been spiking the memory and shutting down MySQL. It hasn’t happened in a while, but that may be because there hasn’t been much activity. Hopefully all that’s behind us. Thanks to Jeff Weir and the other contributors for all the posts they’ve made.

I’ve got several posts queued up (including one on keyboard shortcuts if you can believe that) so stay tuned for those and an announcement on when the new book will be hitting the shelves.

April 18, 2014, 12:33 pm by jkpieterse

Hi everyone!

Enjoy!

April 12, 2014, 12:26 pm by Ron de Bruin

Hi all

I add a new page on my site with a workaround for this problem. I hope that there are no more of this problem icons for the QAT.

Not easy find the problem with bugs like this, you not think that an Excel icon in the QAT can crash Excel.

5/1/2014

DailyDose of Excel

Paste and Transpose icon in QAT crash Excel 2013

Regards Ron de Bruin

Category: Excel 2013 | Comment