Arquitectura – Definición de un DataHelper, Parte 2

Hola ¿Qué tal?

Continuando con la segunda parte de la definición de un DataHelper, dentro de la propuesta de arquitectura de software, continuaremos con la definición de los métodos funcionales.

En general la funcionalidad del Helper se contruye alrededor de las necesidades de los Data Access Components que definiremos más adelante y que básicamente son Insertar, Modificar, Eliminar y Consultar registros en la base de datos. Los DACs tomarán la funcionalidad provista por el Helper a través de los métodos funcionales, estos métodos se definen con alcance protegido y esto debido a que el Helper es una clase abstracta y no todo será expuesto al exterior de los Data Access Components (DACs).

Iniciemos por el método que utilizaremos para Insertar, Modificar y Eliminar registros. Este método usará una transacción aunque dado que en teoría será única la ejecución de la instrucción podría no necesitarse, sin embargo la utilizamos por si la instrucción viene de un Stored Procedure que ejecute varias instrucciones que requieran englobarse en una transacción. El método es del tipo integer y devuelve la cantidad de filas afectadas. Aquí está el método:

VB

Protected Function ExecuteTransaction(ByVal cmd As SqlCommand) As Integer

    mErrorNumber = 0

    If (Not IsValidConnection()) Then

        Return -1

    End If

 

    Dim trn As SqlTransaction = Nothing

    Dim res As Integer

 

    cmd.Connection = cnnHelper

    Using cnnHelper

        Try

            cnnHelper.Open()

            trn = cnnHelper.BeginTransaction()

            cmd.Transaction = trn

            res = cmd.ExecuteNonQuery()

            trn.Commit()

            If (res > 0) Then

                mMessageHelper = _

                    String.Format( _

                    “La operación se realizó con éxito.\nSe afectaron {0} filas”, _

                    res.ToString())

            Else

                mMessageHelper = “La operación no afectó ninguna fila”

            End If

        Catch ex As SqlException

            trn.Rollback()

            mErrorNumber = ex.Number

            mMessageHelper = ex.Message

            res = -1

        End Try

    End Using

    Return res

End Function

C#

protected int ExecuteTransaction(SqlCommand cmd)

{

    errorNumber = 0;

    if (!IsValidConnection())

        return -1;

 

    SqlTransaction trn = null;

    int res;

    cmd.Connection = cnnHelper;

    using (cnnHelper)

    {

        try

        {

            cnnHelper.Open();

            trn = cnnHelper.BeginTransaction();

            cmd.Transaction = trn;

            res = cmd.ExecuteNonQuery();

            trn.Commit();

            if (res > 0)

                messageHelper =

                    string.Format(

                    “La operación se realizó con éxito.\nSe afectaron {0} filas”,

                    res.ToString());

            else

                messageHelper = “La operación no afectó ninguna fila”;

        }

        catch (SqlException ex)

        {

            trn.Rollback();

            errorNumber = ex.Number;

            messageHelper = ex.Message;

            res = -1;

        }

    }

    return res;

}

Como podemos observar, el método recibe un parámetro de tipo SqlCommand. Dentro de la ejecución del método; primeramente se valida que la conexión se pueda utilizar, seguido de la definición de algunas variables, entre ellas la de transacción, pero más importante; se asignará la conexión al SqlCommand que se ha pasado como parámetro. Este SqlCommand viene con las definiciones necesarias para su ejecución pero completamente desconectado, ya que el Helper se encarga de manejar la conexión, todos los parámetros de este y de los demás métodos del Helper que requieran de una conexión no necesitarán incluirla pues el Helper ser las proveerá. Seguido en la ejecución, se engloba el uso de la conexión utilizando la instrucción Using, de esta manera liberamos los recursos de la conexión una vez utilizada. Tenemos una instrucción try – cath para manejar los errores que emerjan por cualquier causa en del servidor o por la ejecución de instrucciones con errores, es conveniente comentar que al controlar estos errores en el helper no se propagarán hacia niveles superiores por la seguridad en la ejecución de la aplicación, sin embargo, tendremos varias maneras de saber qué sucedió, una es por el número de error, donde siempre será cero para los casos en que no se detecten errores y un valor distinto de cero en caso de que los haya, otra manera es la descripción del error, que estará expuesta en la propiedad MessageHelper. El resto ya es práctica conocida del uso de SqlCommand.

