VarChar y NVarChar en SQL Server y MySQL el código para VB y C#

Pues eso… lo prometido es deuda y aquí tienes el código que te prometí del ejemplo correspondiente al post anterior (VarChar y NVarChar en SQL Server) para VB y C#.

Este código te permite comprobar que usando VARCHAR también puedes agregar caracteres especiales del idioma español: vocales acentuadas, eñes, etc.

En este ejemplo también uso dos bases de datos (en realidad 3), una de SQL Server y otra de MySQL en un servidor que está en la nube (en un hosting de Internet, concretamente en uno de Domitienda.com que es donde yo tengo mis sitios alojados). La tercera base es una de SLQ Server en mi servidor local (el de SQLEXPRESS).

El diseño del formulario y el código de VB y C#

Este es el formulario en modo de diseño (figura 1):

Figura 1. El formulario en modo de diseño.
Figura 1. El formulario en modo de diseño.

Nota:

En el código tienes la cadena de conexión para las bases de datos que están en “la web” y es posible que dejen de estar disponibles dentro de algún tiempo (no sé si una semana, un mes, un año o un día), así que… si quieres probarlo, usa cuanto antes el código del ZIP que te pondré al final o crea un proyecto a partir de lo que aquí te muestro.

El proyecto está creado con Visual Studio 2019 Preview 4.3 pero debería cargar también en Visual Studio 2017 y utiliza el .NET Framework 4.7.2.

El código para Visual Basic .NET

'------------------------------------------------------------------------------
' Prueba de los tipos de SQL VarChar y NVarChar                     (19/Mar/19)
' Usando base de SQL Server y MySQL
'
'
' (c) Guillermo (elGuille) Som, 2019
'------------------------------------------------------------------------------
Option Strict On
Option Infer On

Imports System.Data
Imports System.Data.SqlClient
' Versión de mysql.data.dll: Connector/Net 6.0.3.0
Imports MySql.Data.MySqlClient

Imports System.Linq


