一个Color ViewModel
Color始终提供了探索图形用户界面功能的好方法,因此您可能不会惊讶于Xamarin.FormsBook.Toolkit库包含一个名为ColorViewModel的类。
ColorViewModel类公开Color属性,但也显示Red,Green,Blue,Alpha,Hue,Saturation和Luminosity属性,所有这些属性都是可单独设置的。这不是Xamarin.Form Color结构提供的功能。从Color构造函数或Color中的其中一个方法创建Color值时,以Add,From,Multiply或With开头,它是不可变的。
ColorViewModel类因其Color属性和所有组件属性的相互关系而变得复杂。例如,假设已设置Color属性。该类应该不仅为Color而且还为任何也发生变化的组件(如Red或Hue)触发PropertyChanged处理程序。同样,如果Red属性发生变化,那么该类应该为Red和Color以及可能的Hue,Saturation和Luminosity触发PropertyChanged事件。
ColorViewModel类通过仅为Color属性存储支持字段来解决此问题。通过调用Color.FromRgba或Color.FromHsla使用传入值,各个组件的所有set访问器都会创建一个新Color。此新Color值设置为Color属性而不是color字段,这意味着新Color值将在Color属性的set访问器中进行处理:
public class ColorViewModel : INotifyPropertyChanged
{
Color color;
public event PropertyChangedEventHandler PropertyChanged;
public double Red
{
set
{
if (Round(color.R) != value)
Color = Color.FromRgba(value, color.G, color.B, color.A);
}
get
{
return Round(color.R);
}
}
public double Green
{
set
{
if (Round(color.G) != value)
Color = Color.FromRgba(color.R, value, color.B, color.A);
}
get
{
return Round(color.G);
}
}
public double Blue
{
set
{
if (Round(color.B) != value)
Color = Color.FromRgba(color.R, color.G, value, color.A);
}
get
{
return Round(color.B);
}
}
public double Alpha
{
set
{
if (Round(color.A) != value)
Color = Color.FromRgba(color.R, color.G, color.B, value);
}
get
{
return Round(color.A);
}
}
public double Hue
{
set
{
if (Round(color.Hue) != value)
Color = Color.FromHsla(value, color.Saturation, color.Luminosity, color.A);
}
get
{
return Round(color.Hue);
}
}
public double Saturation
{
set
{
if (Round(color.Saturation) != value)
Color = Color.FromHsla(color.Hue, value, color.Luminosity, color.A);
}
get
{
return Round(color.Saturation);
}
}
public double Luminosity
{
set
{
if (Round(color.Luminosity) != value)
Color = Color.FromHsla(color.Hue, color.Saturation, value, color.A);
}
get
{
return Round(color.Luminosity);
}
}
public Color Color
{
set
{
Color oldColor = color;
if (color != value)
{
color = value;
OnPropertyChanged("Color");
}
if (color.R != oldColor.R)
OnPropertyChanged("Red");
if (color.G != oldColor.G)
OnPropertyChanged("Green");
if (color.B != oldColor.B)
OnPropertyChanged("Blue");
if (color.A != oldColor.A)
OnPropertyChanged("Alpha");
if (color.Hue != oldColor.Hue)
OnPropertyChanged("Hue");
if (color.Saturation != oldColor.Saturation)
OnPropertyChanged("Saturation");
if (color.Luminosity != oldColor.Luminosity)
OnPropertyChanged("Luminosity");
}
get
{
return color;
}
}
protected void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
double Round(double value)
{
return Device.OnPlatform(value, Math.Round(value, 3), value);
}
}
Color属性的set访问器负责根据属性的更改来触发所有PropertyChanged事件。
请注意类底部的设备相关Round方法及其在set中的使用以及前七个属性的get访问器。 当第23章“触发器和行为”中的MultiColorSliders示例显示出问题时,会添加此项。 Android似乎在内部舍入颜色组件,导致传递给它的属性之间的不一致
Color.FromRgba和Color.FromHsla方法以及由此产生的Color值的属性,从而导致无限集和get循环。
HslSliders程序在Grid.BindingContext标记之间实例化ColorViewModel,使其成为Grid中所有Slider和Label元素的BindingContext:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:toolkit=
"clr-namespace:Xamarin.FormsBook.Toolkit;assembly=Xamarin.FormsBook.Toolkit"
x:Class="HslSliders.HslSlidersPage"
SizeChanged="OnPageSizeChanged">
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness"
iOS="0, 20, 0, 0" />
</ContentPage.Padding>
<Grid x:Name="mainGrid">
<Grid.BindingContext>
<toolkit:ColorViewModel Color="Gray" />
</Grid.BindingContext>
<Grid.Resources>
<ResourceDictionary>
<Style TargetType="Label">
<Setter Property="FontSize" Value="Large" />
<Setter Property="HorizontalTextAlignment" Value="Center" />
</Style>
</ResourceDictionary>
</Grid.Resources>
<!-- Initialized for portrait mode. -->
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="0" />
</Grid.ColumnDefinitions>
<BoxView Color="{Binding Color}"
Grid.Row="0" Grid.Column="0" />
<StackLayout x:Name="controlPanelStack"
Grid.Row="1" Grid.Column="0"
Padding="10, 5">
<StackLayout VerticalOptions="CenterAndExpand">
<Slider Value="{Binding Hue}" />
<Label Text="{Binding Hue, StringFormat='Hue = {0:F2}'}" />
</StackLayout>
<StackLayout VerticalOptions="CenterAndExpand">
<Slider Value="{Binding Saturation}" />
<Label Text="{Binding Saturation,StringFormat='Saturation = {0:F2}'}" />
</StackLayout>
<StackLayout VerticalOptions="CenterAndExpand">
<Slider Value="{Binding Luminosity}" />
<Label Text="{Binding Luminosity, StringFormat='Luminosity = {0:F2}'}" />
</StackLayout>
</StackLayout>
</Grid>
</ContentPage>
请注意,实例化ColorViewModel时,ColorViewModel的Color属性已初始化。 然后滑块的双向绑定将获取Hue,Saturation和Luminosity属性的结果值。
如果您想要实现红色,绿色和蓝色的十六进制值的显示,则可以使用与前一章中的GridRgbSliders程序相关的DoubleToIntConverter类。
HslSliders程序实现了与纵向和横向模式之间切换相同的技术,如GridRgbSliders程序。 代码隐藏文件处理此开关的机制:
public partial class HslSlidersPage : ContentPage
{
public HslSlidersPage()
{
InitializeComponent();
}
void OnPageSizeChanged(object sender, EventArgs args)
{
// Portrait mode.
if (Width < Height)
{
mainGrid.RowDefinitions[1].Height = GridLength.Auto;
mainGrid.ColumnDefinitions[1].Width = new GridLength(0, GridUnitType.Absolute);
Grid.SetRow(controlPanelStack, 1);
Grid.SetColumn(controlPanelStack, 0);
}
// Landscape mode.
else
{
mainGrid.RowDefinitions[1].Height = new GridLength(0, GridUnitType.Absolute);
mainGrid.ColumnDefinitions[1].Width = new GridLength(1, GridUnitType.Star);
Grid.SetRow(controlPanelStack, 0);
Grid.SetColumn(controlPanelStack, 1);
}
}
}
这个代码隐藏文件不像只调用InitializeComponent的文件那么漂亮,但即使在MVVM的上下文中,纵向和横向模式之间的切换也是代码隐藏文件的合法使用,因为它仅用于 用户界面而不是底层业务逻辑。
这是HslSliders计划的实际应用: