Mostrando las entradas con la etiqueta Formularios. Mostrar todas las entradas
Mostrando las entradas con la etiqueta Formularios. Mostrar todas las entradas

15 de agosto de 2021

Cuadro de controles de la barra de título en el lado izquierdo

Artículo original: CtrlBox on Left Side
http://sandstorm36.blogspot.com/2018/08/ctrlbox-on-left-side.html
Autor: Jun Tangunan
Traducido por: Google Translate


Dado que algunos utilizan árabe/urdu, que se ocupa de la lectura y la entrada de datos de derecha a izquierda, este truco podría resultarles útil. Que es transponer también la posición del cuadro de controles de la barra de título en el lado izquierdo.

Este truco es realmente bastante simple y requiere solo 3 líneas de códigos que involucran GetWindowLong y SetWindowLong. Acabo de agregar algunos códigos para mostrar cómo se ve. Vea si esto puede resultarle útil.

Salud!

loTest = CREATEOBJECT("Form1")
loTest.SHOW(1)
READ EVENTS

DEFINE CLASS form1 AS FORM
  AUTOCENTER = .T.
  CAPTION = "ControlBox en el lado izquierdo"
  SHOWWINDOW = 2

  ADD OBJECT label1 AS LABEL WITH ;
    TOP = 20,;
    LEFT = 10,;
    FONTSIZE = 16,;
    WIDTH = THISFORM.WIDTH -20,;
    HEIGHT = THISFORM.HEIGHT - 20,;
    WORDWRAP = .T.,;
    CAPTION = "Esto muestra cómo invertir las posiciones de los objetos de la barra "+;
    "de título, como el cuadro de control, el icono y las etiquetas, dejando el interior "+;
    "del formulario en las posiciones normales de izquierda a derecha."

  PROCEDURE LOAD
    DECLARE INTEGER SetWindowLong IN user32 INTEGER HWND, INTEGER nIndex, INTEGER dwNewLong
    DECLARE INTEGER GetWindowLong IN user32 INTEGER HWND, INTEGER nIndex
    SetWindowLong(THISFORM.HWND, -20, BITOR(GetWindowLong(THISFORM.HWND, -16), 0x80000))
  ENDPROC

  PROCEDURE DESTROY
    CLEAR EVENTS
  ENDPROC
ENDDEFINE

25 de junio de 2021

Arrastrar, soltar y restringir

Artículo original: Drag, Drop and Restrict
http://sandstorm36.blogspot.com/2020/06/drag-drop-and-restrict.html
Autor: Jun Tangunan
Traducido por: Luis María Guayán


Acabo de leer un problema dentro de Foxite.com en el que cuando un usuario mueve accidentalmente el mouse más allá de los límites del formulario, la función de arrastrar y soltar falla porque los objetos desaparecen en las áreas más allá del formulario.

La solución a eso es restringir los movimientos de arrastrar y soltar dentro de su formulario, o la dimensión dentro de los objetos en su formulario. Aquí hay dos ejemplos que muestran cómo lograrlo:

Ejemplo 1:

* Restricting drag and drop within the form

Local oForm As Form
oForm = Createobject('TestForm')
oForm.Show(1)
Return

Define Class TestForm As Form
      AutoCenter = .T.
      Width = 900
      Height = 440
      Caption = 'Drag, Drop & Restrict Inside Form'
      Add Object container1 As Mycontainer With Top = 30, Left = 50
Enddefine

Define Class Mycontainer As Container
      Height = 100
      Width = 100
      Procedure MouseMove
      Lparameters nButton, nShift, nXCoord, nYCoord
      If m.nButton = 1 And Between(m.nYCoord,0,Thisform.Height-This.Height) And ;
                  BETWEEN(m.nXCoord,0,Thisform.Width-This.Width)
            This.Move(m.nXCoord, m.nYCoord)
      Endif
      Endproc
Enddefine

Ejemplo 2:

* Restricing within objects on form, in this case above or below the lines
Local oForm As Form
oForm = Createobject('TestForm')
oForm.Show(1)
Return

Define Class TestForm As Form
      AutoCenter = .T.
      Width = 900
      Height = 440
      Caption = 'Drag, Drop & Restrict'
      Add Object Shape1 As shape With Top = 30, Left = 0, Width = 900, height = 1
      Add Object Shape2 As shape With Top = 200, Left = 0, Width = 900, height = 1
      Add Object Command1 As MyButton With Caption='Move Me outside of the lines', Top = 35, Left = 5, width = 200, height = 30
Enddefine

Define Class MyButton As CommandButton
      Procedure MouseMove
            Lparameters nButton, nShift, nXCoord, nYCoord
            If m.nButton = 1 AND BETWEEN(m.nYCoord,30,171)
                  This.Move(m.nXCoord, m.nYCoord)
                  WAIT WINDOW m.nYCoord nowait
            Endif
      Endproc
Enddefine

Por si lo necesitas. Saludos!

8 de mayo de 2021

Lecciones de malabarismo

Con tanto tiempo en casa, tengo que mantenerme ocupado.

Esto es totalmente inútil a menos que quieras aprender a hacer malabares.

Cuando estaba en la escuela, aprendí a hacer malabares, un pariente me enseñó. Aquí están las 3 lecciones para practicar.

Tony Vignone
FL, USA

