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
    }
}
Anuncios

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.

Sucesión de Fibonacci

Sucesión de Fibonacci
Publiqué en mi otro blog sobre mates, física y otros temas un post acerca de la sucesión y la espiral de Fibonacci y gustándome como me gustan las funciones recursivas, no podía quedar atrás una implementación en C# de esta sucesión.
Para ellos, en este artículo expondré diversos métodos para calcular la sucesión de Fibonacci a los cuales se les pasará como argumento el valor de n de la función y que a continuación os muestro:
El primero de ellos será mediante la función recursiva. Este es el método natural de cálculo de la sucesión
espiral01.png

        public double GetRecursiveFunction(int n)
        {
            if ((n == 0) || (n == 1)) return 1;
            else return GetRecursiveFunction(n -1) + GetRecursiveFunction(n - 2);
        }

Este método tiene el inconveniente de la complejidad recurrente que se aplica sobre su cálculo, es decir que si solicitamos el valor del elemento 39, haríamos una llamada a GetRecursiveFunction(39) , la cual una vez dentro de esta, llamará a los elementos GetRecursiveFunction(38) y GetRecursiveFunction(37) de la misma función para sumarlos, y estos a su vez a sus dos anteriores hasta llegar a GetRecursiveFunction(0) o GetRecursiveFunction(1) devolviendo el valor de 1 a partir de entonces comenzará a sumar. Esto significa que para cada cálculo se realiza la suma de las iteraciones de los dos elementos anteriores más 1. Si para n igual a 6 se llama a la función 15 veces y para n igual a 7, 25 veces, para n igual a 8 se llamaría a la función 15 más 25 y más 1 de la primera llamada. Esto supone un gasto de recursos enorme ya que para n igual a 40 el número de iteraciones se elevaría a la suma de 78.176.337, 126.491.971 y 1, un total de 204.668.309 iteraciones para obtener el elemento 40 de la sucesión; en tiempo, aproximadamente unos 8 segundos en un dual core con SSD. A todos los efectos, no es eficiente.El siguiente método de cálculo de la sucesión de Fibonacci, será el de la función general extraída mediante la ecuación de recurrencia y sus raíces.espiral14.png

Esta función tiene como inconveniente la precisión y muestro el por qué. En primero lugar creo una propiedad de solo lectura para calcular ϕ (Phi) o el número áureo, el cual es igual a
Espiral04.png
provocando una falta de precisión; por otra parte el uso de la exponenciación a n, lo que a su vez provoca que un número excesivamente grande, sea tratado exponencialmente. Todo esto unido para un n grande, nos hace obtener un número extremadamente aproximado a cualquier elemento de la sucesión, pero sin llegar a serlo, por lo que debemos auxiliarnos de la función de redondeo para obtener unos resultados satisfactorios. A pesar de los complejos cálculos, es muy rápida.

        public double Phi
        {
            get
            {
                return (1 + Math.Sqrt(5)) / 2;
            }
        }

        public double GetGeneralFuncion(int n)
        {
            double result = (double)((Math.Pow(Phi, n) - Math.Pow((1 - Phi), n)) / Math.Sqrt(5));
            return Math.Round(result);
        }

La siguiente función es la iterativa. Esta parte de los dos primeros valores 0 y 1 y va obteniendo el siguiente en cada iteración, sencilla y muy rápida. No tiene problemas de redondeo. El inconveniente que tiene es que no almacena los valores anteriores, sino que simplemente los calcula.

        public double GetIterativeFunction(int n)
        {
            double f0 = 0;
            double f1 = 1;
            for (int i = 1; i < n; i++)
            {
                f1 = f0 + f1;
                f0 = f1 - f0;
            }

            return f1;
        }

Por último, una función parecida a la iterativa pero en la cual quedan almacenados los elementos de la sucesión mediante un objeto List, de este modo se asignan los dos primeros valores y se van generando los siguientes añadiéndolos a la lista según se van calculando.

        public List numbers = new List();
        public void GetArrayListFunction(int n)
        {
            numbers.Clear();
            numbers.Add(0);
            numbers.Add(1);
            for (int i = 2; i < n; i++)
            {
                numbers.Add(numbers[i - 1] + numbers[i - 2]);
            }
        }

Una vez que hemos visto las funciones para obtener los elementos de la sucesión de Fibonacci, vamos a calcular la espiral de Fibonacci mediante dos métodos, XAML y código puro y duro.
Si lo hacemos con XAML, creamos un objeto Path y dentro de Path.Data con el siguiente código:

        <Path Stroke="Red" StrokeThickness="2" >
        <Path.Data>
            <PathGeometry>
                <PathGeometry.Figures>
                    <PathFigureCollection>
                            <PathFigure StartPoint="610,610">
                                <PathFigure.Segments>
                                <PathSegmentCollection>
                                        <ArcSegment Point="600, 600" Size="10 10" />
                                        <ArcSegment Point="590, 610" Size="10 10" />
                                        <ArcSegment Point="610, 630" Size="20 20" />
                                        <ArcSegment Point="640, 600" Size="30 30" />
                                        <ArcSegment Point="590, 550" Size="50 50" />
                                        <ArcSegment Point="510, 630" Size="80 80" />
                                        <ArcSegment Point="640, 760" Size="130 130" />
                                        <ArcSegment Point="850, 550" Size="210 210" />
                                        <ArcSegment Point="510, 210" Size="340 340" />
                                        <ArcSegment Point="-40, 760" Size="550 550" />
                                        <ArcSegment Point="850, 1650" Size="890 890" />
                                        <ArcSegment Point="2290, 210" Size="1440 1440" />
                                    </PathSegmentCollection>
                            </PathFigure.Segments>
                        </PathFigure>
                    </PathFigureCollection>
                </PathGeometry.Figures>
            </PathGeometry>
        </Path.Data>
        </Path>

Lo que hacemos es crear objetos ArcSegment como tantos elementos de la sucesión queramos incluir, pero su límite es evidente, por lo que podríamos crear un método genérico que creara una espiral en base al argumento n, de modo que llamamos por ejemplo a la función iterativa basada en List y con los datos almacenados en esta lista, usarlos para crear tanto objetos ArcSegment como elementos tenga la sucesión. El problema es que la sucesión de Fibonacci tiene un crecimiento alto y la espiral desaparecerá rápidamente de nuestra ventana, pero calcular, calcula correctamente la espiral.
He creado dos propiedades para el centro de la espiral, x e y un ángulo como variable angular. (También podríamos haber usado la función polar de la espiral, pero he escogido esta para mostrar cada arco de segmento).
Para seleccionar el centro de cada arco, he ingeniado un método que mediante el seno y coseno del ángulo de la función, sume o reste los valores de la sucesión y asigne correctamente el centro.
Una vez creados los arcos, los añadimos al objeto Figure y como en XAML vamos añadiendo a la colección.

        public double CenterX { get; set; } = 512;
        public double CenterY { get; set; } = 384;
        public double Angle { get; set; } = Math.PI;

        void create(int n)
        {

            Fibonacci fib = new Fibonacci();
            fib.GetArrayListFunction(n);

            PathGeometry pathGeometry = new PathGeometry();

            PathFigure figure = new PathFigure();
            figure.StartPoint = new Point(CenterX, CenterY);

            for (int i =0; i < fib.numbers.Count; i++)
            {
                double sign = (i % 2 == 0 ? 1 : -1);
                double x = sign==1? Math.Round(Math.Cos(Angle)) : Math.Round(Math.Sin(Angle));
                double y = sign == 1 ? Math.Round(Math.Sin(Angle - Math.PI / 2)) : Math.Round(Math.Cos(Angle-Math.PI/2));
                double a = Math.Round(fib.numbers[i] * sign * x);
                double b = Math.Round(fib.numbers[i] * sign * y);

                CenterX = CenterX + a;
                CenterY = CenterY + b;

                ArcSegment arcSegment = new ArcSegment(new Point(CenterX, CenterY),
                    new Size(fib.numbers[i], fib.numbers[i]),
                    0,false,SweepDirection.Counterclockwise,true);

                figure.Segments.Add(arcSegment);
                Angle += Math.PI/2;

            }

            pathGeometry.Figures.Add(figure);
            path.Data = pathGeometry;
            path.Stroke = Brushes.Green;
        }

el resultado en ambos casos

fib02.png
y esto es todo,
saludos!

Tabla periódica

Tabla periódica
Con este post, vamos a mejorar nuestro aprendizaje sobre el diseño de software MVVM (Model View ViewModel) en las que claramente separamos el código por capas en el mismo ensamblado o en varios, pero en este caso lo haremos en el mismo. Para el ejemplo se me ha ocurrido mostrar la tabla periódica, en la cual no vamos a realizar modificaciones, puesto que los datos son los que son y lo único que nos interesa de ella es mostrarla correctamente.
tabla03
Como ya he comentado en otros posts, esto nos va a permitir diseñar y desarrollar un software mucho más robusto y fácil de mantener.
Las aplicaciones MVVM, son sucesoras de las aplicaciones MVC (Model View Controller). En una aplicación MVC podemos apreciar tres tipos de objetos:
Modelo
Esta capa, es la que se encarga de trabajar con los datos y contiene la capa de negocio.
Vista
La vista es la representación de cara al usuario o el objeto responsable de mostrar y permitir la interacción el usuario sobre los datos
Controlador
Permite transferir los datos desde el modelo a la vistaEsto nos permite poder sustituir o modificar cualquiera de las capas sin afectar o afectando lo menos posible al resto de capas, pudiendo mantener de un modo más eficaz nuestra aplicación. La diferencia con las aplicaciones WPF (o Silverlight) es que la capa ViewModel o modelo-vista es la responsable de exponer los datos del modelo usando bindings y de controlar el comportamiento de la vista

En nuestro caso, usamos como datos un archivo XML que he convertido a este formato desde una hoja de cálculo que tenía con los elementos de la tabla periódica y sus propiedades, como modelo, un gestor de datos desde XML, como vista la tabla periódica en XAML y como ViewModel, cada uno de los elementos de la tabla y la tabla en sí.

Los datos xml están distribuidos mediante un elemento raíz elements y como nodos hijos element los cuales contienen las propiedades en bruto que usará el ViewModel.

<elements xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <element>
    <simbolo>H</simbolo>
    <elemento>Hidrogeno</elemento>
    <numeroAtomico>1</numeroAtomico>
    <pesoAtomico>1,0079</pesoAtomico>
    <densidad>0,071</densidad>
    <valencia>1</valencia>
    <puntoEbullicion>-252,7</puntoEbullicion>
    <puntoFusion>259,2</puntoFusion>
    <estado>Gas</estado>
    <serieQuimica>Otros no metales</serieQuimica>
    <grupo>1</grupo>
    <periodo>1</periodo>
  </element>
  ...
</elements>

Los datos xml son extraídos y pasados a la clase ViewModel mediante la clase XMLManager. Esta clase extrae los datos del archivo XML y al mismo tiempo los convierte en el objeto de la clase ViewModel mediante XML.Linq. Fijaros en la línea subrayada donde desde Linq se crea el nuevo objeto Element por cada una de los componentes del archivo xml y que posteriormente la query se añade a la lista de retorno.

        public List GetElements()
        {
            string path = GetPathXML();
            List elements = new List();

            XDocument xdoc = XDocument.Load(path);

            var query = from u in xdoc.Descendants("element")
                        select new Element
                        {
                            Symbol = u.Element("simbolo").Value,
                            Name = u.Element("elemento").Value,
                            AtomicNumber = Convert.ToInt32(u.Element("numeroAtomico").Value),
                            AtomicMass = Convert.ToDouble(u.Element("pesoAtomico").Value),
                            Density = Convert.ToDouble(u.Element("densidad").Value),
                            Valencia = u.Element("valencia").Value,
                            MeltingPoint = !string.IsNullOrEmpty(u.Element("puntoEbullicion").Value) ? Convert.ToDouble(u.Element("puntoEbullicion").Value): 0,
                            BoilingPoint = !string.IsNullOrEmpty(u.Element("puntoFusion").Value) ? Convert.ToDouble(u.Element("puntoFusion").Value):0,
                            StandarState = u.Element("estado").Value,
                            SerialChemical = u.Element("serieQuimica").Value,
                            Group = Convert.ToInt32(u.Element("grupo").Value),
                            Period = Convert.ToInt32(u.Element("periodo").Value)
                        };

            elements.AddRange(query.ToList());
            return elements;
        }

Como clases ViewModel tenemos por una parte la clase Element que ostenta la entidad de cualquier elemento de la tabla y la clase PeriodicTable que alberga las colecciones. Con extremada sencillez, solo propiedades, sin métodos, de hecho no tenemos que usar la actualización de la propiedades mediante el evento PropertyChanged, ya que las propiedades de los elementos solo se van a cargar una sola vez.

Clase Element

/* ****************************************************************
* © JOAQUIN MARTINEZ RUS 2016
* PROYECTO:        Tabla periódica
* Archivo:         Element.cs
* Descripción:     Clase element.
 *
* Historial:       1. 12 sep 2016. Creación
*
* Comentarios:      Elemento de la tabla periódica
*
*
*******************************************************************/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;

namespace DimitriMendeleyev
{
    public class Element: INotifyPropertyChanged
    {
        public string Symbol { get; set; }
        public string Name { get; set; }
        public int AtomicNumber { get; set; }
        public double AtomicMass { get; set; }
        public double Density { get; set; }
        public string Valencia { get; set; }
        public double MeltingPoint { get; set; }
        public double BoilingPoint { get; set; }
        public string StandarState { get; set; }
        public string SerialChemical { get; set; }
        public int Period { get; set; }
        public int Group { get; set; }

        public event PropertyChangedEventHandler PropertyChanged;

    }
}

Clase PeriodicTable

/* ****************************************************************
* © JOAQUIN MARTINEZ RUS 2016
* PROYECTO:        Tabla periódica
* Archivo:         PeriodicTable.cs
* Descripción:     Clase PeriodicTable.
 *
* Historial:
*                  1. 12 sep 2016. Creación
*
* Comentarios:     Tabla periódica
*
*
*******************************************************************/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;

namespace DimitriMendeleyev
{
    public class PeriodicTable: INotifyPropertyChanged
    {
        public PeriodicTable()
        {
            Elements = new List<Element>();
            SerialChemical = new List<string>();
            SerialChemical.AddRange(new string[] { "Metales", "Metaloides" , "Otros no metales",
                "Halógenos", "Alcalinotérreos", "Alcalinos", "Metales de Transición",
                "Gases nobles", "Lantánidos", "Actínidos"});
            States = new List<string>();
            States.AddRange(new string[] { "Gas", "Líquido", "Sólido", "Artificial" });
        }

        public List<Element> Elements { get; set; }
        public List<string> SerialChemical { get; set; }
        public List<string> States { get; set; }

