Our Blogs
English
Español
Skip Navigation Links
News
Our Team
Mentoring
La Cueva de Desarrollo
Go to home page
Go Search

 
Other Blogs
Developer Cave

Our Blogs > Español > La Cueva de Desarrollo
Presentación de Solid RAD

El pasado día 21 de abril en Madrid y el día 22 de abril en Barcelona tuvo lugar el evento Frameworks y herramientas RAD de código abierto y propietario para soluciones .NET. Donde presenté nuestro Solid RAD, una herramienta para el desarrollo rápido de aplicaciones centradas en datos con una arquitectura de dos y tres capas.

Si estáis interesados en Solid RAD Tenéis disponibles unos vídeos en mms://solidq.com/SolidRad .

NOTA: copiad y pegad mms://solidq.com/SolidRad en la barra de direcciones de IE para ver los vídeos.

[DSL] Agregar un menú personalizado en VS2008

La forma en la que se añade un menú personalizado ha variado en la nueva versión de Visual Studio 2008. El SDK de extensibilidad de Visual Studio 2008 ha variado la forma en la que se generan menús y comandos de forma que ya no se utilizan los "antiguos" ficheros de configuración .ctc.

Como pequeña introducción, os digo que en la nueva versión se ha optado por un nuevo sistema de configuración más acorde a los tiempos que corren basado en XML. Dicho sistema de configuración está completa y exquisitamente explicado en el siguiente enlace (no apto para lectura al final del dia;).

Dicho esto, os aviso de antemano que la documentación que viene con la propia descarga del ejecutable SDK Extensibility 1.0 para Visual Studio 2008 es errónea y hace referencia al antiguo sistema de configuración (por lo menos en lo que nos atañe que son los menús en DSL). El antiguo sistema de agregación de menús personalizados en nuestros lenguajes de dominio está explicado bastante bien en el blog de "El Bruno" o en la propia documentación que viene con el descargable de extensibilidad que he mencionado.

Vamos a ponernos manos a la obra. Para ello parto de que tienes ya tu proyecto de lenguaje de especificación de dominio creado…

Tal cual está, podemos crear nuestro menú de una forma bastante más sencilla que antes siguiendo los siguientes pasos:

  1. En el "Solution Explorer", desplegar el proyecto DslPackage y editar el archivo Commands.vsct

    En dicho archivo es donde vamos a tener que añadir los elementos del menú que queremos. En nuestro caso vamos a crearnos un botoncito que nos diga la figura que hemos clickeado.

    La forma de crear nodos dentro del archivo está "explicada" muy por encima en los documentos de ayuda de mdsn (por lo menos ahora cuando escribo este post) por lo que seguramente acabarás antes viéndolo desde aquí ;)

     

    Básicamente lo que vamos a hacer es definirnos un botón y una identificación del mismo. Importante a tener en cuenta son los nodos <Commands/> y <Symbols/> (no quiero liaros con el nodo <Extern/> que no vamos a tocar ahora)

     

  2. Dentro del nodo <Commands/> introducir un nodo con la descripción del botón que quereis crear

<Buttons>

<Button guid="cmdDimeNombreFiguraGUID" id="cmdDimeNombreFiguraID type="Button">

<Parent guid="guidCmdSet" id="grpidContextMain"/>

<Strings>

<CanonicalName>cmdDimeNombreFigura</CanonicalName>

<ButtonText>Nombre figura</ButtonText>

<ToolTipText>Dice el nombre de la figura seleccionadaToolTipText>

</Strings>

</Button>

</Buttons>

 

  1. Fuera del nodo <Commands/> y al mismo nivel que él, declaramos el nodo <Symbols/> de la siguiente forma

<Symbols>

<GuidSymbol name="cmdDimeNombreFiguraGUID" value="{D5A40ECA-BA87-4a92-B6A7-A36C27C858AE}">

<IDSymbol name="cmdDimeNombreFiguraID" value="1"/>

</GuidSymbol>

</Symbols>

 

El valor del GuidSymbol ha sido generado mediante la aplicación guidgen.exe

El valor del IDSymbol puede ser decimal o hexadecimal (por ejemplo, valores 0x104 pueden darse) y podemos darle el que queramos.

 

  1. Estado del fichero Commands.vsct

    Una vez realizado esto, el fichero queda de la siguiente manera:

<?xml version="1.0" encoding="utf-8"?>

<CommandTable xmlns="http://schemas.microsoft.com/VisualStudio/2005-10-18/CommandTable" xmlns:xs="http://www.w3.org/2001/XMLSchema">

<!-- -->

<!-- This file contains custom command definitions. -->

<!-- -->

<!-- NOTE: Each time commands are added or changed, the "version" parameter to the -->

<!-- ProvideMenuResource attribute in Shell\Package.tt should be incremented. -->

<!-- This causes Visual Studio to re-merge the menu definitions for the package. -->

<!-- Otherwise, changes won't take effect until the next time devenv /setup is run. -->

<!-- -->

<Extern href="stdidcmd.h"/>

<Extern href="vsshlids.h"/>

<Extern href="msobtnid.h"/>

<Extern href="virtkeys.h"/>

<Extern href="DSLToolsCmdID.h"/>

