Определение ширины и высоты объектов

Некоторые типы объектов позволяют устанавливать свои размеры в пикселях. К их числу относятся OBJ_BUTTON, OBJ_CHART, OBJ_BITMAP, OBJ_BITMAP_LABEL, OBJ_EDIT, OBJ_RECTANGLE_LABEL. Кроме того, объекты OBJ_LABEL поддерживают чтение (но не установку) размеров, поскольку надписи автоматически расширяются или сужаются под содержащийся в них текст. Попытка обратиться к свойствам у других типов объектов приведет к ошибке OBJECT_WRONG_PROPERTY (4203).

Идентификатор

Описание

OBJPROP_XSIZE

Ширина объекта по оси X в пикселях

OBJPROP_YSIZE

Высота объекта по оси Y в пикселях

Оба размера — это целые числа и потому обрабатываются функциями ObjectGetInteger/ObjectSetInteger.

Особая обработка размеров производится для объектов OBJ_BITMAP и OBJ_BITMAP_LABEL.

Без назначения картинки эти объекты позволяют задать произвольный размер. При этом они рисуются прозрачными (видна только рамка, если её не "спрятать" также, установив цвет clrNone), но получают все события, в частности, о перемещении мыши (с показом текстового описания, если оно есть, во всплывающей подсказке) и нажатиях её кнопок на объекте.

Когда картинка назначена, она по умолчанию определяет высоту и ширину объекта. Однако MQL-программа может установить меньшие размеры и выбрать фрагмент картинки для показа — подробнее об этом в разделе Кадрирование. Если высоту или ширину попытаться установить больше размера картинки, она перестает отображаться, а размеры объекта не меняются.

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

В новом скрипте ObjectSizeLabel.mq5 мы будем учитывать размер объекта и менять направление перемещения, как только он касается края окна любой своей стороной.

Для правильной реализации такого режима следует учитывать, что каждый угол окна, используемый в качестве центра отсчета координат до точки привязки на объекте, обуславливает характерное направление обоих осей X и Y. Например, если пользователь выбрал во входной переменной ENUM_BASE_CORNER Corner левый верхний угол, то X увеличивается слева направо, а Y — сверху вниз. Если же центром считается правый нижний угол, то X увеличивается справа налево от него, а Y — снизу вверх.

Различное взаимное сочетание угла привязки в окне и точки привязки на объекте требует разной корректировки расстояний между краями объекта и границами окна. В частности, когда выбран один из правых углов и одна из точек привязки на правой стороне объекта, то поправка у правой границы окна не требуется, а у противоположной — левой — мы должны учитывать ширину объекта (чтобы его габариты не вышли налево за пределы окна).

Это правило о внесении поправки на размер объекта можно обобщить:

  • на границе окна, прилегающей к углу привязки, поправка нужна при нахождении точки привязки на дальней стороне объекта по отношению к этому углу;
  • на границе окна, противоположной углу привязки, поправка нужна при нахождении точки привязки на ближней стороне объекта по отношению к этому углу.

Иными словами, если в названии угла (в элементе ENUM_BASE_CORNER) и точки привязки (в элементе ENUM_ANCHOR_POINT) встречается общее слово (например, RIGHT), поправка нужна на дальней стороне окна (то есть удаленной от выбранного угла). Если же в сочетании сторон ENUM_BASE_CORNER и ENUM_ANCHOR_POINT обнаруживаются противоположные направления (например, LEFT и RIGHT), поправка нужна у ближайшей стороны окна. Данные правила работают одинаково для горизонтальной и вертикальной оси.

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

Особый случай представляет точка привязки в центре объекта. Для неё следует всегда иметь запас расстояния в любом направлении, равный половине размера объекта.

Описанная логика реализована в специальной функции GetMargins. Она принимает на вход выбранный угол и точку привязки, а также размеры объекта (dx и dy). Возвращает функция структуру с 4-мя полями, содержащими размеры дополнительных отступов, которые следует отложить от точки привязки в направлении ближних и дальних границ окна, чтобы объект не вышел за пределы видимости. Отступы резервируют расстояние согласно габаритам и относительному расположению самого объекта.

struct Margins
{
   int nearX// добавка по X между точкой объекта и смежной с углом границей окна
   int nearY// добавка по Y между точкой объекта и смежной с углом границей окна    
   int farX;  // добавка по X между точкой объекта и противоположной углу границе окна
   int farY;  // добавка по Y между точкой объекта и противоположной углу границе окна
};
   
