Недавно в форуме возник вопрос - почему не отображается изменение данных в окне 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.
Возникает два вопроса:
- Чем объясняется различие в поведении шейпа и страницы?
- Как заставить страницу тоже работать нормально?
Было замечено, что несколько позже изменения все-таки доходят до окна. Например, если селектировать шейп, а потом опять щелкнуть по странице, то окно 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 путь к экзешнику в качестве разрешенного пути к аддонам, а то не сработает.