你的位置:首页 > ASP.net教程

[ASP.net教程]UWP 颜色选择器(ColorPicker) 和 自定义的Flyout(AdvancedFlyout)


ColorPicker

故事背景

项目里面需要一个像Winfrom里面那样的颜色选择器,如下图所示:

在网上看了一下。没有现成的东东可以拿来使用。大概查看了一下关于颜色的一些知识,想着没人种树,那就由我自己来种树,大家来乘凉好了。

 

设计过程

由于要考虑到手机上的效果,所以说这种向右展开的方式,不是太合适手机,所以最外层我考虑使用Pivot来存放基本颜色和自定义颜色这2页。

第一页是基本颜色,第二页是自定义的颜色,如下图。

ColorPicker这个控件,主要是由一个Button以及FlyoutBase.AttachedFlyout中的Flyout来组成的。

由Button的点击来控制Flyout的打开或者是关闭。

 <Button x:Name="ToggleButton" Padding="{TemplateBinding Padding}" BorderBrush="{TemplateBinding BorderBrush}" Background="{TemplateBinding Background}" BorderThickness="{TemplateBinding BorderThickness}">            <Grid Padding="{TemplateBinding Padding}" Background="#01010101">              <Grid.ColumnDefinitions>                <ColumnDefinition Width="*"/>                <ColumnDefinition Width="auto"/>              </Grid.ColumnDefinitions>              <Rectangle HorizontalAlignment="Stretch" VerticalAlignment="Stretch">                <Rectangle.Fill>                  <!--failed to use TemplateBinding-->                  <SolidColorBrush Color="{Binding SelectedColor,RelativeSource={RelativeSource TemplatedParent}}"/>                </Rectangle.Fill>              </Rectangle>              <TextBlock x:Name="ArrowPolygon" Foreground="{TemplateBinding Foreground}" Visibility="{TemplateBinding ArrowVisibility}" Grid.Column="1" Text="&#xE0E5;" FontSize="{TemplateBinding FontSize}" FontFamily="Segoe UI Symbol" FontWeight="Normal" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="5,0,5,0"/>            </Grid>            <FlyoutBase.AttachedFlyout>              <Flyout x:Name="Flyout">                <Flyout.FlyoutPresenterStyle>                  <Style TargetType="FlyoutPresenter">                    <Setter Property="ScrollViewer.VerticalScrollMode" Value="Disabled"/>                    <Setter Property="ScrollViewer.HorizontalScrollMode" Value="Disabled"/>                    <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Disabled"/>                    <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled"/>                    <!--<Setter Property="MaxHeight" Value="NaN"/>                    <Setter Property="MaxWidth" Value="NaN"/>-->                    <Setter Property="MinHeight" Value="0"/>                    <Setter Property="MinWidth" Value="0"/>                    <Setter Property="Padding" Value="0,0,0,0"/>                    <Setter Property="Margin" Value="0,0,0,0"/>                    <Setter Property="BorderThickness" Value="0"/>                    <Setter Property="Background" Value="White"/>                    <!--<Setter Property="BorderBrush" Value="#A4AFBA"/>-->                    <Setter Property="MaxWidth" Value="NaN"/>                    <Setter Property="MaxHeight" Value="NaN"/>                    <Setter Property="Background" Value="Transparent"/>                    <Setter Property="VerticalContentAlignment" Value="Center"/>                    <Setter Property="HorizontalContentAlignment" Value="Center"/>                  </Style>                </Flyout.FlyoutPresenterStyle>                <Grid Background="#FFD1DCE8" RequestedTheme="Light" BorderBrush="#A4AFBA" BorderThickness="1" Width="{TemplateBinding FlyoutWidth}" Height="{TemplateBinding FlyoutHeight}">                  <Pivot x:Name="Pivot" Style="{StaticResource ColorPickerPivot}">                    <Pivot.Resources>                      <!--<Style TargetType="TextBlock">                      <Setter Property="Foreground" Value="Black"/>                    </Style>-->                      <Style TargetType="PivotHeaderItem" BasedOn="{StaticResource ColorPickerPivotHeaderItem}"/>                      <Style TargetType="PivotItem">                        <Setter Property="HorizontalContentAlignment" Value="Stretch"/>                        <Setter Property="Margin" Value="0"/>                        <Setter Property="Padding" Value="0"/>                        <Setter Property="MinWidth" Value="0"/>                      </Style>                    </Pivot.Resources>                    <PivotItem>                      <PivotItem.Header>                        <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Padding="5">                          <Grid.ColumnDefinitions>                            <ColumnDefinition Width="17"/>                            <ColumnDefinition/>                          </Grid.ColumnDefinitions>                          <Border Width="13" Height="13" Background="#FF97AEBF">                            <Grid>                              <Rectangle Height="10" HorizontalAlignment="Left" VerticalAlignment="Top" Width="3" Fill="#FFFF0000" Margin="1 1 0 0"/>                              <Rectangle Height="5" HorizontalAlignment="Left" VerticalAlignment="Top" Width="3" Fill="#FFFFC000" Margin="5 1 0 0"/>                              <Rectangle Height="5" HorizontalAlignment="Left" VerticalAlignment="Top" Width="3" Fill="#FFFFFF00" Margin="9 1 0 0"/>                              <Rectangle Height="5" HorizontalAlignment="Left" VerticalAlignment="Top" Width="3" Fill="#FF92D050" Margin="1 5 0 0"/>                              <Rectangle Height="5" HorizontalAlignment="Left" VerticalAlignment="Top" Width="3" Fill="#FF00B050" Margin="5 5 0 0"/>                              <Rectangle Height="5" HorizontalAlignment="Left" VerticalAlignment="Top" Width="3" Fill="#FF0C8242" Margin="9 5 0 0"/>                              <Rectangle Height="5" HorizontalAlignment="Left" VerticalAlignment="Top" Width="3" Fill="#FF0070C0" Margin="1 9 0 0"/>                              <Rectangle Height="5" HorizontalAlignment="Left" VerticalAlignment="Top" Width="3" Fill="#FF002060" Margin="5 9 0 0"/>                              <Rectangle Height="5" HorizontalAlignment="Left" VerticalAlignment="Top" Width="3" Fill="#FF7030A0" Margin="9 9 0 0"/>                            </Grid>                          </Border>                          <TextBlock HorizontalAlignment="Left" VerticalAlignment="Center" Text="基本颜色" TextWrapping="Wrap" Grid.Column="1">                                                      </TextBlock>                        </Grid>                      </PivotItem.Header>                      <StackPanel Orientation="Vertical">                        <Border Margin="0,5,0,0" HorizontalAlignment="Stretch" BorderBrush="#A4AFBA" BorderThickness="0,0,0,1" Height="30">                          <TextBlock Margin="5,0" VerticalAlignment="Center">                             <Run Text="{Binding Title,RelativeSource={RelativeSource TemplatedParent}}"/>                             <Run Text=" - "/>                             <Run Text="基本颜色"/>                          </TextBlock>                        </Border>                        <local:ColorPickerItemsControl x:Name="BasicColorItems" MinHeight="43"/>                        <Border Margin="0,5,0,0" BorderBrush="#A4AFBA" BorderThickness="0,0,0,1" HorizontalAlignment="Stretch" Height="30">                          <TextBlock Margin="5,0" Text="最近使用颜色" VerticalAlignment="Center"/>                        </Border>                        <local:ColorPickerItemsControl x:Name="RecentColorItems" MinHeight="43"/>                      </StackPanel>                    </PivotItem>                    <PivotItem>                      <PivotItem.Header>                        <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Padding="5">                          <Grid.ColumnDefinitions>                            <ColumnDefinition Width="17"/>                            <ColumnDefinition/>                          </Grid.ColumnDefinitions>                          <Ellipse Height="14" Margin="0.5,-1,3,-1" Fill="#FFFFFFFF" Width="14"/>                          <Ellipse Width="14" Height="14" Margin="0.5,-1,3,-1">                            <Ellipse.Fill>                              <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">                                <GradientStop Color="#FFFF0000" Offset="0.1"/>                                <GradientStop Color="#00FF0000" Offset="0.5"/>                              </LinearGradientBrush>                            </Ellipse.Fill>                          </Ellipse>                          <Ellipse Height="14" HorizontalAlignment="Stretch" Margin="0.5,-1,3,-1" VerticalAlignment="Stretch" Width="14">                            <Ellipse.Fill>                              <LinearGradientBrush EndPoint="0.982999980449677,0.179000005125999" StartPoint="0.0879999995231628,0.753000020980835">                                <GradientStop Color="#FF079BF0" Offset="0.1"/>                                <GradientStop Color="#00079BF0" Offset="0.5"/>                              </LinearGradientBrush>                            </Ellipse.Fill>                          </Ellipse>                          <Ellipse Height="14" HorizontalAlignment="Stretch" Margin="0.5,-1,3,-1" VerticalAlignment="Stretch" Width="14">                            <Ellipse.Fill>                              <LinearGradientBrush EndPoint="0.136000007390976,0.174999997019768" StartPoint="0.843999981880188,0.822000026702881">                                <GradientStop Color="#FFF2F413" Offset="0.1"/>                                <GradientStop Color="#00F2F413" Offset="0.5"/>                              </LinearGradientBrush>                            </Ellipse.Fill>                          </Ellipse>                          <Ellipse Height="14" HorizontalAlignment="Stretch" Margin="0.5,-1,3,-1" VerticalAlignment="Stretch" Width="14" Visibility="Visible">                            <Ellipse.Fill>                              <LinearGradientBrush>                                <GradientStop Color="#00000000" Offset="0.772"/>                                <GradientStop Color="#4C000000" Offset="1"/>                              </LinearGradientBrush>                            </Ellipse.Fill>                          </Ellipse>                          <Ellipse Height="15" HorizontalAlignment="Stretch" Margin="-0.5,-1.5,2.5,-1.5" VerticalAlignment="Stretch" Width="15" Stroke="#FF8AA3B5"/>                          <TextBlock HorizontalAlignment="Left" VerticalAlignment="Center" Text="自定义颜色" TextWrapping="Wrap" Grid.Column="1">                          </TextBlock>                        </Grid>                      </PivotItem.Header>                      <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">                        <Grid.RowDefinitions>                          <RowDefinition Height="auto"/>                          <RowDefinition Height="*"/>                          <RowDefinition Height="auto"/>                          <RowDefinition Height="auto"/>                        </Grid.RowDefinitions>                        <Grid.Resources>                          <Style TargetType="local:NumericTextBox">                            <Setter Property="InputScope" Value="Number"/>                            <Setter Property="ValueFormat" Value="F0"/>                            <Setter Property="Minimum" Value="0"/>                            <Setter Property="Maximum" Value="255"/>                            <Setter Property="MinWidth" Value="0"/>                            <Setter Property="Margin" Value="5,0,0,0"/>                            <Setter Property="HorizontalContentAlignment" Value="Center"/>                          </Style>                        </Grid.Resources>                        <Border Margin="0,5,0,0" HorizontalAlignment="Stretch" BorderBrush="#A4AFBA" BorderThickness="0,0,0,1" Height="30">                          <TextBlock Margin="5,0" VerticalAlignment="Center">                             <Run Text="{Binding Title,RelativeSource={RelativeSource TemplatedParent}}"/>                             <Run Text=" - "/>                             <Run Text="自定义颜色"/>                          </TextBlock>                        </Border>                        <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Grid.Row="1" BorderBrush="#A4AFBA" BorderThickness="0,0,0,1">                          <Grid.ColumnDefinitions>                            <ColumnDefinition Width="*"/>                            <ColumnDefinition Width="auto"/>                            <ColumnDefinition Width="auto"/>                          </Grid.ColumnDefinitions>                          <ContentControl x:Name="ChoiceGridParent" Grid.Column="0" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch">                            <Grid x:Name="ChoiceGrid" HorizontalAlignment="Stretch" Margin="5,5,0,5" VerticalAlignment="Stretch" >                              <!--<Grid.Background>                            <LinearGradientBrush StartPoint="0,0" EndPoint="1,0">                              <LinearGradientBrush.GradientStops>                                <GradientStop Offset="0.0" Color="White"/>                                <GradientStop Offset="1" Color="#00FFFFFF"/>                              </LinearGradientBrush.GradientStops>                            </LinearGradientBrush>                          </Grid.Background>-->                              <Rectangle HorizontalAlignment="Stretch" VerticalAlignment="Stretch">                                <Rectangle.Fill>                                  <LinearGradientBrush StartPoint="0,0" EndPoint="1,0">                                    <LinearGradientBrush.GradientStops>                                      <GradientStop Offset="0.0" Color="White"/>                                      <GradientStop Offset="1" Color="#00FFFFFF"/>                                    </LinearGradientBrush.GradientStops>                                  </LinearGradientBrush>                                </Rectangle.Fill>                              </Rectangle>                              <Rectangle HorizontalAlignment="Stretch" VerticalAlignment="Stretch">                                <Rectangle.Fill>                                  <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">                                    <LinearGradientBrush.GradientStops>                                      <GradientStop Offset="0.0" Color="#00000000"/>                                      <GradientStop Offset="1" Color="Black"/>                                    </LinearGradientBrush.GradientStops>                                  </LinearGradientBrush>                                </Rectangle.Fill>                              </Rectangle>                              <Canvas x:Name="PadCanvas">                                <Canvas x:Name="Indicator">                                  <Ellipse Height="6" Width="6" Fill="Transparent" Stroke="#FFFFFFFF" StrokeThickness="1" Margin="-3 -3 0 0" />                                  <Ellipse Height="12" Width="12" Fill="Transparent" Stroke="#FF737373" Margin="-6 -6 0 0" />                                </Canvas>                              </Canvas>                            </Grid>                          </ContentControl>                          <Slider x:Name="Hue" Style="{StaticResource ColorPickerHueSlider}" Margin="5,5,0,5" Grid.Column="1">                            <Slider.Background>                              <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">                                <GradientStop Offset="0.0" Color="#FFFF0000"/>                                <GradientStop Offset="0.2" Color="#FFFFFF00"/>                                <GradientStop Offset="0.4" Color="#FF00FF00"/>                                <GradientStop Offset="0.6" Color="#FF0000FF"/>                                <GradientStop Offset="0.8" Color="#FFFF00FF"/>                                <GradientStop Offset="1.0" Color="#FFFF0000"/>                              </LinearGradientBrush>                            </Slider.Background>                          </Slider>                          <Slider x:Name="Alpha" Style="{StaticResource ColorPickerAlphaSlider}" Margin="5" Grid.Column="2">                            <Slider.Background>                              <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">                                <GradientStop Color="Black" Offset="0"/>                                <GradientStop Color="Transparent" Offset="1"/>                              </LinearGradientBrush>                            </Slider.Background>                          </Slider>                        </Grid>                        <Grid Margin="0,0,5,0" Padding="0,0,0,5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Grid.Row="2" BorderBrush="#A4AFBA" BorderThickness="0,0,0,1">                          <Grid.ColumnDefinitions>                            <ColumnDefinition Width="*"/>                            <ColumnDefinition Width="*"/>                            <ColumnDefinition Width="*"/>                            <ColumnDefinition Width="*"/>                          </Grid.ColumnDefinitions>                          <local:NumericTextBox x:Name="AColor" Grid.Column="0">                            <local:NumericTextBox.Header>                              <TextBlock Text="透明度(A)" HorizontalAlignment="Center"/>                            </local:NumericTextBox.Header>                          </local:NumericTextBox>                          <local:NumericTextBox x:Name="RColor" Grid.Column="1" >                            <local:NumericTextBox.Header>                              <TextBlock Text="红(R)" HorizontalAlignment="Center"/>                            </local:NumericTextBox.Header>                          </local:NumericTextBox>                          <local:NumericTextBox x:Name="GColor" Grid.Column="2" >                            <local:NumericTextBox.Header>                              <TextBlock Text="绿(G)" HorizontalAlignment="Center"/>                            </local:NumericTextBox.Header>                          </local:NumericTextBox>                          <local:NumericTextBox x:Name="BColor" Grid.Column="3" >                            <local:NumericTextBox.Header>                              <TextBlock Text="蓝(B)" HorizontalAlignment="Center"/>                            </local:NumericTextBox.Header>                          </local:NumericTextBox>                        </Grid>                        <Grid Grid.Row="3" Margin="5">                          <Grid.ColumnDefinitions>                            <ColumnDefinition Width="*"/>                            <ColumnDefinition Width="auto"/>                          </Grid.ColumnDefinitions>                          <Grid HorizontalAlignment="Stretch" Margin="0,0,10,0">                            <local:TransparentBackground/>                            <Rectangle x:Name="CustomColorRectangle" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">                              <Rectangle.Fill>                                <SolidColorBrush Color="{Binding CurrentCustomColor,RelativeSource={RelativeSource TemplatedParent}}"/>                              </Rectangle.Fill>                              <ToolTipService.ToolTip>                                <ToolTip>                                  <Binding Converter="{StaticResource ColorToStringConverter}" Path="CurrentCustomColor" RelativeSource="{RelativeSource TemplatedParent}"/>                                </ToolTip>                              </ToolTipService.ToolTip>                            </Rectangle>                          </Grid>                          <Button x:Name="CustomColorOkButton" Grid.Column="1" Content="确定" VerticalAlignment="Center" HorizontalAlignment="Right"/>                        </Grid>                      </Grid>                    </PivotItem>                  </Pivot>                  <Button x:Name="CloseButton" Content="关闭" HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="5"/>                </Grid>              </Flyout>            </FlyoutBase.AttachedFlyout>            <ToolTipService.ToolTip>              <ToolTip>                <Binding Path="SelectedColor" RelativeSource="{RelativeSource TemplatedParent}" Converter="{StaticResource ColorToStringConverter}"/>              </ToolTip>            </ToolTipService.ToolTip>          </Button>

