Se ve rebonito ¿no? Como es transparente atrás pueden ver mis gadgets y todo el asunto. Bueno, pero no estamos aquí por lo bonito sino por lo funcional.
2) Posteriormente vamos a crear el achivo XAML que cargaremos dinámicamente, pueden ustedes añadirlo a su proyecto o bien escribirlo en el Notepad
Recuerden que este archivo es el que cargaremos dinámicamente por lo que NO es necesario que lo agreguen a la solución, pueden ponerlo en cualquier carpeta de su PC y como dije anteriormente si quieren sólo darle copy a este código y paste en el notepad :D
En este archivo definiremos lo siguiente
<StackPanel Margin="35.704347826087,16.5478260869566,35.1652173913044,24.3130434782609" MinHeight="50" MinWidth="50" Name="StackPanel1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel.Background>
<RadialGradientBrush xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<GradientStop xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Color="GreenYellow" Offset=".3"></GradientStop>
<GradientStop xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Color="Red" Offset=".7"></GradientStop>
<GradientStop xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Color="Blue" Offset=".2"></GradientStop>
</RadialGradientBrush>
</StackPanel.Background>
<Label xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Height="28.8039130434783" Margin="90.1172463768117,27.7265217391304,86.0869565217392,0" Name="Label1" VerticalAlignment="Center" FontSize="16" >Soy un objeto del tipo label!</Label>
<TextBox xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Opacity=".5" Margin="56.6608695652174,114.008695652174,66.8869565217391,114.469565217391" Name="TextBox1" VerticalAlignment ="Top" ></TextBox>
<Button xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Tag="Click,Saludar1" Height="23" Margin="100.382608695652,0,103.617391304348,36.8347826086957" Name="Button1" VerticalAlignment="Bottom" >Haga click en mi</Button>
<Button xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Tag="MouseLeave,Saludar2" Height="23" Margin="100.382608695652,0,103.617391304348,36.8347826086957" Name="Button2" VerticalAlignment="Bottom" >Pase el mouse sobre mi</Button>
</StackPanel>
Como podemos ver estamos definiendo un objeto contenedor llamado StackPanel1 que adentro tiene varios controles (un label , un textbox y dos botnones) y que define un fondito de gradiente circular. Ignoraremos por un momento las letras monstruo, posteriormente veremos cual es su objetivo… Yo nombré a este archivo Serializado.xaml
Ahora viene lo bueno vamos a tirar código para cargar dinámicamente el archivo XAML, iremos al codebehind de nuestro archivo Window1.xaml (es decir, NO el que acabamos de hacer, sino el anterior al que acabamos de hacer, es decir NO el de los stackpanels, es decir el que tiene el GridContenedor, quedó claro? ;)) y añadiremos el siguiente código
Public Sub LeerXAML(ByVal s As Object, ByVal e As System.Windows.RoutedEventArgs) _
Handles Botoncito.Click
Try
'Escondo el boton para ver el XAML cargado
Me.Botoncito.Visibility = Windows.Visibility.Hidden
'Abro un streamreader para cargar el XAML que esta en un archivo, cambiar
'la ruta definida aquí por la ruta donde se encuentre el archivo a cargar dinamico
Dim lobjLectorStream As StreamReader = New StreamReader("C:\Users\Bichi\Documents\Visual Studio 2005\Projects\XAMLDinamico\XAMLDinamico\XAMLDinamico\Serializado.xaml")
'Declaro e instancio un stackpanel
Dim lobjPanel As New StackPanel
Dim lobjLectorXAML As Object = XamlReader.Load(lobjLectorStream.BaseStream)
'Añado el stackpanel (y sus objetos children) al gridcontenedor del window
Me.GridContenedor.Children.Add(CType(lobjLectorXAML, StackPanel))
Catch ex As Exception
MsgBox("Ocurrio el siguiente error " & ex.ToString())
End Try
End Sub
Al momento de correr este código deberíamos ver lo siguiente:

Como podemos ver hemos hecho uso del método Load de la clase XamlReader; lo cual nos ha permitido cargar un archivo externo a nuestra aplicación y añadirlo en tiempo de ejecución a la ventana Window1. Sorprendente ¿no?
Sin embargo aunque todo parezca muy bonito, como dijimos anteriormente existe un problema con esta manera de deserializar un archivo en xaml y cargarlo dinámicamente, y este problema se refiere a que no es posible guardar información de eventos ni código ni siquiera con la etiqueta <x:Code> entonces nosotros llevaremos un poco más allá el ejemplo y definiremos una manera relativamente sencilla para guardar ese información de los eventos y mapearla hacia código administrado. Evidentemente si actualmente hacemos clic sobre cualquiera de los botones no hacen nada. Vamos a utilizar el mismo markup para obtener la información a los eventos a los que nos queremos suscribir. Es aquí donde haremos uso del código escrito en letrotas de nuestro archivo serializado.xaml
Regresando a dicho archivo podemos encontrar nosotros en la definición del botón que hemos hecho uso de la propiedad Tag; como todos sabemos esta propiedad súper añeja sirve para guardar información acerca de un control que en pocas palabras no cabe ningún otro lado.
En el primer button hemos definido Tag="Click,Saludar1", mientras que en el segundo Tag="MouseLeave,Saludar2" no hace falta pensar mucho para suponer que en el primer botón suscribiremos al evento click en el método saludar1, y el segundo botón será suscrito en el evento MouseLeave al método Saludar2.
Es importante señalar que las funciones y métodos que serán invocados al momento de realizar el mecanismo de publicación suscripción deberán en el ensamblado de aplicación que cargará dinámicamente el archivo XAML, aunque también es posible cargarlos en otra dll haciendo ajustes mínimos.
3) Vamos a modificar ahora la función LeerXAML a la cual agregaremos un ciclo for each que se encargará de barrer todos los objetos cargados dinámicamente y en el caso de que su tipo sea button utilizaremos reflection para ser la asignación del evento al método definido en el markup. Quiero aclarar que aun en el escenario que utilizaramos C# para este ejemplo de todos modos debemos de usar reflexión toda vez que no sabemos a que evento se quiere suscribir el objeto debido a que esa definición se encuentra en el XAML, por lo que no podrán utilizar el operador += de C# o la sentencia AddHandler de Visual Basic para suscribir el evento debido a que no lo sabemos que evento vendrá definido en el archivo XAML.
Pero como bien dicen un código es mejor que 1000 palabras, así que aquí les va el poder:
Public Sub LeerXAML(ByVal s As Object, ByVal e As System.Windows.RoutedEventArgs) _
Handles Botoncito.Click
Try
'Escondo el boton para ver el XAML cargado
Me.Botoncito.Visibility = Windows.Visibility.Hidden
'Abro un streamreader para cargar el XAML que esta en un archivo
Dim lobjLectorStream As StreamReader = New StreamReader("C:\Users\Bichi\Documents\Visual Studio 2005\Projects\XAMLDinamico\XAMLDinamico\XAMLDinamico\Serializado.xaml")
'Declaro e instancio un stackpanel
Dim lobjPanel As New StackPanel
Dim lobjLectorXAML As Object = XamlReader.Load(lobjLectorStream.BaseStream)
'Hago un barrido de todos los objetos Button para asociarlos al manejador
'dinamico de eventos
Dim lobjBoton As Button
Dim lstrInfoSuscripcion() As String
For Each lobjControl As Control In CType(lobjLectorXAML, StackPanel).Children
If lobjControl.GetType() Is GetType(Button) Then
lobjBoton = CType(lobjControl, Button)
lstrInfoSuscripcion = lobjBoton.Tag.ToString.Split(",")
'Utilizo reflection para hacer la suscripción dinámica al evento
Select Case lstrInfoSuscripcion(0)
Case "Click"
AgregarManejadorEvento(lobjBoton, lstrInfoSuscripcion(0), _
New RoutedEventHandler(AddressOf InvocarMetodo))
Case "MouseLeave"
AgregarManejadorEvento(lobjBoton, lstrInfoSuscripcion(0), _
New Input.MouseEventHandler(AddressOf InvocarMetodo))
End Select
MsgBox("Se realizó exitosamente la suscripción del evento " & lstrInfoSuscripcion(0) & _
" al método " & lstrInfoSuscripcion(1) & _
" en el objeto " & lobjControl.Name)
End If
Next
'Añado el stackpanel (y sus objetos children) al gridcontenedor del window
Me.GridContenedor.Children.Add(CType(lobjLectorXAML, StackPanel))
Catch ex As Exception
MsgBox("Ocurrio el siguiente error " & ex.ToString())
End Try
End Sub
Como podemos ver en el código anterior para cada uno de los eventos que tengamos contemplados manejar debemos hacer la suscripción personalizada debido a que cada manejador de evento recibe parámetros diferentes de acuerdo al tipo de delegado definido, por lo cual elegí hacer una sobrecarga de un método al que llamé InvocarMetodo. La suscripción como tal, que tiene el código de reflexión se hace en el método AgregarManejadorEvento, que presento a continuación
Public Function AgregarManejadorEvento(ByVal lobjControl As Object, _
ByVal lstrNombreEvento As String, _
ByVal ldelManejadorEvento As [Delegate]) As _
Boolean
Dim ltypTipo As Type = lobjControl.GetType()
' Obtiene la informacion del evento si no la encuentra se sale
Dim lobjInfoEvento As EventInfo = ltypTipo.GetEvent(lstrNombreEvento)
If lobjInfoEvento Is Nothing Then Return False
Try
lobjInfoEvento.AddEventHandler(lobjControl, ldelManejadorEvento)
Return True
Catch ex As Exception
MsgBox("Ocurrió un error al suscribir el evento " & ex.ToString())
Return False
End Try
End Function
Y estos son los métodos sobrecargados que invocan a los métodos definidos en el markup previa suscripción al evento
Public Sub InvocarMetodo(ByVal o As Object, ByVal e As RoutedEventArgs)
Dim lstrMetodoAInvocar() As String = o.tag.ToString.Split(",")
Try
MsgBox("Se intentará invocar el método " & lstrMetodoAInvocar(1))
Dim lobjMetodoAInvocar As Object = GetType(Window1).InvokeMember(lstrMetodoAInvocar(1), _
BindingFlags.Default Or _
BindingFlags.InvokeMethod, Nothing, Nothing, Nothing)
Catch ex As System.MissingMethodException
MsgBox("Imposible encontrar el método " & lstrMetodoAInvocar(1))
End Try
End Sub
Public Sub InvocarMetodo(ByVal o As Object, ByVal e As Input.MouseEventArgs)
Dim lstrMetodoAInvocar() As String = o.tag.ToString.Split(",")
Try
MsgBox("Se intentará invocar el método " & lstrMetodoAInvocar(1))
Dim lobjMetodoAInvocar As Object = GetType(Window1).InvokeMember(lstrMetodoAInvocar(1), _
BindingFlags.Default Or _
BindingFlags.InvokeMethod, Nothing, Nothing, Nothing)
Catch ex As System.MissingMethodException
MsgBox("Imposible encontrar el método " & lstrMetodoAInvocar(1))
End Try
End Sub
4) Por último vamos a definir los métodos concretos que serán invocados después de la suscripción, es decir Saludar1 y Saludar2 definidos en el markup.
Public Shared Sub Saludar1()
MsgBox("Invocación exitosa" & Chr(13) & _
"Saludos a todos desde el metodo click descrito en el XAML!")
End Sub
Public Shared Sub Saludar2()
MsgBox("Invocación exitosa" & Chr(13) & _
"Saludos a todos desde el metodo mouseover descrito en el XAML!")
End Sub
Al momento de ejecutar ahora nuestra aplicación, en el momento de la carga deberíamos ver los siguientes mensajes mostrados desde el método leer XAML

Ahora al hacer click sobre el primer botón deberíamos ver algo como esto:

Y posteriormente

Conclusión:
En este artículo hemos demostrado la manera de cargar dinámicamente un archivo de XAML y renderearlo Just In Time (dinámicamente), además aprendimos un workaround para, de alguna manera, persistir eventos en un archivo de markup utilizando el poder de Reflection.
Como siempre los invito a que examinen en código y mejoren la implementación original, me alegrará saber que les ha sido útil. Además a partir de este artículo y como lo prometimos anteriormente dejamos el código fuente listo para descargar.
Cualquier duda sobre este u otros artículos, siempre ando por los foros.
Saludos y de parte de todos
¡Feliz Codificación!
Miguel Ángel Morán B.
Microsoft MVP Visual Developer