        public event PropertyChangedEventHandler PropertyChanged;
    }
}

Por último en la vista creo un control de usuario para un elemento de la tabla y que utilizaremos hasta en 108 ocasiones para crear nuestra tabla.
Cada elemento tiene una serie de propiedades como el número atómico, el símbolo, el nombre, la masa atómica, la serie química, el grupo, el periodo, el estado y un sinfín de propiedades. A este control definido, le asignamos mediante binding las propiedades que queremos mostrar y además le asigno la serie química mediante un color y el estado del elemento a 0ºC, (gas, sólido. líquido o artificial) mediante otro color. Para mostrar estos colores uso sendos conversores que a continuación muestro.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;
using System.Windows.Media;

namespace DimitriMendeleyev
{
    public class SerialChemicalToColorConverter : IValueConverter
    {

        #region IValueConverter

        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            SolidColorBrush scb = new SolidColorBrush();

            switch (value.ToString())
            {
                case "Metales":
                    scb.Color = Color.FromRgb(144,238,144);
                    break;
                case "Metaloides":
                    scb.Color = Color.FromRgb(126, 179, 126);
                    break;
                case "Otros no metales":
                    scb.Color = Color.FromRgb(47, 155, 47);
                    break;
                case "Halógenos":
                    scb.Color = Color.FromRgb(134, 221, 221);
                    break;
                case "Alcalinotérreos":
                    scb.Color = Brushes.Khaki.Color;
                    break;
                case "Alcalinos":
                    scb.Color = Brushes.Gold.Color;
                    break;
                case "Metales de Transición":
                    scb.Color = Brushes.BurlyWood.Color;
                    break;
                case "Gases nobles":
                    scb.Color = Brushes.SteelBlue.Color;
                    break;
                case "Lantánidos":
                    scb.Color = Brushes.SandyBrown.Color;
                    break;
                case "Actínidos":
                    scb.Color = Brushes.RosyBrown.Color;
                    break;
            }
            return scb;
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            return Color.FromRgb(255, 0, 0);
        }

        #endregion
    }

    public class StandarStateToColorConverter : IValueConverter
    {

        #region IValueConverter

        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            SolidColorBrush scb = new SolidColorBrush();

            switch (value.ToString())
            {
                case "Gas":
                    scb.Color = Brushes.LightBlue.Color;
                    break;
                case "Líquido":
                    scb.Color = Brushes.Blue.Color;
                    break;
                case "Sólido":
                    scb.Color = Brushes.Black.Color;
                    break;
                case "Artificial":
                    scb.Color = Brushes.DarkRed.Color;
                    break;
            }
            return scb;
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            return Color.FromRgb(255, 0, 0);
        }

        #endregion
    }
}

En cuanto al elemento, contiene un Grid con un control Button y un StackPanel con tres controles Textblock que mostrarán las propiedades mediante binding. Es de reseñar que el modo que usan los enlaces a datos es OneTime, ya que los datos solo se mostrarán una sola vez y no habrá modificaciones; con esto conseguimos menos sobrecarga sobre la aplicación y economía de medios y recursos.

<UserControl x:Class="DimitriMendeleyev.ElementControl"
             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:DimitriMendeleyev"
             mc:Ignorable="d"
             xmlns:Controls="http://metro.mahapps.com/winfx/xaml/controls"
             d:DesignHeight="60" d:DesignWidth="60" x:Name="_element">
    <UserControl.Resources>
        <local:SerialChemicalToColorConverter x:Key="sctcc"/>
        <local:StandarStateToColorConverter x:Key="sstcc"/>
    </UserControl.Resources>
    <Grid>
        <Button Width="auto" Height="auto"
                Background="{Binding Path=SerialChemical, Converter={StaticResource sctcc}, Mode=OneTime}"
                x:Name="buttonElement">
            <StackPanel>
                <TextBlock x:Name="AtomicNumberTextBlock" Text="{Binding AtomicNumber, Mode=OneTime}" FontSize="12" TextAlignment="Center"/>
                <TextBlock x:Name="SymbolTextBlock" Text="{Binding Symbol, Mode=OneTime}" FontSize="15" TextAlignment="Center" FontWeight="Bold" Foreground="{Binding StandarState, Converter={StaticResource sstcc}}"/>
                <TextBlock x:Name="NameTextBlock" Text="{Binding Name, Mode=OneTime}" FontSize="10" TextAlignment="Center" />
            </StackPanel>
        </Button>
    </Grid>
</UserControl>

He creado otro control de usuario que muestra los datos ampliados del elemento de la tabla seleccionado y funciona prácticamente igual pero con la diferencia que se le pasa cada vez que se pulsa sobre un elemento concreto, este elemento, traspasa sus datos mediante el DataContext propio al control de datos ampliados.
Elemento con selección

<UserControl x:Class="DimitriMendeleyev.BigElementControl"
             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:DimitriMendeleyev"
             mc:Ignorable="d"
             d:DesignHeight="220" d:DesignWidth="250" x:Name="big">
    <UserControl.Resources>
        <local:SerialChemicalToColorConverter x:Key="sctcc"/>
        <local:StandarStateToColorConverter x:Key="sstcc"/>
    </UserControl.Resources>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="200*"/>
            <ColumnDefinition Width="50*"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="30"/>
            <RowDefinition Height="50"/>
            <RowDefinition Height="30"/>
            <RowDefinition Height="30"/>
            <RowDefinition Height="30"/>
            <RowDefinition Height="30*"/>
        </Grid.RowDefinitions>
        <Border BorderBrush="Navy" BorderThickness="4"
                Width="auto" Height="auto" Margin="1"
                Background="{Binding Path=SerialChemical, Converter={StaticResource sctcc}}"
                Grid.ColumnSpan="2" Grid.RowSpan="6"/>
        <TextBlock x:Name="AtomicNumberTextBlock" Text="{Binding AtomicNumber}" FontSize="20" TextAlignment="Center" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"/>
        <TextBlock x:Name="SymbolTextBlock" Text="{Binding Symbol}" FontSize="30" TextAlignment="Center" FontWeight="Bold" Grid.Row="1" Grid.Column="0" Foreground="{Binding StandarState, Converter={StaticResource sstcc}}" Grid.ColumnSpan="2"/>
        <TextBlock x:Name="NameTextBlock" Text="{Binding Name}" FontSize="20" TextAlignment="Center" Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2"/>
        <TextBlock Text="{Binding AtomicMass, StringFormat='Masa Atómica: {0}'}" FontSize="14" TextAlignment="Center" Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2"/>
        <TextBlock Text="{Binding Density, StringFormat='Densidad: {0}'}" FontSize="14" TextAlignment="Center" Grid.Row="4" Grid.Column="0" Grid.ColumnSpan="2"/>
        <TextBlock Text="{Binding Valencia}" FontSize="14" TextAlignment="Center" Grid.Row="0" Grid.Column="1" Grid.RowSpan="4" TextWrapping="Wrap" Margin="5"/>
    </Grid>
</UserControl>

Paso del datacontext

        private void ButtonElement_Click(object sender, RoutedEventArgs e)
        {
            var buttonElement = e.OriginalSource as Button;
            var el = buttonElement.Parent as Grid;
            bigElement.DataContext = el.DataContext;
        }

La tabla periódica es la que tiene su “currito”, tenemos que crear un control ElementControl por cada uno de los elementos de la tabla colocándolos en su sitio correcto, dejándolo lo más bonito que nuestro gusto nos permita. El código que contiene esta vista, no es otro que la instanciación de la clase XMLManager, la clase PeriodicTable y asignación al DataContext de la lista de elementos que contiene esta última. Muestro a continuación el código XAML completo de la tabla periódica.