PUBLIC oForm
oForm = CREATEOBJECT('form1')
oForm.SHOW(1)
DEFINE CLASS form1 AS FORM
  HEIGHT = 369
  WIDTH = 461
  DOCREATE = .T.
  AUTOCENTER = .T.
  CAPTION = "Lecciones de malabarismo"
  ad = 0
  NAME = "form1"

  ADD OBJECT b1 AS SHAPE WITH ;
    TOP = 252, ;
    LEFT = 36, ;
    HEIGHT = 24, ;
    WIDTH = 24, ;
    CURVATURE = 99, ;
    BACKCOLOR = RGB(0,128,255), ;
    NAME = "b1"

  ADD OBJECT b2 AS SHAPE WITH ;
    TOP = 252, ;
    LEFT = 372, ;
    HEIGHT = 24, ;
    WIDTH = 24, ;
    CURVATURE = 99, ;
    BACKCOLOR = RGB(255,0,0), ;
    NAME = "b2"

  ADD OBJECT b3 AS SHAPE WITH ;
    TOP = 252, ;
    LEFT = 408, ;
    HEIGHT = 24, ;
    WIDTH = 24, ;
    CURVATURE = 99, ;
    BACKCOLOR = RGB(128,255,128), ;
    NAME = "b3"

  ADD OBJECT Line1 AS LINE WITH ;
    BORDERWIDTH = 3, ;
    HEIGHT = 0, ;
    LEFT = 12, ;
    TOP = 276, ;
    WIDTH = 444, ;
    NAME = "Line1"

  ADD OBJECT Command1 AS COMMANDBUTTON WITH ;
    TOP = 318, ;
    LEFT = 108, ;
    HEIGHT = 25, ;
    WIDTH = 60, ;
    FONTSIZE = 9, ;
    CAPTION = "Malabar 1", ;
    TABSTOP = .F., ;
    BACKCOLOR = RGB(255,179,179), ;
    NAME = "Command1"

  ADD OBJECT Command2 AS COMMANDBUTTON WITH ;
    TOP = 318, ;
    LEFT = 192, ;
    HEIGHT = 25, ;
    WIDTH = 60, ;
    CAPTION = "Malabar 2", ;
    TABSTOP = .F., ;
    BACKCOLOR = RGB(255,179,179), ;
    NAME = "Command2"

  ADD OBJECT Command3 AS COMMANDBUTTON WITH ;
    TOP = 318, ;
    LEFT = 276, ;
    HEIGHT = 25, ;
    WIDTH = 60, ;
    CAPTION = "Malabar 3", ;
    TABSTOP = .F., ;
    BACKCOLOR = RGB(255,179,179), ;
    NAME = "Command3"

  ADD OBJECT Spinner1 AS SPINNER WITH ;
    ALIGNMENT = 2, ;
    HEIGHT = 25, ;
    KEYBOARDHIGHVALUE = 6, ;
    KEYBOARDLOWVALUE = 0, ;
    LEFT = 420, ;
    SPINNERHIGHVALUE =   6.00, ;
    SPINNERLOWVALUE =   0.00, ;
    TABSTOP = .F., ;
    TOP = 318, ;
    WIDTH = 37, ;
    CONTROLSOURCE = "Thisform.ad", ;
    NAME = "Spinner1"

  ADD OBJECT Label2 AS LABEL WITH ;
    FONTNAME = "Tahoma", ;
    FONTSIZE = 8, ;
    ALIGNMENT = 2, ;
    CAPTION = "Para con X, FIN, Barra Espaciadora", ;
    HEIGHT = 13, ;
    LEFT = 154, ;
    TOP = 348, ;
    WIDTH = 169, ;
    NAME = "Label2"

  ADD OBJECT Label1 AS LABEL WITH ;
    FONTNAME = "Tahoma", ;
    FONTSIZE = 8, ;
    WORDWRAP = .T., ;
    ALIGNMENT = 2, ;
    CAPTION = "Ajustar tiro", ;
    HEIGHT = 31, ;
    LEFT = 372, ;
    TOP = 318, ;
    WIDTH = 40, ;
    NAME = "Label1"

  ADD OBJECT Label3 AS LABEL WITH ;
    FONTSIZE = 8, ;
    WORDWRAP = .T., ;
    ALIGNMENT = 2, ;
    BACKSTYLE = 0, ;
    CAPTION = "Practica en este orden", ;
    HEIGHT = 48, ;
    LEFT = 36, ;
    TOP = 309, ;
    WIDTH = 48, ;
    NAME = "Label3"

  ADD OBJECT Label4 AS LABEL WITH ;
    FONTNAME = "Wingdings 3", ;
    FONTSIZE = 14, ;
    CAPTION = "u", ;
    HEIGHT = 25, ;
    LEFT = 84, ;
    TOP = 321, ;
    WIDTH = 18, ;
    NAME = "Label4"

  ADD OBJECT LblMsg AS LABEL WITH ;
    FONTSIZE = 8, ;
    ALIGNMENT = 2, ;
    BACKSTYLE = 0, ;
    CAPTION = "No mires tus manos. Mira hacia adelante.", ;
    HEIGHT = 16, ;
    LEFT = 96, ;
    TOP = 301, ;
    WIDTH = 252, ;
    NAME = "lblMsg"

  ADD OBJECT Image1 AS IMAGE WITH ;
    HEIGHT = 25, ;
    LEFT = 24, ;
    TOP = 278, ;
    WIDTH = 61, ;
    NAME = "Image1"

  ADD OBJECT Image2 AS IMAGE WITH ;
    HEIGHT = 25, ;
    LEFT = 382, ;
    TOP = 278, ;
    WIDTH = 61, ;
    NAME = "Image2"

  PROCEDURE INIT
    DECLARE Sleep IN kernel32 INTEGER dwMilliseconds
    WITH THISFORM

      PUBLIC nspeed
      nspeed = 100
      PUBLIC x1,x2,x3,y1,y2,y3
      PUBLIC esclist
      PUBLIC i1,i2,i3
      PUBLIC w,b1p,b2p,b3p
      PUBLIC side1,side2,side3,msg1,msg2
      w = 30  &&30*12
      esclist = "6,120,32"
      side1 = "L"
      side2 = "R"
      side3 = "R"
      msg1 = "No mires tus manos. Mira hacia adelante."
      msg2 = "Tira debajo de uno entrante para que no choquen"
      SET CURSOR OFF
      .image1.PICTUREVAL = .handpicL()
      .image2.PICTUREVAL = .handpicR()
    ENDWITH
  ENDPROC

  PROCEDURE DESTROY
    SET CURSOR ON
  ENDPROC

  PROCEDURE Command1.CLICK
    WITH THISFORM
      nspeed = 70
      IF .b1.TOP < 252
        .RESET()
        sleep(1000)
      ELSE
        .RESET()
      ENDIF

      LOCAL i
      x1 = .ad
      FOR i=1 TO 30*8
        .throw1()
        IF INLIST(INKEY(),&esclist) THEN  && END
          .RESET()
          EXIT
        ENDIF
      NEXT
    ENDWITH
  ENDPROC

  PROCEDURE Command2.CLICK
    WITH THISFORM
      nspeed = 50
      IF .b1.TOP < 252
        .RESET()
        sleep(1000)
      ELSE
        .RESET()
      ENDIF

      .lblMsg.CAPTION = msg2
      LOCAL i
      x1 = 0
      b1p = 0
      DO WHILE b1p = 0
        .throw1(0.6)
      ENDDO
      x2 = w-.ad
      FOR i=1 TO 30*8
        .throw2()
        .throw1()
        IF INLIST(INKEY(),&esclist) THEN  && END
          .RESET()
          EXIT
        ENDIF
      NEXT
    ENDWITH
  ENDPROC

  PROCEDURE Command3.CLICK
    WITH THISFORM
      nspeed = 25
      IF .b1.TOP < 252
        .RESET()
        sleep(1000)
      ELSE
        .RESET()
      ENDIF

      .lblMsg.CAPTION = msg2
      LOCAL i
      x1 = 0
      b1p = 0
      DO WHILE b1p = 0
        .throw1(.2)
      ENDDO
      x2 = w
      b1p = 0
      DO WHILE b1p = 0
        .throw2()
        .throw1(.6)
      ENDDO
      x3 = w-.ad
      FOR i=1 TO 30*12
        .throw3()
        .throw2()
        .throw1()
        IF INLIST(INKEY(),&esclist) THEN  && END
          .RESET()
          EXIT
        ENDIF
      NEXT
    ENDWITH
  ENDPROC

  PROCEDURE throw1
    LPARAMETER npart
    npart = EVL(npart,0.2)
    WITH THISFORM
      y1 = MAX(0,(x1-.ad)*(w-x1))
      .b1.LEFT = 36+12*x1
      .b1.TOP = 252-y1
      sleep(nspeed)
      IF x1 = INT(npart*w) THEN
        b1p = 1
      ENDIF
      IF side1 = "L" THEN
        x1 = x1+1
        IF x1 = w THEN
          side1 = "R"
        ENDIF
      ELSE
        x1 = x1-1
        IF x1 = .ad THEN
          side1 = "L"
        ENDIF
      ENDIF
    ENDWITH
  ENDPROC

  PROCEDURE throw2
    WITH THISFORM
      y2 = MAX(0,x2*(w-.ad-x2))
      .b2.LEFT = 36+12*x2
      .b2.TOP = 252-y2
      sleep(nspeed)
      IF x2 = INT(.8*w) THEN
        b2p = 1
      ENDIF
      IF side2 = "L" THEN
        x2 = x2+1
        IF x2 = w-.ad THEN
          side2 = "R"
        ENDIF
      ELSE
        x2 = x2-1
        IF x2 = 0 THEN
          side2 = "L"
        ENDIF
      ENDIF
    ENDWITH
  ENDPROC

  PROCEDURE throw3
    WITH THISFORM
      y3 = MAX(0,x3*(w-.ad-x3))
      .b3.LEFT = 36+12*x3
      .b3.TOP = 252-y3
      sleep(nspeed)
      IF side3 = "L" THEN
        x3 = x3+1
        IF x3 = w-.ad THEN
          side3 = "R"
        ENDIF
      ELSE
        x3 = x3-1
        IF x3 = 0 THEN
          side3 = "L"
        ENDIF
      ENDIF
    ENDWITH
  ENDPROC

  PROCEDURE RESET
    WITH THISFORM
      .b1.LEFT = 26
      .b2.LEFT = 372
      .b3.LEFT = 408
      STORE 252 TO .b1.TOP,.b2.TOP,.b3.TOP
      side1 = "L"
      side2 = "R"
      side3 = "R"
      .lblMsg.CAPTION = msg1
    ENDWITH
  ENDPROC

  PROCEDURE handpicL
    LOCAL un
    *     1...5...10....5...20....5...30....5...40....5...50....5...60....5...70....5...80....5...90....5..100'
    un =    'FFD8FFE000104A46494600010101012C012C0000FFE1009245786966000049492A000800000002000E010200590000002600'
    un = m.un+'000098820200090000008000000000000000566563746F7220696C6C757374726174696F6E206F6620612070616C6D207570'
    un = m.un+'206F75747265616368696E672068616E6420676573747572652C2069736F6C61746564206F6E207768697465206261636B67'
    un = m.un+'726F756E642E0000626C61636B7265640000FFDB0043000A07070807060A0808080B0A0A0B0E18100E0D0D0E1D1516111823'
    un = m.un+'1F2524221F2221262B372F26293429212230413134393B3E3E3E252E4449433C48373D3E3BFFDB0043010A0B0B0E0D0E1C10'
    un = m.un+'101C3B2822283B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B'
    un = m.un+'3B3B3B3B3B3BFFC00011080016003303012200021101031101FFC4001F000001050101010101010000000000000000010203'
    un = m.un+'0405060708090A0BFFC400B5100002010303020403050504040000017D010203000411051221314106135161072271143281'
    un = m.un+'91A1082342B1C11552D1F02433627282090A161718191A25262728292A3435363738393A434445464748494A535455565758'
    un = m.un+'595A636465666768696A737475767778797A838485868788898A92939495969798999AA2A3A4A5A6A7A8A9AAB2B3B4B5B6B7'
    un = m.un+'B8B9BAC2C3C4C5C6C7C8C9CAD2D3D4D5D6D7D8D9DAE1E2E3E4E5E6E7E8E9EAF1F2F3F4F5F6F7F8F9FAFFC4001F0100030101'
    un = m.un+'010101010101010000000000000102030405060708090A0BFFC400B511000201020404030407050404000102770001020311'
    un = m.un+'04052131061241510761711322328108144291A1B1C109233352F0156272D10A162434E125F11718191A262728292A353637'
    un = m.un+'38393A434445464748494A535455565758595A636465666768696A737475767778797A82838485868788898A929394959697'
    un = m.un+'98999AA2A3A4A5A6A7A8A9AAB2B3B4B5B6B7B8B9BAC2C3C4C5C6C7C8C9CAD2D3D4D5D6D7D8D9DAE2E3E4E5E6E7E8E9EAF2F3'
    un = m.un+'F4F5F6F7F8F9FAFFDA000C03010002110311003F00F63620726B32E35491837D9235655FF968E4E0FD00EB59C902A01E6C6A'
    un = m.un+'7E6E371CFE95688F9368C01818F415C92AEDAD343A634527A9565D66770639E7F2D8F4585305BD81C935368B61702F5AF648'
    un = m.un+'7ECE850AED6FBD2127AB7FF5F9AA92DAEC6DCF18652796001AB114925B465ADEEA555FEEB8DC31F422A233D6F3349434B40E'
    un = m.un+'887D296B9F6D52F72163916427D23E9FF8F556FB7EA1792BDB4734924A07DD83680BFEF3F6AE9F6CBA1CFEC5EED9D4F14561'
    un = m.un+'C3A46ADE52EED6E543DD5630C07E2DC9FC68AD2E4D97728CD6BA9B49E53B5B1FF8137F854C965A8C49BCB5BE3D0337F85145'
    un = m.un+'70F2A3AEECAD26A72C0712C487D76B1A92D2E66D4D8C76E91A05EA64E7F414515296A53DAE599344BA9CED9B50DB1F758A32'
    un = m.un+'33ED9CD6CD9D9C1636EB05BC61117D3BFB9F7A28AEBA492392726F727A28A2B5323FFFD9'

    RETURN STRCONV(un,16)
  ENDPROC

  PROCEDURE handpicR
    LOCAL un
    *     1...5...10....5...20....5...30....5...40....5...50....5...60....5...70....5...80....5...90....5..100'
    un =    'FFD8FFE000104A46494600010101012C012C0000FFE1009245786966000049492A000800000002000E010200590000002600'
    un = m.un+'000098820200090000008000000000000000566563746F7220696C6C757374726174696F6E206F6620612070616C6D207570'
    un = m.un+'206F75747265616368696E672068616E6420676573747572652C2069736F6C61746564206F6E207768697465206261636B67'
    un = m.un+'726F756E642E0073626C61636B72656400FFFFDB0043000A07070807060A0808080B0A0A0B0E18100E0D0D0E1D1516111823'
    un = m.un+'1F2524221F2221262B372F26293429212230413134393B3E3E3E252E4449433C48373D3E3BFFDB0043010A0B0B0E0D0E1C10'
    un = m.un+'101C3B2822283B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B'
    un = m.un+'3B3B3B3B3B3BFFC00011080016003303012200021101031101FFC4001F000001050101010101010000000000000000010203'
    un = m.un+'0405060708090A0BFFC400B5100002010303020403050504040000017D010203000411051221314106135161072271143281'
    un = m.un+'91A1082342B1C11552D1F02433627282090A161718191A25262728292A3435363738393A434445464748494A535455565758'
    un = m.un+'595A636465666768696A737475767778797A838485868788898A92939495969798999AA2A3A4A5A6A7A8A9AAB2B3B4B5B6B7'
    un = m.un+'B8B9BAC2C3C4C5C6C7C8C9CAD2D3D4D5D6D7D8D9DAE1E2E3E4E5E6E7E8E9EAF1F2F3F4F5F6F7F8F9FAFFC4001F0100030101'
    un = m.un+'010101010101010000000000000102030405060708090A0BFFC400B511000201020404030407050404000102770001020311'
    un = m.un+'04052131061241510761711322328108144291A1B1C109233352F0156272D10A162434E125F11718191A262728292A353637'
    un = m.un+'38393A434445464748494A535455565758595A636465666768696A737475767778797A82838485868788898A929394959697'
    un = m.un+'98999AA2A3A4A5A6A7A8A9AAB2B3B4B5B6B7B8B9BAC2C3C4C5C6C7C8C9CAD2D3D4D5D6D7D8D9DAE2E3E4E5E6E7E8E9EAF2F3'
    un = m.un+'F4F5F6F7F8F9FAFFDA000C03010002110311003F00F66A2B99B84D5749324D35E5C4B175F3906E03FDE43D3EA38A747ABEA0'
    un = m.un+'515CC91BAB747550C3F422B275527666DEC9B574CD7D5ED25BED364821601DB070C786C1CE0FB1AC186F26D33F76E25B361F'
    un = m.un+'76265CA37D074FC8D5B37D753E50DE155C7263400FE7CD537815A4DA8AF2B9FBC5F93F99AC2A544DDE3B9B53834AD2D8D0B6'
    un = m.un+'D5EF9CEE78A1914765CA1FE6456B5B5D457516F8C9F4653D54FA115896F118536923E83B524D1C6D20CA2973DFA1E9EB442B'
    un = m.un+'496FA8A74A2F63A2A2B94366E4E4A7FE3D455FD63C89F61E6755D6B1AE7C3ABF6933D85C1B466E5902E509FA64628A2B7945'
    un = m.un+'495998464E2F420B9B4BEB084CEF24132AF5F94AB7F5ACF8F5A131C470F3DF71C514571D48A8CAC8EDA6F9A3765D861D42F9'
    un = m.un+'498FC841EEE7FC2A096C75281B7E6DB3D321DBFC28A29F246D71733BD8B51E9DAC4881C4D6A011D32DFE1451455AA71239E4'
    un = m.un+'7FFFD9'

    RETURN STRCONV(un,16)
  ENDPROC

ENDDEFINE

Publicado en el foro de Foxite por Tony Vignone: FOR FUN ONLY

27 de abril de 2021

Gráficos de barras simples a través de Grid y BackStyle_Access

Artículo original: Simple Bar Graphs via Grid and Backstyle_Access
http://www.sweetpotatosoftware.com/spsblog/2005/12/10/SimpleBarGraphsViaGridAndBackstyleAccess.aspx
Autor: Craig Boyd
Traducido por: Luis María Guayán


