Постановка задачи

Речь идет о методах Drop и DropMany в Visio.

expression.Drop(ObjectToDrop, xPos, yPos)

Мы знаем, что в качестве ObjectToDrop может выступать как мастер, так и шейп, размещенный на странице. А в справке можно прочесть такую фразу: "If ObjectToDrop is a Master, the pin of the master is dropped at the specified coordinates". По логике - если объект является не мастером, а шейпом, то его может забросить совсем в другое место. Вот этой особенности и посвящена данная статья.

Предположим, нам понадобился такой сценарий:

  • нарисовать квадрат (исходный шейп);
  • размножить его 3 раза методом Drop со смещениями 1, 2 и 3 дюйма по X;
  • сместить исходный шейп на дюйм вверх;
  • размножить его еще 3 раза методом.

По сценарию ожидается такая картинка, как на рисунке Test1.

 

Эксперименты

Пытаемся проделать это с помощью макроса с вот таким текстом:

Dim shp As Visio.Shape
Public Sub Test2()
    Set shp = ActivePage.DrawRectangle(0, 0, 0.4, 0.4)
    shp.Cells("FillForegnd") = 3
    DropSourceShape
    ActiveWindow.DeselectAll
    ActiveWindow.Select shp, visSelect
    ActiveWindow.Selection.Move 0, 1
    DropSourceShape
End Sub
Private Sub DropSourceShape()
    x0 = shp.Cells("PinX").ResultIU
    y0 = shp.Cells("PinY").ResultIU
    Set rez1 = ActivePage.Drop(shp, x0 + 1, y0)
    Set rez2 = ActivePage.Drop(shp, x0 + 2, y0)
    Set rez3 = ActivePage.Drop(shp, x0 + 3, y0)
End Sub

И видим совсем другую картину. Второй ряд шейпов рисуется не в одной строке с исходным шейпом, а неожиданно уходит вверх, хотя в операции Drop изменяется только координата X.

Такое впечатление, что сдвиг исходного шейпа еще раз добавляется к его копиям. Шейп вроде как "помнит", что его сдвинули и проталкивает этот сдвиг в копии.

 

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

Public Sub Test3()
    Set shp = ActivePage.DrawRectangle(0, 0, 0.4, 0.4)
    shp.Cells("FillForegnd") = 3
    DropSourceShape_2
    ActiveWindow.DeselectAll
    Set shp = ActivePage.Shapes(1)
    ActiveWindow.Select shp, visSelect
    ActiveWindow.Selection.Move 0, 1
    DropSourceShape_2
End Sub
Private Sub DropSourceShape_2()
    x0 = shp.Cells("PinX").ResultIU
    y0 = shp.Cells("PinY").ResultIU
    Set rez1 = ActivePage.Drop(shp, x0 + 1, y0)
    Set rez2 = ActivePage.Drop(shp, x0 + 2, y0)
    Set rez3 = ActivePage.Drop(shp, x0 + 3, y0)
    Set shp = Nothing
End Sub

Не помогает. Шейпы рисуются точно так же.

На всякий случай добавляем еще DoEvents перед вторым проходом. И вот тут ситуация исправляется. Вторая линейка появляется на запланированном месте.

Причем для успеха пришлось и переменную очищать и DoEvents вставлять. Полумеры не приносят успеха. Кроме того, если попробовать вместо добавления DoEvents пройти макрос по шагам, то это тоже не поможет.

Public Sub Test4()
    Set shp = ActivePage.DrawRectangle(0, 0, 0.4, 0.4)
    shp.Cells("FillForegnd") = 3
    DropSourceShapeDoEvents()
    ActiveWindow.DeselectAll
    Set shp = ActivePage.Shapes(1)
    ActiveWindow.Select shp, visSelect
    ActiveWindow.Selection.Move 0, 1
    DoEvents
    DropSourceShapeDoEvents()
End Sub
Private Sub DropSourceShapeDoEvents()
    x0 = shp.Cells("PinX").ResultIU
    y0 = shp.Cells("PinY").ResultIU
    Set rez1 = ActivePage.Drop(shp, x0 + 1, y0)
    Set rez2 = ActivePage.Drop(shp, x0 + 2, y0)
    Set rez3 = ActivePage.Drop(shp, x0 + 3, y0)
    Set shp = Nothing
End Sub

