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


5 comentarios :

  1. Muchas gracias por tu tiempo y larga vida a nuestro zorro

    ResponderBorrar
  2. Excelente Amigo. Eso tiene de lindo el Zorro, que nos gusta trabajar con el y nos gusta mostrar nuestros logros enseñando a otros.
    Gracias y Felicitaciones.!!!

    ResponderBorrar
  3. No sabes lo agradecido de la dedicación de tu tiempo Luis Maria hacia los demás. Muchas gracias por tu generosidad, y darle larga vida al zorro.

    ResponderBorrar

Los comentarios son moderados, por lo que pueden demorar varias horas para su publicación.