Semáforo II

Semáforo II
En esta entrega vamos a crear el semáforo. Aunque no lo parezca, pero un semáforo a pesar de su simplicidad visual, tiene aspectos bastante complejos y sobre todo cuando tienen que estar sincronizados o comunicados con otros, existen muchas matemáticas detrás de los modelos de tráfico de las grandes ciudades.

Sin darle más complejidad que la que necesitamos para nuestro proyecto final, que es un sistema de control de semáforos en una avenida para poder cruzarla de una sola vez sin pararnos, le asignaremos las siguientes características:

  • Tiempo en verde
  • Tiempo en amarillo
  • Tiempos en rojo
  • Tiempo de retardo para sincronziación de semáforos
  • Estado en el que se encuentra
  • Función de funcionamiento en manual o automático
  • Función de inicio automático
  • Función de parpadeo

En el diseño XAML, creamos unos objetos rectangle y le incluimos los controles de usuario creados en el post Semáforo I. El cambio de color lo podiamos haber hecho con converter, con propiedades, pero esta vez he optado por hacerlo con estilos y DataTrigger. A cada objeto LEDLight (luz del semáforo) le asigno un estilo genérico con algún efecto de fundido para que parezca más real; luego este estilo es heredado por otros tres estilos, uno por cada color donde los DataTrigger hacen su trabajo comprobando el estado del semáforo y activando la propiedad LightIsEnabled a Truesi cumple con la condición del DataTrigger. (Código al final del post)

Luego en el código de clase, le añado un DispatcherTime que nos hará las funciones de temporizador, creamos unas cuantas propiedades de dependencia para cumplir con nuestras características.
Tiene un método privado que será llamado desde el temporizador y este asignará el estado al semáforo y como consecuencia activando el color correspondiente.

        void changingState()
        {
            // Retardo inicial
            if (DelayTime > 0)
            {
                timer.Interval = new TimeSpan(0, 0, (int)DelayTime);
                DelayTime = 0;
                return;
            }

            // Parpadeando
            if (IsFlashingAmberLight)
            {
                if (State.Equals(LEDTrafficLightState.Yellow))
                {
                    State = LEDTrafficLightState.None;
                    timer.Interval = new TimeSpan(0, 0, 0, 0, 500);
                }
                else
                {
                    State = LEDTrafficLightState.Yellow;
                    timer.Interval = new TimeSpan(0, 0, 0, 1);
                }

                return;
            }

            // Cambio de color
            switch (State)
            {
                case LEDTrafficLightState.Red:
                    // SEMÁFORO EN VERDE
                    State = LEDTrafficLightState.Green;
                    
                    timer.Interval = new TimeSpan(0, 0, GreenTime);
                    break;
                case LEDTrafficLightState.Yellow:
                    // SEMÁFORO EN VERDE
                    State = LEDTrafficLightState.Red;
                    timer.Interval = new TimeSpan(0, 0, RedTime);
                    break;
                case LEDTrafficLightState.Green:
                    // SEMÁFORO EN AMBAR
                    State = LEDTrafficLightState.Yellow;
                    timer.Interval = new TimeSpan(0, 0, YellowTime);
                    break;
                default:
                    break;
            }
        }

y este el resultado instanciando varios semáforos en una ventana

            <controls:LEDTrafficLight x:Name="LEDTL1" Width="100" Height="300" GreenTime="4" RedTime="4" DelayTime="1" Margin="10"/>
            <controls:LEDTrafficLight x:Name="LEDTL2" Width="100" Height="300" GreenTime="5" RedTime="4" DelayTime="2" Margin="10"/>
            <controls:LEDTrafficLight x:Name="LEDTL3" Width="100" Height="300" GreenTime="5" RedTime="5" DelayTime="3" Margin="10"/>
            <controls:LEDTrafficLight x:Name="LEDTL4" Width="100" Height="300" GreenTime="5" RedTime="5" IsFlashingAmberLight="True" Margin="10"/>  

Video7
La próxima entrega, el sistema de control de semáforos

<UserControl x:Class="Controls.LEDTrafficLight"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:controls="clr-namespace:Controls"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="100">
    <Grid x:Name="grid">
        <Grid.Resources>
            <Style x:Key="LightStyle" TargetType="controls:LEDLight">
                <Setter Property="Margin" Value="5"/>
                <Style.Triggers>
                    <Trigger Property="LightIsEnabled" Value="True">
                        <Trigger.ExitActions>
                            <BeginStoryboard>
                                <Storyboard>
                                    <DoubleAnimation
                  Storyboard.TargetProperty="Opacity"
                  From="0" To="1" Duration="0:0:0.2" />
                                </Storyboard>
                            </BeginStoryboard>
                        </Trigger.ExitActions>
                        <Trigger.EnterActions>
                            <BeginStoryboard>
                                <Storyboard>
                                    <DoubleAnimation
                  Storyboard.TargetProperty="Opacity"
                  From="0" To="1" Duration="0:0:0.1" />
                                </Storyboard>
                            </BeginStoryboard>
                        </Trigger.EnterActions>
                    </Trigger>
                </Style.Triggers>
            </Style>
            <Style x:Key="RedLightStyle" TargetType="controls:LEDLight" BasedOn="{StaticResource LightStyle}">
                <Style.Triggers>
                    <DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=controls:LEDTrafficLight},Path=State }" Value="{x:Static controls:LEDTrafficLight+LEDTrafficLightState.Red}">
                        <Setter Property="LightIsEnabled" Value="True"/>
                    </DataTrigger>

                </Style.Triggers>
            </Style>
            <Style x:Key="YellowLightStyle" TargetType="controls:LEDLight" BasedOn="{StaticResource LightStyle}">
                <Style.Triggers>
                    <DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=controls:LEDTrafficLight},Path=State }" Value="{x:Static controls:LEDTrafficLight+LEDTrafficLightState.Yellow}">
                        <Setter Property="LightIsEnabled" Value="True"/>
                    </DataTrigger>

                </Style.Triggers>
            </Style>
            <Style x:Key="GreenLightStyle" TargetType="controls:LEDLight" BasedOn="{StaticResource LightStyle}">
                <Style.Triggers>
                    <DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=controls:LEDTrafficLight},Path=State }" Value="{x:Static controls:LEDTrafficLight+LEDTrafficLightState.Green}">
                        <Setter Property="LightIsEnabled" Value="True"/>
                    </DataTrigger>

                </Style.Triggers>
            </Style>
            <Style x:Key="rectangleTraficLight" TargetType="Border">
                <Setter Property="Width" Value="{Binding ElementName=redEllipse, Path=ActualWidth}"/>
                <Setter Property="Height" Value="5"/>
                <Setter Property="Background" Value="Gray"/>
                <Setter Property="Margin" Value="1"/>
                <Setter Property="VerticalAlignment" Value="Top"/>
                <Setter Property="CornerRadius" Value="3"/>
            </Style>
            <Style x:Key="borderColor" TargetType="Border">
                <Setter Property="BorderBrush" Value="Gray"/>
                <Setter Property="BorderThickness" Value="2"/>
                <Setter Property="Margin" Value="2,0,2,2"/>
                <Setter Property="CornerRadius" Value="3"/>
            </Style>
        </Grid.Resources>
        <Grid.RowDefinitions>
            <RowDefinition Height="100*"/>
            <RowDefinition Height="100*"/>
            <RowDefinition Height="100*"/>
        </Grid.RowDefinitions>
        <Border Grid.RowSpan="3" BorderBrush="DarkGreen" BorderThickness="5" Background="DarkGreen" Opacity=".5" CornerRadius="5"/>
        <Border Style="{StaticResource borderColor}" Grid.Row="0">
            <controls:LEDLight x:Name="RedLight" LEDLightBackgroundON="Red" Style="{StaticResource RedLightStyle}"/>
        </Border>
        <Border Style="{StaticResource borderColor}" Grid.Row="1">
            <controls:LEDLight x:Name="YellowLight"  LEDLightBackgroundON="Gold" Style="{StaticResource YellowLightStyle}"/>
        </Border>
        <Border Style="{StaticResource borderColor}" Grid.Row="2">
            <controls:LEDLight x:Name="GreenLight" Style="{StaticResource GreenLightStyle}"/>
        </Border>
    </Grid>
