Determinar ancho y alto del objeto

Algunos tipos de objetos permiten establecer sus dimensiones en píxeles. Entre ellos se incluyen OBJ_BUTTON, OBJ_CHART, OBJ_BITMAP, OBJ_BITMAP_LABEL, OBJ_EDIT y OBJ_RECTANGLE_LABEL. Además, los objetos OBJ_LABEL admiten la lectura (pero no la configuración) de tamaños porque las etiquetas se expanden o contraen automáticamente para ajustarse al texto que contienen. Si se intenta acceder a propiedades de otros tipos de objetos, se producirá un error OBJECT_WRONG_PROPERTY (4203).

Identificador

Descripción

OBJPROP_XSIZE

Anchura del objeto a lo largo del eje X en píxeles

OBJPROP_YSIZE

Altura del objeto a lo largo del eje Y en píxeles

Ambos tamaños son enteros y, por lo tanto, se manejan con las funciones ObjectGetInteger/ObjectSetInteger.

Para los objetos OBJ_BITMAP y OBJ_BITMAP_LABEL se realiza un tratamiento especial de las dimensiones.

Sin asignar una imagen, estos objetos permiten establecer un tamaño arbitrario. Al mismo tiempo, se dibujan transparentes (sólo el marco es visible si no se «oculta» también configurando el color clrNone), pero reciben todos los eventos, en concreto, sobre los movimientos del ratón (con una descripción de texto, si la hay, en una información sobre herramientas) y los clics de sus botones sobre el objeto.

Cuando se asigna una imagen, ésta tiene por defecto la altura y la anchura del objeto. Sin embargo, un programa MQL puede establecer tamaños más pequeños y seleccionar un fragmento de una imagen para mostrar; encontrará más información al respecto en la sección sobre sincronización de tramas o frames. Si intenta establecer la altura o la anchura mayores que el tamaño de la imagen, ésta deja de mostrarse y las dimensiones del objeto no cambian.

Como ejemplo, vamos a desarrollar una versión mejorada del script ObjectAnchorLabel.mq5 de la sección titulada Definir el punto de anclaje en el objeto. En esa sección, movíamos la etiqueta de texto alrededor de la ventana y la invertíamos cuando alcanzaba alguno de los bordes de la misma, pero lo hacíamos teniendo en cuenta sólo el punto de anclaje. Debido a esto, dependiendo de la ubicación del punto de anclaje en el objeto, podría darse la situación de que la etiqueta se desplazara casi por completo más allá de la ventana. Por ejemplo, si el punto de anclaje estuviera en el lado derecho del objeto, el desplazamiento hacia la izquierda provocaría que casi todo el texto sobrepasara el borde izquierdo de la ventana antes de que el punto de anclaje tocara el borde.

En el nuevo script ObjectSizeLabel.mq5, tendremos en cuenta el tamaño del objeto y cambiaremos la dirección del movimiento en cuanto toque el borde de la ventana con cualquiera de sus lados.

Para la correcta implementación de este modo, debe tenerse en cuenta que cada esquina de ventana utilizada como centro de referencia de coordenadas al punto de anclaje sobre el objeto determina la dirección característica de los ejes X e Y. Por ejemplo, si el usuario selecciona la esquina superior izquierda en la variable de entrada ENUM_BASE_CORNER, entonces X aumenta de izquierda a derecha e Y aumenta de arriba abajo. Si se considera que el centro es la esquina inferior derecha, entonces X aumenta de derecha a izquierda de la misma, e Y aumenta de abajo a arriba.

Una combinación mutua diferente de la esquina de anclaje en la ventana y el punto de anclaje en el objeto requiere diferentes ajustes de las distancias entre los bordes del objeto y los bordes de la ventana. En concreto, cuando se selecciona una de las esquinas de la derecha y uno de los puntos de anclaje del lado derecho del objeto, entonces no es necesaria la corrección en el borde derecho de la ventana, y en el lado opuesto, el izquierdo, hay que tener en cuenta la anchura del objeto (de manera que sus dimensiones no se salgan de la ventana hacia la izquierda).