Margins GetMargins(const ENUM_BASE_CORNER cornerconst ENUM_ANCHOR_POINT anchor,
   int dxint dy)
{
   Margins margins = {}; // по умолчанию нулевые поправки
   ...
   return margins;
}

Для унификации алгоритма введены следующие макроопределения направлений (сторон):

   #define LEFT 0x1
   #define LOWER 0x2
   #define RIGHT 0x4
   #define UPPER 0x8
   #define CENTER 0x16

С их помощью определены битовые маски (комбинации), описывающие элементы перечислений ENUM_BASE_CORNER и ENUM_ANCHOR_POINT.

   const int corner_flags[] = // флаги для элементов ENUM_BASE_CORNER
   {
      LEFT | UPPER,
      LEFT | LOWER,
      RIGHT | LOWER,
      RIGHT | UPPER
   };
   
   const int anchor_flags[] = // флаги для элементов ENUM_ANCHOR_POINT
   {
      LEFT | UPPER,
      LEFT,
      LEFT | LOWER,
      LOWER,
      RIGHT | LOWER,
      RIGHT,
      RIGHT | UPPER,
      UPPER,
      CENTER
   };

Каждый из массивов corner_flags и anchor_flags содержит ровно столько элементов, сколько имеется в соответствующем перечислении.

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

   if(anchor == ANCHOR_CENTER)
   {
      margins.nearX = margins.farX = dx / 2;
      margins.nearY = margins.farY = dy / 2;
   }
   else
   {
      ...
   }

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

      const int mask = corner_flags[corner] & anchor_flags[anchor];
      ...

Если угол и точка привязки на одной стороне по горизонтали, сработает следующее условие, и будет сделана поправка на ширину объекта у дальней границы окна.

      if((mask & (LEFT | RIGHT)) != 0)
      {
         margins.farX = dx;
      }
      ...

Если они не на одной стороне, то могут быть на противоположных, а может быть и случай, что точка привязки в середине горизонтальной стороны (сверху или снизу). Проверка на точку привязки в середине делается с помощью выражения (anchor_flags[anchor] & (LEFT | RIGHT)) == 0 — тогда поправка равна половине ширины объекта.

      else
      {
         if((anchor_flags[anchor] & (LEFT | RIGHT)) == 0)
         {
            margins.nearX = dx / 2;
            margins.farX = dx / 2;
         }
         else
         {
            margins.nearX = dx;
         }
      }
      ...

Иначе, при противоположной ориентации угла и точки привязки делаем поправку в ширину объекта у ближней границы окна.

Аналогичные проверки выполняются для оси Y.

      if((mask & (UPPER | LOWER)) != 0)
      {
         margins.farY = dy;
      }
      else
      {
         if((anchor_flags[anchor] & (UPPER | LOWER)) == 0)
         {
            margins.farY = dy / 2;
            margins.nearY = dy / 2;
         }
         else
         {
            margins.nearY = dy;
         }
      }

Теперь функция GetMargins готова, и можно приступать к основному коду скрипта в функции OnStart. Как и ранее, мы определяем размер окна, рассчитываем начальные координаты в центре, создаем объект OBJ_LABEL и выделяем его для наглядности.