<Include href="GeneratedCode\GeneratedVsct.vsct"/>

<Commands package="guidPkg">

<Buttons>

<Button guid="cmdDimeNombreFiguraGUID" id="cmdDimeNombreFiguraID" type="Button">

<Parent guid="guidCmdSet" id="grpidContextMain"/>

<Strings>

<CanonicalName>cmdDimeNombreFigura</CanonicalName>

<ButtonText>Nombre figura</ButtonText>

<ToolTipText>Dice el nombre de la figura seleccionada</ToolTipText>

</Strings>

</Button>

</Buttons>

</Commands>

<Symbols>

<GuidSymbol name="cmdDimeNombreFiguraGUID" value="{D5A40ECA-BA87-4a92-B6A7-A36C27C858AE}">

<IDSymbol name="cmdDimeNombreFiguraID" value="1"/>

</GuidSymbol>

</Symbols>

</CommandTable>

 

  1. Ahora, siguiendo lo que dice en los comentarios del propio fichero a la parte de arriba, le incrementamos el nº de versión al atributo ProvideMenuResource del fichero "GeneratedCode/Package.tt" (notese que en el comentario referencia a Shell/Package.tt ;)

    Para ello, lo editamos y reemplazamos lo que viene por defecto ( [VSShell::ProvideMenuResource("1000.ctmenu", 1)] ) , por [VSShell::ProvideMenuResource("1000.ctmenu", 2)]

     

  2. Ahora, vamos a asignarle el comportamiento al botón. Para ello hemos de abrir el fichero GeneratedCode/CommandSet.cs

    Y le añadimos los métodos OnPopUpMenuClick(), OnPopUpMenuDisplayAction() y GetMenuComands() a la clase.

    Evidentemente lo que no vamos a hacer es escribir el código directamente sobre el fichero CommandSet.cs puesto que dicho fichero se machaca cuando le damos a "Transform All Templates" por lo que haciendo uso de las posibilidades de definición de clases parciales, le definiremos el código en un nuevo fichero.

     

  3. Creamos la carpeta Customization y luego añadimos una nueva clase, que será la que contenga el código, de forma que nos quedará como la figura siguiente:

    NOTA: En mi caso, mi lenguaje se llama LenguajeOOMM y por eso la clase generada en CommandSet.cs se llama así. Por seguir una nomenclatura estándar he creado dicho nombre al fichero. En cualquier caso, el contenido siempre ha de ser el de la definición de la clase doblemente derivada.

     

    Antes hacíamos referencia al fichero CommandSet.cs. Por poco que lo abramos y le demos un vistazo veremos que sigue la pauta de clase doblemente derivada (que se sale del tema del post pero que en futuros post trataré) que crea una clase heredada de DslShell::CommandSet (en mi caso llamada LenguajeOOMMCommandSetBase), y luego otra clase que hereda de la anterior y es a la que vamos a añadirle la funcionalidad citada en el paso 6.

     

  4. Introducimos el siguiente código dentro de LenguajeOOMMCommandSet.cs (en tu caso, el fichero .cs que has creado para tal fin)

using System;

using System.Collections.Generic;

using System.ComponentModel.Design;

using Microsoft.VisualStudio.Modeling.Shell;

using System.Collections;

using System.Text;

 

namespace LenguajeOOMM

{

/// <summary>

/// Double-derived class to allow easier code customization.

/// </summary>

internal partial class LenguajeOOMMCommandSet : LenguajeOOMMCommandSetBase

{

protected override IList<System.ComponentModel.Design.MenuCommand> GetMenuCommands()

{

IList<System.ComponentModel.Design.MenuCommand> commands = base.GetMenuCommands();

 

DynamicStatusMenuCommand cmdDimeNombreFigura =

new DynamicStatusMenuCommand(

new EventHandler(OnPopUpMenuDisplayAction),

new EventHandler(OnPopUpMenuClick),

new CommandID(new Guid("D5A40ECA-BA87-4a92-B6A7-A36C27C858AE"), 1));

 

commands.Add(cmdDimeNombreFigura);

 

 

return commands;

}

 

/// <summary>

/// Lo que se desencadena al pinchar sobre el boton.

/// En principio no queremos nada mas que se muestren los objetos seleccioonados

/// </summary>

/// <param name="sender"></param>

/// <param name="e"></param>

internal void OnPopUpMenuClick(object sender, EventArgs e)

{

MenuCommand command = sender as MenuCommand;

 

StringBuilder sb = new StringBuilder();

foreach (object selectedObject in this.CurrentSelection)

{

sb.AppendLine("Objetos Seleccionados: " + selectedObject.ToString());

}

 

System.Windows.Forms.MessageBox.Show(sb.ToString());

}

 

/// <summary>

/// Se desencadena cuando vamos a mostrar el menú.

/// </summary>

/// <param name="sender"></param>

/// <param name="e"></param>

internal void OnPopUpMenuDisplayAction(object sender, EventArgs e)

{

MenuCommand command = sender as MenuCommand;

 

foreach (object selectedObject in this.CurrentSelection)

{

// Solo se desencadena si entre todos los objetos seleccionados, hay algun ExampleShape

//para conectores por tanto no sale. Comportamiento a posta

if (selectedObject is ExampleShape)

{

command.Visible = true;

command.Enabled = true;

return;

}

}

//por defecto deshabilitado

command.Visible = false;

command.Enabled = false;

}

}

}

 

 

  1. Una vez llegados a este punto solo nos queda pinchar sobre "Transforma ll templates" y compilar.

    Ahora nos saldrá el elemento del menú en cuestión y al clickear nos saldrá un MessageBox con la información que queríamos.

     

     

    Ni que decir tiene las posibilidades de esto. Podemos crearnos un menú que interactúe con nuestro propio modelo de forma que podamos cambiar incluso el aspecto o propiedades de nuestros objetos para que sin tener que borrar figuras, las podamos modificar