Continuando con la funcionalidad del Helper, toca el turno a la ejecución de instrucciones SQL de forma directa, veamos, una instrucción que pueda ejecutarse a través de un SqlCommand pero que no requiera de una transacción, esto es, una simple instrucción SQL. Se puede utilizar esta instrucción para Insertar, Actualizar y Borrar de igual manera que en la anterior, lo recomendaría solo en el caso en que la instrucción SQL sea única y no implique otros movimientos, esto es, un simple Insert o Update o Delete. El caso anterior es muy parecido a este pero sin la transacción. Veamos pues:

VB

Protected Function ExecuteQuery(ByVal cmd As SqlCommand) As Integer

    mErrorNumber = 0

    Dim res As Integer

 

    If (Not IsValidConnection()) Then

        Return -1

    End If

 

    cmd.Connection = cnnHelper

    Using (cnnHelper)

        Try

            cnnHelper.Open()

            res = cmd.ExecuteNonQuery()

            If res > 0 Then

                mMessageHelper = _

                    String.Format( _

                    “La operación se realizó con éxito.\nSe afectaron {0} filas”, _

                    res.ToString())

            Else

                mMessageHelper = “La operación no afectó ninguna fila”

            End If

 

        Catch ex As SqlException

            mErrorNumber = ex.Number

            mMessageHelper = ex.Message

            res = -1

        End Try

    End Using

 

    Return res

End Function

 

C#

protected int ExecuteQuery(SqlCommand cmd)

{

    errorNumber = 0;

    int res;

    if (!IsValidConnection())

        return -1;

 

    cmd.Connection = cnnHelper;

 

    using (cnnHelper)

    {

        try

        {

            cnnHelper.Open();

            res = cmd.ExecuteNonQuery();

            if (res > 0)

                messageHelper =

                    string.Format(

                    “La operación se realizó con éxito.\nSe afectaron {0} filas”,

                    res.ToString());

            else

                messageHelper = “La operación no afectó ninguna fila”;

 

        }

        catch (SqlException ex)

        {

            errorNumber = ex.Number;

            messageHelper = ex.Message;

            res = -1;

        }

    }

    return res;

}

No hay mucho que explicar, solo que es el mismo método que el anterior solo que no utiliza un SqlTransaction.

Continuando con los métodos adicionales, toca el turno a la consulta de datos. Tenemos dos tipos de consultas, las que devuelven un único conjunto de resultados y las que devuelven varios conjuntos de resultados. Para las consultas que devuelven un solo conjunto de resultados, definiremos un método que ejecute la consulta y que devuelva el conjunto de resultados en un contenedor de datos, que en nuestro caso será un DataTable. Veamos pues este método:

VB

Protected Function GetQuery(ByVal cmd As SqlCommand) As DataTable

    mErrorNumber = 0

    Dim da As SqlDataAdapter

    Dim dt As DataTable = New DataTable()

 

    If (Not IsValidConnection()) Then

        Return dt

    End If

 

    cmd.Connection = cnnHelper

    da = New SqlDataAdapter(cmd)

 

    Try

        da.Fill(dt)

        mMessageHelper = “La operación resultó un éxito”

    Catch ex As SqlException

 

        mErrorNumber = ex.Number

        mMessageHelper = ex.Message

    End Try

 

    Return dt

End Function

 

C#

protected DataTable GetQuery(SqlCommand cmd)