View Code

通过重写Pivot的模板我们可以轻松得到PiovtHeaderItem 在下面的效果(修改Header和PivotItemPresenter的行号)

Pivot部分模板代码如下,注意蓝色部分:

         <Grid x:Name="PivotLayoutElement">                    <Grid.RowDefinitions>                       <RowDefinition Height="*" />                      <RowDefinition Height="Auto" />                    </Grid.RowDefinitions>                    <Grid.ColumnDefinitions>                      <ColumnDefinition Width="Auto" />                      <ColumnDefinition Width="*" />                      <ColumnDefinition Width="Auto" />                    </Grid.ColumnDefinitions>                    <Grid.RenderTransform>                      <CompositeTransform x:Name="PivotLayoutElementTranslateTransform" />                    </Grid.RenderTransform>                    <ContentPresenter Grid.Row="1"         x:Name="LeftHeaderPresenter"         Content="{TemplateBinding LeftHeader}"         ContentTemplate="{TemplateBinding LeftHeaderTemplate}"         HorizontalAlignment="Stretch"         VerticalAlignment="Stretch" />                    <ContentControl Grid.Row="1"         x:Name="HeaderClipper"         Grid.Column="1"         UseSystemFocusVisuals="False"         HorizontalContentAlignment="Stretch">                      <ContentControl.Clip>                        <RectangleGeometry x:Name="HeaderClipperGeometry" />                      </ContentControl.Clip>                      <Grid Background="Transparent" BorderBrush="#A4AFBA" BorderThickness="0,1,0,0">                        <PivotHeaderPanel x:Name="StaticHeader" Visibility="Collapsed" />                        <PivotHeaderPanel x:Name="Header">                          <PivotHeaderPanel.RenderTransform>                            <TransformGroup>                              <CompositeTransform x:Name="HeaderTranslateTransform" />                              <CompositeTransform x:Name="HeaderOffsetTranslateTransform" />                            </TransformGroup>                          </PivotHeaderPanel.RenderTransform>                        </PivotHeaderPanel>                      </Grid>                    </ContentControl>                    <Button Grid.Row="1"          x:Name="PreviousButton"          Grid.Column="1"          Template="{StaticResource PreviousTemplate}"          Width="20"          Height="36"          UseSystemFocusVisuals="False"          Margin="{ThemeResource PivotNavButtonMargin}"          IsTabStop="False"          IsEnabled="False"          HorizontalAlignment="Left"          VerticalAlignment="Top"          Opacity="0"          Background="Transparent" />                    <Button Grid.Row="1"          x:Name="NextButton"          Grid.Column="1"          Template="{StaticResource NextTemplate}"          Width="20"          Height="36"          UseSystemFocusVisuals="False"          Margin="{ThemeResource PivotNavButtonMargin}"          IsTabStop="False"          IsEnabled="False"          HorizontalAlignment="Right"          VerticalAlignment="Top"          Opacity="0"          Background="Transparent" />                    <ContentPresenter Grid.Row="1"          x:Name="RightHeaderPresenter"          Grid.Column="2"          Content="{TemplateBinding RightHeader}"          ContentTemplate="{TemplateBinding RightHeaderTemplate}"          HorizontalAlignment="Stretch"          VerticalAlignment="Stretch" />                    <ItemsPresenter x:Name="PivotItemPresenter" Grid.Row="0" Grid.ColumnSpan="3">                      <ItemsPresenter.RenderTransform>                        <TransformGroup>                          <TranslateTransform x:Name="ItemsPresenterTranslateTransform" />                          <CompositeTransform x:Name="ItemsPresenterCompositeTransform" />                        </TransformGroup>                      </ItemsPresenter.RenderTransform>                    </ItemsPresenter>                  </Grid>