Windows Vista SP1: implicaciones para el desarrollo

Recientemente, Tim Sneath, del equipo de desarrollo de Microsoft en Redmond, publicaba en su blog (http://blogs.msdn.com/tims/) una interesante entrada sobre las implicaciones del Service Pack 1 para Windows Vista de cara al desarrollo.

 

La primera de ellas, tiene que ver con la unificación y solidez de las nuevas API instaladas en el SP1. Por primera vez, Vista comparte con el W2008 un enorme conjunto de librerías base, que permiten que las actualizaciones sean más coherentes y sencillas, para ambos sistemas, y los mecanismos de programación no requieran de personalizaciones en función de la plataforma. Además, Vista SP1, se beneficia de componentes presentes en ambos sistemas, que han sido exhaustivamente testados para el servidor 2008.

 

Uno de esos cambios se aprecia en las herramientas de administración de IIS 7.0, que ahora tienen un nivel de control muy superior al de la versión RTM.

 

Nota: Para los interesados en la programación con Silverlight, sirva decir que las extensiones MIME correspondientes a esta tecnología (.xaml y .xap), se encuentran por defecto instaladas en W.Server 2008, y también las incluye este Service Pack pero solo para instalaciones limpias con el SP1. Si el lector tenía Vista RTM instalado, podrá observar que dichas extensiones no aparecen al examinar los tipos MIME definidos en la Consola de Administración de IIS7. La solución: desinstalar y volver a Instalar IIS7 en el apartado "Activar o Desactivar características de Windows", en el Panel de Control.

 

Además, se han añadido nuevas API para control de las características Data Execution Protection y Kernel Patch Protection, así como realizado mejoras notables en los algoritmos de cifrado (en todas las categorías) y generación de números aleatorios, y todos ellos (junto a una actualización a DirectX 10.1), están disponibles para desarrolladores.

 

El SP1, instala elementos en las API's de .NET 3.0 (convirtiéndolas en .NET 3.0 SP1), estando muchas de las mejoras que aporta .NET 3.5 incluidas en este service pack (con excepciones notables, como LINQ). Esto es así, ya que cada una de las mejoras incluidas en las últimas actualizaciones de .NET Framework, ha sido construida sobre la anterior y no en forma paralela, tal y como puede verse en el gráfico adjunto.

image

 

 

 

 

 

 

 

 

 

 

 

 

Marino Posadas

Development Dept. (Spain)

Compilar desde la línea de comandos

Cómo.NET: Compilar desde la línea de comandos

Introducción:

Como seguramente sabrás, una de las cosas buenas de .NET Framework, es que al instalar el runtime (motor en tiempo de ejecución o CLR), es decir, cuando instalas el .NET Framework para que puedas usar cualquier aplicación escrita en .NET, automáticamente se instala también los compiladores de Visual Basic y C#.

Estos compiladores son los que utiliza el propio Visual Studio (o las versiones Express de Visual Basic y Visual C#) para compilar el código.

Si te decides a usarlos por tu cuenta, es decir, sin dejar que te ayude el Visual Studio, tendrás que hacer varias cosas.
A saber:

  • Abrir una ventana de consola (o ventana de MS-DOS)
  • Escribir el nombre del compilador que vas a usar, en el caso de Visual Basic para .NET es: vbc.exe (Visual Basic Compiler), y si quieres compilar algo de C#, tendrás que usar: csc.exe (C Sharp Compiler).
  • Debido a que esos compiladores están ubicados en la carpeta de .NET Framework, tendrás que incluir esa ubicación en la variable PATH del sistema operativo.
  • Esto último lo puedes hacer más fácil, y es usando el acceso directo que Visual Studio incluye, en el caso de Visual Studio 2008 (en inglés) el nombre es: Visual Studio 2008 Command Prompt y lo puedes encontrar en:
    Start>All programs>Microsoft Visual Studio 2008>Visual Studio Tools.
    • En el caso de Visual Studio 2005, cambia el 2008 por el 2005 y si tu Windows está en castellano, tendrás que cambiar "Start" por "Inicio", "All Programs" por "Todos los programas" y aunque el nombre de la carpeta sigue siendo "Visual Studio Tools", el nombre del acceso directo es: "Símbolo del sistema de Visual Studio 2008".
  • Ese acceso directo llama a un fichero llamado "vcvarsall.bat" que está en el directorio de instalación de Visual Studio, concretamente en el directorio de C++.
  • Si no tienes el Visual Studio, por ejemplo, porque hayas instalado solo el .NET Framework o porque hayas instalado solo las versiones Express de Visual Basic o Visual C#, aún así todavía puedes usar los compiladores, ya que no es necesario instalar ningún IDE o entorno de desarrollo para crear programas de .NET.
    • En ese caso, tendrás que crear un acceso directo a un fichero que contenga lo que te muestro en el listado 1.
      En el que tendrás que indicar el path correcto de .NET, que normalmente es como te muestro en ese listado, pero que debes indicar correctamente. Esto es válido para .NET 3.5.
    • Y la forma de llamar a ese fichero (para que se abra la ventana de MS-DOS) es de esta forma:
      %comspec% /k "path y nombre del fichero del listado 1"
  • Una vez que ya tienes la "línea de comandos" lista para usar, es cuestión de escribir los comandos necesarios.
  • Pero esos dependerán de lo que estés compilando.
  • Para ver las opciones disponibles, usa el parámetro /?, por ejemplo, para ver las opciones del compilador de Visual Basic, tendrás que usar: vbc /?
    Y si es para C#: csc /?
  • Y ahora... ¡a jugar! ;-)))

 