<Controls:MetroWindow x:Class="DimitriMendeleyev.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:Controls="http://metro.mahapps.com/winfx/xaml/controls"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:DimitriMendeleyev"
        mc:Ignorable="d"
        Title="MainWindow" Height="800" Width="1140"
                      BorderBrush="{DynamicResource AccentColorBrush}"
                      BorderThickness="1" WindowState="Maximized" WindowStartupLocation="CenterScreen">
    <Controls:MetroWindow.Resources>
        <Style x:Key="baseStyle" TargetType="TextBlock">
            <Setter Property="TextAlignment" Value="Center"/>
            <Setter Property="VerticalAlignment" Value="Center"/>
            <Setter Property="HorizontalAlignment" Value="Stretch"/>
            <Setter Property="Margin" Value="2"/>
            <Setter Property="FontSize" Value="22"/>
            <Setter Property="Foreground" Value="DarkGray"/>
        </Style>
        <Style x:Key="serialChemicalStyle" TargetType="TextBlock">
            <Setter Property="HorizontalAlignment" Value="Stretch"/>
            <Setter Property="TextAlignment" Value="Left"/>
            <Setter Property="VerticalAlignment" Value="Center"/>
            <Setter Property="Margin" Value="10"/>
            <Setter Property="FontSize" Value="12"/>
            <Setter Property="TextWrapping" Value="Wrap"/>
            <Setter Property="Width" Value="auto"/>
        </Style>
        <local:SerialChemicalToColorConverter x:Key="sctcc"/>
        <local:StandarStateToColorConverter x:Key="sstcc"/>
    </Controls:MetroWindow.Resources>
    <Grid x:Name="grid" HorizontalAlignment="Center">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="40"/>
            <ColumnDefinition Width="60"/>
            <ColumnDefinition Width="60"/>
            <ColumnDefinition Width="60"/>
            <ColumnDefinition Width="60"/>
            <ColumnDefinition Width="60"/>
            <ColumnDefinition Width="60"/>
            <ColumnDefinition Width="60"/>
            <ColumnDefinition Width="60"/>
            <ColumnDefinition Width="60"/>
            <ColumnDefinition Width="60"/>
            <ColumnDefinition Width="60"/>
            <ColumnDefinition Width="60"/>
            <ColumnDefinition Width="60"/>
            <ColumnDefinition Width="60"/>
            <ColumnDefinition Width="60"/>
            <ColumnDefinition Width="60"/>
            <ColumnDefinition Width="60"/>
            <ColumnDefinition Width="60"/>
            <ColumnDefinition Width="20"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="80"/>
            <RowDefinition Height="60"/>
            <RowDefinition Height="60"/>
            <RowDefinition Height="60"/>
            <RowDefinition Height="60"/>
            <RowDefinition Height="60"/>
            <RowDefinition Height="60"/>
            <RowDefinition Height="60"/>
            <RowDefinition Height="20"/>
            <RowDefinition Height="60"/>
            <RowDefinition Height="60"/>
            <RowDefinition Height="60"/>
            <RowDefinition Height="20"/>
        </Grid.RowDefinitions>
        <!-- CONTROL GRANDE -->
        <local:BigElementControl x:Name="bigElement" Grid.Column="0" Grid.Row="9" Grid.ColumnSpan="3" Grid.RowSpan="3" Margin="10,0,0,0"/>
        <!-- Periodos -->
        <TextBlock Text="1" Grid.Column="0" Grid.Row="1" Style="{StaticResource baseStyle}"/>
        <TextBlock Text="2" Grid.Column="0" Grid.Row="2" Style="{StaticResource baseStyle}"/>
        <TextBlock Text="3" Grid.Column="0" Grid.Row="3" Style="{StaticResource baseStyle}"/>
        <TextBlock Text="4" Grid.Column="0" Grid.Row="4" Style="{StaticResource baseStyle}"/>
        <TextBlock Text="5" Grid.Column="0" Grid.Row="5" Style="{StaticResource baseStyle}"/>
        <TextBlock Text="6" Grid.Column="0" Grid.Row="6" Style="{StaticResource baseStyle}"/>
        <TextBlock Text="7" Grid.Column="0" Grid.Row="7" Style="{StaticResource baseStyle}"/>
        <!-- Grupos -->
        <TextBlock Text="1" Grid.Column="1" Grid.Row="0" Style="{StaticResource baseStyle}"/>
        <TextBlock Text="2" Grid.Column="2" Grid.Row="0" Style="{StaticResource baseStyle}"/>
        <TextBlock Text="3" Grid.Column="3" Grid.Row="0" Style="{StaticResource baseStyle}"/>
        <TextBlock Text="4" Grid.Column="4" Grid.Row="0" Style="{StaticResource baseStyle}"/>
        <TextBlock Text="5" Grid.Column="5" Grid.Row="0" Style="{StaticResource baseStyle}"/>
        <TextBlock Text="6" Grid.Column="6" Grid.Row="0" Style="{StaticResource baseStyle}"/>
        <TextBlock Text="7" Grid.Column="7" Grid.Row="0" Style="{StaticResource baseStyle}"/>
        <TextBlock Text="8" Grid.Column="8" Grid.Row="0" Style="{StaticResource baseStyle}"/>
        <TextBlock Text="9" Grid.Column="9" Grid.Row="0" Style="{StaticResource baseStyle}"/>
        <TextBlock Text="10" Grid.Column="10" Grid.Row="0" Style="{StaticResource baseStyle}"/>
        <TextBlock Text="11" Grid.Column="11" Grid.Row="0" Style="{StaticResource baseStyle}"/>
        <TextBlock Text="12" Grid.Column="12" Grid.Row="0" Style="{StaticResource baseStyle}"/>
        <TextBlock Text="13" Grid.Column="13" Grid.Row="0" Style="{StaticResource baseStyle}"/>
        <TextBlock Text="14" Grid.Column="14" Grid.Row="0" Style="{StaticResource baseStyle}"/>
        <TextBlock Text="15" Grid.Column="15" Grid.Row="0" Style="{StaticResource baseStyle}"/>
        <TextBlock Text="16" Grid.Column="16" Grid.Row="0" Style="{StaticResource baseStyle}"/>
        <TextBlock Text="17" Grid.Column="17" Grid.Row="0" Style="{StaticResource baseStyle}"/>
        <TextBlock Text="18" Grid.Column="18" Grid.Row="0" Style="{StaticResource baseStyle}"/>

        <!-- series quimicas-->
        <Grid Grid.Column="4" Grid.Row="1" Grid.ColumnSpan="9" Grid.RowSpan="3" Height="183" VerticalAlignment="Bottom">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="50"/>
                <ColumnDefinition Width="50"/>
                <ColumnDefinition Width="50"/>
                <ColumnDefinition Width="50"/>
                <ColumnDefinition Width="50"/>
                <ColumnDefinition Width="50"/>
                <ColumnDefinition Width="50"/>
                <ColumnDefinition Width="50"/>
                <ColumnDefinition Width="50"/>
                <ColumnDefinition Width="50"/>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="35"/>
                <RowDefinition Height="35"/>
                <RowDefinition Height="35"/>
                <RowDefinition Height="35"/>
                <RowDefinition Height="35"/>
            </Grid.RowDefinitions>
            <!--Metaloides-->
            <Grid Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="2" Grid.RowSpan="2" Margin="2,0,2,2">
                <Border Width="auto" Height="auto"  Background="{Binding Path=SerialChemical[1], Converter={StaticResource sctcc}}" CornerRadius="10,0,0,0"/>
                <TextBlock Text="{Binding Path=SerialChemical[1]}" Style="{StaticResource serialChemicalStyle}"/>
            </Grid>
            <!--No metales-->
            <Border Grid.Row="0" Grid.Column="3" Grid.ColumnSpan="6" BorderBrush="MidnightBlue" BorderThickness="2,2,2,0" CornerRadius="0,10,0,0" Margin="2"/>
            <TextBlock Text="No metales" HorizontalAlignment="Stretch" Grid.Row="0" Grid.Column="3" Grid.ColumnSpan="6" TextAlignment="Center" FontSize="19" VerticalAlignment="Top"/>
            <Grid Grid.Row="0" Grid.Column="3" Grid.ColumnSpan="2" Grid.RowSpan="2" Margin="2" VerticalAlignment="Bottom">
                <Border Width="auto" Height="auto"  Background="{Binding Path=SerialChemical[2], Converter={StaticResource sctcc}}" CornerRadius="0,0,0,8"/>
                <TextBlock Text="{Binding Path=SerialChemical[2]}" Style="{StaticResource serialChemicalStyle}" FontSize="10"/>
            </Grid>
            <Grid Grid.Row="0" Grid.Column="5" Grid.ColumnSpan="2" Grid.RowSpan="2" Margin="2" VerticalAlignment="Bottom">
                <Border Width="auto" Height="auto"  Background="{Binding Path=SerialChemical[3], Converter={StaticResource sctcc}}"/>
                <TextBlock Text="{Binding Path=SerialChemical[3]}" Style="{StaticResource serialChemicalStyle}"/>
            </Grid>
            <Grid Grid.Row="0" Grid.Column="7" Grid.ColumnSpan="2" Grid.RowSpan="2" Margin="2" VerticalAlignment="Bottom">
                <Border Width="auto" Height="auto" Background="{Binding Path=SerialChemical[7], Converter={StaticResource sctcc}}" CornerRadius="0,0,8,0"/>
                <TextBlock Text="{Binding Path=SerialChemical[7]}" Style="{StaticResource serialChemicalStyle}"/>
            </Grid>
            <!--Metales-->
            <Border Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="10" BorderBrush="MidnightBlue" BorderThickness="2,2,2,0" CornerRadius="10,10,0,0" Margin="2"/>
            <TextBlock Text="Metales" HorizontalAlignment="Stretch" Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="10" TextAlignment="Center" FontSize="19" VerticalAlignment="Top"/>

            <Grid Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2" Grid.RowSpan="2" Margin="2">
                <Border Width="auto" Height="auto" Background="{Binding Path=SerialChemical[5], Converter={StaticResource sctcc}}" CornerRadius="0,0,0,10"/>
                <TextBlock Text="{Binding Path=SerialChemical[5]}" Style="{StaticResource serialChemicalStyle}"/>
            </Grid>
            <Grid Grid.Row="3" Grid.Column="2" Grid.ColumnSpan="2" Grid.RowSpan="2" Margin="2">
                <Border Width="auto" Height="auto" Background="{Binding Path=SerialChemical[4], Converter={StaticResource sctcc}}" />
                <TextBlock Text="{Binding Path=SerialChemical[4]}" Style="{StaticResource serialChemicalStyle}" FontSize="11"/>
            </Grid>
            <Grid Grid.Row="3" Grid.Column="4" Grid.ColumnSpan="2" Margin="2">
                <Border Width="auto" Height="auto" Background="{Binding Path=SerialChemical[8], Converter={StaticResource sctcc}}" />
                <TextBlock Text="{Binding Path=SerialChemical[8]}" Style="{StaticResource serialChemicalStyle}"/>
            </Grid>
            <Grid Grid.Row="4" Grid.Column="4" Grid.ColumnSpan="2" Margin="2">
                <Border Width="auto" Height="auto" Background="{Binding Path=SerialChemical[9], Converter={StaticResource sctcc}}" />
                <TextBlock Text="{Binding Path=SerialChemical[9]}" Style="{StaticResource serialChemicalStyle}"/>
            </Grid>
            <Grid Grid.Row="3" Grid.Column="6" Grid.ColumnSpan="2" Grid.RowSpan="2" Margin="2">
                <Border Width="auto" Height="auto" Background="{Binding Path=SerialChemical[6], Converter={StaticResource sctcc}}" />
                <TextBlock Text="{Binding Path=SerialChemical[6]}" Style="{StaticResource serialChemicalStyle}"/>
            </Grid>
            <Grid Grid.Row="3" Grid.Column="8" Grid.ColumnSpan="2" Grid.RowSpan="2" Margin="2">
                <Border Width="auto" Height="auto" Background="{Binding Path=SerialChemical[0], Converter={StaticResource sctcc}}" CornerRadius="0,0,10,0"/>
                <TextBlock Text="{Binding Path=SerialChemical[0]}" Style="{StaticResource serialChemicalStyle}"/>
            </Grid>

        </Grid>

        <!-- Elementos -->
        <Grid Grid.Column="2" Grid.Row="1">
            <Border BorderBrush="{DynamicResource AccentColorBrush}" BorderThickness="1" Width="auto" Height="auto" Margin="1"/>
            <StackPanel>
                <TextBlock x:Name="AtomicNumberTextBlock" Text="NºAtóm." FontSize="12" TextAlignment="Center"/>
                <TextBlock x:Name="SymbolTextBlock" Text="Símbolo" FontSize="14" TextAlignment="Center" FontWeight="Bold"/>
                <TextBlock x:Name="NameTextBlock" Text="Nombre" FontSize="12" TextAlignment="Center"/>
            </StackPanel>
        </Grid>
        <StackPanel Grid.Column="3" Grid.Row="1" Grid.RowSpan="2">
            <TextBlock Text="{Binding States[0]}" Foreground="{Binding States[0], Converter={StaticResource sstcc}}" FontSize="14" FontWeight="Bold" TextAlignment="Center"/>
            <TextBlock Text="{Binding States[1]}" Foreground="{Binding States[1], Converter={StaticResource sstcc}}" FontSize="14" FontWeight="Bold" TextAlignment="Center"/>
            <TextBlock Text="{Binding States[2]}" Foreground="{Binding States[2], Converter={StaticResource sstcc}}" FontSize="14" FontWeight="Bold" TextAlignment="Center"/>
            <TextBlock Text="{Binding States[3]}" Foreground="{Binding States[3], Converter={StaticResource sstcc}}" FontSize="14" FontWeight="Bold" TextAlignment="Center"/>
        </StackPanel>
        <local:ElementControl DataContext="{Binding Elements[0]}" Grid.Column="1" Grid.Row="1"/>
        <local:ElementControl DataContext="{Binding Elements[1]}" Grid.Column="18" Grid.Row="1"/>
        <local:ElementControl DataContext="{Binding Elements[2]}" Grid.Column="1" Grid.Row="2"/>
        <local:ElementControl DataContext="{Binding Elements[3]}" Grid.Column="2" Grid.Row="2"/>
        <local:ElementControl DataContext="{Binding Elements[4]}" Grid.Column="13" Grid.Row="2"/>
        <local:ElementControl DataContext="{Binding Elements[5]}" Grid.Column="14" Grid.Row="2"/>
        <local:ElementControl DataContext="{Binding Elements[6]}" Grid.Column="15" Grid.Row="2"/>
        <local:ElementControl DataContext="{Binding Elements[7]}" Grid.Column="16" Grid.Row="2"/>
        <local:ElementControl DataContext="{Binding Elements[8]}" Grid.Column="17" Grid.Row="2"/>
        <local:ElementControl DataContext="{Binding Elements[9]}" Grid.Column="18" Grid.Row="2"/>
        <local:ElementControl DataContext="{Binding Elements[10]}" Grid.Column="1" Grid.Row="3"/>
        <local:ElementControl DataContext="{Binding Elements[11]}" Grid.Column="2" Grid.Row="3"/>
        <local:ElementControl DataContext="{Binding Elements[12]}" Grid.Column="13" Grid.Row="3"/>
        <local:ElementControl DataContext="{Binding Elements[13]}" Grid.Column="14" Grid.Row="3"/>
        <local:ElementControl DataContext="{Binding Elements[14]}" Grid.Column="15" Grid.Row="3"/>
        <local:ElementControl DataContext="{Binding Elements[15]}" Grid.Column="16" Grid.Row="3"/>
        <local:ElementControl DataContext="{Binding Elements[16]}" Grid.Column="17" Grid.Row="3"/>
        <local:ElementControl DataContext="{Binding Elements[17]}" Grid.Column="18" Grid.Row="3"/>
        <local:ElementControl DataContext="{Binding Elements[18]}" Grid.Column="1" Grid.Row="4"/>
        <local:ElementControl DataContext="{Binding Elements[19]}" Grid.Column="2" Grid.Row="4"/>
        <local:ElementControl DataContext="{Binding Elements[20]}" Grid.Column="3" Grid.Row="4"/>
        <local:ElementControl DataContext="{Binding Elements[21]}" Grid.Column="4" Grid.Row="4"/>
        <local:ElementControl DataContext="{Binding Elements[22]}" Grid.Column="5" Grid.Row="4"/>
        <local:ElementControl DataContext="{Binding Elements[23]}" Grid.Column="6" Grid.Row="4"/>
        <local:ElementControl DataContext="{Binding Elements[24]}" Grid.Column="7" Grid.Row="4"/>
        <local:ElementControl DataContext="{Binding Elements[25]}" Grid.Column="8" Grid.Row="4"/>
        <local:ElementControl DataContext="{Binding Elements[26]}" Grid.Column="9" Grid.Row="4"/>
        <local:ElementControl DataContext="{Binding Elements[27]}" Grid.Column="10" Grid.Row="4"/>
        <local:ElementControl DataContext="{Binding Elements[28]}" Grid.Column="11" Grid.Row="4"/>
        <local:ElementControl DataContext="{Binding Elements[29]}" Grid.Column="12" Grid.Row="4"/>
        <local:ElementControl DataContext="{Binding Elements[30]}" Grid.Column="13" Grid.Row="4"/>
        <local:ElementControl DataContext="{Binding Elements[31]}" Grid.Column="14" Grid.Row="4"/>
        <local:ElementControl DataContext="{Binding Elements[32]}" Grid.Column="15" Grid.Row="4"/>
        <local:ElementControl DataContext="{Binding Elements[33]}" Grid.Column="16" Grid.Row="4"/>
        <local:ElementControl DataContext="{Binding Elements[34]}" Grid.Column="17" Grid.Row="4"/>
        <local:ElementControl DataContext="{Binding Elements[35]}" Grid.Column="18" Grid.Row="4"/>
        <local:ElementControl DataContext="{Binding Elements[36]}" Grid.Column="1" Grid.Row="5"/>
        <local:ElementControl DataContext="{Binding Elements[37]}" Grid.Column="2" Grid.Row="5"/>
        <local:ElementControl DataContext="{Binding Elements[38]}" Grid.Column="3" Grid.Row="5"/>
        <local:ElementControl DataContext="{Binding Elements[39]}" Grid.Column="4" Grid.Row="5"/>
        <local:ElementControl DataContext="{Binding Elements[40]}" Grid.Column="5" Grid.Row="5"/>
        <local:ElementControl DataContext="{Binding Elements[41]}" Grid.Column="6" Grid.Row="5"/>
        <local:ElementControl DataContext="{Binding Elements[42]}" Grid.Column="7" Grid.Row="5"/>
        <local:ElementControl DataContext="{Binding Elements[43]}" Grid.Column="8" Grid.Row="5"/>
        <local:ElementControl DataContext="{Binding Elements[44]}" Grid.Column="9" Grid.Row="5"/>
        <local:ElementControl DataContext="{Binding Elements[45]}" Grid.Column="10" Grid.Row="5"/>
        <local:ElementControl DataContext="{Binding Elements[46]}" Grid.Column="11" Grid.Row="5"/>
        <local:ElementControl DataContext="{Binding Elements[47]}" Grid.Column="12" Grid.Row="5"/>
        <local:ElementControl DataContext="{Binding Elements[48]}" Grid.Column="13" Grid.Row="5"/>
        <local:ElementControl DataContext="{Binding Elements[49]}" Grid.Column="14" Grid.Row="5"/>
        <local:ElementControl DataContext="{Binding Elements[50]}" Grid.Column="15" Grid.Row="5"/>
        <local:ElementControl DataContext="{Binding Elements[51]}" Grid.Column="16" Grid.Row="5"/>
        <local:ElementControl DataContext="{Binding Elements[52]}" Grid.Column="17" Grid.Row="5"/>
        <local:ElementControl DataContext="{Binding Elements[53]}" Grid.Column="18" Grid.Row="5"/>
        <local:ElementControl DataContext="{Binding Elements[54]}" Grid.Column="1" Grid.Row="6"/>
        <local:ElementControl DataContext="{Binding Elements[55]}" Grid.Column="2" Grid.Row="6"/>

        <local:ElementControl DataContext="{Binding Elements[71]}" Grid.Column="4" Grid.Row="6"/>
        <local:ElementControl DataContext="{Binding Elements[72]}" Grid.Column="5" Grid.Row="6"/>
        <local:ElementControl DataContext="{Binding Elements[73]}" Grid.Column="6" Grid.Row="6"/>
        <local:ElementControl DataContext="{Binding Elements[74]}" Grid.Column="7" Grid.Row="6"/>
        <local:ElementControl DataContext="{Binding Elements[75]}" Grid.Column="8" Grid.Row="6"/>
        <local:ElementControl DataContext="{Binding Elements[76]}" Grid.Column="9" Grid.Row="6"/>
        <local:ElementControl DataContext="{Binding Elements[77]}" Grid.Column="10" Grid.Row="6"/>
        <local:ElementControl DataContext="{Binding Elements[78]}" Grid.Column="11" Grid.Row="6"/>
        <local:ElementControl DataContext="{Binding Elements[79]}" Grid.Column="12" Grid.Row="6"/>
        <local:ElementControl DataContext="{Binding Elements[80]}" Grid.Column="13" Grid.Row="6"/>
        <local:ElementControl DataContext="{Binding Elements[81]}" Grid.Column="14" Grid.Row="6"/>
        <local:ElementControl DataContext="{Binding Elements[82]}" Grid.Column="15" Grid.Row="6"/>
        <local:ElementControl DataContext="{Binding Elements[83]}" Grid.Column="16" Grid.Row="6"/>
        <local:ElementControl DataContext="{Binding Elements[84]}" Grid.Column="17" Grid.Row="6"/>
        <local:ElementControl DataContext="{Binding Elements[85]}" Grid.Column="18" Grid.Row="6"/>

        <local:ElementControl DataContext="{Binding Elements[86]}" Grid.Column="1" Grid.Row="7"/>
        <local:ElementControl DataContext="{Binding Elements[87]}" Grid.Column="2" Grid.Row="7"/>

        <local:ElementControl DataContext="{Binding Elements[103]}" Grid.Column="4" Grid.Row="7"/>
        <local:ElementControl DataContext="{Binding Elements[104]}" Grid.Column="5" Grid.Row="7"/>
        <local:ElementControl DataContext="{Binding Elements[105]}" Grid.Column="6" Grid.Row="7"/>
        <local:ElementControl DataContext="{Binding Elements[106]}" Grid.Column="7" Grid.Row="7"/>
        <local:ElementControl DataContext="{Binding Elements[107]}" Grid.Column="8" Grid.Row="7"/>
        <local:ElementControl DataContext="{Binding Elements[108]}" Grid.Column="9" Grid.Row="7"/>
        <local:ElementControl DataContext="{Binding Elements[109]}" Grid.Column="10" Grid.Row="7"/>
        <local:ElementControl DataContext="{Binding Elements[110]}" Grid.Column="11" Grid.Row="7"/>
        <local:ElementControl DataContext="{Binding Elements[111]}" Grid.Column="12" Grid.Row="7"/>
        <local:ElementControl DataContext="{Binding Elements[112]}" Grid.Column="13" Grid.Row="7"/>
        <local:ElementControl DataContext="{Binding Elements[113]}" Grid.Column="14" Grid.Row="7"/>
        <local:ElementControl DataContext="{Binding Elements[114]}" Grid.Column="15" Grid.Row="7"/>
        <local:ElementControl DataContext="{Binding Elements[115]}" Grid.Column="16" Grid.Row="7"/>
        <local:ElementControl DataContext="{Binding Elements[116]}" Grid.Column="17" Grid.Row="7"/>
        <local:ElementControl DataContext="{Binding Elements[117]}" Grid.Column="18" Grid.Row="7"/>

        <!--  Lineas de lantánidos y actínidos -->
        <Polygon Grid.Column="3" Grid.Row="6" Grid.RowSpan="6" Stroke="Blue"
              Grid.ColumnSpan="18" Points="2 2 2 318 58 318 58 198 962 198 962 318 975 318 975 180 58 180 58 2">
            <Polygon.Fill>
                <SolidColorBrush Color="Coral" Opacity="0.3"/>
            </Polygon.Fill>
        </Polygon>
        <!--<local:ElementControl Grid.Column="3" Grid.Row="6" Background="Beige" />
        <local:ElementControl Grid.Column="3" Grid.Row="7" Background="PaleGoldenrod" />-->
        <TextBlock Text="57-71" Grid.Column="3" Grid.Row="6" Style="{StaticResource baseStyle}" FontSize="16"/>
        <TextBlock Text="89-103" Grid.Column="3" Grid.Row="7" Style="{StaticResource baseStyle}" FontSize="16"/>

        <local:ElementControl DataContext="{Binding Elements[56]}" Grid.Column="4" Grid.Row="10"/>
        <local:ElementControl DataContext="{Binding Elements[57]}" Grid.Column="5" Grid.Row="10"/>
        <local:ElementControl DataContext="{Binding Elements[58]}" Grid.Column="6" Grid.Row="10"/>
        <local:ElementControl DataContext="{Binding Elements[59]}" Grid.Column="7" Grid.Row="10"/>
        <local:ElementControl DataContext="{Binding Elements[60]}" Grid.Column="8" Grid.Row="10"/>
        <local:ElementControl DataContext="{Binding Elements[61]}" Grid.Column="9" Grid.Row="10"/>
        <local:ElementControl DataContext="{Binding Elements[62]}" Grid.Column="10" Grid.Row="10"/>
        <local:ElementControl DataContext="{Binding Elements[63]}" Grid.Column="11" Grid.Row="10"/>
        <local:ElementControl DataContext="{Binding Elements[64]}" Grid.Column="12" Grid.Row="10"/>
        <local:ElementControl DataContext="{Binding Elements[65]}" Grid.Column="13" Grid.Row="10"/>
        <local:ElementControl DataContext="{Binding Elements[66]}" Grid.Column="14" Grid.Row="10"/>
        <local:ElementControl DataContext="{Binding Elements[67]}" Grid.Column="15" Grid.Row="10"/>
        <local:ElementControl DataContext="{Binding Elements[68]}" Grid.Column="16" Grid.Row="10"/>
        <local:ElementControl DataContext="{Binding Elements[69]}" Grid.Column="17" Grid.Row="10"/>
        <local:ElementControl DataContext="{Binding Elements[70]}" Grid.Column="18" Grid.Row="10"/>

        <local:ElementControl DataContext="{Binding Elements[88]}" Grid.Column="4" Grid.Row="11"/>
        <local:ElementControl DataContext="{Binding Elements[89]}" Grid.Column="5" Grid.Row="11"/>
        <local:ElementControl DataContext="{Binding Elements[90]}" Grid.Column="6" Grid.Row="11"/>
        <local:ElementControl DataContext="{Binding Elements[91]}" Grid.Column="7" Grid.Row="11"/>
        <local:ElementControl DataContext="{Binding Elements[92]}" Grid.Column="8" Grid.Row="11"/>
        <local:ElementControl DataContext="{Binding Elements[93]}" Grid.Column="9" Grid.Row="11"/>
        <local:ElementControl DataContext="{Binding Elements[94]}" Grid.Column="10" Grid.Row="11"/>
        <local:ElementControl DataContext="{Binding Elements[95]}" Grid.Column="11" Grid.Row="11"/>
        <local:ElementControl DataContext="{Binding Elements[96]}" Grid.Column="12" Grid.Row="11"/>
        <local:ElementControl DataContext="{Binding Elements[97]}" Grid.Column="13" Grid.Row="11"/>
        <local:ElementControl DataContext="{Binding Elements[98]}" Grid.Column="14" Grid.Row="11"/>
        <local:ElementControl DataContext="{Binding Elements[99]}" Grid.Column="15" Grid.Row="11"/>
        <local:ElementControl DataContext="{Binding Elements[100]}" Grid.Column="16" Grid.Row="11"/>
        <local:ElementControl DataContext="{Binding Elements[101]}" Grid.Column="17" Grid.Row="11"/>
        <local:ElementControl DataContext="{Binding Elements[102]}" Grid.Column="18" Grid.Row="11"/>

    </Grid>
