
En el archivo xml ya teníamos incluidos los datos correspondientes a los puntos de fusión y ebullición, por tanto solo nos queda implementar nuestra modificación.
En el anterior post sobre la tabla periódica, el estado del elemento se trataba mediante un converter
(clase que implementaba la interfaz IValueConverter
en el binding
al que le pasábamos el estado y devolvía un color, de modo que según el estado, se le asignaba un color al símbolo. En este caso además del estado, tenemos más parámetros y son la temperatura actual y los puntos de fusión y ebullición. Y ¿cómo hacemos para mostrar los estados de los elementos con variación de temperatura? Se me ocurre incluir un control Slider
que varíe desde los -2500ºC hasta los 6000ºC (valor inferior y superior del menor y mayor valor de fusión y ebullición respectivamente) y el valor de este pasarlo junto a los valores de los puntos de fusión y ebullición de cada elemento, pero debéis recordar que la clase que implementa IValueConverter
solo podíamos pasar un valor y un parámetro, por lo que para nuestra modificación no nos serviría. Entonces es cuando tenemos que echar mano de otra interfaz muy similar pero que se adapta mucho más a nuestras necesidades y es IMultiValueConverter
. Esta interfaz contiene dos métodos Convert
y ConvertBack
como la interfaz IValueConverter
pero con la diferencia de que los valores que se pasan se trata de un array
de objetos, por lo cual ahora podemos pasar todos nuestros valores al conversor; a continuación os muestro la clase.
public class StateToColorConverter : IMultiValueConverter { public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) { SolidColorBrush scb = new SolidColorBrush(); try { double temp= double.Parse(values[0].ToString()); double meltingPoint = (double)values[1]; double boilingPoint = (double)values[2]; string state = values[3].ToString(); //parameter if (temp <= boilingPoint) { // Sólido if (state == "Artificial") scb.Color = Brushes.DarkRed.Color; else scb.Color = Brushes.Black.Color; } else if (temp > boilingPoint && temp < meltingPoint) { // Líquido scb.Color = Brushes.Blue.Color; } else { // Gas scb.Color = Brushes.LightBlue.Color; } return scb; } catch (InvalidCastException) { return scb; } } public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) { return null; } }
El código XAML para enlazar los datos con esta clase, debemos hacerlo con un multibinding. Lo primero, instanciar la clase y asignarle un key
, segundo desde la propiedad que queremos obtener el color que es el control que visualiza el símbolo con TextBlock.Foreground
, le añadimos el tag
y posteriormente añadimos tantos binding como valores queremos pasar
<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:StateToColorConverter x:Key="stcc"/> </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=TwoWay}" FontSize="15" TextAlignment="Center" FontWeight="Bold"> <TextBlock.Foreground> <MultiBinding Converter="{StaticResource stcc}"> <Binding Path="Temperature"/> <Binding Path="MeltingPoint"/> <Binding Path="BoilingPoint"/> <Binding Path="StandarState"/> </MultiBinding> </TextBlock.Foreground> </TextBlock> <TextBlock x:Name="NameTextBlock" Text="{Binding Name, Mode=OneTime}" FontSize="10" TextAlignment="Center" /> </StackPanel> </Button> </Grid> </UserControl>
Queda un detalle y es que la temperatura actual hay que pasarla a cada elemento para que este pueda hacer el binding y esto lo he conseguido añadiendo una nueva propiedad a la clase Element
y PeriodicTable
, entonces enlazamos la propiedad Value
del slider que hemos incluido con la propiedad de la PeriodicTable
y cada vez que cambie esta, se actualizan las propiedades de cada objeto Element
.
/* **************************************************************** * © JOAQUIN MARTINEZ RUS 2016 * PROYECTO: Tabla periódica * Archivo: Element.cs * Descripción: Clase element. * * Historial: * 1. 12 sep 2016. Creación * 2. 03 oct 2016. Modificación. Se añade la propiedad * Temperature para implementación de estados variables * en función de la temperatura * * 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; } private double temperature; public double Temperature { get { return temperature; } set { temperature = value; OnPropertyChanged("Temperature"); } } public event PropertyChangedEventHandler PropertyChanged; /// <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)); } } } } /* **************************************************************** * © JOAQUIN MARTINEZ RUS 2016 * PROYECTO: Tabla periódica * Archivo: PeriodicTable.cs * Descripción: Clase PeriodicTable. * * Historial: * 1. 12 sep 2016. Creación * 2. 03 oct 2016. Modificación. Se añade la propiedad * Temperature para implementación de estados variables * en función de la temperatura * * 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(); SerialChemical = new List(); 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(); States.AddRange(new string[] { "Gas", "Líquido", "Sólido", "Artificial" }); } public List Elements { get; set; } public List SerialChemical { get; set; } public List States { get; set; } private double temperature; public double Temperature { get { return temperature; } set { temperature = value; setTemperatures(); } } public event PropertyChangedEventHandler PropertyChanged; /// <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)); } } void setTemperatures() { foreach (var element in Elements) { element.Temperature = this.Temperature; } } } }
el código XAML del control es
<TextBlock x:Name="Temp" Grid.Column="4" Grid.Row="8" Grid.RowSpan="2" Text="{Binding ElementName=slider, Path=Value, StringFormat='{}{0:0}°C'}" FontSize="40" TextAlignment="Center" VerticalAlignment="Top" Grid.ColumnSpan="3"/> <Slider x:Name="slider" Grid.ColumnSpan="12" Grid.Column="7" Grid.RowSpan="2" HorizontalAlignment="Stretch" VerticalAlignment="Top" Height="30" Margin="10" Grid.Row="8" Minimum="-2500" Maximum="6000" Value="{Binding Temperature, Mode=TwoWay}" Ticks="1" TickFrequency="15" SmallChange="1"/>
Y el resultado es el siguiente
Con esto debemos aprender varias cosas, la primera es que un código bien definido inicialmente, nos va a permitir realizar modificaciones sobre nuestra aplicación con facilidad y por otra parte, aprovecharnos de las condiciones que WPF nos brinda, nos permite seguir manteniendo aplicaciones robustas con los menos BUG,s posibles aunque en este caso de la tabla periódica, la sencillez de la aplicación no creo que sea un buen ejemplo, no obstante nunca hay que fiarse.
Una vez más, saludos