</UserControl>
namespace Controls
{
    /// <summary>
    /// Lógica de interacción para LEDTrafficLight.xaml
    /// </summary>
    public partial class LEDTrafficLight : UserControl
    {
        #region Constructor

        public LEDTrafficLight()
        {
            InitializeComponent();
            this.DataContext = this;
            timer.Tick += Timer_Tick;
            timer.IsEnabled = StartAuto;
        }

        #endregion

        #region Enum

        public enum LEDTrafficLightState
        {
            Red,
            Yellow,
            Green,
            None
        }

        #endregion

        #region DependencyProperties

        public int GreenTime
        {
            get { return (int)GetValue(GreenTimeProperty); }
            set
            {
                value = value > YellowTime ? value - YellowTime : YellowTime + 1;
                SetValue(GreenTimeProperty, value);
            }
        }

        public int RedTime
        {
            get { return (int)GetValue(RedTimeProperty); }
            set
            {
                if (value <= 0) value = 1;
                SetValue(RedTimeProperty, value);
            }
        }

        public int YellowTime
        {
            get { return (int)GetValue(YellowTimeProperty); }
            set { SetValue(YellowTimeProperty, value); }
        }

        public int DelayTime
        {
            get { return (int)GetValue(TimeProperty); }
            set { SetValue(TimeProperty, value); }
        }

        public bool IsFlashingAmberLight
        {
            get { return (bool)GetValue(IsFlickingProperty); }
            set
            {
                SetValue(IsFlickingProperty, value);
                State = LEDTrafficLightState.Yellow;
                // en 100 ms cambia a Parpadeo
                timer.Interval = new TimeSpan(0, 0, 0, 0, 100);
            }
        }

        public bool IsManual
        {
            get { return (bool)GetValue(IsManualProperty); }
            set
            {
                SetValue(IsManualProperty, value);
                timer.IsEnabled = !value;
            }
        }

        public bool StartAuto
        {
            get { return (bool)GetValue(StarAutoProperty); }
            set
            {
                SetValue(StarAutoProperty, value);
                timer.IsEnabled = value;
            }
        }

        public LEDTrafficLightState State
        {
            get { return (LEDTrafficLightState)GetValue(StateProperty); }
            set { SetValue(StateProperty, value); }
        }

        #region DependecyProperties Register

        // Using a DependencyProperty as the backing store for StarAuto.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty StarAutoProperty =
            DependencyProperty.Register("StartAuto", typeof(bool), typeof(LEDTrafficLight), new PropertyMetadata(true));

        // Using a DependencyProperty as the backing store for IsManual.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty IsManualProperty =
            DependencyProperty.Register("IsManual", typeof(bool), typeof(LEDTrafficLight), new PropertyMetadata(false));

        // Using a DependencyProperty as the backing store for IsFlicking.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty IsFlickingProperty =
            DependencyProperty.Register("IsFlashingAmberLight", typeof(bool), typeof(LEDTrafficLight), new PropertyMetadata(false));

        // Using a DependencyProperty as the backing store for State.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty StateProperty =
            DependencyProperty.Register("State", typeof(LEDTrafficLightState), typeof(LEDTrafficLight), new PropertyMetadata(LEDTrafficLightState.Red));

        // Using a DependencyProperty as the backing store for Time.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty TimeProperty =
            DependencyProperty.Register("DelayTime", typeof(int), typeof(LEDTrafficLight), new PropertyMetadata(0));

        // Using a DependencyProperty as the backing store for TimeYellow.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty YellowTimeProperty =
            DependencyProperty.Register("YellowTime", typeof(int), typeof(LEDTrafficLight), new PropertyMetadata(2));

        // Using a DependencyProperty as the backing store for TimeRed.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty RedTimeProperty =
            DependencyProperty.Register("RedTime", typeof(int), typeof(LEDTrafficLight), new PropertyMetadata(7));

        // Using a DependencyProperty as the backing store for TimeGreen.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty GreenTimeProperty =
            DependencyProperty.Register("GreenTime", typeof(int), typeof(LEDTrafficLight), new PropertyMetadata(7));

        #endregion

        #endregion

        #region Fields & Const & Event

        DispatcherTimer timer = new DispatcherTimer();

        #endregion


        #region Publics Method

        /// <summary>
        /// Inicia el semáforo cuando se encuentra en modo automático
        /// </summary>
        public void Start()
        {
            if (IsManual) return;
            timer.Start();
        }

        /// <summary>
        /// Detiene el semáforo
        /// </summary>
        public void Stop()
        {
            timer.Stop();
        }

        /// <summary>
        /// Cambia el semáforo a rojo
        /// </summary>
        public void OnRed()
        {
            State = LEDTrafficLightState.Red;
        }

        /// <summary>
        /// Cambia el semáforo a verde
        /// </summary>
        public void OnGreen()
        {
            State = LEDTrafficLightState.Green;
        }

        /// <summary>
        /// Cambia el semáforo a amarillo
        /// </summary>
        public void OnYellow()
        {
            State = LEDTrafficLightState.Yellow;
        }

        public void Test()
        {
            throw new NotImplementedException();
        }

        #endregion

        #region Privates methods

        /// <summary>
        /// Cambia el estado del semáforo según el estado anterior
        /// </summary>
        void changingState()
        {
            // Retardo inicial
            if (DelayTime > 0)
            {
                timer.Interval = new TimeSpan(0, 0, (int)DelayTime);
                DelayTime = 0;
                return;
            }

            // Parpadeando
            if (IsFlashingAmberLight)
            {
                if (State.Equals(LEDTrafficLightState.Yellow))
                {
                    State = LEDTrafficLightState.None;
                    timer.Interval = new TimeSpan(0, 0, 0, 0, 500);
                }
                else
                {
                    State = LEDTrafficLightState.Yellow;
                    timer.Interval = new TimeSpan(0, 0, 0, 1);
                }

                return;
            }

            // Cambio de color
            switch (State)
            {
                case LEDTrafficLightState.Red:
                    // SEMÁFORO EN VERDE
                    State = LEDTrafficLightState.Green;
                    
                    timer.Interval = new TimeSpan(0, 0, GreenTime);
                    break;
                case LEDTrafficLightState.Yellow:
                    // SEMÁFORO EN VERDE
                    State = LEDTrafficLightState.Red;
                    timer.Interval = new TimeSpan(0, 0, RedTime);
                    break;
                case LEDTrafficLightState.Green:
                    // SEMÁFORO EN AMBAR
                    State = LEDTrafficLightState.Yellow;
                    timer.Interval = new TimeSpan(0, 0, YellowTime);
                    break;
                default:
                    break;
            }

        }