@echo off
Echo Linea de comandos para los compiladores de .NET Framework 3.5
Echo.
rem %comspec% /k "net_bin.bat"
rem

@SET FrameworkDir=C:\WINDOWS\Microsoft.NET\Framework
@SET FrameworkVersion=v2.0.50727

@set PATH=%FrameworkDir%\v3.5;%FrameworkDir%\%FrameworkVersion%;%PATH%
@set LIBPATH=%FrameworkDir%\v3.5;%FrameworkDir%\%FrameworkVersion%;%LIBPATH%

Listado 1. Contenido mínimo del fichero para usar los compiladores de .NET Framework 3.5

 

¿Por qué te explico todo esto?

Entre otras cosas, por si has comprado mi libro de las Novedades de Visual Basic 9.0 y quieres crearte tu propia DLL para suplir el Runtime de Visual Basic y como eso debes hacerlo desde la línea de comandos (no se pueden crear ese tipo de aplicaciones que usan una versión independiente del runtime de VB desde el IDE de Visual Studio), pues así sabes cómo tener esa línea de comandos, además de que (creo) que con las versiones Express no se crea ese acceso directo, pues... ya no tendrás límite para crear tu código sin necesidad de tener que instalar el Visual Studio.

 

Espero que te sea de utilidad.

Nos vemos.
Guillermo

Este artículo también está publicado en mi sitio:
Cómo.NET: Compilar desde la línea de comandos

Herramienta para generar código .Net de llamada a funciones API de Windows

Se encuentra disponible en MSDN una herramienta para generar dicho tipo de código.

EL artículo correspondiente con vínculo para descargar la herramienta, está aquí. http://msdn2.microsoft.com/en-us/magazine/cc164193.aspx

Registrar en SQL Server los inicios y cierres de sesión de los usuarios de un dominio
Más de una vez se ha planteado en los foros esta cuestión. Por esta razón me he decidido a escribir este artículo sobre el tema.
 
Para hacer esto, es decir, para registrar en una tabla de SQL Server los inicios y cierres de sesión de los usuarios de un dominio, tenemos varias alternativas. Una de ellas sería crear un componente que leyera el registro de sucesos de Windows en el controlador de dominio. Este componente podría leer el registro de sucesos cada cierto tiempo y volcar los inicios y cierres de sesión en una tabla de SQL Server. También podría estar pendiente del registro de sucesos e insertar un registro en la tabla cada vez que se inserte en el registro de sucesos un registro correspondiente a un inicio o cierre de sesión. La verdad es que este enfoque no me gusta demasiado porque tal componente tendríamos que ejecutarlo en todos los controladores de dominio y para andar escuchando el registro de sucesos tendríamos que crear un servicio Windows... demasiado complejo.
 
Otra alternativa, que es la que voy a presentar en este artículo, se basa en establecer los scripts de incio y cierre de sesión para los usuarios. Creamos unos scripts sencillos en Visual Basic Script que se encarguen de hacer el registro y configuramos las políticas de grupo para que se ejecuten estos scripts al inicio y cierre de sesión de los usuarios.
 
Los scripts podrían ejecutar directamente un procedimiento almacenado en la base de datos o podríamos buscar otras alternativas. Lo de ejecutar directamente un procedimiento almacenado desde los scripts tiene sus inconvenientes. Por un lado todos los usuarios tendrían que tener acceso directo al servidor de base de datos, lo que no simpre es conveniente y a veces no es posible. Por otra parte si hay muchos usuarios tendríamos muchas conexiones y no podríamos hacer uso del connection pooling. Además de que de esta manera se ralentiza un poco el inicio de sesión del usuario.
 