这个色块就比较简单了,通过Just Color Picker 把Winform 里面的颜色都给搞出来,通过ItemsControl把他们都布局在一块。

最近使用颜色,这个就是记录最近你点击修改的颜色,我这里用了一个帮助类来进行管理。

internal static class ColorPickerColorHelper  {    const string ColorPickerRecentColorsKey = "ColorPickerRecentColors.json";    private static ObservableCollection<Color> RecentColors;    //private static List<Color> systemColors;    //private static List<Color> basicColors;    private static bool hasLoadedRecentColors;    //public static List<Color> BasicColors    //{    //  get    //  {    //    return basicColors;    //  }    //}    static ColorPickerColorHelper()    {      //basicColors = new List<Color>();      RecentColors = new ObservableCollection<Color>();      //systemColors = new List<Color>();      //foreach (var color in typeof(Colors).GetRuntimeProperties())      //{      //  basicColors.Add((Color)color.GetValue(null));      //}    }    public static async Task<ObservableCollection<Color>> GetRecentColorsAsync()    {      if (!hasLoadedRecentColors)      {        hasLoadedRecentColors = true;        RecentColors = await GetRecentColorsAsyncInternal();        var temp = await GetRecentColorsAsyncInternal();        if (temp != null)        {          RecentColors = temp;        }      }      return RecentColors;    }    public async static Task SetRecentColorsAsync(Color color)    {      if (RecentColors != null)      {        if (RecentColors.LastOrDefault() == color)        {          return;        }        RecentColors.Add(color);        if (RecentColors.Count > 8)        {          RecentColors.RemoveAt(0);        }        await SaveRecentColorsAsync();      }    }    private static async Task<ObservableCollection<Color>> GetRecentColorsAsyncInternal()    {      var jsonText = await StorageHelper.ReadFileAsync(ColorPickerRecentColorsKey);      return JsonConvert.DeserializeObject<ObservableCollection<Color>>(jsonText);    }    private static async Task SaveRecentColorsAsync()    {      string jsonText = "";      if (RecentColors.Count > 0)      {        jsonText = JsonConvert.SerializeObject(RecentColors);      }      await StorageHelper.WriteFileAsync(ColorPickerRecentColorsKey, jsonText);    }  }}

 

第二页是自定义的色盘

这里用到HSL 色彩模式,之前不了解的小伙伴可以先去看一下,RGB→HSL 和 HSL→RGB转换的算法也有。

