Xwork's Blog

The lord is waiting to take your hand.

Vamos a programar #67 - Syntax Highlighter M (beta)

Hola de nuevo a todos, el día de hoy vamos a continuar con más sobre expresiones regulares, pero mas específicamente, cómo resaltar la sintaxis usando código en C#.

En el post anterior vimos una prueba de concepto (Proof Of Concept {POC}) de un programa que sirve para realzar la sintaxis, pero debido a varias razones que son difíciles de explicar (o porque soy flojo), no todo el código estaba implementado. Por eso hoy veremos cómo es que funciona la versión beta de este programa y explicaré un poco que hace cada función.

Antes que nada, veamos el código que hace funcionar las cosas:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading;

namespace SyntaxHighlighter
{
	public partial class FrmMain : Form
	{
		private enum WordTypes : int
		{
			WType1 = 0,
			WType2,
			WType3,
			NType,
			OpType,
			SType,
			CType,
			OtherType
		}
		#region "Cargar Estilos por default"
		private Color FontColor1 = (Color)Properties.Settings.Default["ColorRW1"];
		private Color FontColor2 = (Color)Properties.Settings.Default["ColorRW2"];
		private Color FontColor3 = (Color)Properties.Settings.Default["ColorRW3"];
		private Color ColorComments = (Color)Properties.Settings.Default["ColorCom"];
		private Color ColorNum = (Color)Properties.Settings.Default["ColorNum"];
		private Color ColorStrings = (Color)Properties.Settings.Default["ColorStrings"];
		private Color ColorOperator = (Color)Properties.Settings.Default["ColorOp"];
		private Color GeneralColor = (Color)Properties.Settings.Default["GeneralFontColor"];
		private Font GeneralFont = (Font)Properties.Settings.Default["GeneralFont"];
		#endregion

	# region "Palabras reservadas"
		private string ReservedWords1 = @"\b(as|base|bool|break|byte|case|catch|char|checked|class|continue|decimal|default|delegate|do|double|else|enum|explicit|false|finally|fixed|float|for|foreach|goto|if|implicit|in|int|interface|is|lock|long|namespace|null|object|operator|out|params|ref|return|sbyte|short|sizeof|stackalloc|string|struct|switch|this|throw|true|try|typeof|uint|ulong|unchecked|ushort|using|var|void|while)\b";
		private string ReservedWords2 = @"\b(public|private|internal|protected|abstract|async|const|event|extern|new|override|partial|readonly|sealed|static|unsafe|virtual|volatile)\b";
		private string Comments = @"((\/\/.+)|(\/\*[\s\S]*?\*\/)|\b(\\\\)\b)";
		private string Numbers = @"((0[xX])(\d|[a-f|A-F])+)|(\b(\d+)\b)";
		private string Strings = "(\".*?\")";
		private string Operators = @"\[|\]|\(|\)|\.|\?|\b(and)\b|(\:\:)|\+|\-|\*|\/|\%|\&|\^|\!|\~|\=|\<|\>|\?\:|\++\|\-\-|\&\&|\<\<|\>\>|\=\=|\!\=|\<\=|\>\=|\+\=|\-\=|\*\=|\/\=|\%\=|\&\=|\=|\^\=|\<\<\=|\>\>\=|\-\>|\?\?|\=\>|";
	#endregion

	#region Delegados
		private delegate void UpadteRTBDelegate(int Start, int End, Color Colores);
		private delegate string GetTextDelegate();
	#endregion

		private void UpdateRTB(int Start, int End, Color Colores)
		{
			if (RTBMain.InvokeRequired)
				RTBMain.Invoke(new UpadteRTBDelegate(UpdateRTB), Start, End, Colores);
			else
			{
				RTBMain.SelectionStart = Start;
				RTBMain.SelectionLength = End;
				RTBMain.SelectionColor = Colores;
			}

		}

		private string GetText()
		{
			if (RTBMain.InvokeRequired)
				return (string)RTBMain.Invoke(new GetTextDelegate(GetText));
			else
				return RTBMain.Text;
		}

		private void DoTheWork()
		{
			Thread DoWork = new Thread(new ThreadStart(() => ColorizeAll()));
			DoWork.Start();
		}

		public FrmMain()
		{
			InitializeComponent();
		}

		private void ColorizeAll()
		{
			Colorize(ReservedWords1, WordTypes.WType1);
			Colorize(ReservedWords2, WordTypes.WType2);
			Colorize(Numbers, WordTypes.NType);
			Colorize(Operators, WordTypes.OpType);
			Colorize(Strings, WordTypes.SType);
			Colorize(Comments, WordTypes.CType);
		}

		private void SaveColor(WordTypes Tipo, Color Colores)
		{
			switch (Tipo)
			{
				case WordTypes.WType1:
					FontColor1 = Colores;
					Properties.Settings.Default["ColorRW1"] = Colores;
					Properties.Settings.Default.Save();
					break;
				case WordTypes.WType2:
					FontColor2 = Colores;
					Properties.Settings.Default["ColorRW2"] = Colores;
					Properties.Settings.Default.Save();
					break;
				case WordTypes.WType3:
					FontColor3 = Colores;
					Properties.Settings.Default["ColorRW3"] = Colores;
					Properties.Settings.Default.Save();
					break;
				case WordTypes.NType:
					ColorNum = Colores;
					Properties.Settings.Default["ColorNum"] = Colores;
					Properties.Settings.Default.Save();
					break;
				case WordTypes.OpType:
					ColorOperator = Colores;
					Properties.Settings.Default["ColorOp"] = Colores;
					Properties.Settings.Default.Save();
					break;
				case WordTypes.SType:
					ColorStrings = Colores;
					Properties.Settings.Default["ColorStrings"] = Colores;
					Properties.Settings.Default.Save();
					break;
				case WordTypes.CType:
					ColorComments = Colores;
					Properties.Settings.Default["ColorCom"] = Colores;
					Properties.Settings.Default.Save();
					break;
				case WordTypes.OtherType:
					GeneralColor = Colores;
					RTBMain.ForeColor = GeneralColor;
					Properties.Settings.Default["GeneralFontColor"] = Colores;
					Properties.Settings.Default.Save();
					break;
				default:
					break;
			}
		}

		private void Colorize(string Patron, WordTypes Tipo)
		{
			Color CurrentFontColor = GeneralColor;
			switch (Tipo)
			{
				case WordTypes.WType1:
					CurrentFontColor = FontColor1;
					break;
				case WordTypes.WType2:
					CurrentFontColor = FontColor2;
					break;
				case WordTypes.WType3:
					CurrentFontColor = FontColor3;
					break;
				case WordTypes.CType:
					CurrentFontColor = ColorComments;
					break;
				case WordTypes.NType:
					CurrentFontColor = ColorNum;
					break;
				case WordTypes.SType:
					CurrentFontColor = ColorStrings;
					break;
				case WordTypes.OpType:
					CurrentFontColor = ColorOperator;
					break;
				case WordTypes.OtherType:
					CurrentFontColor = GeneralColor;
					break;
			}
			//Si quitas el comentario siguiente y comentas la linea que sigue, puedes ver lo importante de la funcion GetText()
			//MatchCollection wordColl = Regex.Matches(RTBMain.Text, Patron);
			MatchCollection wordColl = Regex.Matches(GetText(), Patron);
			foreach (Match m in wordColl)
			{
				UpdateRTB(m.Index, m.Length, CurrentFontColor);
			}
		}

		private void TSBtnOpen_Click(object sender, EventArgs e)
		{
			OpenFileDialog OpDiag = new OpenFileDialog();
			OpDiag.Filter = "Código fuente C#|*.cs|Arvhivos de texto|*.txt|Todos los archivos|*.*";
			OpDiag.Multiselect = false;
			DialogResult DiagRes = OpDiag.ShowDialog();
			if (DiagRes == DialogResult.OK)
			{
				RTBMain.Clear();
				StreamReader SR = new StreamReader(OpDiag.FileName, Encoding.UTF8);
				RTBMain.AppendText(SR.ReadToEnd());
				DoTheWork();

			}
		}

		private void TSBtnSave_Click(object sender, EventArgs e)
		{
			SaveFileDialog SaveDiag = new SaveFileDialog();
			SaveDiag.Filter = "Texto enriquecido|*.rtf";
			if (SaveDiag.ShowDialog() == DialogResult.OK)
				RTBMain.SaveFile(SaveDiag.FileName);
		}

