Поле со списком отображает значение только после завершения макроса

#excel #vba #combobox

#excel #vba #поле со списком

Вопрос:

У меня есть приложение с несколькими диаграммами и множеством выпадающих списков (элемент управления ActiveX). Когда пользователь изменяет значение любого combobox, диаграммы обновляются. Здесь нет проблем.

Итак, я создал код для экспорта всего экрана приложения в виде изображения. Это используется для моделирования нескольких сценариев.

Но здесь начинается проблема.

В этом коде есть несколько циклов «для … следующего», чтобы изменить значения этих списков со списком. При экспорте изображений диаграммы обновляются, как и ожидалось, но поля со списком НЕ меняют своих значений. Они показывают одно и то же значение в каждом сценарии, даже если диаграммы обновляются.

Итак, вопрос в следующем: есть ли способ обновить значение combobox до завершения кода?

 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
  

Комментарии:

1. Добавление однострочных листов («Приложение»). ComboBox1.Активировать после установки значения поля со списком также помогает.

2. если в экспортируемом изображении также требуется очистить поле со списком, можно выбрать любую ячейку, например «Листы» («Приложение»). Диапазон («A1»). Выберите `после строк таблицы («Приложение»). ComboBox1.Activate и на картинке также есть поле со списком

Ответ №1:

Объяснение

Прежде всего, поздравляю: вы обнаружили здесь очень досадную ошибку. Я попытался воспроизвести вашу проблему, и я могу сделать это очень легко.

  • Если вы установили точку останова после обновления поля со списком (т.Е. Поток приостановлен) => компонент ActiveX обновляется
  • Если вы установите Application.Wait (TimeSerial(Hour(Now()), Minute(Now()), Second(Now()) 5)) (т. Е. Визуально Остановите выполнение на 5 секунд, но технически поток все еще запущен) =>, вы можете четко видеть, что компонент ActiveX не обновляется, и именно поэтому ваше изображение генерируется неправильно.

Я перепробовал все очевидные приемы ( Application.ScreenUpdating = True DoEvents , Application.EnableEvents = True , Application.Calculate и т.д.), Но в любом случае безуспешно.

Действительно кажется, что компоненты ActiveX будут обновляться Microsoft Excel только после завершения потока VBA. Вау.

Единственный способ обойти эту ошибку, о котором я могу думать

Единственный способ, который я могу придумать, чтобы технически остановить выполнение после обновления компонента ActiveX и возобновить его позже, — это использовать Application.OnTime метод Excel:

Application.OnTime планирует выполнение процедуры в указанное время в будущем (либо в определенное время суток, либо по истечении определенного времени).

Насколько уродливо это может выглядеть с технической точки зрения, вы можете обновить свой combobox, а затем запланировать выполнение остальной части вашего кода через секунду после того, как вы это сделали. С технической точки зрения:

  • Поток VBA 1: обновляет ваш ComboBox и завершается => компонент ActiveX обновляется
  • 1-секундная пауза без потоков VBA.
  • Поток VBA 2: создает диаграмму и экспортирует изображение с использованием обновленного компонента ActiveX.

Практически ваш код будет выглядеть примерно так:

 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