        private void Timer_Tick(object sender, EventArgs e)
        {
            changingState();
        }

        #endregion
    }
}

Semáforo I

Semáforo I

Vamos a crear un sistema de control de semáforos. Un sistema de control de tráfico, debe coordinar semáforos conforme a la situación geográfica, prioridad de paso, alta circulación y sinfín de factores, en este caso vamos a crear uno sencillo que nos permita cruzar una avenida de una sola vez. Para ello debemos tener un sistema de control y semáforos lógicamente. La mayoría de los semáforos, como de todos es bien sabido, tienen 3 luces, una roja, otra ámbar y otra verde, algunos además tienen control de peatones e indicaciones auxiliares, pero nosotros vamos a crear uno sencillo, con sus tres luces y más adelante ya veremos si ampliamos sus funcionalidades.

Su funcionamiento básico es verde, pasa a amarillo esperando durante unos segundos pasando a rojo finalizando el bucle, vuelve al verde y comienza de nuevo. También puede estar parpadeando.

Pues empezando por lo básico, vamos a crear las luces y como estamos en 2017, vamos a crearlos de LED,s, ya que cambiar el color y añadir un degradado radial no es muy complicado, lo vamos a hacer un poco más difícil o más fácil según se vea.

He usado WPF para crear un control de usuario y dentro de este, he añadido un grid donde voy a distribuir los LED,s dándole un aspecto como el siguiente (el código lo adjunto al final del post)
semaforo01

Además, le he agregado algunas propiedades de dependencia en las que podamos cambiar el color de los LED cuando está encendida la luz, (en este caso yo las he puesto blancas), el color del LED cuando está apagada y el color de fondo cuando están de igual modo apagadas o encendidas. A continuación el código

        #region DependecyProperties

        public bool LightIsEnabled
        {
            get { return (bool)GetValue(LightIsEnabledProperty); }
            set { SetValue(LightIsEnabledProperty, value); }
        }

        public Brush LEDColorON
        {
            get { return (Brush)GetValue(LEDColorONProperty); }
            set { SetValue(LEDColorONProperty, value); }
        }

        public Brush LEDColorOFF
        {
            get { return (Brush)GetValue(LEDColorOFFProperty); }
            set { SetValue(LEDColorOFFProperty, value); }
        }

        public Brush LEDLightBackgroundOFF
        {
            get { return (Brush)GetValue(LEDLightBackgroundProperty); }
            set { SetValue(LEDLightBackgroundProperty, value); }
        }

        public Brush LEDLightBackgroundON
        {
            get { return (Brush)GetValue(LEDLightBackgroundONProperty); }
            set { SetValue(LEDLightBackgroundONProperty, value); }
        }

        // Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty LEDLightBackgroundONProperty =
            DependencyProperty.Register("LEDLightBackgroundON", typeof(Brush), typeof(LEDLight), new PropertyMetadata(Brushes.Lime));

        // Using a DependencyProperty as the backing store for LEDLightBackground.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty LEDLightBackgroundProperty =
            DependencyProperty.Register("LEDLightBackgroundOFF", typeof(Brush), typeof(LEDLight), new PropertyMetadata(Brushes.Silver));

        // Using a DependencyProperty as the backing store for LEDColorOFF.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty LEDColorOFFProperty =
            DependencyProperty.Register("LEDColorOFF", typeof(Brush), typeof(LEDLight), new PropertyMetadata(Brushes.LightGray));

        // Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty LEDColorONProperty =
            DependencyProperty.Register("LEDColorON", typeof(Brush), typeof(LEDLight), new PropertyMetadata(Brushes.White));

        // Using a DependencyProperty as the backing store for ON.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty LightIsEnabledProperty =
            DependencyProperty.Register("LightIsEnabled", typeof(bool), typeof(LEDLight), new PropertyMetadata(false));

        #endregion

Y ¿como se enciende y se apaga? He incluido un par de DataTrigger que comprueban si el valor de la propiedad LightIsEnabled es verdadero y si es así asigna el valor de la propiedad LEDColorON a cada LED y el valor de la propiedad LEDLightBackgroundON al relleno de la ellipse interna. Si al crear una instancia de este, cambiamos esta propiedad, la luz se encenderá o se apagará.
Ahora solo nos queda crear una instancia desde un formulario y ver si funciona

Video5.gif
Fondo de color y LED,s blancos

Video6.gif

Fondo y LED,s de color

y las instancias desde XAML, quedarían así:

            
            
                        

y el código del control de usuario. En la próxima entrega el semáforo.




<!--ESTILO DE LED-->













<!--ESTILO DEL CIRCULO CENTRAL EXTERIOR-->





<!--ESTILO DEL FONDO DEL CIRCULO INTERIOR-->




































































<!--Ellipse central. INTERIOR-->


<!--Linea 0-->






<!--Linea 1-->









<!--Linea 2-->










<!--Linea 3-->











<!--Linea 4-->












<!--Linea 5-->













<!--Linea 6-->












<!--Linea 7-->













<!--Linea 8-->












<!--Linea 9-->











<!--Linea 10-->










<!--Linea 11-->









<!--Linea 12-->






<!--Ellipse central. EXTERIOR-->



ForEach

ForEach

Una colección que implementa la interfaz IEnumerable contiene un método GetEnumerator y este a su vez implementa la interfaz IEnumerator que contiene la propiedad Current y los métodos MoveNext y Reset. Esto va a permitir a la instrucción foreach iterar por la colección.

Así, si generamos una colección de 20.000.000 de números enteros y queremos efectuar una operación por cada uno de ellos, en este caso el inverso del producto de la raiz cuadrada por el logaritmo neperiano y luego realizar la suma sobre cada resultado, podemos iterar con una instrucción foreach,

static int n = 20000000;
static List list = Enumerable.Range(1, n).ToList();

static double forEachStandar()
        {
            var result = 0.0;

            foreach (var item in list)
            {
                result += 1 / Math.Sqrt(item) * Math.Log(item);
            }
            return result;
        }

Una colección además contiene un método ForEach, el cual es accesible mediante una expresión Lambda, asignando la acción que vamos a realizar en cada uno de los elementos.

        static double forEachLambda()
        {
            var result = 0.0;
            list.ForEach(n => result += 1 / Math.Sqrt(n) * Math.Log(n));
            return result;
        }

Podemos aprovechar al máximo los recursos disponibles y es iterando en paralelo por los elementos de la colección pudiendo realizar cálculos simultáneos. Este medio debe ser tratado con cuidado, puesto que si el orden interviene en el resultado final, esta herramienta no sería útil, y en todo caso, los resultados no serían los esperados; así mismo, siempre hay que replantearse el uso en paralelo. Para usarlo, podemos hacerlo mediante la clase Parallel
Parallel.ForEach(list, k => result += 1 / Math.Sqrt(n) * Math.Log(n));
o mediante el método AsParallel de la colección.

foreach (var item in list.AsParallel())
{
result += 1 / Math.Sqrt(item) * Math.Log(item);
}

Aprovechando el artículo sobre LINQ publicado en Wakicode, es posible efectuar cálculos sobre elementos de la colección mediante sintáxis de consulta en Linq. En el siquiente ejemplo, efectuamos el cálculo sobre cada uno de los elementos asignádolo a la variable sum y posteriormente efectuar la suma por cada uno de los elementos