		private void TSBtnFont_Click(object sender, EventArgs e)
		{
			FontDialog FontDiag = new FontDialog();
			FontDiag.ShowApply = false;
			if (FontDiag.ShowDialog() == DialogResult.OK)
			{
				RTBMain.Font = FontDiag.Font;
				Properties.Settings.Default["GeneralFont"] = FontDiag.Font;
				Properties.Settings.Default.Save();
				DoTheWork();
			}
		}

		private void TSBtnColor_Click(object sender, EventArgs e)
		{
			ColorDialog ColorDiag = new ColorDialog();
			if (ColorDiag.ShowDialog() == DialogResult.OK)
			{
				SaveColor((WordTypes)TSLBWordTypes.SelectedIndex, ColorDiag.Color);
			}

		}

		private void FrmMain_Load(object sender, EventArgs e)
		{
			RTBMain.Font = GeneralFont;
			RTBMain.ForeColor = GeneralColor;
			TSLBWordTypes.SelectedIndex = 7;
		}

		private void TSBtnReDo_Click(object sender, EventArgs e)
		{
			DoTheWork();
		}

		private void TSLBWordTypes_TextChanged(object sender, EventArgs e)
		{

		}
	}
}

La explicación de cada parte en orden de aparición.

Primero tenemos una enumeración llamada "WordTypes" que hereda del tipo "int", esto solo es para poder usar su valor de manera mas sencilla. La enumeración esta compuesta  por los elementos:

  • "WType1" - y su valor es 0
  • "WType2" - Su valor es 1
  • "Wtype3" - Su valor es 2
  • "NType" - Su valor es 3
  • "OpType" - Su valor es 4
  • "SType" - Su valor es 5
  • "CType" - Su valor es 6
  • "OtherType" - Su valor es 7
Solo para tenerlo en cuenta, "WType" es por Word Type, "NType" es por Numbers Type, "OpType" es por Operator Type, "SType" es por String Type, "CType" es por Comment Type y "OtherType" su nombre es muy explicatorio. Si bien recuerdo, en la gran mayoría de los lenguajes se cubren todos los tipos de palabras reservadas, si alguno requiriera de algún tipo más, se harán las adecuaciones para que se puedan escoger mas tipos, pero por ahora cubrimos gran parte y el principal por ahora que es C#.

Después creamos la función "UpdateRTB()", está función servirá para poder darle los estilos al texto en el control "RTBMain" y hay dos razones para usar un procedimiento para esa tarea. El primero es que esta acción se va a realizar mas de una vez y la otra; es para poder usarla desde un "Thread" (alguna vez dije que en lugar de "Thread" usaría hilo pero naahh) y por eso es que justo antes de crear el procedimiento, también creamos un delegado que contiene los mismos parámetros que la función. La función recibe tres parámetro, el primero del tipo "int", el segundo del tipo "int" y el tercero del tipo "Color", dentro de la función lo primero que revisa es si el control esta siendo invocado, si es así, manda a llamar al delegado para que este establezca los parámetros. Si hacemos un poco de memoria, en el post anterior, vimos que para resaltar una palabra usábamos tres valores (de ahí los tres parámetros), el primero era donde iba a comenzar la selección, el segundo que tanto se iba a seleccionar y el tercero era el color para el resaltado. Con las adecuaciones que hicimos, ahora podemos hacer el resaltado desde otro "Thread".

La siguiente función es "GetText()", está función tiene cómo objetivo obtener el texto del control "RTBMain" Al igual que la función anterior, primero prueba si el control está siendo invocado, si es asi la funcion llama al delegado "GetTextDelegate" y regresa un valor del tipo "string" con el texto que el delegado obtuvo, en caso contrario devuelve un valor del tipo "string" con el texto de "RTBMain"
Aquí vemos la importancia de los delgados. Ya los veremos mas adelante ;)
La siguiente función es "DoTheWork()", está función crea un "Thread" diferente y ejecuta la función "ColorizeAll()"

El siguiente procedimiento es "ColorizeAll()", esta función su trabajo es llamar a la función "Colorize", llamándola tantas veces cómo tipos de palabras reservadas tenemos, debido a cómo está hecho el programa, aquí es muy importante conservar el orden (incluso ahora dos de los elementos están al revés por lo que podrás notar que alguna cosas tienen color que no deben, eso si, nada grave).

La siguiente función es "SaveColor". Esta función recibe dos parámetros, el primero es del tipo "WorTypes" y el segundo es del tipo "Color", esta función se usa para guardar los colores que el usuario elija para cada tipo de palabra reservada, usando la instrucción "switch", comprueba de que caso se trata y dependiendo de que valor sea "Tipo"; que es del tipo "WordTypes";  y guarda el valor en la configuración del programa.

Finalmente el procedimiento "Colorize". Recibe dos parámetro, el primero del tipo "string" que es el patrón de expresión regular que se va a buscar y el segundo del tipo "WordTypes". Al igual que el procedimiento para guardar las configuraciones, en este procedimiento "probamos" cual de los posibles valores es el que se va a evaluar y a partir de ahí cambiar al color que se designó para el resaltado.

Al implementar todas la funciones, el programa ya sirve, pero aun es beta, puesto que tienen errores básicos. Todos esto los arreglaremos, pero primero trataremos de implementar las funciones que serian "de cajón", es decir resaltar la sintaxis de otros lenguajes y no solo de C#. Eso lo haremos en el siguiente post, continuaremos con mas de este programa.

Y bien, por ahora es todo, cómo de costumbre, puedes bajar el código de mi dropbox para probarlo y modificarlo.

Los leo luego.

Vamos a programar #66 - Expresiones regulares - Resaltador de sintaxis (POC).

Hola de nuevo a todos, el día de hoy vamos a continuar con un poco mas de expresiones regulares. Para seguir con el aprendizaje, veremos cómo hacer un programa en ## que servirá para resaltar la sintaxis de código fuente en C#.

El otro día mientras revisaba los mensajes de twitter, vi uno que me llamo la atencion:
Recuerden, anonimato ante todo!!.

Un usuario me pregunto si era posible crear una aplicación para resaltar la sintaxis de un código fuente y aprovechando que andamos en las expresiones regulares, dije: "por qué no?".

Para empezar, hay que recordar un poco de los fundamentos del lenguaje que queremos resaltar, por lo general está formado por componentes de la siguiente lista:

  • Palabras clave
    • Tipo uno
    • Tipo dos
    • Tipo tres
  • Números
  • Operadores
  • Cadenas de texto
  • Comentarios
En C# hay una lista de palabras reservadas, para fines prácticos, los podemos separar en instrucciones, tipos, modificadores, números, operadores, cadenas de texto y comentarios (espero no omitir nada), entonces podemos crear un expresión regular para cada grupo.
Una lista de las palabras reservadas en C# (sin separar) es esta:

  • as
  • base
  • bool
  • break
  • byte
  • case
  • catch
  • char
  • checked
  • class
  • continue
  • decimal
  • default
  • delegate
  • do
  • double
  • else
  • enum
  • explicit
  • false
  • finally
  • fixed
  • float
  • for
  • foreach
  • goto
  • if
  • implicit
  • in
  • int
  • interface
  • is
  • lock
  • long
  • namespace
  • null
  • object
  • operator
  • out
  • params
  • ref
  • return
  • sbyte
  • short
  • sizeof
  • stackalloc
  • string
  • struct
  • switch
  • this
  • throw
  • true
  • try
  • typeof
  • uint
  • ulong
  • unchecked
  • ushort
  • using
  • var
  • void
  • while
  • public
  • private
  • internal
  • protected
  • abstract
  • async
  • const
  • event
  • extern
  • new
  • override
  • partial
  • readonly
  • sealed
  • static
  • unsafe
  • virtual
  • volatile
Una vez que tenemos la lista, podemos crear una expresión regular teniendo en cuenta que son palabras exactas entonces si tomamos de la lista anterior solo los primeros tres elemento, la expresion regular queda asi

\b(as|base|bool)\b

Obviamente hay que agregar el resto de ellas para poder encontrar todas.