{

    errorNumber = 0;

    SqlDataAdapter da;

    DataTable dt = new DataTable();

 

    if (!IsValidConnection())

        return dt;

 

    cmd.Connection = cnnHelper;

    da = new SqlDataAdapter(cmd);

 

    try

    {

        da.Fill(dt);

        messageHelper = “La operación resultó un éxito”;

    }

    catch (SqlException ex)

    {

        errorNumber = ex.Number;

        messageHelper = ex.Message;

    }

    return dt;

}

En este método utilizamos un SqlDataAdapter para poder utilizar el método Fill que se encarga de llenar el DataTable con los datos resultantes de la consulta. Como observación, solo basta asignar la conexión al data adapter una vez que se ha inicializado con el SqlCommand que vienen como parámetro de entrada, no es necesario abrir la conexión ya que el método Fill tiene la característica de que si la conexión no está abierta, la abre y ejecuta la consulta, llena el DataTable y terminando cierra la conexión. Si la conexión estuviese abierta, el método Fill la dejaría abierta. Bien, pues continuemos con lo siguiente.

El método anterior solo tiene la capacidad de devolver un DataTable con el conjunto de resultados de una consulta, sin embargo, hay ocasiones en que se ejecutan consultas que devuelven más de un conjunto de resultados y en esos casos el método anterior no sería de mucha utilidad, además de que no sería muy óptimo dividir una consulta única en varias para obtener todos los resultados por separado. Bien, para esta labor, dejaremos que Sql Server se encargue de todas las consultas de una vez y en nuestro Helper dejaremos que se encargue de devolver un único contenedor de resultados, este sería un contenedor de DataTables, o sea, un DataSet. Veamos cómo queda este método:

VB

Protected Function GetQueries(ByVal cmd As SqlCommand) As DataSet

    mErrorNumber = 0

    Dim da As SqlDataAdapter

    Dim ds As DataSet = New DataSet()

 

    If (Not IsValidConnection()) Then

        Return ds

    End If

 

    cmd.Connection = cnnHelper

    da = New SqlDataAdapter(cmd)

 

    Try

        da.Fill(ds)

        mMessageHelper = “La operación resultó un éxito”

    Catch ex As SqlException

        mErrorNumber = ex.Number

        mMessageHelper = ex.Message

    End Try

 

    Return ds

End Function

C#

protected DataSet GetQueries(SqlCommand cmd)

{

    errorNumber = 0;

    SqlDataAdapter da;

    DataSet ds = new DataSet();

 

    if (!IsValidConnection())

        return ds;

 

    cmd.Connection = cnnHelper;

    da = new SqlDataAdapter(cmd);

 

    try

    {

        da.Fill(ds);

        messageHelper = “La operación resultó un éxito”;

    }

    catch (SqlException ex)

    {

        errorNumber = ex.Number;

        messageHelper = ex.Message;

    }

    return ds;

}

Como podemos ver, este método es muy parecido al anterior, con la única diferencia de que en este utilizamos un DataSet en lugar de un DataTable, lo demás es exactamente lo mismo.

Los dos últimos métodos de este artículo son a los que llamo de información puntual, uno es para devolver un solo registro con los campos que se definan en la consulta, y el otro sería más al punto, pues se encarga de devolver un solo valor. Veamos aquí el método que devuelve un registro:

VB

Protected Function GetRecord(ByVal cmd As SqlCommand) As DataRow

    Dim dt As DataTable = GetQuery(cmd)

    If dt.Rows.Count > 0 Then

        Return dt.Rows(0)

    Else

        Return Nothing

    End If

End Function

C#

protected DataRow GetRecord(SqlCommand cmd)

{

    DataTable dt = GetQuery(cmd);

    if (dt.Rows.Count > 0)

        return dt.Rows[0];

    else

        return null;

}

