Introducción:
En este artículo veremos cómo poner (o meter o incrustar o incluir) dentro
de un control o de un formulario una aplicación que, o bien esté ya
funcionando o bien la iniciemos nosotros. Para hacer esto, vamos a echar
mano del API de Windows, ya que, (al menos no lo he encontrado), el .NET
Framework no dispone de clases para hacerlo.
Funciones del API de
Windows usadas para poner una aplicación en un contenedor de Windows Forms
Como he comentado antes, para hacer el trabajo que nos ocupa, vamos a usar
tres funciones del API de Windows:
- FindWindow, esta nos servirá para buscar una ventana por el
título y devolverá el "handle" de esa ventana.
- SetParent, esta función hará que una ventana sea hija de otra.
- MoveWindow, la utilizaremos para cambiar el tamaño de una
ventana de la que conocemos el handle.
Nota:
Si quieres ver la declaración para VB6, VB.NET, C/C++ y C# de estas funciones,
pásate por la página de
Equivalencias con el
API de Windows.
Encontrar una ventana
Lo primero
que tenemos que hacer es encontrar la ventana que queremos incluir dentro de
un control o bien dentro de un formulario. De esa ventana lo que nos
interesa es el "handle" de la misma. Una vez que conocemos ese
"identificador", podremos usar otras funciones del API de Windows para que
trabajen con ella.
Una de las cosas que haremos en la aplicación de
ejemplo será "incrustar" una aplicación que nosotros iniciemos. Para iniciar
esa aplicación vamos a usar la clase Process (System.Diagnostics.Process),
esa clase tiene una propiedad llamada Handle, pero esa propiedad no
devuelve el "identificador" de la ventana creada, sino que devuelve el
identificador del proceso recién creado. Por tanto no nos servirá ese valor,
en su lugar usaremos la función del API FindWindow, a la cual se le
pasan dos parámetros, el primero es el nombre de la clase de la ventana,
pero este se puede omitir, pasando un valor nulo (Nothing en VB,
null
en C#); el segundo parámetro es el título de la ventana que queremos
encontrar. Ese nombre debe ser el título exacto que tiene la ventana. Por
ejemplo, si queremos "buscar" el bloc de notas recién abierto, buscaremos el
siguiente texto si nuestro sistema operativo está en inglés: Untitled -
Notepad, y este otro: Sin título - Bloc de notas, si el sistema
operativo está en español. En cualquiera de los dos casos debemos indicar el
título completo, ya que si sólo buscamos parte de él, FindWindow
devolverá un cero, indicando que no ha encontrado una ventana con el título
indicado.
Cambiar el contenedor de una ventana
Es complicado
esto de las traducciones "técnicas", ya que en inglés sería parent window,
que traducido literalmente podría ser ventana madre, ventana
progenitora e incluso ventana padre, pero como ventana es
femenino, pues como que "parent" no se adecua mucho a padre, por eso me he
decidido por usar "contenedor de una ventana", con idea de que quede
claro de que una ventana puede tener a su vez otras ventanas, además de que
es la forma que se utiliza en la documentación de Visual Studio .NET.
Para cambiar el contenedor de una ventana, por defecto suele ser el
escritorio la ventana predeterminada, vamos a usar la función del API de
Windows: SetParent. Esta función recibe dos parámetros, los cuales
son los identificadores de las ventanas que están en juego, el primer
parámetro será la ventana que queremos contener dentro de otra, y el segundo
será el identificador de la ventana que hará de contenedor.
En .NET
Framework, todos los controles usados con las aplicaciones de
Windows.Forms se derivan de la clase Control, esa clase tiene una
propiedad llamada Handle que es el identificador (HWND) de la ventana
usada para mostrar ese control, por tanto, al menos teóricamente, podemos
usar cualquier control como contenedor de otros controles (o ventanas).
En la aplicación de ejemplo utilizaremos un control Panel para contener
a una aplicación.
Cambiar el tamaño y posición de una ventana
Otra de las cosas que haremos en la aplicación de ejemplo es mover la ventana
"hija" dentro de la ventana contenedora, con idea de que en un principio
esté totalmente visible y ocupe el mismo espacio que el control (o
formulario) en el que vamos a incrustar la ventana de la aplicación que
indiquemos. Debido a que de esa aplicación solo conoceremos el identificador
de la ventana, tendremos que echar nuevamente mano del API de Windows para
usar la función
MoveWindow, la cual recibe seis parámetros, el primero es el identificador
de la ventana que queremos mover, los dos siguientes son la posición de la
equina superior izquierda de la ventana (x e y), los dos siguientes son el
ancho y el alto de la ventana, y por último un valor que indica si queremos
que se "repinte" la ventana después de moverla.
La posición de la
ventana será relativa al contenedor, no a la ventana principal (escritorio),
por tanto, si le indicamos 0, 0, estaremos situando la ventana hija en la
esquina superior izquierda de la ventana contenedora.
La aplicación
de ejemplo
Con la aplicación que vamos a usar para explicar cómo usar
esas tres funciones del API de Windows para que hagan lo que queremos,
haremos varias cosas:
1- Podremos iniciar una aplicación y ponerla dentro de un control del
formulario principal o bien ponerla dentro de un segundo formulario.
2- También podremos indicar el título de una de las aplicaciones que estén en
ejecución y meterla dentro del control o de otro formulario.
Nota:
Es importante advertir que todo este "manejo" de ventanas puede llegar
a producir inestabilidad en el sistema operativo, por tanto, deberías tener
mucho cuidado con que ventana quieres indicar para que se "incruste" en
otra, incluso esa inestabilidad del sistema puede llegar a producirse al
manipular aplicaciones simples como el bloc de notas.
Una
clase con las funciones del API de Windows
Para tener mejor
organizado nuestro código, vamos a declarar las tres funciones del API de
Windows en una clase.
En Visual Basic podemos usar un módulo en lugar de una clase, incluso sería
más cómodo de usar, pero como quiero que el código mostrado quede claro para
todo el mundo, voy a usar una clase en la que esas funciones estarán
compartidas (Shared en VB, static en C#), de forma que para
usar cualquiera de ellas, solo tengamos que indicar el nombre de la clase
seguida de la función: WinApi.SetParent(h1, h2).
Iniciar un
proceso y meter la aplicación en un control
Esta es una de las
opciones que tendremos en el programa.
Para iniciar el proceso usaremos la clase Process, el nombre de la
aplicación a iniciar estará en la caja de textos.
Este es el código de Visual Basic a usar:
' Iniciar el proceso indicado en txtApp
Dim proc As New System.Diagnostics.Process
proc.StartInfo.FileName = txtApp.Text
proc.Start()
hWndNotepad = WinApi.FindWindow(Nothing, proc.MainWindowTitle)
If hWndNotepad.ToInt32 > 0 Then
WinApi.MoveWindow(hWndNotepad, 0, 0, PanelApp.Bounds.Width - 4, PanelApp.Bounds.Height - 4, 1)
WinApi.SetParent(hWndNotepad, PanelApp.Handle)
Else
proc.Close()
hWndNotepad = IntPtr.Zero
End If
Buscar el título de la ventana a contener en un control
El código para esta tarea es similar al anterior, con la única diferencia de
que en esta ocasión no tendremos que iniciar ningún proceso, simplemente
buscamos el título de la ventana que queremos meter dentro del control y ya
está.
' Capturar la ventana con el título indicado en txtApp
hWndNotepad = WinApi.FindWindow(Nothing, txtApp.Text)
If hWndNotepad.ToInt32 > 0 Then
WinApi.MoveWindow(hWndNotepad, 0, 0, PanelApp.Bounds.Width - 4, PanelApp.Bounds.Height - 4, 1)
WinApi.SetParent(hWndNotepad, PanelApp.Handle)
Else
hWndNotepad = IntPtr.Zero
End If
El formulario principal de la aplicación
El
formulario principal de nuestra aplicación tendrá una serie de controles que
nos permitirán realizar las acciones que te he indicado, y este es el
aspecto de ese formulario:
La aplicación con el bloc de notas en un control Panel
Como podemos
comprobar, tenemos cuatro botones, una caja de textos, un checkBox y un
control Panel.
Dos de esos botones servirán para iniciar la aplicación indicada en la caja de
textos. Dependiendo de que queramos "meter" esa aplicación en el panel o que
se muestre otro formulario que lo contenga, pulsaremos en uno u otro botón.
Los otros dos botones nos servirán para "capturar" una aplicación que ya esté
en funcionamiento, para poder capturar esa aplicación, tendremos que indicar
en la caja de textos el título completo de la ventana. Al igual que ocurría
con los otros botones, uno nos servirá para poner la aplicación en el panel
de este formulario y el otro para incluirlo en un nuevo formulario.
El checkBox lo utilizaremos para ajustar la aplicación al tamaño del panel.
En el otro formulario, también habrá un checkBox con la misma funcionalidad.
Nota:
En las pruebas que he hecho, solo me "capturaba" la primera aplicación que le
indicara, si después intentaba capturar otras aplicaciones, "pasaba"
olímpicamente, y la única solución que me quedaba era cerrar el programa y
volver a abrirlo. Seguramente será cosa del código, pero no me he puesto a
"mirarlo" a fondo... así que, si lo solucionas, me lo dices y lo corrijo.
Y esto es todo amigos... espero que te sea de utilidad y que lo
disfrutes.
Aquí tienes el proyecto con el código tanto para Visual
Basic como para C#.
Y aquí abajo tienes el código de los dos formularios y la clase con las
declaraciones del API de Windows, también para Visual
Basic
y para Visual C#.
Nos vemos.
Guillermo
El código para VB .NET
|
|
'------------------------------------------------------------------------------
' Clase con las funciones del API de Windows (14/Ago/05)
' Las funciones están declaradas como compartidas
' para usarlas sin crear una instancia.
'
' ©Guillermo 'guille' Som, 2005
'------------------------------------------------------------------------------
Imports Microsoft.VisualBasic
Imports System
Public Class WinApi
' Hace que una ventana sea hija (o esté contenida) en otra
<System.Runtime.InteropServices.DllImport("user32.dll")> _
Public Shared Function SetParent(ByVal hWndChild As IntPtr, _
ByVal hWndNewParent As IntPtr) As IntPtr
End Function
' Devuelve el Handle (hWnd) de una ventana de la que sabemos el título
<System.Runtime.InteropServices.DllImport("user32.dll")> _
Public Shared Function FindWindow(ByVal lpClassName As String, _
ByVal lpWindowName As String) As IntPtr
End Function
' Cambia el tamaño y la posición de una ventana
<System.Runtime.InteropServices.DllImport("user32.dll")> _
Public Shared Function MoveWindow(ByVal hWnd As IntPtr, _
ByVal x As Integer, ByVal y As Integer, _
ByVal nWidth As Integer, ByVal nHeight As Integer, _
ByVal bRepaint As Integer) As Integer
End Function
End Class
|
|
'------------------------------------------------------------------------------
' Poner una aplicación dentro de un formulario (14/Ago/05)
'
' ©Guillermo 'guille' Som, 2005
'------------------------------------------------------------------------------
Imports Microsoft.VisualBasic
Imports System
Imports System.Windows.Forms
Imports System.Drawing
Public Class Form1
Inherits System.Windows.Forms.Form
'
Private hWndNotepad As IntPtr = IntPtr.Zero
'
<STAThread()> _
Shared Sub Main()
Application.EnableVisualStyles()
Application.DoEvents()
'
Application.Run(New Form1)
End Sub
Public Sub New()
MyBase.New()
'El Diseñador de Windows Forms requiere esta llamada.
InitializeComponent()
'Agregar cualquier inicialización después de la llamada a InitializeComponent()
End Sub
'
#Region " Código generado por el Diseñador de Windows Forms "
#End Region
Private Sub btnFormRun_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles btnFormRun.Click
' Iniciar el proceso indicado en txtApp en un formulario
Dim frm2 As New FormContenedor
frm2.chkAjustarAuto.Checked = True
frm2.IniciarApp(txtApp.Text)
frm2.Show()
End Sub
Private Sub PanelApp_Resize(ByVal sender As Object, _
ByVal e As System.EventArgs) _
Handles PanelApp.Resize
' Ajustar automáticamente el tamaño del contenido, si así se ha indicado
If hWndNotepad.ToInt32 > 0 Then
If chkAjustarAuto.Checked Then
WinApi.MoveWindow(hWndNotepad, 0, 0, _
PanelApp.Bounds.Width - 4, PanelApp.Bounds.Height - 4, 1)
End If
End If
End Sub
Private Sub btnPanelRun_Click(ByVal sender As Object, _
ByVal e As System.EventArgs) _
Handles btnPanelRun.Click
' Iniciar el proceso indicado en txtApp
Dim proc As New System.Diagnostics.Process
proc.StartInfo.FileName = txtApp.Text
proc.Start()
hWndNotepad = WinApi.FindWindow(Nothing, proc.MainWindowTitle)
If hWndNotepad.ToInt32 > 0 Then
WinApi.MoveWindow(hWndNotepad, 0, 0, _
PanelApp.Bounds.Width - 4, PanelApp.Bounds.Height - 4, 1)
WinApi.SetParent(hWndNotepad, PanelApp.Handle)
Else
proc.Close()
hWndNotepad = IntPtr.Zero
End If
End Sub
Private Sub btnPanel_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles btnPanel.Click
' Capturar la ventana con el título indicado en txtApp
hWndNotepad = WinApi.FindWindow(Nothing, txtApp.Text)
If hWndNotepad.ToInt32 > 0 Then
WinApi.MoveWindow(hWndNotepad, 0, 0, _
PanelApp.Bounds.Width - 4, PanelApp.Bounds.Height - 4, 1)
WinApi.SetParent(hWndNotepad, PanelApp.Handle)
Else
hWndNotepad = IntPtr.Zero
End If
End Sub
Private Sub btnForm_Click(ByVal sender As Object, _
ByVal e As EventArgs) _
Handles btnForm.Click
' Capturar la aplicación con el título indicado en txtApp
' y mostrarlo en otro formulario
Dim frm2 As New FormContenedor
frm2.chkAjustarAuto.Checked = True
frm2.CapturarApp(txtApp.Text)
frm2.Show()
End Sub
Private Sub chkAjustarAuto_CheckedChanged(ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles chkAjustarAuto.CheckedChanged
' Ajustar automáticamente el tamaño del contenido, si así se ha indicado
If hWndNotepad.ToInt32 > 0 Then
If chkAjustarAuto.Checked Then
WinApi.MoveWindow(hWndNotepad, 0, 0, _
PanelApp.Bounds.Width - 4, PanelApp.Bounds.Height - 4, 1)
End If
End If
End Sub
End Class
|
|
'------------------------------------------------------------------------------
' Formulario contenedor de aplicaciones (14/Ago/05)
'
' ©Guillermo 'guille' Som, 2005
'------------------------------------------------------------------------------
Imports Microsoft.VisualBasic
Imports System
Imports System.Windows.Forms
Imports System.Drawing
Public Class FormContenedor
Inherits System.Windows.Forms.Form
'
#Region " Código generado por el Diseñador de Windows Forms "
#End Region
'
Private hWndApp As IntPtr = IntPtr.Zero
'
Public Sub IniciarApp(ByVal nombre As String)
' Iniciar el proceso indicado en el parámetro
Dim proc As New System.Diagnostics.Process
proc.StartInfo.FileName = nombre
proc.Start()
hWndApp = WinApi.FindWindow(Nothing, proc.MainWindowTitle)
CapturarApp(proc.MainWindowTitle)
End Sub
Public Sub CapturarApp(ByVal titulo As String)
' Captura la ventana con el título indicado y la incrusta en el formulario
hWndApp = WinApi.FindWindow(Nothing, titulo)
If hWndApp.ToInt32 > 0 Then
WinApi.MoveWindow(hWndApp, 0, 0, Me.ClientRectangle.Width, _
Me.ClientRectangle.Height - 4 - chkAjustarAuto.Height, 1)
WinApi.SetParent(hWndApp, Me.Handle)
Else
hWndApp = IntPtr.Zero
Close()
End If
End Sub
'
Private Sub FormContenedor_Resize(ByVal sender As Object, _
ByVal e As EventArgs) _
Handles MyBase.Resize
' Ajustar automáticamente el tamaño del contenido, si así se ha indicado
If hWndApp.ToInt32 > 0 Then
If chkAjustarAuto.Checked Then
WinApi.MoveWindow(hWndApp, 0, 0, Me.ClientRectangle.Width, _
Me.ClientRectangle.Height - 4 - chkAjustarAuto.Height, 1)
End If
End If
End Sub
Private Sub chkAjustarAuto_CheckedChanged(ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles chkAjustarAuto.CheckedChanged
' Ajustar también al pulsar en el checkbox
If chkAjustarAuto.Checked Then
FormContenedor_Resize(Nothing, Nothing)
End If
End Sub
End Class
|
El código para C#
|
|
//-----------------------------------------------------------------------------
// Clase con las funciones del API de Windows (14/Ago/05)
// Las funciones están declaradas como compartidas
// para usarlas sin crear una instancia.
//
// ©Guillermo 'guille' Som, 2005
//-----------------------------------------------------------------------------
using System;
namespace ponerAppDentroFormCS
{
public class WinApi
{
// Hace que una ventana sea hija (o esté contenida) en otra
[System.Runtime.InteropServices.DllImport("user32.dll")]
public extern static IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
// Devuelve el Handle (hWnd) de una ventana de la que sabemos el título
[System.Runtime.InteropServices.DllImport("user32.dll")]
public extern static IntPtr FindWindow(string lpClassName, string lpWindowName);
// Cambia el tamaño y la posición de una ventana
[System.Runtime.InteropServices.DllImport("user32.dll")]
public extern static int MoveWindow(IntPtr hWnd, int x, int y,
int nWidth, int nHeight, int bRepaint);
}
}
|
|
//-----------------------------------------------------------------------------
// Poner una aplicación dentro de un formulario (14/Ago/05)
//
// ©Guillermo 'guille' Som, 2005
//-----------------------------------------------------------------------------
using System;
using System.Windows.Forms;
using System.Drawing;
namespace ponerAppDentroFormCS
{
public class Form1 : System.Windows.Forms.Form
{
private IntPtr hWndNotepad = IntPtr.Zero;
//
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.DoEvents();
//
Application.Run(new Form1());
}
public Form1() : base()
{
//El Diseñador de Windows Forms requiere esta llamada.
InitializeComponent();
//Agregar cualquier inicialización después de la llamada a InitializeComponent()
btnFormRun.Click += new System.EventHandler(btnFormRun_Click);
PanelApp.Resize += new System.EventHandler(PanelApp_Resize);
btnPanelRun.Click += new System.EventHandler(btnPanelRun_Click);
btnPanel.Click += new System.EventHandler(btnPanel_Click);
chkAjustarAuto.CheckedChanged += new System.EventHandler(chkAjustarAuto_CheckedChanged);
btnForm.Click += new EventHandler(btnForm_Click);
}
//
#region Código generado por el Diseñador de Windows Forms
#endregion
//
private void btnFormRun_Click(System.Object sender, System.EventArgs e)
{
// Iniciar el proceso indicado en txtApp en un formulario
FormContenedor frm2 = new FormContenedor();
frm2.chkAjustarAuto.Checked = true;
frm2.IniciarApp(txtApp.Text);
frm2.Show();
}
private void PanelApp_Resize(object sender, System.EventArgs e)
{
// Ajustar automáticamente el tamaño del contenido, si así se ha indicado
if( hWndNotepad.ToInt32() > 0 )
if( chkAjustarAuto.Checked )
WinApi.MoveWindow(hWndNotepad,
0, 0,
PanelApp.Bounds.Width - 4, PanelApp.Bounds.Height - 4, 1);
}
private void btnPanelRun_Click(object sender, System.EventArgs e)
{
// Iniciar el proceso indicado en txtApp
System.Diagnostics.Process proc = new System.Diagnostics.Process();
proc.StartInfo.FileName = txtApp.Text;
proc.Start();
hWndNotepad = WinApi.FindWindow(null, proc.MainWindowTitle);
if( hWndNotepad.ToInt32() > 0 )
{
WinApi.MoveWindow(hWndNotepad,
0, 0,
PanelApp.Bounds.Width - 4, PanelApp.Bounds.Height - 4, 1);
WinApi.SetParent(hWndNotepad, PanelApp.Handle);
}
else
{
proc.Close();
hWndNotepad = IntPtr.Zero;
}
}
private void btnPanel_Click(System.Object sender, System.EventArgs e)
{
// Capturar la ventana con el título indicado en txtApp
hWndNotepad = WinApi.FindWindow(null, txtApp.Text);
if( hWndNotepad.ToInt32() > 0 )
{
WinApi.MoveWindow(hWndNotepad,
0, 0,
PanelApp.Bounds.Width - 4, PanelApp.Bounds.Height - 4, 1);
WinApi.SetParent(hWndNotepad, PanelApp.Handle);
}
else
hWndNotepad = IntPtr.Zero;
}
private void btnForm_Click(object sender, EventArgs e)
{
// Capturar la aplicación con el título indicado en txtApp
// y mostrarlo en otro formulario
FormContenedor frm2 = new FormContenedor();
frm2.chkAjustarAuto.Checked = true;
frm2.CapturarApp(txtApp.Text);
frm2.Show();
}
private void chkAjustarAuto_CheckedChanged(System.Object sender, System.EventArgs e)
{
// Ajustar automáticamente el tamaño del contenido, si así se ha indicado
if( hWndNotepad.ToInt32() > 0 )
if( chkAjustarAuto.Checked )
WinApi.MoveWindow(hWndNotepad,
0, 0,
PanelApp.Bounds.Width - 4, PanelApp.Bounds.Height - 4, 1);
}
}
}
|
|
//-----------------------------------------------------------------------------
// Formulario contenedor de aplicaciones (14/Ago/05)
//
// ©Guillermo 'guille' Som, 2005
//-----------------------------------------------------------------------------
using System;
using System.Windows.Forms;
using System.Drawing;
namespace ponerAppDentroFormCS
{
public class FormContenedor : System.Windows.Forms.Form
{
//
public FormContenedor() : base()
{
//El Diseñador de Windows Forms requiere esta llamada.
InitializeComponent();
//Agregar cualquier inicialización después de la llamada a InitializeComponent()
base.Resize += new EventHandler(FormContenedor_Resize);
chkAjustarAuto.CheckedChanged += new System.EventHandler(chkAjustarAuto_CheckedChanged);
}
//
#region Código generado por el Diseñador de Windows Forms
#endregion
//
private IntPtr hWndApp = IntPtr.Zero;
//
public void IniciarApp(string nombre)
{
// Iniciar el proceso indicado en el parámetro
System.Diagnostics.Process proc = new System.Diagnostics.Process();
proc.StartInfo.FileName = nombre;
proc.Start();
hWndApp = WinApi.FindWindow(null, proc.MainWindowTitle);
CapturarApp(proc.MainWindowTitle);
}
public void CapturarApp(string titulo)
{
// Captura la ventana con el título indicado y la incrusta en el formulario
hWndApp = WinApi.FindWindow(null, titulo);
if( hWndApp.ToInt32() > 0 )
{
WinApi.MoveWindow(hWndApp,
0, 0,
this.ClientRectangle.Width,
this.ClientRectangle.Height - 4 - chkAjustarAuto.Height, 1);
WinApi.SetParent(hWndApp, this.Handle);
}
else
{
hWndApp = IntPtr.Zero;
Close();
}
}
//
private void FormContenedor_Resize(object sender, EventArgs e)
{
// Ajustar automáticamente el tamaño del contenido, si así se ha indicado
if( hWndApp.ToInt32() > 0 )
if( chkAjustarAuto.Checked )
WinApi.MoveWindow(hWndApp,
0, 0,
this.ClientRectangle.Width,
this.ClientRectangle.Height - 4 - chkAjustarAuto.Height, 1);
}
private void chkAjustarAuto_CheckedChanged(System.Object sender, System.EventArgs e)
{
// Ajustar también al pulsar en el checkbox
if( chkAjustarAuto.Checked )
FormContenedor_Resize(null, null);
}
}
}
|