Esta regla sobre la corrección del tamaño de un objeto puede generalizarse:

  • En el borde de la ventana adyacente a la esquina de anclaje, la corrección es necesaria cuando el punto de anclaje se encuentra en el lado más alejado del objeto con respecto a esta esquina;
  • En el borde de la ventana opuesto a la esquina de anclaje, la corrección es necesaria cuando el punto de anclaje se encuentra en el lado cercano del objeto con respecto a esta esquina.

En otras palabras: si el nombre de la esquina (en el elemento ENUM_BASE_CORNER) y el punto de anclaje (en el elemento ENUM_ANCHOR_POINT) contienen una palabra común (por ejemplo, DERECHA), la corrección es necesaria en el lado más alejado de la ventana (es decir, lejos de la esquina seleccionada). Si se encuentran direcciones opuestas en la combinación de los lados ENUM_BASE_CORNER y ENUM_ANCHOR_POINT (por ejemplo, IZQUIERDA y DERECHA), la corrección es necesaria en el lado más cercano de la ventana. Estas reglas funcionan igual para los ejes horizontal y vertical.

Además, hay que tener en cuenta que el punto de anclaje puede estar en el centro de cualquier lado del objeto. A continuación, en la dirección perpendicular se requiere una sangría desde los bordes de la ventana, igual a la mitad del tamaño del objeto.

Un caso especial es el punto de anclaje en el centro del objeto. Para ello, debe tener siempre un margen de distancia en cualquier dirección, igual a la mitad del tamaño del objeto.

La lógica descrita se implementa en una función especial denominada GetMargins, la cual toma como entradas la esquina y el punto de anclaje seleccionados, así como las dimensiones del objeto (dx y dy). La función devuelve una estructura con 4 campos que contienen los tamaños de las sangrías adicionales que deben apartarse del punto de anclaje en la dirección de los bordes cercano y lejano de la ventana para que el objeto no se pierda de vista. Las sangrías reservan la distancia en función de las dimensiones y la posición relativa del propio objeto.

struct Margins
{
   int nearX// X increment between the object point and the window border adjacent to the corner
   int nearY// Y increment between the object point and the window border adjacent to the corner
   int farX;  // X increment between the object's point and the opposite corner of the window border
   int farY;  // Y increment between the object's point and the opposite corner of the window border
};
   
Margins GetMargins(const ENUM_BASE_CORNER cornerconst ENUM_ANCHOR_POINT anchor,
   int dxint dy)
{
   Margins margins = {}; // zero corrections by default
   ...
   return margins;
}

Para unificar el algoritmo se introducen las siguientes definiciones macro de direcciones (lados):

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

Con su ayuda se definen máscaras de bits (combinaciones) que describen los elementos de las enumeraciones ENUM_BASE_CORNER y ENUM_ANCHOR_POINT.

   const int corner_flags[] = // flags for ENUM_BASE_CORNER elements
   {
      LEFT | UPPER,
      LEFT | LOWER,
      RIGHT | LOWER,
      RIGHT | UPPER
   };
   
   const int anchor_flags[] = // flags for ENUM_ANCHOR_POINT elements
   {
      LEFT | UPPER,
      LEFT,
      LEFT | LOWER,
      LOWER,
      RIGHT | LOWER,
      RIGHT,
      RIGHT | UPPER,
      UPPER,
      CENTER
   };

Cada uno de los arrays, corner_flags y anchor_flags, contiene exactamente tantos elementos como existen en la enumeración correspondiente.

A continuación viene el código de la función principal. En primer lugar, tratemos la opción más sencilla: el punto de anclaje central.

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

Para analizar el resto de situaciones, utilizaremos las máscaras de bits de los arrays anteriores direccionándolos directamente por los valores recibidos corner y anchor.

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