Bien, pues no es mucho lo que se podría explicar de este método ya que aprovecha la funcionalidad de uno de los métodos definidos más arriba. Solo quiero advertir que se debe tener prudencia al implementar este método en la práctica, ya que por rapidez debe utilizarse con instrucciones SQL preparadas para devolver un solo registro. El método tomará el primer registro del DataTable resultante de la consulta, validando antes que tenga al menos un registro, y lo devolverá como un DataRow. Si no se tienen elementos, se devuelve null (Nothing en Visual Basic). Este método es útil para llenar los campos de un Data Access Component, como lo veremos más adelante.

VB

Protected Function GetScalar(ByVal cmd As SqlCommand) As Object

    mErrorNumber = 0

    If Not IsValidConnection() Then

        Return Nothing

    End If

 

    cmd.Connection = cnnHelper

 

    Using (cnnHelper)

        Try

            cnnHelper.Open()

            mMessageHelper = “La operación se completó con éxito”

            Return cmd.ExecuteScalar()

        Catch ex As SqlException

            mErrorNumber = ex.Number

            mMessageHelper = ex.Message

            Return Nothing

        Catch ex As InvalidCastException

            mErrorNumber = -2

            mMessageHelper = ex.Message

            Return Nothing                                                               

        End Try

    End Using

End Function

 

C#

protected object GetScalar(SqlCommand cmd)

{

    errorNumber = 0;

    if (!IsValidConnection())

        return null;

 

    cmd.Connection = cnnHelper;

 

    using (cnnHelper)

    {

        try

        {

            cnnHelper.Open();

            messageHelper = “La operación se completó con éxito”;

            return cmd.ExecuteScalar();

        }

        catch (SqlException ex)

        {

            errorNumber = ex.Number;

            messageHelper = ex.Message;

            return null;

        }

        catch (InvalidCastException ex)

        {

            errorNumber = -2;

            messageHelper = ex.Message;

            return null;

        }

    }

}

 

Este método a pesar de verse un poco más extenso, solo se encarga de exponer el método ExecuteScalar del SqlCommand validando la conexión y manejando los errores.

Con estos métodos le damos al Helper la capacidad funcional completa, con ello se podrán hacer todas las operaciones que un Data Access Component requiere, sin embargo, tenemos los Business Components, que se encargan de administrar las transacciones a diferencia del Helper que solo administra la conexión, un BC siempre administra las transacciones, pero esos métodos los veremos en la siguiente parte de este documento, a los cuales les llamo… los métodos transaccionales.

También pueden leer la primera parte del documento en:
http://msmvps.com/blogs/otelis/archive/2009/03/11/arquitectura-definici-243-n-de-un-datahelper-parte-1.aspx

El código de descarga lo encuentran en:
http://code.msdn.microsoft.com/datahelper

Continuar con la siguiente parte:
http://msmvps.com/blogs/otelis/archive/2009/03/15/arquitectura-definici-243-n-de-un-datahelper-parte-3.aspx

Saludos…

Octavio Telis


 

Arquitectura – Definición de un DataHelper, Parte 1

Hola ¿Qué tal?…