Public Class Form1

    Private inicializando As Boolean = True

    ''' <summary>
    ''' Para las primeras pruebas uso una base local
    ''' </summary>
    Private UsarBaseLocal As Boolean = True

    Private ServerName As String = ".\SQLEXPRESS"
    Private DatabaseName As String = "PruebaVarChar"
    Private UsuarioDB As String = "elGuille_info"
    Private PasswordDB As String = "VarChar_19"

    Private Tabla As String = "VarCharElGuille"
    ' Campos: Fecha, ConVarChar varchar(50), ConNVarChar nvarchar(50)
    ' en MySQL no hay NVARCHAR y uso TEXT(50)

    Private MaxFilas As Integer = 5

    ''' <summary>
    ''' La cadena de conexión a la base de datos
    ''' </summary>
    Private Function CadenaConexionSQL() As String
        ConectarSQL()

        Dim csb As New SqlConnectionStringBuilder

        csb.DataSource = ServerName
        csb.InitialCatalog = DatabaseName
        csb.UserID = UsuarioDB & "SQL"
        csb.Password = PasswordDB
        csb.IntegratedSecurity = False

        Return csb.ConnectionString
    End Function

    ''' <summary>
    ''' La cadena de conexión a la base de datos
    ''' </summary>
    Private Function CadenaConexionMySQL() As String
        If UsarBaseLocal Then Return "No hay base local de MySQL"

        ConectarMySQL()

        Dim csb As New MySqlConnectionStringBuilder

        csb.Server = ServerName
        csb.Database = DatabaseName
        csb.UserID = UsuarioDB & "My"
        csb.Password = PasswordDB

        Return csb.ConnectionString
    End Function


    ''' <summary>
    ''' Conectar a la base de datos de SQL Server
    ''' </summary>
    Private Sub ConectarSQL()
        If UsarBaseLocal Then
            ServerName = ".\SQLEXPRESS"
        Else
            ServerName = "sql3.servidoreswindows.net"
        End If
    End Sub

    ''' <summary>
    ''' Conectar a la base de datos de MySQL.
    ''' Siempre en remoto.
    ''' </summary>
    Private Sub ConectarMySQL()
        ServerName = "mysql1.servidoreswindows.net"
    End Sub


    ''' <summary>
    ''' Añadir el dato a la base de SQL Server
    ''' </summary>
    Private Function AñadirSQL() As String
        Dim sCon As String = CadenaConexionSQL()
        Dim sel = $"INSERT INTO {Tabla} (Fecha, ConVarChar, ConNVarChar) " &
                  $"VALUES('{DateTime.Now}', '{txtDato.Text}', '{txtDato.Text}')"

        Using con As New SqlConnection(sCon)
            Dim cmd As New SqlCommand(sel, con)

            Try
                con.Open()
                cmd.ExecuteNonQuery()
                con.Close()

                Return ""
            Catch ex As Exception
                Return $"ERROR: {ex.Message}"
            End Try
        End Using
    End Function

    ''' <summary>
    ''' Añadir el dato a la base de datos de MySQL
    ''' </summary>
    Private Function AñadirMySQL() As String
        If UsarBaseLocal Then Return "No hay base local de MySQL"

        Dim sCon = CadenaConexionMySQL()
        ' En MySQL he definido Fecha como varchar(20)
        Dim sel = $"INSERT INTO {Tabla} (Fecha, ConVarChar, ConNVarChar) " &
                  $"VALUES('{DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss")}', '{txtDato.Text}', '{txtDato.Text}')"

        Using con As New MySqlConnection(sCon)
            Dim cmd As New MySqlCommand(sel, con)

            Try
                con.Open()
                cmd.ExecuteNonQuery()
                con.Close()

                Return ""
            Catch ex As Exception
                Return $"ERROR: {ex.Message}"
            End Try
        End Using
    End Function

    ''' <summary>
    ''' Mostrar los datos de la base de SQLServer
    ''' </summary>
    Private Function MostrarDatosSQL(ByRef dt As DataTable) As String
        Dim orden = If(chkOrden.Checked, "DESC", "ASC")
        Dim sel = $"SELECT TOP ({MaxFilas}) * FROM {Tabla} ORDER BY Fecha {orden}"

        Dim sCon = CadenaConexionSQL()
        Dim da As SqlDataAdapter
        dt = New DataTable

        Try
            ' Crear un nuevo objeto del tipo DataAdapter
            da = New SqlDataAdapter(sel, sCon)

            ' Llenar la tabla con los datos indicados
            da.Fill(dt)

            Return $"Recuperadas {dt.Rows.Count} filas."
        Catch ex As Exception
            Return $"ERROR: {ex.Message}"
        End Try

    End Function

    ''' <summary>
    ''' Mostrar los datos de MySQL
    ''' </summary>
    Private Function MostrarDatosMySQL(ByRef dt As DataTable) As String
        If UsarBaseLocal Then Return "No hay base local de MySQL"

        Dim orden = If(chkOrden.Checked, "DESC", "ASC")
        ' MySQL no tiene TOP, usar en su lugar LIMIT
        Dim sel = $"SELECT * FROM {Tabla} ORDER BY Fecha {orden} LIMIT {MaxFilas}"

        Dim sCon = CadenaConexionMySQL()
        Dim da As MySqlDataAdapter
        dt = New DataTable

        Try
            ' Crear un nuevo objeto del tipo DataAdapter
            da = New MySqlDataAdapter(sel, sCon)

            ' Llenar la tabla con los datos indicados
            da.Fill(dt)

            Return $"Recuperadas {dt.Rows.Count} filas."
        Catch ex As Exception
            Return $"ERROR: {ex.Message}"
        End Try

    End Function

    Private Sub btnAñadir_Click(sender As Object,
                                e As EventArgs) Handles btnAñadir.Click
        Dim msg = ""

        msg = AñadirSQL() & vbCrLf
        msg &= AñadirMySQL()

        LabelError.Text = msg

        btnRefrescar.PerformClick()
    End Sub

    Private Sub btnRefrescar_Click(sender As Object,
                                   e As EventArgs) Handles btnRefrescar.Click
        Dim dt As DataTable = Nothing
        Dim msg = ""
        lvDatos.Items.Clear()

        Dim i = 5
        Integer.TryParse(txtFilas.Text, i)
        MaxFilas = i

        msg = MostrarDatosSQL(dt) & vbCrLf
        asignarDatos("SQL", dt)

        Dim lvi = lvDatos.Items.Add("---")
        lvi.SubItems.Add("---")
        lvi.SubItems.Add("---")
        lvi.SubItems.Add("---")

        dt = Nothing
        msg &= MostrarDatosMySQL(dt)
        asignarDatos("MySQL", dt)

        LabelError.Text = msg
    End Sub

    Private Sub asignarDatos(base As String, dt As DataTable)
        If dt Is Nothing Then Return

        For Each r As DataRow In dt.Rows
            Dim lvi = lvDatos.Items.Add(base)
            lvi.SubItems.Add(r("Fecha").ToString)
            lvi.SubItems.Add(r("ConVarChar").ToString)
            lvi.SubItems.Add(r("ConNVarChar").ToString)
        Next

    End Sub

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        inicializando = False
    End Sub

    Private Sub ChkUsarBaseLocal_CheckedChanged(sender As Object, e As EventArgs) Handles chkUsarBaseLocal.CheckedChanged
        If inicializando Then Return

        UsarBaseLocal = chkUsarBaseLocal.Checked
    End Sub

End Class

El código para C#

//-----------------------------------------------------------------------------
// Prueba de los tipos de SQL VarChar y NVarChar                    (21/Mar/19)
// Usando base de SQL Server y MySQL
//
//
// (c) Guillermo (elGuille) Som, 2019
//-----------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

using System.Data.SqlClient;
// Versión de mysql.data.dll: Connector/Net 6.0.3.0
using MySql.Data.MySqlClient;