</Controls:MetroWindow>

Con esto, podemos apreciar que podríamos implementar lo que quisiéramos sobre la tabla periódica sin necesidad de modificar ni una sola línea de código de las clases MVVM. Como ejemplo, podríamos incluir en la tabla un control Slider que cambie la temperatura, de modo que una vez superara o quedara por debajo de las temperaturas de fusión y ebullición, el estado de los elementos cambiaría y por tanto el color del símbolo también.

Y aquí el resultado

Tabla05.gif
Espero que os haya gustado y como no, si queréis que os pase el proyecto, no dudéis en escribir un comentario o ponerse en contacto conmigo.

Saludos amig@s

Nota: Esta vez el NameSpace se lo dedico, como no, al químico Dimitri Mendeleiev que descubrió el fantástico patrón con el que los elementos químicos quedan ordenados en su tabla periódica. La imagen de la cabecera, es un monumento en la ciudad de Bratislava en su honor.

Sudoku

Sudoku
El post de hoy va sobre la generación de un sudoku.
Para el que no lo sepa, un sudoku consta de una matriz de 9 cuadrados de 3×3, los cuales contienen 9 números del 1 al 9 cada uno.
La característica principal del sudoku, es que no se repite ningún número en las filas, columnas y cuadrados de 3×3. Un ejemplo de ello, sería el siguiente:sudoku01Podéis comprobar que en todas las filas, se encuentran los números del 1 al 9
los cuales no se repiten; si analizamos las columnas, ocurre lo mismo y si lo hacemos con los cuadrados de 3×3, pasa igual.Para generar una matriz completa de sudoku, debemos crear un método que inicie una secuencia y en el caso de que no sea idónea, es decir que se repita algún número o que no se pueda incluir, debe iniciarla de nuevo hasta encontrar una que lo sea, pero para ello, debemos ayudarle a escoger el buen camino y que el número de repeticiones sea el menor posible.Llevándolo a la práctica, el método que he utilizado para generar la matriz es el siguiente:Lo primero de todo, es optimizar el rendimiento de nuestra aplicación y para hacerlo, nos ayudaremos del objeto List, el cual permite añadir (Add), eliminar(remove) y obtener un objeto mediante su índice. En él, añado los números del 1 al 9 y obteniendo un número aleatorio desde 0 al número de objetos de la lista, puedo ir extrayendo como si de un bombo se tratara cada número, así nos aseguramos que los números no se repiten.En primer lugar, esta primera secuencia la creamos por filas.

En cada una de las posiciones, elimino los elementos que están a la izquierda o en su misma fila, elimino los que están por encima o los que pertenecen a la misma columna y por último, elimino los que se encuentran en el mismo cuadrado de 3×3.

De estos números candidatos, debemos elegir el más idóneo, pero… dejando que el azar haga su trabajo y para realizar esto, calculo la probabilidad de incluir el número en la fila o lo que es lo mismo, determinar el número de veces que puede incluirse en las siguientes posiciones. Recordemos que la probabilidad de un suceso es igual al número de casos favorables o el número de casos que cumplen con nuestro objetivo, dividido entre el número de casos totales o posibles. En nuestro caso, si estamos decidiendo obtener el número central del cuadrado, el número con coordenadas 4,4 (en la imagen equivale al número 7 central, las coordenadas van desde 0 hasta 8), la probabilidad de incluir el número 7 en las siguientes columnas sería de 3/5, es decir 3 columnas en las que es posible incluir el número 7 de las 5 que quedan.

sudoku02

Pongamos un ejemplo

sudoku03

La posición con coordenadas 4,5, tiene como números candidatos el 1 y el 6; el 1 tiene un 50 % de probabilidad de colocarse en las siguientes columnas, (puede incluirse en la columna 5 y 8 sobre 4 columnas)  y el 6 un 75% (puede incluirse en la columna 5,6 y 8 sobre 4 columnas), por tanto lo que hacemos es “incentivar” a los que tienen menos probabilidad con un número igual a 1-probabilidad o probabilidad de no ocurrencia, en este caso creo un conjunto con un número igual a la probabilidad de no ocurrencia multiplicado por 10; en el ejemplo creo un conjunto de 0.5 x 10=5 números 1 y 0.25 x 10=2,5 o 2 números 6 {1,1,1,1,1,6,6}, seleccionamos un número al azar y ya tenemos nuestra elección, el número 1 con más probabilidad de incluirse que el 6, pero ambos están en el bombo y así no descartamos futuras combinaciones válidas.

La secuencia va aumentando hasta que, o no tiene ningún candidato en alguna de las posiciones y por tanto debe comenzar de nuevo o llega al final de la secuencia con una combinación de números adecuada gracias a nuestra ayuda probabilística.

Esto aplicado a código, lo hago con C# y WPF, me encanta escribir y no escribir código! me encanta escribir clases independientes que no tengan ninguna relación con la interfaz gráfica y me encanta crear formularios sin código (C# o VB .NET).

Como clase ViewModel, la clase SudokuManager, las dos implementan la interfaz INotifiPropertyChanged para que podamos enlazar los datos de nuestro código XAML y de las cuales podemos mostrarlas mediante el siguiente diagrama de clases

sudoku04.png

Creo una clase llamada Point para cada posición de la matriz con las coordenadas, el valor de la casilla, una propiedad de solo lectura para calcular el cudrado de 3×3 al que pertenece y si la posición es visible para ocultarla en el futuro y poder calcularla.

En la clase SudokuManager, tenemos una propiedad que es una colección de estas 81 posiciones, dos propiedades una para obtener el tiempo de generación en milisegundos y otra para ver el número de iteraciones que ha necesitado para generar el sudoku completo y una vez que este ha sido creado correctamente, la propiedad IsGood pasa a True.

Como métodos públicos, Start para generar una matriz nueva y ToString para obtener la lista completa textualmente. Tengo otros métodos para ocultar los números en base a la dificultad y poder jugar o poder calcular el Sudoku automáticamente, pero eso quedará para otro día.

Como maquinaria interna o métodos privados, el principal es el método setSelection al cual le pasamos como parámetro el objeto Point o cuadrícula de la matriz, de la lista inicial del 1 al 9, le quita los elementos de su izquierda o de su misma fila, los elementos superiores o de su columna y los elementos de su cuadrícula de 3×3, una vez hecho esto, solo quedarían los números candidatos y llamaría al método getSelection, el cual calcula la probabilidad que hemos explicado y obtiene el número definitivo que se le asignará a la posición.

Una vez hecho esto, vamos a ver si el rendimiento es adecuado, ya que imaginad que para generar una matriz, necesitara 5 segundos, algo inaceptable para los tiempos que corren, por lo que he realizado varias muestras de 50 generaciones y de todas ellas, obtengo un promedio de 53,58 iteraciones y un promedio de 192 milisegundos por cada una, estando el valor mínimo de 1 iteración y 5 ms (con un I5 2400, que no es un I7 6500!)  y unos valores máximos de 165 iteraciones y medio segundo de los cuales hay que decir que tienen una probabilidad de salir de un 2%, o una probabilidad de un 16% de superar las 100 iteraciones y los 350ms.

Bueno, para ser el primero, creo que no está mal, seguro que se pueden arañar algún milisegundo en alguna operación.

En cuanto al XAML, he creado un control de usuario para que no sea muy extenso el código de la página principal, al cual le pasamos como DataContext la clase SudokuManager y a cada posición, le asignamos como DataContext el objeto Point que le pertenece, de modo que desde el estilo, podemos hacer un Binding a sus propiedades, un poco de código como muestra