Una buena alternativa sería que los scripts llamaran a un servicio web y fuera este servicio web el que llamara al procedimiento almacenado, así haríamos un uso efectivo del connection pooling y no necesitaríamos tener acceso directo de los usuarios al servidor de base de datos. El inconveniente es que, aunque es posible, resulta bastante complicado llamar a un servicio web desde Visual Basic Script. Para simplificar las cosas no vamos a hacer un servicio web SOAP estándar, sino un servicio web "casero" muchísimo más simple que pueda llamarse fácilmente desde Visual Basic Script. La idea es crear un generic handler en un sitio web ASP.NET muy simple. Al generic handler le indicamos la operación (inicio o cierre de sesión) mediante la query string y el scrip de Visual Basic Script sólo tiene que hacer una petición GET. Para hacer las cosas mejor, el script puede hacer la llamada de forma asíncrona, en cuanto haya enviado la petición se olvida de todo sin esperar a que el generic handler ejecute el procedimiento almacenado.
 
Este es el código del script LogOnScript.vbs:
Dim req
Set req = WScript.CreateObject("MSXML2.XMLHTTP")
req.open "GET", "http://MiServidorWeb/MiSitioWeb/RegistrarSesion.ashx?Operacion=InicioSesion"
req.send

Do While req.readyState <= 1
	 WScript.Sleep 50
Loop
 
El script envía de forma asíncrona una petición HTTP GET al generic handler RegistrarSesion.ashx pasándole el tipo de operación en la query string. Luego espera en un bucle donde comprueba cada 50 milisegundos que se haya enviado la petición y entonces termina si esperar a que el generic handler procese la petición.
 
El código de LogOffScript.vbs es muy similar, sólo cambia la query string:
 
Dim req
Set req = WScript.CreateObject("MSXML2.XMLHTTP")
req.open "GET", "http://MiServidorWeb/MiSitioWeb/RegistrarSesion.ashx?Operacion=CierreSesion"
req.send

Do While req.readyState <= 1
	 WScript.Sleep 50
Loop
 
En nuestro servidor web tenemos nuestro generic handler que es el que se encarga de llamar al procedimiento almacenado RegistrarSesion. Este es el código del generic handler:
 
 
Imports System
Imports System.Web
Imports System.Data.SqlClient

Public Class RegistrarSesion : Implements IHttpHandler

    Public Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest
        Using cn As SqlConnection = CrearConexion(), _
            proc As New Procedimientos.RegistrarSesion(cn)
            Dim operacion As String = context.Request.QueryString("Operacion")
            If operacion = "InicioSesion" Then
                proc.ExecuteNonQuery(context.User.Identity.Name, 1)
                context.Response.Write("OK")
            ElseIf operacion = "CierreSesion" Then
                proc.ExecuteNonQuery(context.User.Identity.Name, 2)
                context.Response.Write("OK")
            Else
                context.Response.StatusCode = 500
                context.Response.Write("Operacion no valida")
            End If
        End Using
    End Sub

    Public ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable
        Get
            Return False
        End Get
    End Property

    Private Function CrearConexion() As SqlConnection
        Return New SqlConnection(ConfigurationManager.ConnectionStrings("ConnectionString").ConnectionString)
    End Function

End Class
 
 
El generic handler comprueba el valor de la query string para establecer el valor del parámetro operacion del procedimiento almacenado. Procedimientos.RegistrarSesion es una clase generada por el generador de clases para procedimientos almacenados correspondiente al procedimiento almacenado RegistrarSesion.
 
La tabla y procedimiento almacenado son los siguientes:
 
CREATE TABLE Sesiones
(
    IdSesion int IDENTITY(1,1) PRIMARY KEY,
    FechaHora datetime NOT NULL DEFAULT GETDATE(),
    Usuario varchar(128) NOT NULL,
    Operacion tinyint NOT NULL
)

GO

CREATE PROCEDURE RegistrarSesion(
    @Usuario varchar(128),
    @Operacion tinyint
)
AS
    INSERT INTO Sesiones(Usuario, Operacion)
    VALUES (@Usuario, @Operacion)

GO

El último paso sería configurar las políticas de grupo para que se ejecuten los scripts. Eso lo podemos hacer con la consola administrativa en el controlador de dominio.
 
Aquí tenéis algunas capturas de pantalla:
 
 
GP
 
 
GP
 
 
 
 
 
 
 
 
 
 
 
 
Orden de las columnas DataGridView al asignarle una consulta de LINQ (en VB9)

Introducción:

Cuando Visual Basic 9.0 (ó 2008) crea un tipo anónimo en una consulta de LINQ (siempre crea un tipo anónimo para el valor devuelto por la cláusula Select), se ve que las propiedades las clasifica por orden alfabético, pero en el orden de los nombres de esas propiedades.

Nota:
Este artículo lo escribo a raíz de una consulta de erickorlando en mis foros.
Si quieres ver ese hilo, pulsa en este link:
http://foros.elguille.info/Mensajes.aspx?ID=39024 

Esto en principio no es problema, pero cuando el resultado de esa consulta se asigna a un DataGridView, ese control crea automáticamente las columnas (cabeceras) a partir de las columnas de los datos que se le asigna, y en el caso de asignarle el resultado de una consulta LINQ, esas columnas no son otra cosa que las propiedades del tipo anónimo creado en la orden Select. Y debido a que VB ordena las propiedades/columnas por el nombre de las mismas, resulta que es posible que lo que se muestre en el DataGridView no sea lo deseado.

