Недавно в форуме возник вопрос - почему не отображается изменение данных в окне Shape Data Window. Рассматривалась вот такая конструкция.

Страница имеет 4 поля данных. По замыслу переключение значения в поле Show/Hide All должно переключать видимость остальных трех полей.Цепочка событий:

  • изменяем Show на Hide в окне Shape Data;
  • значение ячейки User.HideAll изменяется с FALSE на TRUE;
  • значение ячеек Invisible трех остальных полей переключается в TRUE;
  • поля A, B, C в окне Shape Data становятся скрытыми.

Так вот, пользователь заметил, что такая конструкция на уровне шейпа работает, как и задумано, а вот на уровне страницы поведение сильно отличается.

Ниже картинка для шейпа.

А теперь смотрим такую же конструкцию для страницы.

Как видите, изменение данных проникает вплоть до ячеек Invisible, но вот в окно Shape Data это изменение не передается. Поля данных продолжают находиться в том же состоянии, как и до переключения поля Show/Hide All.

Возникает два вопроса:

  1. Чем объясняется различие в поведении шейпа и страницы?
  2. Как заставить страницу тоже работать нормально?

Было замечено, что несколько позже изменения все-таки доходят до окна. Например, если селектировать шейп, а потом опять щелкнуть по странице, то окно Shape Data обновится и изменения "проявятся".

Более того, как показано на следующей картинке, они проявляются даже при изменении форматирования текста.

Да практически факт изменения значения в любой ячейке шейп-листа страницы приводит к обновлению окна. То есть создается впечатление, что цепочка изменений в какой-то момент обрывается и не доходит до конца. Но достаточно вновь запустить процесс пересчета через некоторое время, чтобы "протолкнуть" события дальше.

Примечание. Нужно отметить, что с модальным окном Shape Data, которое вызывается по DoCmd(1312), такого не происходит. Тормознутостью отличается только немодальное окно, вызываемое DoCmd(1658).

В итоге создается впечатление, что немодальное окно просто становится "глухим" к событиям на некоторое время. А именно на то, пока Visio отрабатывает инициированные им изменения. А когда чувствительность включается, событие уже потеряно и окно не дергается.

Другое предположение, что в цепочке взаимовлияния участвует объект Selection. И в случае, когда коллекция пуста (как раз когда изменяются данные страницы), цепочка рвется. Это хорошо объясняло бы отличия в поведении шейпа и страницы, но кажется маловероятным. Тогда это было бы слишком грубой ошибкой разработчиков. А блокировка - это вроде как и не ошибка, просто так обстоятельства складываются.

Как же вернуть странице нормальное поведение? Принцип понятен - немного выждать и подтолкнуть.

Вариантов реализации может быть множество.

Во-первых, можно ничего не делать. То есть проигнорировать ошибку, зная, что после каких-то очередных действий оператора процесс обновления дойдет и до окна Shape Data. В принципе, это самый легкий путь, но если неопытный пользователь обратит внимание на такую задержку, то реакция может быть от недоумения до паники.

Можно написать памятку для тех, кто будет работать с таким документом. Типа, "если окно не отреагировало на ваши действия, то селектируйте какой-нибудь шейп и опять щелкните по странице". Так себе решение. Хочется все-таки не грузить лишний раз пользователя. Значит нужно искать автоматические методы.

Можно использовать функцию Now(). Например, прописать ее в шейп-лист документа, в какую-нибудь User или Scratch ячейку. Это поможет обновлению окна, но плохо то, что такая функция срабатывает только раз в минуту. Значит задержка будет весьма ощутима.

Неплохим решением выглядит обновление окна Shape Data при закрытии и открытии. Если дважды использовать функцию DOCMD(1658) при изменении ячейки User.HideAll, то окно моргнет и обновится. И пользователь скорее всего это даже не заметит.

Scratch.A1=DOCMD(1658)+DOCMD(1658)+DEPENDSON(User.HideAll)

Наверно это можно считать оптимальным решением, хотя еще лучше было бы использовать таймер или событие VisioIsIdle для изменения какой-нибудь ячейки в шейп-листе старницы или документа, а дальше уже Visio сам выстроил бы цепочку до обновления окна. К сожалению, ничего похожего среди функций шейп-листа нет.

Задача облегчается, если разрешено использование макросов. Тогда и таймер и обработчик события IsIdle не являются проблемой. Хотя, городить из-за этого программный таймер, наверное нецелесообразно. IsIdle проще. Например, используем вот такой код:

Dim WithEvents ap As Visio.Application
Sub PostponedEvent()
  Set ap = ActiveDocument.Application
End Sub
Private Sub ap_VisioIsIdle(ByVal app As IVApplication)
  ActiveDocument.DocumentSheet.Cells("User.Timer") = ActiveDocument.DocumentSheet.Cells("User.Timer").ResultIU + 1
  Set ap = Nothing
End Sub

И вызов

Scratch.A1=RUNMACRO("ThisDocument.PostponedEvent")+DEPENDSON(User.HideAll)

Теперь при переключении свойства Show/Hide All запустится макрос PostponedEvent, создаст объект ap, с отслеживаемым событием VisioIsIdle. Первое же срабатывание этого события изменит значение ячейки User.Timer и вызовет в итоге обновление окна Shape Data. И уничтожит более не нужный объект ap, чтобы не отвлекаться на обработку лишних событий.

Кстати, похожим образом можно обеспечивать управляемую задержку, если подсчитывать количество срабатываний VisioIsIdle и помнить, что они происходят примерно через 20 мсек.

Например, вот с таким обработчиком

Private Sub ap_VisioIsIdle(ByVal app As IVApplication)
  WithActiveDocument.DocumentSheet
  If .Cells("User.Timer").ResultIU < 100 Then
    .Cells("User.Timer") =.Cells("User.Timer").ResultIU + 1
  ElseIf.Cells("User.Timer").ResultIU = 100 Then
    Set ap = Nothing
  End If
  End With
End Sub

Если включить Event Monitor и сравнить происходящие события, то без обработки VisioIsIdle при изменении Show/Hide All можно увидеть вот такую цепочку событий

а после добавления обработчика - вот такую

То есть полностью повторяются все события, вызванные действием пользователя. Где-то в конце срабатывает вызов макроса и, как результат, после VisioIsIdle изменяется формула в User.Timer и обновляется окно.

Наконец, еще один "экзотический" способ. Если использование макросов запрещено, но хочется иметь отложенное событие, то можно использовать специальный Add-on. Например, создаем в VB6 экзешник AddonMinMin.exe вот с таким кодом

Его задача - после запуска записать единичку в ячейку User.Timer.

Option Explicit
Sub Main()
  Dim appVisio As Visio.Application
  Set appVisio = GetObject(, "Visio.Application")
  appVisio.Documents(1).DocumentSheet.Cells("User.Timer").Formula = "1"
  Set appVisio = Nothing
End Sub

В шейп-листе страницы прописываем вызов аддона

=RUNADDON("AddonMinMin")+DEPENDSON(User.HideAll)

Далее все происходит аналогично. При изменении ячейки User.HideAll вызывается аддон. В нем можно было бы организовать и таймер, но для данной задачи уже процесс запуска аддона даст нужную минимальную задержку, поэтому без таймера тоже работает. Аддон изменит User.Timer, что приведет к обновлению нужного окна. Нужно только не забыть прописать в настройках Visio путь к экзешнику в качестве разрешенного пути к аддонам, а то не сработает.