  HSL通道  透明度通道   这个2个我用到了Slider控件,当然模板我重新写了一下

 你可以通过拖拽、点击、键盘上下左右来微调颜色数值,这个属于比较简单的拖拽实现,Ellipse通过计算得出它的位置。

 

 当然你可以通过直接设置ARGB来设置颜色。这个输入框,我设计成了NumericTextBox继承于TextBox控件,支持Format

public class NumericTextBox : TextBox  {    private bool _isChangingTextWithCode;    private bool _isChangingValueWithCode;    private const double Epsilon = .00001;    public event EventHandler ValueChanged;    public double Value    {      get { return (double)GetValue(ValueProperty); }      set { SetValue(ValueProperty, value); }    }    // Using a DependencyProperty as the backing store for Value. This enables animation, styling, binding, etc...    public static readonly DependencyProperty ValueProperty =      DependencyProperty.Register("Value", typeof(double), typeof(NumericTextBox), new PropertyMetadata(0.0, new PropertyChangedCallback(OnValueChanged)));    private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)    {      (d as NumericTextBox).UpdateValueText();      (d as NumericTextBox).OnValueChanged();    }    public string ValueFormat    {      get { return (string)GetValue(ValueFormatProperty); }      set { SetValue(ValueFormatProperty, value); }    }    // Using a DependencyProperty as the backing store for ValueFormat. This enables animation, styling, binding, etc...    public static readonly DependencyProperty ValueFormatProperty =      DependencyProperty.Register("ValueFormat", typeof(string), typeof(NumericTextBox), new PropertyMetadata("F0"));    public double Minimum    {      get { return (double)GetValue(MinimumProperty); }      set { SetValue(MinimumProperty, value); }    }    // Using a DependencyProperty as the backing store for Minimum. This enables animation, styling, binding, etc...    public static readonly DependencyProperty MinimumProperty =      DependencyProperty.Register("Minimum", typeof(double), typeof(NumericTextBox), new PropertyMetadata(double.MinValue));    public double Maximum    {      get { return (double)GetValue(MaximumProperty); }      set { SetValue(MaximumProperty, value); }    }    // Using a DependencyProperty as the backing store for Maximum. This enables animation, styling, binding, etc...    public static readonly DependencyProperty MaximumProperty =      DependencyProperty.Register("Maximum", typeof(double), typeof(NumericTextBox), new PropertyMetadata(double.MaxValue));    public NumericTextBox()    {      Text = this.Value.ToString(CultureInfo.CurrentCulture);      TextChanged += this.OnValueTextBoxTextChanged;      KeyDown += this.OnValueTextBoxKeyDown;      PointerExited += this.OnValueTextBoxPointerExited;    }    private void OnValueTextBoxPointerExited(object sender, PointerRoutedEventArgs e)    {    }    private void OnValueTextBoxKeyDown(object sender, KeyRoutedEventArgs e)    {    }    private void OnValueTextBoxTextChanged(object sender, TextChangedEventArgs e)    {      this.UpdateValueFromText();    }    protected override void OnGotFocus(RoutedEventArgs e)    {      base.OnGotFocus(e);    }    protected override void OnLostFocus(RoutedEventArgs e)    {      this.UpdateValueFromText();      base.OnLostFocus(e);    }    private void UpdateValueText()    {      _isChangingTextWithCode = true;      this.Text = this.Value.ToString(this.ValueFormat);      this.SelectionStart = this.Text.Length;      _isChangingTextWithCode = false;    }    private void OnValueChanged()    {      if (ValueChanged != null)      {        ValueChanged(null, null);      }    }    private bool UpdateValueFromText()    {      if (_isChangingTextWithCode)      {        return false;      }      double val;      if (double.TryParse(this.Text, NumberStyles.Any, CultureInfo.CurrentUICulture, out val) ||        Calculator.TryCalculate(this.Text, out val))      {        _isChangingValueWithCode = true;        if (val < Minimum)        {          val = Minimum;        }        if (val > Maximum)        {          val = Maximum;        }        this.Value = val;        UpdateValueText();        _isChangingValueWithCode = false;        return true;      }      else      {        if (this.Text == "")        {          this.Value = Minimum;        }        UpdateValueText();      }      return false;    }    private bool SetValueAndUpdateValidDirections(double value)    {      // Range coercion is handled by base class.      var oldValue = this.Value;      if (value < Minimum)      {        value = Minimum;      }      if (value > Maximum)      {        value = Maximum;      }      this.Value = value;      if (value < Minimum || value > Maximum)      {        UpdateValueText();      }      //this.SetValidIncrementDirection();      return Math.Abs(this.Value - oldValue) > Epsilon;    }  }

View Code