Por ejemplo, si tenemos una consulta como la de este código:

' Listado 1
Dim cust = From c In northwind.Customers _
           Where c.CompanyName.StartsWith("L") _
           Order By c.CustomerID, c.CompanyName, c.ContactName _
           Select c.CustomerID, c.CompanyName, c.ContactName

' En DataGridView se clasifica como CompanyName, ContactName, CustomerID
DataGridView1.DataSource = cust

El DataGridView genera las columnas tal como se ve en la figura 1.
Fíjate que en vez de mostrarse tal como se indica en el Select, se ha clasificado por el nombre de las propiedades.

Figura 1. Las columnas del grid están por orden alfabético
Figura 1. Las columnas del grid están por orden alfabético

La solución que proporciona erickorlando en el hilo que te he comentado antes, es creando un tipo anónimo que tenga los nombres de las columnas de forma que se clasifiquen bien y se muestren los datos como se quiere... es decir, primero el ID del cliente, después la empresa y por último el nombre del contacto.
Este es el código que hace eso:

' Listado 2
Dim cust = From c In northwind.Customers _
           Where c.CompanyName.StartsWith("L") _
           Order By c.CustomerID, c.CompanyName, c.ContactName _
           Select New With {Key .Cliente = c.CustomerID, _
                            Key .Compañia = c.CompanyName, _
                            Key .Contacto = c.ContactName}

DataGridView1.DataSource = cust

Ese mismo código se puede escribir también de esta forma:

' Listado 3
Dim cust = From c In northwind.Customers _
           Where c.CompanyName.StartsWith("L") _
           Order By c.CustomerID, c.CompanyName, c.ContactName _
           Select Cliente = c.CustomerID, _
                  Compañia = c.CompanyName, _
                  Contacto = c.ContactName

DataGridView1.DataSource = cust

La diferencia entre el listado 2 y el 3, es que en el segundo las propiedades son de solo lectura (Key indica que es de solo lectura) y en el listado 3, son de lectura y escritura, además de que, para generar el tipo anónimo no es necesario indicarlo explícitamente. Es la ventaja de VB, je, je, que hasta en esto nos facilita el trabajo.

Los dos listados anteriores mostrarán los datos como en la figura 2.

Figura 2. Las columnas ordenadas como queremos
Figura 2. Las columnas ordenadas como queremos

 

El problema de esta solución es que si esos nombres de las propiedades indicadas después de Select no están en el orden que nos gustaría, pues... que no se mostrarían como queremos.

Por tanto, la solución que "casi" nos puede solucionar la papeleta es decirle al DataGridView que no genere automáticamente los nombres, de las columnas.
Pero si le decimos eso... pues... ¡no veremos nada! (o casi), así que... tenemos que crear las columnas manualmente, indicar qué queremos que se muestre en la cabecera del DataGridView... pero lo más importante es que le tenemos que indicar qué columna de los datos corresponde con cada columna del DataGridView.
Los nombres de las columnas de los datos son los nombres de las propiedades del tipo anónimo que creamos en la consulta de LINQ, para ser más concreto los nombres que usemos en el Select.

Este es el código que no genera los nombres de las columnas del DataGridView y que puedes usar para que estén en el orden que te interese:

' Listado 4
Dim cust = From c In northwind.Customers _
           Where c.CompanyName.StartsWith("L") _
           Order By c.CustomerID, c.CompanyName, c.ContactName _
           Select c.CustomerID, c.CompanyName, c.ContactName

DataGridView1.AutoGenerateColumns = False
DataGridView1.Columns.Clear()

DataGridView1.Columns.Add("CustomerID", "CustomerID")
DataGridView1.Columns.Add("CompanyName", "CompanyName")
DataGridView1.Columns.Add("ContactName", "ContactName")
DataGridView1.Columns(0).DataPropertyName = "CustomerID"
DataGridView1.Columns(1).DataPropertyName = "CompanyName"
DataGridView1.Columns(2).DataPropertyName = "ContactName"

DataGridView1.DataSource = cust

Fíjate que lo que hay en la cláusula Select es lo mismo que en el listado 1, es decir, los nombres de las columnas de la tabla Customers de la base de datos Northwind.

Esos nombres de las columnas de la tabla son los que el compilador de Visual Basic creará para los nombres de las propiedades del tipo anónimo que se crea siempre con Select. Pero como ya vimos que el VB ordena alfabéticamente esas propiedades, lo que tenemos que hacer es asignar al DataGridView lo que queremos que se muestre, y en el orden que nos interese. En este caso, las columnas se muestran en el mismo orden que están indicadas después de Select, y el resultado sería el de la figura 3.

Figura 3. Las columnas ordenadas manualmente
Figura 3. Las columnas ordenadas manualmente

El código del listado 3, hace lo siguiente:

  1. Genera el código de la consulta de LINQ.
  2.  Le asignamos un valor False a la propiedad AutoGenerateColumns del DataGridView para que no genere las columnas.
  3.  Eliminamos las columnas que tuviera, ya que si antes hemos mostrado los datos, pues habría más columnas de la cuenta, tal como puedes ver en la figura 4.