En esta ocasión continuando con la propuesta de arquitectura que comenté en el último artículo de arquitectura (http://msmvps.com/blogs/otelis/archive/2008/01/08/hablemos-de-arquitectura-de-software.aspx)  quiero comenzar por establecer primeramente las directrices basadas enteramente en el documento titulado “Application Architecture for .NET: Designing Applications and Services” (http://msdn2.microsoft.com/en-us/library/ms954595.aspx). En principio estaré enfocado al capítulo 2 de este documento para definir simplemente la base de esta propuesta.


Considerando que encontramos tres grandes grupos de componentes, divididos en Presentación, Negociación y Datos, quisiera empezar primeramente por la parte más baja, donde encontramos a los datos, para iniciar desde ahí. El por qué empezar desde este punto es por definición de donde continúa la construcción de una aplicación, donde primeramente se diseña la base de datos y en seguida se construye la aplicación.


De acuerdo a las recomendaciones, cada tabla de la base de datos debe corresponderse con un componente de acceso a datos, en mi percepción, genero un componente de acceso a datos por tabla siguiendo ciertos criterios que posteriormente mostraré. Inicialmente, debo definir un DataHelper, que será el encargado de establecer la conexión con la base de datos, y que será dónde la conexión se administre y viva, ahí se ejecutará cualquier acción dirigida a la base de datos, acciones como, consultas, modificaciones y hasta alteraciones a la estructura de la base de datos, así pues, debemos entender que la conexión con la base de datos solo estará en este componente, solo en el Helper y en ninguna otra parte. La definición en el modelo que propongo será sobre una clase abstracta y deberá ser heredada en cada componente de acceso a datos. El criterio para definirla abstracta, es porque no debe ser instanciada de forma directa, así los componentes de acceso a datos serán quienes tengan la capacidad de exponer la funcionalidad con la base de datos totalmente específica de cada uno de estos de un modo íntimo y discreto, exponiendo únicamente los métodos que le concierne a los componentes que los consuman.


Este Helper contará con el soporte de una interfaz cuya función será propagar la información necesaria para conectarse a la base de datos, esto es, la información básica de usuario, servidor, base de datos y contraseña, con esta interfaz podremos inicializar el helper y darle capacidad de interactuar con la base de datos. Esta información se propagará en el constructor de cada componente de acceso a datos que se derive del helper. Es importante mencionar que esta interfaz es la chispa de vida de cada componente de acceso a datos y que sin ella nada funcionará.


Entonces, teniendo en cuenta esto podemos empezar a definir la estructura de nuestro helper. Veamos entonces de una buena vez cómo definimos la clase de este componente.


VB


Namespace DataHelper


    Public MustInherit Class SqlDataHelper


        Public Sub New(ByVal sqlSet As DBInfo.IDbSettings)


 


        End Sub


    End Class


End Namespace


C#


namespace DataHelper


{


    public abstract class SqlDataHelper


    {


        public SqlDataHelper(DBInfo.IDbSettings sqlSet)


        {


 


        }


    }


}


 


La interfaz que sirve de apoyo al transporte de la información de conexión de la base de datos estaría definida de la siguiente manera:


VB


Namespace DBInfo


 


    Public Interface IDbSettings


        Property Server() As String


        Property DataBase() As String


        Property User() As String


        Property Password() As String


        Function GetConnectionString() As String


    End Interface


 


End Namespace


C#


namespace DBInfo


{   


    public interface IDbSettings


    {


        string Server{get;set;}


        string DataBase{get;set;}


        string User{get;set;}


        string Password{get;set;}


        string GetConnectionString();


    }


}


Nótese que se tienen dos espacios de nombre, el primero totalmente relacionado al DataHelper y el segundo relacionado a información de la Base de Datos, he decidido dejarlos separados para no mezclar conceptos.


Ahora bien, ya definimos la clase y la interfaz de información de conexión, sin embargo, habrá todavía más que hacer, empezaré terminando con la definición básica del DataHelper, el cual es alusivo a SQL Server; por cierto.


Lo siguiente será definir la funcionalidad del constructor, y declarar la variable de conexión con la que se trabajará en las operaciones relacionadas con el mismo apuntando a una base de datos.


Inicialmente debemos declarar una variable del tipo SqlConnection, para utilizarla en todo el helper, utilizaremos varias variables adicionales para devolver los mensajes que se generen dentro de cada método del helper, además de los mensajes nativos del servidor y los números de error en caso de que se presenten y por si fuera poco, también una variable que indique si la conexión es buena o no y una variable para almacenar la información de conexión para tenerla disponible en todo momento dentro del helper. Así pues tendremos las siguientes declaraciones iníciales:


VB


Public MustInherit Class SqlDataHelper


    Private cnnHelper As SqlConnection


    Private mMessageHelper As String


    Private mMessageServer As String


    Private mErrorNumber As Integer


    Private mIsValidConnection As Boolean


    Private mSqlSettings As IDbSettings


 


    Public Sub New(ByVal sqlSet As DBInfo.IDbSettings)


 


    End Sub


End Class


C#


public abstract class SqlDataHelper


{


    private SqlConnection cnnHelper;


    private string messageHelper;


    private string messageServer;


    private int errorNumber;


    private bool isValidConnection;


    private IDbSettings sqlSettings;


 


    public SqlDataHelper(DBInfo.IDbSettings sqlSet)


    {


    }


}


Continuando con el constructor, instanciaremos la variable de conexión, además deberemos verificar que la conexión es válida para no caer en errores de conexión al intentar utilizar algún método de nuestro helper, por lo que definiremos un método para esta tarea. Al verificar la valides de la conexión podremos agregar un manejador para el evento que devuelve información del servidor cada que se utiliza la conexión. Este evento es el denominado InfoMessage de la conexión. No nos olvidemos del método manejador del evento InfoMessage de la conexión. Quedando la clase de esta manera:


VB


Public MustInherit Class SqlDataHelper


    Private cnnHelper As SqlConnection


    Private mMessageHelper As String


    Private mMessageServer As String


    Private mErrorNumber As Integer


    Private mIsValidConnection As Boolean


    Private mSqlSettings As IDbSettings


 


    Public Sub New(ByVal sqlSet As DBInfo.IDbSettings)


 


        mSqlSettings = sqlSet


        cnnHelper = New SqlConnection()


 


        If IsValidConnection() Then


            AddHandler cnnHelper.InfoMessage, _


            AddressOf cnnHelper_InfoMessage


        End If


 


    End Sub


 


    Private Sub cnnHelper_InfoMessage( _


            ByVal sender As Object, _


            ByVal e As SqlInfoMessageEventArgs)


 


        mMessageServer = e.Message


 


    End Sub


 


    Private Function IsValidConnection() As Boolean


 


        mErrorNumber = 0


        If cnnHelper.State = ConnectionState.Open Then


            cnnHelper.Close()


        End If


 


        cnnHelper.ConnectionString = mSqlSettings.GetConnectionString()


 


        Try


 


            If Not IsValidConnection Then


                cnnHelper.Open()


                cnnHelper.Close()


            End If


 


            mIsValidConnection = True


            mMessageHelper = “Conexión exitosa”


 


        Catch ex As SqlException


 


            mIsValidConnection = False


            mErrorNumber = ex.Number


            mMessageHelper = ex.Message


 


        End Try


 


        Return mIsValidConnection


 


    End Function


 


End Class


C#


public abstract class SqlDataHelper


{


    private SqlConnection cnnHelper;


    private string messageHelper;


    private string messageServer;


    private int errorNumber;


    private bool isValidConnection;


    private IDbSettings sqlSettings;


 


    public SqlDataHelper(DBInfo.IDbSettings sqlSet)


    {


        sqlSettings = sqlSet;


        cnnHelper = new SqlConnection();


 


        if (IsValidConnection())


        {


            cnnHelper.InfoMessage +=


                new SqlInfoMessageEventHandler(cnnHelper_InfoMessage);


        }


    }


 


    private void cnnHelper_InfoMessage(object sender, SqlInfoMessageEventArgs e)


    {


        messageServer = e.Message;


    }


 


    private bool IsValidConnection()


    {


        errorNumber = 0;


        if (cnnHelper.State == ConnectionState.Open)


            cnnHelper.Close();


 


        cnnHelper.ConnectionString = sqlSettings.GetConnectionString();


        try


        {


            if (!isValidConnection)


            {


                cnnHelper.Open();


                cnnHelper.Close();


            }


            isValidConnection = true;


            messageHelper = “Conexión exitosa”;


        }


        catch (SqlException ex)


        {


            isValidConnection = false;


            errorNumber = ex.Number;


            messageHelper = ex.Message;


        }


        return isValidConnection;


    }


}


Como podrá notarse el método manejador el evento InfoMessage es muy simple, solo tiene que pasarse el valor del mensaje del servidor a nuestra variable de mensaje de servidor, por otra parte, el método validador de la conexión, utiliza la variable isValidConnection (mIsValidConnection en VB) para no intentar nuevamente la conexión si esta es válida.  La cadena de conexión para la variable cnnHelper se toma del método GetConnectionString() de la variable sqlSettings (mSqlSettings en VB) que se encarga de generar la cadena de conexión en base a los parámetros establecidos en las variables de conexión.


Hasta aquí, tenemos ya la primera parte del DataHelper, y los instrumentos que se utilizarán a través de los métodos subsecuentes del helper. La funcionalidad básica será adicionada a esta clase método por método según las necesidades de funcionalidad para con la base de datos.


Antes de continuar, terminemos el complemento de las variables declaradas a nivel de clase con las propiedades correspondientes, mismas que serán de utilidad para exponer lo que sucede dentro del helper hacia el exterior, para informes o toma de decisiones. Debemos tener en cuenta que como los valores de las variables se asignan dentro del helper las propiedades tendrán que ser de solo lectura a modo de propiedades informativas. Así pues, agreguemos las siguientes propiedades:


VB


Public ReadOnly Property SqlSettings() As IDbSettings


 


    Get


        Return mSqlSettings


    End Get


 


End Property


 


Public ReadOnly Property ErrorNumber() As Integer


 


    Get


        Return mErrorNumber


    End Get


 


End Property


 


Public ReadOnly Property MessageHelper() As String


 


    Get


        Return mMessageHelper


    End Get


 


End Property


 


Public ReadOnly Property MessageServer() As String


 


    Get


        Return mMessageServer


    End Get


 


End Property


C#


public IDbSettings SqlSettings


{


    get


    {


        return sqlSettings;


    }


}


public int ErrorNumber


{


    get


    {


        return errorNumber;


    }


}


public string MessageHelper


{


    get


    {


        return messageHelper;


    }


}


public string MessageServer


{


    get


    {


        return messageServer;


    }


}


Por el momento será todo para la primera parte, en la segunda parte terminaremos con la definición del data helper, pero aún faltará subir un peldaño con la definición del Data Access Component, así que tendremos que ir un poco más rápido con los temas para no perder la continuidad.


Pues bien, nos estaremos viendo en el siguiente post.


La liga para descarga de los archivos relacionados con este artículo:
http://code.msdn.microsoft.com/datahelper


Continuar con la siguiente parte:
http://msmvps.com/blogs/otelis/archive/2009/03/15/arquitectura-definici-243-n-de-un-datahelper-parte-2.aspx


Saludos…


Octavio Telis

Los controles contenedores en WPF

Hola ¿qué tal?


Continuando con XAML y WPF quisiera continuar en esta ocasión con el uso de los controles contenedores que se tienen disponibles para el diseño de la interfaz de usuario desde Visual Studio 2008.


Ahora vamos a ver lo que hay en el diseño de formularios con WPF que nos permitirá dibujar la interfaz de usuario de una manera ordenada. En WPF existen algunos controles que se definen como contenedores con características particulares, algunos con similitudes a los utilizados en HTML y otros utilizados típicamente de versiones anteriores y unos más que surgieron de conceptos del diseño de la interfaz de usuario de la versión anterior de Windows Forms. No iré muy a fondo en cada uno, ya que se encuentra mucho acerca de cada control en la ayuda, sin embargo, mencionar los rasgos generales nos dará una idea de su uso para ubicarnos rápidamente en la construcción de la interfaz de usuario.


Canvas


Definido por las etiquetas <Canvas></Canvas>, al utilizar este control el diseño de la interfaz de usuario se vuelve simple y permite colocar controles en el área contenedora de manera relativa a las propiedades Canvas.Left y Canvas.Top que se definen directamente en el control, cabe mencionar que la propiedad Margin se combina con dichas propiedades, esto es, Canvas.Left se suma con la distancia a la izquierda de Margin, y Canvas.Top se suma con la distancia superior de Margin. Desde el diseñador de Visual Studio se pueden mover los controles de manera libre en cualquier posición dentro del canvas.


Grid


Definido por las etiquetas <Grid></Grid>, es parecido al canvas, y se puede utilizar de igual manera, sin embargo a diferencia del anterior, en un grid se pueden definir sub regiones contenedoras establecidas por columnas (Grid.Column) y renglones (Grid.Row) similar a una tabla, creando celdas, donde los controles se posicionan relativos al área delimitada por la celda, permitiendo con esto un arreglo ordenado de la interfaz de usuario. En el diseñador de Visual Studio se puede mover el control de manera libre, sin embargo, para que se limite el control a una celda del grid se deberá especificar las propiedades Grid.Column y Grid.Row en el mismo, ya que de no hacerlo se tendrá un efecto parecido al canvas dejando al control libre en el área total del Grid.


DockPanel


Definido por las etiquetas <DockPanel></DockPanel>, que  es el reemplazo de la propiedad Dock de los controles de versiones anteriores, ahora en WPF, se utilizan paneles para definir las áreas de específicas del formulario. Es rígido con los controles, al colocarlos no es simple manipularlos en su interior, por lo que se recomienda incluir un Canvas o un Grid por comodidad, la importancia de este control es para ubicar las áreas del formulario, las cuales se definen estableciendo las propiedades VerticalAlignment para definir el Top, Center, Bottom o Stretch, y la propiedad HorizontalAlignment para definir el Left, Right, Center, Stretch. Cabe mencionar que al utilizar Stretch en ambas propiedades da un resultado parecido a Fill, también es prudente mencionar que se debe utilizar la propiedad Margin para ubicar bien los paneles en sus límites con otros paneles.


StackPanel


Definido por las etiquetas <StackPanel></StackPanel>, este control es semejante al DockPanel, sin embargo se utiliza más para definir un conjunto de controles ordenados, ya sea de modo horizontal o vertical, los controles se ordenen en el mismo orden que se describen dentro del XAML, respetan los márgenes verticales (Top, Bottom) de los controles al estar orientados de manera vertical, y de la misma manera respetan los márgenes horizontales (Left y Right) de los controles  al estar orientados de forma horizontal. La orientación de los controles se define con la propiedad Orientation que tiene dos valores, Vertical y Horizontal. Es útil utilizar este control para hacer composiciones de controles, digamos como agrupar un TextBlock con un TextBox como una sola pieza dentro de otros controles contenedores manteniendo su relación de aspecto.


Existen otros controles contenedores como El GroupBox y el TabItem, sin embargo estos se utilizan con los controles anteriores, ya que solo pueden contener un control, por lo que es recomendable utilizar un grid o un canvas para mostrar el contenido. El GroupBox tiene una propiedad Header que hace más visible la conceptualización de un grupo de controles. El TabControl depende de otro control, el TabItem para mostrar los tabs o pestañas, y tiene la misma limitante que el GroupBox.


Pues bien, espero que sea de utilidad esta información para ubicar rápidamente los elementos primarios del diseño de la interfaz de usuario con WPF. Más detalles del funcionamiento, eventos y propiedades de estos controles lo pueden encontrar en el MSDN Library, ya sea en línea (http://msdn.microsoft.com/library) , o bien en el que se instala con visual studio o las versiones express de visual studio.


 


Temas relacionados:


http://msmvps.com/blogs/otelis/archive/2009/02/25/introducci-243-n-al-uso-de-xaml-en-wpf.aspx


 


Saludos…


Octavio Telis