static double queryLinq()
        {
            var query = (from u in list
                        select new { sum = 1 / Math.Sqrt(u) * Math.Log(u) }).Sum(k=>k.sum);

            return query;
        }

pero existe un método mejor y es que toda colección contiene el método Sum, y podremos entonces hacer lo siguiente

        static double collectionSum()
        {
            return list.Sum(k => 1 / Math.Sqrt(k) * Math.Log(k));
        }

Bien, todavía habría alguna otra forma de iterar sobre colecciones, pero con estas nos basta y ahora vamos a ver como se comportan en base a su rendimiento y para ello he creado cuatro hilos, de los cuales cada uno generará la suma de los elementos calculados de la colección, pero cada uno con su método y una vez que terminen todos, que nos muestre el tiempo invertido en los cuatro procesos y cual ha sido el más eficiente. Lanzaremos los procesos por este orden, foreach estándar, expresión foreach lambda, Linq sintáxis de consulta y por último la suma directa sobre la colección mediante expresión lambda.

        static void inBackGround()
        {
            string text = "";
            List cronos = new List();

            Stopwatch stw = new Stopwatch();
            Stopwatch stw1 = new Stopwatch();
            Stopwatch stw2 = new Stopwatch();
            Stopwatch stw3 = new Stopwatch();
            Stopwatch stw4 = new Stopwatch();

            cronos.Add(new KeyValuePair(stw1, "For Each Estandar"));
            cronos.Add(new KeyValuePair(stw2, "For Each Lambda"));
            cronos.Add(new KeyValuePair(stw2, "Query LINQ"));
            cronos.Add(new KeyValuePair(stw2, "Colección.Sum()"));

            stw.Start();
            Task[] tasks;

            var t1 = Task.Factory.StartNew(() =>
            {
                stw1.Start();
                text = string.Format("For Each Estandar. n = {0}. Resultado = {1}. Tiempo del proceso: {2}", n, forEachStandar(), stw1.Elapsed);
                stw1.Stop();
                Console.WriteLine(text);
            });

            var t2 = Task.Factory.StartNew(() =>
            {
                stw2.Start();
                text = string.Format("For Each Lambda. n = {0}. Resultado = {1}. Tiempo del proceso: {2}", n, forEachLambda(), stw2.Elapsed);
                stw2.Stop();
                Console.WriteLine(text);
            });

            var t3 = Task.Factory.StartNew(() =>
            {
                stw3.Start();
                text = string.Format("Query LINQ. n = {0}. Resultado = {1}. Tiempo del proceso: {2}", n, queryLinq(), stw3.Elapsed);
                stw3.Stop();
                Console.WriteLine(text);
            });

            var t4 = Task.Factory.StartNew(() =>
            {
                stw4.Start();
                text = string.Format("Colección.Sum(). n = {0}. Resultado = {1}. Tiempo del proceso: {2}", n, collectionSum(), stw4.Elapsed);
                stw4.Stop();
                Console.WriteLine(text);
            });

            tasks = new Task[] { t1, t2, t3, t4 };

            Task.WaitAll(tasks);
            Console.WriteLine(string.Format("Los cuatro procesos han terminado. Tiempo: {0}",stw.Elapsed));
            var theBest = cronos.Where(k => k.Key.Elapsed == cronos.Min(m => m.Key.Elapsed)).FirstOrDefault();
            Console.WriteLine(string.Format("El más efectivo ha sido {0} con un tiempo de {1}",theBest.Value, theBest.Key.Elapsed));

            Console.Read();

        }

y ahora el resultado en el que el ganador en cuanto a rendimiento es el método For Each Lambda por muy poco sobre la suma sobre la colección, el foreach estándar y el menos eficiente la consulta Linq.

foreach.png
El fin del artículo, para un problema podemos usar distintos métodos que producen el mismo efecto o resultado, pero debemos escoger el mejor de ellos en cuanto a eficiencia y rendimiento. Además, necesitamos conocer todos los métodos, no podemos escoger o seleccionar si no tenemos o no conocemos opciones.

Hilando

Hilando

Desde .NET 4, los hilos en C# se convirtieron en una tarea de lo más fácil. En este post vamos a realizar una par de muestras, una prueba sencilla donde vamos a ejecutar una tarea que usa mucho tiempo en terminarse como es la obtención de π(x) o la cantidad de números primos menores que un número.Vamos a usar una función lambda para obtener el codiciado π(x) y para ello vamos a usar la clase Enumerable del espacio System.Linq y un método llamado Range para obtener un conjunto de números enteros entre dos rangos numerable.Range(2, max), contamos los elementos con el predicado con el que obtengamos todos los números entre el rango de 2 y la raiz del número Count(n => Enumerable.Range(2, (int)Math.Sqrt(n) - 1) que cumplan la condición de tener un módulo mayor que 0 All(i => n % i > 0))o lo que es lo mismo todos los números que no son múltiplos.

Enumerable.Range(2, max).Count(n => Enumerable.Range(2, (int)Math.Sqrt(n) - 1).All(i => n % i > 0))

Linq es bestial, ¿no?
Si le damos un valor alto a la variable max com por ejemplo 10.000.000, la tarea dejará la interfaz bloqueada y por tanto tendremos que crear un hilo para evitar este problema y aquí viene lo bueno de la clase Task.

Esta clase tiene un método Factory.StartNew al que se le pasa un método como delegado, el cual se ejecutará de forma asincrónica devolviendo la tarea iniciada, es decir no devuelve un valor sino la tarea Task en si con sus propiedades y métodos.

Task.Factory.StartNew(() => TareaMuyCostosa());
En nuestro código vamos a ver solo dos aspectos de los muchos de los que podemos extraerle a Task, uno es la propiedad IsCompleted y otra sería el método Wait.
Nuestra aplicación como he dicho, generará números primos en una tarea y mientras que no esté finalizada, mostrará un mensaje indicando el tiempo empleado y una vez que finalice, mostrará el resultado que lo mostraremos de dos modos, uno esperando a que finalice y otro con el método ContinueWith.

La primera

static void primeNumbersCount(int max)
        {
            Stopwatch stw = new Stopwatch();
            stw.Start();
            int sec = 0;
            var piXTask = Task.Factory.StartNew(() => Enumerable.Range(2, max).Count(n => Enumerable.Range(2, (int)Math.Sqrt(n) - 1).All(i => n % i > 0)));

            var calculando = "Calculando ";
            while (!piXTask.IsCompleted || piXTask.IsFaulted || piXTask.IsCanceled)
            {
                Thread.Sleep(1000);
                Console.Clear();
                Console.WriteLine(calculando.PadRight(sec % 5 + 11,'.'));
                Console.WriteLine(string.Format("Tiempo empleado: {0} sengundos", sec++));
            }

            piXTask.Wait();

            string text = string.Format("Calculo de {0} números primos menores de {1}", piXTask.Result, max);
            Console.WriteLine(text);
            Console.WriteLine(string.Format("Tiempo empleado: {0}", stw.Elapsed));
            Console.Read();
        }