Figura 4. Si no borramos las columnas, se van acumulando
Figura 4. Si no borramos las columnas, se van acumulando

  1. Añadimos cada una de las columnas que queremos tener.
    El primero parámetro del método Add es el nombre de la columna, y el segundo es lo que queremos que se muestre en esa columna. Si quisiéramos que se mostrara como en la figura 5, tendríamos que crear esas columnas tal como se muestra en el listado 5.

Figura 5. Cabeceras personalizadas
Figura 5. Cabeceras personalizadas

' Listado 5
DataGridView1.Columns.Add("CustomerID", "ID")
DataGridView1.Columns.Add("CompanyName", "Empresa")
DataGridView1.Columns.Add("ContactName", "Contacto")
  1. Como te decía antes, la parte más importante es asignar a la propiedad DataPropertyName de cada columna el campo al que queremos enlazar los datos, ya que ese es el "dato" que tiene en cuenta el DataGridView para saber qué se debe mostrar en cada columna de cada fila.

Nota:
Si el código del listado 4 lo pones junto con otros códigos que manipulen el contenido del DataGridView, debes borrar siempre el contenido de las columnas: DataGridView1.Columns.Clear() antes de asignar los datos al DataGridView por medio de la asignación a la propiedad DataSource. Al menos si no quieres que haya columnas huérfanas, al estilo de la figura 4.

Y si siempre vas a usar esas columnas con el DataGridView, puedes escribir todo el código que hay después de crear la consulta LINQ y antes de asignar el valor al DataSource en el evento Load del formulario o en cualquier otro sitio, ya que si siempre vas a asignar los mismos datos (las mismas columnas/propiedades), pues... te evitas estar repitiendo ese código.

 

Tal como te comento, todo este "follón" solo es necesario hacerlo con Visual Basic, ya que en C# no se ordenan las propiedades de los tipos anónimos generados en la cláusula select.
En el siguiente código tienes lo que habría que escribir en C# para tener algo parecido a la figura 3.

// Listado 6
var cust = from c in northwind.Customers 
           where c.CompanyName.StartsWith("L") 
           orderby c.CustomerID, c.CompanyName, c.ContactName 
           select new {c.CustomerID, c.CompanyName, c.ContactName};

DataGridView1.DataSource = cust;

 

En el ZIP tienes dos proyectos, uno para Visual Basic y el otro para C#.

En esos proyectos se usan objetos LINQ to SQL, que están conectados a la base de datos Northwind, por tanto, debes tener esa base de datos y seguramente tendrás que cambiar la cadena de conexión, ya que la que yo uso es:
Data Source=(local)\sqlexpress; Initial Catalog=Northwind; Integrated Security=True.

 

Nos vemos.
Guillermo

P.S.
Si quieres saber más sobre todo lo referente a LINQ y otras novedades de Visual Basic 9.0 (ó 2008), te recomiendo que compres mi libro electrónico Las Novedades de Visual Basic 9.0. ;-))))

 


Espacios de nombres usados en el código de este artículo:

System.Windows.Forms
System.Linq


Este artículo también lo puedes ver en mi sitio:
http://www.elguille.info/NET/dotnet/LINQ_orden_columnas_DataGridView_VB9.aspx
 
Y puedes bajarte los proyectos de prueba para Visual Basic 9.0 y C# 3.0 

Añadir un DataSet (tipado) a un proyecto de Visual Studio 2008

Introducción:

En este artículo te explico los pasos para añadir un DataSet tipado (conjunto de datos) a un proyecto de Visual Studio 2008. Estos pasos, entre otras cosas, te serán de utilidad si quieres usar todo lo relacionado con LINQ to DataSet.

Nota:
Estos pasos te serán de utilidad para seguir el ejemplo del capítulo 9 de mi libro Novedades de Visual Basic 9.0.

 

Añadir un DataSet (conjunto de datos) a un proyecto de Visual Studio 2008

Empecemos añadiendo un DataSet a nuestro proyecto, ese objeto tendrá inicialmente la tabla Employees de la base de datos Northwind, que es la que utilizaremos en nuestro primer ejemplo, después veremos cómo agregar más información a ese DataSet.

Creo que a estas alturas de la programación "asistida" ya sabremos crear un DataSet con los asistentes, pero por si algún lector ha llegado hasta aquí sin antes haber usado un asistente, veamos los pasos que tendremos que dar.

Para simplificar las cosas y no distraernos, vamos a crear un proyecto de tipo consola, ya que lo que realmente interesa es el código que tenemos que escribir, de otras facilidades ya nos ocuparemos en otra ocasión (aunque sea sin mi asistencia directa).

En el proyecto, desde el menú contextual en el Explorador de soluciones (figura 1), seleccionamos agregar un nuevo elemento, y de ese cuadro de diálogo, seleccionamos Conjunto de datos (DataSet en inglés) y le damos el nombre NorthwindDataSet.xsd, tal como vemos en la figura 2.

En unos segundos tendremos el diseñador del DataSet en blanco, ahora tendremos que crear una conexión a la base de datos de la que queremos obtener la información.