void OnStart()
{
   const int t = ChartWindowOnDropped();
   Comment(EnumToString(Corner));
   
   const string name = "ObjSizeLabel";
   int h = (int)ChartGetInteger(0CHART_HEIGHT_IN_PIXELSt) - 1;
   int w = (int)ChartGetInteger(0CHART_WIDTH_IN_PIXELS) - 1;
   int x = w / 2;
   int y = h / 2;
      
   ObjectCreate(0nameOBJ_LABELt00);
   ObjectSetInteger(0nameOBJPROP_SELECTABLEtrue);
   ObjectSetInteger(0nameOBJPROP_SELECTEDtrue);
   ObjectSetInteger(0nameOBJPROP_CORNERCorner);
   ...

Для анимации в бесконечном цикле предусмотрены переменные pass (счетчик итераций) и anchor (точка привязки, которая будет периодически выбираться случайным образом).

   int pass = 0;
   ENUM_ANCHOR_POINT anchor = 0;
   ...

Но по сравнению с ObjectAnchorLabel.mq5 сделан и ряд изменений.

Мы не станем генерировать случайные перемещения объекта. Вместо этого зададим постоянную скорость 5 пикселей по диагонали.

   int px = 5py = 5;

Для хранения размера надписи зарезервируем две новых переменных.

   int dx = 0dy = 0;

Результат подсчета дополнительных отступов будем сохранять в переменной m типа Margins.

   Margins m = {};

Далее следует непосредственно цикл перемещения и модификации объекта. В нем на каждой 75-ой итерации (одна итерация 100 мсек, см. далее) мы случайно выбираем новую точку привязки, формируем из неё новый текст (содержимое объекта) и ждем, когда изменения применятся к объекту (вызываем ChartRedraw). Последнее необходимо, потому что размер надписи автоматически подгоняется под содержимое, а нам важен новый размер, чтобы корректно посчитать отступы в вызове GetMargins.

Размеры мы получаем с помощью вызовов ObjectGetInteger со свойствами OBJPROP_XSIZE и OBJPROP_YSIZE.

   for( ;!IsStopped(); ++pass)
   {
      if(pass % 75 == 0)
      {
         // ENUM_ANCHOR_POINT состоит из 9 элементов: случайно выберем один
         const int r = rand() * 8 / 32768 + 1;
         anchor = (ENUM_ANCHOR_POINT)((anchor + r) % 9);
         ObjectSetInteger(0nameOBJPROP_ANCHORanchor);
         ObjectSetString(0nameOBJPROP_TEXT" " + EnumToString(anchor)
            + StringFormat("[%3d,%3d] "xy));
         ChartRedraw();
         Sleep(1);
   
         dx = (int)ObjectGetInteger(0nameOBJPROP_XSIZE);
         dy = (int)ObjectGetInteger(0nameOBJPROP_YSIZE);
         
         m = GetMargins(Corneranchordxdy);
      }
      ...

После того как точка привязки и все расстояния известны, выполняем перемещение объекта. Если он "упрется" в стенку, меняем направление движения на противоположное (px на -px или py на -py, в зависимости от стороны).

      // отскок от границ окна, объект полностью видимый
      if(x + px >= w - m.farX)
      {
         x = w - m.farX + px - 1;
         px = -px;
      }
      else if(x + px < m.nearX)
      {
         x = m.nearX + px;
         px = -px;
      }
      
      if(y + py >= h - m.farY)
      {
         y = h - m.farY + py - 1;
         py = -py;
      }
      else if(y + py < m.nearY)
      {
         y = m.nearY + py;
         py = -py;
      }
      
      // рассчитываем новую позицию надписи
      x += px;
      y += py;
      ...

Остается обновить состояние самого объекта: вывести текущие координаты в надпись, и назначить их свойствам OBJPROP_XDISTANCE и OBJPROP_YDISTANCE.

      ObjectSetString(0nameOBJPROP_TEXT" " + EnumToString(anchor)
         + StringFormat("[%3d,%3d] "xy));
      ObjectSetInteger(0nameOBJPROP_XDISTANCEx);
      ObjectSetInteger(0nameOBJPROP_YDISTANCEy);
      ...

После изменения объекта вызываем ChartRedraw и ждем 100 мсек, чтобы обеспечить достаточно плавную анимацию.

      ChartRedraw();
      Sleep(100);
      ...

В концовке цикла мы снова проверяем размер окна, так как пользователь может его изменить в процессе работы скрипта, а также повторяем запрос размеров.

      h = (int)ChartGetInteger(0CHART_HEIGHT_IN_PIXELSt) - 1;
      w = (int)ChartGetInteger(0CHART_WIDTH_IN_PIXELS) - 1;
      
      dx = (int)ObjectGetInteger(0nameOBJPROP_XSIZE);
      dy = (int)ObjectGetInteger(0nameOBJPROP_YSIZE);
      m = GetMargins(Corneranchordxdy);
   }

Некоторые прочие нововведения скрипта ObjectSizeLabel.mq5 мы опустили ради краткости — желающие могут обратиться к коду. В частности, были использованы отличительные цвета для надписи: каждый конкретный цвет соответствует собственной точке привязки, что делает более заметными моменты её переключения. Кроме того, вы можете нажать Delete во время работы скрипта: это удалит выделенный объект с графика, и скрипт автоматически завершится.