En este código, vemos que se lanza la tarea, luego pasamos a un bucle del que debemos asegurarnos que saldremos algún día (esta opción no la aconsejo, pero para mostrar que la app se queda en sus cosas mientras la tarea se está realizando, está bien), mediante la propiedad IsCompleted, IsFaulted o IsCanceled, es decir cuando pase alguna de estas cosas, sale del bucle.
Luego pasamos al método Wait, que lo que hace es lo mismo que el bucle, hasta que la tarea no haya finalizado, espera y no sobrepasa a la siguiente línea de código. Una vez acabado, muestra el resultado con la propiedad Result del tipo int, porque es el resultado que entrega el delegado que lanzó. Y el resultadotask03task01

Pero para mi, en este caso usaría lo mismo pero con el método ContinueWith.

static void primeNumbersCount(int max)
        {
            Stopwatch stw = new Stopwatch();
            stw.Start();
            int sec = 0;
            var piXTask = Task.Factory.StartNew(() => Enumerable.Range(2, max).Count(n => Enumerable.Range(2, (int)Math.Sqrt(n) - 1).All(i => n % i > 0)));
            var calculando = "calculando ";
            piXTask.ContinueWith((p) =>
            {
                string text = string.Format("Calculo de {0} números primos menores de {1}", piXTask.Result, max);
                Console.WriteLine(text); Console.WriteLine(string.Format("Tiempo empleado: {0}", stw.Elapsed));
            });
            Console.WriteLine("Esto no ha terminado...");
            Thread.Sleep(1000); sec++;
            while (!piXTask.IsCompleted)
            {
                Thread.Sleep(1000); Console.Clear();
                Console.WriteLine(calculando.PadRight(sec % 5 + 11, '.'));
                Console.WriteLine(string.Format("Tiempo empleado: {0} segundos", sec++));
            }
          }

Aquí podemos apreciar que se lanza la tarea, vemos el método ContinueWith que contiene código en su interior mediante otro delegado y posteriormente el código ese que hemos creado para mostrar cosas mientras la tarea se realiza. Al ejecutarlo, se lanza la tarea y se obvia el código del método ContinueWith en cuestión, continua y pasa al bucle, una vez que ha terminado la tarea, continua con lo que le queda dentro del bloque ContinueWith y se acabó, ¿fácil? facilísimo!!

Video4
Ya veremos en otra ocasión el uso de Arrays de Task, esperas, sincronías entre ellos y alguna otra cosa más.

Un poco de Linq

Un poco de Linq

LINQ en inglés es Language Integrated Query o lenguaje de consultas integrado. Lleva en la brecha desde 2007 con la versión 3.5 de .NET, por tanto no estoy descubriendo nada y para el que no lo conozca, con este post podrá introducirse e implementar sus posibilidades.

LINQ es un lenguaje parecido a SQL, es decir mediante una gramática concreta aplicada sobre un conjunto de datos, obtenemos un resultado que puede ser valores o un conjunto de resultados. Uno de los aspectos fundamentales de este lenguaje es que puede ser usado con cualquier colección de datos, ya sea un array, una lista de objetos, una cadena de texto (es un array), una colección, una clase enumerable, una base de datos, un documento XML, etc.

Primer ejemplo, vamos a enumerar los objetos tipo char de de una cadena de texto ya que un tipo string es lo mismo que array de objetos char; si la cadena de texto es “Wakicode” podríamos obtener los char del siguiente modo:

string cadena = "Wakicode";
var query = from u in cadena
select u;
// el resultado de query es un array de objetos char
// query = {'W', 'a', 'k', 'i', 'c', 'o', 'd', 'e'}

Vamos a repasar la gramática de esta consulta de LINQ. Comienza por from, asignamos la variable usada en la consulta, en este caso u, le decimos mediante in, donde vamos a efectuar la consulta y con select, la salida de la consulta que en este caso es la variable o lo que es lo mismo la variable u que es cada uno de los objetos de la cadena. Si le aplicamos una sentencia condicional, por ejemplo queremos obtener todas las vocales de nuestra cadena y para ello he creado una función que determine si un caracter es vocal y ahora agregamos el predicado where en la sentencia LINQ.

string cadena = "Wakicode";
var query = from u in cadena
where esVocal(u)
            select u;
bool esVocal(char c)
{
return "aeiouAEIOU".Contains(c);
}
// el resultado de query es un array de objetos char
// query = {'a','i','o','e'}

Vamos a crear una lista de enteros desde el valor 0 hasta el 100 y luego ejecutaremos consultas básicas sobre la colección. De momento vamos a obtener los múltiplos de 7.

List list = new List();
for (int i = 0; i < 100; i++) {
list.Add(i);
}
var query = from u in list
where u % 7 == 0
select u;
// query = {0,7,14,21,28,35,...91,98}

Por último, vamos a complicar la cosa efectuando consultas sobre objetos complejos y para ello creamos una clase persona con alguna propiedad, creamos una colección de esta clase y le agregamos algún objeto.

public class Persona
{
public string Name { get; set; }
public string City { get; set; }
public int Age { get; set; }
}

public class Main
{
public Main()
{
List people = new List();
// añadimos unos objetos

people.Add(new Persona() { Name = "Donald Trump", City = "Washington", Age = 56 });
people.Add(new Persona() { Name = "Tiriti Trump", City = "New York", Age = 45 });
people.Add(new Persona() { Name = "Michael Knight", City = "Washington", Age = 35 });
people.Add(new Persona() { Name = "Paul Churches", City = "Teheran", Age = 34 });
people.Add(new Persona() { Name = "John Charles Purse", City = "Valencia", Age = 25 });

var query = from u in people
where u.Name.Contains("Trump")
select u.Name;
// query = Donald Trump, Tiriti Trump

var query1 = from u in people
where u.City.Contains("Washington")
select u.Name;
// query1 = Donald Trump, Michael Knight

var query2 = from u in people
where u.Age < 40
select u.Name;
// query2 = Michael Knight, Paul Churches, John Charles Purse
}
}

Podemos apreciar que a la variable u le corresponde el valor de cada uno de los objetos de la lista en cada iteración, les aplica el filtro y se agregan a la variable si cumplen con la condición, pero se agregan según la clausula select. En el caso del ejemplo, seleccionamos la propiedad del tipo string Name, pero si hubieramos dejado select u, estariamos creando una consulta con objetos del tipo Persona pudiendo posteriormente usar alguna de las propiedades del objeto.

var query2 = from u in people
                         where u.Age < 40
                         select u;
            foreach (var item in query2)
            {
                Console.WriteLine(string.Format("Nombre: {0}, Edad: {1}", item.Name, item.Age));
            }
//Nombre: Michael Knight, Edad: 35
//Nombre: Paul Churches, Edad: 34
//Nombre: John Charles Purse, Edad: 25

Como ya comentamos al inicio, podemos usar Linq con cualquier colección y su uso con bases de datos es fantástico, de hecho existe una herramienta LinqToSql que genera las clases y propiedades (tablas y campos) de una base de datos automáticamente, de modo que podemos acceder a los datos de las tablas y sus relaciones de un modo rápido y eficaz.
Pd: Para el que quiera avanzar, puede hacerlo con el siguiente libro.

Controles de Usuario en WPF 2

Controles de Usuario en WPF 2
Continuando con los controles de usuario en WPF, voy a mostrarles un control un poco más complejo y en este caso se trata de un reloj un poco especial, se trata de un reloj binario.
Se trata de un reloj, pero los dígitos se muestran uno a uno en modo binario mediante una bola de indicador. Por ejemplo para indicar la hora 15, se muestra un 1 en binario 001 y un 5 en binario 101.He creado un control básico que hará las veces de indicador con un tono grisáceo cuando el indicador está apagado o es un cero y un tono verdoso para indicar un 1 o cuando está encendido.

