79662687

Date: 2025-06-11 21:23:21
Score: 1.5
Natty:
Report link

Gentlemen. After 6 years I stumbled on this thread. It seems as if it's the only source for what I need for my work in the whole of internet. So, very impressive, thank you very much. I need this code because I'm a strength analyst, and an Excel heavy user. I need to be able to compare the formulas straight to the equations presented in standards, because it takes effort to figure out the corresponding measure for each address in the formula. In order to reach the required level of automation I taped PEH's code with the code by M-- in the thread:

How to run a string as a command in VBA

M--'s code creates, writes, runs and deletes Module1 in VBA editor. That way PEH's command to modify the equation shape can be constucted and run as a string. I was not able to solve the initiation of the equation ether, so the first equation opened will modify. I call my macro from Developer:Macros. In the pictures below are the views of my VBA Editor and Calculation sheet. Create similar Excel, and create a userform by clicking Insert: UserForm in the VBA editor. Insert textbox and a button objects to the userform. Clicking them opens the list of object attributes, and mark the name of the textbox as "TxtCellAddress" and the name of the button as "CommmandButton1". Then create two new modules by clicking Insert: Module twise. Then delete Module1. In Module2 create the sub (presented in the picture) that initiates the userform when macro is called.

My View of the VBA Editor

My View of the Excel Sheet

The UserForm1 in the VBA editor has "View object" and "View code" buttons in the top left corner of the project vindow. Click on the "View code" button to view the canvas, and paste the code below on it. (Check each line first to prevent malice code!)

Option Explicit
Private Sub CommandButton1_Click()
On Error GoTo ErrorHandler

 'UserForm1 contains a textbox(TxtCellAddress) and a button(CommandButton1).
 'When user writes CELL address, and clicks the button, the first shape on the
 'ActiveSheet(In this case, the shape is an equation box, which has previously
 'been plased on the sheet manually.) is evoked. This program modifies the
 'equation box to present the formula.
        
 'This code requires lowering your Excel security. Do not open Excel files sent
 'by other people while you work, and please remember to reverse these changes
 'after your work. Remember that malicious macros open, when Excel workbook is
 'opened. From File: Options: Trust Center: Macro Settings:
 'Click: Enable VBA macros(not recommended; potentially dangerous code can run)
 'Click: Trust access to the VBA project object model
        
 'The formula to be presented in equation box is obtained as a string value
 'from a cell the user must name in in the textbox. First the string containing
 'the formula is checked for INDIRECT() functions, that are evaluated.
        
 'The string containing the formula is then processed so, that the cell
 'addresses in formula are replaced by the names of the measures they keep. The
 'names of the measures may consist of symbols or they may have two parts,
 'first the normal part, and second the subscripted part. The default font of
 'Excel Equation Editor is "Cambria Math". I dont know how to change that, so
 'it is advisable to use that font in the column of the names of the measures.
        
 'In the ActiveSheet the calculation is set on column J, The description of the
 'measures are on column D, and the names of the measures are on column E. The
 'identifications of the calculation are marked on row 8. When the calculation
 'is copied column to column for say analysis of flanges in different pipe
 'locations, the identification helps, and thus we also want to see it in the
 'equation box. The string with the formula is then translated to a string
 'consisting of a set of character commands, readable by the equation editor.
        
 'Next a subroutine ExecuteString() is called. It creates Module1, and writes
 'the subs foo(), MakeEquationLinear() and MakeEquationProfessional(), and then
 'calls a subroutine foo() to be executed. Subroutine foo() sends the info to
 'the Equation Editor, that one has to previously open manually. The code
 'expects the Equation Editor to appear in the first shape i.e.
 'ActiveSheet.Shapes(1). Afterwards Module1 is deleted. Put the subroutine that
 'calls Userform1 to another module, because Module1 is reserved for the
 'operation of this code.
        
Dim start As Long
Dim count As Integer
Dim aihio As String
Dim measureText As String
Dim aihioLen As Integer
Dim state As Integer
Dim char As String * 1
Dim prevChar As String * 1
Dim i As Long
Dim ii As Integer
Dim originalText As String
Dim cellAddress As String
Dim measure(0 To 1000) As String
Dim indirectNum As Integer
Dim indirectLocation(1 To 1000) As Long
Dim indirectLength(1 To 1000) As Integer
Dim indirectRef As String
Dim iE As String
Dim rowNum As Integer
Dim colNum As Integer
Dim stringToRun As String
Dim columnOfMeasureNames As String
Dim CN As Integer
Dim rowOfItemNames As Integer
Dim rE As Range

columnOfMeasureNames = "C"
rowOfItemNames = 8
CN = Columns(columnOfMeasureNames).Column

 'Get cell adress from textbox. "Me" refers to the userform object. Because
 'this subroutine is in the UserForm1 module the precise object identification
 'is not required.