<UserControl x:Class="Ramanujan.Matriz"
             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:Ramanujan"
             mc:Ignorable="d" 
             d:DesignHeight="490" d:DesignWidth="490">
    <Grid x:Name="grid">        
        <Grid.Resources>
            <!-- CONVERTER -->
            <local:BoolToVisibleConverter x:Key="b2vc"/>
            <!-- ESTILO DE TEXTBLOCK -->
            <Style x:Key="textBlockStyle" TargetType="TextBlock">
                <Setter Property="HorizontalAlignment" Value="Center"/>
                <Setter Property="VerticalAlignment" Value="Center"/>
                <Setter Property="TextAlignment" Value="Center"/>
                <Setter Property="FontSize" Value="24"/>
                <Setter Property="Visibility" Value="{Binding IsVisible,Converter={StaticResource b2vc}}"/>
                <Setter Property="Text" Value="{Binding Value}"/>
            </Style>
        </Grid.Resources>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="20"/>
            <ColumnDefinition Width="50"/>
            <ColumnDefinition Width="50"/>
            <ColumnDefinition Width="50"/>
            <ColumnDefinition Width="50"/>
            <ColumnDefinition Width="50"/>
            <ColumnDefinition Width="50"/>
            <ColumnDefinition Width="50"/>
            <ColumnDefinition Width="50"/>
            <ColumnDefinition Width="50"/>
            <ColumnDefinition Width="20*"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="20"/>
            <RowDefinition Height="50"/>
            <RowDefinition Height="50"/>
            <RowDefinition Height="50"/>
            <RowDefinition Height="50"/>
            <RowDefinition Height="50"/>
            <RowDefinition Height="50"/>
            <RowDefinition Height="50"/>
            <RowDefinition Height="50"/>
            <RowDefinition Height="50"/>
            <RowDefinition Height="20*"/>
        </Grid.RowDefinitions>
        <Border Grid.Column="1" Grid.Row="1" Grid.RowSpan="9" Grid.ColumnSpan="9" BorderBrush="WhiteSmoke" BorderThickness="2"/>
        <Border Grid.Column="1" Grid.Row="1" Grid.RowSpan="3" Grid.ColumnSpan="3" BorderBrush="WhiteSmoke" BorderThickness="1"/>
        <Border Grid.Column="4" Grid.Row="1" Grid.RowSpan="3" Grid.ColumnSpan="3" BorderBrush="WhiteSmoke" BorderThickness="1"/>
        <Border Grid.Column="7" Grid.Row="1" Grid.RowSpan="3" Grid.ColumnSpan="3" BorderBrush="WhiteSmoke" BorderThickness="1"/>
        <Border Grid.Column="1" Grid.Row="4" Grid.RowSpan="3" Grid.ColumnSpan="3" BorderBrush="WhiteSmoke" BorderThickness="1"/>
        <Border Grid.Column="4" Grid.Row="4" Grid.RowSpan="3" Grid.ColumnSpan="3" BorderBrush="WhiteSmoke" BorderThickness="1"/>
        <Border Grid.Column="7" Grid.Row="4" Grid.RowSpan="3" Grid.ColumnSpan="3" BorderBrush="WhiteSmoke" BorderThickness="1"/>
        <Border Grid.Column="1" Grid.Row="7" Grid.RowSpan="3" Grid.ColumnSpan="3" BorderBrush="WhiteSmoke" BorderThickness="1"/>
        <Border Grid.Column="4" Grid.Row="7" Grid.RowSpan="3" Grid.ColumnSpan="3" BorderBrush="WhiteSmoke" BorderThickness="1"/>
        <Border Grid.Column="7" Grid.Row="7" Grid.RowSpan="3" Grid.ColumnSpan="3" BorderBrush="WhiteSmoke" BorderThickness="1"/>
        <TextBlock x:Name="textBlock00" DataContext="{Binding Points[0]}" Grid.Column="1" Grid.Row="1" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock01" DataContext="{Binding Points[1]}" Grid.Column="2" Grid.Row="1" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock02" DataContext="{Binding Points[2]}" Grid.Column="3" Grid.Row="1" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock03" DataContext="{Binding Points[3]}" Grid.Column="4" Grid.Row="1" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock04" DataContext="{Binding Points[4]}" Grid.Column="5" Grid.Row="1" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock05" DataContext="{Binding Points[5]}" Grid.Column="6" Grid.Row="1" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock06" DataContext="{Binding Points[6]}" Grid.Column="7" Grid.Row="1" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock07" DataContext="{Binding Points[7]}" Grid.Column="8" Grid.Row="1" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock08" DataContext="{Binding Points[8]}" Grid.Column="9" Grid.Row="1" Style="{StaticResource textBlockStyle}"/>

        <TextBlock x:Name="textBlock10" DataContext="{Binding Points[9]}" Grid.Column="1" Grid.Row="2" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock11" DataContext="{Binding Points[10]}" Grid.Column="2" Grid.Row="2" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock12" DataContext="{Binding Points[11]}" Grid.Column="3" Grid.Row="2" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock13" DataContext="{Binding Points[12]}" Grid.Column="4" Grid.Row="2" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock14" DataContext="{Binding Points[13]}" Grid.Column="5" Grid.Row="2" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock15" DataContext="{Binding Points[14]}" Grid.Column="6" Grid.Row="2" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock16" DataContext="{Binding Points[15]}" Grid.Column="7" Grid.Row="2" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock17" DataContext="{Binding Points[16]}" Grid.Column="8" Grid.Row="2" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock18" DataContext="{Binding Points[17]}" Grid.Column="9" Grid.Row="2" Style="{StaticResource textBlockStyle}"/>

        <TextBlock x:Name="textBlock20" DataContext="{Binding Points[18]}" Grid.Column="1" Grid.Row="3" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock21" DataContext="{Binding Points[19]}" Grid.Column="2" Grid.Row="3" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock22" DataContext="{Binding Points[20]}" Grid.Column="3" Grid.Row="3" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock23" DataContext="{Binding Points[21]}" Grid.Column="4" Grid.Row="3" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock24" DataContext="{Binding Points[22]}" Grid.Column="5" Grid.Row="3" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock25" DataContext="{Binding Points[23]}" Grid.Column="6" Grid.Row="3" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock26" DataContext="{Binding Points[24]}" Grid.Column="7" Grid.Row="3" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock27" DataContext="{Binding Points[25]}" Grid.Column="8" Grid.Row="3" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock28" DataContext="{Binding Points[26]}" Grid.Column="9" Grid.Row="3" Style="{StaticResource textBlockStyle}"/>

        <TextBlock x:Name="textBlock30" DataContext="{Binding Points[27]}" Grid.Column="1" Grid.Row="4" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock31" DataContext="{Binding Points[28]}" Grid.Column="2" Grid.Row="4" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock32" DataContext="{Binding Points[29]}" Grid.Column="3" Grid.Row="4" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock33" DataContext="{Binding Points[30]}" Grid.Column="4" Grid.Row="4" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock34" DataContext="{Binding Points[31]}" Grid.Column="5" Grid.Row="4" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock35" DataContext="{Binding Points[32]}" Grid.Column="6" Grid.Row="4" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock36" DataContext="{Binding Points[33]}" Grid.Column="7" Grid.Row="4" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock37" DataContext="{Binding Points[34]}" Grid.Column="8" Grid.Row="4" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock38" DataContext="{Binding Points[35]}" Grid.Column="9" Grid.Row="4" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock40" DataContext="{Binding Points[36]}" Grid.Column="1" Grid.Row="5" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock41" DataContext="{Binding Points[37]}" Grid.Column="2" Grid.Row="5" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock42" DataContext="{Binding Points[38]}" Grid.Column="3" Grid.Row="5" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock43" DataContext="{Binding Points[39]}" Grid.Column="4" Grid.Row="5" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock44" DataContext="{Binding Points[40]}" Grid.Column="5" Grid.Row="5" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock45" DataContext="{Binding Points[41]}" Grid.Column="6" Grid.Row="5" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock46" DataContext="{Binding Points[42]}" Grid.Column="7" Grid.Row="5" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock47" DataContext="{Binding Points[43]}" Grid.Column="8" Grid.Row="5" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock48" DataContext="{Binding Points[44]}" Grid.Column="9" Grid.Row="5" Style="{StaticResource textBlockStyle}"/>

        <TextBlock x:Name="textBlock50" DataContext="{Binding Points[45]}" Grid.Column="1" Grid.Row="6" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock51" DataContext="{Binding Points[46]}" Grid.Column="2" Grid.Row="6" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock52" DataContext="{Binding Points[47]}" Grid.Column="3" Grid.Row="6" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock53" DataContext="{Binding Points[48]}" Grid.Column="4" Grid.Row="6" Style="{StaticResource textBlockStyle}"/> 
        <TextBlock x:Name="textBlock54" DataContext="{Binding Points[49]}" Grid.Column="5" Grid.Row="6" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock55" DataContext="{Binding Points[50]}" Grid.Column="6" Grid.Row="6" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock56" DataContext="{Binding Points[51]}" Grid.Column="7" Grid.Row="6" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock57" DataContext="{Binding Points[52]}" Grid.Column="8" Grid.Row="6" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock58" DataContext="{Binding Points[53]}" Grid.Column="9" Grid.Row="6" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock60" DataContext="{Binding Points[54]}" Grid.Column="1" Grid.Row="7" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock61" DataContext="{Binding Points[55]}" Grid.Column="2" Grid.Row="7" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock62" DataContext="{Binding Points[56]}" Grid.Column="3" Grid.Row="7" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock63" DataContext="{Binding Points[57]}" Grid.Column="4" Grid.Row="7" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock64" DataContext="{Binding Points[58]}" Grid.Column="5" Grid.Row="7" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock65" DataContext="{Binding Points[59]}" Grid.Column="6" Grid.Row="7" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock66" DataContext="{Binding Points[60]}" Grid.Column="7" Grid.Row="7" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock67" DataContext="{Binding Points[61]}" Grid.Column="8" Grid.Row="7" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock68" DataContext="{Binding Points[62]}" Grid.Column="9" Grid.Row="7" Style="{StaticResource textBlockStyle}"/>

        <TextBlock x:Name="textBlock70" DataContext="{Binding Points[63]}" Grid.Column="1" Grid.Row="8" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock71" DataContext="{Binding Points[64]}" Grid.Column="2" Grid.Row="8" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock72" DataContext="{Binding Points[65]}" Grid.Column="3" Grid.Row="8" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock73" DataContext="{Binding Points[66]}" Grid.Column="4" Grid.Row="8" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock74" DataContext="{Binding Points[67]}" Grid.Column="5" Grid.Row="8" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock75" DataContext="{Binding Points[68]}" Grid.Column="6" Grid.Row="8" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock76" DataContext="{Binding Points[69]}" Grid.Column="7" Grid.Row="8" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock77" DataContext="{Binding Points[70]}" Grid.Column="8" Grid.Row="8" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock78" DataContext="{Binding Points[71]}" Grid.Column="9" Grid.Row="8" Style="{StaticResource textBlockStyle}"/>

        <TextBlock x:Name="textBlock80" DataContext="{Binding Points[72]}" Grid.Column="1" Grid.Row="9" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock81" DataContext="{Binding Points[73]}" Grid.Column="2" Grid.Row="9" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock82" DataContext="{Binding Points[74]}" Grid.Column="3" Grid.Row="9" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock83" DataContext="{Binding Points[75]}" Grid.Column="4" Grid.Row="9" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock84" DataContext="{Binding Points[76]}" Grid.Column="5" Grid.Row="9" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock85" DataContext="{Binding Points[77]}" Grid.Column="6" Grid.Row="9" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock86" DataContext="{Binding Points[78]}" Grid.Column="7" Grid.Row="9" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock87" DataContext="{Binding Points[79]}" Grid.Column="8" Grid.Row="9" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock88" DataContext="{Binding Points[80]}" Grid.Column="9" Grid.Row="9" Style="{StaticResource textBlockStyle}"/>

    </Grid>

en cuanto a la clase generadora del Sudoku, os dejo el código de generación, si alguien lo necesita, que no dude en solicitarme el proyecto que gustosamente se lo pasaré.

Os dejo un gif de como funciona. Un saludos amig@s

sudoku06.gif

Pd: El espacio de nombres no tiene nada que ver con los sudokus, pero a cada proyecto de visual studio, le asigno un nombre de un ilustre matemático o físico y en este caso le ha tocado a Ramanujan.

/* ****************************************************************
* © JOAQUIN MARTINEZ RUS 2016
* PROYECTO:        Clase generadora de Sudoku
* Archivo:         SudokuManager.cs
* Descripción:     Clase SudokuManager.
 *
* Historial:
*                  1. 09 sep 2016. Creación
*
* Comentarios:      Genera un sudoku mediante iteraciones erróneas
*
*
*******************************************************************/
using System;
using System.Collections.Generic;
using System.Linq;
using System.ComponentModel;

namespace Ramanujan
{
    public class SudokuManager : INotifyPropertyChanged
    {
        #region Constructor

        public SudokuManager()
        {
            Points = new List();
        }

        #endregion

        #region Properties and fields

        public List Points { get; set; }

        List initialCollection = new List();
        List selection = new List();
        public bool IsGood { get; set; }
        private int iterations;
        public int Iterations
        {
            get { return iterations; }
            set
            {
                iterations = value;
                OnPropertyChanged("Iterations");
            }
        }
        private double miliseconds;

        public double Miliseconds
        {
            get { return miliseconds; }
            set
            {
                miliseconds = value;
                OnPropertyChanged("Miliseconds");
                OnPropertyChanged("Points");
            }
        }

        #endregion

        #region Events

        public event PropertyChangedEventHandler PropertyChanged;

        public void OnPropertyChanged(string _PropertyName)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(_PropertyName));
            }
        }

        #endregion

        #region Public methods

        public bool Start()
        {
            reset();
            Iterations++;
            foreach (var item in Points)
            {
                int number = setSelection(item);
                if (number != -1)
                {
                    item.Value = number;
                    item.IsVisible = true;
                }
                else
                {
                    IsGood = false;
                    return IsGood;
                }
            }
            IsGood = true;

            return IsGood;
        }

        public string ToString()
        {
            string textOut = "";
            foreach (var item in Points)
            {
                textOut += item.Value.ToString() + " ";
                if (item.YCoor == 8)
                {
                    textOut += "\n";
                }

            }
            return textOut;
        }

        #endregion

        #region Private Methods

        private void reset()
        {
            initialCollection.Clear();
            selection.Clear();
            Points.Clear();
            for (int i = 1; i < 10; i++)
            {
                initialCollection.Add(i);
            }

            setCoordinates();
        }

        private void setCoordinates()
        {
            for (int i = 0; i  Points.Where(k => k.YCoor == y).Select(k => k.Value).Contains(n));

            // eliminar los elementos cuya coordenada x sea igual que la del punto
            var xx = Points.Where(k => k.XCoor == x && k.Value != 0);
            selection.RemoveAll(n => Points.Where(k => k.XCoor == x).Select(k => k.Value).Contains(n));

            // eliminar los elementos del cuadrado
            var square = getFirstSquareNumber(Points.IndexOf(p));
            selection.RemoveAll(n => square.Contains(n));

            return getElection(p);
        }

        private KeyValuePair getProbability(int n, Point p)
        {
            int nColExistNumber = 0;
            for (int i = p.YCoor + 1; i  h.Value).First();
                    if (k == n)
                    {
                        nColExistNumber++;
                    }
                }
            }
            double casosTotales = 9 - p.XCoor;
            double probability = (casosTotales - nColExistNumber) / casosTotales;
            return new KeyValuePair(n, probability);
        }

        private int getElection(Point p)
        {
            List values = new List();
            foreach (var item in selection)
            {
                values.Add(getProbability(item, p));

            }

            List candidatosTemporales = new List();
            foreach (var item in values)
            {
                if (item.Value == 1)
                {
                    candidatosTemporales.Add(item.Key);
                }
                else
                {
                    for (int i = 0; i  0 ? candidatosTemporales[index] : -1;
            return r;
        }

        private List getFirstSquareNumber(int index)
        {
            List numbers = new List();
            int restoX = index % 3;
            int multiplo = (index / 9) % 3;
            int first = index - restoX - 9 * multiplo;
            int nextIndex = 0;

            for (int i = 0; i < 3; i++)
            {
                for (int j = 0; j < 3; j++)
                {
                    nextIndex = first + i * 9 + j;
                    if (Points[nextIndex].Value != 0)
                    {
                        numbers.Add(Points[nextIndex].Value);
                    }
                }
            }
            return numbers;
        }

        #endregion

    }

    public class Point:INotifyPropertyChanged
    {
        public Point(int x, int y)
        {
            XCoor = x;
            YCoor = y;
        }

        public int XCoor { get; set; }
        public int YCoor { get; set; }
        public int Value { get; set; }
        private bool isVisible=true;

        public bool IsVisible
        {
            get { return isVisible; }
            set
            {
                isVisible = value;
                OnPropertyChanged("IsVisible");
            }
        }

        public int Square
        {
            get
            {
                int x = XCoor % 3;
                int y = YCoor % 3;
                return y + 3 * x ;
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        public void OnPropertyChanged(string _PropertyName)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(_PropertyName));
            }
        }

    }
}