Para poder encontrar los números, primero tenemos que recordar que en C# se pueden escribir los numero directamente en base decimal o en base hexadecimal. Para la base hexadecimal hay que recordar que los número tendrán el formato "0x" antes de cualquier número en esa base.

Para poder encontrar los números, podemos crear una expresión regular cómo esta:

(\b(\d+)\b)

para los números decimales. Y algo cómo esta:

(0[xX])(\d|[a-f|A-F])+)

Para los números hexadecimales.

Para poder encontrar los comentarios, lo primero que haremos es recordar cómo es que forman los comentarios en c#. En C# hay al menos dos tipos de comentarios. El primero es el comentario sencillo y siempre comienza por "//" hasta el final de la linea. El segundo es el comentario multi-linea, este consiste que se comentará todo desde el inicio hasta al final (aunque parezca redundante), eso quiere decir que desde la marca de inicio "/*" sin importar que haya (nuevas lineas, apostrofes, comas, etc.) el comentario se hará hasta que se encuentre con el indicio de cierre "*/"

Para encontrar los comentarios simple podemos crear una expresión regular cómo la que sigue:

(\/\/.+)

Y para los comentario de muchas líneas, podemos crear una cómo la que sigue

(\/\*[\s\S]*?\*\/))

Para las cadenas texto hay que recordar que siempre van entre comillas " " ":