Me llegó un correo electrónico

Sue Cunningham me envió un correo electrónico y me contó sobre un uso muy bueno que había encontrado para la técnica BackStyle_Access que había explicado en una publicación de mi blog. Si no tienes tiempo para leer esa entrada de blog, aquí tienes la versión resumida: Al usar el método BackStyle_Access de un control contenido en la columna de un Grid, se puede hacer casi cualquier cosa que se desee, ya que ese método se activará cuando se pinte cada fila visible en el Grid. Realmente ni siquiera hay una necesidad de todos esos métodos DynamicLoQueSea que el Team FoxPro de Microsoft agregó al objeto Columna, cuando usas BackStyle_Access. También permite realizar operaciones mucho más complejas y efectos visuales dentro de las filas del Grid. De todos modos, Sue me envió un correo electrónico y me dijo que había ideado una forma relativamente simple para producir gráficos de barras. Algo que se parece mucho a las barras de degradado recientes sobre las que Calvin Hsia escribió en su blog, pero mucho más simple, sin necesidad de GDI+.

Yo decidí probarlo

Sue no me proporcionó el código para su solución, pero según la explicación que me había dado en su correo electrónico, sentí que tenía la idea básica y decidí intentarlo y ver qué podía crear. El resultado (ver la imagen a continuación) fue tan agradable como fácil. Sue también me dijo que estaba encontrando algunos resultados impredecibles al colocar el Grid dentro de un control contenedor, por lo que también produje un ejemplo (Formulario Example2 que se puede descargar al final) que también usa esta técnica para mostrar gráficos en dos páginas separadas de un marco de página. No encontré ninguno de los resultados inconsistentes (usando VFP 9.0 SP1) que comentó Sue, así que no estoy seguro de qué le está sucediendo a ella. La técnica parece funcionar perfectamente (Nota: las barras se truncarán y/o tendrán problemas de pintura si permito que el Grid muestre ambos paneles al mismo tiempo y el panel izquierdo es un Change Panel. El Change Panel es el único lugar que vi este comportamiento no deseado, aparte de eso, todo está bien).

Todo esta involucrado

Tengo una subclase Grid que contiene dos columnas. La primer columna se usa para la descripción de lo que sea el elemento que estoy graficando ... puedes pensar en ello como la etiqueta del eje, supongo. La segunda columna contiene una subclase Container bastante simple que contiene un Shape y una subclase Label. el control Shape se usa para producir las barras y el control Label se usa para mostrar el valor que fue graficado. La magia de todo esto está en el método BackStyle_Access del control Container (que está en la columna2). Aquí está el código actual que es bastante pequeño ...

LOCAL lnTotalTicks, lnValue, lnWidth
lnTotalTicks = (this.Parent.Parent.MaxValue - This.Parent.Parent.MinValue)
lnValue = EVALUATE(this.Parent.Parent.recordsource + ".value")
lnWidth = (This.Parent.Width - 35) * (lnValue/lnTotalTicks) && - 35 to leave room for the value caption
This.cshape1.Width = lnWidth
This.clabel1.Caption = TRANSFORM(lnValue)
This.cLabel1.Left = lnWidth + 5 && leave a five pixel margin
RETURN THIS.BackStyle

Eso es básicamente todo lo que hay que hacer. Las barras serán del color del Shape en el contenedor. Sin duda, podría mejorar lo que estoy mostrando aquí agregando una propiedad de color de barra para el Grid o tal vez mostrando las barras en diferentes filas y en diferentes colores basado en algunos criterios, o llamando a un método del Grid desde el método BackStyle_Access del contenedor para que el código anterior no esté tan oculto. Solo estoy mostrando lo básico aquí, esto ciertamente no es un producto terminado con todo el cotillón posible. Además, a medida que observa los ejemplos, puede notar que hay propiedades MinValue y MaxValue que se han agregado a la subclase del Grid. Estas son para que no tengamos que graficar siempre en 0-100 o lo que sea (ver el código arriba). Los valores graficados pueden ser cualquier cosa, como 1000-10000, 250-350, etc.

Finalmente, para ver los cursores con los que se ejecutan los gráficos de barra del Grid, mire en los métodos Load de los formularios de ejemplo donde encontrará algo como el siguiente código ...

LOCAL lnCounter
CREATE CURSOR crsValues (Descript c(25), Value I)
=RAND(-1)
FOR lnCounter = 1 TO 100
 INSERT INTO crsValues (Descript, Value) VALUES ("Item " + TRANSFORM(lnCounter), INT(100 * RAND( ) + 1))
ENDFOR
GO TOP IN crsValues

Como puede ver, el cursor que se necesita para esto es extremadamente simple, de hecho solo dos campos, uno para la descripción y el otro para los valores. En cualquier caso, esto solo muestra una vez más el valor de la clase Grid de Visual FoxPro cuando se acopla con el método BackStyle_Access. Con muy pocas líneas de código podemos producir una clase bastante útil para mostrar datos visualmente a nuestros usuarios. Gracias por la sugerencia enviada por correo electrónico Sue!

Código fuente y captura de pantalla

Cuando descargue el archivo zip, extraiga el contenido. Abra el proyecto en Visual FoxPro y ejecute los formularios Example y Example2.

Descargue el código fuente de Grid Graph con los ejemplos desde el siguiente enlace: gridgraph.zip (23 KB aprox.)


17 de abril de 2021

Breve Reseña Sobre Sesiones de Datos Públicas Y Privadas

Si bién antiguamente usábamos cualquier tabla desde cualquier programa (lo que luego se denominó "Sesión de datos Pública") ahora es más frecuente, aunque no imprescindible, usar "Sesión de datos Privada", que permite abrir otra copia las mismas tablas como si se tratara de otra sesión de VFP (como cuando abrís VFP 2 veces o más).

Qué tipo de Sesión de Datos quieras usar dependerá de cómo pretendas encarar tu proyecto. Voy a intentar hacer un breve resúmen de ventajas y desventajas de cada caso.

1) Ventajas de la Sesión de datos Pública

  • Las tablas son visibles desde todos los módulos del sistema (PRG, SCX, VCX, MNX, FRX, etc.)
  • El hecho de que se las tablas se abran una sola vez implica un cierto ahorro de memoria RAM, ya que por cada tabla abierta se guardan en memoria datos como: Indice actual, tipo de ordenamiento del índice, puntero de registro, etc.
  • La sesión, al ser Pública, implica usar sólo una copia en memoria RAM de los seteos (comandos SET ON/OFF) que son afectados por las sesiones de datos (en la ayuda del comando SET dice si es afectado o no por la sesión de datos)
  • Es compatible con el modo de funcionamiento de FoxPro 2.X, lo que permitiría mantener código heredado de esa versión

2) Desventajas de la Sesión de datos Pública:

  • En cada programa tenés que hacer tu propio manejo de los punteros de las tablas para que cuando abrís un programa nuevo o cambiás a otro, cada uno restaure el puntero del registro al lugar correspondiente.
  • Si desde un procedimiento llamás a otro procedimiento que cambia el puntero de una tabla o el orden de un índice que estabas usando y no lo restaurás, es muy probable que le ocaciones problemas al procedimiento original, pudiéndose llegar a cancelar el programa
  • No es recomendable para objetos COM
  • Los comandos SET ON/OFF afectan a todos los módulos por igual
  • Si la misma tabla se abre varias veces, se debe hacer con alias distintos (USE MiTabla AGAIN SHARED ALIAS ...)
  • Se dificulta hacer código genérico para manejar tablas, ya que hay que agregar manejo de punteros, índices, etc.
  • El bloqueo / desbloqueo de registros (LOCK / RLOCK / FLOCK) es más complejo de manejar, ya que si un módulo bloquea uno o más registros, otro módulo puede desbloquear esos mismos registros pudiendo provocar un problema de seguridad y/o coherencia de datos entre módulos

3) Ventajas de la Sesión de datos Privada (Objeto Session / Form / Reporte):

  • Varios comandos SET ON/OFF son dependientes de la sesión en la que se los usa (Ej: SET DELETED),lo que permite tener distintos valores entre sesiones de datos privadas (cada sesión puede tener un valor distinto para el mismo comando, sin afectar a las demás)
  • Una misma tabla se puede abrir con el mismo alias en distintas sesiones privadas
  • Si la misma tabla se abre en distintas sesiones, cada sesión mantiene automáticamente el puntero de registro de las tablas y el orden de los índices
  • Es más fácil hacer código genérico para manejar tablas, ya que el mantenimiento de punteros de registro e índices es automático
  • Es recomendable para hacer objetos COM
  • El bloqueo / desbloqueo de registros (LOCK / RLOCK / FLOCK) es más fácil de manejar, ya que si una sesión bloquea uno o más registros de una tabla, sólo esa sesión podrá desbloquear esos mismos registros, lo que beneficia a la seguridad y/o coherencia de datos entre módulos
  • Una sesión de datos Privada se comporta comosi fuera una nueva Sesión de VFP (como abrir 2 ó mas veces VFP)

4) Desventajas de la Sesión de datos Privada (Objeto Session / Form / Reporte):

  • El mantenimiento independiente de los punteros de registro, índices, valores de los comandos SET ON/OFF, etc. por cada sesión implica un mayor uso de memoria RAM por cada una de las tablas abiertas y de los comandos SET ON/OFF. Ej: Si se usa una tabla con un indice en 3 sesiones privadas distintas, en realidad es como si se estuvieran usando 6 archivos distintos.
  • El código heredado de versiones de FoxPro 2.X no funcionará del modo esperado, ya que no estaba preparado para este esquema de trabajo por Sesiones

Seguramente hay más cosas para agregar en cada caso, pero esto es para dar una idea.

Saludos

Fernando D. Bozzo

19 de enero de 2021

Aplicaciones de terceros desde nuestros formularios

Artículo original: 3rd party apps from within our forms
https://sandstorm36.blogspot.com/2014/05/3rd-party-apps-from-within-our-forms.html
Autor: Jun Tangunan
Traducido por: Luis María Guayán


Ahora es la 1:30 AM y todavía no puedo volver a dormir, así que hagamos que mi tiempo sea un poco útil. Inspirado por lo que Bernard Bout ha mostrado AQUI, hoy decidí ver de que forma se puede haceruna instancia de Excel. Por favor, lea en enlace anterior antes de proceder a continuar como voy a tratar de separar las cosas para nuestra mejor comprensión.

Las herramientas más básicas del oficio

  • un Shape en nuestro formulario
  • SetParent
  • WinExec
  • FindWindow
  • SetWindowPos

Bernard nos dió un gran punto de partida porque los ingredientes básicos ya están ahí. Y estoy de acuerdo y aprecio que Bernard no haya mostrado todos los trucos porque si los ha hecho, no trataré de entender algunos de ellos y simplemente usaré el truco a ciegas; y no los comprenderé mejor.

Comencemos la deconstrucción y reconstrucción:

¿WinExec es la única forma?

No. Está ya que es uno de los comandos más simples para abrir una aplicación de terceros. Pero hay alternativas como RUN, ShellExecute(), Scripting y algunas más.

Me he dado cuenta de que el objetivo principal y el primer paso es abrir el archivo o la aplicación de cualquiera de las formas posibles y, dado que mi objetivo aquí es Excel, me gusta la automatización cuando se trata de Excel, entonces es la automatización.

¿Donde está la ventana?

El segundo paso después de abrir la aplicación de terceros es controlar la ventana de esa aplicación tratando de encontrarla. Y eso se puede hacer a través de Winapi con FindWindow() de esta manera:

nHwnd = FindWindow(NULL, "Untitled - Notepad")

Donde nHwnd es el identificador de ventana de la aplicación de terceros que queremos. Lo anterior dice, en términos sencillos, busque una instancia de Bloc de Notas recién abierta y sin guardar entre las ventanas abiertas y obtenga el identificador de su ventana para que podamos trabajar más en ella. Ese título es lo que verá como título cuando abra un Bloc de Notas solo.