namespace VarChar_y_NVarChar_cs
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private bool inicializando = true;

        /// <summary>
        /// Para las primeras pruebas uso una base local
        /// </summary>
        private bool UsarBaseLocal = true;

        private string ServerName = @".\SQLEXPRESS";
        private string DatabaseName = "PruebaVarChar";
        private string UsuarioDB = "elGuille_info";
        private string PasswordDB = "VarChar_19";

        private string Tabla = "VarCharElGuille";
        // Campos: Fecha, ConVarChar varchar(50), ConNVarChar nvarchar(50)
        // en MySQL no hay NVARCHAR y uso TEXT(50)

        private int MaxFilas = 5;

        /// <summary>
        /// La cadena de conexión a la base de datos
        /// </summary>
        private string CadenaConexionSQL()
        {
            ConectarSQL();

            var csb = new SqlConnectionStringBuilder();

            csb.DataSource = ServerName;
            csb.InitialCatalog = DatabaseName;
            csb.UserID = UsuarioDB + "SQL";
            csb.Password = PasswordDB;
            csb.IntegratedSecurity = false;

            return csb.ConnectionString;
        }

        /// <summary>
        /// La cadena de conexión a la base de datos
        /// </summary>
        private string CadenaConexionMySQL()
        {
            if (UsarBaseLocal)
                return "No hay base local de MySQL";

            ConectarMySQL();

            var csb = new MySqlConnectionStringBuilder();

            csb.Server = ServerName;
            csb.Database = DatabaseName;
            csb.UserID = UsuarioDB + "My";
            csb.Password = PasswordDB;

            return csb.ConnectionString;
        }


        /// <summary>
        /// Conectar a la base de datos de SQL Server
        /// </summary>
        private void ConectarSQL()
        {
            if (UsarBaseLocal)
                ServerName = @".\SQLEXPRESS";
            else
                ServerName = "sql3.servidoreswindows.net";
        }

        /// <summary>
        /// Conectar a la base de datos de MySQL.
        /// Siempre en remoto.
        /// </summary>
        private void ConectarMySQL()
        {
            ServerName = "mysql1.servidoreswindows.net";
        }


        /// <summary>
        /// Añadir el dato a la base de SQL Server
        /// </summary>
        private string AñadirSQL()
        {
            string sCon = CadenaConexionSQL();
            var sel = $"INSERT INTO {Tabla} (Fecha, ConVarChar, ConNVarChar) " + 
                      $"VALUES('{DateTime.Now}', '{txtDato.Text}', '{txtDato.Text}')";

            using (var con = new SqlConnection(sCon))
            {
                var cmd = new SqlCommand(sel, con);

                try
                {
                    con.Open();
                    cmd.ExecuteNonQuery();
                    con.Close();

                    return "";
                }
                catch (Exception ex)
                {
                    return $"ERROR: {ex.Message}";
                }
            }
        }

        /// <summary>
        /// Añadir el dato a la base de datos de MySQL
        /// </summary>
        private string AñadirMySQL()
        {
            if (UsarBaseLocal)
                return "No hay base local de MySQL";

            var sCon = CadenaConexionMySQL();
            // En MySQL he definido Fecha como varchar(20)
            var sel = $"INSERT INTO {Tabla} (Fecha, ConVarChar, ConNVarChar) " + 
                      $"VALUES('{DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss")}', '{txtDato.Text}', '{txtDato.Text}')";

            using (var con = new MySqlConnection(sCon))
            {
                var cmd = new MySqlCommand(sel, con);

                try
                {
                    con.Open();
                    cmd.ExecuteNonQuery();
                    con.Close();

                    return "";
                }
                catch (Exception ex)
                {
                    return $"ERROR: {ex.Message}";
                }
            }
        }

        /// <summary>
        /// Mostrar los datos de la base de SQLServer
        /// </summary>
        private string MostrarDatosSQL(ref DataTable dt)
        {
            var orden = chkOrden.Checked ? "DESC" : "ASC";
            var sel = $"SELECT TOP ({MaxFilas}) * FROM {Tabla} ORDER BY Fecha {orden}";

            var sCon = CadenaConexionSQL();
            SqlDataAdapter da;
            dt = new DataTable();

            try
            {
                // Crear un nuevo objeto del tipo DataAdapter
                da = new SqlDataAdapter(sel, sCon);

                // Llenar la tabla con los datos indicados
                da.Fill(dt);

                return $"Recuperadas {dt.Rows.Count} filas.";
            }
            catch (Exception ex)
            {
                return $"ERROR: {ex.Message}";
            }
        }

        /// <summary>
        /// Mostrar los datos de MySQL
        /// </summary>
        private string MostrarDatosMySQL(ref DataTable dt)
        {
            if (UsarBaseLocal)
                return "No hay base local de MySQL";

            var orden = chkOrden.Checked ? "DESC" : "ASC";
            // MySQL no tiene TOP, usar en su lugar LIMIT
            var sel = $"SELECT * FROM {Tabla} ORDER BY Fecha {orden} LIMIT {MaxFilas}";

            var sCon = CadenaConexionMySQL();
            MySqlDataAdapter da;
            dt = new DataTable();

            try
            {
                // Crear un nuevo objeto del tipo DataAdapter
                da = new MySqlDataAdapter(sel, sCon);

                // Llenar la tabla con los datos indicados
                da.Fill(dt);

                return $"Recuperadas {dt.Rows.Count} filas.";
            }
            catch (Exception ex)
            {
                return $"ERROR: {ex.Message}";
            }
        }

        private void BtnAñadir_Click(object sender, EventArgs e)
        {
            var msg = "";

            msg = AñadirSQL() + "\n";
            msg += AñadirMySQL();

            LabelError.Text = msg;

            btnRefrescar.PerformClick();
        }

        private void BtnRefrescar_Click(object sender, EventArgs e)
        {
            DataTable dt = null;
            var msg = "";
            lvDatos.Items.Clear();

            var i = 5;
            int.TryParse(txtFilas.Text, out i);
            MaxFilas = i;

            msg = MostrarDatosSQL(ref dt) + "\n";
            asignarDatos("SQL", dt);

            var lvi = lvDatos.Items.Add("---");
            lvi.SubItems.Add("---");
            lvi.SubItems.Add("---");
            lvi.SubItems.Add("---");

            dt = null;
            msg += MostrarDatosMySQL(ref dt);
            asignarDatos("MySQL", dt);

            LabelError.Text = msg;
        }

        private void asignarDatos(string @base, DataTable dt)
        {
            if (dt == null) return;

            foreach (DataRow r in dt.Rows)
            {
                var lvi = lvDatos.Items.Add(@base);
                lvi.SubItems.Add(r["Fecha"].ToString());
                lvi.SubItems.Add(r["ConVarChar"].ToString());
                lvi.SubItems.Add(r["ConNVarChar"].ToString());
            }
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            inicializando = false;
        }

        private void ChkUsarBaseLocal_CheckedChanged(object sender, EventArgs e)
        {
            if (inicializando) return;

            UsarBaseLocal = chkUsarBaseLocal.Checked;
        }
    }
}