Cartón de bingo

Cartón de bingo

Hoy toca la composición y como no teclear código con WPF.Digo esto porque no pretendo explicar las diferencias entre composición y agregación, lo que está claro es que la composición quiere decir que se compone, un coche se compone de motor, ruedas, chasis, etc y el coche no es nada sin estas piezas y estas piezas, podrían serlo por su cuenta, pero fueron diseñadas para funcionar en el coche, por tanto aplicándolo al cartón de bingo (que no tiene nada que ver con el coche en cuanto a fisonomía pero si a composición), este se compone de líneas y cada línea de números. Los números tienen aplicación en la vida, pero un cartón sin estos poco valdría y por supuesto un cartón sin líneas, ¿que cantaríamos? números! no, el bingo tiene sus reglas y ahí estamos los programadores para aplicar las reglas al software y llevarlas a cabo.

Si realizamos un diagrama de clases (simplificado) de un cartón de bingo, sería algo como esto

bingo01

La línea que une las clases o la asociación con el rombo relleno de negro, indica eso, que es una composición.

Sabiendo esto, vamos a diseñar un cartón de bingo más en serio y crearemos

bingo02.png

Esto es lo que se me ha ocurrido (ahora solo el diagrama), un objeto Number, el cual tiene dos propiedades, una el valor y otra si el número está marcado, un objeto Row (Línea) que contiene una colección de números y un objeto Card que contiene una colección de líneas. Por partes.

La clase Number, como ya he dicho tiene dos propiedades y además tiene dos constructores y un solo método; los constructores uno sin parámetros y otro para asignarle el número. Como la clase implementa la interfaz Icomparable<Number>, obligatoriamente el método CompareTo debemos crearlo para que posteriormente pueda ordenarse la lista de números como nosotros queremos. Y visto esto, aquí el código

    public class Number:IComparable<Number>
    {
        #region Constructor

        /// <summary>
        /// Representa un número de cartón de bingo
        /// </summary>
        public Number() { }

        /// <summary>
        /// Representa un número de cartón de bingo
        /// </summary>
        /// <param name="number">Número que se asigna como valor</param>
        public Number(int number)
        {
            this.Value = number;
        }

        #endregion

        #region Fields, properties and constants

        /// <summary>
        /// Valor del número
        /// </summary>
        public int Value { get; set; }

        /// <summary>
        /// Indica si el número está marcado o no
        /// </summary>
        public bool IsChecked { get; set; } = false;
        
        #endregion

        #region Public Methods

        /// <summary>
        /// Compara esta instancia con un objeto específico y retorna un entero que indica
        /// </summary>
        /// <param name="other">Objeto a comparar</param>
        /// <returns>Retorna un entero con signo. Negativo si es menor, un 0 si es igual y un entero positivo si es mayor.</returns>
        public int CompareTo(Number other)
        {
            int result = 0;
            if (this.Value > other.Value)
            {
                result = 1;
            }
            else if (this.Value < other.Value)
            {
                result = -1;
            }
            return result;
        }

        #endregion
    }

la clase Row o la clase de la línea le he incluido dos constructores, uno que que genera una línea en base a una lista de números disponibles, esto es porque a la línea le interesa saber que números hay que escoger para que no se repitan y el otro al que le pasamos los números en un array como parámetro.

Tiene una constante que indica la cantidad de números que tiene una línea y una colección ¿de que?, pues de la clase Number. El motor de la clase o los métodos privados, son dos, uno para obtener una lista de decenas y otro para crear la línea con sus números, los cuales no deben repetir decena ni repetir números de otras líneas. Para esto creo una lista de las decenas, 0, 10, 20, … 70 y 80, instancio la clase Random con una semilla sobre la suma de los milisegundos del tiempo actual y las decenas pendientes, esto provoca una semilla distinta en cada iteración y por tanto algo más parecido a la aleatoriedad, además le incluyo un número impar de milisegundos de retardo. Esto también nos va a generar cartones más reales y con una distribución más normal, imaginad un cartón con los 15 números agrupados desde las unidades a la columna de 40 y el resto de columnas (50, 60, 70 y 80) vacías, esos cartones son válidos, pero poca gente los querría, es como si nos dieran un billete de lotería con el 11111, está en el bombo, pero poca gente lo quiere. Después de esto,

  • Sobre la lista de decenas escojo una al azar,
  • Filtro los números disponibles para acotarlos en esa decena y vuelvo a escoger uno aleatorio
  • Elimino el número escogido de la lista de números disponibles (para que en el siguiente número no lo esté)
  • Añado el número a la colección de la línea
  • Ordeno la línea.

con esto, cada vez que genero una línea, me aseguro que no se repitan las decenas y los números por línea.

Los métodos públicos, son:

  • ToString(). Para devolver una cadena con los números de la línea, sustituyendo los espacios por asteriscos (este lo he ppuesto por si alguien lo quiere obtener por consola)
  • GetRowNumbersToString(). Este igual que el anterior, pero con la diferencia de que solo aparecen los números separados por tabulaciones (Cabe destacar el uso de ForEach en una sola línea)
  • GetRowNumber(). Que devuelve una lista de objetos Number con todos los números de la línea.