indicadores

Y ahora muestro el código de la bolita

<UserControl x:Class="Controls.EllipseUserControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:Controls="clr-namespace:Controls"
             mc:Ignorable="d"
             d:DesignHeight="48" d:DesignWidth="48" x:Name="ellipseUserControl">
    <Grid x:Name="gridEllipse" Height="{Binding ElementName=ellipseUserControl,Path=Height}"
          Width="{Binding ElementName=ellipseUserControl,Path=Width}">
        <Grid.Resources>
            <Style x:Key="ellipseStyle" TargetType="Ellipse">
                <Setter Property="VerticalAlignment" Value="Stretch"/>
                <Setter Property="HorizontalAlignment" Value="Stretch"/>
                <Setter Property="Margin" Value="2"/>
                <Setter Property="Fill">
                    <Setter.Value>
                        <RadialGradientBrush GradientOrigin="0.6,0.2"
                    Center="0.6,0.2"
                    RadiusX="0.4" RadiusY="0.4">
                            <RadialGradientBrush.GradientStops>
                                <GradientStop Color="White" Offset="0" />
                                <GradientStop Color="{Binding ElementName=gridEllipse, Path=Parent.ActiveColor}" Offset="1.5" />
                            </RadialGradientBrush.GradientStops>
                        </RadialGradientBrush>
                    </Setter.Value>
                </Setter>
            </Style>
        </Grid.Resources>
        <Ellipse x:Name="ellipse" Style="{StaticResource ellipseStyle}"/>
    </Grid>
</UserControl>

Podemos apreciar que el control básico solo tiene un Grid y dentro de este una ellipse, la ellipse tiene un estilo declarado en Grid.Resources y el cual tiene un degradado radial y desde una de sus propiedades GradientStop enlazamos con una propiedad DependecyProperty ActiveColor, de modo que podamos convertir valores booleanos en colores y se la asignemos a esta propiedad y por tanto esta propiedad cambiará el color de GradientStop. Esta propiedad la declaramos en la clase del control de usuario del siguiente modo:


public Color ActiveColor
{
get { return (Color)GetValue(ActiveColorProperty); }
set { SetValue(ActiveColorProperty, value); }
}

// Using a DependencyProperty as the backing store for IsActiveColor. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ActiveColorProperty =
DependencyProperty.Register("ActiveColor", typeof(Color), typeof(EllipseUserControl), new PropertyMetadata(Brushes.Red.Color));

Ahora vamos al trabajo duro, que es crear un control de usuario más complejo, basado en unos cuantos controles básicos. Para las horas usaremos para:

  • Horas. Primer dígito 2, o vale 0, 1 o 2 (00, 01 o 11). Segundo dígito 4 indicadores, desde 0 a 9 y el 9 es 1001 en binario.
  • Minutos. Primer dígito. Valor máximo es 5, por tanto 3 indicadores (101), segundo dígito valor máximo 9, cuatro indicadores (1001)
  • Segundos. Igual que los minutos.

Ahora que tenemos el diseño del reloj, lo generamos en XAML

<UserControl x:Class="Controls.BinaryClock"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:Controls="clr-namespace:Controls"
             mc:Ignorable="d"
             xmlns:sys="clr-namespace:System;assembly=mscorlib"
             d:DesignHeight="155" d:DesignWidth="180" >
    <UserControl.Resources>
        <Controls:BooleanToBrushes x:Key="btb"/>
    </UserControl.Resources>
    <Grid x:Name="grid">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="30"/>
            <ColumnDefinition Width="30"/>
            <ColumnDefinition Width="30"/>
            <ColumnDefinition Width="30"/>
            <ColumnDefinition Width="30"/>
            <ColumnDefinition Width="30"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="25"/>
            <RowDefinition Height="25"/>
            <RowDefinition Height="25"/>
            <RowDefinition Height="25"/>
            <RowDefinition Height="25"/>
            <RowDefinition Height="25"/>
            <RowDefinition Height="25"/>
        </Grid.RowDefinitions>
        <!-- HORAS -->
        <TextBlock Text="H" Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="2" HorizontalAlignment="Center" FontSize="20"/>
        <Border BorderThickness="1" BorderBrush="LightCoral" Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="2" Grid.RowSpan="4" Margin="1"/>

        <Controls:EllipseUserControl x:Name="hour11" Grid.Column="0" Grid.Row="4" ActiveColor="{Binding ElementName=grid, Path=Parent.H10, Converter={StaticResource btb}}"/>
        <Controls:EllipseUserControl x:Name="hour12" Grid.Column="0" Grid.Row="3" ActiveColor="{Binding ElementName=grid, Path=Parent.H11, Converter={StaticResource btb}}"/>
        <Controls:EllipseUserControl x:Name="hour21" Grid.Column="1" Grid.Row="4" ActiveColor="{Binding ElementName=grid, Path=Parent.H20, Converter={StaticResource btb}}"/>
        <Controls:EllipseUserControl x:Name="hour22" Grid.Column="1" Grid.Row="3" ActiveColor="{Binding ElementName=grid, Path=Parent.H21, Converter={StaticResource btb}}"/>
        <Controls:EllipseUserControl x:Name="hour24" Grid.Column="1" Grid.Row="2" ActiveColor="{Binding ElementName=grid, Path=Parent.H22, Converter={StaticResource btb}}"/>
        <Controls:EllipseUserControl x:Name="hour28" Grid.Column="1" Grid.Row="1" ActiveColor="{Binding ElementName=grid, Path=Parent.H23, Converter={StaticResource btb}}"/>

        <!-- MINUTOS -->
        <TextBlock Text="M" Grid.Column="2" Grid.Row="0" Grid.ColumnSpan="2" HorizontalAlignment="Center" FontSize="20"/>
        <Border BorderThickness="1" BorderBrush="LightBlue" Grid.Column="2" Grid.Row="1" Grid.ColumnSpan="2" Grid.RowSpan="4" Margin="1"/>

        <Controls:EllipseUserControl x:Name="minute11" Grid.Column="2" Grid.Row="4" ActiveColor="{Binding ElementName=grid, Path=Parent.M10, Converter={StaticResource btb}}"/>
        <Controls:EllipseUserControl x:Name="minute12" Grid.Column="2" Grid.Row="3" ActiveColor="{Binding ElementName=grid, Path=Parent.M11, Converter={StaticResource btb}}"/>
        <Controls:EllipseUserControl x:Name="minute14" Grid.Column="2" Grid.Row="2" ActiveColor="{Binding ElementName=grid, Path=Parent.M12, Converter={StaticResource btb}}"/>
        <Controls:EllipseUserControl x:Name="minute21" Grid.Column="3" Grid.Row="4" ActiveColor="{Binding ElementName=grid, Path=Parent.M20, Converter={StaticResource btb}}"/>
        <Controls:EllipseUserControl x:Name="minute22" Grid.Column="3" Grid.Row="3" ActiveColor="{Binding ElementName=grid, Path=Parent.M21, Converter={StaticResource btb}}"/>
        <Controls:EllipseUserControl x:Name="minute24" Grid.Column="3" Grid.Row="2" ActiveColor="{Binding ElementName=grid, Path=Parent.M22, Converter={StaticResource btb}}"/>
        <Controls:EllipseUserControl x:Name="minute28" Grid.Column="3" Grid.Row="1" ActiveColor="{Binding ElementName=grid, Path=Parent.M23, Converter={StaticResource btb}}"/>

        <!-- SEGUNDOS -->
        <TextBlock Text="S" Grid.Column="4" Grid.Row="0" Grid.ColumnSpan="2" HorizontalAlignment="Center" FontSize="20"/>
        <Border BorderThickness="1" BorderBrush="LightGreen" Grid.Column="4" Grid.Row="1" Grid.ColumnSpan="2" Grid.RowSpan="4" Margin="1"/>

        <Controls:EllipseUserControl x:Name="second11" Grid.Column="4" Grid.Row="4" ActiveColor="{Binding ElementName=grid, Path=Parent.S10, Converter={StaticResource btb}}"/>
        <Controls:EllipseUserControl x:Name="second12" Grid.Column="4" Grid.Row="3" ActiveColor="{Binding ElementName=grid, Path=Parent.S11, Converter={StaticResource btb}}"/>
        <Controls:EllipseUserControl x:Name="second14" Grid.Column="4" Grid.Row="2" ActiveColor="{Binding ElementName=grid, Path=Parent.S12, Converter={StaticResource btb}}"/>
        <Controls:EllipseUserControl x:Name="second21" Grid.Column="5" Grid.Row="4" ActiveColor="{Binding ElementName=grid, Path=Parent.S20, Converter={StaticResource btb}}"/>
        <Controls:EllipseUserControl x:Name="second22" Grid.Column="5" Grid.Row="3" ActiveColor="{Binding ElementName=grid, Path=Parent.S21, Converter={StaticResource btb}}"/>
        <Controls:EllipseUserControl x:Name="second24" Grid.Column="5" Grid.Row="2" ActiveColor="{Binding ElementName=grid, Path=Parent.S22, Converter={StaticResource btb}}"/>
        <Controls:EllipseUserControl x:Name="second28" Grid.Column="5" Grid.Row="1" ActiveColor="{Binding ElementName=grid, Path=Parent.S23, Converter={StaticResource btb}}"/>

        <TextBlock Text="{Binding ElementName=grid, Path=Parent.Hour}" Grid.Column="0" Grid.Row="5" Grid.ColumnSpan="2" HorizontalAlignment="Center" FontSize="22"/>
        <TextBlock Text="{Binding ElementName=grid, Path=Parent.Minutes}" Grid.Column="2" Grid.Row="5" Grid.ColumnSpan="2" HorizontalAlignment="Center" FontSize="22"/>
        <TextBlock Text="{Binding ElementName=grid, Path=Parent.Seconds}" Grid.Column="4" Grid.Row="5" Grid.ColumnSpan="2" HorizontalAlignment="Center" FontSize="22"/>
    </Grid>