 最后这个色块就是显示的最终的颜色,点击确认会生产自定义的颜色。这里说一下透明色的效果是怎么做成的。
在我们VS里面当把颜色设置为Transparent的时候,效果是如下图

其实就是添加了些灰色的Rect,知道效果,怎么做就简单了,代码如下

   public class TransparentBackground : Grid  {    public double SquareWidth    {      get { return (double)GetValue(SquareWidthProperty); }      set { SetValue(SquareWidthProperty, value); }    }    // Using a DependencyProperty as the backing store for SquareWidth. This enables animation, styling, binding, etc...    public static readonly DependencyProperty SquareWidthProperty =      DependencyProperty.Register("SquareWidth", typeof(double), typeof(TransparentBackground), new PropertyMetadata(4.0, new PropertyChangedCallback(OnUpdateSquares)));    private static void OnUpdateSquares(DependencyObject d, DependencyPropertyChangedEventArgs e)    {      (d as TransparentBackground).UpdateSquares();    }    public Brush SquareBrush    {      get { return (Brush)GetValue(SquareBrushProperty); }      set { SetValue(SquareBrushProperty, value); }    }    // Using a DependencyProperty as the backing store for SquareBrush. This enables animation, styling, binding, etc...    public static readonly DependencyProperty SquareBrushProperty =      DependencyProperty.Register("SquareBrush", typeof(Brush), typeof(TransparentBackground), new PropertyMetadata(new SolidColorBrush(Color.FromArgb(0xFF, 0xd7, 0xd7, 0xd7)), new PropertyChangedCallback(OnUpdateSquares)));    public Brush AlternatingSquareBrush    {      get { return (Brush)GetValue(AlternatingSquareBrushProperty); }      set { SetValue(AlternatingSquareBrushProperty, value); }    }    // Using a DependencyProperty as the backing store for AlternatingSquareBrush. This enables animation, styling, binding, etc...    public static readonly DependencyProperty AlternatingSquareBrushProperty =      DependencyProperty.Register("AlternatingSquareBrush", typeof(Brush), typeof(TransparentBackground), new PropertyMetadata(new SolidColorBrush(Colors.White), new PropertyChangedCallback(OnUpdateSquares)));    public TransparentBackground()    {      HorizontalAlignment = HorizontalAlignment.Stretch;      VerticalAlignment = VerticalAlignment.Stretch;      //this.SizeChanged += (s, e) =>      //{      //  if (e.NewSize != e.PreviousSize)      //  {      //    UpdateSquares();      //  }      //};    }    Size pre = Size.Empty;    protected override Size ArrangeOverride(Size finalSize)    {      if (pre != finalSize)      {        UpdateSquares(finalSize);        pre = finalSize;      }      return base.ArrangeOverride(finalSize);    }    private void UpdateSquares(Size? finalSize = null)    {      Size size = finalSize == null ? new Size(this.ActualWidth, this.ActualHeight) : finalSize.Value;      //size = new Size(this.ActualWidth, this.ActualHeight);      this.Children.Clear();      for (int x = 0; x < size.Width / SquareWidth; x++)      {        for (int y = 0; y < size.Height / SquareWidth; y++)        {          var rectangle = new Rectangle();          rectangle.Fill = ((x % 2 == 0 && y % 2 == 0) || (x % 2 == 1 && y % 2 == 1)) ? SquareBrush : AlternatingSquareBrush;          rectangle.Width = Math.Max(0, Math.Min(SquareWidth, size.Width - x * SquareWidth));          rectangle.Height = Math.Max(0, Math.Min(SquareWidth, size.Height - y * SquareWidth));          rectangle.Margin = new Thickness(x * SquareWidth, y * SquareWidth, 0, 0);          rectangle.HorizontalAlignment = HorizontalAlignment.Left;          rectangle.VerticalAlignment = VerticalAlignment.Top;          this.Children.Add(rectangle);        }      }    }  }

这样子我们整个控件就差不多了。

 

扩展

由于项目里面,一个页面上需要有很多个这样的控件,感觉如果有10个需要选择颜色的地方,就要有10个实例的话,比较傻,固做以下的扩展。

添加了

Owner 属性-作为ColorPicker 颜色改变的接受源
PlacementTarget 属性- 作为ColorPicker 弹出的Target

Show 方法- 能够使用代码显示ColorPicker

用法如下:

前台Xaml

<control:ColorPicker x:Name="colorPicker" Width="300" Height="40" Opacity="0" Closed="colorPicker_Closed" SelectedColorChanged="colorPicker_SelectedColorChanged" Placement="BottomCenter" HorizontalAlignment="Center" VerticalAlignment="Top" SelectedColor="Transparent" ArrowVisibility="Visible"/>      <Rectangle x:Name="rectangle1" Width="100" Height="30" Margin="100" Fill="Green" Tapped="Rectangle_Tapped"/>      <Rectangle x:Name="rectangle2" Width="100" Height="30" Margin="100" Fill="Yellow" Tapped="Rectangle_Tapped"/>

后台cs