Si VFP no puede encontrar el identificador de la ventana, nHwnd devolverá 0.

Intente con un formato de archivo xlsx

Como estaba haciendo la automatización, el primer intento se realiza en un archivo xlsx:

Local loExcel As excel.Application, lcFile
loExcel = Createobject('excel.application')
lcFile = Getfile('xls,xlsx')
If !Empty(m.lcFile)
      loExcel.Workbooks.Open(m.lcFile)
      * Get a handle on its Window
      nHwnd = FindWindow('XLMain',Alltrim(Justfname(m.lcFile))+' - Microsoft Excel')
Endif

¡Y falla, no pasó nada! Tarde me doy cuenta de que a pesar de lo que se muestra en la pantalla en la barra de título de Excel 2007, internamente todavía lo está haciendo de la manera anterior como esta:

nHwnd = FindWindow('XLMain','Microsoft Excel - '+Alltrim(Justfname(m.lcFile)))

No hace falta decir que lo anterior funciona. ¡Hasta aquí todo bien!

Intente con un formato de archivo xls

Luego intenté abrir un formato de archivo xls y ¡vuelve a fallar! ¡¡¡Maldito!!! Recordé que cuando abres un archivo xls en Excel 2007, se agregará un título adicional de [Modo de compatibilidad]. Esa es una forma de recordarnos visualmente que actualmente estamos trabajando en un formato de archivo antiguo. Hmmm ... pedazo de alcornoque, lo haré así entonces:

* Attempt to open without that compatibility mode caption
nHwnd = FindWindow(Null, "Microsoft Excel - "+Alltrim(Justfname(m.lcFile)))
If m.nHwnd = 0
      * Failed, so attempt to open with that added compatibility mode caption
      nHwnd = FindWindow('XLMain', 'Microsoft Excel - +Alltrim(Justfname(m.lcFile))+' [Compatibility Mode]')
Endif

Y no se abre correctamente. Quiero decir, se abre pero se abre fuera de mi aplicación por sí solo. ¡¡¡Maldito sea !!!

Perdí mi tiempo tratando de encontrar la combinación correcta en el título interno de la barra de título de Excel aplicando los casos reales de nombre de archivo usando FSO ... todavía no tuve suerte (me di cuenta al final a través de repetidas pruebas, aunque ese caso de caracteres como adecuado, superior e inferior no lo afecte), mezclando y reubicando las palabras en el pie de foto ... de nuevo no tuve suerte ... y estaba a punto de rendirme porque ya he perdido casi 2 horas solo para ese estúpido título interno (hey yo estaba frustrado, ¡LOL!) y estba a punto de archivar todo el proyecto cuando se me ocurrió una idea. ¡¡¡¡Diablos!!!! Estoy haciendo automatización, entonces, ¿qué me impide hacer esto?

nHwnd = FindWindow('XLMain', loExcel.Caption)

Y listo !!!! ¡Una forma muy flexible de asegurarse de encontrar el título correcto de un archivo de Excel abierto sin importar si está en modo de compatibilidad o no, o en cualquier versión de Excel que esté usando! ¡Excelente! Por supuesto, dicho enfoque no se limita a Excel.

Shape de mi corazón

Ahora vamos a la otra parte. Lo que también he notado es que el truco usa un Shape. Y cuando leí y vi el truco por primera vez, mi presunción es que Excel o cualquier aplicación de terceros aparece mágicamente en dicho Shape transformando dicho Shape en esa aplicación de terceros. Pero como ahora estoy tratando de separar las cosas, me doy cuenta de que con o sin ese Shape, podemos abrir esas aplicaciones de terceros dentro de nuestra aplicación.

Entonces, ¿para qué es ese Shape? Dicho Shape invisible/visible (su elección) en el formulario existe solo por una razón. Eso no es para mostrar la aplicación de terceros, sino para servir como una manera fácil de establecer las coordenadas de esas aplicaciones de terceros desde nuestro formulario, ya que es más fácil cambiar el tamaño de un Shape e indicar a dicha aplicación de terceros que "siga" las coordenadas de ese Shape, que hacerlo por código, de forma repetida y a prueba y error.

Cree un Shape, dimensione y colóquelo en el formulario a su gusto, escóndelo si lo desea e indique a la aplicación de terceros que siga sus coordenadas. Muy ingenioso por aquellos que originalmente pensaron en la idea.

¡Excel no se puede hacer clic, no se puede editar y está muy loco!

Para simplificar las cosas, me doy cuenta de que con VFP, aunque "nosotros" podemos ver los objetos, internamente no puede ser visto por VFP. Al igual que cuando agregamos mediante códigos algunos objetos en un Grid, tenemos que hacerlo Visible, de lo contrario, puede verlos pero no puede funcionar correctamente en él. Entonces:

loExcel.Visible = .T.

Entonces, si desea que esto se destaque dentro de nuestro formulario solo con fines de visualización pura, establezca la propiedad Visible en .F.

loExcel.Visible = .F.

Y eso es todo. Todo lo que el usuario puede hacer es desplazarse hacia abajo y hacia arriba para ver su contenido. :)

¿Que hay en el menu?

Pero una vez que haya hecho visible Excel, entonces todo el archivo de Excel estará dentro de nuestro formulario con todo su esplendor como cinta, barra de fórmulas, pestañas de la hoja de trabajo, etc. Bueno, algunos de ustedes pueden quererlo de esa manera, pero yo no. Así que tengo que esconderlos. Simplemente verifique los códigos más adelante para saber cómo hacerlo.

¡Dimensióname!

No es sorprendente que el cambio de tamaño del formulario deje la aplicación de terceros en sus coordenadas originales. Pero esto es fácil, revisa los códigos.

Hacer zoom, guardar, detectar y algo más

Solo revisa los códigos ... Estoy empezando a tener sueño, finalmente...

Resumen:

Hay dos o más cosas que estoy buscando pero que todavía no he podido encontrar una solución y que tampoco me siento cómodo dejando esos asuntos sin resolver. Y lo básico de esos deseos son:

  • Ocultar la barra de título de Excel
  • No permitir arrastrar y soltar

Incluso jugar con uFlags de SetWindowPos no me dio los resultados esperados. Sin embargo, pude utilizar una buen Flag cuando intentamos abrir un nuevo archivo de Excel para que Excel no destruya lentamente sus objetos frente a nuestros ojos cuando lo cerramos.

Seguiré jugando con esto y si encuentro formas, actualizaré esto. O dado que el plan, como de costumbre, es publicar este foro interno de Foxite, que se encuentra entre los foros de desarrolladores más amigables que he visto, con suerte alguien que haya jugado con esto antes que yo (lo siento, siempre llego tarde) pueda compartir con nosotros cómo solucionar esos problemas; luego editaré esta publicación para incluir sus códigos y al colaborador.

El día siguiente:

¡Encontré el eslabón perdido, LOL! El truco para ocultar la barra de título de Excel es, en lugar de buscar propiedades ocultas de Excel para ocultarlas, manipularlo directamente usando estas dos WinAPI, es decir, GetWindowLong y SetWindowLong. Esos son posibles porque ya tenemos el Handle de la ventana. ¡Compruebe en los códigos a continuación cómo se hace!

Códigos:

loTest = Createobject("Form1")
loTest.Show(1)

Define Class Form1 As Form
      AutoCenter= .T.
      Height = 493
      Width = 955
      Caption = 'Excel within our Form'
      _nHwnd = .F.
      _oExcel = .F.

      Add Object Shape1 As Shape With ;
            Top = 36, Left = 6, Height= 445, Width = 936,;
            BackColor = Rgb(255,255,255), BorderColor = Rgb(0,128,192)

      Add Object label1 As Label With ;
            Top = 15, Left = 6, Caption = 'Preview', FontBold = .T.,;
            FontName = 'Calibri', FontSize = 12, AutoSize = .T.

      Add Object label2 As Label With ;
            Top = 12, Left = 836, Caption = 'Zoom', FontBold = .T.,;
            FontName = 'Calibri', FontSize = 12, AutoSize = .T.,;
            Anchor = 9

      Add Object cmdOpen As CommandButton With ;
            Top = 8, Left = 312, Caption = '\<Open', Width = 84, Height = 24

      Add Object cmdSave As CommandButton With ;
            Top = 8, Left = 399, Caption = '\<Save', Width = 84, Height = 24

      Add Object cmdClose As CommandButton With ;
            Top = 8, Left = 486, Caption = '\<Close', Width = 84, Height = 24

      Add Object chkShowTabs As Checkbox With ;
            Top = 12, Left = 732, Caption = 'Show \<Tabs', AutoSize = .T.,;
            Anchor = 9

      Add Object SpinZoom As Spinner With ;
            Top = 10, Left = 882, KeyboardLowValue = 10, SpinnerLowValue = 10,;
            Value = 100, Anchor = 9, Width = 60

      Procedure Load
            Declare Integer SetParent In user32;
                  INTEGER hWndChild,;
                  INTEGER hWndNewParent

            Declare Integer FindWindow In user32;
                  STRING lpClassName, String lpWindowName

            Declare Integer SetWindowPos In user32;
                  INTEGER HWnd,;
                  INTEGER hWndInsertAfter,;
                  INTEGER x,;
                  INTEGER Y,;
                  INTEGER cx,;
                  INTEGER cy,;
                  INTEGER uFlags

            Declare Integer GetWindowLong In User32;
                  Integer HWnd, Integer nIndex

            Declare Integer SetWindowLong In user32 ;
                  Integer HWnd,;
                  INTEGER nIndex,;
                  Integer dwNewLong
      Endproc

      Procedure Resize
            Thisform._SetCoord()
      Endproc

      Procedure Destroy
            If Type('thisform._oexcel') = 'O'
                  This._Clear()
            Endif
      Endproc

      Procedure cmdOpen.Click
            If Type('thisform._oexcel') = 'O'
                  Thisform._Clear()
            Endif
            Thisform._linkapp()
      Endproc

      Procedure cmdSave.Click
            If Type('thisform._oexcel') = 'O'
                  Thisform._oExcel.activeworkbook.Save()
                  Messagebox('Changes made are saved!',64,'Save')
            Else
                  Messagebox('Nothing to save yet!',64,'Opppppssss!')
            Endif
      Endproc

      Procedure cmdClose.Click
            If Type('thisform._oexcel') = 'O'
                  Thisform._Clear()
            Endif
      Endproc

      Procedure SpinZoom.InteractiveChange
            Thisform._oExcel.ActiveWindow.Zoom=Thisform.SpinZoom.Value
      Endproc

      Procedure chkShowTabs.Click
            Thisform._oExcel.ActiveWindow.DisplayWorkbookTabs=This.Value
      Endproc

      Procedure _Clear
            With This
                  With  .Shape1
                        * Show shape
                        .Visible = .T.
                        * Hide window via uFlags
                        SetWindowPos(This._nHwnd, 1, .Left, .Top, .Width, .Height,0x0080)
                  Endwith

                  With ._oExcel
                        * Restore those we have hidden
                        .DisplayFormulaBar = .T.
                        .DisplayStatusBar = .T.
                        .ActiveWindow.DisplayWorkbookTabs=.T.
                        .ActiveWindow.DisplayHeadings=.T.

                        .activeworkbook.Close()
                        .Visible = .F.
                        .Quit
                  Endwith
                  ._oExcel = .F.
            Endwith
      Endproc

      Procedure _HideRibbon
            Local loRibbon
            loRibbon =This._oExcel.CommandBars.Item("Ribbon")
            If m.loRibbon.Height > 0
                  This._oExcel.ExecuteExcel4Macro('Show.Toolbar("Ribbon",False)')
            Endif
      Endproc

      Procedure _linkapp
            Local loExcel As excel.Application, lcFile
            loExcel = Createobject('excel.application')
            lcFile = Getfile('xls,xlsx')
            If !Empty(m.lcFile)
                  loExcel.Workbooks.Open(m.lcFile)

                  * This is so we can tap into it on other methods/events
                  This._oExcel = loExcel

                  With loExcel
                        .Visible = .T.
                        .DisplayAlerts = .F.
                        .Application.ShowWindowsInTaskbar=.F.

                        .DisplayFormulaBar = .F.
                        .DisplayDocumentActionTaskPane=.F.
                        .DisplayStatusBar = .F.

                        * Ensure scroll bars are shown
                        .ActiveWindow.DisplayVerticalScrollBar=.T.
                        .ActiveWindow.DisplayHorizontalScrollBar=.T.

                        * Hide Workbook Tabs
                        .ActiveWindow.DisplayWorkbookTabs=Thisform.chkShowTabs.Value

                        .ActiveWindow.DisplayHeadings=.F.
                        .ActiveWindow.WindowState = -4137  && xlMaximized
                        .ActiveWindow.Zoom=Thisform.SpinZoom.Value

                        * Get a handle on Window
                        nHwnd = FindWindow('XLMain', .Caption)
                  Endwith

                  * Add this so we can work on other methods
                  This._nHwnd = m.nHwnd

                  * Hide Ribbon
                  Thisform._HideRibbon()


                  * Hide the title bar, disallow drag and drop of the excel window
                  Local lnStyle

                  * Get the current style of the window
                  lnStyle = GetWindowLong(nHwnd, -6)

                  * Set the new style for the window
                  SetWindowLong(nHwnd, -16, Bitxor(lnStyle, 0x00400000))

                  * force it inside our form
                  SetParent(nHwnd,Thisform.HWnd)

                  * Size it
                  Thisform._SetCoord()

                  * Hide shape
                  Thisform.Shape1.Visible = .F.
            Endif
      Endproc

      Procedure _SetCoord
            * size it based on Invisible shape
            With This.Shape1
                  SetWindowPos(This._nHwnd, 0, .Left, .Top, .Width, .Height, 2)
            Endwith
      Endproc

