jueves, 1 de octubre de 2020

Patrones de diseño: Prototipo

Para generar objetos a partir de otros sin tener que crearlos de cero se puede usar el patrón prototipo. Para ello se genera un clon de un objeto (en C# se hace utilizando la interfaz ICloneable).
Debemos distinguir dos tipos de clonación: shallow copy y deep copy. El primero clona un objeto y copia el valor de todas las propiedades del mismo. Sin embargo, en el caso de los tipos por referencia se copia la referencia. En el segundo caso, las propiedades se copian como en el primer caso aunque la diferencia se encuentra para los tipos por referencia que se clonan también.
A continuación se puede ver un ejemplo sencillo de su utilización.

using System;
public class Program
{
public static void Main()
{
Person p = new Person();
p.name = "Edward";
p.surname = "Reddington";
p.age = 48;
p.addr = new Address("Main Street, 16");
Console.WriteLine("Person 1 - " + p.name + " " + p.surname + ", " + p.age + ", " + p.addr.street);
Person p2 = (Person)p.Clone();
Console.WriteLine("Person 2 - " + p2.name + " " + p2.surname + ", " + p2.age + ", " + p2.addr.street);
p2.name = "Peter";
p2.surname = "Baltimore";
p2.addr = new Address("Eight Av. 132");
Console.WriteLine("Person 2 - " + p2.name + " " + p2.surname + ", " + p2.age + ", " + p2.addr.street);
}
}

public class Address:ICloneable {
public String street;
public Address(String street) {
this.street = street;
}
public object Clone() {
return this.MemberwiseClone();
}
}

public class Person:ICloneable {
public String name;
public String surname;
public int age;
public Address addr;
public object Clone() {
return this.MemberwiseClone();
}
}

jueves, 2 de julio de 2020

Listbox con elementos de apariencia diferente

El control Listbox de Windows Forms es bastante útil en determinadas situaciones y muy utilizado también. Una característica que puede ser poco conocida para algunos desarrolladores es la posibilidad de definir las características de cada línea (elemento) mostrada en este control de forma independiente.
Este resultado se puede lograr de un modo bastante sencillo realizando 3 simples pasos:
  - En primer lugar debemos establecer la propiedad DrawMode al valor OwnerDrawFixed o el valor OwnerDrawVariable.
  - En segundo lugar, hay que definir el manejador del evento DrawItem. Aquí podremos dibujar lo que queremos mostrar en cada línea del Listbox de forma individual para cada elemento. Es posible definir el color, tamaño y familia de la fuente, por ejemplo.
  - En último lugar, y sólo en caso de haber seleccionado la opción DrawMode = OwnerDrawVariable, debemos definir el manejador del evento MeasureItem que nos permitirá variar las dimensiones de la línea que ocupa cada elemento de la fuente de datos del ListBox.

A continuación se muestra un poco de código con un ejemplo de lo arriba descrito. En el código se puede ver una clase auxiliar que se utiliza para definir cada uno de los elementos que muestra el control Listbox. En esta clase se incluye la propiedad Fuente que nos permite definir la fuente del texto mostrado en cada línea del control de forma independiente.

Public Class frmFuentes
    Private Const FUENTE_MIN As Integer = 8
    Private Const FUENTE_MAX As Integer = 14
    Dim listaFuentes As List(Of ItemCboFuentes)

    Private Sub frmFuentes_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Dim fuentesInstaladas As New InstalledFontCollection
        Dim strFamiliasFuentes As New StringBuilder("")
        Dim itemCombo As ItemCboFuentes
        listaFuentes = New List(Of ItemCboFuentes)
        Dim valor As Integer = 0
        For Each familia In fuentesInstaladas.Families
            If valor Mod 2 = 0 Then
                itemCombo = New ItemCboFuentes(familia.Name, valor.ToString())
            Else
                itemCombo = New ItemCboFuentes(familia.Name, valor.ToString(), FUENTE_MAX)
            End If
            listaFuentes.Add(itemCombo)
            valor = valor + 1
        Next
        cboFuente.ValueMember = "Valor"
        cboFuente.DisplayMember = "Texto"
        cboFuente.DataSource = listaFuentes
        lstFuentes.DrawMode = DrawMode.OwnerDrawVariable
        lstFuentes.ValueMember = "Valor"
        lstFuentes.DisplayMember = "Texto"
        lstFuentes.DataSource = listaFuentes
    End Sub

    Private Sub cboFuente_SelectedIndexChanged(sender As Object, e As EventArgs) Handles cboFuente.SelectedIndexChanged
        MessageBox.Show("Text: " & cboFuente.SelectedItem.ToString() & " -- " & cboFuente.SelectedValue)
    End Sub

    Private Sub ListBox1_DrawItem(sender As Object, e As DrawItemEventArgs) Handles lstFuentes.DrawItem
        Dim indice As Integer = e.Index
        Dim rectF As RectangleF
        rectF = RectangleF.op_Implicit(e.Bounds)
        e.DrawBackground()
        e.Graphics.DrawString(listaFuentes.Item(indice).Texto, listaFuentes.Item(indice).Fuente, Brushes.Black, RectangleF.op_Implicit(e.Bounds))
        e.DrawFocusRectangle()
    End Sub

    Private Sub lstFuentes_MeasureItem(sender As Object, e As MeasureItemEventArgs) Handles lstFuentes.MeasureItem
        If e.Index Mod 2 <> 0 Then
            e.ItemHeight = e.ItemHeight * 2
        End If
    End Sub

    'Clase auxiliar para definir los elementos que vamos a mostrar en el control Listbox.
    'Esta clase incluye una propiedad Fuente que nos permite definir la fuente de cada br/>     'elemento de forma independiente.
    Private Class ItemCboFuentes
        Private _texto As String
        Public Property Texto() As String
            Get
                Return _texto
            End Get
            Set(ByVal value As String)
                _texto = value
            End Set
        End Property
        Private _valor As String
        Public Property Valor() As String
            Get
                Return _valor
            End Get
            Set(ByVal value As String)
                _valor = value
            End Set
        End Property
        Private _fuente As Font
        Public Property Fuente() As Font
            Get
                Return _fuente
            End Get
            Set(ByVal value As Font)
                _fuente = value
            End Set
        End Property
        Sub New()
            Texto = ""
            Valor = ""
            Fuente = New Font(FontFamily.GenericSansSerif, FUENTE_MIN)
        End Sub

        Sub New(ByVal texto As String, ByVal value As String)
            _texto = texto
            _valor = value
            _fuente = New Font(texto, FUENTE_MIN)
        End Sub

        Sub New(ByVal texto As String, ByVal value As String, ByVal tam As Integer)
            _texto = texto
            _valor = value
            _fuente = New Font(texto, tam)
        End Sub
    End Class

End Class

El código mostrado permite obtener el resultado que se observa en la siguiente imagen.


miércoles, 1 de julio de 2020

Captura de eventos de teclado

Es habitual el desarrollo de aplicaciones que, en algún momento, tengamos que controlar los eventos de teclado. Lo más habitual es tener que controlar la pulsación de alguna tecla para realizar alguna tarea, o simplemente para actuar como atajos de teclado para poder realizar acciones comunes de un modo más ágil.
Para detectar las pulsaciones realizadas dentro de un determinado control debemos definir las operaciones a realizar dentro del manejador del evento correspondiente (keydown, keyup, keypress, etc.) para ese control.

Private Sub TextBox1_KeyPress(sender As Object, e As KeyPressEventArgs) Handles TextBox1.KeyPress
        If e.KeyChar >= ChrW(48) And e.KeyChar <= ChrW(57) Then
            MessageBox.Show(("TextBox.KeyPress: '" +
                e.KeyChar.ToString() + "' pulsado."))
            Select Case e.KeyChar
                Case ChrW(49), ChrW(57)
                    MessageBox.Show(("TextBox.KeyPress: '" +
                        e.KeyChar.ToString() + "' absorbido."))
                    e.Handled = True
            End Select
        End If
    End Sub

Sin embargo, si queremos detectar la pulsación de las teclas en el formulario debemos hacer algunas modificaciones. En primer lugar, debemos poner a True la propiedad KeyPreview del formulario. Además, debemos definir el código de las acciones a realizar en el manejador de eventos del formulario. De este modo, las teclas pulsadas serán detectadas primero por el formulario y después se pasarán a los distintos controles, salvo que dentro del manejador del evento correspondiente se defina la propiedad e.Handled = true.

Sub Form1_KeyPress(ByVal sender As Object,  ByVal e As KeyPressEventArgs) Handles Me.KeyPress
...
End Sub

Incluso haciendo estos ajustes existen casos en los que las teclas no alfanuméricas pueden no ser detectadas. Para estos casos es recomendable sobreescribir la función ProcessCmdKey. En este caso no es necesario establecer la propiedad del formulario KeyPreview a True. Si esta función devuelve True el evento detectado será consumido por esta función y no será recibido por el control que tenga el foco en ese momento.

Protected Overrides Function ProcessCmdKey(ByRef msg As Message, ByVal keyData As Keys) As Boolean
    If keyData = Keys.Up Then
        MessageBox.Show("Flecha arriba pulsada")
        Return True
    End If
    If keyData = Keys.Down Then
        MessageBox.Show("Flecha abajo pulsada")
        Return True
    End If

    Return MyBase.ProcessCmdKey(msg, keyData)
End Function

lunes, 1 de junio de 2020

Empezando con Git

Git es una herramienta de control de versiones que facilita la colaboración entre equipos de desarrollo de software. Esta herramienta ha sido desarrollada por Linus Torvalds (inició y mantiene actualmente el desarrollo del núcleo del sistema operativo Linux).
Para utilizar Git lo primero que tenemos que hacer es descargar la aplicación desde aquí. En este artículo vamos a ver los comandos utilizados en la interfaz de texto pero también podemos utilizar una interfaz visual que se puede descargar desde el enlace anterior también.
A continuación comentaré los comandos más útiles de esta herramienta y para qué se usan.
> git init
   Crea un repositorio vacío o reinicia uno existente en el directorio actual.

> git init [repository]
   Crea un subdirectorio dentro del directorio actual e inicia un repositorio para ese subdirectorio.

> git clone
   Permite clonar un repositorio dentro del directorio actual

> git status
   Muestra información sobre el estado del árbol de trabajo. Muestra la rama actual, los ficheros que no están controlados.

> git config --global user.name "Nombre Apellido"
   Permite definir la identidad del usuario como un modo de hacer seguimiento de quién realiza los cambios.

> git config --global user.email "myemail@email.com"
   Permite definir como parte de la identidad del usuario su dirección de correo electrónico.

> git add [file_name]
> git add .
   Añade un fichero o todos los ficheros del directorio al índice del repositorio.

> git add --all
   Añade también los ficheros borrados.

> git commit
   Graba los cambios pendientes en el repositorio.

> git commit -m "[commit message]"
   Graba los cambios pendientes en el repositorio y se le asocia un mensaje.

> git branch
   Lista las ramas existentes e indica la actual.

> git branch [branch_name]
   Crea una nueva rama con el nombre [branch_name].

> git branch -d [branch_name]
   Intenta eliminar una rama.

> git branch -D [branch_name]
   Elimina una rama sin avisos.

> git checkout [branch_name]
   Cambia de la rama actual a otra llamada [branch_name].

> git checkout -b [branch_name]
   Cambia de una rama a otra. Si esta última no existe, la crea primero.

> git merge [branch_name]
   Añade los cambios a mayores de la rama [branch_name] a la actual.

> git remote add origin https://e*****o@bitbucket.org/e*****o/git_test.git
   Conecta el repositorio local origin con el remoto definido por la url. En este caso el repositorio remoto está alojado en Bitbucket, pero podría utilizarse otro como GitHub, por ejemplo.

> git remote
   Obtiene la lista de repositorios remotos.

> git push -u origin master (-u sólo la primera vez)
   Envía el contenido de la rama master del repositorio local al remoto.

> git fetch
   Obtiene todos los datos del repositorio remoto origin.

> git pull [remote] [alias]  (git pull origin master) o git pull
   Obtiene la rama que estamos solicitando y hace un merge con la rama actual.

> git merge [branch_name]
   Mezclamos los datos de [branch_name] con la rama actual.

> git push origin master
   Actualiza el repositorio remoto con los cambios de la rama master, en este caso.

En cualquier caso, para obtener información más detallada y actualizada se puede visitar la documentación oficial en este enlace.
También existen otras fuentes de información útiles que se pueden consultar para aprender más sobre Git como, por ejemplo:
   - Git HandBook
   - Aprende a usar Git con BitBucket Cloud
   - Pro Git

Por otro lado, es posible utilizar un cliente GUI para evitar la línea de comandos. De este modo se realizarían las operaciones en un entorno totalmente visual. Una de las aplicaciones de este tipo gratuitas mejor valoradas es Sourcetree que se puede descargar en el siguiente enlace

jueves, 2 de abril de 2020

Winforms TextBox personalizado

A todos los que nos dedicamos a esto de la programación nos ha pasado en alguna o muchas ocasiones que necesitemos de un determinado control ofrecido por el framework algo adicional. A veces, son pequeños detalles que nos facilitarían la tarea a realizar y otras veces son simplemente efectos visuales que consideramos atractivos. Sea cual sea la situación, voy a mostrar un ejemplo de como personalizar el control TextBox en .NET WinForms.
En este ejemplo se optimiza el control TextBox para actuar comprobando la fortaleza de las contraseñas introducidas en el mismo. Para ello se añaden varias propiedades: Validate (permite establecer si queremos o no esta funcionalidad adicional), MinChars (en la versión inicial permite definir el número mínimo de caracteres para considerar una contraseña mínimamente fuerte), Valid (indica la validez o no de la contraseña introducida en el control), Fortaleza (devuelve un valor indicativo de la fortaleza de la contraseña con respecto a los criterios establecidos).
En la primera versión del control (se encuentra comentada en el código) se utilizaba la propiedad MinChars y cuando la longitud de la cadena introducida es mayor que MinChars + 3 y cumple el patrón, esta es considera válida y el fondo del control se pone en color verde. Para valores menores no válidos el color de fondo va variando desde el rojo hasta el verde correspondiente a una contraseña fuerte y por tanto válida.
En la segunda versión, se utiliza una función que nos da un número indicativo de la fortaleza de la cadena de caracteres que se le pasa como parámetro para su uso como contraseña.
La función que calcula el nivel de dificultad de adivinar una contraseña comprueba primero la longitud de la misma y después la pondera comprobando también los distintos tipos de caracteres empleados en la misma.



Public Class EBSTextBox
    Inherits TextBox
    Private _validate As Boolean
    Private patron As String = "^[a-zA-Z0-9.,?_-]+$"
    <Description("Define si se realiza validación o no")>
    <Category("Seguridad")>
    <DefaultValue(False)>
    <Browsable(True)>
    Public Property Validate() As Boolean
        Get
            Return _validate
        End Get
        Set(ByVal value As Boolean)
            _validate = value
        End Set
    End Property
    Private _minChars As Integer
    <Description("Define el mínimo número de caracteres para una contraseña")>
    <Category("Seguridad")>
    <Browsable(True)>
    Public Property MinChars() As Integer
        Get
            Return _minChars
        End Get
        Set(ByVal value As Integer)
            _minChars = value
        End Set
    End Property
    Private _valid As Boolean
    Public ReadOnly Property Valid() As Boolean
        Get
            Return _valid
        End Get
    End Property
    Private _fortaleza As Integer
    Public ReadOnly Property Fortaleza() As Integer
        Get
            Return _fortaleza
        End Get
    End Property
    Protected Overrides Sub OnTextChanged(ByVal e As EventArgs)
        _valid = False
        If _validate = True Then
            ''Validaciones básicas
            'If Me.Text.Length < MinChars Then
            '    Me.BackColor = Color.Red
            'ElseIf Me.Text.Length < MinChars + 3 Then
            '    Me.BackColor = Color.OrangeRed
            'Else
            '    'Comprobamos si se cumplen los requisitos de complejidad
            '    If Not System.Text.RegularExpressions.Regex.IsMatch(Me.Text, patron) Then
            '        Me.BackColor = Color.Yellow
            '        _valid = False
            '    Else
            '        Me.BackColor = Color.Green
            '        _valid = True
            '    End If
            '    Me.BackColor = Color.Green
            '    _valid = True
            ''Validaciones mediante función ObtenerFortalezaClave
            _fortaleza = ObtenerFortalezaClave(Me.Text)
            If _fortaleza <= 4 Then
                _valid = False
                Me.BackColor = Color.Red
            ElseIf _fortaleza < 10 Then
                _valid = False
                Me.BackColor = Color.Orange
            ElseIf _fortaleza < 20 Then
                _valid = True
                Me.BackColor = Color.LightGreen
            Else
                _valid = True
                Me.BackColor = Color.Green
            End If
        End If
        MyBase.OnTextChanged(e)
    End Sub
    Private Function ObtenerFortalezaClave(ByVal clave As String)
        Dim fortaleza As Integer = 0
        If clave.Length < 4 Then
            fortaleza = 0
        ElseIf clave.Length < 6 Then
            fortaleza = 2
        ElseIf clave.Length < 10 Then
            fortaleza = 5
        Else
            fortaleza = 10
        End If
        If Not System.Text.RegularExpressions.Regex.IsMatch(clave, "^[a-z]+$") Then
            If System.Text.RegularExpressions.Regex.IsMatch(clave, "^[a-zñ0-9]+$") Then
                fortaleza = fortaleza * 2
            ElseIf System.Text.RegularExpressions.Regex.IsMatch(clave, "^[a-zñ0-9A-ZÑ]+$") Then
                fortaleza = fortaleza * 4
            ElseIf System.Text.RegularExpressions.Regex.IsMatch(clave, "^[a-zñA-ZÑ0-9\W]+$") Then
                fortaleza = fortaleza * 5
            End If
        End If
        Return fortaleza
    End Function
End Class


En la imagen siguiente se puede observar el efecto de los cambios introducidos en el control TextBox. Como se puede ver en el ejemplo de uso del control, se utiliza la propiedad Valid para decidir si se habilita/deshabilita el botón que lo acompaña.


En este caso, la función de comprobación de la fortaleza da más importancia al uso de mayúsculas o signos de puntuación que a la longitud de la cadena (sobre todo si esta es pequeña). Este es un criterio más o menos acertado que se puede adaptar a las necesidades en cada momento. Lo importante del ejemplo son las posibilidades de personalización que ofrecen los controles en .NET.

El código del formulario de prueba del control es el siguiente:

Public Class frmTestInheritedControl
    Private Sub EbsTextBox1_TextChanged(sender As Object, e As EventArgs) Handles EbsTextBox1.TextChanged
        If CType(sender, EBSTextBox).Valid Then
            btnEntrar.Enabled = True
        Else
            btnEntrar.Enabled = False
        End If
        lblFortaleza.Text = CType(sender, EBSTextBox).Fortaleza.ToString()
    End Sub
    Private Sub btnEntrar_Click(sender As Object, e As EventArgs) Handles btnEntrar.Click
        If EbsTextBox1.Valid Then
            lblPassword.Text = EbsTextBox1.Text
        Else
            lblPassword.Text = ""
        End If
    End Sub
End Class

miércoles, 27 de diciembre de 2017

Uniqueidentifier en T-SQL

El tipo de datos uniqueidentifier permite asignar valores a columnas de las tablas o a variables de datos en T-SQL con el formato xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, donde cada X es un dígito hexadecimal.
Para obtener un valor de tipo uniqueidentifier en T-SQL se utiliza la función NEWID().

DECLARE @myid uniqueidentifier ; 
SET @myid = 'A972C577-DFB0-064E-1189-0154C99310DAAC12'; 
SELECT @myid;


Es posible convertir una cadena de caracteres a Uniqueidentifier siempre que se ajuste al formato indicado previamente.

DECLARE @myid uniqueidentifier = NEWID(); 
SELECT CONVERT(char(255), @myid) AS 'char';

En caso de que se intente convertir una cadena con formato correcto pero con una longitud superior a la aceptada se truncará la cadena de caracteres.

DECLARE @ID nvarchar(max) = N'0E984725-C51C-4BF4-9960-E1C80E27ABA0wrong';
SELECT @ID, CONVERT(uniqueidentifier, @ID) AS TruncatedValue;

Se pueden consultar más detalles sobre este tipo de datos aquí, y sobre la función NEWID() aquí.

viernes, 10 de abril de 2015

C#. Tipos de datos

En C# los tipos de datos se pueden agrupar en dos tipos: tipos por valor y tipos por referencia.
Los tipos por valor son int, float, double, struct, enum, etc.
Los tipos por referencia son las clases, interfaces, delegados, arrays, etc.

A los tipos por valor no se les puede asignar el valor null. Sin embargo, estos tipos se pueden convertir en tipos anulables, que pueden representar todos los valores del tipo original y el valor null. Para convertir un tipo por valor en anulable sólo hay que añadir el símbolo ? después del tipo.

int? opcion = null;
...
if (opcion != null) Console.WriteLine("La opcion elegida es: {0}", opcion);
else Console.WriteLine("No se ha elegido una opcion");


El código anterior se puede simplificar considerablemente utilizando el operador ??. En el siguiente ejemplo a la cadena se le asigna el valor anterior al operador ?? si la variable opcion no tiene valor nulo, en caso contrario, se le asigna el valor que hay después de dicho operador.

String respuesta = "La opción elegida es: " + opcion.Value.toString() ?? "No se ha elegido una opción";
Console.WriteLine(respuesta);

martes, 7 de abril de 2015

Javascript: marca de agua

Una tarea muy común en el desarrollo de una página web es  la creación de controles de texto con una marca de agua. A continuación se muestra un ejemplo de esta tarea.

<html>
<head>
<script type="text/javascript">
    function establecerMarcaAgua() {
        var txtTexto = document.getElementById("txtTexto");
       
        if (txtTexto.value.length == 0) {
            txtTexto.value= "Introduzca el texto aquí";
            txtTexto.className = "marcaAgua";
        }
    }
    function quitarMarcaAgua() {
        var txtTexto = document.getElementById("txtTexto");
       
        if (txtTexto.value == "Introduzca el texto aquí") {
            txtTexto.value = "";
            txtTexto.className = "texto";
        }
    }
</script>
<style type="text/css">
    .marcaAgua {
        color: gray;
        font-weight: bold;
        font-size: 0.8em;
    }
    .texto {
        color: black;
        font-weight: normal;
        font-size: 1em;
    }
</style>
</head>

<body>
    Texto:
    <input type="text" id="txtTexto" value="Introduzca el texto aquí"
      class="marcaAgua"
      onfocus="quitarMarcaAgua()" onblur="establecerMarcaAgua()" />
</body>
</html>

A continuación se puede ver una versión genérica de las funciones anteriores que se pueden utilizar con múltiples controles.

<html>
<head>
<script type="text/javascript">
    function establecerMarcaAgua(texto, control) {      
        if (control.value.length == 0) {
            control.value= "Introduzca el texto aquí";
            control.className = "marcaAgua";
        }
    }
    function quitarMarcaAgua(texto, control) {      
        if (control.value == "Introduzca el texto aquí") {
            control.value = "";
            control.className = "texto";
        }
    }
</script>
<style type="text/css">
    .marcaAgua {
        color: gray;
        font-weight: bold;
        font-size: 0.8em;
    }
    .texto {
        color: black;
        font-weight: normal;
        font-size: 1em;
    }
</style>
</head>

<body>
    Texto:
    <input type="text" id="txtTexto" value="Introduzca el texto aquí"
        class="marcaAgua"
        onfocus='quitarMarcaAgua("Introduzca el texto aquí", this)'
        onblur='establecerMarcaAgua("Introduzca el texto aquí", this)' />
</body>
</html>

lunes, 6 de abril de 2015

ASP .NET: Procedimiento almacenado

En esta entrada se puede ver un ejemplo de uso de procedimientos almacenados en un método de una aplicación en ASP .NET.

private void Borrar(string IDs) {
  string cs = ConfigurationManager.ConnectionStrings["conStr"].ConnectionString;
  SqlConnection con = new SqlConnection(cs);
  SqlCommand cmd = new SqlCommand("spBorrarEmpleados", con);

  cmd.CommandType = CommandType.StoredProcedure;
  SqlParameter parameter = new SqlParameter("@IDs", IDs);
  cmd.Parameters.Add(parameter); con.Open(); 
  cmd.ExecuteNonQuery();

  con.Close(); }

viernes, 3 de abril de 2015

Android cheat sheet

Esta entrada está dedicada a anotar conceptos básicos para la programación en Android y que es necesario tener presentes en todo momento, ya sea en la memoria o en una entrada en un blog.

- Layouts:
  <FrameLayout  xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"> </FrameLayout>
  <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
      android:orientation="horizontal"
      android:padding="25dp"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:weightSum="100"  (peso total de las vistas contenidas)
      android:background="@drawable/imagen_background.png" >
  </LinearLayout>
  <TableLayout  xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
       <TableRow>
         <TextView android:text="celda 1.1" />
         <TextView android:text="celda 1.2" />
      </TableRow>
      <TableRow>
         <TextView android:text="celda 2.1" android:layout_span="2" />
        (ocupa dos columnas)
     </TableRow>
   </TableLayout>
   <GridLayout xmlns:android="http://schemas.android.com/apk/res/android" 
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:rowCount="2"
     android:columnCount="3"
     android:orientation="horizontal" >
         <TextView android:text="celda 1.1" />
         <TextView android:text="celda 1.2" />
         <TextView android:text="celda 2.1"  android:layout_columnSpan="2" />
   </GridLayout>
  <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 
     android:layout_width="match_parent"
     android:layout_height="match_parent" >
      <EditText android:id="@+id/TxtNombre"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:inputType="text" />
     <Button android:id="@+id/BtnAceptar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/TxtNombre"
        android:layout_alignParentRight="true" />
  </RelativeLayout>
- Controles(views):
  <Button
    android:layout_width="250dp"
    android:layout_height="wrap_content"
    android:text="Pulsar"
    android:layout_gravity="center"
    android:weight="80"  (en realidad, ocupa el 20% del peso total)
    android:textSize="20dp"
    android:id="@+id/btnPulsar" />
  <ToggleButton android:layout_weight="20" android:text="ToggleButton"
    android:id="@+id/tbtn1" android:layout_width="match_parent"
    android:paddingBottom="10dp" android:checked="true"
    android:layout_height="wrap_content" />
  <EditText android:layout_width="match_parent" android:layout_height="wrap_content"
    android:id="@+id/etNombre" android:hint="Escribe el nombre"
    android:password="true" />
- Definir actividad en AndroidManifest.xml:
  <activity android:name=".Splash" android:label="@string/nombre">
    <intent-filter>
      <action android:name="android.intent.action.MAIN" /> (not.ebacelo.android.APP)
      <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
  </activity>
- Button btnA = (Button) findViewById(R.id.btnA);
   TextView tvTexto = (TextView)findViewById(R.id.txtTexto);
   final EditText etEntrada= (EditText)findViewById(R.id.etEntrada);
   etEntrada.setInputType(InputType.TYPE_CLASS_TEXT |
     InputType.TYPE_TEXT_VARIATION_PASSWORD);
   tvTexto.setGravity(Gravity.CENTER);
- btnA.setOnClickListener(new View.OnClickListener() {
    public void onClick(View v) {
      Random rand = new Random();
      if (cadena.contentEquals("texto")
        txtTexto.setText("Texto para la vista");
        txtTexto.setTextColor(Color.BLUE);
        txtTexto.setTextSize(rand.nextInt(40);
    }
  }
  //Otra opción es implementar la interfaz View.OnClickListener y
  //el método onClick dentro de la clase correspondiente.
  //btnA.setOnClickListener(this)
- protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.splash);

    ourSong = MediaPlayer.create(Splash.this,  R.raw.sonido);
    ourSong.start();

    Thread timer = new Thread() {
       public void run() {
         try {
           sleep(5000);
         } catch (InterruptedException e) {
           e.printStackTrace();
         } finally {
           Intent openSplash = new Intent("not.ebacelo.android.SPLASH");
           startActivity(openSplash);
         }
       }
     }
  }
- protected void onPause() {
    super.onPause();
    ourSong.release();
    finish();
  }

- Lista de elementos:
  public class Menu extends ListActivity {
    String elementos[] = {"elemento1", "elemento2", "elemento3", "elemento4"};

    protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setListAdapter(new ArrayAdapter<String>(Menu.this,
         android.R.layout.simple_list_item_1, elementos));
    }
    protected void onListItemClick(ListView l, View v, int position, long id) {
      super.onListItemClick(l, v, position, id);
      String elementoSeleccionado = elementos[position];
      try {
        Class miClase = Class.forName("not.ebacelo.android.splash");
        Intent miIntent = new Intent(Menu.this, miClase);
        startActivity(miIntent);
      } catch (ClassNotFounException e) {
        e.printStackTrace();
      }
    }
  }

lunes, 23 de marzo de 2015

Javascript: cosas a recordar

Hoy voy a anotar aquí una serie de cosas que es recomendable tener en cuenta a la hora de trabajar con Javascript. Espero que estas anotaciones me sirvan de recordatorio cada vez que tenga que trabajar con Javascript y, de paso, que puedan resultarle útiles a cualquiera que lea este blog.

- Intérprete javascript online: jsbin.com

- Usar .textContent en lugar de innerHTML por el bien de la compatibilidad con
  todos los navegadores

- Usar doc.getElementById("elemento").value en lugar de .value (este último no
  funciona en IE)
- document.getElementById("imagen").getAttribute("src");
- document.getElementById("imagen").setAttribute("src", "imagen.jpg");

- Colocar las etiquetas <script> justo antes de la etiqueta de cierre </body>. Los
  navegadores paran la  descarga del HTML al encontrar etiquetas <script> para
  descargar los ficheros .js. También se puede usar atributo
  async de <script> en navegadores modernos.

- Sensible a mayúsculas y minúsculas.

- Comentarios: // (única línea) y /* comentario */ (varias líneas)

- Tipos de datos: Number, Boolean, String. Para definir una variable usamos var.

- parseInt() y parseFloat() para convertir strings a números enteros y decimales.
  isNaN() para comprobar si es un número.
- Number(prompt("Introduce un número: ", "");  //convierte una cadena a número

- cadena1.concat(" ", cadena2);
  también se puede utilizar: cadena1 + " " + cadena2.

- cadena1 = "Hola"; cadena2 = "mundo"; cadena1.concat(" ", cadena2, "!");

- cadena1.toUpperCase(); cadena2.toLowerCase();

- "cadena de texto".length

- trim(): eliminar blancos, cadena1.replace("cadena1", "cadena2");
  El primer parámetro de replace puede   ser una expresión regular

- substring(inicio, fin); substr(inicio, contador); slice(inicio, fin);
  substr no funciona en IE8 y anteriores, slice no intercambia inicio y fin cuando   inicio>fin

- cadena1.indexOf("c"); cadena2.lastIndexOf("d");

- document.getElementById("idElemento");

- prompt("Mensaje a mostrar", "texto por defecto para devolver si el usuario no
  introduce nada");

- Definir arrays:
  var arrayVacio = []; var miArray = ["uno", "dos", "tres"];
  var array10 = new Array(10);
  var array2d = new Array(3); for (var i=0; i<3 data-blogger-escaped-array2d=""
  data-blogger-escaped-br="" data-blogger-escaped-i="" data-blogger-escaped-new="">
  Array(3);} - miArray.push("valor a añadir");
- var elemento = miArray.pop(); //devuelve el último elemento del array y lo
  elimina del mismo.
- miArray.shift(); //obtiene el primer elemento del array y lo quita del mismo
- miArray.unshift("valor"); //añade un valor como primer elemento del array.
- miArray.sort(); //por defecto supone que los elementos son strings y los ordena.
- miArray.sort(function(a,b) {return a-b}); //ordena de menor a mayor
- var a = miArray.sort(function(a,b) {return b-a}); ó a.reverse();
- miArray.splice(índice, contadorEliminar, elemento1...elementoN); //añadir y
  eliminar
- miArray.filter(esPar(value, index, array) { return value % 2; }); //sólo los
  pares
- miArray.filter(function (value, index, array) { return array.indexOf(value) ==
  index; }); //no repetidos

- Se puede usar una función antes de su definición (function hoisting).
- Funciones anónimas: var sumar = function (x, y) { return x+y }; sumar(1, 2);
- arguments.length; arguments[i]; //es un objeto no un array
  var arrayArgs = Array.prototype.slice.call(arguments); //convertir en array
- Lanzar exceptiones: throw {error: "Número inválido", message: "No es un formato
  numérico válido"}

- window.onerror = function(msg, url, line) {
  alert("Mensaje: " + msg + "\nURL: " + url + "\nLinea: " + line);
  return true; }
- <img src="imagenNoExiste.jpg" onerror="imageErrorHandler()" />
  function imageErrorHandler(msg, url, line) {alert("Hay un problema
  con la imagen");}

- var fecha = new Date(); fecha = new Date("March 26, 2015 11:06:00");
- var fecha = new Date(year, month, day, hour, minute, second, millisecond);
- fecha.getFullYear(); fecha.getMonth(); fecha.getDate(); fecha.getDay();
  fecha.getHours(); fecha.getMinutes(); fecha.getSeconds();

- var idIntervalo = window.setInterval(funcion, intervaloMilisegundos);
  window.clearInterval(idIntervalo);
- var idTemporizador = window.setTimeout(funcion, esperaMilisegundos);
  window.clearTimeout(idTemporizador);

- if (confirm("¿Está seguro?")) {return true; } else {return false;}
  onclick="return confirmOnSubmit()"

- Atributos de la propiedad style (ver aquí).
- Eventos de elementos: onclick, onblur, onmouseover, onmouseout,...
- Eventos de DOM: document.getElementById("idElemento").onmouseover =
  nombreFuncion;
- idBtn.addEventListener("mouseover", changeColorOnMouseOver, false);
- idBtn.removeEventListener("mouseover", changeColorOnMouseOver);
  (Disponibles en IE9+ y otros navegadores modernos).
- idBtn.attachEvent("onclick", clickEventHandler);
- idBtn.detachEvent("onclick", clickEventHandler);
  (Soportado en IE8 y versiones anteriores)
- if (btn.addEventListener) {... (será true para IE9+ y otros navegadores
  modernos).
- <input type="button" value="Pulsar" id="btn"
  onclick="obtenerDetallesEvento(event)" />
  function obtenerDetallesEvento(event) {   
     var sourceElement;
     if (event.srcElement) {   //soportado en IE8-
        sourceElement = event.srcElement;
     } else {
        sourceElement = event.target;   //soportado en IE9+ y otros
     }
     return event.type + " " + event.clientX + " " + event.clientY + " " +
     sourceElement.type + " " + sourceElement.tagName;
  }

- Utilizar event bubbling para evitar asignar manejador en muchas ocasiones.
- Se puede activar event capturing poniendo el último parámetro del
  método .addEventListener a TRUE. Si es FALSE, se produce event
  bubbling. Se pueden activar los dos si se añade dos veces el manejador con
  los valores distintos para el último parámetro.
- Cancelar event bubbling:
  event = event || window.event;  //por compatibilidad con IE8-
  if (event.stopPropagation) {
    event.stopPropagation();  //IE9+ y otros
  } else {
    event.cancelBubble = true;  //IE8-
  }

- Evitar funcionamiento por defecto del navegador (click botón derecho,
  pulsar enlace):
  <body oncontextmenu="return false">
  document.oncontextmenu = deshabilitaPorDefecto;

  function deshabilitaPorDefecto(event) {
    event = event || window.event;
    if (event.preventDefault) {  //IE9+ y otros
      event.preventDefault();
    } else {
      event.returnValue = false;  //IE8-
    }

  Prevenir que un enlace nos lleve a otra página:
  <a href="http://www.google.com" onclick="return false" >Google</a>
  <a href="http://www.google.com" onclick="deshabilitaPorDefecto(event)">
  Google
  </a>

- Detectar el botón del ratón pulsado:
  <input type="button" value="Click me" onmouseup="obtenerClickRaton(event)" />
  function obtenerClickRaton(event) {
    var botonPulsado;
    if (event.which)  {
      botonPulsado = event.which;  //IE9+ y otros
    } else {
      botonPulsado = event.button;  //IE8-
    }
  }

- Ventanas emergentes:
  window.open(URL, nombre, caracteristicas, reemplazar);
  <input type="button" value="Abrir ventana"
  onclick="window.open('http://www.google.com', '_self ', 'height=100,width=150',
  false)" />

  var ventana;
  function abrirVentana() {
    ventana = window.open("http://www.google.com", "ventana", "height=100,
    width=100");
  }
  function cerrarVentana() { ventana.close(); }


- Expresiones regulares:
  Herramienta gratuíta: Expresso Librería de expresiones: Regexlib
  var valor= document.getElementById("txtCadena").value;
  var resultado = valor.match(/\d+/g);
  if (resultado != null) {
    for (var i=0; i<resultado.length;i++) {
      document.getElementById("txtResultado").value += resultado[i] + "\r\n";
    }
  }
    \d: valor numérico (\d+: 1 o varios elementos numéricos consecutivos)
    /g: búsqueda global
    \d{5,}: números de 5 dígitos o más ---  \b\d{4}\b:  números de justo 4 dígitos
    [0-9]{5}: equivalente al anterior
    \b[a-zA-Z]\b: palabras con letras sin tilde
    [a|b|c]: busca cualquiera de los caracteres incluídos

  var cadena="Cadena con números: 10 11 222";
  cadena.match(/\d+/g); //g: global, i: insensible a mayúsculas y minúsculas,
  m: multilínea
  cadena.replace("/\d+/g, "XXX");  //reemplaza números por XXX
  cadena.split("/\d+/");
  var posicion = cadena.search(/\d+/);

  var regexp = /\d+/g;   //expresión regular literal (mejor rendimiento si
  la expresión regular no varía)
  var regexp = new RegExp("\\d+", "gim");  //global, ignore case, multiline
  regexp.exec(cadena);  //busca coincidencia y devuelve la primera encontrada
  regexp.test(cadena); //busca coincidencia y devuelve true o false
  regexp.toString();  //devuelve cadena con la expresión regular
  regexp.global; regexp.ignoreCase; regexp.multiline; regexp.source;

- Validación lado cliente:
  function validarLogName() {
    var txtName = document.getElementById("txtName");
    var name= txtName.value;
    var regExName = /[a-zA-Z0-9]+/;

    txtName.style.color = "white";
    if (regExName.text(name))
      txtName.style.backgroundColor = "green";
    else
      txtName.style.backgroundColor = "red";
  }

- Herramientas para reducir código Javascript (minification):
  - http://crockford.com/javascript/jsmin
  - https://marijnhaverbeke.nl/uglifyjs (online)



martes, 24 de septiembre de 2013

Método de cifrado inventado (en Android)

En este caso voy a publicar una pequeña aplicación muy parecida a la de la publicación anterior, al menos en apariencia. En este caso, la principal diferencia con respecto a la anterior radica en el algoritmo de cifrado que se aplica. Se trata de un método creado por mi mismo mezclando sustitución y transposición en el mismo algoritmo.
Este algoritmo consiste en dos pasos:

  •  Primero se sustituye cada carácter del mensaje por el correspondiente a su posición en el alfabeto con un desplazamiento 9 si se trata de una letra y 5 si es un dígito. En caso de ser otro carácter no se modificará.
  • En segundo lugar, divide el mensaje resultante de aplicar el proceso anterior en partes de 3 caracteres y se invierte el orden de los caracteres en cada grupo. Después se vuelven a agrupar y así se obtiene el mensaje final cifrado.
La implementación del algoritmo de cifrado/descifrado se hace en una clase independiente con lo que es muy fácil cambiar el algoritmo utilizado sin tener que modificar la interfaz de usuario, o haciendo cambios mínimos.
A continuación podéis ver el código y descargar un fichero para instalarla en un dispositivo Android.


package com.example.firstapp;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;

public class MainActivity extends Activity {
private final String abc = new String("ABCDEFGHIJKLMNÑOPQRSTUVWXYZ");


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
     
       
        Button btnConvertir = (Button)findViewById(R.id.btnConvertir);
        Button btnLimpiar = (Button)findViewById(R.id.btnLimpiar);
     
        final EditText etPlano = (EditText)findViewById(R.id.etPlano);
        final EditText etCifrado = (EditText)findViewById(R.id.etCifrado);
     
        btnLimpiar.setOnClickListener(new View.OnClickListener() {

@Override
public void onClick(View v) {
etPlano.setText("");
etCifrado.setText("");
}
});
     
        btnConvertir.setOnClickListener(new View.OnClickListener() {

@Override
public void onClick(View v) {
String textoPlano = etPlano.getText().toString();
String textoCifrado = etCifrado.getText().toString();
int txtLen = etPlano.length();
int cifrLen = etCifrado.length();

ECipherDecipher ecd = new ECipherDecipher();

if ((txtLen != 0) || (cifrLen !=0))
{
if (txtLen > 0)
{
String contenidoCifrado = ecd.cifrar(textoPlano);
etCifrado.setText(contenidoCifrado);
}
else
{
String contenidoPlano = ecd.descifrar(textoCifrado);
etPlano.setText(contenidoPlano);
}
}
}
});
    }

}

El código correspondiente a la implementación del algorirtmo se muestra en el código siguiente.

package com.example.firstapp;

public class ECipherDecipher {
private final String abc = new String("ABCDEFGHIJKLMNÑOPQRSTUVWXYZ");
private final String abc2 = new String("0123456789");

//Definición del desplazamiento en la parte de cifrado por sustitución (César)
private int DESPLAZAMIENTO_CARACTERES = 9;
private int DESPLAZAMIENTO_DIGITOS = 5;

public ECipherDecipher()
{

}

public String cifrar(String cadena)
{
char caracterCifrado;
int posCaracterPlano;
int posCaracterCifrado;
int endPosSubCadena;
StringBuilder sbTextoIntermedio = new StringBuilder();
StringBuilder sbTextoCifrado = new StringBuilder();
StringBuilder subCadena3;

//Convertimos a mayúsculas
String texto = cadena.toUpperCase();

//Para cada caracter del texto
for (int i=0; i<texto.length(); i++)
{
char caracter = texto.charAt(i);
//Si es alfabético
if (Character.isLetter(caracter))
{
//Desplazamiento de cada carácter 9 posiciones
posCaracterPlano = abc.indexOf(caracter);
posCaracterCifrado = (posCaracterPlano + DESPLAZAMIENTO_CARACTERES) % abc.length();
caracterCifrado = abc.charAt(posCaracterCifrado);
sbTextoIntermedio.append(caracterCifrado);
}
else if (Character.isDigit(caracter))
{
//Si es numérico
//Desplazamiento de cada dígito 5 posiciones
posCaracterPlano = abc2.indexOf(caracter);
posCaracterCifrado = (posCaracterPlano + DESPLAZAMIENTO_DIGITOS) % abc2.length();
caracterCifrado = abc2.charAt(posCaracterCifrado);
sbTextoIntermedio.append(caracterCifrado);

}
else
{
//En otro caso no modificar el carácter
sbTextoIntermedio.append(caracter);
}
}

//Con el texto convertido anteriormente cogemos subcadenas de longitud 3
//y los invertimos.
while (sbTextoIntermedio.length()>0)
{
endPosSubCadena = 3;

if (sbTextoIntermedio.length()< 3)
{
endPosSubCadena = sbTextoIntermedio.length();
}
subCadena3 = new StringBuilder(sbTextoIntermedio.substring(0, endPosSubCadena));
sbTextoIntermedio = sbTextoIntermedio.delete(0, endPosSubCadena);
subCadena3 = subCadena3.reverse();
sbTextoCifrado.append(subCadena3);
}

return sbTextoCifrado.toString();
}

public String descifrar(String texto)
{
char caracterDescifrado;
int posCaracterCifrado;
int posCaracterDescifrado;
int endPosSubCadena;
StringBuilder sbTextoIntermedio = new StringBuilder();
StringBuilder sbTextoCifrado = new StringBuilder();
StringBuilder sbTextoPlano = new StringBuilder();
StringBuilder subCadena3;

sbTextoCifrado = new StringBuilder(texto.toUpperCase());

//Con el texto cogemos subcadenas de longitud 3 y los invertimos.
while (sbTextoCifrado.length()>0)
{
//Posición de fin de la subcadena
endPosSubCadena = 3;

if (sbTextoCifrado.length()< 3)
{
endPosSubCadena = sbTextoCifrado.length();
}
subCadena3 = new StringBuilder(sbTextoCifrado.substring(0, endPosSubCadena));
sbTextoCifrado = sbTextoCifrado.delete(0, endPosSubCadena);
subCadena3 = subCadena3.reverse();
sbTextoIntermedio.append(subCadena3);
}

//Para cada caracter del texto
texto = sbTextoIntermedio.toString();

for (int i=0; i<texto.length(); i++)
{

char caracter = texto.charAt(i);
//Si es alfabético
if (Character.isLetter(caracter))
{
//Desplazamiento de cada carácter -9 posiciones
posCaracterCifrado = abc.indexOf(caracter);
posCaracterDescifrado = (posCaracterCifrado - DESPLAZAMIENTO_CARACTERES +
(abc.length())) % abc.length();
caracterDescifrado = abc.charAt(posCaracterDescifrado);
sbTextoPlano.append(caracterDescifrado);
}
else if (Character.isDigit(caracter))
{
//Si es numérico
//Desplazamiento de cada dígito -5 posiciones
posCaracterCifrado = abc2.indexOf(caracter);
posCaracterDescifrado = (posCaracterCifrado - DESPLAZAMIENTO_DIGITOS +
(abc2.length())) % abc2.length();
caracterDescifrado = abc2.charAt(posCaracterDescifrado);
sbTextoPlano.append(caracterDescifrado);
}
else
{
//En otro caso no modificar el carácter
sbTextoPlano.append(caracter);
}
}

return sbTextoPlano.toString();
}
}

La apariencia de la aplicación es casi igual a la de la aplicación descrita en la entrada anterior del blog.


El fichero de instalación se puede descargar aquí.