     private void Rectangle_Tapped(object sender, TappedRoutedEventArgs e)    {      colorPicker.Placement = AdvancedFlyoutPlacementMode.RightCenter;      colorPicker.PlacementTarget = (sender as FrameworkElement);      colorPicker.Owner = sender;      colorPicker.Show();          }    private void colorPicker_SelectedColorChanged(object sender, EventArgs e)    {      if (colorPicker.Owner!=null)      {        (colorPicker.Owner as Rectangle).Fill = new SolidColorBrush(colorPicker.SelectedColor);        colorPicker.Owner = null;      }    }    private void colorPicker_Closed(object sender, object e)    {      colorPicker.PlacementTarget = null;    }

 

总结

其实ColorPicker这个控件总体来说还是比较简单的,搞清楚UI 和HSL算法就ok。对了Colorpicker是固定了主题Light和大小的,黑色主题太丑了,而且会使色块看着及其不爽,所以背景和主题以及大小我都是写死了的。

AdvancedFlyout

背景
做这个东西,是被微软逼的。

10586 和 14393上面Flyout这个控件 行为上有很大区别。
主要问题是在10586上面,不能支持同时2个Flyout打开,就是说打开一个。再打开下一个的时候会关闭上一个。

没办法,只有自己搞一个。

AdvancedFlyoutBase/AdvancedFlyout

把微软的FlyoutBase/Floyout 属性方法都搞过来,我们自己用Popup来实现。