Enddefine

Palabras de despedida:

En primer lugar, muchas gracias a Bernard Bout, cuya publicación me sirve de inspiración para este enfoque. Un agradecimiento especial a Yousfi Benameur quien nos compartió también antes de los códigos para ocultar la cinta de Excel en Office 2007.

Siempre que estoy haciendo este estilo de redacción, estoy apuntando a que los lectores que son nuevos en esto intenten comprender el propósito de cada paso / objeto / código. ¿Cuál es el propósito de WinExec aquí, por qué necesitamos conocer el título, cuál es el propósito de la forma en el formulario, etc.?

Otra es que mis lectores provienen de diferentes partes del mundo donde el inglés no es el idioma nativo y supongo que mi enfoque de redacción es una de las razones por las que mi Blog todavía recibe nuevas páginas vistas de vez en cuando. Aunque espero no aburrirte de esta manera. :)

Hora de dormir....


7 de enero de 2021

Guía de uso del Control Ribbon similar a Microsoft Office 365

Traducción libre de la documentación del Control Ribbon similar a Microsoft Office 365 de Doug Hennig.

El proyecto proporciona un Control Ribbon similar al de Microsoft Office 365 para ser usado en formularios VFP

El código fuente del proyecto y la documentación en inglés se encuentra en: https://github.com/DougHennig/Ribbon

Nota: la traducción se hizo con la documentación al día 06 de enero de 2021.


Usando el Control Ribbon

Para agregar un control ribbon a un formulario, suelte una instancia de la clase SFRibbon de SFRibbon.vcx en el formulario y luego escriba el código en algún método del formulario o quizás en el método Init del objeto del ribbon, para agregar pestañas, secciones y botones; consulte la sección "Componentes" para obtener más detalles.

Para implementar el control ribbon en su aplicación, agregue lo siguiente al proyecto:

  • SFRibbon.vcx
  • SFGDIMeasureString.prg
  • SFRibbonDown.png
  • SFRibbonRight.png
  • RibbonThemes.xml

Además, incluya System.app con los archivos instalados de su aplicación.

Componentes

El control ribbon consta de varios componentes, todos los cuales están definidos en la biblioteca de clases visuales SFRibbon.vcx:

  • Una pestaña (la clase SFRibbonTab) es un elemento del menú en la parte superior, como "Home" y "Send/Receive" de la imagen de arriba. Para agregar una pestaña al ribbon, llame al método AddTab, pasando opcionalmente el nombre de la pestaña (si no se pasa, AddTab asigna un nombre único). Establezca la propiedad "Caption" del objeto Tab devuelto por AddTab.

        loTab = Thisform.oRibbon.AddTab('Home')
        loTab.Caption = 'Home'
    

    Las pestañas tienen una propiedad "Selected" que es .T. para la pestaña seleccionada y .F. para todas las demás pestañas. Establezca esta propiedad en .T. para seleccionar una pestaña mediante programación:

        Thisform.oRibbon.Home.Selected = .T.
    
  • Cada pestaña tiene una barra de herramientas (una instancia de la clase SFRibbonToolbar) debajo. No necesita preocuparse por las barras de herramientas, ya que están allí solo para su comodidad: cuando se selecciona una pestaña, su barra de herramientas se hace visible y las barras de herramientas de las otras pestañas están ocultas.

  • Una sección (la clase SFRibbonToolbarSection) es un conjunto de botones con una barra separadora a la derecha y un título en la parte inferior. Puede haber varias secciones en una barra de herramientas. Para agregar una sección, llame al método AddSection de una pestaña (que en realidad agrega una sección a la barra de herramientas de una pestaña), pasando opcionalmente el nombre de la sección (si no se pasa, AddSection asigna un nombre único). Establezca la propiedad Caption del objeto de sección devuelto por AddSection

        loSection = loTab.AddSection()
        loSection.Caption = 'New'
    
  • Hay dos tipos de botones: normales (la clase SFRibbonToolbarButton) y horizontales (la clase SFRibbonToolbarButtonHorizontal). La diferencia es que los botones normales están dispuestos de izquierda a derecha y tienen una imagen de 32 x 32 con un título debajo, mientras que los botones horizontales se apilan uno encima del otro y tienen una imagen de 16 x 16 con un título a la derecha. Puede haber varios botones en una sección. Al hacer clic en un botón, se ejecuta un comando o se muestra un menú desplegable (que se explica a continuación).

    Para agregar un botón normal, llame al método AddButton de una sección, pasando opcionalmente el nombre del botón (si no se pasa, AddButton asigna un nombre único).

    Para agregar un botón horizontal, llame al método AddHorizontalButton de una sección, pasando opcionalmente el nombre del botón (si no se pasa, AddHorizontalButton asigna un nombre único).

    Establezca las propiedades Caption, Image, Command y EnabledExpression del objeto  botón devuelto por AddButton o AddHorizontalButton. Para utilizar un título de varias líneas, incluya un retorno de carro (carácter CHR(13)) en el texto del título. El código en Command se ejecuta a través de la función EXECSCRIPT(), por lo que puede constar de varias declaraciones separadas por retornos de carro si es necesario. EnabledExpression contiene opcionalmente una expresión (como una cadena) que el método Refresh evalúa para determinar si el botón está habilitado.

          loButton = loSection.AddButton()
          with loButton
              .Caption = 'New' + chr(13) + 'Email'
              .Image   = 'newmail.png'
              .Command = 'Thisform.NewMail()' + chr(13) + 'Thisform.Refresh()'
              .EnabledExpression = 'Thisform.IsButtonEnabled()'
          endwith
          loButton = .AddHorizontalButton()
          with loButton
              .Caption = 'Ignore'
              .Image   = 'ignore.png'
              .Command = 'Thisform.Ignore()'
          endwith
    
  • Un botón puede tener un menú desplegable; si es así, aparece una pequeña flecha hacia abajo en el título del botón. El botón tiene un menú si tiene barras. Para agregar una barra al menú, llame al método AddBar del botón, pasando el título de la barra (use "\<" delante del carácter utilizado como tecla de acceso rápido para la barra tal como lo haría con un menú de VFP) , el comando a ejecutar, opcionalmente una imagen para la barra (16 x 16), y opcionalmente una expresión que determina cuando la barra está habilitada (si no se pasa, la barra siempre está habilitada).

    Para agregar una barra separadora, no pase ningún parámetro al método AddBar.

    Para agregar un submenú a una barra de menú, llame al método AddBar del objeto de barra devuelto por AddBar. Por ejemplo, en el código siguiente, el botón "New Items" tiene un menú con cuatro barras: "E-mail Message", "Appointment", un separador y "E-mail Message Using", el último de los cuales tiene un submenú con "More Stationery...", un separador, y barras de "Plain Text", "Rich Text" y "HTML".

          loButton = .AddButton('NewItems')
          with loButton
              .Caption = 'New' + chr(13) + 'Items'
              .Image   = 'newitems.png'
              .AddBar('E-\<mail Message', 'Thisform.Email()', 'newemailsmall.png')
              .AddBar('\<Appointment', 'Thisform.Appointment()', 'appointmentsmall.png')
              .AddBar()
              loBar = .AddBar('E-mail Message \<Using')
              loBar.AddBar('\<More Stationery...', 'Thisform.MoreStationery()')
              loBar.AddBar()
              loBar.AddBar('\<Plain Text')
              loBar.AddBar('\<Rich Text')
              loBar.AddBar('\<HTML')
          endwith
    
  • El control ribbon también puede tener un menú desplegable, que se muestra al hacer clic con el botón derecho en la parte del ribbon que no está cubierta con botones. Para agregar barras al menú del ribbon, llame al método AddBar del ribbon utilizando los mismos parámetros descritos anteriormente. Los submenús también se admiten de la misma manera que se discutió anteriormente.

          Thisform.oRibbon.AddBar('Change Theme to Colorful', ;
              "Thisform.oRibbon.Theme = 'Colorful'" + chr(13) + ;
              "Thisform.Refresh()", , ;
              "Thisform.oRibbon.Theme <> 'Colorful'")
          Thisform.oRibbon.AddBar('Change Theme to Dark Grey', ;
              "Thisform.oRibbon.Theme = 'Dark Grey'" + chr(13) + ;
              "Thisform.Refresh()", , ;
              "Thisform.oRibbon.Theme <> 'Dark Grey'")
    
  • Puede agregar otros tipos de controles además de botones a una sección: llame a AddControl, pasando el nombre (opcional), la clase y la biblioteca para el control. Asegúrese de llamar al método CalculateWidth para la sección, para que el ancho de la sección se ajuste en consecuencia. Por ejemplo, este código agrega un cuadro de texto:

          loControl = loSection.AddControl('Test', 'textbox', '')
          with loControl
            .FontName = 'Segoe UI'
            .Width    = 200
    		.Height   = 24
    		.Top      = int((loSection.Height - .Height)/2) && center it vertically
    	endwith
    	.CalculateWidth()
    
  • Puede hacer referencia a objetos jerárquicamente por el nombre, comenzando desde el ribbon. Por ejemplo, Thisform.oRibbon.Home.Toolbar.New.NewEmail, puede hacer referencia al botón NewEmail en la sección "New" de la barra de herramientas de la pestaña Home" en el ribbon. Esta es la razón principal para pasar un nombre a los distintos métodos Add*; de lo contrario, tiene que usar los nombres asignados automáticamente al objeto, algo como Thisform.oRibbon.Tab1.Toolbar.Section1.Button1.

Comportamiento