(\".*?\")

Y para los operadores en C#, la lista es la siguiente:

  • []
  • ()
  • .
  • ?
  • and
  • ::
  • +
  • -
  • *
  • /
  • %
  • &
  • ^
  • !
  • ~
  • =
  • <
  • >
  • ?:
  • ++
  • --
  • &&
  • <<
  • >>
  • ==
  • !=
  • <=
  • >=
  • +=
  • -=
  • *=
  • /=
  • %=
  • &=
  • =
  • ^=
  • <<=
  • >>=
  • ->
  • ??
  • =>

Y podemos crear un patrón cómo el que sigue (Solo para los primeros tres).

(\[|\]|\()

Ahora podemos pasar al código en C#.

/*+----------------------------------------------------------+
| Lista de palabras reservadas                               |
+------------------------------------------------------------+
| as|base|bool|break|byte|case|catch|char|checked|class      |
+------------------------------------------------------------+
| continue|decimal|default|delegate|do|double|else|enum      |
+------------------------------------------------------------+
| explicit|false|finally|fixed|float|for|foreach|goto|if     |
+------------------------------------------------------------+
| implicit|in|int|interface|is|lock|long|namespace|null      |
+------------------------------------------------------------+
| object|operator|out|params|ref|return|sbyte|short|sizeof   |
+------------------------------------------------------------+
| stackalloc|string|struct|switch|this|throw|true|try|typeof |
+------------------------------------------------------------+
| uint|ulong|unchecked|ushort|using|var|void|while           |
+----------------------------------------------------------+*/
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Text.RegularExpressions;

namespace SyntaxHighlighter
{
	public partial class FrmMain : Form
	{
		private string ReservedWords1 = @"\b(as|base|bool|break|byte|case|catch|char|checked|class|continue|decimal|default|delegate|do|double|else|enum|explicit|false|finally|fixed|float|for|foreach|goto|if|implicit|in|int|interface|is|lock|long|namespace|null|object|operator|out|params|ref|return|sbyte|short|sizeof|stackalloc|string|struct|switch|this|throw|true|try|typeof|uint|ulong|unchecked|ushort|using|var|void|while)\b";
		private string ReservedWords2 = @"\b(public|private|internal|protected|abstract|async|const|event|extern|new|override|partial|readonly|sealed|static|unsafe|virtual|volatile)\b";
		private string Comments = @"((\/\/.+)|(\/\*[\s\S]*?\*\/))";
		private string Numbers = @"((0[xX])(\d|[a-f|A-F])+)|(\b(\d+)\b)";
		private string Strings = "(\".*?\")";
		private string Operators = @"\[|\]|\(|\)|\.|\?|(and)|(\:\:)|\+|\-|\*|\/|\%|\&|\^|\!|\~|\=|\<|\>|\?\:|\++\|\-\-|\&\&|\<\<|\>\>|\=\=|\!\=|\<\=|\>\=|\+\=|\-\=|\*\=|\/\=|\%\=|\&\=|\=|\^\=|\<\<\=|\>\>\=|\-\>|\?\?|\=\>|";
		public FrmMain()
		{
			InitializeComponent();
		}

		private Color FontColor1 = (Color)Properties.Settings.Default["ColorRW1"];
		private Color FontColor2 = (Color)Properties.Settings.Default["ColorRW2"];
		//private Color FontColor3 = (Color)Properties.Settings.Default["ColorRW3"];
		private Color FontComments = (Color)Properties.Settings.Default["ColorCom"];
		private Color FontNum = (Color)Properties.Settings.Default["ColorNum"];
		private Color FontStrings = (Color)Properties.Settings.Default["ColorStrings"];
		private Color FontOperator = (Color)Properties.Settings.Default["ColorOp"];

		private enum WordTypes
		{
			WType1  = 0,
			WType2,
			WType3,
			CType,
			NType,
			SType,
			OpType,
			OtherType
		}

		private void ColorizeAll()
		{
			Colorize(ReservedWords1, WordTypes.WType1);
			Colorize(ReservedWords2, WordTypes.WType2);
			Colorize(Numbers, WordTypes.NType);
			Colorize(Operators, WordTypes.OpType);
			Colorize(Strings, WordTypes.SType);
			Colorize(Comments, WordTypes.CType);
		}

		private void Colorize(string Patron, WordTypes Tipo)
		{
			Color CurrentFontColor = Color.Black;
			switch (Tipo)
			{
				case WordTypes.WType1:
				CurrentFontColor = FontColor1;
				break;
				case WordTypes.WType2:
				 CurrentFontColor = FontColor2;
				break;
				case WordTypes.WType3:
				CurrentFontColor = Color.Black;
				break;
				case WordTypes.CType:
				CurrentFontColor = FontComments;
				break;
				case WordTypes.NType:
				CurrentFontColor = FontNum;
				break;
				case WordTypes.SType:
				CurrentFontColor = FontStrings;
				break;
				case WordTypes.OpType:
				CurrentFontColor = FontOperator;
				break;
				case WordTypes.OtherType:
				CurrentFontColor = Color.Black;
				 break;
			}

				MatchCollection wordColl = Regex.Matches(RTBMain.Text, Patron);
				foreach (Match m in wordColl)
				{
					RTBMain.SelectionStart = m.Index;
					RTBMain.SelectionLength = m.Length;
					RTBMain.SelectionColor = CurrentFontColor;
			}
		}

		private void TSBtnOpen_Click(object sender, EventArgs e)
		{
			OpenFileDialog OpDiag = new OpenFileDialog();
			OpDiag.Filter = "Código fuente C#|*.cs|Archivos de texto|*.txt|Todos los archivos|*.*";
			OpDiag.Multiselect = false;
			DialogResult DiagRes = OpDiag.ShowDialog();
			if (DiagRes == DialogResult.OK)
			{
				RTBMain.Clear();
				StreamReader SR = new StreamReader(OpDiag.FileName, Encoding.UTF8);
				RTBMain.AppendText(SR.ReadToEnd());
				ColorizeAll();
				
			}
		}

		private void TSBtnSave_Click(object sender, EventArgs e)
		{
			SaveFileDialog SaveDiag = new SaveFileDialog();
			SaveDiag.Filter = "Texto enriquecido|*.rtf";
			if (SaveDiag.ShowDialog() == DialogResult.OK)
			RTBMain.SaveFile(SaveDiag.FileName);
		}

		private void TSBtnFont_Click(object sender, EventArgs e)
		{
			FontDialog FontDiag = new FontDialog();
			FontDiag.ShowApply = false;
			if(FontDiag.ShowDialog() == DialogResult.OK)
				RTBMain.Font = FontDiag.Font;
		}

		private void TSBtnColor_Click(object sender, EventArgs e)
		{
			Form FrmOptions = new FrmAjustes();
			DialogResult DiagRes = FrmOptions.ShowDialog();
			if (DiagRes == DialogResult.OK)
			{
			}
		}
	}
}

Y bien, por ahora es todo, en lo mientras y para no alargar tanto el post, puedes descargar la versión beta del programas desde mi dropbox, la semana que viene, explicare cada parte del código (por ahora no, ya que mas seguro buena parte va a cambiar) y agrgare más funciones para que no solo resalte el código de c#

Los leo luego

Vamos a programar #65 - Expresiones regulares en C#

Hola de nuevo a todos el dia de hoy vamos a continuar con más sobre expresiones regulares y cómo es que podemos implementarlas en C#.

En post recientes hemos visto ejemplos de expresiones regulares, para poder ejecutarlos usamos Notepad++, pero ¿que pasa si no tienes instalado este programa o simplemente no quieres hacerlo porque ya tienes otro bloc de notas? Para eso vamos a crear un pequeño programa en C# que servira para probar las expresiones regulares.

Para empezar, veamos el siguiente código:


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using System.IO;

namespace WordsFinder
{
	public partial class FrmMain : Form
	{
		public FrmMain()
		{
			InitializeComponent();
		}
		
		private Color FontColor = (Color)Properties.Settings.Default["ColorFont"];
		private Color BackGroundColor = (Color)Properties.Settings.Default["ColorBack"];
		
		private void ResetRTB()
		{
			RTBText.SelectAll();
			RTBText.SelectionBackColor = Color.FromName("white");
			RTBText.SelectionColor = Color.FromName("black");
		}

		private void Buscar()
		{
			if(!LBHistory.Items.Contains(TXTRegEx.Text))
				LBHistory.Items.Add(TXTRegEx.Text);
			ResetRTB();
			MatchCollection wordColl = Regex.Matches(RTBText.Text, TXTRegEx.Text);
			foreach (Match m in wordColl)
			{
				RTBText.SelectionStart = m.Index;
				RTBText.SelectionLength = m.Length;
				RTBText.SelectionBackColor  = BackGroundColor;
				RTBText.SelectionColor = FontColor;
			}
			RTBText.ScrollToCaret();
			TSSlblFound.Text = "Se encontraron: " + wordColl.Count.ToString();
		}

		private void BtnSearch_Click(object sender, EventArgs e)
		{
			Buscar();
		}

		private void BtnReset_Click(object sender, EventArgs e)
		{
			ResetRTB();
		}

		private void TSBtnOpen_Click(object sender, EventArgs e)
		{
			OpenFileDialog OpDiag = new OpenFileDialog();
			OpDiag.Filter = "Archivos de texto|*.txt|Todos los archivos|*.*";
			OpDiag.Multiselect = false;
			DialogResult DiagRes = OpDiag.ShowDialog();
			if (DiagRes == DialogResult.OK)
			{
				RTBText.Clear();
				this.Text = "Word Finder - " + OpDiag.FileName;
				StreamReader SR = new StreamReader(OpDiag.FileName, Encoding.UTF8);
				RTBText.AppendText(SR.ReadToEnd());
			}
		}

		private void TSBtnColor_Click(object sender, EventArgs e)
		{
			Form FrmOptions = new FrmAjustes();
			DialogResult DiagRes = FrmOptions.ShowDialog();
			if (DiagRes == DialogResult.OK)
			{
				FontColor = (Color)Properties.Settings.Default["ColorFont"];
				BackGroundColor = (Color)Properties.Settings.Default["ColorBack"];
			}
		}

		private void LBHistory_SelectedIndexChanged(object sender, EventArgs e)
		{
			TXTRegEx.Text = LBHistory.Items[LBHistory.SelectedIndex].ToString();
		}

		private void TXTRegEx_KeyPress(object sender, KeyPressEventArgs e)
		{
			if (e.KeyChar == 13)
				Buscar();
		}
	}
}

Para poder usar las expresiones regulares, debemos de importar el espacio de nombres "System.Text.RegularExpressions". Luego, todo el trabajo se hace en la función "Buscar()". En esta funcion, lo primero que hacemos es revisar si la expresión que queremos buscar ya está guardada en el "ListBox" "LBHistory", si no esta la agrega; en caso contrario, no lo hace. Luego creamos una variable del tipo "MatchCollection" llamada "wordColl" cuyo valor será el resultado de la función  "Regex.Matches()", a su vez esta función recibe dos parámetros; el primero: Es un valor del tipo "string" que será la cadena de texto en la que se buscarán los patrones de expresión regular, para este parámetro simplemente asignamos el contenido de "RTBText" que es un cuadro de texto de formato enriquecido (RichTextBox). El segundo parámetro también es una cadena de texto, este debe de ser el patrón de expresión regular que se quiere evaluar, en este caso el valor "Text" del control "TXTRegEx" (TextBox).

Una vez que se ejecuta la función, todos los resultados se "guardarán" en la variable "wordColl". Para poder saber cuales fueron los resultados, debemos de recorrer los valores, para eso, podemos usar un iteardor "foreach" recorriendo todos los valores. Cada uno de estos será un valor del tipo "Match" y  cómo lo sugiere su nombre, es el singular de "MatchCollection". Cada valor del tipo "Match" posee sub-funciones, en este caso solo usamos "Match.Index()" que sirve para indicar en que posición se encontró la coincidencia dentro de la cadena de texto que se pasó por parámetro a Regex.Matches()". este valor se usa para iniciar la selección en el control "RTBText" y luego usamos "Match.Length()"; que indica cual es la longitud de la coincidencia; para poder resaltar la palabra que se encontró en el texto del control "RTBText".



Con la anterior, se marcará cada coincidencia, pero ademas la aplicación dispone de un par de funciones adicionales, una es para abrir un archivo de texto externo y otra para poder configurar cómo es que queremos que el texto se resalte. Para eso hacemos uso de un segundo formulario cuyo código es el que sigue:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace WordsFinder
{
	public partial class FrmAjustes : Form
	{
		public FrmAjustes()
		{
			InitializeComponent();
		}

		private void BtnFontColor_Click(object sender, EventArgs e)
		{
			ColorDialog ColorDiag = new ColorDialog();
			DialogResult DiagRes = ColorDiag.ShowDialog();
			if (DiagRes == DialogResult.OK)
			{
				Properties.Settings.Default["ColorFont"] = ColorDiag.Color;
				lblMuestraBig.ForeColor = ColorDiag.Color;
				Properties.Settings.Default.Save();
			}
		}

		private void BtnAccept_Click(object sender, EventArgs e)
		{
			this.DialogResult = DialogResult.OK;
			this.Close();
		}

		private void BtnBackColor_Click(object sender, EventArgs e)
		{
			ColorDialog ColorDiag = new ColorDialog();
			DialogResult DiagRes = ColorDiag.ShowDialog();
			if (DiagRes == DialogResult.OK)
			{
				Properties.Settings.Default["ColorBack"] = ColorDiag.Color;
				lblMuestraBig.BackColor = ColorDiag.Color;
				Properties.Settings.Default.Save();

			}

		}

		private void BtnCancel_Click(object sender, EventArgs e)
		{
			this.DialogResult = DialogResult.Cancel;
			this.Close();
		}

		private void FrmAjustes_Load(object sender, EventArgs e)
		{
			lblMuestraBig.BackColor = (Color)Properties.Settings.Default["ColorBack"];
			lblMuestraBig.ForeColor = (Color)Properties.Settings.Default["ColorFont"];
		}
	}
}



La Aplicación es sencilla y no hace uso de todas las herramientas disponibles en la clase "Regex", pero para probar los ejemplos sirve bien. Cómo de costumbre el código lo puedes descargar de mi dropbox para poder probarlo.

Y bien, por ahora es todo, recuerda que los ejemplos de expresiones regulares que hemos visto en post anteriores, los puedes probar en este programa, aun no es a prueba de errores, pero eso lo veremos mas adelante.

Los leo luego.

Visual Studio 2010 express - Descarga

Hola de nuevo a todos, el día de hoy vamos a ver cómo conseguir visual studio 2010 express.
Hace algunos días en la clásica batalla del hombre contra windows 10, mi pc resultó con fallas por lo que me vi obligado a reiniciarla y restablecerla en su modo inicial. Comencé a instalar todos los programas que requiero y cuando llego la hora de instalar visual c# (que en su mayoría ya solo es para programas de ocio), decidí; en lugar de buscar mi unidad de DVD e instalarla; descargar el instalador desde la web.

Comencé a buscar y no encontré resultados útiles, la mayoría de descargas estaban en inglés y si bien no es problema, opté por buscar una versión en español. Después de buscar por un buen rato, no obtuve resultados. Lo cual me llevo a pensar "¿Que pasa si comparto la imagen que yo tengo?" y pues, eso hice.

Primero que nada, hay que recordar algunas de las primicias de este blog:

  • Aquí no promovemos la piratería.
  • Somos respetuosos de las ideas, por lo general tratatremos de crear nuestro propio código/programa.
  • Si usamos software de otros, en la medida de lo posible le daremos el crédito al autor.
Todo lo anterior es para resaltar que si en algún momento Microsoft dice que elimine el enlace, lo haré. Además, al instalar cualquiera de los programas, es necesario registrarlos. Si en algún momento ya no puedes obtener un registro, puedes usar las versiones más recientes de visual studio, actualmente hay una version gratuita.
Si venias por un número de registro estás en el lugar equivocado, ve al sitio de Microsoft, aun es posible registrar estas versiones.
Y bien , el vinculo para descargar el iso completo en español de visual studio express 2010 está en MEGA. Recuerda que Visual Studio 2010 es gratuito siempre y cuando lo registres, una vez que tienes tu número de activación guárdalo y funcionará cada vez que lo requieras. La semana que viene continuamos con más de las expresiones regulares.

Los leo luego.

Vamos a programar #64 - Expresiones regulares - Ejemplos (pt. 1).

Hola de nuevo a todos, el día de hoy vamos a continuar con las expresiones regulares.


En el post anterior vimos cuales son los caracteres que sirven para crear los patrones para las expresiones regulares, el día de hoy; y antes de pasar al código de VEncoder, vamos a ver cómo se usan con algunos ejemplos.
En esta ocasión voy a usar Notepad++ ya que soporta la búsqueda en el texto mediante expresiones regulares y los patrones no cambian tanto entre las que usemos aquí y las que usaremos en el código de C# (hay algunos cambios pero son mínimos). Para las palabras de prueba, copie la lista de las 1000 palabras básicas de Wikcionario, solo las pase a notepad++, pero puedes descargarlas de mi dropbox en un archivo de texto plano.

Con todo lo anterior, vamos a ver ejemplos. Supongamos que de la lista de palabras, solo queremos  buscar aquellas que comiencen con la letra "a". Para eso podemos crear un patrón cómo el que sigue:

\ba\w+\b

La expresión anterior buscará solo las palabras completas que comiencen con la letra "a", al buscar el patrón anterior en notepad++ y contar los resultados, tendremos algo cómo lo que sigue:

El patrón funciona de la siguiente manera:

  • \b - hace la búsqueda de "finales de palabra", puede ser puntos, nuevas lineas o cualquier símbolo que no se una letra.
  • a - es la letra a.
  • \w+ - indica que se vale cualquier carácter, el símbolo + indica que se puede repetir varias veces (no necesariamente la misma letra, sino el valor de carácter).
  • \b - hace la búsqueda de "finales de palabra", puede ser puntos, nuevas lineas o cualquier símbolo que no se una letra.
Cabe mencionar que solo se buscaran las palabras que exactamente comiencen con la letra "a" por lo que si en la lista están las palabras: Aburrido y aburrido; solamente se contará aburrido. Las expresiones regulares son sensibles a mayúsculas y minúsculas (dependiendo del motor de las expresiones regulares, se puede establecer un flag para ignorarlo, pero siempre es recomendable crear un patrón que sea preciso).

Ahora supongamos que ademas de que queremos buscar las palabras que empiezan con "a", queremos que solo cuente aquellas que también terminan con la letra "n". Podemos crear un patrón cómo el que sigue:

\ba\w+n\b

Cómo podrás ver  simplemente agregamos justo antes de "\b" una letra "n", al ejecutar la búsqueda en notepad++, veremos algo cómo lo que sigue:

En el idioma español, es muy poco común oír palabras con las letras x,y y z. Supongamos que queremos buscar todas las palabras que incluyan esas letras; podemos crear un patrón cómo el que sigue:

\b\w+[xyz]\w+\b

Al implementarlo, verás que palabras cómo arroz o zanahoria no las cuenta, esto se debe a que "\w+" indica que debe de haber alguna letra antes Y después de cualquiera de las letras que esta en el grupo (X, Y y Z) , podemos tratar de arreglarlo con la siguiente:

\b[xyz]\b

Pero lejos de acercarnos al resultado, lo empeora ya que el patrón anterior solo buscara exactamente cada letra que este en medio de dos corchetes, espacios en blanco o cualquier cosa que indique final de palabra.


 Para poder plantear un poco mejor que es lo que queremos buscar, podemos plantearlo de la siguiente manera: "busca palabras que contengan las letras X, Y o Z, sea al inicio o en medio o al final de la palabra". Aquí la clave es el comparador "o" en las expresiones regulares, hay patrones que sirven cómo un "or" lógico en programación, eso quiere decir que podemos definir mas de un patron de busqueda simultaneo. Ahora probemos con la siguiente expresión regular:

\b(\w+[xyz]|\w+[xyz]\w+|[xyz]\w+)\b

Al ejecutarlo veremos que palabras cómo "yegua" y "arroz" son encontradas. La explicación del patrón anterior es simple:
  • ( -inicio del grupo
    • \b - hace la búsqueda de "finales de palabra", puede ser puntos, nuevas lineas o cualquier símbolo que no se una letra.
    • \w+[xyz] - Buscará las palabras que tengan al final x, y o z.
    • | - Alternación, buscará el patrón que este antes o después.
    • \w+[xyz]\w+ - Buscará las palabras que tengan entre otras las letras x, y o z
    • | - Alternación, buscará el patrón que este antes o después.
    • [xyz]\w+ - Buscará las palabras que empiecen con x, y o z y que tengan más letras despues de estas.
    • \b - hace la búsqueda de "finales de palabra", puede ser puntos, nuevas lineas o cualquier símbolo que no se una letra.
  • ) - fin del grupo.
Con lo anterior, tendremos cómo resultado:

Otro caso es común, es buscar palabras con determinado número de letras, para este caso hay varias formas de hacerlo (incluso para la mayoría de los casos siempre hay mas de una forma de lograr el resultado). Supongamos que queremos listar todas las palabras que contienen exactamente 15 letras para eso disponemos de los siguiente patrones:

\b\w\w\w\w\w\w\w\w\w\w\w\w\w\w\w\b

o el siguiente

\b\w{15}\b

El segundo, es el mas optimo, hay que recordar que cuando se usan los corchetes, buscará de manera repetida el patrón que le antecede, el resultado:
Pero ademas se puede usar de tres maneras diferentes, la primera es la anterior, ahora supongamos para la segunda forma que queremos buscar todas las palabras que tengan diez o mas letras, podemos crear un patrón cómo el que sigue:

\b\w{10,}\b

El primer número indica que se deben de repetir en al menos diez veces el patron "\w", cómo no se indica el número maximo para las repeticiones, buscará todas las palabras sin importar el número de letras (a partir de 10). Al ejecutar la búsqueda, veremos algo cómo lo que sigue:

Ahora supongamos que solo queremos buscar las palabras que tengan de tres a cinco letras, podemos crear un patrón así:

\b\w{3,5}\b

Cuando usamos dos números entre los corchetes, indicaremos que queremos que busque tantas veces cómo el primer dígito lo indica, el patrón que le precede, pero ademas solo lo hará hasta el parámetro superior, en este caso solo se buscaran las palabras que contengan de tres a cinco letras, otro ejemplo de este, seria buscar palabras que tengan la misma letra o silaba repetida.

(co){2}

El ejemplo anterior solo encontrará la palabra cocodrilo, ya que se repite la silaba "co" dos veces.

Y bien, por ahora es todo, en el siguiente post continuaremos con mas ejemplos y después pasaremos a la parte que tiene que ver con VEncoder.

Los leo luego

Learning Machine #13 - Expresiones regulares.

Hola de nuevo a todos, el día de hoy veremos un poco acerca de las expresiones regulares. Imagina que haces alguna clase de programa que sirve de "GUI" para ffmpeg, haces una interfaz bonita y haces los controles para funciones que suenan genial... pero nunca implementaste.


Hace algunos años publiqué un programa llamado VEncoder 2, que en su tiempo funcionó, pero poco despues se volvio obsoleto debido a que ffmpeg sufrió cambios y por lo tanto VEncoder paso a ser obsoleto.

Para solucionar eso, decidí que ya era hora de empezar a actualizarlo y tratar de hacerlo a "prueba de tiempo", pero lo haré poco a poco.

Una de las funciones que estaban planeadas desde el inicio, era la de poder escribir a mano algunos parámetros adicionales o simplemente sobre-escribir los pararmetros y sus valores. Para poder realizar ese trabajo, debemos de encontrar una forma de encontrar exactamente cada valor, es decir si queremos cambiar la resolución, debemos de encontrar la forma de saber si ya hay o no este parámetro. En c# hay varias formas de lograrlo, pero creo que la mas adecuada es mediante el uso de expresiones regulares.

Expresiones regulares

Antes que nada, veamos que son las expresiones regulares.

En cómputo teórico y teoría de lenguajes formales una expresión regular, también conocida como regex,​ es una secuencia de caracteres que forma un patrón de búsqueda, principalmente utilizada para la búsqueda de patrones de cadenas de caracteres u operaciones de sustituciones. Por ejemplo, el grupo formado por las cadenas Handel, Händel y Haendel se describe con el patrón "H(a|ä|ae)ndel". La mayoría de las formalizaciones proporcionan los siguientes constructores: una expresión regular es una forma de representar los lenguajes regulares (finitos o infinitos) y se construye utilizando caracteres del alfabeto sobre el cual se define el lenguaje. Wikipedia/expresionRegular.
 Entonces para definir un patrón, disponemos de las siguientes:

  • Clases de caracteres
    • . - Cualquier carácter (menos el de nueva linea).
    • \w\d\s - palabra, dígito, espacio en blanco.
    • \W\D\S - NO palabra, NO dígito, No espacio en blanco.
    • [abc] - cualquier letra dentro del corchete.
    • [^abc] - Ninguna de las letras dentro del corchete.
    • [a-g] - carácter entre "a" y "g".
  • Anclas
    • ^abc$ - inicio/ fin de la palabra.
    • \b\B - final/NO final.
  • Agrupación.
    • (abc) - Agrupación.
    • \1 - referencia hacia atrás.
    • (?:abc) omitir grupo.
    • (?=abc) busqueda hacia adelante.
    • (?!abc) busqueda haci atras.
  • Alternación
    • a*a+a? - 0 o mas, 1 o mas, 0 o 1.
    • a{5}a{2,} - Exactamente 5,2 o mas.
    • a{1,3} - entre uno y tres.
    • a+?a{2,}? - Buscar el menor posible.
    • ab|cd - busca ab o cd.
Y bien, por ahora es todo, en el siguiente post veremos cómo hacer uso de las expresiones regulares en C# y ademas implementaremos la función que describi al inicio.

Los leo luego.

Vamos a platicar #9 - ¿Eres realmente tan listo?

Hola de nuevo a todos, el día de hoy vamos a platicar un poco.
Hace poco mientras platicaba con algunos amigos, uno de ellos expresó su malestar porque era incapaz de resolver el cubo de Rubik. Yo por motivos de la facultad me di a la tarea de aprender a armarlo para poder desarrollar algún algoritmo para que después la computadora lo resolviera. En su momento fallé de manera tan tremenda, que por un tiempo me obsesioné con resolverlo, poco tiempo después logre armarlo por mi cuenta, pero no de manera practica; por suerte mientras buscaba cosas relacionadas al cubo de Rubik, encontré algunos "métodos" para poder resolverlo de manera más sencilla (solo cómo dato; 20 es el número de dios para el cubo Rubik).

Todo bien hasta ahí, pero mi amigo me cuestiono, "si aprender a resolver el cubo usando los métodos que ya alguien creo, ¿igual cuenta?", lo que me llevo a pensar, hasta que punto es valido decir que aprendimos algo, si lo hicimos en base a los descubrimientos de alguien mas. Antes de que alguien me diga "esa es precisamente la base del progreso"; eso ya lo se, solo que hasta que punto es válido decir aprendí/entendí algo.

Esto me ha llevado a cuestionarme también a mi. hace poco jugué ajedrez contra un niño de seis años, a leguas se veía que es un prodigio; me ganó de manera tan avasallante que me hizo preguntarme que tan bueno soy para el ajedrez.

Desde ahí empecé a jugar mas seguido y poco a poco me di cuenta que probablemente no sea lo mio y aquí es donde todo se jode porque me crea un conflicto. Me gusta jugar al ajedrez, he empezado a leer los fundamentos, pero entre mas trato de aplicar la teoría, peor pierdo, cuando mi amigo me expreso su malestar por el cubo de Rubik, fue precisamente un día en que jugamos un mini-torneo (en el cual perdí a la primera) y me quejé, dije que era un asco, pero el dijo lo del cubo de Rubik solo para hacer la comparación.

Si bien yo le respondí que no viene al caso, el ajedrez es estrategia pura y me funcionaba mejor cuando no sabia nada a cuando intenté aprender algo, cosa que en el cubo de Rubik no aplica, una vez aprendiendo algún método, eventualmente llegará el momento en que lo harás de manera automática y si lo haces en el ajedrez, probablemente funcione los primero cuatro o cinco movimientos, pero después ya no.

El maestro @morsa dijo que hay que estudiar años para poder siquiera considerarse un novato, pero tras perder de forma tan miserable, me he dado cuenta que probablemente no se lo mio.

Y bien por ahora es todo, en el siguiente post vamos a comenzar con la programación. Si quieres jugar contra mi al ajedrez, suelo jugar por la noche en la pagina de CHESS.COM.

Los leo luego.

Feliz 2019

Hola de nuevo a todos, el día de hoy solo escribo para desearles un feliz año 2019, cómo de costumbre, el año que viene publicaré mas y mas post que espero que sean de utilidad para el que los lee.

Sin nada mas que decir, FELIZ 2019!!!
Los leo luego.

Vamos a programar #63 - Actualizando un gadget de windows 7 a windows 10 (parte 2).

Hola de nuevo a todos, el dia de hoy vamos a terminar de modificar el gadget de reloj para usarlo en windows 10. Retomando el post anteror, el dia de hoy vamos a hacer modificaciones al gadget para hacerlo un poco mas a nuestro gusto.

Cambiando la localización.

Usualmente cuando nos referimos a localización, hablamos del idioma en el que estará el programa. Por default, el gadget viene en ingles, pero si queremos cambiarlo al idioma español (o al que sea), para hacer los cambios, debemos de editar el archivo "localization.js" y cambiar todo el contenido por el siguiente (si quieres que el gadget esté en español):


console.log('loaded: localization.js');

var m = ['ENERO','FEBRERO','MARZO','ABRIL','MAYO','JUNIO','JULIO','AGOSTO','SEPTIEMBRE','OCTUBRE','NOVIEMBRE','DICIEMBRE'];
var d = ['DOMINGO','LUNES','MARTES','MIERCOLES','JUEVES','VIERNES','SABADO'];

Básicamente lo que hacemos es escribir los nombres para cada día de la semana y para cada mes, esto no tiene realmente nada de difícil, salvo la parte de escribir los nombres de los días en otros idiomas.

De 12 a 24 horas

Otro de las partes que se puede modificar del gadget, es cómo se muestra la hora, por default el reloj viene en formato de 12 horas, pero además no se muestran lo segundos.

Antes de continuar, hay que resaltar que actualizar la hora a cada segundo, puede traer algunos problemas de rendimiento en algunos equipos, en lo personal, no lo he notado en un equipo medianamente viejo y mucho menos se siente en equipos mas modernos, por lo que si experimentas problemas de rendimiento, debes de cambiar el código para que funcione mejor (o quitar el gadget).
Una vez hecha la advertencia anterior, veamos el código.

console.log('loaded: gadget.js');

var font;
var color;
var opacity;
// var negativeVerticalMargin;
// var negativeHorizontalMargin;

var toTime;
var toDate;
var toMonth;
var toDay;

var targetWidth;
var targetHeight;

function init() {
	console.log('[init]');

	System.Gadget.settingsUI = 'settings.html';
	System.Gadget.onSettingsClosed = function() {
		readSettings();
		createTextObjects();
		refresh(false);
	};

	readSettings();
	createTextObjects();
	refresh(true);
}

function readSettings() {
	console.log('[readSettings]');

	font = System.Gadget.Settings.readString('font') || 'Arial';
	color = System.Gadget.Settings.readString('color') || 'white';
	opacity = System.Gadget.Settings.read('opacity') || 30;
	// negativeVerticalMargin = System.Gadget.Settings.read('negativeVerticalMargin') || 0;
	// negativeHorizontalMargin = System.Gadget.Settings.read('negativeHorizontalMargin') || 0;

	console.log('    font: '+font);
	console.log('    color: '+color);
	console.log('    opacity: '+opacity);
}

function createTextObjects() {
	console.log('[createTextObjects]');

	var bg = document.getElementById('bg');
	bg.removeObjects();

	// 0's are dummy chars for initial text object alignment
	toTime = bg.addTextObject('00:00:00', font, 105, color, 0, 0);
	toDate = bg.addTextObject('0', font, 80, color, toTime.left, 90); //(toTime.top+toTime.height)*0.69);
	toMonth = bg.addTextObject('0', font, 45, color, toDate.left+toDate.width, 95);
	toDay = bg.addTextObject('0', font, 30, color, toDate.left+toDate.width, 135);

	//we really don't want to be changing the width of the gadget because of clock time
	//because gadgets are anchored on the desktop by the left side, and the content is right-aligned

	targetWidth = document.getElementsByTagName('body')[0].style.width = toTime.width;
	targetHeight = document.getElementsByTagName('body')[0].style.height = toDay.top + toDay.height;
}

function refresh(autoreload) {
	console.log('[refresh]');

	console.log('    font: '+font);
	console.log('    color: '+color);
	console.log('    opacity: '+opacity);


	var currentTime = System.Time.getLocalTime(System.Time.currentTimeZone);
	var currentDate = new Date(Date.parse(currentTime));

	var hours = currentDate.getHours();

	var minutes = currentDate.getMinutes();
	minutes = ((minutes < 10) ? ':0' : ':') + minutes;
// Comentar las siguientes dos lineas para no mostrar los segundos
	var seconds = currentDate.getSeconds();
	seconds = ((seconds < 10) ? ':0' : ':') + seconds;
//Quitar los comentarios de la siguiente linea y comentar la siguiente para no mostar los segundos
	//toTime.value = hours + minutes + " HR";
	toTime.value = hours + minutes + seconds;
	toTime.font = font;
	toTime.color = color;
	toTime.opacity = opacity;

	toTime.left = targetWidth - toTime.width;
	toTime.top = 0;


	toDate.value = currentDate.getDate();
	toDate.font = font;
	toDate.color = color;
	toDate.opacity = opacity;

	toDate.left = toTime.left;
	toDate.top = 90; // - negativeVerticalMargin;


	toMonth.value = m[currentDate.getMonth()];
	toMonth.font = font;
	toMonth.color = color;
	toMonth.opacity = opacity;

	toMonth.left = toDate.left + toDate.width;
	// goofly little formula here to goose the month riiiigh up to but not touching the time, for most fonts
	toMonth.top = toDate.top + 4 + (toDate.top * 0.05) ; //95;


	toDay.value = d[currentDate.getDay()];
	toDay.font = font;
	toDay.color = color;
	toDay.opacity = opacity;

	toDay.left = toMonth.left;
	toDay.top = toMonth.top + 41; //135;


	console.log('    width: '+toTime.width+', height: '+toDay.top + toDay.height);
	console.log('    toTime: x='+toTime.left+', y='+toTime.top+', h='+toTime.height+', w='+toTime.width);
	

	if (autoreload) {
		//cuando mostramos los segundos hay que actualizar cada segundo, pero si queremos actualizar cada minutos, debemos hacerlo cada 60
		//en lugar de tener 1*1000, debemos cambiar a 60*1000
		setTimeout(function() { refresh(true); }, 1*1000);
	}
}

Este código es el que vamos a sustituir en el archivo "gadget.js" que es el principal. Con lo anterior, hemos cambiado la presentación de 12 a 24 horas, el código viene comentado para que hagas los cambios que creas necesarios, si te gusta más el formato de 12 horas, bastará con que revises el código fuente del original y el que aquí vimos.

El gadget empaquetado y que luce cómo el de la imagen, lo puedes descargar de mi dropbox y probarlo.

Los leo y luego y feliz 2019!!

Vamos a programar #62 - Actualizando un gadget de windows 7 a windows 10.

Hola de nuevo a todos, el día de hoy vamos a ver cómo es "posible" actualizar un gadget de Windows 7 para poder usarse en Windows 10.
Cómo muchos sabrán, hago programas para computadora, la mayoría de ellos están diseñados para ejecutarse en computadoras con Windows, pero muy específicamente en computadoras con Windows 7 para abajo. Gracias a que en la mayoria de los programas se usa .NET que se encarga de hacer el trabajo sucio por nosotros si es necesario que la aplicación se ejecute en un sistema operativo superior; llámese windows 10. Pero hace unos días, recibí la solicitud de un programa; mejor dicho; una "app" de windows 10 (y no voy a entrar en detalles por ahora) por lo que me vi en la necesidad de actualizar mi computadora.


La solicitud llegó unos días después, justo cuando había logrado personalizar mi escritorio a mi gusto.
Para hacer memoria, los gadgets son "mini-aplicaciones" basados en html, javascript y varios controles activex mas, estos aparecieron  por primera vez en windows vista y su tiempo de vida solo se prolongo hasta windows 7 (8?), debido a que se encontraron fallas críticas que permitían de manera potencial, permitir el acceso al equipo.

Es posible utilizar los gadgets en windows 10, pero para lograr una experiencia similar a windows 7, es necesario modificarlos, en el caso muy particular del gadget que vamos a modificar, el principal problema eran las fuentes. Solo estaban disponibles las basicas de windows y a pesar de haber instalado varias más, ninguna de éstas se mostraba.

Antes de continuar, si quieres usar el gadget en windows 10, debes de buscar algún programa que permita hacerlo, por cuestiones de seguridad no lo voy a poner aqui, ya que cómo mencioné antes, hay vulnerabilidades que podrían ser explotadas por un tercero. Si aun así quieres continuar, puedes buscar en internet "windows 10 gadgets", luego debes de descargar la version original del gadget desde su sitio web , descargarlo e instalarlo (solo para darle el debido crédito a sus autores, ya que en teoría vamos a reemplazar muchos de los archivos originales, aunque según la licencia, podemos modificarlo a nuestro gusto si no es que todo).

Ahora que ya estan dichas todas las advertencias, procederemos a abrir el gadget con Winrar.
Y extraeremos todo el contenido en una carpeta, una vez hecho buscaremos la carpeta llamada "js" y con un bloc de notas (en mi caso notepad++), abriremos el archivo "font-picker.js" y reemplazaremos su contenido con el siguiente codigo:

/*
Based on Jonathan Abbott's Flip Calendar gadget
http://vistagadgets.spaces.live.com/
*/

console.log('loaded: font-picker.js');

var fontPicker = {
	supportedFontTypes: ' (truetype) ',
	fontsToSkip: ':Bookshelf Symbol 7:'+':MS Outlook:'+':MS Reference Specialty:'+':MT Extra:'+':Symbol:'+':Webdings:'+':Wingdings:'+':Wingdings 2:'+':Wingdings 3:',

	fillYield: 50,	// Yield back to IE every X ms

	fontSizer: null,
	fonttypeIndex: 0,
	currentFont: System.Gadget.document.parentWindow.font,
	selectingFont: -1,

	init: function() {
		console.log('[init]');

		// System.Gadget.onSettingsClosing = this.settingsClosing;

		divFonttype.style.fontFamily = divFonttype.innerText = this.currentFont;
		divFonttype.style.fontSize = '16px';

		this.fontSizer = document.createElement('<g:background />').addTextObject(this.currentFont, this.currentFont, 16, 'black', 0, 0);

		//remove focus from the 1st checkbox
		document.body.focus();
		
		//read the fonts from the registry and build the table
		this.buildList(this.readFonts());

		// setFont(currentFont, 0);
		// console.log(tdFonts);
		// console.log(this.fonttypeIndex);
		tdFonts.rows(this.fonttypeIndex).className = 'menuItemSelected';
	},

	settingsClosing: function(event) {
		console.log('[settingsClosing]');
		
		if (event.closeAction == event.Action.commit) {
			System.Gadget.Settings.writeString('font', divFonttype.innerText);
		}
		// event.cancel = false;
	},

	readFonts: function() {
		console.log('[readFonts]');

		var HKLM = 2147483649 ;
		var rPath = 'SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts';
	

		// connect to the registry
		var oSwbem = new ActiveXObject('WbemScripting.SwbemLocator');
		var oSvc = oSwbem.ConnectServer(null, 'root\\default');
		var oReg = oSvc.Get('StdRegProv');

		// enumerate the values 
		var oMethod = oReg.Methods_.Item('EnumValues');
		var oInParam = oMethod.InParameters.SpawnInstance_();
		oInParam.hDefKey = HKLM;
		oInParam.sSubKeyName = rPath;
		var oOutParam = oReg.ExecMethod_(oMethod.Name, oInParam);

		// get the values into an array
		var sNames = oOutParam.sNames.toArray();

		// sort it
		sNames.sort();

		return sNames;
	},

	buildList: function(listItems) {
		console.log('[buildList]');

		// try{
			var counter = new Date();

			while (listItems.length > 0 && (new Date())-counter<this.fillYield) {
				// is the font one we can use
				var fontType = ' ' + listItems[0].substring(listItems[0].lastIndexOf('(')).toLowerCase() + ' ';
				var fontName = listItems[0].substring(0, listItems[0].length - fontType.length + 1);

				if(this.supportedFontTypes.indexOf(fontType) > -1 && this.fontsToSkip.indexOf(':' + fontName + ':') == -1) {
					//check the height, to see if it's a font we can use
					this.fontSizer.font = this.fontSizer.value = fontName;
					this.fontSizer.height = 0;

					if (this.fontSizer.height != 0) {
						// console.log('Using font: '+fontName);

						var tRow = tdFonts.insertRow(tdFonts.rows.length);
						tRow.className = 'menuItem';
						
						var tCell = tRow.insertCell(0);
						tCell.innerText = fontName;
						tCell.style.fontFamily = fontName;
						tCell.style.fontSize = divFonttype.style.fontSize;

						if (fontName == this.currentFont) {
							this.fonttypeIndex = tdFonts.rows.length - 1;
							// tRow.className = 'menuItemSelected';
						}

					} else {
						// console.log('Cant use font: '+fontName);
					}
				}
				listItems.shift();
			}

			if (listItems.length > 0) {
				var context = this;
				setTimeout(function() { context.buildList(listItems); }, 20);
			}
		// } catch(err) {
			// console.log('buildList: '+err.name+' - '+err.message);
		// }
	},

	showFontMenu: function(id, x, y) {
		console.log('[showFontMenu]');

		if (id == -1) {
			if (fontMenu.style.display != 'none') {
				id = this.selectingFont;
			} else {
				return;
			}
		}
			
		fontMenu.style.display = (fontMenu.style.display == 'none') ? 'inline' : 'none';

		if (fontMenu.style.display == 'none') {
			console.log('[showFontMenu] hidden');

			switch (this.selectingFont) {
				case 0:
					fontDropPNG.src = 'images/dropmenu.png';
					break;
			}
			this.selectingFont = -1;
		} else {
			console.log('[showFontMenu] displayed');

			fontMenu.style.top = y;
			fontMenu.style.left = x;
			this.selectingFont = id;
			
			// console.log(tdFonts.rows(fonttypeIndex).className);
			// console.log(id);

			switch (id) {
				case 0:
					fontMenu.scrollTop = tdFonts.rows(this.fonttypeIndex).offsetTop - 65;
					tdFonts.rows(this.fonttypeIndex).className = 'menuItemSelected';
					break;
			}

			// console.log(tdFonts.rows(fonttypeIndex).className);
		}
	},

	setFont: function(index, viaKey) {
		console.log('[setFont] '+index+', '+viaKey);

		var sFont = tdFonts.rows(index).cells(0).innerText;

		var selecting = (viaKey != null) ? viaKey : this.selectingFont;
		switch (selecting) {
			case 0:
				divFonttype.style.fontFamily = sFont;
				divFonttype.innerText = sFont;
				tdFonts.rows(this.fonttypeIndex).className = 'menuItem';
				this.fonttypeIndex = index;
				tdFonts.rows(this.fonttypeIndex).className = 'menuItemSelected';
				break;
		}

		// adjust the scrollbar if needed
		if (fontMenu.scrollTop > tdFonts.rows(index).offsetTop) {
			fontMenu.scrollTop = tdFonts.rows(index).offsetTop;
		}

		if (fontMenu.scrollTop < tdFonts.rows(index).offsetTop+tdFonts.rows(index).offsetHeight-150) {
			fontMenu.scrollTop = tdFonts.rows(index).offsetTop+tdFonts.rows(index).offsetHeight-150;
		}
	},

	setFontStyle: function(i) {
		console.log('[setFontStyle]');

		switch (i) {
			case 0:
				divFonttype.style.fontWeight = divFonttypeBold.checked ? 'bold' : '';
				divFonttype.style.fontStyle = divFonttypeItalic.checked ? 'italic' : '';
				break;
		}
	},

	keyPress: function(i) {
		console.log('[keyPress]');

		switch (event.keyCode)
		{
			case 40:	//down arrow
				if (i == 0) {
					if (this.fonttypeIndex < tdFonts.rows.length-1) {
						this.setFont(this.fonttypeIndex+1, i);
					}
				}
				break;
			case 38:	//up arrow
				if (i == 0) {
					if (this.fonttypeIndex > 0) {
						this.setFont(this.fonttypeIndex-1, i);
					}
				}
				break;
			case 36:	//home
				this.setFont(0, i);
				break;
			case 35:	//end
				this.setFont(tdFonts.rows.length - 1, i);
				break;
		}
	},

	mouseOver: function(mouseEntered, id) {
		// console.log('[mouseOver]');

		var selected = (this.selectingFont == 0) ? this.fonttypeIndex : 0;

		if (mouseEntered) {
			id.className = (id.rowIndex == selected) ? 'menuItemHoverSelected' : 'menuItemHover';
		} else {
			id.className = (id.rowIndex == selected) ? 'menuItemSelected' : 'menuItem';
		}
	}
}

La modificaciones que hicimos en este archivo sirven para detectar todas las fuentes disponibles en Windows. Anteriormente, las fuentes se guardaban por default en la ruta "\Windows\fonts" y el "catalogo" de fuentes se guardaba en el registro en la siguiente ruta: "HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts\\", pero para Windows 10 (y no se si para el 8 tambien), se cambio la ubicacion real y la del catalogo; ahora en lugar de estar en "HKLM" (HKey Local Machine), paso a almacenarse en "HKCU" (HKey Current User). Para hacer uso de cada una de las llaves, debemos de acceder a ellas por su valor hexadecimal, tomemos cómo referencia la siguiente lista:
  • HKEY_CLASSES_ROOT = 2147483648 (0x80000000)
  • HKEY_CURRENT_USER = 2147483649 (0x80000001)
  • HKEY_LOCAL_MACHINE = 2147483650 (0x80000002)
  • HKEY_USERS = 2147483651 (0x80000003)
  • HKEY_CURRENT_CONFIG = 2147483653 (0x80000005)
  • HKEY_DYN_DATA = 2147483654 (0x80000006)
Si revisas detenidamente, verás que en el código javascript hay una variable llamada "HKLM" cuyo valor corresponde a la llave "HKey Local Machine" que su vez es el número 2147483650, entonces la manera fácil de cambiar la llave a la que vamos a acceder es simplemente cambiando el valor de ésta a la del valor de la llave de destino (en este caso 2147483649).

Una vez cambiado este valor, para volver a empaquetar el gadget, debemos de usar winrar y elegir en formato "zip" y en método de compresión "No comprimir". Al archivo resultante de la compresión, simplemente le cambiamos el nombre de tal modo que la extensión en lugar de ".zip", sea ".gadget"

Después para instalarlo, simplemente hacemos doble clic en el archivo y se instalará al repositorio de gadgets.

Y bien, por ahora es todo, el siguiente post veremos como crear una versión de acuerdo al idioma que elijas y cómo agregar un par de personalizaciones para que se adapte un poco a tu gusto.