  /// <summary>  /// to solve issue that can't open two flyouts in 10586.  /// </summary>  [ContentProperty(Name = nameof(Content))]  public class AdvancedFlyout : AdvancedFlyoutBase  {    public UIElement Content { get; set; }    /// <summary>    /// FlyoutPresenter Style    /// </summary>    public Style FlyoutPresenterStyle { get; set; }    protected override Control CreatePresenter()    {      var fp = base.CreatePresenter() as FlyoutPresenter;      if (FlyoutPresenterStyle != null)      {        fp.Style = FlyoutPresenterStyle;      }      fp.Content = Content;      return fp;    }  }

主要的实现在于控制Popup的位置。
AdvancedFlyoutBase 里面我添加了

FlyoutBase 没有的三个属性:
IsLightDismissEnabled

VerticalOffset

HorizontalOffset

这3个属性都是Popup的。主要是在Placement的基准上再给于用户微调的权利。PlacementMode是一个枚举,比微软的分的更细。

  public enum AdvancedFlyoutPlacementMode  {    TopLeft = 0,    TopCenter,    TopRight,    BottomLeft,    BottomCenter,    BottomRight,    LeftTop,    LeftCenter,    LeftBottom,    RightTop,    RightCenter,    RightBottom,    FullScreen,    CenterScreen,  }

我们在ShowAt方法中来实现计算Popup的具体位置

