Miguel Angel Morán

Machines take me by surprise with great frecuency...

May 2007 - Artículos

Workflow Foundation: El Banco del Workflow

El "Banco del Workflow" es un proyecto que hice para la gira de Pronet CodeCamp y que si fueron a cualquiera de estos eventos, lo debieron de haber visto ya sea presentado por mi o por otros speakers. El proyecto se trata de un banquito que funciona con puras reglas de negocio basadas en flujos de trabajo. Contiene 7 demos que implementan bastante funcionalidad de Workflow Foundation, entre ella:

  • Hostear el runtime de Workflow
  • Invocación de webservices
  • Utilización de actividades paralelas
  • Creación de actividades personalizadas (además de esto será mi siguiente post, pero por si no pueden esperar pueden bajar el demo de una vez jeje)
  • Utilización de runtime services (se muestra tracking services)
  • Exponiendo workflows como webservices
  • Utilizando XOML para  definir webservices

En la gira de Pronet lo máximo a lo que llegué es al demo 3 porque se me acababa el tiempo juar juar, así que aunque hayan asistido a la gira encontrarán harto contenido inédito.

El zip que ustedes pueden bajar aquí, trae las instrucciones de instalación, la base de datos que utiliza para la operación de los webservices y para utilizar el servicio de tracking services, y los webservices utilizados como tal.

El proyecto tiene dos carpetas

  • Solution: Tiene el contenido listo para usarse previa instalación del proyecto
  • Starter: Tiene los "templates" para que ustedes puedan seguir las instrucciones que encontrarán aquí  que es un documento donde viene paso a paso como hacer el demo y llegar al objetivo.

Espero que lo encuentren interesante, creo que es un buen comienzo para que se introduzcan en el estudio de Workflow Foundation.

A continuación unos pantallazos del proyecto:

 

 

Saludos a todos y

 

¡Feliz Codificación!


Miguel A. Morán B.
Microsoft MVP Visual Developer

Cargando interfaces gráficas y eventos dinámicamente con XAML

Introducción

Una de las cosas que más me agradan de los lenguajes basados en XML es la capacidad de poder ser cargados en tiempo de ejecución si necesidad de recompilar el código completo.

Esto trae muchos beneficios sobre todo en las empresas que requieren una operación de altísima disponibilidad y que no pueden parar ni un segundo su operación o en aquellas organizaciones cuyos datos son muy cambiantes y que requieren una gran agilidad para poder operar adecuadamente y de acuerdo al ritmo que su propio negocio les va dictando.

Tanto Windows Presentation Foundation, como Windows Workflow Foundation, como Windows Communication Foundation soportan en diferentes formas y a diferentes niveles el cargado dinámico de markup para ofrecernos los beneficios anteriormente descritos.

En el caso de WF es posible describir completamente en XOML un flujo de trabajo y pasar del motor de ejecución (Workflow Runtime) de flujos de trabajo la ruta de un archivo de markup, en el caso de WCF toda la configuración de los “fierros” (como canales de comunicación, contratos, etc) se hace a través de configuración en archivos basados en XML.

En el caso de WPF, que es el tema que trataremos en este artículo, es posible cargar dinámicamente un archivo XAML y generar un objeto a partir de él para añadirlo la jerarquía de objetos que se encuentren cargados dentro de la ventana o página de WPF.

Ahora bien, como en todo, no todo es miel sobre hojuelas. El hecho de que un motor de ejecución tenga que cargar dinámicamente lenguaje makup implica necesariamente poder de procesamiento lo cual de una u otra manera puede impactar en el performance general de nuestra aplicación.

Otro de los problemas a los cuales nos enfrentamos al momento de querer cargar dinámicamente un archivo de markup es que la implementación de la clase XAMLReader del .NET Framework qué es la encargada de deserializar los objetos no lee ni serializa los eventos y el código agregados a un objeto en específico es decir si nosotros definiremos por ejemplo un botón y este botón tuviera código para manejar el evento click o cualquier otro evento, al momento de ser serializado con la clase XAMLWriter perdería esta información, todo esto sucede derivado a que el XAML debe ser compilado antes de poder ser ejecutado.

 En este capítulo presentaremos una manera en la cual es posible cargar dinámicamente archivos de markup y no sólo eso, sino también presentamos una manera alternativa para persistir información de eventos y que ésta no se pierda al momento de la Serialización y que sea posible asigna dinámicamente los eventos al momento cómo hacer que el markup que se cargue tenga las instrucciones para ejecutar un código del lado del host.

 Manos a la obra

1) Para empezar definiremos una ventana transparente, ustedes, desde luego pueden quitarle o ponerle payasaditas de presentación. Lo importante de nuestro código está en jumboletrotas: Un grid llamado GridContenedor que adentro tiene un textblock y un botonzote grandote y rojo que cuando lo apretemos cargará dinámicamente un archivo XAML. El código es el siguiente

<Window AllowsTransparency="True"  WindowStyle="None"  x:Class="Window1"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    Title="DevelopersDotNet" Height="484.48695652174" Width="529.565217391304"  Opacity=".7">

 

    <Grid Name="GridContenedor">

      <TextBlock HorizontalAlignment="Center">Haz click en el boton y veras una linda sorpresa

      <LineBreak />

      <Bold>XAML Cargado dinamicamente!!!!</Bold>

      </TextBlock>

      <Button  BorderThickness="0" Name="Botoncito" Margin="80.1391304347826,96,87.6521739130435,65.9478260869563"  BorderBrush="Transparent">

 

        <Canvas Width="350" Height="300" >

          <Rectangle Width="350" Height="300" RadiusX="10" RadiusY="10"

          Stroke="Green" StrokeThickness="10" Fill="Red">

          </Rectangle>

          <TextBlock Margin="20,100,10,10" FontSize="60" Text="DevDotNet!"

    Foreground="Cyan">

        

            <TextBlock.BitmapEffect>

              <DropShadowBitmapEffect

                 Noise ="1"

                ShadowDepth="8"

                Direction="200"

                Color="Blue"

                Opacity="0.75"

                Softness="0.0" />

            </TextBlock.BitmapEffect>

           

          </TextBlock>

         </Canvas>

 

      </Button>

    </Grid>

  </Window>

 

Al momento de ejecutar la aplicación deberíamos tener un resultado como el siguiente:

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