El control ribbon tiene el siguiente comportamiento:

  • La pestaña actualmente seleccionada tiene una barra debajo (una barra azul en la primera captura de pantalla anterior; el color se especifica usando el color "tabbordercolor" en el tema actual; consulte la sección "Temas" a continuación) y su título está en negrita.
  • Al pasar el mouse sobre una pestaña, se resalta la pestaña en el color "tabhighlightcolor" especificado por el tema actual y, si es la pestaña seleccionada, se alarga la barra debajo de la pestaña.
  • Hacer clic en una pestaña la convierte en la seleccionada y muestra la barra de herramientas para esa pestaña.
  • Al pasar el mouse sobre un botón, se resalta el botón en el color "buttonhighlightcolor" especificado por el tema actual.
  • Al hacer clic en un botón se ejecuta el comando para ese botón. Si no se especificó ningún comando y no hay un menú para el botón (consulte el siguiente punto), aparece un cuadro de mensaje con "No implementado".
  • Aparece una flecha hacia abajo en el título de un botón si tiene un menú. Al hacer clic en el botón, se muestra el menú. Al hacer clic en el botón nuevamente, hacer clic fuera del menú o mover el mouse fuera del formulario, se oculta el menú.
  • Al pasar el mouse sobre una barra de menú, se resalta la barra en el color "menuitemhighlightcolor" especificado por el tema actual.
  • Al hacer clic en una barra se ejecuta el comando para esa barra. Si no se especificó ningún comando y no hay un submenú para la barra (ver el siguiente punto), aparece un cuadro de mensaje con "No implementado".
  • Aparece una flecha hacia la derecha en el título de una barra si tiene un submenú. Los submenús se comportan como menús.

Temas

Un tema define un conjunto de colores para el control ribbon. El tema predeterminado es el tema "Colorido" (Colorful)

Aquí está el tema "Gris oscuro" (Darkgrey):

Los temas se definen en el archivo RibbonThemes.xml:

<themes>
	<theme name="Colorful" 
		ribbonbackcolor="243,242,241" 
		ribbonbordercolor="210,208,206" 
		ribbonshadowcolor1="234,234,234"
		ribbonshadowcolor2="238,238,238"
		ribbonshadowcolor3="243,243,243"
		ribbonshadowcolor4="247,247,247"
		ribbonshadowcolor5="251,251,251"
		menuitembackcolor="255,255,255"
		menuitemhighlightcolor="210,208,206"
		menuseparatorcolor="225,223,221"
		buttonhighlightcolor="200,198,196"
		tabhighlightcolor="250,249,248"
		tabbordercolor="16,110,190"
		sectionseparatorcolor="179,176,173"
	/>
	<theme name="Dark Grey" 
		ribbonbackcolor="190,187,184" 
		ribbonbordercolor="68,68,68" 
		ribbonshadowcolor1="75,75,75"
		ribbonshadowcolor2="82,82,82"
		ribbonshadowcolor3="88,88,88"
		ribbonshadowcolor4="95,95,95"
		ribbonshadowcolor5="102,102,102"
		menuitembackcolor="243,242,241"
		menuitemhighlightcolor="200,198,196"
		menuseparatorcolor="210,208,206"
		buttonhighlightcolor="151,149,147"
		tabhighlightcolor="200,198,196"
		tabbordercolor="32,31,30"
		sectionseparatorcolor="121,119,117"
	/>
</themes>

Los valores numéricos son colores RGB.

Los atributos del XML son:

  • ribbonbackcolor: El color de fondo del ribbon.
  • ribbonbordercolor: el color de la línea del borde en la parte inferior del ribbon.
  • ribbonshadowcolor1, ribbonshadowcolor2, ribbonshadowcolor3, ribbonshadowcolor4 y ribbonshadowcolor5: Hay una "sombra" debajo de la línea del borde en la parte inferior del ribbon. Esta sombra en realidad consta de cinco líneas de un píxel de altura. Estos atributos especifican los colores de esas líneas.
  • menuitembackcolor: El color de fondo de una barra en un menú.
  • menuitemhighlightcolor: El color de fondo de una barra cuando se mueve el mouse.
  • menuseparatorcolor: el color de una barra separadora en un menú.
  • buttonhighlightcolor: El color de fondo de un botón cuando el mouse está sobre él.
  • tabhighlightcolor: El color de fondo de una pestaña cuando el mouse está sobre él.
  • tabbordercolor: el color de la línea del borde debajo de una pestaña.
  • sectionseparatorcolor: El color de la línea de separación a la derecha de una sección.

Para establecer el tema del ribbon, establezca la propiedad "Theme" con el nombre del tema deseado (actualmente solo "Colorido" y "Gris oscuro"). La propiedad "Theme" del ribbon contiene una colección de los nombres de temas disponibles. El formulario Sample.scx incluido con este proyecto utiliza un cuadro combinado con Thisform.oRibbon.Themes como RowSource y Thisform.oRibbon.Theme como ControlSource para elegir el tema del ribbon.

Clases

Todas las clases de los componentes del control ribbon están en SFRibbon.vcx.

  • SFRibbonBase: la clase principal para la mayoría de los componentes del ribbon, excepto como se muestra; una subclase de Container.
  • SFRibbon: la clase de ribbon.
  • SFRibbonTab: la clase de Tab.
  • SFRibbonToolbar: la barra de herramientas de una pestaña.
  • SFRibbonToolbarSection: la clase de sección.
  • SFRibbonToolbarButton: una clase de botón normal.
  • SFRibbonToolbarButtonHorizontal: una clase de botón horizontal; una subclase de SFRibbonToolbarButton.
  • SFRibbonMenu: una clase utilizada para definir menús; una subclase de Custom.
  • SFRibbonMenuBar: la clase de la barra de menú; una subclase de SFRibbonToolbarButtonHorizontal.
  • SFRibbonMenuSeparator: la clase de barra separadora de menú.
  • SFRibbonMenuForm: una clase de formulario que proporciona un menú desplegable; una subclase de Form.

1 de diciembre de 2020

Controles Unicode en Visual FoxPro: Un nuevo enfoque más rápido y eficiente

Articulo original: Unicode Controls in Visual FoxPro - A new faster and efficient aproach
https://vfpimaging.blogspot.com/2020/11/unicode-controls-in-visual-foxpro-new.html
Autor: Cesar Ch.
Traducido por: Luis María Guayán


Como continuación de mi último artículo publicado: Iconos Unicode en botones de Visual FoxPro, decidí seguir probando diferentes soluciones para llevar Unicodes a nuestros controles, especialmente como iconos en botones.

La opción más natural sería usar botones de Windows reales como el ejemplo que tenemos en dicho artículo: https://comunidadvfp.blogspot.com/2020/11/iconos-unicode-en-botones-de-visual.html

Pero esto trae la desventaja de que tendríamos que hacer varios cambios en nuestros formularios heredados para adaptar todos los códigos relacionados con el evento Click y otros.

Entonces, decidí probar una solución híbrida - "Dibujar" un control Win32 "Estático", similar a nuestro control Label de VFP sobre CommandButtons y Labels comunes. Estos controles "estáticos" de Win32 permiten Unicodes, y nos permitirían mantener todos nuestros códigos heredados como están.

Así que aquí está FoxyObjects, una clase de VFP personalizada que se puede incluir en nuestros formularios y que "convertirá" todos los botones y etiquetas que tienen contenido entre las etiquetas <UC> </UC> a Unicodes, al igual que el artículo anterior. Esta vez unas 4 ó 5 veces más rápido que GradObjects, con el mismo resultado.

Si no leíste el artículo anterior, mi objetivo es traer algunos íconos atractivos, los mismos que vemos en la interfaz de usuario de Windows 10, especialmente los de "SEGOE MDL2 ASSETS", como se muestra en el siguiente CharMap:

El uso de esta nueva clase es realmente muy simple:

  • Abra el proyecto FoxyObjects
  • Lance una instancia de FoxyObjects a tu formulario
  • Establezca la propiedad FontName del botón de comando en "SEGOE MDL2 ASSETS" o cualquier otra fuente que desee. Configure la propiedad Caption del botón para aceptar unicodes, introduciendo los unicodes entre las etiquetas , por ejemplo:
    • griego <UC> 03b5 03b9 03c1 03ae 03bd 03b7 </UC> - Esto mostrará la palabra "Paz" en caracteres griegos, en cualquier fuente regular, como Arial, Tahoma, Segoe UI, etc.
    • Para obtener el icono de la impresora de la fuente SEGOE MDL2 ASSETS, establezca la propiedad FontName del botón de comando y agregue lo siguiente a la propiedad Caption: "<UC> E749 </UC>"

FoxyObjects trae algunas propiedades, que se aplicarán a todos los controles CommandButton y Label que están en el mismo nivel de objeto que el nivel de FoxyObjects. Por ejemplo, si desea que FoxyObjects aplique cambios a algunos objetos seleccionados, puede insertarlos en un contenedor y agregarle una instancia de la clase. De esta forma, el resto de objetos no se verán afectados.

De forma predeterminada, se aplicará cambios solo a los objetos que tengan la etiqueta <UC> en sus propiedades Caption.

  • BackColor: Numérico, especifica el color de fondo utilizado para mostrar texto y gráficos en un objeto.
  • DisabledForeColor: Numérico, especifica el color utilizado para mostrar el texto cuando el objeto está desactivado.
  • ForeColor: Numérico, especifica el color utilizado para mostrar el texto.
  • MouseOverForeColor: Numérico, especifica el color en el que se convertirá el texto del objeto (y el icono) cuando el mouse esté sobre el objeto especificado. Si no desea este efecto, especifique el valor -1
  • lBindAll: Logical, determina que todos los objetos se verán afectados, incluso si no tienen el en el Caption. Esto significa que puede cambiar el Caption en tiempo de ejecución, y se respetarán los Unicodes
  • lBindLabels: Lógico, determina que tanto los CommandButtons como los Labels se verán afectados
  • lBindResize: Lógico, determina que siempre que se cambie el tamaño o se mueva cualquier control, la máscara de etiqueta también se actualizará.
  • lBindVisible: Lógico, determina que siempre que cualquier control esté oculto o visible, la máscara de etiqueta también se actualizará..

Los Unicodes se pueden obtener directamente a través de CharMap.EXE o en toda la web. Aquí hay un excelente punto de partida: https://docs.microsoft.com/en-us/windows/uwp/design/style/segoe-ui-symbol-font

La fuente "SEGOE MDL2 ASSETS" viene con Windows 10, pero no se permite su distribución a otros sistemas operativos. Esto no es gran cosa, porque Tahoma también nos trae varias opciones, y siempre podemos trabajar con algunas de las fuentes gratuitas disponibles en la web, como "Material.io". Traen toneladas de iconos modernos y hermosos de forma gratuita. Realmente vale la pena una visita: https://material.io/resources/icons/?style=outline

AGRADECIMIENTOS ESPECIALES a Mustapha Bihmuten de Marruecos y Leandro Walfrans de Brasil por probar la versión Pre-Alpha de la clase y por brindar valiosas sugerencias

Descargar de FoxyObjects v.0.4


2 de noviembre de 2019

Cómo poner una imagen sobre otra en un formulario

Cómo poner una imagen sobre otra en un formulario

Artículo original: How to put one image over another in a form
https://vfpimaging.blogspot.com/2007/10/how-to-put-one-image-over-another-in.html
Autor: Cesar Ch.
Traducido por: Luis María Guayán


Esto es una cosa muy básica y fácil.

La imagen que quedará en la parte superior debe tener algún tipo de transparencia establecida. Se recomienda que utilice un BMP de 24bits. VFP convertirá automáticamente la parte blanca de la imagen en transparente, como en la siguiente imagen, si pone la propiedad "BackStyle" del objeto Imagen en "0 - Transparente". No olvide de "Traer al frente" la imagen, desde la barra de herramienta de diseño.

El primer formulario muestra 2 imágenes separadas, pero con fondos opacos. Luego, en el segundo, configuro el fondo transparente, y luego cubro una parte de la imagen para que pueda ver cómo queda. Puede obtener más información en este artículo traducido al español en este Blog en: BMPs con fondos transparentes.

Otra buena manera es usar PNGs, que soportan ALPHA (transparencias), pero lo más recomendado, y tiene mejor soporte en VFP, es el uso de BMPs, tal como aquí se muestra.

Actualización del 30 de Octubre de 2007