Y esto es todo… espero que te sea de utilidad.

Nos vemos.
Guillermo

El código completo del ejemplo
(solución para Visual Studio usando .NET 4.7.2)

El ZIP con el código completo (una solución de Visual Studio 2019 Preview con los proyectos de Visual Basic y C# además de la DLL de MySQL con la referencia a la copia dentro del directorio de la solución.

El zip: VarChar_y_NVarChar_20190321.zip (147 KB)

MD5 Checksum: 888346C11622183F6A39FE5FBA4339D3

Nota:

Aquí tienes el enlace para la descarga del ZIP que me descargué con el conector de MySQL para .NET versión 6.0.3 (mysql-connector-net-6.0.3-noinstall.zip) el tamaño es 1.64MB y el MD5 Checksum es: 57BB7F42645665D5A616A3484041F0C0.

VarChar y NVarChar en SQL Server

Actualizado el 21/Mar/2019 con los pasos para resolver el fallo que tenía de acceso a las bases de datos remotas y también con el enlace al código para VB y C#.

Pues eso… el otro día estaba creando una base de datos de MySQL y me di cuenta que MySQL no tiene el tipo NVARCHAR o al menos a mí no me daba la opción de crear un campo con ese tipo, al menos desde el panel de control del hosting que utilizo (Domitienda.com), así que… utilicé VARCHAR, el problema que me temía que iba a tener era no poder usar las vocales acentuadas (con tilde), la eñe, etc. Pero resultó que sí… que lo aceptaba sin problemas.

Así que, me puse a buscar en Internet a ver cuál era la diferencia entre VARCHAR y NVARCHAR, ya que yo siempre usaba NVARCHAR en mis bases de SQL Server (que es el tipo de base que uso, salvo excepciones muy, muy contadas) porque era el tipo que me permitía usar los caracteres especiales del idioma español (o al menos así lo entendí yo, que puede ser que lo entendiese mal… ).

Pero resulta que si utilizo VARCHAR puedo indicar esos caracteres especiales sin mayor problema.

Por supuesto la información que encontré hablando de esa diferencia entre esos dos tipos de datos era… bueno… no aclaratoria del todo… pero en realidad me daba igual lo que me quisieran decir, ya que yo he comprobado por mí mismo que se puede guardar algo así: Raúl Rodríguez López es de Valdepeñas en un campo del tipo VARCHAR.

Un ejemplo para acceder a SQL Server y MySQL

Aquí tienes una captura de un ejemplo que he hecho para mostrar que sí funciona eso de guardar los caracteres especiales, al menos los usados en español.

El código lo publicaré otro día, ya que es tarde y además no me funciona el código para acceder a las bases de datos remotas (en el hosting de Domitienda), seguramente habré configurado algo mal, ya que a otras que tengo en el mismo hosting si que puedo acceder…

Nota del 21 de Marzo:

Según parece resulta que en el “plesk” del hosting no permite usar el mismo nombre de usuario para varias bases de datos o bien la longitud de la contraseña debe ser menor de la que usé… en cualquier caso lo he solucionado haciendo lo siguiente:

He creado dos usuarios diferentes, el de la base de datos de SQL Server se llama elGuille_infoSQL y el de MySQL se llama elGuille_infoMy.

He usado una clave más corta.

Y ahora todo funciona bien… o casi… ya que añadí un campo DATETIME a la base de MySQL y da error al convertir el contenido de la base de datos a un tipo DateTime de .NET, así que… lo he creado como VARCHAR(20).

Otra cosa con la que me he encontrado con la consulta de MySQL es que no acepta TOP para indicar el número de filas a devolver, en su lugar he usado LIMIT y ya va bien.

En la captura de la Figura 2 puedes ver la aplicación totalmente operativa y accediendo a las bases de datos remotas.

Si te fijas en los valores mostrados al final con todo a cero, eso era lo que había añadido usando el tipo de datos DATETIME en MySQL, después al añadirlo como VARCHAR ya se muestra bien.

El código de VB y C# lo puedes ver en la entrada publicada el 21 de marzo: VarChar y NVarChar en SQL Server y MySQL el código para VB y C#.

Figura 2. La aplicación operativa con acceso remoto después de los cambios hechos en el hosting.
Figura 2. La aplicación operativa con acceso remoto después de los cambios hechos en el hosting.

Figura 1. Formulario de prueba
Figura 1. El formulario de prueba

Los datos mostrados en la figura 1 son de una base de datos de SQL Server usada localmente, el mensaje de error que se muestra es el devuelto por el servidor de MySQL.

Lo importante es que puedes comprobar que tanto el campo VARCHAR como NVARCHAR muestran las vocales con tilde y la eñe.

Seguramente si uso caracteres de otros idiomas como el CHINO o RUSO no se guardarían bien en el VARCHAR… no lo sé porque no lo he probado… 😉

Pues esto es todo por hoy… en los enlaces puedes ver más información sobre esos tipos de datos (en la documentación de SQL Server) y lo que cuentan en algunos sitios sobre la diferencia entre ellos.

Este enlace es sobre VARCHAR en la documentación de MySQL.

A ver si el jueves próximo (día 21, luna llena, por más señas) te pongo el código de ejemplo de la aplicación que guarda esos caracteres en los dos tipos de datos y como de costumbre tanto para Visual Basic como para C#.

Nos vemos.
Guillermo

Nota:
Según el panel de control la versión de MySQL es:

mysql1.servidoreswindows.net
Versión del servidor: 5.1.73-log

Un regalito a una suscripción a Visual Studio Enterprise (por 12 meses)

Pues eso… que te acabo de contar que la licencia de Visual Studio 2017 Professional que parecía que me habían dado hace unos meses ya había expirado (y parecía que era definitiva y no de prueba) y acabo de abrir el correo y me encuentro que me han dado una suscripción a Visual Studio Enterprise (válida por un año) tal como puedes ver en la captura de la figura 1.

Un regalito para un ex-MVP (ahora rMVP)
Figura 1. Un regalito para un ex-MVP (ahora rMVP)

Esta suscripción (para 12 meses) contiene el Visual Studio y muchas más cosas (Windows, Office, etc.), como las antiguas suscripciones a MSDN que nos daban a los MVP reconocidos como developers… pero esta vez la dan por 12 meses a algunos ex-MVP ahora llamados MVP Reconnect (rMVP) y puede que la renueven… a ver qué pasa dentro de un año.

¡Muchas gracias!

Nos vemos.
Guillermo

Donde dije digo, digo Diego (y buscar las referencias a un método)

Pues eso… como te comenté hace unos meses (Tiene una licencia para Visual Studio Professional 2017) resulta que solo era de evaluación, pero se ve que de unos tres meses… en fin… eso de que no digan las cosas claras es un poco frustrante, por decir algo suave, ya que en ningún lugar decían nada de que fuese una licencia de prueba, en fin… uno que siempre intenta ver la parte buena… Winking smile

Nota:
Poco después de publicar esta entrada he abierto el correo y me encontrado con un bonito regalo: Un regalito a una suscripción a Visual Studio Enterprise (por 12 meses).
Gracias

Pero resulta que solo era una licencia temporal… o al menos así lo dice la captura de la ventana de aviso que me ha salido (ver la figura 1).

La licencia de prueba ha expirado
Figura 1. La licencia de prueba ha expirado

En esta ocasión si dice que “era” una licencia de prueba (Trial extension), en fin…

Pero no me importa, con la versión Community me apaño, ya que en realidad la versión Professional la instalé porque tenía una cosilla que me venía muy bien: saber dónde se estaban usando los métodos de mis clases, (con enlaces a cada uno de los sitios en los que se accedía al método) cosa que desde hace unos meses ya no aparece, al menos como aparecía antes: encima de la definición del método.

 

Buscar todas las referencias a un método

Si te sitúas en la definición de un método y pulsas en el menú contextual, te saldrá algo como lo mostrado en la figura 2 y seleccionando Buscar todas las referencias te mostrará una lista desde dónde se accede a ese método (ver la figura 3).

Menú contextual de la definición de un método
Figura 2. Menú contextual de la definición de un método
Las referencias al método
Figura 3. Las referencias al método

Nota:
Comentarte que no solo se pueden ver las referencias a un método, también a las clases y otros tipos definidos.

Y esto es todo… seguiré con la versión Community de Visual Studio 2017 y (mientras tenga acceso) a las versiones Preview de Visual Studio 2019 y cuando salga la definitiva (recuerda que está prevista para el 2 de abril) si no consigo una versión mayor, seguiré con la de la comunidad (que esa sí que es gratuita de por vida… o al menos por ahora lo es).

Nos vemos.
Guillermo

ApplicationCommands, localización u otras historias idiomáticas

Pues eso… si has usado alguna vez los ApplicationCommands en una aplicación WPF sabrás de que te hablo, al menos si has trabajado con el Visual Studio en un idioma diferente al inglés o si tienes una versión de Windows configurada con otro idioma diferente al inglés, que para el caso es lo mismo… o casi…

Si has mirado el enlace de ApplicationCommands sabrá que son comandos para añadir funcionalidad “automática” de Guardar, Abrir, etc. a tus opciones de menús, botones o donde decidas que esos comandos se ejecuten.

Lo primero con lo que nos encontramos al añadir uno de esos comandos, por ejemplo el comando de Guardar (Save) a un elemento de menú (MenuItem) es que esa opción no estará habilitada, siempre se mostrará inhabilitada… ya que ese comando de aplicación necesita saber si la opción debe estar disponible o no…

Vayamos por pasos, así que… añadimos primero un menú Archivo a una aplicación de WPF definiendo un menú principal y la opción de Guardar, tal como vemos en el siguiente código XAML.

<Menu Grid.Row="0" Grid.Column="0" 
      Grid.ColumnSpan="2" Background="AliceBlue">
    <MenuItem Header="_Archivo" ToolTip="Abrir, Guardar, Salir">
        <MenuItem x:Name="mnuGuardar" Header="_Guardar" 
                  Command="ApplicationCommands.Save"
                  ToolTip="Guardar (Ctrl+S o Ctrl+G)"
                  Click="MnuGuardar_Click">
            <MenuItem.Icon>
                <Image Source="Images\Save_16x.png" />
            </MenuItem.Icon>
        </MenuItem>
</Menu>

En modo edición este menú se mostrará más o menos como vemos en la figura 1, en la que hay más opciones de ese menú de archivo que las mostradas en el código anterior, pero solo debes prestar atención al menú Guardar.

Figura 1. El menú Guardar usando ApplicationCommands.Save en modo diseño

Fíjate que el texto Ctrl+S lo ha añadido automáticamente. Ese sería el acceso de teclado rápido para el idioma inglés.

Decirte que esa captura la he tomando con el Visual Studio 2017 (lo mismo mostraría el VS 2019) con el idioma inglés.

Si iniciamos la aplicación, veremos dos cosas interesantes… al menos una de ellas es interesante, la otra… pues… cosas de la localización automática según el idioma de Windows, que en mi caso es el Windows 10 con el paquete de idiomas en español.

Si te fijas en la figura 2 verás que el comando Guardar está deshabilitado y la segunda cosa a destacar es que ahora no se muestra Ctrl+S si no Ctrl+G que es el equivalente a Guardar en el idioma español.

Figura 2.

¡Guay! o no… Ahora verás porque puede no ser cool (guay).

Veamos qué ocurre si el Visual Studio lo usamos con el idioma en español… pues… tal como puedes ver en la figura 3, se muestra Ctrl+G en lugar de Ctrl+S.

Figura 3. Usando el Visual Studio con el idioma en español.

Aquí vuelve a mostrar Ctrl+G tanto en modo diseño como en ejecución.

Pero lo curioso de todo esto es que ¡el acceso de teclado Ctrl+G no funciona!

El que funciona es Ctrl+S está en el idioma que esté el Visual Studio y el Windows. Pues sí, como te lo cuento…

Solucionando entuertos…

Vayamos arreglando las cosas… para empezar el que ese menú esté inaccesible (deshabilitado) es porque no le hemos dicho que se habilite.

Esto lo arreglamos añadiendo una definición en el código XAML , concretamente en la sección <Window.CommandBindings> tal como vemos en el siguiente código XAML, que estará después de la definición de la ventana.

<Window.CommandBindings>
    <CommandBinding Command="ApplicationCommands.Save" 
                    CanExecute="CommandGuardar_CanExecute"
                    Executed="CommandGuardar_Executed" />
    

</Window.CommandBindings>

Con ese código le estamos indicando que tenemos dos métodos definidos, el primero asignado a la propiedad CanExecute que es el que indica si debe estar habilitado o no ese comando, el segundo (el asignado a la propiedad Execute) es lo que debe hacer si se puede ejecutar el comando (el usuario puede pulsar en la opción del menú o usar el acceso rápido del teclado).

El siguiente código (para Visual Basic) te muestra lo que yo he hecho en esos dos comandos.

' Comando guardar
Private Sub CommandGuardar_CanExecute(sender As Object, 
                                       e As CanExecuteRoutedEventArgs)
    If IsLoaded = False Then Return
    e.CanExecute = rtbModificado
End Sub

Private Sub CommandGuardar_Executed(sender As Object, 
                                     e As ExecutedRoutedEventArgs)
    mnuGuardar.RaiseEvent(New RoutedEventArgs(MenuItem.ClickEvent))
End Sub

En el método asociado al comando CanExecute le indicamos cuando habilitar la opción, que será cuando la ventana esté cargada y se podrá ejecutar si el valor de rtbModificado es TRUE. Esa variable indicará si el texto se ha modificado.

Por otro lado, el comando Execute contiene una llamada al método que se encarga de llamar al método asociado con el evento Click del MenuItem. Fíjate que no se pone el nombre del método de evento, de cuál es ya se encargará el compilador o motor de ejecución de averiguarlo.

Ya tenemos una parte del problema resuelta. Creo que la más importante, ya que si el menú no está habilitado, pues… el resto de problemas dejan de serlos Winking smile

Ahora veamos cómo dar la funcionalidad de que ese comando se ejecute si el usuario que utiliza nuestra aplicación en un entorno con el idioma español pulsa Ctrl+G (recuerda que te dije que aunque se muestra Ctrl+G, el acceso rápido sigue siendo Ctrl+S).

Lo que necesitamos es añadir funcionalidad a la pulsación de teclas Ctrl+G y que esté relacionado con el comando guardar.

Decirte que no vale (o al menos no vale la pena) intentar interceptar en la aplicación esa combinación de teclas, ya que tenemos una forma más fácil de hacerlo… al menos si se sabe cómo hacerlo… y ya sabes… aquí esta el Guille para decirte cómo… jejeje.

En el código XAML de la definición de la ventana, creamos una sección llamada <Window.InputBindings> (la podemos poner detrás de la que creamos antes para definir los comandos). Y añadimos lo siguiente:

<Window.InputBindings>
    <!-- podemos indicar las teclas a usar con los comandos definidos 
         aunque no estén asociados a ningún control ni MenuItem -->

    <!-- aunque muestra Ctrl+G en realidad usa Ctrl+S por eso defino Ctrl+G -->
    <KeyBinding Command="ApplicationCommands.Save" Key="G" Modifiers="Ctrl" />


</Window.InputBindings>

Y eso es todo, por supuesto la combinación predeterminada (Ctrl+S) sigue funcionando.

Decirte que no todas las combinaciones que pongas ahí se tendrán en cuenta, hay algunas combinaciones que ya están asociadas a otros comandos que no podrás usar.

Además me ha ocurrido que hay ciertas combinaciones de teclas que tampoco se pueden usar y aparentemente no están asociadas a nada… (por ejemplo Ctrl+Shift+S que en Visual Studio es Guardar todo, pero en mi aplicación… ¿qué comando es?). En fin… misterios por resolver I don't know smile

Nos vemos.
Guillermo

Ya están los foros en modo SOLO-LECTURA

Pues eso… ya es 1 de febrero de 2019 y como te comenté hace unos días, los foros (no el blog ni el sitio) ya está en modo SOLO-LECTURA, es decir, se podrán seguir viendo los mensajes que haya, pero no se podrán hacer nuevas consultas, ni responder a las existentes, ni tampoco editarlas. aunque sí se podrán marcar los mensajes como que responden a la pregunta o consulta realizada, pero si quieres que te diga la verdad, dudo que muchos entren nuevamente en los foros para marcar las respuestas… si no lo hicieron en su día… no lo van a hacer después de algún tiempo… Winking smile

Desde el 1 de febrero de 2019 los foros del Guille son de solo lectura

Si eres moderador de mis foros o estás en la categoría de MVP (antes debes estar registrado en los foros) podrás seguir modificando, respondiendo o escribiendo nuevos mensajes.

Los usuarios “normales” si pulsan en el botón de responder, modificar o nuevo, los mandará a la página de aviso de que está cerrado.

Por cierto, si quieres colaborar comprobando las respuestas a los mensajes para ver si responden a la pregunta, coméntalo en este mismo post y así los foros tendrán un valor añadido. Gracias.

Y esto es todo por ahora…. en marzo nuevos cambios… espero…

Nos vemos.
Guillermo

Quitar avisos de Windows 10 al instalar con ClickOnce

Pues eso… que el otro día estaba probando en mi equipo con Windows 10 la instalación con ClickOnce de una aplicación y era un rollo la de preguntas que te decía, que si te está protegiendo, que no confía en la aplicación, y cosas así (ver figura 1).

Figura 1. Aviso de Windows 10 al instalar con ClickOnce desde un sitio Web
Figura 1. Aviso de Windows 10 al instalar con ClickOnce desde un sitio Web

La solución es simple.

Pero debes hacerlo en el equipo en el que se va a instalar la aplicación.

Panel control>opciones internet>Seguridad>Sitios de confianza>sitios (agregar la dirección web)

Abre el Panel de Control, (si no lo tienes a mano abre Inicio y escribe “panel” y te lo mostrará), selecciona Opciones de Internet (si no ves las opciones de internet, pulsa en Redes e Internet) se mostrará una ventana como la de la figura 2.

Figura 2. Propiedades de Internet.
Figura 2. Propiedades de Internet.

Pulsa en la pestaña Seguridad, a continuación en Sitios de confianza y en el botón Sitios. Te mostrará una ventana nueva con los Sitios de confianza (figura 3).

Figura 3. Sitios de confianza.
Figura 3. Sitios de confianza.

Si tu sitio no es https quita la marca de abajo a la derecha donde dice “Requerir comprobación del servidor (https:) para todos los sitios de esta zona“.

Escribe el nombre de tu sitio en “Agregar este sitio web a la zona de:” (sin el http:// ni https://) y pulsa en el botón Agregar.

Cierra todas las ventanas y ya no tendrás más avisos como el de la figura 1 (y otros parecidos) cuando se vuelva a instalar (o actualizar) algo desde el sitio que has indicado.

Y ya está… Smile

Espero que te sea de utilidad.

Nos vemos.
Guillermo

En breve cerraré los foros del Guille

Pues eso.,.. me refiero a la página: http://foros.elguille.info/ no al blog, ni al sitio ni la página de Facebook Winking smile

Ya han pasado unos 15 años desde que los puse en marcha (hubo antes otras versiones de los foros, pero me refiero al que está actualmente ¿activo?) y no hay prácticamente movimiento, ni por mi parte… así que… si no cambio de opinión… el 31 de enero de este año 2019 será el último día que se podrá postear en los foros de elGuille.info… y a partir del 1 de febrero de 2019 serán de solo lectura (o solo para consultar y ver los hilos), porque quitarlo no lo voy a quitar, ya que sé que hay respuestas válidas, y muchas… Si acaso quitaré visibilidad a los hilos que no han obtenido respuesta, más que nada para que no aparezcan en los buscadores, que no es plan que alguien busque algo y le aparezca solo la pregunta sin respuesta (que suele ocurrir en muchos foros, jeje).

Los foros de elGuille.info pasarán a solo lectura el 1 de febrero de 2019
Los foros de elGuille.info pasarán a solo lectura el 1 de febrero de 2019

Pues eso… si quieres dar tu opinión, puedes darla en este hilo.

Gracias.

Nos vemos.
Guillermo

DoEvents para aplicaciones WPF

Pues eso… que en las aplicaciones de Windows Presentation Foundation (WPF) no podemos usar el equivalente a DoEvents de Windows Forms, simplemente porque no existe esa funcionalidad para WPF, pero no te preocupes porque aquí te explico cómo crear un método DoEvents listo para usar en las aplicaciones de WPF, por supuesto con el código del VB y C#.

Yo utilizo DoEvents en las ocasiones que quiero refrescar la pantalla (formulario o ventana) de la aplicación, por ejemplo cuando se está haciendo un proceso largo para que no se quede congelada la aplicación.

Y anoche me ocurrió eso mientras ejecutaba una aplicación (de WPF) que me fabriqué para copiar el contenido de una lista de canciones (PlayList tipo m3u) en una carpeta. Y como el disco usado para guardar los MP3 era un disco externo, pues… aparte de que eran muchas canciones (354), pues… parecía que la aplicación fallaba, ya que no mostraba la canción que estaba copiando y… pues eso… que hasta yo pensé que me había equivocado escribiendo el código… Angel

Así que… sabiendo que DoEvents no está definido en WPF y después de probar con que tampoco hay Refresh en los controles de WPF, probé con Thread.Sleep que no solucionó el problema (y no era plan de crear un temporizador), así que… ¡a buscar en Internet!

Y dio resultado la búsqueda, concretamente en esta página:
Implement Application.DoEvents in WPF, ¿el problema? ninguno, salvo que, como suele ocurrir por los interneses, todo está con ejemplos para C#, así que… a convertir el código encontrado.

Este es el código de esa página para simular el DoEvents (en C#):

public static void DoEvents()
    {
        Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Background,
                new EmptyDelegate(delegate{}));
    }

En otra parte del código debes tener la definición de EmptyDelegate:

private delegate void EmptyDelegate();

No me sonó demasiado a chino mandarín ya que algo parecido usé hace muuuuuuchos años para llamar a otro control desde el método de evento de un temporizador:
24- Acceder a un control desde un evento de un timer, pero ya ni me acordaba Eye rolling smile

Y esta es la versión para Visual Basic .NET:

' Adaptado de:
' http://www.java2s.com/Tutorial/CSharp/0470__Windows-Presentation-Foundation/
'   ImplementApplicationDoEventsinWPF.htm

Private Delegate Sub EmptyDelegate()

Private Sub DoEvents()
    Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Background,
                                        New EmptyDelegate(Sub()
                                                          End Sub))
End Sub

Te tienes que fiar de mi palabra, pero sin el DoEvents, al pulsar en el botón Copiar la aplicación parece como si se hubiese colgado… hasta pasado un buen rato no termina… (Figura 1)

Figura 1. Sin el DoEvents, después de pulsar en Copiar la aplicación se queda congelada
Figura 1. Sin el DoEvents, después de pulsar en Copiar la aplicación se queda congelada

Sin embargo, poniendo el DoEvents, se va mostrando el progreso de copia. Y de eso se trata… que se vea lo que está haciendo Winking smile

Pues… ¡ya está! esto es todo…

Nos vemos.
Guillermo

Si quieres que tu aplicación se muestre en el monitor externo dile que se centre en la pantalla (CenterScreen)

Pues eso… que estuve un tiempo buscando soluciones para que se mostrasen mis aplicaciones en el monitor secundario (que es el que uso como principal) y resulta que la solución es más simple que todo eso… sí, solo con indicarle que se centre en la pantalla (CenterScreen) es suficiente Winking smile

Y esto vale tanto para aplicaciones de Windows Forms como para las de WPF (Windows Presentation Foundation).

En WPF lo haces con este código en el diseñador de la ventana (Window):

WindowStartupLocation = "CenterScreen"

En WinForms asigna a la propiedad StartPosition del formulario de inicio el valor CenterScreen.

Y ya está… ya no tengo más que contarte Winking smile

Nos vemos.
Guillermo