Результат достигнут, но объяснить его тяжело. Особенно влияние DoEvents с учетом опыта пошагового исполнения. Но есть еще варианты. Будем в каждом проходе вместо исходного шейпа получать и размножать его копию, а после прорисовки удалять ее. Тем самым уберем ненужную "память" шейпа. Ведь копия каждый раз создается как новый шейп, значит памяти не имеет.

Теперь текст такой:

Public Sub Test5()
    Set shp = ActivePage.DrawRectangle(0, 0, 0.4, 0.4)
    shp.Cells("FillForegnd") = 3
    DropCopy
    ActiveWindow.DeselectAll
    Set shp = ActivePage.Shapes(1)
    ActiveWindow.Select shp, visSelect
    ActiveWindow.Selection.Move 0, 1
    DropCopy
End Sub
Private Sub DropCopy()
    shp.Copy 1
    ActivePage.Paste 1
    Set shp1 = ActiveWindow.Selection(1)
    x0 = shp1.Cells("PinX").ResultIU
    y0 = shp1.Cells("PinY").ResultIU
    Set rez1 = ActivePage.Drop(shp1, x0 + 1, y0)
    Set rez2 = ActivePage.Drop(shp1, x0 + 2, y0)
    Set rez3 = ActivePage.Drop(shp1, x0 + 3, y0)
    shp1.Delete
End Sub

Результат опять нормальный, но в отличие от предыдущего варианта, он поддается объяснению. Шейп новый, "память" отсутствует. Ухищрения типа очистки переменной и DoEvents не нужны.

Наконец, пробуем последний вариант. Берем текст от самого первого макроса (который не работал) и после Drop еще раз прописываем PinX, PinY, вычисленные по той же самой формуле.

Public Sub Test6()
    Set shp = ActivePage.DrawRectangle(0, 0, 0.4, 0.4)
    shp.Cells("FillForegnd") = 3
    DropAndCorrect
    ActiveWindow.DeselectAll
    ActiveWindow.Select shp, visSelect
    ActiveWindow.Selection.Move 0, 1
    DropAndCorrect
End Sub
Private Sub DropAndCorrect()
    x0 = shp.Cells("PinX").ResultIU
    y0 = shp.Cells("PinY").ResultIU
    Set rez1 = ActivePage.Drop(shp, x0 + 1, y0)
    Set rez2 = ActivePage.Drop(shp, x0 + 2, y0)
    Set rez3 = ActivePage.Drop(shp, x0 + 3, y0)
    rez1.Cells("PinX") = x0+1
    rez1.Cells("PinY") = y0
    rez2.Cells("PinX") = x0+2
    rez2.Cells("PinY") = y0
    rez3.Cells("PinX") = x0+3
    rez3.Cells("PinY") = y0
End Sub

И ведь оно работает! В операции Drop лишняя "память" мешается, но в самом-то исходном шейпе координаты нормальные. И повторное вычисление по той же формуле с прямой записью в ячейки смещает созданные шейпы в нужную позицию.

Рассуждения

Как можно объяснить подобное поведение?

По-видимому, причина кроется в отличии конструкции мастера от конструкции шейпа. Мастер аналогичен странице, на которой размещен шейп. И метод Drop рассчитан именно на такую конструкцию.

Тогда при использовании шейпа в качестве ObjectToDrop Visio может где-то во внутренней памяти попытаться создать аналог мастера. То есть добавить к шейпу страницу. И прототипом такой страницы будет страница документа. Вот здесь и появляется зависимость от смещения исходного шейпа относительно страницы.

Псевдо-мастер явно создается один раз при обнаружении нового исходного шейпа для операции Drop. В нашем случае - при первом использовании.

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

А почему срабатывает повторный расчет? Потому что это уже не операция Drop. Теперь используется истинное значение PinY шейпа без всяких псевдо-страниц.

Таким образом, найдено три способа исправления данной особенности операции Drop. Первый (с очисткой переменной) следует отбросить из-за его нечеткости. Второй (промежуточная копия) и третий (дополнительное перемещение) объяснимы и работают. Какой из них выбрать - зависит от ситуации. Третий выглядит более надежным, если не помешает тот факт, что сначала копия будет заброшена в другую область. Иногда это может мешать.

Выводы

  1. Операцию Drop с шейпом в качестве исходного объекта следует использовать осторожно. Нужно помнить, что смещение исходного шейпа может исказить результат.
  2. Для исправления ситуации можно использовать промежуточное копирование исходного шейпа с последующим уничтожением копии.
  3. Также можно использовать повторный расчет и корректировку положения копий путем прямой записи координат.