Bernard Bout recuerda que "tenga en cuenta que TODAS las áreas blancas de una imagen serán transparente. Por ejemplo, en la segunda imagen puesta por Cesar, el fondo del formulario es visible a través de los ojos del hombre brasileño. Si esto no es lo que quiere, entonces usted debe "enmascarar" las partes blancas del mapa de bits que desea que NO SEA TRANSPARENTE. Simplemente abra la imagen y pinte las partes blancas que se quiere mantener de NEGRO. La imagen debe ser del mismo tamaño. Pinte todas las otras partes de blanco y guardarlo como un BMP pero con una extensión .MSK. VFP automáticamente usará este archivo para "enmascarar" las zonas de blancas que serán transparentes".

Para este caso, yo por lo general edito la imagen, y cambio el RGB "blanco" puro -RGB(255,255,255)- que quiero mantener, cerca de otro blanco, como RGB(254,254,254). Nadie se dará cuenta de la diferencia y funciona muy bien, con la ventaja de no tener que crear un archivo de imagen adicional.

De todos modos, si prefiere crear una máscara, elija para crear un formato de imagen de 1bpp MONOCHROMÁTICO. Por lo general la gente guarda en el formato "24bits". Pero como las imágenes .MSK son unicamente en blanco & negro, monocromático, tienen un tamaño de archivo más pequeño. Comparando una imagen monocromática, la imágen de 24bpp será 24 veces mayor en tamaño.

En MSPaint, por ejemplo, elija, "Guardar como..."

Otras notas importantes sobre archivos MSK

En la Ayuda para PictureVal:

"Ud. no puede especificar un archivo de máscara (MSK) con la propiedad PictureVal. Si Ud. está utilizando imágenes BMP y quiere una máscara, Ud. debe usar la propiedad Picture. Ud. también puede utilizar un formato diferente de imagen, como un GIF, que tiene definida la transparencia dentro de la imagen."

How to: Enhance Control Display: Una buena explicación (en inglés) acerca de los archivos .MSK.


3 de octubre de 2019

Colocar menús popup

Colocar menús popup

Artículo original: Positioning popup menus
http://www.foxpert.com/knowlbits_200801_2.htm
Autor: Christof Wollenhaupt
Traducido por: Ana María Bisbé York


No soy un gran fan de los menús contextuales (clic derecho del ratón). En muchas aplicaciones se implementan de forma incorrecta y no se pueden invocar con la tecla de menú contextual o la combinación estándar Shift+10. Para muchos desarrolladores, a veces parecen como un depósito para descargas. En lugar de pensar en cómo incorporar una nueva característica en la justa medida, simplemente agregan la función al menú contextual de un formulario. En esos casos, los menús contextuales son difíciles de descubrir para el usuario final. Teniendo que hacer clic en cada píxel y descubrir si hay alguna característica oculta, solamente está genial para los juegos; pero realmente no es apropiado para una aplicación productiva.

Existen usos para el menú contextual. Hay que tener en mente, que el nombre "acceso directo" es el hecho de que los menús originales sean una vía rápida para las características de acceso con un ratón, que estarían también disponible en el menú o en los botones. Las características que están solamente disponibles a través de menú contextual o de menús popups de múltiples niveles, y cuando se abre una ventana en algún otro lugar en la pantalla, difícilmente entran dentro de esa categoría.

Regresamos al tema. Existen muchas ocasiones en las que tiene sentido brindar menos opciones utilizando funcionalidades en un formulario. Por ejemplo, una característica útil para un control TextBox, es abrir una ventana examinador con una búsqueda web. Para esta funcionalidad utilizo un pequeño botón en el control TextBox que muestra una flecha hacia abajo ("u" en fuente Marlett). Este botón abre un pequeño popup, justo debajo del botón, como se ve en Microsoft Office.

Colocar menús popup produce código que utiliza MCOL() y MROW() para colocar los menús según la posición del ratón. Esto trabaja bien sin que haga clic en ningún lugar del título de la ventana. En este caso, las dos funciones devuelven -1 moviendo el popup a algún lugar fuera de esta ventana.

Cuando intenta mover el popup a algún lugar, se da cuenta enseguida que tiene una posición dada en Fóxeles en lugar de píxeles. Los Fóxeles son para Visual FoxPro lo que los Twips son para Visual Basic. Un Fóxel depende del tamaño de la fuente actual del formulario. En otras palabras, cambiar la propiedad ThisForm.FontName o cualquiera otra propiedad de fuente hará que cambie las medidas a Fóxeles.

La propiedad ScaleMode, que se utiliza raramente, permite especificar las coordenadas del formulario en Fóxeles o píxeles. Como nota al margen, un formulario con medidas Fóxeles es una buena forma de romper lo genérico de la tercera parte de los componentes. Muchos desarrolladores no trabajan mucho con las posiciones que tienen lugares decimales y añaden píxeles a las propiedades Left, Top, Width y Height sin verificar la escala antes.

Entonces, para ubicar un control popup justo debajo o arriba de un botón de comandos, puede cambiar las medidas, leer la posición del botón en Fóxeles y devolver el valor a píxeles. Esto trabaja bien para botones que estén colocados en el formulario directamente. Con controles anidados, sin embargo, tiene que utilizar OBJTOCLIENT(), una función que ignora completamente la propiedad ScaleMode. Para solucionar esto, agrego un botón derecho invisible en el formulario.

Local lnRow, lnColumn
If Vartype(Thisform.PosHelp) != "O"
  Thisform.AddObject("PosHelp", "Commandbutton")
EndIf
Thisform.PosHelp.Move( ;
  Objtoclient(This,2), ;
  Objtoclient(This,1) + This.Height ;
  )
Thisform.ScaleMode = 0
lnRow = Thisform.PosHelp.Top
lnColumn = Thisform.PosHelp.Left
Thisform.ScaleMode = 3

Con la posición en Fóxeles puedo ahora definir un menú popup como este:

DEFINE POPUP shortcut ;
  SHORTCUT RELATIVE ;
  From m.lnRow, m.lnColumn ;
  Font Thisform.FontName, This.FontSize
Define bar 1 of Shortcut Prompt "Elemento 1"
Define bar 2 of Shortcut Prompt "Elemento 2"
Define bar 3 of Shortcut Prompt "Elemento 3"
Activate Popup Shortcut

Si es que sorprende sobre la cláusula FONT que utilicé aquí, explico. Algunos clientes insisten en un control redimensionar para el formulario. Esto es, cuando un formulario se redimensiona, el contenido se alargara. Con esta cláusula el menú popup tiene el mismo tamaño que todo lo demás en el formulario.


25 de mayo de 2019

No filtre datos en una cuadrícula

Artículo original: Don't FILTER Data in a Grid
http://www.craigberntson.com/blog/2008/08/dont-filter-data-in-grid.asp
Autor: Craig Berntson
Traducido por: Luis María Guayán


He visto varios mensajes en los foros donde últimamente la gente pregunta acerca de cómo filtrar los datos mostrados en una cuadrícula. Esta es una muy mala idea, ya que el deslizador de la barra de desplazamiento no funciona correctamente. Mire este formulario, que utiliza un filtro.


La tabla tiene más de 200.000 registros. Yo apliqué:

SET FILTER TO ContactName = "Alejandra Camino".

El resultado no es bueno. Vea la posición del deslizador. Cuando se mueve el deslizador hacia la parte superior de la barra de desplazamiento, éste salta de nuevo a la posición que se muestra en la imagen capturada, porque es ahí donde está el registro actual en la tabla entera. Esto es muy confuso para el usuario.

Ahora mire lo que sucede cuando se utiliza una consulta o una vista.


En este formulario creo una vista local y configuro la cuadrícula para usar la vista como fuente de datos. Luego consulto los datos utilizando una vista parametrizada. Mire en este caso, que el deslizador se encuentra en la parte superior de la barra de desplazamiento cuando el puntero de registro se encuentra en el primer registro. Esto se debe a que los datos de la cuadrícula solo contienen los registros:

WHERE ContactName = "Alejandra Camino"

en lugar de la tabla entera.

Escribir aplicaciones de esta forma podrá requerir un nuevo entrenamiento de los usuarios, ya que no tienen todos los datos al mismo tiempo, pero siempre pueden consultar los datos que deseen. En mi experiencia veo que los usuarios prefieren este tipo de aplicaciones. Su opinión puede ser distinta.

Una ventaja adicional es que estamos preparándonos para trabajar con SQL Server, ya sólo utiliza conjuntos de datos. Siempre debemos consultar los datos que deseamos y traer solamente estos, en lugar de todos.

Copyright 2008, Craig Berntson. Used by permission

24 de septiembre de 2018

Como me aseguro que los formularios apuntan a las tablas correctas

Los formularios almacenan el path de las tablas definidas en el Entorno de Datos.

Por desgracia cuando se distribuye la aplicación, estos formularios pueden apuntar al directorio donde se desarrollaron. Estos paths deben ser reseteados en tiempo de ejecución para asegurarnos que apuntan a las tablas correctas.

* en el programa principal o en la de conexion determinar el directorio actual
* almacenarlo en variables globalos o del objeto aplicación.
gcAppPath = sys(2003)
gcDBPath = alltrim(gcAppPath)+"datos"
gcDBName = "mibasededatos.dbc"

Poner en el entorno de datos la propiedad AutoOpenTables = .F.

modificar la clase base del formulario (o cambialo en cada formulario) en el Metodo Load:

* apuntar todas las tablas al directorio y base de datos correcto
thisform.SetAll("Database",gcDbPath+gcDbname,"Cursor")
* abrir las tablas
thisform.dataenvironment.opentables()

NOTA: esto funciona con tablas de una base de datos, este codigo debe ser modificado si se usan tablas libres.

Pablo Roca

30 de junio de 2018

Acceder a objetos y su código dentro de contenedores

Muchas veces, mientras trabajamos con los Diseñadores de clases y formularios, repetimos acciones de búsqueda de controles que se encuentran debajo de uno o varios contenedores, o de búsqueda de código relativo a controles. En este escrito, resumo algunas de las técnicas que nos puedes ayudar a ganar productividad.

Acceder a objetos y su código dentro de contenedores

Hoy vamos a comentar sobre técnicas para lograr un acceso más rápido a controles dentro de contenedores y al código de métodos o eventos de estos controles.

Vamos a suponer que tenemos un contenedor (por ejemplo un formulario), que contiene, digamos un pageframe y en una de sus páginas otro contenedor con otro grupo de controles y etc etc. Cada vez que deseamos posicionarnos en un control determinado tenemos varias variantes, por cierto, todas tediosas.

Podemos, por ejemplo, acceder desde la ventana Propiedades. En la esquina superior existe un cuadro desplegable con todos los controles existentes en el formulario o clase en cuestión. Si nos desplazamos por esta lista desplegada nos podemos colocar directamente en el control deseado. Pero sucede que a veces, como en nuestro caso, todos los controles no caben en esta lista lo cual hace necesario buscar por la lista y luego posicionarnos en el control.

Otra opción es aprovechar las teclas de acceso directo que han sido programadas y vienen de forma nativa con VFP. Así tenemos que al trabajar con los contenedores, tales como el pageframe (marco de página) se puede modificar rápidamente el objeto dentro del contenedor utilizando Ctrl+Clic sobre el objeto. Esto "pasa por encima" del contenedor y permite tomar el control del objeto que está dentro. Si existen contenedores dentro de otros contenedores, puede utilizar Ctrl+Shift+Clic para profundizar aun más en el nivel. Por ejemplo, en nuestro caso, tenemos un control optiongroup en una página del pageframe, entonces podemos acceder a uno de los botones presionando Ctrl+Shift+Clic, y VFP pasa por encima de los controles pageframe, page y optiongroups y va directo al control optionbutton.

Pero la verdad es que, al menos yo, no suelo acordarme nunca de esto. Y he aquí que me he encontrado algo que me parece fenomenal en estos casos en que tenemos muchos controles y mucha herencia de contenedores. Se trata de un árbol de contenedores y es sacado de http://fox.wikis.com/wc.dll?Wiki~ContainerTree