Nota:
Los pasos que siguen a continuación son válidos para Visual Studio 2008, pero no para la versión Express de Visual Basic 2008, ya que las versiones Express solo permiten crear conexiones asistidas a archivos de bases de datos no a una instancia de SQL Server, si utilizas la versión Express, tendrás que crear manualmente la conexión al servidor de SQL Server.

Puedes ver este artículo para un ejemplo paso a paso de cómo crear esa conexión a un servidor de SQL Server desde la versión Express de Visual Basic:
http://www.elguille.info/NET/ADONET/base_sql_asistente_express.htm.

 

Figura 1. Agregar un nuevo elemento desde el explorador de soluciones
Figura 1. Agregar un nuevo elemento desde el explorador de soluciones

 

Figura 2. Agregamos un nuevo conjunto de datos (DataSet)
Figura 2. Agregamos un nuevo conjunto de datos (DataSet)

 

Conectar a un servidor de SQL Server y elegir la base de datos

Para crear la cadena de conexión a una base de datos, debemos mostrar la ventana del Explorador de servidores (si no está visible, podemos mostrarla desde el menú Ver>Explorador de servidores o pulsando las teclas Ctrl+Alt+S), en esa ventana seleccionamos Conexiones de datos y del menú mostrado al pulsar con el botón secundario del ratón seleccionamos Agregar conexión (figura 3), esta acción nos mostrará un cuadro de diálogo en el que se nos pide que indiquemos la base de datos queremos usar, y debido a que en Visual Studio 2008 tenemos varias formas de hacerlo, nos mostrará un cuadro de diálogo como el de la figura 4 para seleccionar el "origen de datos".

 

Figura 3. Agregar una nueva conexión al explorador de servidores
Figura 3. Agregar una nueva conexión al explorador de servidores

 

Figura 4. Elegir el proveedor del origen de datos
Figura 4. Elegir el proveedor del origen de datos

 

En el cuadro de diálogo Elegir origen de datos, seleccionamos Microsoft SQL Server y pulsamos en Continuar, ahora tendremos un nuevo cuadro de diálogo en el que indicaremos el servidor de SQL Server en el que tenemos la base de datos a la que queremos acceder (en mi caso la he instalado en la instancia de SQLEXPRESS) y una vez indicado el servidor, podremos elegir la base de datos de la lista desplegable que hay bajo la opción Seleccione o escriba el nombre de la base de datos, tal como vemos en la figura 5.

 

Figura 5. Seleccionamos el servidor de SQL Server y la base de datos para la conexión
Figura 5. Seleccionamos el servidor de SQL Server y la base de datos para la conexión

 

Después de aceptar, veremos en el Explorador de servidores la nueva conexión, si expandimos la conexión y mostramos el contenido, podremos elegir la tabla que queremos incluir en el DataSet, para nuestro ejemplo, seleccionaremos la tabla Employees y la arrastramos hasta el diseñador del "conjunto de datos" (figura 6).

 

Figura 6. Desde las conexiones de datos, agregamos las tablas que queremos tener en el DataSet
Figura 6. Desde las conexiones de datos, agregamos las tablas que queremos tener en el DataSet

 

Una vez que tenemos definido correctamente el DataSet que queremos usar, podemos pasar al código del Modulo1 y empezar a escribir nuestro código para acceder al contenido del DataSet que acabamos de crear.

 

Nos vemos.
Guillermo

P.S.
Este artículo también está publicado en mi sitio: http://www.elguille.info/NET/ADONET/novedadesVB9_DataSet_tipado.aspx

Generador de clases para procedimientos almacenados
En marzo de 2006 publiqué el generador de clases para procedimientos almacenados en la web de El Guille. En ese artículo se explicaba el funcionamiento de tal generador y se proporcionaba un instalador, aunque no se proporcionaba el código fuente.
 
Con la venida de Visual Studio 2008 he decidido retomar el tema y hacer lo siguiente con el generador:
  • Migrar el instalador de Wix 2.0 a Wix 3.0.
  • Corregir un bug que aparecía cuando cambiaban los parámetros de un procedimiento almacenado.
  • Hacer que funcione dentro de VS 2008.
  • Publicar el código fuente.

El código fuente lo podéis desargar de aquí y e instalador lo podéis descargar de aquí.

El generador sigue siendo una solución de Visual Studio 2005 que para abrirla se necesita el Visual Studio 2005 SDK de febrero de 2007 y el Wix 3.0

 

 

 

 

 

 

 
 
Utilidad para cambiar la resolución y calidad de imágenes JPG
El otro día un amigo me planteo un problema. Tenía miles de imágenes JPG de alta resolución que tenía que subir a la web. Las imágenes eran demasiado pesadas para colgarlas tal cual en la web, así que había que cambiarles la resolución.
 
Hay varios programas que pueden cambiar la resolución y calidad de las imágenes, pero generalmente sólo permiten hacerlo uno a uno. Así que me decidí a hacer una pequeña utilidad que pudiera cambiar la resolución y calidad de todas las imágenes contenidas en una carpeta.
 
Este es el aspecto que tiene la utilidad:
 
Compresor de imágenes
 
El funcionamiento es bastante sencillo:
  1. Seleccionas la carpeta donde están las imágenes a convertir.
  2. Seleccionas la carpeta donde van a ir las imágene