UWP 颜色选择器(ColorPicker) 和 自定义的Flyout(AdvancedFlyout)

简介: 原文:UWP 颜色选择器(ColorPicker) 和 自定义的Flyout(AdvancedFlyout)ColorPicker 故事背景 项目里面需要一个像Winfrom里面那样的颜色选择器,如下图所示: 在网上看了一下。
原文: 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个控件在项目里面的合体照。

目录
相关文章
|
iOS开发
iOS开发 - 改变picker选中行字体颜色
iOS开发 - 改变picker选中行字体颜色
223 0
从零开始学Pyqt5之【控件介绍】(13):QFontDialog字体选择对话框控件
从零开始学Pyqt5之【控件介绍】(13):QFontDialog字体选择对话框控件
从零开始学Pyqt5之【控件介绍】(13):QFontDialog字体选择对话框控件
|
C#
WPF自定义TabControl样式
原文:WPF自定义TabControl样式 WPF自定义TabControl,TabControl美化 XAML代码: ...
4675 0
QTabWidget添加自定义样式
QTabWidget添加自定义样式
665 0
QTabWidget添加自定义样式
|
程序员 C语言
Qt编写自定义控件52-颜色下拉框
一、前言 这个控件写了很久了,元老级别的控件之一,开发之初主要是自己的好几个项目要用到,比如提供一个颜色下拉框设置对应的曲线或者时间颜色,视频监控项目中经常用到的OSD标签设置,这个控件的难度系数接近0,初学者都会,其实网上搜索也很多人提供了绘制的方法,就是枚举QColor::colorNames()拿到所有的内置的颜色,然后生成对应的图片作为icon设置到下拉框的item中去,对应icon的宽高由控件本身的宽高决定,本控件继承自qcombobox控件,完全保留了该控件的所有特性,同时新增了颜色改变信号,以便用户使用。
1290 0
Qt编写自定义控件28-颜色滑块面板
一、前言 相比于上一个颜色按钮面板,此控件就要难很多,颜色值有三种表示形式,除了程序员最常用的RGB以外,还有HSB和CMY方式。RGB色彩模式是工业界的一种颜色标准,是通过对红(R)、绿(G)、蓝(B)三个颜色通道的变化以及它们相互之间的叠加来得到各式各样的颜色的,RGB即是代表红、绿、蓝三个通道的颜色,这个标准几乎包括了人类视力所能感知的所有颜色,是目前运用最广的颜色系统之一。
887 0
|
C#
WPF自定义Window窗体样式
原文:WPF自定义Window窗体样式 资源文件代码: ...
2503 0
|
前端开发 Windows
[UWP]使用Picker实现一个简单的ColorPicker弹窗
原文:[UWP]使用Picker实现一个简单的ColorPicker弹窗 在上一篇博文《[UWP]使用Popup构建UWP Picker》中我们简单讲述了一下使用Popup构建适用于MVVM框架下的弹窗层组件Picker的过程。
1107 0
|
C# 开发者
[UWP]新控件ColorPicker
原文:[UWP]新控件ColorPicker 1. 前言 Fall Creators Update中提供了一个新得ColorPicker控件,解决了以前选择颜色只能用Combo Box的窘境。 2. 一个简单的例子 如上所示,ColorPiker可以通过在光谱或色轮上拖动滑块,或者在RGB/HSV及十六进制的TextBox中直接输入颜色的数值改变Color属性。
1018 0
|
C#
自定义WPF 窗口样式
原文:自定义WPF 窗口样式 自定义 Window 在客户端程序中,经常需要用到自定义一个 Window ,大部分是为了好看吧。
1289 0