</UserControl>

como podéis apreciar, he distribuido los controles para dar forma al reloj quedando su diseño así
bin01
Cada indicador usa una clase converter para convertir el valor booleano en un color y con esto finaliza el código XAML.

public class BooleanToBrushes : IValueConverter
    {
        #region Constructor

        #endregion

        #region Propiedades

        #endregion

        #region IValueConverter

        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            bool bValue = (bool)value;
            if (bValue)
            {
                return Brushes.Green.Color;
            }
            else
            {
                return Brushes.Gray.Color;
            }
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            return null;
        }

        #endregion
    }

Vamos al código. Hay una propiedad booleana por cada uno de los indicadores. Un DispatcherTime para calcular los valores en cada segundo y un par de métodos para convertir la hora a binario y pasarla a cada uno de los indicadores mediante las propiedades. Paso el código.

    public partial class BinaryClock : UserControl,INotifyPropertyChanged
    {
        public BinaryClock()
        {
            InitializeComponent();

            dispatcher.Tick += Dispatcher_Tick; ;
            dispatcher.Interval = TimeSpan.FromSeconds(1);
            dispatcher.Start();
        }

        #region Privates Method

        List setDecimalToBinary(int value, bool isHour)
        {
            List values = new List();
            string valueToString = value.ToString("00");
            string digit1 = valueToString.Substring(0, 1);
            string digit2 = valueToString.Substring(1, 1);
            string binaryDigit1 = convertToBinary(Convert.ToInt16(digit1)).PadLeft(2, '0');
            if (!isHour)
            {
                binaryDigit1 = binaryDigit1.PadLeft(3, '0');
            }
            string binaryDigit2 = convertToBinary(Convert.ToInt16(digit2)).PadLeft(4, '0');
            foreach (var item in binaryDigit1 + binaryDigit2)
            {
                values.Add(item.Equals('0') ? false : true);
            }
            return values;
        }

        string convertToBinary(int value)
        {
            return Convert.ToString(value, 2);
        }

        List setDecimalToBinary(int value)
        {
            return setDecimalToBinary(value, false);
        }

        private void Dispatcher_Tick(object sender, EventArgs e)
        {
            DateTime horaActual = DateTime.Now;

            Hour = horaActual.Hour.ToString();
            Minutes = horaActual.Minute.ToString();
            Seconds = horaActual.Second.ToString();

            var hora = setDecimalToBinary(horaActual.Hour, true);
            H10 = hora[1];
            H11 = hora[0];

            H20 = hora[5];
            H21 = hora[4];
            H22 = hora[3];
            H23 = hora[2];

            var minutos = setDecimalToBinary(horaActual.Minute);
            M10 = minutos[2];
            M11 = minutos[1];
            M12 = minutos[0];

            M20 = minutos[6];
            M21 = minutos[5];
            M22 = minutos[4];
            M23 = minutos[3];

            var segundos = setDecimalToBinary(horaActual.Second);
            S10 = segundos[2];
            S11 = segundos[1];
            S12 = segundos[0];

            S20 = segundos[6];
            S21 = segundos[5];
            S22 = segundos[4];
            S23 = segundos[3];

        }

        #endregion

        public event PropertyChangedEventHandler PropertyChanged;

        #region Public Methods

        ///
<summary>
        /// Inicia el evento de cambio de propiedad
        /// </summary>

        /// Nombre de la propiedad
        public void OnPropertyChanged(string p_PropertyName)
        {

            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(p_PropertyName));
            }
        }

        #endregion

        #region Properties &amp; fields

        private bool h10;
        private bool h11;
        private bool h20;
        private bool h21;
        private bool h22;
        private bool h23;
        private bool m10;
        private bool m11;
        private bool m12;
        private bool m20;
        private bool m21;
        private bool m22;
        private bool m23;
        private bool s10;
        private bool s11;
        private bool s12;
        private bool s20;
        private bool s21;
        private bool s22;
        private bool s23;
        DispatcherTimer dispatcher = new DispatcherTimer();

        private string hour;
        private string minute;
        private string seconds;

        public string Hour
        {
            get { return hour; }
            set
            {
                hour = value;
                OnPropertyChanged("Hour");
            }
        }

        public string Minutes
        {
            get { return minute; }
            set
            {
                minute = value;
                OnPropertyChanged("Minutes");
            }
        }

        public string Seconds
        {
            get { return seconds; }
            set
            {
                seconds = value;
                OnPropertyChanged("Seconds");
            }
        }

        // HORAS. 1ER DIGITO
        public bool H10
        {
            get { return h10; }
            set
            {
                h10 = value;
                OnPropertyChanged("H10");
            }
        }

        public bool H11
        {
            get { return h11; }
            set
            {
                h11 = value;
                OnPropertyChanged("H11");
            }
        }

        // HORAS. 2º DIGITO
        public bool H20
        {
            get { return h20; }
            set
            {
                h20 = value;
                OnPropertyChanged("H20");
            }
        }
        public bool H21
        {
            get { return h21; }
            set
            {
                h21 = value;
                OnPropertyChanged("H21");
            }
        }
        public bool H22
        {
            get { return h22; }
            set
            {
                h22 = value;
                OnPropertyChanged("H22");
            }
        }
        public bool H23
        {
            get { return h23; }
            set
            {
                h23 = value;
                OnPropertyChanged("H23");
            }
        }

        //MINUTOS. 1ER DIGITO
        public bool M10
        {
            get { return m10; }
            set
            {
                m10 = value;
                OnPropertyChanged("M10");
            }
        }
        public bool M11
        {
            get { return m11; }
            set
            {
                m11 = value;
                OnPropertyChanged("M11");
            }
        }
        public bool M12
        {
            get { return m12; }
            set
            {
                m12 = value;
                OnPropertyChanged("M12");
            }
        }
        // MINUTOS. 2º DIGITO
        public bool M20
        {
            get { return m20; }
            set
            {
                m20 = value;
                OnPropertyChanged("M20");
            }
        }
        public bool M21
        {
            get { return m21; }
            set
            {
                m21 = value;
                OnPropertyChanged("M21");
            }
        }
        public bool M22
        {
            get { return m22; }
            set
            {
                m22 = value;
                OnPropertyChanged("M22");
            }
        }
        public bool M23
        {
            get { return m23; }
            set
            {
                m23 = value;
                OnPropertyChanged("M23");
            }
        }

        // SEGUNDOS. 1ER DIGITO
        public bool S10
        {
            get { return s10; }
            set
            {
                s10 = value;
                OnPropertyChanged("S10");
            }
        }
        public bool S11
        {
            get { return s11; }
            set
            {
                s11 = value;
                OnPropertyChanged("S11");
            }
        }
        public bool S12
        {
            get { return s12; }
            set
            {
                s12 = value;
                OnPropertyChanged("S12");
            }
        }
        // SEGUNDOS. 2º DIGITO
        public bool S20
        {
            get { return s20; }
            set
            {
                s20 = value;
                OnPropertyChanged("S20");
            }
        }
        public bool S21
        {
            get { return s21; }
            set
            {
                s21 = value;
                OnPropertyChanged("S21");
            }
        }
        public bool S22
        {
            get { return s22; }
            set
            {
                s22 = value;
                OnPropertyChanged("S22");
            }
        }
        public bool S23
        {
            get { return s23; }
            set
            {
                s23 = value;
                OnPropertyChanged("S23");
            }
        }

        #endregion
    }

