Home » excel » excel – Combobox only shows value after macro ends

excel – Combobox only shows value after macro ends

Posted by: admin April 23, 2020 Leave a comment

Questions:

I have an application with some charts and many comboboxes (activex control).
When the user changes the value of any combobox the charts are updated. No problem here.

So, I made a code to export the whole screen of the app as image. This is used to simulate several scenarios.

But, here is where the problem begins.

There are some “for…next” loops in this code to change the values of these comboboxes. When the images are exported, the charts are updated as expected, but the comboboxes DO NOT change their values. They show the same value on every scenario, even that the charts are being updated.

So, the question is: Is there a way to refresh the value of a combobox before the code is ended?

Sub example()

For Each elem In myArray

    Sheets("App").ComboBox1.Value = elem

    Sheets("Temp").Shapes.AddChart

    Set cht = Sheets("Temp").ChartObjects(1)

    Sheets("App").Range("A1:AM103").CopyPicture Appearance:=xlScreen, Format:=xlBitmap

    With cht.Chart
        .Paste
        .export Filename:="test.jpg", FilterName:="jpg"
        .Parent.Delete
    End With

Next

End Sub
How to&Answers:

Explanation

First of all, congratulations: you’ve found a very annoying bug here.
I’ve tried to reproduce your issue, and I can do it very easily.

  • If you set a breakpoint after updating the combobox (i.e. the thread is paused) => the ActiveX component is refreshed
  • If you set an Application.Wait (TimeSerial(Hour(Now()), Minute(Now()), Second(Now()) + 5)) (i.e. you visually stop the execution for 5 seconds, but technically speaking the thread is still running) => you can clearly see the ActiveX component not getting updated, and that’s why your image is generated wrongly.

I’ve tried all the obvious tricks (Application.ScreenUpdating = True, DoEvents, Application.EnableEvents = True, Application.Calculate etc.) but no success in any case.

It really seems that ActiveX components will get refreshed by Microsoft Excel only when the VBA thread ends. Wow.

The only way around this bug that I can think of

The only way I can think of to technically stop the execution after updating the ActiveX component, and resume it later, is to use the Application.OnTime method of Excel:

Application.OnTime schedules a procedure to be run at a specified time in the future (either at a specific time of day or after a specific amount of time has passed).

For how ugly it can look from a technical point of view, you could update your combobox and then schedule the rest of your code to happen a second after you did it. From a technical point of view:

  • VBA thread 1: updates your ComboBox and ends => the ActiveX component gets refreshed
  • 1 second pause without VBA threads.
  • VBA thread 2: creates the chart and export the image using the updated ActiveX component.

Practically, your code would look something like this:

Dim myArray(2) 'declare your array as global so that it can be accessed by all the macros - in my example I assume it contains 3 elements
Dim currentElem As Integer 'declare this index as global so it remains in memory even after the code ended execution

Sub example()

    'call this macro.
    'you first initialize your values:
    myArray(0) = "test 1"
    myArray(1) = "test 2"
    myArray(2) = "test 3"
    currentElem = 0
    'and then call the first update of your activeX component
    first_step_set_activeX

End Sub

Sub first_step_set_activeX()

    If currentElem < UBound(myArray) Then
        'for each element not treated yet
        '(that's why the If currentElem < UBound(myArray)
        elem = myArray(currentElem) 'get current element from array
        Sheets("App").ComboBox1.Value = elem 'update your ActiveX component
        currentElem = currentElem + 1 'increase the currentElem index
        Application.OnTime TimeSerial(Hour(Now()), Minute(Now()), Second(Now()) + 1), "second_step_make_chart_and_print" 'schedule the call of the printing part
    End If

End Sub

Sub second_step_make_chart_and_print()

    'here do the job of the printing part
    Sheets("Temp").Shapes.AddChart

    Set cht = Sheets("Temp").ChartObjects(1)

    Sheets("App").Range("A1:AM103").CopyPicture Appearance:=xlScreen, Format:=xlBitmap

    With cht.Chart
        .Paste
        .Export Filename:="test.jpg", FilterName:="jpg"
        .Parent.Delete
    End With

    'and reschedule the call for the next activeX component
    Application.OnTime TimeSerial(Hour(Now()), Minute(Now()), Second(Now()) + 1), "first_step_set_activeX"

End Sub