    public void ShowAt(FrameworkElement placementTarget)    {      if (Opening != null)      {        Opening(this, null);      }      if (_popup == null)      {        _popup = new Popup();        _popup.ChildTransitions = new TransitionCollection() { new PopupThemeTransition() };        _popup.Opened += _popup_Opened;        _popup.Closed += _popup_Closed;        _popup.Child = CreatePresenter();      }      reCalculatePopupPosition = !CalculatePopupPosition(placementTarget);      _popup.IsLightDismissEnabled = IsLightDismissEnabled;      this.placementTarget = placementTarget;      if (reCalculatePopupPosition || FlyoutPresenter.Style == null)      {        _popup.Opacity = 0;      }      _popup.HorizontalOffset += HorizontalOffset;      _popup.VerticalOffset += VerticalOffset;      _popup.IsOpen = true;    }

其中CalculatePopupPosition 是我们的重中之重。

我们计算Popup的位置需要参考下面几样:

1.PlacementTarget在页面上的位置
其实就是控件相对于Window的位置,由以下代码获得

 var placementTargetRect = placementTarget.TransformToVisual(Window.Current.Content as FrameworkElement).TransformBounds(new Rect(0, 0, placementTarget.ActualWidth, placementTarget.ActualHeight));

2.弹出页面的大小

FlyoutPresenter的实际大小,由以下代码获得

        var fp = FlyoutPresenter;        fp.Width = double.NaN;        fp.Height = double.NaN;        if (fp.DesiredSize == fpSize)        {          fp.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));        }        fpSize = fp.DesiredSize;

3.Window 的大小

var windowSize = new Size(Window.Current.Bounds.Width, Window.Current.Bounds.Height);

有了之上3个参考数据,那么我们就很容易来计算出Popup显示的位置,

下面以Top为例:

    private bool TryHandlePlacementTop(Rect placementTargetRect, Size fpSize, Size windowSize)    {      if (placementTargetRect.Y - fpSize.Height < 0)      {        return false;      }      double x = 0;      _popup.VerticalOffset = placementTargetRect.Y - fpSize.Height;      if (fpSize.Width > windowSize.Width)      {        _popup.HorizontalOffset = 0;        return true;      }      switch (Placement)      {        case AdvancedFlyoutPlacementMode.TopLeft:          x = placementTargetRect.X;          break;        case AdvancedFlyoutPlacementMode.TopCenter:          x = placementTargetRect.X + placementTargetRect.Width / 2 - fpSize.Width / 2;          if (x < 0)          {            x = 0;          }          break;        case AdvancedFlyoutPlacementMode.TopRight:          x = placementTargetRect.X + placementTargetRect.Width - fpSize.Width;          if (x < 0)          {            x = 0;          }          break;        default:          goto case AdvancedFlyoutPlacementMode.TopCenter;      }      if (x + fpSize.Width > windowSize.Width)      {        x = windowSize.Width - fpSize.Width;      }      _popup.HorizontalOffset = x;      return true;    }

如果target控件上面的空间不够,那么肯定我们不能把Popup放上面,故return false,再尝试把Popup放在其他方位上。

如果可以放的话,我们再按照是Left,Center,Right的参考位置来计算,注意我们要考虑到Window的大小,不能超出Window。

最终Top的代码如下图

            case AdvancedFlyoutPlacementMode.TopLeft:            case AdvancedFlyoutPlacementMode.TopCenter:            case AdvancedFlyoutPlacementMode.TopRight:              if (!TryHandlePlacementTop(placementTargetRect, fpSize, windowSize))              {                if (!TryHandlePlacementBottom(placementTargetRect, fpSize, windowSize))                {                  if (!TryHandlePlacementLeft(placementTargetRect, fpSize, windowSize))                  {                    if (!TryHandlePlacementRight(placementTargetRect, fpSize, windowSize))                    {                      TryHandlePlacementCenterScreen(fpSize, windowSize);                    }                  }                }              }              break;

在开发过程中发现

如果在Popup Open之前计算FlyoutPresenter的大小,

可能导致Size不正确,如果没有给FlyoutPresenter 赋Style,这个时候还不会使用默认FlyoutPresenter 的样式,Pading,Margin这些参数还没得到赋值。

或者抛异常,比如FlyoutPresenter内部是Pivot的时候会抛异常。

所以我增加了容错。

在计算出错或者FlyoutPresenter的Style 为Null的时候,讲Popup的Opacity设置为0,

并且在Popup Open之后 重写计算位置,然后把Popup Opacity设置1.

      if (reCalculatePopupPosition || FlyoutPresenter.Style == null)      {        _popup.Opacity = 0;      }     private void _popup_Opened(object sender, object e)    {      //DesiredSize was not right when style was null before opened      //we should re-calcuatePopupPosition after FlyoutPresenter get default values from default style or app resource style      if (FlyoutPresenter.Style == null || reCalculatePopupPosition)      {        CalculatePopupPosition(placementTarget);        _popup.HorizontalOffset += HorizontalOffset;        _popup.VerticalOffset += VerticalOffset;        _popup.Opacity = 1;      }      if (Opened != null)      {        Opened(this, e);      }    }

这样就解决位置不对的问题。。其实我在使用Flyout的时候也遇到过显示的位置从左上角 跳到正确位置的情况,估计跟我这个原因一样。。估计微软也做了容错。不过没把Opacity设置一下。

总结

其实在开发中,有时间去抱怨微软版本控件有问题,不如静下心来想想其他办法,也需会比微软更好的版本,也更容易方便我们自定义。

开源有益,源码GitHub地址。

最后放上2个控件在项目里面的合体照。