cellAddress = Me.TxtCellAddress.Value
 'Get row number of the cell named in textbox
rowNum = ActiveSheet.Range(cellAddress).Row
 'Get column number of the cell named in textbox
colNum = ActiveSheet.Range(cellAddress).Column
 'Get the formula as string from the cell named in textbox
originalText = ActiveSheet.Range(cellAddress).Formula

'************ START OF INDIRECT() FUNCTIONS PROCESSING ************'
 'If this segment causes a problem, remove it
indirectNum = 0
state = 0
For i = 1 To Len(originalText)
  char = Mid(originalText, i, 1)
  If (char = "I" Or char = "i") And state = 0 Then
    state = 1
  ElseIf (char = "N" Or char = "n") And state = 1 Then
    state = 2
  ElseIf (char = "D" Or char = "d") And state = 2 Then
    state = 3
  ElseIf (char = "I" Or char = "i") And state = 3 Then
    state = 4
  ElseIf (char = "R" Or char = "r") And state = 4 Then
    state = 5
  ElseIf (char = "E" Or char = "e") And state = 5 Then
    state = 6
  ElseIf (char = "C" Or char = "c") And state = 6 Then
    state = 7
  ElseIf (char = "T" Or char = "t") And state = 7 Then
    state = 8
    indirectNum = indirectNum + 1
    indirectLocation(indirectNum) = i - 7
  ElseIf char = "(" And state > 7 Then
    state = state + 1
  ElseIf char = ")" And state > 8 Then
    state = state - 1
    If state = 8 Then
      indirectLength(indirectNum) = i - indirectLocation(indirectNum) + 1
       'Go back incase of INDIRECT statements inside INDIRECT statement.
      i = indirectLocation(indirectNum) + 7
      state = 0
    End If
  ElseIf state < 9 Then
    state = 0
  End If
Next
If indirectNum > 0 Then
  For i = indirectNum To 1 Step -1
     'Get the formula between the caps of the indirect function
    indirectRef = Mid(originalText, indirectLocation(i), indirectLength(i))
     'Evaluate the line formula betveen the caps of the indirect function
    iE = Application.Evaluate(Mid(indirectRef, 10, indirectLength(i) - 10))
     'Replace the Indirect function with the evaluation in the formula
    originalText = Replace(originalText, indirectRef, iE)
  Next
End If
'************ END OF INDIRECT() FUNCTIONS PROCESSING ************'

 'Get the Address of the name of the measure from the column of names
originalText = ActiveSheet.Cells(rowNum, CN).Address & originalText

'************ START OF LISTING CELL ADDRESSES IN FORMULA *************'
 'Consider each character in string that contains the formula. If it is an
 'alphabetic letter or a "$" sign, then start making a record of an address. If
 'the next is also alphabetic or a "$" sign continue makin record. If the next
 'is a number, continue making the record, but accept only numeral characters
 'from now on. If the character is something else, then stop making record. If
 'the record is a full address when stopped, add it to the measure array,
 'otherwise discard it.
state = 0
count = 0
For i = 1 To Len(originalText)
  char = Mid(originalText, i, 1)
  If IsAlpha(char) Or char = "$" Then
    If state = 0 Then
      aihio = char
      If i > 1 Then
        prevChar = Mid(originalText, i - 1, 1)
      Else
         'No previous character for the first letter. Here it's just A
        prevChar = "A"
      End If
      state = 1
    ElseIf state = 1 Then
      aihio = aihio & char
    ElseIf state = 2 Then
      state = 0
      measure(count) = aihio
      count = count + 1
    End If
  ElseIf IsNumeric(char) And state > 0 Then
    aihio = aihio & char
    state = 2
     'If formula ends in a cell address, the last character is a number.
    If i = Len(originalText) Then
       'If another sheet "!" or an array of cells ":" is referenced.
      If prevChar = "!" Or prevChar = ":" Then
        state = 0
      Else
        measure(count) = aihio
        count = count + 1
      End If
    End If
  ElseIf state = 2 Then
    If prevChar = "!" Or prevChar = ":" Or char = ":" Then
      state = 0
    Else
      state = 0
      measure(count) = aihio
      count = count + 1
    End If
  Else
    state = 0
  End If
Next
'************ END OF LISTING CELL ADDRESSES IN FORMULA *************'