El código…

    public class Row
    {
        #region Constructor

        /// <summary>
        /// Representa una línea de cartón de bingo
        /// </summary>
        /// <param name="_availableNumbers">Lista de números disponibles desde la cual se generará los objetos Number</param>
        public Row(List<int> _availableNumbers)
        {
            CreateRow(_availableNumbers);
        }

        /// <summary>
        /// Representa una línea de cartón de bingo
        /// </summary>
        /// <param name="args">Array con los números que se usarán para generar los objetos Number</param>
        public Row(int[] args)
        {
            if (args.Length > numbersCount) throw new OverflowException(String.Format("El número máximo de números que acepta una línea es de {0}", numbersCount));
            foreach (var item in args)
            {
                Number number = new Number();
                number.Value = item;
                rowNumbers.Add(number);
            }

            rowNumbers.Sort();
        }

        #endregion

        #region Fields, properties and constants

        /// <summary>
        /// Constante que indica la cantida de números de una linea
        /// </summary>
        private const int numbersCount = 5;

        /// <summary>
        /// Lista de objetos Number
        /// </summary>
        public List<Number> rowNumbers = new List<Number>();

        #endregion

        #region Public Methods

        /// <summary>
        /// Retorna una cadena formateada por decenas con los valores de los objetos Number contenidos en esta instancia
        /// </summary>
        /// <returns>Cadena de texto con los valores de los objetos Number</returns>
        public override string ToString()
        {
            string textToString = "";

            for (int i = 0; i < 9; i++)
            {
                int decena = i * 10;
                int incremento = i == 8 ? decena + 11 : decena + 10;
                var query = this.rowNumbers.Where(k => k.Value > decena && k.Value < incremento);

                if (query.Count() > 0)
                {
                    textToString += query.First().Value + "\t";
                }
                else
                {
                    textToString += "*\t";
                }

            }

            return textToString;
        }

        /// <summary>
        /// Retorna una cadena con los valores  de los objetos Number contenidos en esta instancia
        /// </summary>
        /// <returns>Cadena de texto con los valores de los objetos Number</returns>
        public string GetRowNumbersToString()
        {
            string textToString = "";
            this.rowNumbers.ForEach(k => textToString += k.Value + "\t");
            return textToString;
        }

        public List<Number> GetRowNumber()
        {
            List<Number> numbers = new List<Number>();

            for (int i = 0; i < 9; i++)
            {
                int decena = i * 10;
                int incremento = i == 8 ? decena + 11 : decena + 10;
                var query = this.rowNumbers.Where(k => k.Value > decena && k.Value < incremento);

                if (query.Count() > 0)
                {
                    numbers.Add(query.First());
                }
                else
                {
                    numbers.Add(new Number(-1));
                }

            }

            return numbers;
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Inicia la instancia actual con objetos Number a partir de una lista de números disponibles
        /// </summary>
        /// <param name="_availableNumbers">Lista de números disponibles desde la cual se generará los objetos Number</param>
        private void CreateRow(List<int> _availableNumbers)
        {
            List<int> decenas = GetTen();
            int number = 0;
            Random rnd=new Random(DateTime.Now.Millisecond + _availableNumbers.Count);
            for (int i = 0; i < numbersCount; i++)
            {
                System.Threading.Thread.Sleep(3);
                int decena = decenas[rnd.Next(0, decenas.Count)];
                
                // Eliminar la decena de la lista para no solicitarla en la próxima petición
                decenas.Remove(decena);

                int incremento = decena == 80 ? decena + 11 : decena + 10;
                var temp = _availableNumbers.Where(k => k > decena && k < incremento);
                number = temp.ElementAt(rnd.Next(0, temp.Count()));
                _availableNumbers.Remove(number);

                rowNumbers.Add(new Number(number));
            }

            this.rowNumbers.Sort();
        }

        /// <summary>
        /// Retorna una lista con las decenas desde 0 al 80
        /// </summary>
        /// <returns></returns>
        private List<int> GetTen()
        {
            List<int> decenas = new List<int>();
            for (int i = 0; i < 9; i++)
            {
                decenas.Add(i*10);
            }
            return decenas;
        }

        #endregion
    }

Por último, la clase Card (Cartón, no ibas a pensar que algo tan sencillo como un cartón iba a tener tanta complejidad). Esta realiza lo siguiente:

  • Rellena la lista de números disponibles, los números del 1 al 90.
  • Crea un nuevo cartón, el cual instancia tres veces la clase Row,envíandole la lista de números disponibles, si en la primera línea se han añadido el 7, 23, 35,67,77 y 89, estos números una vez que se ha creado la fila, se eliminan de la lista de disponibilidad, esto lo hago para seleccionar solo los números que quedan y aumentar el rendimiento; os imagináis escoger un número del 1 al 90 y solo queda el número 43, hasta que acierte la aplicación puede pasar un tiempo no computado, mientras que si vamos extrayendo números, el tiempo siempre será el mismo
  • Llena una lista de cadenas de texto con los números del cartón con el fin de usarlos para la visualización.

Además, he implementado la interfaz IEquatable<Card> para en el futuro por si hubiera que comprobar un cartón, poder hacerlo comparando el cartón ganador. el código de la clase lo muestro a continuación (si teneís alguna duda con el código, no dudéis en preguntame y añadir un comentario, lo responderé gustosamente)

    public class Card: IEquatable<Card>,INotifyPropertyChanged
    {
        #region Constructor

        /// <summary>
        /// Representa un cartón de bingo con 3 líneas y 5 números por línea
        /// </summary>
        public Card()
        {
            FillAvailableNumbers();
            CreateNewCard();
            cardNumbers = new List<string>();

            foreach (var item in GetRowNumber().Select(k => k.Value))
            {
                cardNumbers.Add(item==-1?"":item.ToString());
            }
        }

        #endregion

        #region Fields, properties and constants

        /// <summary>
        /// Número de líneas que alberga un cartón
        /// </summary>
        private const int rowCount = 3;

        /// <summary>
        /// Lista de Líneas del cartón
        /// </summary>
        List<Row> cardRows = new List<Row>();

        /// <summary>
        /// Lista de números disponibles que se pueden agregar a un cartón 
        /// </summary>
        List<int> availableNumbers = new List<int>();

        /// <summary>
        /// Listado de número ordenados por filas para visualización
        /// </summary>
        public List<string> cardNumbers { get; set; }

        public event PropertyChangedEventHandler PropertyChanged;

        public int CardNumber { get; set; }
        public int SerialNumber { get; set; }

        #endregion

        #region Public Methods

        /// <summary>
        /// Retorna una cadena con los datos del cartón formateados
        /// </summary>
        /// <returns>Cadena detexto con los datos del cartón</returns>
        public override string ToString()
        {
            string textToString = "";

            foreach (var row in this.cardRows)
            {
                textToString += row.ToString() + "\n";
            }

            textToString += "\n";
            foreach (var row in this.cardRows)
            {
                textToString += row.GetRowNumbersToString() + "\n";
            }

            return textToString;
        }

        /// <summary>
        /// Retorna un valor que indica que esta instancia es igual a un objeto específico
        /// </summary>
        /// <param name="other">Un objeto Card a comparar con esta instancia</param>
        /// <returns>True si tiene el mismo valor que esta instancia; en caso contrario false</returns>
        public bool Equals(Card other)
        {
            bool isEquals = true;
            List<int> cardNumbersThis = GetCardNumbers(this);
            List<int> cardNumbersOther = GetCardNumbers(other);

            cardNumbersThis.ForEach(k => cardNumbersOther.Remove(k));

            if (cardNumbersOther.Count > 0) isEquals = false;

            return isEquals;
        }

        /// <summary>
        /// Retorna una lista con los números ordenados de cada fila y asignados los huecos con el valor -1
        /// </summary>
        /// <returns></returns>
        public List<Number> GetRowNumber()
        {
            List<Number> numbers = new List<Number>();

            foreach (var row in this.cardRows)
            {
                numbers.AddRange(row.GetRowNumber());
            }

            return numbers;
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Retorna una lista de todos los números del cartón, indicando los elementos vacíos con -1
        /// </summary>
        /// <param name="card">Cartón de bingo del que se extraen los datos</param>
        /// <returns>Lista de enteros con todos los números del cartón. A los elementos vacíos, se les asigna -1</returns>
        private List<int> GetCardNumbers(Card card)
        {
            List<int> cardNumbers = new List<int>();

            foreach (var row in card.cardRows)
            {
                cardNumbers.AddRange(row.rowNumbers.Select(k => k.Value));
            }

            return cardNumbers;
        }

        /// <summary>
        /// crea las tres líneas y las asigna al cartón
        /// </summary>
        private void CreateNewCard()
        {

            for (int i = 0; i < rowCount; i++)
            {
                Row row = new Row(availableNumbers);

                foreach (var item in row.rowNumbers)
                {
                    availableNumbers.Remove(item.Value);
                }

                cardRows.Add(row);
            }
        }

        /// <summary>
        /// Rellena los números disponibles para generar cartones
        /// </summary>
        private void FillAvailableNumbers()
        {
            for (int i = 1; i < 91; i++)
            {
                availableNumbers.Add(i);
            }
        }

        #endregion

    }

Y ahora  vamos a diseñar el cartón con WPF sin escribir código C#, XAML si, pero este se encarga de los errores y la robustez.

Creo un control de usuario y le añado lo siguiente

<UserControl x:Class="Bingo.CardForm"
             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:Pascal"
             mc:Ignorable="d" Height="250" Width="650">
    <Grid x:Name="CardGrid">
        <Grid.Resources>
            <Style x:Key="baseStyle" TargetType="TextBlock">
                <Setter Property="TextAlignment" Value="Center"/>
                <Setter Property="VerticalAlignment" Value="Stretch"/>
                <Setter Property="HorizontalAlignment" Value="Stretch"/>
                <Setter Property="Margin" Value="3"/>
                <Setter Property="FontSize" Value="14"/>
            </Style>
            <Style x:Key="numberStyle" TargetType="TextBlock" BasedOn="{StaticResource baseStyle}">
                <Setter Property="FontSize" Value="40"/>
                <Setter Property="Foreground" Value="DarkGray"/>
                <Setter Property="Background" Value="LightGray"/>
            </Style>
        </Grid.Resources>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="10"/>
            <ColumnDefinition Width="70"/>
            <ColumnDefinition Width="70"/>
            <ColumnDefinition Width="70"/>
            <ColumnDefinition Width="70"/>
            <ColumnDefinition Width="70"/>
            <ColumnDefinition Width="70"/>
            <ColumnDefinition Width="70"/>
            <ColumnDefinition Width="70"/>
            <ColumnDefinition Width="70"/>
            <ColumnDefinition Width="10"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="30"/>
            <RowDefinition Height="70"/>
            <RowDefinition Height="70"/>
            <RowDefinition Height="70"/>
            <RowDefinition Height="10"/>
        </Grid.RowDefinitions>
        <TextBlock Text="Cartón Nº:" Grid.Column="1" Grid.Row="0" HorizontalAlignment="Right" Style="{StaticResource baseStyle}"/>
        <TextBlock x:Name="CardNumber" Text="{Binding CardNumber}" Grid.Column="2" Grid.Row="0" Style="{StaticResource baseStyle}"/>
        <TextBlock Text="Serie:" Grid.Column="4" Grid.Row="0" HorizontalAlignment="Right" Style="{StaticResource baseStyle}"/>
        <TextBlock x:Name="SerialNumber" Text="{Binding SerialNumber}" Grid.Column="5" Grid.Row="0" Style="{StaticResource baseStyle}"/>

        <TextBlock x:Name="Row11" Text="{Binding cardNumbers[0]}" Grid.Column="1" Grid.Row="1" Style="{StaticResource numberStyle}"/>
        <TextBlock x:Name="Row12" Text="{Binding cardNumbers[1]}" Grid.Column="2" Grid.Row="1" Style="{StaticResource numberStyle}"/>
        <TextBlock x:Name="Row13" Text="{Binding cardNumbers[2]}" Grid.Column="3" Grid.Row="1" Style="{StaticResource numberStyle}"/>
        <TextBlock x:Name="Row14" Text="{Binding cardNumbers[3]}" Grid.Column="4" Grid.Row="1" Style="{StaticResource numberStyle}"/>
        <TextBlock x:Name="Row15" Text="{Binding cardNumbers[4]}" Grid.Column="5" Grid.Row="1" Style="{StaticResource numberStyle}"/>
        <TextBlock x:Name="Row16" Text="{Binding cardNumbers[5]}" Grid.Column="6" Grid.Row="1" Style="{StaticResource numberStyle}"/>
        <TextBlock x:Name="Row17" Text="{Binding cardNumbers[6]}" Grid.Column="7" Grid.Row="1" Style="{StaticResource numberStyle}"/>
        <TextBlock x:Name="Row18" Text="{Binding cardNumbers[7]}" Grid.Column="8" Grid.Row="1" Style="{StaticResource numberStyle}"/>
        <TextBlock x:Name="Row19" Text="{Binding cardNumbers[8]}" Grid.Column="9" Grid.Row="1" Style="{StaticResource numberStyle}"/>
        <TextBlock x:Name="Row21" Text="{Binding cardNumbers[9]}" Grid.Column="1" Grid.Row="2" Style="{StaticResource numberStyle}"/>
        <TextBlock x:Name="Row22" Text="{Binding cardNumbers[10]}" Grid.Column="2" Grid.Row="2" Style="{StaticResource numberStyle}"/>
        <TextBlock x:Name="Row23" Text="{Binding cardNumbers[11]}" Grid.Column="3" Grid.Row="2" Style="{StaticResource numberStyle}"/>
        <TextBlock x:Name="Row24" Text="{Binding cardNumbers[12]}" Grid.Column="4" Grid.Row="2" Style="{StaticResource numberStyle}"/>
        <TextBlock x:Name="Row25" Text="{Binding cardNumbers[13]}" Grid.Column="5" Grid.Row="2" Style="{StaticResource numberStyle}"/>
        <TextBlock x:Name="Row26" Text="{Binding cardNumbers[14]}" Grid.Column="6" Grid.Row="2" Style="{StaticResource numberStyle}"/>
        <TextBlock x:Name="Row27" Text="{Binding cardNumbers[15]}" Grid.Column="7" Grid.Row="2" Style="{StaticResource numberStyle}"/>
        <TextBlock x:Name="Row28" Text="{Binding cardNumbers[16]}" Grid.Column="8" Grid.Row="2" Style="{StaticResource numberStyle}"/>
        <TextBlock x:Name="Row29" Text="{Binding cardNumbers[17]}" Grid.Column="9" Grid.Row="2" Style="{StaticResource numberStyle}"/>
        <TextBlock x:Name="Row31" Text="{Binding cardNumbers[18]}" Grid.Column="1" Grid.Row="3" Style="{StaticResource numberStyle}"/>
        <TextBlock x:Name="Row32" Text="{Binding cardNumbers[19]}" Grid.Column="2" Grid.Row="3" Style="{StaticResource numberStyle}"/>
        <TextBlock x:Name="Row33" Text="{Binding cardNumbers[20]}" Grid.Column="3" Grid.Row="3" Style="{StaticResource numberStyle}"/>
        <TextBlock x:Name="Row34" Text="{Binding cardNumbers[21]}" Grid.Column="4" Grid.Row="3" Style="{StaticResource numberStyle}"/>
        <TextBlock x:Name="Row35" Text="{Binding cardNumbers[22]}" Grid.Column="5" Grid.Row="3" Style="{StaticResource numberStyle}"/>
        <TextBlock x:Name="Row36" Text="{Binding cardNumbers[23]}" Grid.Column="6" Grid.Row="3" Style="{StaticResource numberStyle}"/>
        <TextBlock x:Name="Row37" Text="{Binding cardNumbers[24]}" Grid.Column="7" Grid.Row="3" Style="{StaticResource numberStyle}"/>
        <TextBlock x:Name="Row38" Text="{Binding cardNumbers[25]}" Grid.Column="8" Grid.Row="3" Style="{StaticResource numberStyle}"/>
        <TextBlock x:Name="Row39" Text="{Binding cardNumbers[26]}" Grid.Column="9" Grid.Row="3" Style="{StaticResource numberStyle}"/>
    </Grid>
</UserControl>

El código XAML del formulario principal

   
<Window x:Class="Bingo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Bingo"
        mc:Ignorable="d"
        Title="MainWindow" Height="401.567" Width="812.461">
    <Grid>
        <local:CardForm x:Name="cardform"/>
        <Button x:Name="button" Content="Generar" HorizontalAlignment="Left" Height="38" Margin="591,306,0,0" VerticalAlignment="Top" Width="136" Click="button_Click"/>
    </Grid>
</Window>

en este bloque de código, debemos fijarnos en como estamos declarando el control de usuario que hemos creado y que ahora estamos insertando en el formulario principal; primero declaramos el espacio clr-namespace:Bingo con la key local (línea 6) y luego llamamos al control desde esta key que hace referencia al espacio de nombres donde se encuentra el control, si no hacemos esto, jamás podremos declarar el control de usuario dentro de la ventana principal, porque no lo encontraría.

y el code behind de este form, solo esto, el resto lo hace WPF. En este bloque cabe destacar que los único que hacemos desde la ventana principal es instanciar un objeto Card (Cartón) y asignar al DataContext del grid del control de usuario, el objeto instanciado card. El botón, lo único que hace es llamar al método CartonNuevo mostrando cada vez que se pulse, un nuevo cartón aleatorio.

namespace Bingo
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        void CartonNuevo()
        {
                Card card = new Card();
                card.CardNumber = 1729;
                card.SerialNumber = 1;
                cardform.CardGrid.DataContext = card;
        }

        private void button_Click(object sender, RoutedEventArgs e)
        {
            CartonNuevo();            
        }
    }
}

Volvemos al código XAML del control de usuario, en el control grid hay varias cosas que ver

  • En el bloque Resources del Grid, creo un Style para los Textblock llamado baseStyle
  • Luego creo otro estilo llamado numberStyle que hereda de baseStyle y modifica algunas propiedades (Está subrayado desde la línea 9 a la 22)
  • En las propiedades Text de los controles Textblock, hacemos un Binding al elemento concreto de la colección List (Text="{Binding cardNumbers[0]}") del objeto Card, al número de cartón y a la serie, de modo que sin ningún código en C#, el formulario nos mostrará el cartón, solo debemos decirle desde el formulario principal, que el DataContext es el objeto instanciado card. (Dejo subrayado como ejemplo el primer TextBlock de la línea 48)

He aquí como quedaría el resultado

bingo03

como siempre, si tenéis alguna duda o pregunta, comentario al canto

Saludos