Pues con este código, cada segundo, se comprueba la hora y se pasa a cada uno de los indicadores un valor booleano correspondiente a su valor en binario, este se convierte en un color con el converter y et voilá, un reloj binario. Le he añadido la hora con números arábigos para que podáis contrastar. Y el resultado… en la cabecera.Espero que os haya gustado.

Controles de usuario en WPF

Controles de usuario en WPF

Entre todas las cosas que me gustan de WPF, una de las que más, es la versatilidad, los programadores podemos hacer practicamente lo que queramos. Hace unos “pocos” años, el programador debía servirse de controles de otras empresas porque a veces, los controles básicos de Microsoft se quedaban “cortos” para cuestiones más complejas; posteriormente, se podían crear controles de este tipo pero para que funcionaran (como deben funcionar), había que afinar mucho, pero la facilidad con la que se puede crear y la complejidad que se le puede dar a un control actualmente es bestial. Para que se hagan una idea, en mis proyectos tengo un control de usuario creado sobre una clase Persona estándar, de modo que cada vez que tengo que iniciar un proyecto en el que esa clase hace acto de presencia, el control con sus binding lo hace de igual modo y ya es cuestión de adaptar si queremos que aparezca tal propiedad o no, pero las validaciones de datos como DNI, cuenta bancaria, tarjetas,etc, los cálculos (por ejemplo la edad), las máscaras de entrada, editar y guardar, marcas de edición, etc., es decir un sinfín de procesos, se encuentran hechos. Solo nos quedaría, darle forma a la interfaz gráfica para adaptarla a las necesidades del cliente.

Un control de usuario podría ser, una control ListView con cada uno de sus items que fueran un Treeview por ejemplo, podríamos crear un  control ProgressBar con varios colores como un vumeter, un Progressbar circular, un TextBox inteligente que nos ayude a escribir, podríamos crear un control que realice funciones de filtrado sobre un contenido de registros, dándole una colección de entrada y entregando una colección de salida con los parámetros de filtrado, podríamos crear un reloj analógico como control, un reloj binario, un lo que se nos venga a la cabeza, esa es su función, crear objetos complejos en base a otros controles.

En este artículo, como ejempo vamos a mostrar un control básico que hará las siguientes funciones:

  • Control slider
  • Debe iluminarse la zona que hemos desplazado con el cursor
  • Debe aparecer un texto con el valor
  • Dependiendo del valor, debe cambiar su color

Vamos a ver el código XAML

<UserControl x:Class="Controls.SliderUserControl" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:Controls" mc:Ignorable="d" d:DesignHeight="40" d:DesignWidth="300">
    <StackPanel>
        <Grid>
            <Grid.Resources>
                <local:ValueToBrush x:Key="vtb"/>
            </Grid.Resources>
            <ProgressBar Minimum="{Binding ElementName=mainSlider,Path=Minimum}" Maximum="{Binding ElementName=mainSlider,Path=Maximum}" Value="{Binding ElementName=mainSlider, Path=Value}" Height="10"/>
            <Slider x:Name="mainSlider" Maximum="100" Minimum="0" Value="30" BorderBrush="Gray" BorderThickness="1" Background="{Binding ElementName=mainSlider,Path=Value,Converter={StaticResource vtb}}" Opacity=".2"/>
        </Grid>
        <TextBlock Text="{Binding ElementName=mainSlider, Path=Value, StringFormat={}{0:N0}}" HorizontalAlignment="Center" FontSize="16"/>
    </StackPanel>
</UserControl>

Analizando el código, creamos un slider que veremos con más detenimiento, un control progressbar que nos indicará visualmente el valor del slider y un TextBlock.
El control ProgressBar le hacemos un binding a la propiedad Maximun, Minimun y Value a las mismas propiedades del control Slider.
El control TextBlock le hacemos un binding a la propiedad Text pero dándole formato para que no aparezcan decimales.
El control Slider tiene alguna cosa más y es que la propiedad Opacity la hemos bajado a 0.2 y la propiedad Background se alimenta de la propiedad Value la cual es convertida en un objeto Brush desde una clase que implementa IValueConverter llamada ValueToBrush. Esta clase es instanciada desde el código XAML y la llamamos en el binding de la propiedad Text.

public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if ((double)value < 50)
{
return Brushes.Green;
}
else if ((double)value < 75)
{
return Brushes.Yellow;
}
else
{
return Brushes.Red;
}
}

de este modo cambia de color dependiendo del valor.
El resultado es el siguiente:Video

Lo mejor de todo, el control sin una sola línea de código (sin contar el converter 😉 ). En próximas ediciones, algún control más complejo.

Un saludo