Si la esquina y el punto de anclaje están en el mismo lado horizontal, funcionará la siguiente condición y se ajustará la anchura del objeto en el borde más alejado de la ventana.

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

Si no están en el mismo lado, pueden estar en lados opuestos, o puede darse el caso de que el punto de anclaje esté en medio del lado horizontal (arriba o abajo). La comprobación de un punto de anclaje en el centro se realiza mediante la expresión (anchor_flags[anchor] & (LEFT | RIGHT)) == 0; entonces, la corrección es igual a la mitad de la anchura del objeto.

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

En caso contrario, con la orientación opuesta de la esquina y el punto de anclaje, realizamos un ajuste de la anchura del objeto en el borde cercano de la ventana.

Se realizan comprobaciones similares para el eje 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;
         }
      }

Ahora la función GetMargins está lista, y podemos proceder al código principal del script en la función OnStart. Como antes, determinamos el tamaño de la ventana, calculamos las coordenadas iniciales en el centro, creamos un objeto OBJ_LABEL y lo seleccionamos.

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);
   ...

Para la animación, un bucle infinito proporciona las variables pass (contador de iteraciones) y anchor (el punto de anclaje, que se elegirá periódicamente de forma aleatoria).

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

Pero hay algunos cambios en comparación con ObjectAnchorLabel.mq5.

No generaremos movimientos aleatorios del objeto; en lugar de ello, vamos a establecer una velocidad constante de 5 píxeles en diagonal.

   int px = 5py = 5;

Para almacenar el tamaño de la etiqueta de texto, reservaremos dos nuevas variables.

   int dx = 0dy = 0;

El resultado del recuento de sangrías adicionales se almacenará en una variable m de tipo Margins.

   Margins m = {};

A esto le sigue directamente el bucle de mover y modificar el objeto. En él, cada 75 iteraciones (una iteración de 100 ms, véase más adelante), seleccionamos aleatoriamente un nuevo punto de anclaje, formamos un nuevo texto (el contenido del objeto) a partir de él y esperamos a que los cambios se apliquen al objeto (llamando a ChartRedraw). Esto último es necesario porque el tamaño de la inscripción se ajusta automáticamente al contenido, y el nuevo tamaño es importante para que podamos calcular correctamente las sangrías en la llamada a GetMargins.

Obtenemos las dimensiones utilizando las llamadas ObjectGetInteger con las propiedades OBJPROP_XSIZE y OBJPROP_YSIZE.

   for( ;!IsStopped(); ++pass)
   {
      if(pass % 75 == 0)
      {
         // ENUM_ANCHOR_POINT consists of 9 elements: randomly choose one
         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);
      }
      ...

Una vez que conocemos el punto de anclaje y todas las distancias, movemos el objeto. Si este «choca» contra la pared, cambiamos el sentido del movimiento al opuesto (px a -px o py a -py, según el lado).

      // bounce off window borders, object fully visible
      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;
      }
      
      // calculate the new label position
      x += px;
      y += py;
      ...

Queda por actualizar el estado del propio objeto: mostrar las coordenadas actuales en la etiqueta de texto y asignarlas a las propiedades OBJPROP_XDISTANCE y OBJPROP_YDISTANCE.

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

Después de cambiar el objeto, llamamos a ChartRedraw y esperamos 100 ms para garantizar una animación razonablemente suave.

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

Al final del bucle, volvemos a comprobar el tamaño de la ventana, ya que el usuario puede cambiarlo mientras se ejecuta el script, y también repetimos la petición de tamaño.

      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);
   }

Hemos omitido algunas otras innovaciones del script ObjectSizeLabel.mq5 para que la explicación sea concisa. Quienes lo deseen pueden consultar el código. En concreto, se han utilizado colores distintivos para la inscripción: cada color específico corresponde a su propio punto de anclaje, lo que hace que los puntos de cambio sean más perceptibles. También puede hacer clic en Delete mientras se ejecuta el script: esto eliminará el objeto seleccionado del gráfico y el script finalizará automáticamente.