Hola Qué Tal?
En esta ocasión, no he dejado pasar tanto tiempo para terminar la trilogía del uso de Data Access Component con un ejemplo.
Bien, pues en este artículo veremos el uso del componente ya creado, cómo extenderemos la funcionalidad del componente y cómo lo aplicamos en la interfaz de usuario.
Primeramente, debemos crear un proyecto de Windows, agregar la referencia al DataHelper y al componente de acceso a datos. Realizar algunas modificaciones a los espacios de nombre y a los nombres de los archivos para que queden congruentes.
Explicaré brevemente el diseño de la interfaz de usuario; para este ejemplo utilizaré WPF (VS2008 ó VS2010), en un proyecto de Windows WPF. Será una ventana que tenga dos comportamientos, uno será para mostrar los registros que están en la base de datos y el otro comportamiento será para registrar o editar los registros. Es la misma venta con dos comportamientos, según se construya la ventana, será su comportamiento, es por eso que tendremos de antemano dos constructores. Tendremos algunos eventos, bindings y otras cosas que ya iremos viendo en el transcurso de este artículo.
Aquí empiezo dejando el XAML de la ventana que utilizaremos:
VB
<Window x:Class="TipoProducto"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Tipo de Producto" Height="200" Width="400">
<Grid>
<DockPanel x:Name="docPanMenu" Height="20"
VerticalAlignment="Top">
<Menu x:Name="mnuMain" Height="Auto" Width="Auto" >
<MenuItem Header="Archivo" x:Name="mnuArchivo">
<MenuItem Header="Eliminar registro actual"
Name="mnuEliminar"
Click="mnuEliminar_Click" />
<MenuItem Header="Salir"
Name="mnuSalir"
Click="mnuSalir_Click" />
</MenuItem>
</Menu>
</DockPanel>
<DockPanel Margin="0,20,0,55" x:Name="docPanRegistro">
<Grid x:Name="grid1" Width="Auto">
<StackPanel Height="25" Margin="6,15,0,0"
x:Name="skpClaveTipoProducto"
Orientation="Horizontal"
VerticalAlignment="Top"
HorizontalAlignment="Left" Width="365">
<TextBlock Height="16" Width="95"
TextAlignment="Right"
FontWeight="Bold" FontStretch="Normal"
FontStyle="Oblique" Text="Clave:" />
<TextBox Name="txtClaveTipoProducto" Width="250"
MaxLength="20" Margin="5,0,0,0"
Text="{Binding Path=ClaveTipoProducto}" />
</StackPanel>
<StackPanel Height="25" Margin="6,45,0,0"
x:Name="skpDescripcion"
Orientation="Horizontal"
VerticalAlignment="Top"
HorizontalAlignment="Left" Width="365">
<TextBlock Height="16" Width="95"
TextAlignment="Right"
FontWeight="Bold" FontStyle="Oblique"
Text="Descripción:" />
<TextBox Name="txtDescripcion" Width="250"
MaxLength="150" Margin="5,0,0,0"
Text="{Binding Path=Descripcion}" />
</StackPanel>
</Grid>
</DockPanel>
<DockPanel Margin="0,0,0,20" x:Name="docPanBotones"
Height="35" VerticalAlignment="Bottom">
<Grid Width="Auto">
<Button Margin="0,6,90,6" Name="btnSalvar"
Content="Salvar" Click="btnSalvar_Click"
HorizontalAlignment="Right" Width="73" />
<Button Margin="0,6,8,6" Content="Salir"
IsCancel="True" HorizontalAlignment="Right"
Width="73" />
</Grid>
</DockPanel>
<DockPanel Height="20" x:Name="docPanStatus"
VerticalAlignment="Bottom">
<StatusBar Height="Auto" x:Name="statusBar1"
Width="Auto" Foreground="White">
<StatusBarItem x:Name="sbiTitle"
Content="Ejemplo DAC - .NET Chronicles"/>
</StatusBar>
</DockPanel>
<DockPanel Name="docPanSeleccion" Margin="420,25,-230,20" >
<Grid>
<DockPanel Margin="0,0,0,40" Width="Auto">
<ListView Name="lvwSeleccion" Height="Auto"
Width="Auto"
MouseDoubleClick=
"lvwSeleccion_MouseDoubleClick"
SelectionChanged=
"lvwSeleccion_SelectionChanged"
SelectionMode="Single"
KeyUp="lvwSeleccion_KeyUp">
<ListView.View>
<GridView AllowsColumnReorder="True" >
<GridViewColumn
DisplayMemberBinding=
"{Binding Path=ClaveTipoProducto}"
Width="Auto"
Header="Clave" />
<GridViewColumn
DisplayMemberBinding=
"{Binding Path=Descripcion}"
Width="Auto"
Header="Descripcion" />
</GridView>
</ListView.View>
</ListView>
</DockPanel>
<DockPanel Height="40" VerticalAlignment="Bottom"
Width="Auto">
<Grid Height="Auto" Width="Auto">
<Button HorizontalAlignment="Right"
Width="73" Height="22"
Margin="0,0,170,6"
x:Name="btnNuevo" Content="Nuevo"
Click="btnNuevo_Click"/>
<Button HorizontalAlignment="Right"
Width="73"
Height="22" Margin="0,0,90,6"
x:Name="btnAceptar"
Content="Aceptar"
Click="btnAceptar_Click"/>
<Button HorizontalAlignment="Right"
Width="73"
Height="22" Margin="0,0,9,6"
x:Name="btnCancelar"
Content="Salir" IsCancel="True" />
</Grid>
</DockPanel>
</Grid>
</DockPanel>
</Grid>
</Window>
C#
<Window x:Class="UI.TipoProducto"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Tipo de Producto" Height="200" Width="400">
<Grid>
<DockPanel x:Name="docPanMenu" Height="20"
VerticalAlignment="Top">
<Menu x:Name="mnuMain" Height="Auto" Width="Auto" >
<MenuItem Header="Archivo" x:Name="mnuArchivo">
<MenuItem Header="Eliminar registro actual"
Name="mnuEliminar"
Click="mnuEliminar_Click" />
<MenuItem Header="Salir"
Name="mnuSalir"
Click="mnuSalir_Click" />
</MenuItem>
</Menu>
</DockPanel>
<DockPanel Margin="0,20,0,55" x:Name="docPanRegistro">
<Grid x:Name="grid1" Width="Auto">
<StackPanel Height="25" Margin="6,15,0,0"
x:Name="skpClaveTipoProducto"
Orientation="Horizontal"
VerticalAlignment="Top"
HorizontalAlignment="Left" Width="365">
<TextBlock Height="16" Width="95"
TextAlignment="Right"
FontWeight="Bold" FontStretch="Normal"
FontStyle="Oblique" Text="Clave:" />
<TextBox Name="txtClaveTipoProducto" Width="250"
MaxLength="20" Margin="5,0,0,0"
Text="{Binding Path=ClaveTipoProducto}" />
</StackPanel>
<StackPanel Height="25" Margin="6,45,0,0"
x:Name="skpDescripcion"
Orientation="Horizontal"
VerticalAlignment="Top"
HorizontalAlignment="Left" Width="365">
<TextBlock Height="16" Width="95"
TextAlignment="Right"
FontWeight="Bold" FontStyle="Oblique"
Text="Descripción:" />
<TextBox Name="txtDescripcion" Width="250"
MaxLength="150" Margin="5,0,0,0"
Text="{Binding Path=Descripcion}" />
</StackPanel>
</Grid>
</DockPanel>
<DockPanel Margin="0,0,0,20" x:Name="docPanBotones"
Height="35" VerticalAlignment="Bottom">
<Grid Width="Auto">
<Button Margin="0,6,90,6" Name="btnSalvar"
Content="Salvar" Click="btnSalvar_Click"
HorizontalAlignment="Right" Width="73" />
<Button Margin="0,6,8,6" Content="Salir"
IsCancel="True" HorizontalAlignment="Right"
Width="73" />
</Grid>
</DockPanel>
<DockPanel Height="20" x:Name="docPanStatus"
VerticalAlignment="Bottom">
<StatusBar Height="Auto" x:Name="statusBar1"
Width="Auto" Foreground="White">
<StatusBarItem x:Name="sbiTitle"
Content="Ejemplo DAC - .NET Chronicles"/>
</StatusBar>
</DockPanel>
<DockPanel Name="docPanSeleccion" Margin="420,25,-230,20" >
<Grid>
<DockPanel Margin="0,0,0,40" Width="Auto">
<ListView Name="lvwSeleccion" Height="Auto"
Width="Auto"
MouseDoubleClick=
"lvwSeleccion_MouseDoubleClick"
SelectionChanged=
"lvwSeleccion_SelectionChanged"
SelectionMode="Single"
KeyUp="lvwSeleccion_KeyUp">
<ListView.View>
<GridView AllowsColumnReorder="True" >
<GridViewColumn
DisplayMemberBinding=
"{Binding Path=ClaveTipoProducto}"
Width="Auto"
Header="Clave" />
<GridViewColumn
DisplayMemberBinding=
"{Binding Path=Descripcion}"
Width="Auto"
Header="Descripcion" />
</GridView>
</ListView.View>
</ListView>
</DockPanel>
<DockPanel Height="40" VerticalAlignment="Bottom"
Width="Auto">
<Grid Height="Auto" Width="Auto">
<Button HorizontalAlignment="Right"
Width="73" Height="22"
Margin="0,0,170,6"
x:Name="btnNuevo" Content="Nuevo"
Click="btnNuevo_Click"/>
<Button HorizontalAlignment="Right"
Width="73"
Height="22" Margin="0,0,90,6"
x:Name="btnAceptar"
Content="Aceptar"
Click="btnAceptar_Click"/>
<Button HorizontalAlignment="Right"
Width="73"
Height="22" Margin="0,0,9,6"
x:Name="btnCancelar"
Content="Salir" IsCancel="True" />
</Grid>
</DockPanel>
</Grid>
</DockPanel>
</Grid>
</Window>
Nótese que las propiedad Text de cada caja de texto (TextBox) ha sido ligada a una propiedad del componente, así, se vincula directamente el componente con la interfaz de usuario. Así mismo las columnas del ListView. Esta propuesta de interfaz de usuario tiene interacción directa con el usuario como se verá más adelante. Además de que son prácticamente idénticos para VB y para C#, solo cambia el origen de la clase, pues en C# se incluye el Namespace.
En resumen tenemos cinco paneles acoplables, el primero, docPanMenu, es para poner los menú que se utilicen en la ventana, también está el docPanRegistro, que se refiere a la información que se mostrará del registro, en la captura o edición. Otro es el docPanBotones, que es donde se colocan los botones de acción del usuario. Un cuarto panel; docPanEstatus, que es el que contiene la barra de estado y por último, docPanSelección, este panel contiene la vista de datos que se tienen registrados en la base de datos, desde donde podremos seleccionar alguno para editar o interactuar con él.
Este tipo de ventanas utilizan la configuración de conexión para poder trabajar, por lo que necesitaremos una ventana principal, solo para inicializar las variables y lanzar la ventana. Una ventana MainWindow bastará, simple y sin complicaciones. Aquí dejo el XAML:
VB
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="300" Width="300">
<Grid>
<Button
Margin="10,10"
Content=" Tipo Producto "
x:Name="btnTipoProducto"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Click="btnTipoProducto_Click" />
</Grid>
</Window>
C#
<Window x:Class="UI.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="300" Width="300">
<Grid>
<Button
Margin="10,10"
Content=" Tipo Producto "
x:Name="btnTipoProducto"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Click="btnTipoProducto_Click" />
</Grid>
</Window>
Bien, pues empecemos por esta ventana de una vez de aquí nos pasamos a la nuestra y regresamos y vaya que quiero confundirlos. Bueno, mejor ventana por ventana, empecemos con esta entonces, que por supuesto requerirá de algunas cositas más.
Veamos el código, primeramente el uso de namespaces (using o Imports), bueno para esta clase no tenemos nada más allá de los predeterminados, seguido, la declaración de campos.
VB
Private sqlSet As SqlSettings
C#
private SqlSettings sqlSet;
Bien, para aclarar, este campo es del tipo SqlSettings, este tipo implementa la interfaz IDbSettings que se definió en la parte 1 de la definición de un DataHelper (http://msmvps.com/blogs/otelis/archive/2009/03/11/arquitectura-definici-243-n-de-un-datahelper-parte-1.aspx), pero si hay que ser honestos, no he definido la clase SqlSettings como tal, bien, pues tendré que hacerlo, no sin antes comentar lo siguiente: Esta clase debe estar en un componente de proceso de interfaz de usuario o bien (UIPC), para este ejemplo, he agregado una clase más en el proyecto de Windows WPF que estamos utilizando para la interfaz de usuario, sin embargo, podría estar definido en una biblioteca de clases aparte, según les convenga. Bueno, al archivo de la clase lo he definido ProcessUI y contendrá la definición de la clase SqlSettings, además, se tendrá la definición de un delegado genérico, mismo que usaremos para crear nuestros eventos y la declaración de uso del namespace DBInfo:
VB
Imports DataHelper.DBInfo
Public Delegate Sub _
GenericEventHandler(Of T, U) _
(ByVal sender As T, ByVal e As U)
Public Class SqlSettings
Implements IDbSettings
Private _server As String
Private _database As String
Private _user As String
Private _pwd As String
Public Sub New()
_server = ""
_database = ""
_user = ""
_pwd = ""
End Sub
Public Property Server() As String _
Implements IDbSettings.Server
Get
Return _server
End Get
Set(ByVal value As String)
_server = value
End Set
End Property
Public Property DataBase() As String _
Implements IDbSettings.DataBase
Get
Return _database
End Get
Set(ByVal value As String)
_database = value
End Set
End Property
Public Property UserId() As String _
Implements IDbSettings.User
Get
Return _user
End Get
Set(ByVal value As String)
_user = value
End Set
End Property
Public Property Password() As String _
Implements IDbSettings.Password
Get
Return _pwd
End Get
Set(ByVal value As String)
_pwd = value
End Set
End Property
Public Function GetConnectionString() As String _
Implements IDbSettings.GetConnectionString
Dim cnnStr As String = ""
If _server.Trim() <> "" AndAlso _database.Trim() <> "" Then
If _user.Trim() = "" Then
cnnStr = String.Format( _
"Integrated Security=SSPI; Server={0}; Database={1}", _
_server, _database)
Else
cnnStr = String.Format( _
"Server={0}; Database={1}; User Id={2}; Password={3}", _
_server, _database, _user, _pwd)
End If
End If
Return cnnStr
End Function
End Class
C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DBInfo;
namespace UI
{
public delegate void GenericEventHandler<T, U>(T sender, U e);
public partial class SqlSettings : IDbSettings
{
private string server;
private string database;
private string user;
private string pwd;
public SqlSettings()
{
server = "";
database = "";
user = "";
pwd = "";
}
public string Server
{
get
{
return server;
}
set
{
server = value;
}
}
public string DataBase
{
get
{
return database;
}
set
{
database = value;
}
}
public string UserId
{
get
{
return user;
}
set
{
user = value;
}
}
public string Password
{
get
{
return pwd;
}
set
{
pwd = value;
}
}
public string GetConnectionString()
{
string cnnStr = "";
if (server.Trim() != "" && database.Trim() != "")
{
if (user.Trim() == "")
cnnStr = String.Format(
"Integrated Security=SSPI; " +
"Server={0}; Database={1}",
server, database);
else
cnnStr = String.Format(
"Server={0}; Database={1}; " +
"User Id={2}; Password={3}",
server, database, user, pwd);
}
return cnnStr;
}
public object Clone()
{
return new SqlSettings();
}
string IDbSettings.Server
{
get
{
return server;
}
set
{
server = value;
}
}
string IDbSettings.DataBase
{
get
{
return database;
}
set
{
database = value;
}
}
string IDbSettings.User
{
get
{
return user;
}
set
{
user = value;
}
}
string IDbSettings.Password
{
get
{
return pwd;
}
set
{
pwd = value;
}
}
string IDbSettings.GetConnectionString()
{
return GetConnectionString();
}
}
}
Observemos que se implementa la interfaz IDbSettings explícitamente. Esta clase ya la estamos usando en la MainWindow, en la declaración, sin embargo no hemos visto cómo la utilizaremos. Regresemos pues a nuestra clase MainWindow y veamos el constructor predeterminado que le hemos creado:
VB
Public Sub New()
InitializeComponent()
sqlSet = New SqlSettings()
sqlSet.Server = "(LOCAL)"
sqlSet.DataBase = "EjemploDAC"
sqlSet.UserId = ""
sqlSet.Password = ""
End Sub
C#
public MainWindow()
{
InitializeComponent();
sqlSet = new SqlSettings();
sqlSet.Server = "(LOCAL)";
sqlSet.DataBase = "EjemploDAC";
sqlSet.UserId = "";
sqlSet.Password = "";
}
De este constructor, vemos que llama al método interno InitializeComponent(), que como sabrán es el que inicializa la ventana y sus controles, seguido de esto, inicializamos la variable sqlSet, misma que nos servirá más adelante para crear la ventana de captura. Notamos aquí que estamos pasando los valores de conexión para la base de datos, aunque no es buena práctica dejarlos código duro para caso práctico en este ejemplo los usaré así. No olviden poner los valores propios para el equipo donde realicen la prueba.
Para crear la ventana de captura, hemos puesto un botón que será el responsable de mostrarnos la ventana con la información de la base de datos, el evento click de dicho botón contendrá lo siguiente:
VB
Private Sub btnTipoProducto_Click( _
ByVal sender As System.Object, _
ByVal e As System.Windows.RoutedEventArgs)
Dim tipoProducto As TipoProducto = _
New UI.TipoProducto(sqlSet, False)
tipoProducto.Owner = Me
tipoProducto.WindowStartupLocation = _
Windows.WindowStartupLocation.CenterScreen
tipoProducto.ShowDialog()
End Sub
C#
private void btnTipoProducto_Click(
object sender,
RoutedEventArgs e)
{
TipoProducto tipoProducto =
new TipoProducto(sqlSet, false);
tipoProducto.Owner = this;
tipoProducto.WindowStartupLocation =
WindowStartupLocation.CenterScreen;
tipoProducto.ShowDialog();
}
Es interesante ver cómo estamos instanciando a la ventana de captura, al ver que estamos pasando dos parámetros en el constructor, uno de ellos es la variable sqlSet que contiene la información de conexión a la base de datos y la otra es un valor booleano. En breve estaré explicando este constructor, pero antes definamos las particularidades de la clase de la venta TipoProducto.
Primeramente la declaración de uso de namespaces:
VB
Imports System.Collections.Generic
C#
using System.Collections.Generic;
Este namespace lo necesitamos porque estaremos haciendo uso de listas genéricas para manipular los datos.
Seguido tenemos la declaración de campos:
VB
Private tipoProducto As DAC.TipoProducto
Private isSelection As Boolean = False
Private isSearch As Boolean = False
C#
private DAC.TipoProducto tipoProducto;
private bool isSelection = false;
private bool isSearch = false;
Estos campos, bueno… el tipoProducto es una variable que contiene la información del registro, esto es, es el DAC de TipoProducto, y la requerimos como campo porque la utilizamos en toda la clase, de hecho hay un constructor que la utiliza. Los otros dos campos, isSelection y isSearch son para definir el estado de la vista de datos, esto es, si la vista es para seleccionar un valor basado en los datos mostrados, entonces la variable isSelection será true y la variable isSearch será false, en cambio, si la vista de datos es para tener acceso al registro y poder modificarlo, entonces, se dará el caso inverso, donde isSelection será false e isSearch será true. Estos campos se asignan inicialmente ambos en false, pero cambian su valor en uno de los constructores, como veremos más adelante.
Pues continuando con el código de la ventana, tendremos la declaración de los eventos de esta, serán eventos que lanzaremos cuando se modifiquen los datos, en general, Salvar y Borrar. Para esto, tenemos definido el tipo GenericEventHandler en el archivo ProcessUI, como lo mostré anteriormente en este artículo, siendo así, queda el código siguiente:
VB
Public Event RecordSaved As _
GenericEventHandler( _
Of UI.TipoProducto, DAC.TipoProducto)
Public Event RecordSelected As _
GenericEventHandler( _
Of UI.TipoProducto, DAC.TipoProducto)
C#
public event GenericEventHandler
<UI.TipoProducto, DAC.TipoProducto> RecordSaved;
public event GenericEventHandler
<UI.TipoProducto, DAC.TipoProducto> RecordSelected;
Bien, como les mencioné, están los dos eventos, ambos creados a partir del tipo declarado en el ProcessUI. Más adelante veremos su uso.
Continuando, definiremos los constructores de la ventana, teniendo en cuenta que no habrá un constructor sin parámetros, lo anterior para asegurar que la ventana se construya con un enlace a la base de datos y un objeto conectado a la misma, esto es, un DAC con vida. Veamos pues estos constructores:
VB
Public Sub New(ByVal sqlSet As SqlSettings)
tipoProducto = New DAC.TipoProducto(sqlSet)
InitForm()
End Sub
Public Sub New(ByVal sqlSet As SqlSettings, _
ByVal IsSelection As Boolean)
tipoProducto = New DAC.TipoProducto(sqlSet)
IsSelection = IsSelection
isSearch = Not IsSelection
InitForm()
End Sub
Public Sub New(ByVal pTipoDeclaracion As DAC.TipoProducto)
tipoProducto = pTipoDeclaracion
InitForm()
End Sub
C#
public TipoProducto(SqlSettings sqlSet)
{
tipoProducto = new DAC.TipoProducto(sqlSet);
InitForm();
}
public TipoProducto(SqlSettings sqlSet, bool IsSelection)
{
tipoProducto = new DAC.TipoProducto(sqlSet);
isSelection = IsSelection;
isSearch = !IsSelection;
InitForm();
}
public TipoProducto(DAC.TipoProducto pTipoDeclaracion)
{
tipoProducto = pTipoDeclaracion;
InitForm();
}
Como podemos observar, el parámetro sqlSet del tipo SqlSettings se utiliza para construir el objeto tipoProducto, el cual es del tipo DAC.TipoProducto, que es el componente de acceso a datos que programamos.
El primer constructor construirá al objeto tipoProducto como un objeto nuevo, o bien, un registro nuevo. El segundo constructor, será simplemente para mostrar la ventana en su forma de búsqueda o selección. En la ventana MainWindow estamos optando por la primera forma, esto es, como búsqueda para permitir la edición del registro. El tercer constructor recibe el objeto TipoProducto previamente construido y lo asigna a nuestra variable del mismo tipo.
En todos los casos podemos ver que se invoca el método InitForm(), este método se llama para inicializar al formulario por primera vez, este método no sirve para tareas recurrentes, o sea, no es útil para reinicializar al formulario, para eso he creado otro método denominado InitializeForm(), que será de utilidad para reinicializar el formulario en cualquier momento. A continuación, el código de dichos métodos:
VB
Private Sub InitForm()
InitializeComponent()
initializeForm()
AddHandler RecordSaved, _
AddressOf Record_Saved
If isSelection Then
AddHandler RecordSelected, _
AddressOf Record_Selected
End If
End Sub
Private Sub Record_Saved( _
ByVal sender As UI.TipoProducto, _
ByVal e As DAC.TipoProducto)
Return
End Sub
Private Sub Record_Selected( _
ByVal sender As UI.TipoProducto, _
ByVal e As DAC.TipoProducto)
Return
End Sub
Private Sub initializeForm()
Me.DataContext = tipoProducto
If tipoProducto.Nuevo Then
If isSelection OrElse isSearch Then
mnuArchivo.Items.Clear()
mnuArchivo.Items.Add(mnuSalir)
docPanRegistro.Visibility = Visibility.Hidden
docPanBotones.Visibility = Visibility.Hidden
docPanSeleccion.Margin = _
New System.Windows.Thickness(0, 25, 0, 25)
If isSelection Then
Title = "Selección de Tipo de Producto"
btnAceptar.Content = "Seleccionar"
Else
Title = "Búsqueda de Tipo de Producto"
btnAceptar.Content = "Editar"
End If
Width = 600
Height = 600
FillSelection()
btnAceptar.IsEnabled = False
Else
mnuArchivo.Items.Clear()
mnuArchivo.Items.Add(mnuSalir)
docPanSeleccion.Visibility = Visibility.Hidden
End If
Else
mnuArchivo.Items.Clear()
mnuArchivo.Items.Add(mnuEliminar)
mnuArchivo.Items.Add(mnuSalir)
docPanSeleccion.Visibility = Visibility.Hidden
End If
End Sub
C#
private void InitForm()
{
InitializeComponent();
initializeForm();
RecordSaved +=
delegate(UI.TipoProducto T, DAC.TipoProducto U)
{ return; };
if (isSelection)
{
RecordSelected +=
delegate(UI.TipoProducto T, DAC.TipoProducto U)
{ return; };
}
}
private void initializeForm()
{
this.DataContext = tipoProducto;
if (tipoProducto.Nuevo)
{
if (isSelection || isSearch)
{
mnuArchivo.Items.Clear();
mnuArchivo.Items.Add(mnuSalir);
docPanRegistro.Visibility = Visibility.Hidden;
docPanBotones.Visibility = Visibility.Hidden;
docPanSeleccion.Margin =
new System.Windows.Thickness(0, 25, 0, 25);
if (isSelection)
{
this.Title = "Selección de Tipo de Producto";
btnAceptar.Content = "Seleccionar";
}
else
{
this.Title = "Búsqueda de Tipo de Producto";
btnAceptar.Content = "Editar";
}
this.Width = 600;
this.Height = 600;
FillSelection();
btnAceptar.IsEnabled = false;
}
else
{
mnuArchivo.Items.Clear();
mnuArchivo.Items.Add(mnuSalir);
docPanSeleccion.Visibility = Visibility.Hidden;
}
}
else
{
mnuArchivo.Items.Clear();
mnuArchivo.Items.Add(mnuEliminar);
mnuArchivo.Items.Add(mnuSalir);
docPanSeleccion.Visibility = Visibility.Hidden;
}
}
Podemos observar que el método InitForm() se encarga de invocar al método interno InitializeComponent(), invoca además el método antes mencionado InitializeForm() y además, inicializa los eventos. Esta inicialización es para evitar la invocación de un delegado sin método, así pues he agregado dos métodos en el caso de VB y dos métodos anónimos en el caso de C#. En realidad es porque si ejecuto el código y no se generó un manejador para el evento, la aplicación lanza una excepción, por esta razón, digamos que desvío la ejecución a los métodos anónimos para evitar contrariedades.
Con respecto al método InitializeForm(), podemos observar que inicializa las características de la ventana teniendo en cuenta cómo se ha construido la misma, vemos evaluaciones de las variables isSearch, isSelection y de la propiedad nuevo del objeto tipoProducto, con esto podemos construir la ventana en el modo que nos convenga, y esto gracias a que desde el momento de realizar la instancia de la ventana estamos anticipando su comportamiento. No hay mucho que explicar en esta parte, salvo que se oculta, muestran y mueven los paneles para cambiar la apariencia de la ventana según los criterios mencionados.
Continuando con nuestro recorrido en la generación del código de la ventana, llegamos a los métodos comunes, uno de ellos está invocado en el método InitializeForm(), ese y otros más se definen a continuación:
VB
Private Sub FillSelection()
lvwSeleccion.ItemsSource = Nothing
lvwSeleccion.ItemsSource = _
tipoProducto.GetListTiposProductos()
End Sub
Private Sub RaiseSelection()
If lvwSeleccion.SelectedItems.Count = 0 Then
Return
End If
tipoProducto = TryCast(lvwSeleccion.SelectedItem, _
DAC.TipoProducto)
If isSelection Then
RaiseEvent RecordSelected(Me, tipoProducto)
Close()
Else
Cursor = Cursors.Wait
Dim tipoDec As UI.TipoProducto = _
New TipoProducto(tipoProducto)
tipoDec.Owner = Me
tipoDec.WindowStartupLocation = _
WindowStartupLocation.CenterOwner
AddHandler tipoDec.RecordSaved, _
AddressOf tipoProd_RecordSaved
tipoDec.ShowDialog()
Cursor = Cursors.Arrow
End If
End Sub
C#
private void FillSelection()
{
lvwSeleccion.ItemsSource = null;
lvwSeleccion.ItemsSource =
tipoProducto.GetListTiposProductos();
}
private void RaiseSelection()
{
if (lvwSeleccion.SelectedItems.Count == 0)
return;
tipoProducto = lvwSeleccion.SelectedItem as DAC.TipoProducto;
if (isSelection)
{
RecordSelected(this, tipoProducto);
Close();
}
else
{
this.Cursor = Cursors.Wait;
UI.TipoProducto tipoDec =
new TipoProducto(tipoProducto);
tipoDec.Owner = this;
tipoDec.WindowStartupLocation =
WindowStartupLocation.CenterOwner;
tipoDec.RecordSaved += tipoProd_RecordSaved;
tipoDec.ShowDialog();
this.Cursor = null;
}
}
De estos dos métodos puedo decirles que, el primero FillSelection() se encarga de inicializar el ItemsSource del ListView que utilizamos para mostrar los registros que están en la base de datos. Ahora bien, pueden darse cuenta que se invoca un método del DAC que no tenemos actualmente, sin embargo lo vamos a definir en un momento más.
El otro método, RaiseSelection(), es más interesante, ya que este método se invoca cada vez que se quiere mostrar en un elemento seleccionado en el ListView, bueno, tiene dos comportamientos como pueden observar, si isSelection es true, solo se lanza el evento RecordSelected y se cierra, de lo contrario, se inicializará una ventana nueva con el elemento seleccionado para mostrar los datos en modo de edición.
Continuando, tenemos los dos métodos que operan hacia la base de datos, así es, estos se manda a llamar en varios eventos dependiendo de la acción del usuario. Son los métodos Salvar() y Eliminar(), donde se aprecia en detalle la funcionalidad del DAC. Veamos pues:
VB
Private Sub Salvar()
If tipoProducto.ClaveTipoProducto.Trim() = "" Then
MessageBox.Show("La Clave es requerida. Rectifique", _
"Mensaje de Sistema", _
MessageBoxButton.OK, _
MessageBoxImage.Information, _
MessageBoxResult.OK)
txtClaveTipoProducto.Focus()
Return
ElseIf tipoProducto.Descripcion.Trim() = "" Then
MessageBox.Show("La Descripción es requerida. Rectifique", _
"Mensaje de Sistema", _
MessageBoxButton.OK, _
MessageBoxImage.Information, _
MessageBoxResult.OK)
txtDescripcion.Focus()
Return
End If
Dim res As Integer = tipoProducto.Salvar()
If res > 0 Then
MessageBox.Show("El registro se salvó correctamente", _
"Registro salvado", _
MessageBoxButton.OK, _
MessageBoxImage.Information, _
MessageBoxResult.OK)
ElseIf (res <= 0) Then
MessageBox.Show("No se pudo Salvar el Registro", _
"Mensaje de Sistema", _
MessageBoxButton.OK, _
MessageBoxImage.Warning, _
MessageBoxResult.OK)
Return
End If
RaiseEvent RecordSaved(Me, tipoProducto)
Close()
End Sub
Private Sub Eliminar()
If MessageBox.Show( _
String.Format( _
"¿Desea Eliminar El Tipo de Producto con Clave {0}?", _
tipoProducto.ClaveTipoProducto.Trim()), _
"Mensaje de Sistema", _
MessageBoxButton.YesNo, _
MessageBoxImage.Question, _
MessageBoxResult.No) = System.Windows.MessageBoxResult.Yes Then
Dim res As Integer = tipoProducto.Borrar()
If res <= 0 Then
MessageBox.Show("No se pudo Borrar el Registro", _
"Mensaje de Sistema", _
MessageBoxButton.OK, _
MessageBoxImage.Information, _
MessageBoxResult.OK)
Else
RaiseEvent RecordSaved(Me, tipoProducto)
If isSelection OrElse isSearch Then
FillSelection()
Else
Close()
End If
End If
End If
End Sub
C#
private void Salvar()
{
if (this.tipoProducto.ClaveTipoProducto.Trim() == "")
{
MessageBox.Show("La Clave es requerida. Rectifique",
"Mensaje de Sistema",
MessageBoxButton.OK,
MessageBoxImage.Information,
MessageBoxResult.OK);
this.txtClaveTipoProducto.Focus();
return;
}
else if (tipoProducto.Descripcion.Trim() == "")
{
MessageBox.Show("La Descripción es requerida. Rectifique",
"Mensaje de Sistema",
MessageBoxButton.OK,
MessageBoxImage.Information,
MessageBoxResult.OK);
txtDescripcion.Focus();
return;
}
int res = tipoProducto.Salvar();
if (res > 0)
MessageBox.Show("El registro se salvó correctamente",
"Registro salvado",
MessageBoxButton.OK,
MessageBoxImage.Information,
MessageBoxResult.OK);
else if (res <= 0)
{
MessageBox.Show("No se pudo Salvar el Registro",
"Mensaje de Sistema",
MessageBoxButton.OK,
MessageBoxImage.Warning,
MessageBoxResult.OK);
return;
}
RecordSaved(this, tipoProducto);
Close();
}
private void Eliminar()
{
if (MessageBox.Show(
string.Format(
"¿Desea Eliminar El Tipo de Producto con Clave {0}?",
tipoProducto.ClaveTipoProducto.Trim()),
"Mensaje de Sistema",
MessageBoxButton.YesNo,
MessageBoxImage.Question,
MessageBoxResult.No) == System.Windows.MessageBoxResult.Yes)
{
int res = tipoProducto.Borrar();
if (res <= 0)
MessageBox.Show("No se pudo Borrar el Registro",
"Mensaje de Sistema",
MessageBoxButton.OK,
MessageBoxImage.Information,
MessageBoxResult.OK);
else
{
RecordSaved(this, tipoProducto);
if (isSelection || isSearch)
FillSelection();
else
Close();
}
}
}
Veamos en el método salvar, iniciamos con la validación de la información introducida, recordemos que es aquí, en la interfaz de usuario, donde se realizan la validaciones de tipo, formato, etc. Bien, una vez pasada la validación, no nos queda más que invocar el método salvar del objeto TipoProducto, y es tan simple porque hemos establecido de antemano el binding de las propiedades del objeto con los controles de la interfaz. De hecho si se dan cuenta, la validación se hace contra el contenido de las propiedades del objeto y no contra los controles. Después de hacer esto, bueno, se le informa al usuario del éxito o fracaso de la operación y posteriormente se lanza el evento RecordSaved para concluir con el cierre de la ventana.
El evento eliminar, es parecido, solo hay que tener en cuenta que se invoca el método borrar sin parámetros, esto es, se intentará borrar el registro con el identificador contenido en el objeto. También lanza el evento RecordSaved y dependiendo del estado de la ventana llena el ListView o cierra la ventana.
Bueno, es algo simple, y todo se resume a Salvar o Borrar.
Lo que resta son los eventos de los controles, que no serán difíciles de entender, aquí les dejo el código:
VB
Private Sub tipoProd_RecordSaved( _
ByVal sender As TipoProducto, _
ByVal e As DAC.TipoProducto)
FillSelection()
End Sub
Private Sub mnuEliminar_Click( _
ByVal sender As Object, _
ByVal e As RoutedEventArgs)
Eliminar()
End Sub
Private Sub mnuSalir_Click( _
ByVal sender As Object, _
ByVal e As RoutedEventArgs)
Close()
End Sub
Private Sub btnSalvar_Click( _
ByVal sender As Object, _
ByVal e As RoutedEventArgs)
Salvar()
End Sub
Private Sub btnNuevo_Click( _
ByVal sender As Object, _
ByVal e As RoutedEventArgs)
Dim tipoDec As TipoProducto = _
New TipoProducto( _
TryCast(tipoProducto.SqlSettings, _
SqlSettings))
tipoDec.Owner = Me
tipoDec.WindowStartupLocation = _
WindowStartupLocation.CenterOwner
AddHandler tipoDec.RecordSaved, _
AddressOf tipoProd_RecordSaved
tipoDec.ShowDialog()
End Sub
Private Sub btnAceptar_Click( _
ByVal sender As Object, _
ByVal e As RoutedEventArgs)
RaiseSelection()
End Sub
Private Sub lvwSeleccion_MouseDoubleClick( _
ByVal sender As Object, _
ByVal e As MouseButtonEventArgs)
RaiseSelection()
End Sub
Private Sub lvwSeleccion_SelectionChanged( _
ByVal sender As Object, _
ByVal e As SelectionChangedEventArgs)
btnAceptar.IsEnabled = _
lvwSeleccion.SelectedItems.Count > 0
End Sub
Private Sub lvwSeleccion_KeyUp( _
ByVal sender As Object, _
ByVal e As KeyEventArgs)
If e.Key = Key.Enter Then
RaiseSelection()
ElseIf e.Key = Key.Delete Then
If lvwSeleccion.SelectedItems.Count > 0 Then
tipoProducto = _
TryCast(lvwSeleccion.SelectedItem, _
DAC.TipoProducto)
Eliminar()
End If
End If
End Sub
C#
private void tipoProd_RecordSaved(
TipoProducto sender, DAC.TipoProducto e)
{
FillSelection();
}
private void mnuEliminar_Click(
object sender, RoutedEventArgs e)
{
Eliminar();
}
private void mnuSalir_Click(
object sender, RoutedEventArgs e)
{
this.Close();
}
private void btnSalvar_Click(
object sender, RoutedEventArgs e)
{
Salvar();
}
private void btnNuevo_Click(
object sender, RoutedEventArgs e)
{
TipoProducto tipoDec =
new TipoProducto(tipoProducto.SqlSettings as SqlSettings);
tipoDec.Owner = this;
tipoDec.WindowStartupLocation =
WindowStartupLocation.CenterOwner;
tipoDec.RecordSaved += tipoProd_RecordSaved;
tipoDec.ShowDialog();
}
private void btnAceptar_Click(
object sender, RoutedEventArgs e)
{
RaiseSelection();
}
private void lvwSeleccion_MouseDoubleClick(
object sender, MouseButtonEventArgs e)
{
RaiseSelection();
}
private void lvwSeleccion_SelectionChanged(
object sender, SelectionChangedEventArgs e)
{
btnAceptar.IsEnabled =
lvwSeleccion.SelectedItems.Count > 0;
}
private void lvwSeleccion_KeyUp(
object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
RaiseSelection();
else if (e.Key == Key.Delete)
{
if (lvwSeleccion.SelectedItems.Count > 0)
{
tipoProducto =
lvwSeleccion.SelectedItem as DAC.TipoProducto;
Eliminar();
}
}
}
Veamos, qué tenemos aquí…
En primero el evento tipoProd_RecordSaved que se enlaza cada que construimos una ventana, se usa en el método RiseSelection y en el método btnNuevo_Click, y bueno, lo que tiene es ya conocido.
Los siguiente son eventos típicos de los controles. En el caso del botón btnNuevo, vemos que lanza una ventana para el registro de un nuevo tipo de producto, esto actualizará la lista mediante el evento RecordSaved. Otro caso interesante es el método lvwSeleccion_KeyUp que maneja el evento KeyUp del ListView, bueno, en ese se muestran dos opciones del valor de tecla, uno es Enter que invoca el evento RaiseSelection(), el caso de Delete, se ve si hay algún elemento seleccionado para proceder a eliminarlo.
Solo resta la actualización que debemos hacer en el DAC.
Recordando el Stored Procedure que creamos en la primera parte, recordarán la opción 5, bueno, esta opción la utilizaremos para llenar el ListView, son dos métodos, uno llena a un DataTable y con este en el otro método se llena a una lista genérica con los DAC correspondientes.
Recuerden que estos métodos se definen en el DAC, así que completemos el DAC con el siguiente código.
VB
Public Function GetTiposProductos() As DataTable
cmd.Parameters.Clear()
cmd.Parameters.Add("@SP_OPCION", SqlDbType.TinyInt).Value = 5
Return GetQuery(cmd)
End Function
Public Function GetListTiposProductos() _
As List(Of DAC.TipoProducto)
Dim res As List(Of DAC.TipoProducto) = _
New List(Of TipoProducto)()
Dim tProd As DAC.TipoProducto
For Each dr As DataRow In GetTiposProductos().Rows
tProd = New TipoProducto(MyBase.SqlSettings)
tProd.IntializeFields(dr)
res.Add(tProd)
Next
Return res
End Function
C#
public DataTable GetTiposProductos()
{
cmd.Parameters.Clear();
cmd.Parameters.Add("@SP_OPCION", SqlDbType.TinyInt).Value = 5;
return GetQuery(cmd);
}
public List<DAC.TipoProducto> GetListTiposProductos()
{
List<DAC.TipoProducto> res = new List<TipoProducto>();
DAC.TipoProducto tProd;
foreach (DataRow dr in GetTiposProductos().Rows)
{
tProd = new TipoProducto(base.SqlSettings);
tProd.InitializeFields(dr);
res.Add(tProd);
}
return res;
}
Veamos el método GetTiposProductos(), simplemente asigna los parámetros del SqlCommand para consultar contra la opción mencionada y devuelve lo que tenga en el método GetQuery() como un DataTable. El otro método invoca al primer método para ser recorrido e inicializar y agregar a la lista genérica los elementos TipoProducto que se traen de la base de datos, esto mediante el método InitilizeFields, al cual se le pasa un DataRow e inicializa las propiedades del objeto.
Bien, pues con esto terminamos la trilogía del DAC con ejemplo, espero sea ilustrativo y útil.
Referencias:
Parte 1: http://msmvps.com/blogs/otelis/archive/2009/09/11/arquitectura-definici-243-n-de-un-data-acces-component-con-un-ejemplo.aspx
Parte 2: http://msmvps.com/blogs/otelis/archive/2010/05/12/arquitectura-definici-243-n-de-un-data-access-component-con-un-ejemplo-parte-2.aspx
Saludos…
Octavio Telis