Su autor, Burkhard Stiller, ha presentado un código que nos permite obtener un árbol para acceder directamente a cada objeto de nuestra clase o formulario. Es muy cómodo especialmente si tenemos gran cantidad de objetos o controles dentro de varios niveles de contenedores. Es preciso tener VFP 9.0 para poderlo aplicar.

Se trata sólo de copiar y pegar en un editor de programas y ejecutar siempre que nos sea necesario. Automáticamente rellena el árbol a partir del formulario o clase activa. Según el autor puede encontrar datos que estén dentro de un pageframe, que están a su vez, dentro de otro pageframe y a su vez dentro de otro pageframe, .... así sucesivamente ...De esta forma nos ilustra la profundidad de contenedores que tiene en cuenta. Todos los controles son tenidos en cuenta, sin importar si tiene el método SetFocus(). Se puede emplear en contenedores basados en SCX y VSC.

Veamos una imagen para irnos familiarizando. Como vemos, nos encontramos con un árbol en el que se encuentran todos los controles y los contenedores son nodos que se abren para dejar ver los controles contenidos. Podemos cerrar y abrir los nodos que no vamos a necesitar y de esta forma nuestra lista de controles no está abierta para todos aquellos que no vamos a necesitar.

Pero aun no terminan las bondades de este aporte, que como han dicho, puede ser candidato a formar parte del proyecto Sedna. Bueno, ¿qué más tiene? Pues es Anclable, lo que significa que lo podemos mantener abierto formando parte de nuestra configuración y así tenemos que al cerrar el formulario actual y abrir uno nuevo, bastaría con pararnos en la ficha FrmClassTree (que además, podemos renombrar), yo le he llamado Arbol_controles y lo tengo en mi ventana de comandos junto a las otras ventanas que me gusta mantener: la ventana Propiedades, la ventana Sesión de Datos y la ventana Vista de Documento

Para llamar a esta utilidad basta con ejecutar su programa; pero ... ¿qué tal si nos acordamos de IntelliSense? Pues, podemos agregar un registro de usuario a nuestra tabla FoxCode.DBF e indicar una abreviatura y el código o la llamada al prg, como nos parezca mejor. De esta forma, bastaría con escribir la abreviatura para activar nuestro árbol de controles para la sesión de trabajo actual.

¿Qué tal? Yo lo encuentro muy cómodo. ¿Y ... qué tal un acceso directo a los métodos o eventos de los controles contenidos? Pues también hay posibilidad de lograrlo y nos la brinda VFP de forma nativa.

Tenemos la posibilidad de acceder desde la ventana propiedades, ficha Métodos. Lo que sucede es que nos tendríamos que colocar primeramente en el control para que se muestren sus métodos. Otra variante es desde el diseñador de Formularios / Clases; pero ... nuevamente tenemos que localizar el control y luego el método asociado. Aquí la ventaja sobre seleccionarlo desde la ventana Propiedades es que los procedimientos que tienen código en la instancia actual se muestran al inicio para cada control y muchas veces, si no son muchos, no hay que desplazarse.

Una forma más o menos cómoda es recorrer con las teclas de Avance y Retroceso de páginas desde la ventana de códigos de los Métodos y eventos. Esto lo que va a hacer es que nos movamos por los distintos procedimientos que hemos creado para todos los controles. Eso puede ayudar; pero... lo que sí es verdaderamente productivo es emplear una herramienta de VFP a partir de 8.0 que se llama Vista del Documento. La podemos mantener activada todo el tiempo y es válida para programas de procedimientos o con los Diseñadores de Clases y Formularios.

En la figura hay un ejemplo donde los controles no tienen contenedores adicionales; pero trabaja de la misma forma si los controles están contenidos, independientemente de la complejidad de la herencia de contenedores. La vista de Documentos se puede mantener anclada a la ventana comandos y siempre activa, ya que VFP guarda la configuración al salir.

Pues hasta aquí algunas ideas para lograr acceso directo a controles y el código vinculado a ellos. Aplicando estas técnicas ganamos en productividad, tan necesaria para nuestros desarrollos.

Saludos,

Ana María Bisbé York

7 de mayo de 2018

Agregar efectos a sus formularios

Artículo original: Add a fading effect to your forms
http://www.ml-consult.co.uk/foxst-40.htm
Autor: Mike Lewis
Traducido por: Ana María Bisbé York


Al crear formularios que se muestren y desaparezcan gradualmente puede añadir efectos visuales a sus aplicaciones Visual FoxPro

Puede haber notado que en sus aplicaciones favoritas de Windows algunas ventanas aparecen y desaparecen gradualmente cuando se abren, en lugar de abrirse instantáneamente. De forma similar, cuando se cierra una ventana, puede tomar un segundo o más en lugar de desaparecer de pronto. Esto puede ser un efecto visual agradable (siempre y cuando sea con moderación). La técnica trabaja especialmente bien con ventanas desplegables y alertas que el usuario puede no esperar. La aparición gradual de una ventana es frecuentemente más agradable a la vista que una que destella de pronto en la pantalla.

En este artículo, vamos a explicar cómo agregar este efecto a sus formularios Visual FoxPro. Describiremos además una clase formulario sencilla, llamada FadeForm, que incorpora esta técnica. Puede descargar la clase y utilizarla directamente para crear sus propios formularios. Vea debajo las instrucciones para la descarga.

Esencialmente, esta técnica se encarga de incrementar o disminuir gradualmente la transparencia del formulario. Incluso si no está interesado en crear formularios, puede adaptar este código para otras situaciones que pudiera desear hacer un formulario parcialmente transparente.

Un par de inconvenientes

Antes de seguir adelante, debemos puntualizar un par de limitaciones de esta técnica. Primeramente necesita ciertas llamadas de API que trabajan solamente en Windows 2000 o superior. Si sus usuarios aun trabajan en Windows 98, no podrá ofrecerles esta posibilidad.

Más importante aun, la técnica trabaja solamente en formularios de nivel superior. Un formulario de nivel superior es uno que aparece independientemente de la ventana de salida principal de la aplicación. De forma predeterminada, tiene su propio botón en la barra de tareas de Windows (se puede ocultar este botón al igualar a .F. la propiedad ShowInTaskbar y puede moverse fuera de la ventana de salida. Para crear un formulario de nivel superior iguale a 2 la propiedad de formulario ShowWindow (ya lo hemos hecho en la clase FadeForm en la descarga de este artículo)

Crear un formulario transparente

Para lograr esta magia, el formulario utiliza una función API de Windows llamada SetLayeredWindowAttributes(). Esta función puede ayudar a crear un grupo interesante de trucos visuales. En el que estamos interesados es en la capacidad de hacer un formulario transparente. Más exactamente, puede incrementar o disminuir el grado de opacidad de un formulario en pasos muy pequeños. El factor de opacidad puede variar desde 0 (completamente transparente) a 255 (completamente opaco).

Entonces, para hacer que el formulario aparezca gradualmente, puede comenzar haciéndolo completamente transparente. Puede entonces, incrementar la opacidad hasta que el formulario sea completamente opaco. De forma similar, para hacer que el formulario desaparezca puede comenzar con el máximo de opacidad (255) y disminuirla hasta que alcance cero.

Declarar funciones API

Antes de que haga nada de esto, tiene que declarar la función SetLayeredWindowAttributes() dentro de VFP. Además, debe declarar un par de llamadas a funciones API: SetWindowLong, que activa el formulario como "ventana en capas", y Sleep(), el que le permite introducir una demora corta cada vez que ajusta el factor de opacidad.

Como todas las declaraciones API, es ideal que estos DECLAREs se hagan sólo una vez, cerca del inicio de la sesión. Para mayor conveniencia, la clase formulario FadeForm en el archivo de descarga ejecuta la declaración en su evento Load; pero esto es lo ideal. Esto significa que las funciones se van a re-declarar cada vez que cualquier formulario basado en esta clase se instancie, lo que es una pérdida de tiempo. Sugerimos mover estas declaraciones a algún punto más adecuado al inicio de comienzo del programa principal.

He aquí el código para declarar las funciones API (las primeras dos de estas requieren Windows 2000 o superior):

DECLARE INTEGER SetWindowLong In Win32Api ; 
  INTEGER HWnd, INTEGER Index, INTEGER NewVal

DECLARE INTEGER SetLayeredWindowAttributes In Win32Api ; 
  INTEGER HWnd, STRING ColorKey, ; 
  INTEGER Opacity, INTEGER Flags

DECLARE Sleep IN WIN32API INTEGER Duration

Lograr que aparezca el formulario ...

El trabajo real de lograr que el formulario aparezca gradualmente se hace en el método FadeIn, el que debe ser llamado desde el Init del formulario. Ya que el formulario es normalmente invisible durante la instalación, FadeIn debe ser idealmente llamado al final del procesamiento del Init. Esto se hace automáticamente desde el método Init de la clase FadeForm. Si sobreescribe el método Init, asegúrese de llamar explícitamente a FadeForm en un lugar adecuado.

El método FadeIn comienza por verificar una propiedad de formulario llamada lFade, la que actúa como un interruptor maestro para el efecto gradual al aparecer y desaparecer. Esta propiedad está igualada a .F. de forma predeterminada. Para lograr este efecto, asegúrese de cambiarla a .T.

Esta es la parte principal del código del método FadeIn:

* Si no hay que lograr el efecto, no hace nada 
IF NOT thisform.lFade 
  RETURN 
ENDIF

* Comienza por crear el formulario completamente transparente 
SetWindowLong(THISFORM.hWnd, -20, 0x00080000) 
SetLayeredWindowAttributes(THISFORM.hWnd, 0, 0, 2)

* Muestra el formulario (le dice a VFP que muestre un formulario en el fondo 
* no se mostrará nada en la pantalla porque 
* el formulario está completamente transparente) 
thisform.Visible = .T.
* Ahora, se muestra gradualmente, necesitando para ello aproximadamente un segundo 
lnTrans = 10 
DO WHILE .T. 
  SetLayeredWindowAttributes(THISFORM.hWnd, 0, lnTrans, 2) 
  lnTrans = lnTrans + 10 
  IF lnTrans > 255 
    EXIT 
  ENDIF
  Sleep(35) 
ENDDO

* Ahora el formulario está completamente opaco

Si desea ajustar la velocidad de este efecto, cambie el valor numérico pasado a la función Sleep(). Esta es la cantidad de milisegundos durante el cual el método se va a detener cada vez que se ajuste la opacidad. Auméntelo para hacer que el formulario se abra más lentamente, disminúyalo para lograr lo contrario.

... Y desaparecer

El método FadeOut de la clase (que no está mostrado aquí) trabaja de la misma forma que FadeIn, la diferencia principal, es que disminuye el factor de opacidad en lugar de incrementarlo. Además, FadeOut no necesita alterar la propiedad Visible del formulario.

Llame al método FadeOut desde el método Destroy del formulario. Como con FadeIn, la clase en el archivo de descarga lo hace de forma predeterminada. Si sobreescribe el método Destroy, asegúrese de llamar explícitamente al método FadeOut. (Tenga cuidado de no llamar más de una vez a FadeOut o FadeIn por sesión de formulario, o usted creará un efecto realmente extraño).

Descargar la clase

Seleccione el enlace que aparece más abajo para descargar la clase FadeForm. La clase está contenida en una biblioteca de clases (FadeForm.VCX) el que se encuentra a su vez en un archivo ZIP. El tamaño de la descarga es solamente 3 KB.

Puede incluso utilizar la clase tal cual, o extraer el código para utilizarlo en sus propios formularios o clases. De cualquier manera, lea el método Documentation, que contiene notas sobre cómo utilizar la clase.

Descargue ahora desde http://www.ml-consult.co.uk/download/fadeform.zip

Como siempre, apreciamos sus comentarios sobre nuestros artículos y descargas (vea nuestra página de contactos).

Mike Lewis Consultants Ltd. Junio 2006.