'**** START OF REPLACING CELL ADDRESSES WITH THE NAMES OF THE MEASURES ****'
'For each name of measure in measure array
For i = 0 To count - 1
  Set rE = ActiveSheet.Range(Replace(measure(i), "$", ""))
   'If the cell of the measure is not empty
  If Not IsEmpty(rE.Value) Then
     'Get name of the measure
    measureText = ActiveSheet.Cells(rE.Row, CN).Value
    If measureText = "" Then
       'Forgot to name the measure in the column of measure names?
      measureText = "?"
    Else
       'For each character in name of the measure
      For ii = 1 To Len(measureText)
         'If the character in the name of the measure in the cell is subscript
        If ActiveSheet.Cells(rE.Row, CN).Characters(ii, 1).Font.Subscript Then
           'Add markings for subscript
          measureText = Left(measureText, ii - 1) _
          & "_(" & Right(measureText, Len(measureText) - ii + 1) & ")"
           'Break the For loop when the objective is accomplished
          Exit For
        End If
      Next
    End If
     'Replace addresses in the formula string with the name of the measure
    originalText = Replace(originalText, measure(i), measureText)
  End If
Next
'**** END OF REPLACING CELL ADDRESSES WITH THE NAMES OF THE MEASURES ****'
 'The Identification of the calculation is added to the equation string
originalText = ActiveSheet.Cells(rowOfItemNames, colNum).Value _
& ":" & originalText
 'Adds the start of the command to the command linestring
stringToRun = "MyEquation.DrawingObject.Text = " & outputString(originalText)
 'Here the subroutine to write, execute and delete a new module is called.
ExecuteString stringToRun
Exit Sub

ErrorExit:
  Exit Sub
ErrorHandler:
  Debug.Print Err.Number & vbNewLine & Err.Description
  Resume ErrorExit
End Sub

Function IsAlpha(s$) As Boolean
 'This function returns true if the input character (String * 1) is alphabetic.
 'Otherwise it retuns false. Copied from
 'https://stackoverflow.com/questions/29633517/how-
 'can-i-check-if-a-string-only-contains-letters
IsAlpha = Not s Like "*[!a-zA-Z]*"
End Function

Function outputString(inputString$) As String
On Error GoTo ErrorHandler
   'If the text is taken from Cell as text, only the ASCII characters and
   'markings are presented correctly. Others, symbols and such are presented
   'by ?.'This function takes every character, weather ASCII or a Symbol, and
   'gives it ChrW number. The output is a string of ChrW commands, that is
   'readable by the Excel Equation Editor. This was copied from
   'https://stackoverflow.com/questions/55478312/is-there-any-documentation-on-
   'how-to-drive-the-office-equation-editor-through
  Dim ChrIdx As Long
  For ChrIdx = 1 To Len(inputString)
    outputString = outputString & IIf(outputString <> vbNullString, " & ", "") _
    & "ChrW(" & AscW(Mid$(inputString, ChrIdx, 1)) & ")"
  Next ChrIdx

ErrorExit:
  Exit Function
ErrorHandler:
  Debug.Print Err.Number & vbNewLine & Err.Description
  Resume ErrorExit
End Function

