5

I'm using vba for checking a spreadsheet for strikethrough text. As

ActiveCell.Font.Strikethrough 

only detects strikethrough in the entire cell, I used following code that counts individual characters with strikethrough.

Dim iCh As Long
Dim StrikethroughFont As Long: StrikethroughFont = 0

If Len(ActiveCell) > 0  Then
    For iCh = 1 To Len(ActiveCell)
        With ActiveCell.Characters(iCh, 1)
            If .Font.Strikethrough = True Then
                StrikethroughFont = StrikethroughFont + 1
            End If
        End With
    Next iCh
End If

Code works as it should. Problem is that execution time increases exponentially with cell content length.

  • with less than 100 characters in every cell, code runs superfast.
  • with 1000 characters somewhere in 1 cell execution time is 30 seconds - still acceptable for the project
  • with 3000 characters somewhere in 1 cell execution time about half an hour.
  • with 5000 characters somewhere in 1 cell Excel continues to run seemingly forever, sometimes it crashes

I know that Excel is not intended for writing stories in a cell and revising them with strikethrough. But I have not control over what people do with these spreadsheets. Most people behave, but sometimes an individual exaggerates. I don't want this individual to make my work look bad. A not-so-nice workaround I found is adding a

And Len(ActiveCell) < 1000

statement to the first If, so that it completely skips cells with over 1000 characters. I'm fearing that Excel 2010 SP2 that I'm using is not handling the ActiveCell.Characters(iCh, 1) very well.
Any suggestions to speed things up?

Question update after reading the many valuable replies & comments As pointed out, I made an incorrect statement in my question on line 3 and update it now in order not to mislead readers who haven't read all comments yet:

ActiveCell.Font.Strikethrough 

Can actually detect partial strikethrough text in a cell: The possible return values are FALSE, TRUE and NULL, the latter meaning that there is a mix of strikethrough and normal font in the cell. This has no influence on the 'exponential' part of the question, but a lot on the 'workaround' part.

OldFrank
  • 810
  • 6
  • 6
  • And you need to count or just know if exists in the cell? If so just use a Do while loop that will exit when it finds the first Strikethrough character. Or use Exit for. – CRondao Feb 28 '14 at 09:16
  • Can you please show us a little more code, because neither I or CRonando can replicate the slowness – Archlight Feb 28 '14 at 09:22
  • 2
    From [MSDN's dev reference](http://msdn.microsoft.com/en-us/library/office/ff198232(v=office.15).aspx) it seems that `Characters` is **not a collection**. It is an object that represents a range of characters within the object text. This means that each time you access `cell.Characters` a new object of `Characters` type is created. This would kind of explain why the time increases with the characters count increasing. –  Feb 28 '14 at 09:30

3 Answers3

3

Try stopping excel from updating the screen as you are doing this. Usually this fixes all kinds of speed problems when running macros.

Application.ScreenUpdating = False

Dim iCh As Long
Dim StrikethroughFont As Long: StrikethroughFont = 0

If Len(ActiveCell) > 0  Then
    For iCh = 1 To Len(ActiveCell)
        With ActiveCell.Characters(iCh, 1)
            If .Font.Strikethrough = True Then
                StrikethroughFont = StrikethroughFont + 1
            End If
        End With
    Next iCh
End If

Application.ScreenUpdating = True

*Edit

As the above did not help at all, I just could not stop thinking how to fix this. AND HERE IT IS...

You need to add microsoft.wordXX object library as reference in your vba editor.

This counts 21000 words with 450 strikethrough words wich did not work att all in the above code, and here it takes about 3 secs now, using word as the counter and its counting WORDS with strikethrough. not nr of characters striketrhough. You can then afterwards loop through the words and count the caracters.

Sub doIt()


    Dim WordApp
    Dim WordDoc As Word.Document

    Set WordApp = CreateObject("Word.Application")
    WordApp.Visible = True ' change to false when ready :)

    Set WordDoc = WordApp.Documents.Add

    Range("a1").Copy
    Dim wdPasteRTF As Integer
    Dim wdInLine As Integer

    wdInLine = 0
    wdPasteRTF = 1

    WordApp.Selection.PasteSpecial Link:=False, DataType:=wdPasteRTF, _
    Placement:=wdInLine, DisplayAsIcon:=False

    Dim rngWords As Word.Range
    Set rngWords = WordDoc.Content
    Dim iStrikethrough As Long

    Do

    With rngWords.Find
        .Font.Strikethrough = True
        .Forward = True
        .Execute
    End With
    If rngWords.Find.Found = True Then
        iStrikethrough = iStrikethrough + rngWords.Words.Count
    Else
        Exit Do
    End If
    Loop
    MsgBox iStrikethrough

    WordDoc.Close savechanges:=False

    Set WordDoc = Nothing
    Set WordApp = Nothing

End Sub
Archlight
  • 1,871
  • 2
  • 19
  • 30
  • 1
    It will not help much, the code is not accessing the screen :) – CRondao Feb 28 '14 at 08:51
  • 1
    have you tried running this with 3000 strike-through characters? Were the results significantly better? What were they in comparison to OP's test? For me it makes no difference in terms of time on 3800 characters. Why does it not make a difference? Because nothing is modified on the spreadsheet so you `.ScreenUpdating` does literally nothing in this case. –  Feb 28 '14 at 08:53
  • Application.ScreenUpdating = False was already in there. (I only copied the problem part of the code) – OldFrank Feb 28 '14 at 09:02
  • In Excel 2010 swedish / norwegian professional with 21729 chars and all the characters with strikethrough. With screenUpdate on = 4 secs with screenupdate off = 2 secs – Archlight Feb 28 '14 at 09:02
  • 1
    Again, the code is not accessing the screen, I tested in a 1000 iterations loop, took exactly the same time... that must be same kind of miracle – CRondao Feb 28 '14 at 09:05
  • @CRondao did you manage to replicate the slowness. I did not. – Archlight Feb 28 '14 at 09:19
  • @Archlight your computer may be faster then the OP's the point is that YES, time exponentially increases with len = len + 1 so the OP's asking for an alternative solution... –  Feb 28 '14 at 09:28
  • Now its totally rewritten using Word's find function to count words that are strikethrough. Word seems to be much better at this but please check on your machine. – Archlight Feb 28 '14 at 10:54
  • Solution looks great, but also rather complex. As other people in other countries also have to use this, I'm a bit concerned that somewhere somebody might run into trouble (e.g. Word not installed). I'd prefer to keep it simple and keep it to Excel. But thanks a lot for the out-of-the-Excel box thinking and the code. I'll try it out tomorrow. – OldFrank Feb 28 '14 at 16:59
