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 True
si 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"/>
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 } }
Un comentario en “Semáforo II”