Sub ExecuteString(s As String)
On Error GoTo ErrorHandler
    
   'This subroutine creates a new module, then runs the code from within, and
   'then deletes the module after use. The Idea is, that because there are no
   'direct vba commands to dynamically operate the Excel Equation Editor. The
   'dynamic(using information in command that is not provided by the programmer)
   'operation is made possible by automatically creating new module, writing
   'new subroutines and the executing them. The codes here are copied and
   'modified from the following sources:
   'https://stackoverflow.com/questions/43216390/how-to-run-a-string-as-a-
   'command-in-vba
   'https://stackoverflow.com/questions/55478312/is-there-any-documentation-on-
   'how-to-drive-the-office-equation-editor-through
   'The Excel Equation Manager takes input as it is evoked from a list of ChrW
   'commands. It is propably possible somehow to give the command as combination
   'of ASCII tect and ChrW commands just to make the command string shorter.
    
  Dim code As String
  code = "Option Explicit" & vbCrLf
  code = code & "Sub foo()" & vbCrLf
  code = code & "On Error GoTo ErrorHandler" & vbCrLf
  code = code & "Dim MyEquation As Shape" & vbCrLf
  code = code & "Set MyEquation = ActiveSheet.Shapes(1)" & vbCrLf
  code = code & "MakeEquationLinear MyEquation" & vbCrLf
  code = code & "Application.EnableEvents = False" & vbCrLf
  Dim i As Long
  Dim ii As Long
  '**** START OF SPLITTING THE COMMAND STRING TO LINES < 1024 CHAR ****'
  i = IIf(1000 < Len(s), 1000, Len(s)) 'IIf() = Min(1000,Len(s))
  ii = 1
  While i <= Len(s)
    If Mid(s, i, 1) = " " Then
      code = code & Mid(s, ii, i - ii + 1) & "_" & vbCrLf
      ii = i + 1
       'If Min() was available in VBA: IIf() = Min(999,Len(s)-i-1)
      i = i + IIf(999 < Len(s) - i - 1, 999, Len(s) - i - 1)
    ElseIf i = Len(s) Then
      code = code & Mid(s, ii, i - ii + 1) & vbCrLf
    End If
    i = i + 1
  Wend
  '**** END OF SPLITTING THE COMMAND STRING TO LINES < 1024 CHAR ****'
  code = code & "Application.EnableEvents = True" & vbCrLf
  code = code & "MakeEquationProfessional MyEquation" & vbCrLf
  code = code & "ErrorExit:" & vbCrLf
  code = code & "   Application.EnableEvents = True" & vbCrLf
  code = code & "   Exit Sub" & vbCrLf
  code = code & "ErrorHandler:" & vbCrLf
  code = code & "Debug.Print Err.Number & vbNewLine & Err.Description" & vbCrLf
  code = code & "Resume ErrorExit" & vbCrLf
  code = code & "End Sub" & vbCrLf
    
  Dim suba As String
  suba = "Public Sub MakeEquationLinear(ByVal Equation As Shape)" & vbCrLf
  suba = suba & "On Error GoTo ErrorHandler" & vbCrLf
  suba = suba & "Dim OriginalSheet As Object" & vbCrLf
  suba = suba & "If Equation.Parent.Name <> ActiveSheet.Name Then" & vbCrLf
  suba = suba & "   Set OriginalSheet = ActiveSheet" & vbCrLf
  suba = suba & "   Equation.Parent.Activate" & vbCrLf
  suba = suba & "End If" & vbCrLf
  suba = suba & "Application.EnableEvents = False" & vbCrLf
  suba = suba & "Equation.Select" & vbCrLf
  suba = suba & "Application.CommandBars.ExecuteMso ""EquationLinearFormat""" _
  & vbCrLf
  suba = suba & "Application.EnableEvents = True" & vbCrLf
  suba = suba & "If Not OriginalSheet Is Nothing Then OriginalSheet.Activate" _
  & vbCrLf
  suba = suba & "ErrorExit:" & vbCrLf
  suba = suba & "   Application.EnableEvents = True" & vbCrLf
  suba = suba & "   Exit Sub" & vbCrLf
  suba = suba & "ErrorHandler:" & vbCrLf
  suba = suba & "   Debug.Print Err.Number & vbNewLine & Err.Description" _
  & vbCrLf
  suba = suba & "   Resume ErrorExit" & vbCrLf
  suba = suba & "End Sub" & vbCrLf
 
  Dim subb As String
  subb = "Public Sub MakeEquationProfessional(ByVal Equation As Shape)" & vbCrLf
  subb = subb & "On Error GoTo ErrorHandler" & vbCrLf
  subb = subb & "Dim OriginalSheet As Object" & vbCrLf
  subb = subb & "If Equation.Parent.Name <> ActiveSheet.Name Then" & vbCrLf
  subb = subb & "Set OriginalSheet = ActiveSheet" & vbCrLf
  subb = subb & "Equation.Parent.Activate" & vbCrLf
  subb = subb & "End If" & vbCrLf
  subb = subb & "Application.EnableEvents = False" & vbCrLf
  subb = subb & "Equation.Select" & vbCrLf
  subb = subb & "Application.CommandBars.ExecuteMso ""EquationProfessional""" _
  & vbCrLf
  subb = subb & "Application.EnableEvents = True" & vbCrLf
  subb = subb & "If Not OriginalSheet Is Nothing Then OriginalSheet.Activate" _
  & vbCrLf
  subb = subb & "ErrorExit:" & vbCrLf
  subb = subb & "   Application.EnableEvents = True" & vbCrLf
  subb = subb & "   Exit Sub" & vbCrLf
  subb = subb & "ErrorHandler:" & vbCrLf
  subb = subb & "   Debug.Print Err.Number & vbNewLine & Err.Description" _
  & vbCrLf
  subb = subb & "   Resume ErrorExit" & vbCrLf
  subb = subb & "End Sub" & vbCrLf
       
  Dim tempVBC As Object
  Set tempVBC = ActiveWorkbook.VBProject.VBComponents.Add(1)
  tempVBC.CodeModule.AddFromString code
  tempVBC.CodeModule.AddFromString suba
  tempVBC.CodeModule.AddFromString subb
  Application.Run tempVBC.Name & ".foo"
  ThisWorkbook.VBProject.VBComponents.Remove tempVBC
    
ErrorExit:
  Exit Sub
ErrorHandler:
  Debug.Print Err.Number & vbNewLine & Err.Description
  Resume ErrorExit
End Sub

In the code you can edit the following lines to match the column and row on your sheet:

columnOfMeasureNames = "C"

rowOfItemNames = 8

Result:

Success

Reasons:
  • Blacklisted phrase (0.5): thank you
  • Blacklisted phrase (0.5): I need
  • Blacklisted phrase (1): stackoverflow
  • Long answer (-1):
  • Has code block (-0.5):
  • Low reputation (1):
Posted by: Tarmo Timonen