3

The following suggestion doesn't directly address your problem. But could be helpful in some situation.

Instead of checking each character in your sheet check cells first if there is any strikethrought character inside. What you need is the following logic:

'if activecell is fully Strikethrough, the following line:
 Debug.Print Activecell.Font.Strikethrough    '>> you get TRUE result

'if activecell has at min. one character strikethrought then the following line:
 Debug.Print Activecell.Font.Strikethrough    '>> will result with NULL

'if there is nothing in activecell which is Strikethrought, then
 Debug.Print Activecell.Font.Strikethrough    '>> you get FALSE result

For either TRUE or NULL you will do what you need. For FALSE you can skip the cell.

ALTERNATIVE SOLUTION

You could divide cell text into parts and check if there is any character you look for. The following solution reduced execution time from six to two seconds.

Dim iCh As Long
Dim StrikethroughFont As Long: StrikethroughFont = 0

Dim intStep As Integer
    intStep = 50       'make some experiments to find optimal range
Dim iPart As Integer

If Len(ActiveCell) > 0 Then
    'divide text into pices
    For iPart = 0 To Int(Len(ActiveCell) / intStep) + 1

        If ActiveCell.Characters(iPart * intStep + 1, intStep).Font.Strikethrough = True Or _
            IsNull(ActiveCell.Characters(iPart * intStep + 1, intStep).Font.Strikethrough) Then

            'run only if there is at min. one character to count
            For iCh = (iPart * intStep + 1) To ((iPart + 1) * intStep)
                If iCh > Len(ActiveCell) Then Exit For     'additional condition
                With ActiveCell.Characters(iCh, 1)
                    If .Font.Strikethrough = True Then
                        StrikethroughFont = StrikethroughFont + 1
                    End If
                End With

            Next iCh
        End If
    Next iPart
End If
Kazimierz Jawor
  • 18,184
  • 7
  • 30
  • 51
  • I think OP already knows this much though. I think the problem is that the current algorithm increases its execution time when there are more characters in the cell. What *I think* OP is looking for it a faster way to access `Characters` at specific origin within Range –  Feb 28 '14 at 09:37
  • @mehow, yes, I rather agree with you and I've seen your interesting comment under the question. However, I think there could be some missing logic in the question therefore I decided to add one sentence at the beginning of my answer and keep it. – Kazimierz Jawor Feb 28 '14 at 09:42
  • still helpful I agree but I don't think this answers the question I am going to hold on til further explanation from OP is given. –  Feb 28 '14 at 09:44
  • @mehow, what do you think of proposed new solution? – Kazimierz Jawor Feb 28 '14 at 10:20
  • I can't say much til the OP clarifies what information is needed. If he wants the count of striked characters or just to know if there any...Your solution reduces the time by 50% so I guess it's alright :) –  Feb 28 '14 at 10:31
  • The purpose of the code was to go through all cells in a series of spreadsheets and to find any mishaps, strikethrough being one. Next step would be to make a mishap list and then correct manually. That was the reason for counting the number of strikethrough characters. If only one char in strikethrough it is sometimes hard to visually find it in a lot of text, so count gives a clue. I can already work with the first solution KazJaw proposed, but the second one looks even better. I completely missed the difference between NULL and FALSE and focused too much on TRUE. THANKS !!!!!! – OldFrank Feb 28 '14 at 16:34
  • @OldFrank, I've just added additional condition which I missed recently... If you are finally happy with the solution please accept the answer. – Kazimierz Jawor Feb 28 '14 at 20:38
0

In general, any call to a range is super heavy. I am pretty sure you can do the same with only one reference to the cell...

Pierre
  • 1,006
  • 5
  • 17