Calculadora Números Complejos (C# y WPF)

Calculadora Números Complejos (C# y WPF)

Java y C# son lenguajes muy parecidos, de hecho C# deriva de C++, Java y otros lenguajes alimentándose de lo bueno de cada uno; particularmente para mi es el mejor por su potencia y sus increíbles mejoras que nos hacen la programación mucho más fácil.

En el anterior post, describí una calculadora de números complejos en Java y en este, lo implemento con C# y WPF. Con esto intento tres cosas, en primer lugar demostrar que la clase (muy pero que muy similar a la de Java) es invariable porque me daría igual usar una aplicación de consola o gráfica, otra es que podáis ver la diferencia entre lenguajes pero la similitud estructural y por último implementarla mediante WPF con el cual nos vamos a ahorrar mucho código quedando la aplicación muy limpia y robusta y de camino os enseño como se aplican estilos a los controles.

Lo primero vayamos a la clase Complex. Las principales diferencias con la de Java son las que voy a enumerar.

  1. Implemento la interfaz INotifyPropertyChanged de modo que la clase debe contener el evento public event PropertyChangedEventHandler PropertyChanged; el cual invocamos mediante el método OnPropertyChanged(string p_PropertyName) cada vez que cambia la propiedad de la parte real o imaginaria de un número complejo.
  2. En esta clase, he sobrecargado las operaciones básicas de suma, resta, multiplicación, división y negación (a este operador le he asignado el opuesto). Para ver como lo he hecho, os paso el código de la operación suma con el que con un método
            public static Complex operator + (Complex _complex1, Complex _complex2)
            {
                return new Complex(_complex1.Real + _complex2.Real, _complex1.Imaginary + _complex2.Imaginary);
            }
    

    permitiéndome efectuar sumas sobre dos complejos.

A continuación paso el código completo de la clase.

/*************************************************************************************************************************************************
* © JOAQUIN MARTINEZ RUS 2015
* PROYECTO:        ComplexCalc. Calculadora de números complejos
* Archivo:         Complex.cs
* Descripción:     Clase de números complejos
* Historial:
*                  1. Joaquin Martínez Rus - 18 jul 2016. Creación
*
* Comentarios:
*
*
**************************************************************************************************************************************************/

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ComplexCal
{
    public class Complex: INotifyPropertyChanged
    {
        #region Constructor

        public Complex(double _real=0, double _imaginary=0)
        {
            this.Real = _real;
            this.Imaginary = _imaginary;
        }

        public Complex(Complex _complex)
        {
            this.Real = _complex.Real;
            this.Imaginary = _complex.Imaginary;
        }

        #endregion

        #region Propiedades

        private double real;
        private double imaginary;

        public event PropertyChangedEventHandler PropertyChanged;

        public double Module { get; set; }
        public double DegreesAngle { get; set; }
        public double RadiansAngle { get; set; }

        public double Imaginary
        {
            get { return imaginary; }
            set
            {
                imaginary = value;
                SetPolarComplex();
                OnPropertyChanged("Imaginary");
                OnPropertyChanged("ToString");
            }
        }

        public double Real
        {
            get { return real; }
            set
            {
                real = value;
                SetPolarComplex();
                OnPropertyChanged("Real");
                OnPropertyChanged("ToString");
            }
        }

        public string ToBinomialString
        {
            get
            {
                return "z = " + this.Real.ToString("F2") + (this.Imaginary < 0 ? " - " : " + ") + Math.Abs(this.Imaginary).ToString("F2") + " i";
            }
        }

        public string ToPolarString
        {
            get
            {
                return "z = " + this.Module.ToString("F2") + ") " + this.DegreesAngle.ToString("F1") + "°";
            }
        }

        public string ToString
        {
            get { return this.ToBinomialString + new string(' ',10) + this.ToPolarString; }
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Asigna el módulo y el argumento de un número complejo
        /// </summary>
        void SetPolarComplex()
        {
            this.Module= Math.Sqrt(Math.Pow(this.Real, 2) + Math.Pow(this.Imaginary, 2));
            this.RadiansAngle= Math.Atan2(this.Imaginary, this.Real);
            this.DegreesAngle = this.RadiansAngle * 180 / Math.PI;
        }

        #endregion

        #region Static Public Methods

        // SOBRECARGA DE LOS OPERADORES BÁSICOS

        public static Complex operator + (Complex _complex1, Complex _complex2)
        {
            return new Complex(_complex1.Real + _complex2.Real, _complex1.Imaginary + _complex2.Imaginary);
        }

        public static Complex operator - (Complex _complex1, Complex _complex2)
        {
            return new Complex(_complex1.Real - _complex2.Real, _complex1.Imaginary - _complex2.Imaginary);
        }

        public static Complex operator * (Complex _complex1, Complex _complex2)
        {
            return new Complex((_complex1.Real * _complex2.Real - _complex1.Imaginary * _complex2.Imaginary),
                (_complex1.Real * _complex2.Imaginary + _complex1.Imaginary * _complex2.Real));
        }

        public static Complex operator / (Complex _complex1, Complex _complex2)
        {
            double xx = (_complex1.Real * _complex2.Real + _complex1.Imaginary * _complex2.Imaginary) / (Math.Pow(_complex2.Real, 2) + Math.Pow(_complex2.Imaginary, 2));
            double yy = (_complex1.Imaginary * _complex2.Real - _complex1.Real * _complex2.Imaginary) / (Math.Pow(_complex2.Real, 2) + Math.Pow(_complex2.Imaginary, 2));
            return new Complex(xx, yy);
        }

        public static Complex operator ! (Complex _complex)
        {
            return new Complex(-_complex.Real, -_complex.Imaginary);
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Inicia el evento de cambio de propiedad
        /// </summary>
        /// <param name="p_PropertyName">Nombre de la propiedad</param>
        public void OnPropertyChanged(string p_PropertyName)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(p_PropertyName));
            }
        }

        /// <summary>
        /// Efectúa una copia del objeto
        /// </summary>
        /// <param name="_complex">Objeto desde el cual se efectúa la copia</param>
        public void Clone(Complex _complex)
        {
            this.Real = _complex.Real;
            this.Imaginary = _complex.Imaginary;
        }

        /// <summary>
        /// Devuelve el conjugado de un número complejo
        /// </summary>
        /// <param name="complex">Número complejo al que se le calcula</param>
        /// <returns>Objeto Complex</returns>
        public Complex Conjugate(Complex complex)
        {
            return new Complex(complex.Real, -complex.Imaginary);
        }

        /// <summary>
        /// Devuelve el conjugado de un número complejo
        /// </summary>
        /// <returns>Objeto Complex</returns>
        public Complex Conjugate()
        {
            return Conjugate(this);
        }

        /// <summary>
        /// Determina cuando una instancia y un objeto tienen los mismos valores
        /// </summary>
        /// <param name="_complex">El objeto a comparar con la instancia</param>
        /// <returns>Valor booleano</returns>
        public bool Equals(Complex _complex)
        {
            if (_complex == null)
            {
                return false;
            }
            if (this.GetType() != _complex.GetType())
            {
                return false;
            }

            return this.Real == _complex.Real && this.Imaginary == _complex.Imaginary;
        }

        /// <summary>
        /// Obtiene el inverso del objeto Complex actual
        /// </summary>
        /// <returns>Objeto Complex</returns>
        public Complex Reverse()
        {
            return Reverse(this);
        }

        /// <summary>
        /// Obtiene el inverso de un objeto Complex
        /// </summary>
        /// <param name="_complex">Objeto Complex del que se obtiene el inverso</param>
        /// <returns>Objeto Complex</returns>
        public Complex Reverse(Complex _complex)
        {
            double denominador = Math.Pow(_complex.Real, 2) + Math.Pow(_complex.Imaginary, 2);
            return new Complex(_complex.Real / denominador, -_complex.Imaginary / denominador);
        }

        /// <summary>
        /// Retorna el objeto Complex en formato Polar
        /// </summary>
        /// <param name="isDegrees">La salida se muestra en grados sexagesimales</param>
        /// <returns>String</returns>
        public string PolarToString(bool isDegrees)
        {
            return this.Module.ToString("D1") + ")" + (isDegrees?this.DegreesAngle.ToString("D1"):this.RadiansAngle.ToString("D2"));
        }

        /// <summary>
        /// Retorna el objeto Complex en formato Polar
        /// </summary>
        /// <returns>String</returns>
        public string PolarToString()
        {
            return PolarToString(true);
        }

        #endregion
    }
}


Ahora damos paso al código XAML del formulario. Lo primero declaramos los recursos de la ventana con &lt;code&gt;Window.Resources&lt;/code&gt; y en este lugar, incluimos los estilos. He creado tres estilos, uno llamado &lt;span style='font-family: &quot;;&quot;;' data-mce-style='font-family: &quot;;'&gt;normal&lt;/span&gt; aplicado a los controles &lt;code&gt;TextBlock&lt;/code&gt;, otro llamado &lt;code&gt;header&lt;/code&gt; que hereda de normal y por tanto es aplicado a los mismos controles y un tercer estilo llamado &lt;code&gt;textBox&lt;/code&gt; aplicado a controles &lt;code&gt;TextBox&lt;/code&gt; (muy original, eh?), por tanto a cada control que le asigne este estilo se le aplicarán los valores de las propiedades incluidas en el estilo a no ser que se sobreescriban al crear el control.( En el control &lt;code&gt;TextBox&lt;/code&gt; he incluido un validador para que no se incluya texto que no sea numérico, pero eso lo veremos otro día)Una vez creados los estilos vamos a centrarnos en&amp;nbsp;el control &lt;code&gt;TextBlock&lt;/code&gt; del siguiente código: 
 
      <StackPanel x:Name="stackResult" Orientation="Horizontal" Grid.Column="2" Grid.Row="5" Grid.ColumnSpan="5">
            <TextBlock x:Name="result"  Style="{StaticResource header}" Text="Resultado" VerticalAlignment="Center"/>
            <TextBlock x:Name="labelComplexNumberResult" Style="{StaticResource normal}" Text="{Binding ToString}" Margin="123,0,0,0"/>
</StackPanel>  
  1. Le asignamos un nombre
  2. Le asignamos el estilo con Style="{StaticResource textBox}"
  3. Le asignamos el valor enlazado de la propiedad Text con  la propiedad Real de la clase Complex. Style="{StaticResource textBox}" Text="{Binding Real}"¿Y de donde extrae los datos la propiedad Text?

Para que los controles sepan de donde tienen que extraer los datos de la clase Complex, al crear la ventana principal, asigno a cada StackPanel donde se encuentran contenidos los controles de cada número, la clase desde donde se alimentará y cada vez que se alteren los datos de cada clase, se visualizarán automáticamente en los controles sin necesidad de código. Por tanto en el code-behind de la ventana principal solo y exclusivamente incluyo la declaración de los tres objetos Complex, los dos de cálculo y el resultado, la asignación del DataContext de los StackPanel, los métodos de los eventos de cambio de propiedad y cambio de operación y el cálculo  de la operación.

/* ************************************************************************************************************************************************
* © JOAQUIN MARTINEZ RUS 2015
* PROYECTO:        ComplexCalc. Calculadora de números complejos
* Archivo:         MainWindow.cs
* Descripción:     Clase de la ventana principal
* Historial:
*                  1. Joaquin Martínez Rus - 18 jul 2016. Creación
*
* Comentarios:
*
*
**************************************************************************************************************************************************/
using System.Windows;
using System.Windows.Controls;

namespace ComplexCal
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        Complex c1 = new Complex(2,4);
        Complex c2 = new Complex(3,5);
        Complex ComplexResult=new Complex();

        public MainWindow()
        {
            InitializeComponent();

            // asignar a los Stackpanel el Datacontext de cada objeto Complex

            stackC1.DataContext = c1;
            stackC2.DataContext = c2;
            stackResult.DataContext = ComplexResult;

            // Crear evento para que cada vez que se cambie una propiedad, se calcule la operación
            c1.PropertyChanged += Complex_PropertyChanged;
            c2.PropertyChanged += Complex_PropertyChanged;

        }

        private void Complex_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            Calculate(comboBox.SelectionBoxItem.ToString());
        }

        private void comboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            ComboBoxItem op = e.AddedItems[0] as ComboBoxItem;
            Calculate(op.Content.ToString());
        }

        /// <summary>
        /// Efectúa el cálculo entre dos complejos
        /// </summary>
        /// <param name="operatorToString">Operador</param>
        void Calculate(string operatorToString)
        {
            switch (operatorToString)
            {
                case "+":
                    ComplexResult.Clone(c1 + c2);
                    break;
                case "-":
                    ComplexResult.Clone(c1 - c2);
                    break;
                case "x":
                    ComplexResult.Clone(c1 * c2);
                    break;
                case "÷":
                    ComplexResult.Clone(c1 / c2);
                    break;
                case "Inverso":
                    ComplexResult.Clone(c1.Reverse());
                    break;
                case "Conjugado":
                    ComplexResult.Clone(c1.Conjugate());
                    break;
                case "Opuesto":
                    ComplexResult.Clone(!c1);
                    break;
            }

        }

    }
}

y el código XAML full:

<Window x:Class="ComplexCal.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:ComplexCal"
        mc:Ignorable="d"
        Title="Complex Calc" Height="480" Width="640">
    <Window.Resources>

        <Style x:Key="normal" TargetType="TextBlock">
            <Setter Property="VerticalAlignment" Value="Center"/>            
            <Setter Property="FontSize" Value="14"/>
            <Setter Property="FontFamily" Value="Segoe UI"/>
            <Setter Property="Height" Value="25"/>
            <Setter Property="Width" Value="Auto"/>
        </Style>
        <Style x:Key="header" TargetType="TextBlock" BasedOn="{StaticResource normal}">
            <Setter Property="FontWeight" Value="Bold"/>
            <Setter Property="VerticalAlignment" Value="Bottom"/>
            <Setter Property="HorizontalAlignment" Value="Center"/>            
        </Style>
        <Style x:Key="textBox" TargetType="TextBox">
            <Setter Property="VerticalAlignment" Value="Center"/>
            <Setter Property="Height" Value="25"/>
            <Setter Property="Width" Value="50"/>
            <Setter Property="HorizontalAlignment" Value="Center"/>
            <Setter Property="Validation.ErrorTemplate">
                <Setter.Value>
                    <ControlTemplate>
                        <DockPanel LastChildFill="true">
                            <Border Background="OrangeRed" DockPanel.Dock="right" Margin="5,0,0,0" 
                                Width="20" Height="20" CornerRadius="5"
                                ToolTip="{Binding ElementName=customAdorner, 
                                          Path=AdornedElement.(Validation.Errors)[0].ErrorContent}">
                                <TextBlock Text="!" VerticalAlignment="center" HorizontalAlignment="center" 
                                   FontWeight="Bold" Foreground="white" />
                            </Border>
                            <AdornedElementPlaceholder Name="customAdorner" VerticalAlignment="Center" >
                                <Border BorderBrush="red" BorderThickness="1" />
                            </AdornedElementPlaceholder>
                        </DockPanel>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
            <Setter Property="Margin" Value="20,0,0,0"/>
        </Style>
    </Window.Resources>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="28*"/>
            <ColumnDefinition Width="27*"/>
            <ColumnDefinition Width="82*"/>
            <ColumnDefinition Width="132*"/>
            <ColumnDefinition Width="154*"/>
            <ColumnDefinition Width="60*"/>
            <ColumnDefinition Width="149*"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="49*"/>
            <RowDefinition Height="49*"/>
            <RowDefinition Height="49*"/>
            <RowDefinition Height="49*"/>
            <RowDefinition Height="49*"/>
            <RowDefinition Height="49*"/>
            <RowDefinition Height="155*"/>
        </Grid.RowDefinitions>
        <TextBlock x:Name="labelReal" Grid.Column="2" Grid.Row="1" Style="{StaticResource header}" Text="Real"/>
        <TextBlock x:Name="labelImaginaria" Grid.Column="3" Grid.Row="1" Style="{StaticResource header}" Text="Imaginaria"/>
        <TextBlock x:Name="labelNumberHeader" Grid.Column="4" Grid.Row="1" Style="{StaticResource header}" Text="Número complejo"/>
        <TextBlock x:Name="z1" Grid.Column="1" Grid.Row="2" Style="{StaticResource normal}" Text="z1" Margin="0,12"/>
        <TextBlock x:Name="z2" Grid.Column="1" Grid.Row="4" Style="{StaticResource normal}" Text="z2" Margin="0,12" />
        <TextBlock x:Name="op" Grid.Column="2" Grid.Row="3" Style="{StaticResource normal}" Text="Operación" Margin="0,12"/>
        <ComboBox x:Name="comboBox" Grid.Column="3" HorizontalAlignment="Left" Grid.Row="3" VerticalAlignment="Center" Width="80" Height="25" SelectionChanged="comboBox_SelectionChanged">
            <ComboBoxItem Content="+" IsSelected="True"/>
            <ComboBoxItem Content="-"/>
            <ComboBoxItem Content="x"/>
            <ComboBoxItem Content="÷"/>
            <ComboBoxItem Content="Inverso"/>
            <ComboBoxItem Content="Conjugado"/>
            <ComboBoxItem Content="Opuesto"/>
        </ComboBox>
        <StackPanel x:Name="stackC1" Orientation="Horizontal" Grid.Column="2" Grid.Row="2" Grid.ColumnSpan="5">
            <TextBox x:Name="textBoxReal1"  Style="{StaticResource textBox}" Text="{Binding Real}"/>
            <TextBox x:Name="textBoxImagin1" Style="{StaticResource textBox}" Text="{Binding Imaginary}"/>
            <TextBlock x:Name="labelComplexNumber1" Style="{StaticResource normal}" Text="{Binding ToString}" Margin="50,0,0,0" />
        </StackPanel>
        <StackPanel x:Name="stackC2" Orientation="Horizontal" Grid.Column="2" Grid.Row="4" Grid.ColumnSpan="5">
            <TextBox x:Name="textBoxReal2" Style="{StaticResource textBox}" Text="{Binding Real}"/>
            <TextBox x:Name="textBoxImagin2" Style="{StaticResource textBox}" Text="{Binding Imaginary}"/>
            <TextBlock x:Name="labelComplexNumber2" Style="{StaticResource normal}" Text="{Binding ToString}" Margin="50,0,0,0" />
        </StackPanel>
        <StackPanel x:Name="stackResult" Orientation="Horizontal" Grid.Column="2" Grid.Row="5" Grid.ColumnSpan="5">
            <TextBlock x:Name="result"  Style="{StaticResource header}" Text="Resultado" VerticalAlignment="Center"/>
            <TextBlock x:Name="labelComplexNumberResult" Style="{StaticResource normal}" Text="{Binding ToString}" Margin="123,0,0,0"/>
        </StackPanel>
    </Grid>
</Window>


Pues esto es todo, la clase Complex en C# y su ejecución bajo WPF con los controles enlazados.

Nos vemos. Saludos