WPF 实现颜色选择器控件

 WPF 实现颜色选择器控件

控件名:ColorPicker

作   者:WPFDevelopersOrg - 黄佳 | 驚鏵

原文链接[1]:https://github.com/WPFDevelopersOrg/WPFDevelopers

  • 框架使用.NET4 至 .NET6

  • Visual Studio 2022;

9e9261e30d9c07fef7e2ebdca3cae1c1.jpeg

1)新增 ColorPicker.cs 代码如下:

  • 包含一个Slider(用于选择色调)、一个Canvas(用于选择饱和度和亮度)、一个Thumb(拖动选择饱和度和亮度的指示器)和一个Button(用于切换颜色类型)。

  • 通过使用ColorPicker控件,用户可以选择一个颜色,并且可以通过绑定SelectedColor属性来获取所选颜色。这个属性是一个依赖属性,支持双向绑定,并且当颜色发生改变时会触发SelectedColorChanged事件。

  • ColorType属性用于指定颜色类型,可以选择RGB、HSL或HEX三种类型之一。当用户点击Button时,可以循环切换颜色类型。

  • 在控件的模板中,通过TemplatePart特性标记了四个重要的子元素:HueSlider、Canvas、Thumb和Button。在OnApplyTemplate方法中,根据模板找到这些子元素,并注册相应的事件处理程序。

  • 控件通过使用颜色转换工具类ColorUtil实现颜色的转换和计算。当用户在Canvas上点击或拖动Thumb时,会计算得到相应的HSB(色调、饱和度、亮度)值,并将其设置为依赖属性HSB的值。然后根据HSB的值计算出对应的颜色,并更新SelectedColor属性的值。

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
using WPFDevelopers.Utilities;

namespace WPFDevelopers.Controls
{
    [TemplatePart(Name = HueSliderColorTemplateName, Type = typeof(Slider))]
    [TemplatePart(Name = CanvasTemplateName, Type = typeof(Canvas))]
    [TemplatePart(Name = ThumbTemplateName, Type = typeof(Thumb))]
    [TemplatePart(Name = ButtonTemplateName, Type = typeof(Button))]
    public class ColorPicker : Control
    {
        private const string HueSliderColorTemplateName = "PART_HueSlider";

        private const string CanvasTemplateName = "PART_Canvas";

        private const string ThumbTemplateName = "PART_Thumb";

        private const string ButtonTemplateName = "PART_Button";

        private static readonly DependencyPropertyKey HueColorPropertyKey =
            DependencyProperty.RegisterReadOnly("HueColor", typeof(Color), typeof(ColorPicker),
                new PropertyMetadata(Colors.Red));

        public static readonly DependencyProperty HueColorProperty = HueColorPropertyKey.DependencyProperty;

        public static readonly DependencyProperty SelectedColorProperty =
            DependencyProperty.Register("SelectedColor", typeof(Color), typeof(ColorPicker),
                new FrameworkPropertyMetadata(Colors.Red, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
                    OnSelectedColorChanged));

        private static readonly DependencyPropertyKey HSBPropertyKey =
            DependencyProperty.RegisterReadOnly("HSB", typeof(HSB), typeof(ColorPicker),
                new PropertyMetadata(new HSB()));

        public static readonly DependencyProperty HSBHProperty = HSBPropertyKey.DependencyProperty;

        public static readonly DependencyProperty ColorTypeProperty =
            DependencyProperty.Register("ColorType", typeof(ColorTypeEnum), typeof(ColorPicker),
                new PropertyMetadata(ColorTypeEnum.RGB));

        private Button _button;

        private Canvas _canvas;

        private Slider _hueSliderColor;

        private bool _isInnerUpdateSelectedColor;

        private Thumb _thumb;

        private ColorTypeEnum[] colorTypeEnums;

        private int currentGridStateIndex;


        static ColorPicker()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(ColorPicker),
                new FrameworkPropertyMetadata(typeof(ColorPicker)));
        }

        public Color HueColor => (Color) GetValue(HueColorProperty);

        public Color SelectedColor
        {
            get => (Color) GetValue(SelectedColorProperty);
            set => SetValue(SelectedColorProperty, value);
        }

        public HSB HSB => (HSB) GetValue(HSBHProperty);


        public ColorTypeEnum ColorType
        {
            get => (ColorTypeEnum) GetValue(ColorTypeProperty);
            set => SetValue(ColorTypeProperty, value);
        }

        private static void OnSelectedColorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var ctrl = d as ColorPicker;
            if (ctrl._isInnerUpdateSelectedColor)
            {
                ctrl._isInnerUpdateSelectedColor = false;
                return;
            }

            var color = (Color) e.NewValue;
            double h = 0, s = 0, b = 0;
            ColorUtil.HsbFromColor(color, ref h, ref s, ref b);
            var hsb = new HSB {H = h, S = s, B = b};
            ctrl.SetValue(HueColorPropertyKey, ColorUtil.ColorFromHsb(hsb.H, 1, 1));
            ctrl.SetValue(HSBPropertyKey, hsb);
            Canvas.SetLeft(ctrl._thumb, s * ctrl._canvas.ActualWidth - ctrl._thumb.ActualWidth / 2);
            Canvas.SetTop(ctrl._thumb, (1 - b) * ctrl._canvas.ActualHeight - ctrl._thumb.ActualHeight / 2);
            ctrl._hueSliderColor.Value = 1 - h;
        }

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            if (_hueSliderColor != null)
                _hueSliderColor.ValueChanged -= HueSliderColor_OnValueChanged;
            _canvas = GetTemplateChild(CanvasTemplateName) as Canvas;
            if (_canvas != null)
            {
                _canvas.Loaded += Canvas_Loaded;
                _canvas.MouseUp += Canvas_MouseUp;
            }

            _thumb = GetTemplateChild(ThumbTemplateName) as Thumb;
            if (_thumb != null)
                _thumb.DragDelta += Thumb_DragDelta;
            _hueSliderColor = GetTemplateChild(HueSliderColorTemplateName) as Slider;
            if (_hueSliderColor != null)
                _hueSliderColor.ValueChanged += HueSliderColor_OnValueChanged;

            _button = GetTemplateChild(ButtonTemplateName) as Button;
            currentGridStateIndex = 0;
            colorTypeEnums = (ColorTypeEnum[]) Enum.GetValues(typeof(ColorTypeEnum));
            if (_button != null)
                _button.Click += Button_Click;
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            currentGridStateIndex = (currentGridStateIndex + 1) % colorTypeEnums.Length;
            ColorType = colorTypeEnums[currentGridStateIndex];
        }

        private void Canvas_MouseUp(object sender, MouseButtonEventArgs e)
        {
            var canvasPosition = e.GetPosition(_canvas);
            GetHSB(canvasPosition);
        }

        private void Canvas_MouseDown(object sender, MouseButtonEventArgs e)
        {
            var canvasPosition = e.GetPosition(_canvas);
            GetHSB(canvasPosition);
        }

        private void GetHSB(Point point, bool isMove = true)
        {
            var newLeft = point.X - _thumb.ActualWidth / 2;
            var newTop = point.Y - _thumb.ActualHeight / 2;
            var thumbW = _thumb.ActualWidth / 2;
            var thumbH = _thumb.ActualHeight / 2;
            var canvasRight = _canvas.ActualWidth - thumbW;
            var canvasBottom = _canvas.ActualHeight - thumbH;
            if (newLeft < -thumbW)
                newLeft = -thumbW;
            else if (newLeft > canvasRight)
                newLeft = canvasRight;
            if (newTop < -thumbH)
                newTop = -thumbH;
            else if (newTop > canvasBottom)
                newTop = canvasBottom;

            if (isMove)
            {
                Canvas.SetLeft(_thumb, newLeft);
                Canvas.SetTop(_thumb, newTop);
            }

            var hsb = new HSB
            {
                H = HSB.H, S = (newLeft + thumbW) / _canvas.ActualWidth,
                B = 1 - (newTop + thumbH) / _canvas.ActualHeight
            };
            SetValue(HSBPropertyKey, hsb);
            var currentColor = ColorUtil.ColorFromAhsb(1, HSB.H, HSB.S, HSB.B);
            if (SelectedColor != currentColor)
            {
                _isInnerUpdateSelectedColor = true;
                SelectedColor = currentColor;
            }
        }

        private void Thumb_DragDelta(object sender, DragDeltaEventArgs e)
        {
            var point = Mouse.GetPosition(_canvas);
            GetHSB(point);
        }

        private void Canvas_Loaded(object sender, RoutedEventArgs e)
        {
            var width = (int) _canvas.ActualWidth;
            var height = (int) _canvas.ActualHeight;
            var point = new Point(width - _thumb.ActualWidth / 2, -_thumb.ActualHeight / 2);
            Canvas.SetLeft(_thumb, point.X);
            Canvas.SetTop(_thumb, point.Y);
            var hsb = new HSB {H = _hueSliderColor.Value, S = HSB.S, B = HSB.B};
            SetValue(HSBPropertyKey, hsb);
        }

        private void HueSliderColor_OnValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if (DoubleUtil.AreClose(HSB.H, e.NewValue))
                return;
            var hsb = new HSB {H = 1 - e.NewValue, S = HSB.S, B = HSB.B};
            SetValue(HSBPropertyKey, hsb);
            SetValue(HueColorPropertyKey, ColorUtil.ColorFromHsb(HSB.H, 1, 1));

            var newLeft = Canvas.GetLeft(_thumb);
            var newTop = Canvas.GetTop(_thumb);
            var point = new Point(newLeft, newTop);
            GetHSB(point, false);
        }
    }

    public enum ColorTypeEnum
    {
        RGB,
        HSL,
        HEX
    }
}

2)新增 ColorPicker.xaml 代码如下:

  • 在Canvas的背景中使用了DrawingBrush和DrawingGroup来创建渐变和几何形状。

  • 创建了一个线性渐变刷(LinearGradientBrush),渐变的起点是(0,0),终点是(1,0)。其中的GradientStop标签指定了两个渐变停止点,一个偏移量为0,颜色为白色,另一个偏移量为1,颜色绑定到了HueColor属性。

  • 接下来,同样方式创建了另一个线性渐变刷,渐变的起点是(0,0),终点是(0,1)。GradientStop指定了两个渐变停止点,一个偏移量为0,颜色为透明("#00000000"),另一个偏移量为1,颜色为黑色。

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:controls="clr-namespace:WPFDevelopers.Controls"
    xmlns:convert="clr-namespace:WPFDevelopers.Converts"
    xmlns:helpers="clr-namespace:WPFDevelopers.Helpers"
    xmlns:input="clr-namespace:System.Windows.Input;assembly=PresentationCore"
    xmlns:po="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options">
    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="Basic/ControlBasic.xaml" />
    </ResourceDictionary.MergedDictionaries>

    <convert:ColorToBrushConverter x:Key="WD.ColorToBrushConverter" />
    <convert:ColorToRedConverter x:Key="WD.ColorToRedConverter" />
    <convert:ColorToGreenConverter x:Key="WD.ColorToGreenConverter" />
    <convert:ColorToBlueConverter x:Key="WD.ColorToBlueConverter" />
    <convert:ColorTypeToVisibilityConverter x:Key="WD.ColorTypeToVisibilityConverter" />
    <convert:ColorToStringConverter x:Key="WD.ColorToStringConverter" />
    <convert:HToColorConverter x:Key="WD.HToColorConverter" />
    <convert:SToColorConverter x:Key="WD.SToColorConverter" />
    <convert:LToColorConverter x:Key="WD.LToColorConverter" />

    <LinearGradientBrush x:Key="WD.ColorPickerRainbowBrush" po:Freeze="True">
        <GradientStop Color="#FF0000" />
        <GradientStop Offset="0.167" Color="#FF00FF" />
        <GradientStop Offset="0.334" Color="#0000FF" />
        <GradientStop Offset="0.501" Color="#00FFFF" />
        <GradientStop Offset="0.668" Color="#00FF00" />
        <GradientStop Offset="0.835" Color="#FFFF00" />
        <GradientStop Offset="1" Color="#FF0000" />
    </LinearGradientBrush>

    <ControlTemplate x:Key="WD.ColorPickerSliderThumbTemplate" TargetType="Thumb">
        <Border
            Width="{TemplateBinding Width}"
            Height="{TemplateBinding Height}"
            Background="Transparent"
            BorderBrush="White"
            BorderThickness="3"
            CornerRadius="{Binding ActualWidth, RelativeSource={RelativeSource Self}, Converter={StaticResource WD.HalfValueConverter}}" />
    </ControlTemplate>


    <Style x:Key="WD.ColorPickerSliderRepeatButtonBaseStyle" TargetType="RepeatButton">
        <Setter Property="OverridesDefaultStyle" Value="true" />
        <Setter Property="Background" Value="Transparent" />
        <Setter Property="Focusable" Value="false" />
        <Setter Property="IsTabStop" Value="false" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="RepeatButton">
                    <Rectangle
                        Width="{TemplateBinding Width}"
                        Height="{TemplateBinding Height}"
                        Fill="{TemplateBinding Background}" />
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>



    <Style x:Key="WD.ColorPickerSlider" TargetType="{x:Type Slider}">
        <Setter Property="Background" Value="{StaticResource WD.ColorPickerRainbowBrush}" />
        <Setter Property="Stylus.IsPressAndHoldEnabled" Value="false" />
        <Setter Property="Orientation" Value="Horizontal" />
        <Setter Property="Height" Value="15" />
        <Setter Property="Margin" Value="4,0" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type Slider}">
                    <controls:SmallPanel>
                        <Border
                            MaxWidth="{TemplateBinding MaxWidth}"
                            Margin="{TemplateBinding Margin}"
                            Background="{TemplateBinding Background}"
                            CornerRadius="2" />
                        <Track x:Name="PART_Track" Orientation="{TemplateBinding Orientation}">
                            <Track.DecreaseRepeatButton>
                                <RepeatButton Command="{x:Static Slider.DecreaseLarge}" Style="{StaticResource WD.ColorPickerSliderRepeatButtonBaseStyle}" />
                            </Track.DecreaseRepeatButton>
                            <Track.IncreaseRepeatButton>
                                <RepeatButton Command="{x:Static Slider.IncreaseLarge}" Style="{StaticResource WD.ColorPickerSliderRepeatButtonBaseStyle}" />
                            </Track.IncreaseRepeatButton>
                            <Track.Thumb>
                                <Thumb
                                    x:Name="Thumb"
                                    Width="15"
                                    Height="15"
                                    Focusable="False"
                                    OverridesDefaultStyle="True"
                                    Template="{StaticResource WD.ColorPickerSliderThumbTemplate}">
                                    <Thumb.Effect>
                                        <DropShadowEffect Opacity=".6" ShadowDepth="0" />
                                    </Thumb.Effect>
                                </Thumb>
                            </Track.Thumb>
                        </Track>
                    </controls:SmallPanel>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>


    <Style
        x:Key="WD.ColorPicker"
        BasedOn="{StaticResource WD.ControlBasicStyle}"
        TargetType="{x:Type controls:ColorPicker}">
        <Setter Property="Width" Value="260" />
        <Setter Property="Height" Value="200" />
        <Setter Property="Margin" Value="2" />
        <Setter Property="BorderThickness" Value="1" />
        <Setter Property="BorderBrush" Value="{DynamicResource WD.BaseSolidColorBrush}" />
        <Setter Property="Background" Value="{DynamicResource WD.BackgroundSolidColorBrush}" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type controls:ColorPicker}">
                    <Border
                        Margin="{TemplateBinding Margin}"
                        Background="{TemplateBinding Background}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}">
                        <Grid>
                            <Grid.RowDefinitions>
                                <RowDefinition />
                                <RowDefinition Height="Auto" />
                                <RowDefinition Height="Auto" />
                            </Grid.RowDefinitions>
                            <Canvas
                                x:Name="PART_Canvas"
                                Margin="1,1,1,0"
                                ClipToBounds="True">
                                <Canvas.Background>
                                    <DrawingBrush>
                                        <DrawingBrush.Drawing>
                                            <DrawingGroup>
                                                <GeometryDrawing>
                                                    <GeometryDrawing.Brush>
                                                        <LinearGradientBrush EndPoint="1,0">
                                                            <GradientStop Offset="0" Color="White" />
                                                            <GradientStop Offset="1" Color="{Binding HueColor, RelativeSource={RelativeSource TemplatedParent}}" />
                                                        </LinearGradientBrush>
                                                    </GeometryDrawing.Brush>
                                                    <GeometryDrawing.Geometry>
                                                        <RectangleGeometry Rect="0,0,5,5" />
                                                    </GeometryDrawing.Geometry>
                                                </GeometryDrawing>
                                                <GeometryDrawing>
                                                    <GeometryDrawing.Brush>
                                                        <LinearGradientBrush EndPoint="0,1">
                                                            <GradientStop Offset="0" Color="#00000000" />
                                                            <GradientStop Offset="1" Color="{StaticResource WD.BlackColor}" />
                                                        </LinearGradientBrush>
                                                    </GeometryDrawing.Brush>
                                                    <GeometryDrawing.Geometry>
                                                        <RectangleGeometry Rect="0,0,5,5" />
                                                    </GeometryDrawing.Geometry>
                                                </GeometryDrawing>
                                            </DrawingGroup>
                                        </DrawingBrush.Drawing>
                                    </DrawingBrush>
                                </Canvas.Background>
                                <Thumb
                                    x:Name="PART_Thumb"
                                    Width="15"
                                    Height="15"
                                    Background="Transparent"
                                    BorderBrush="White"
                                    BorderThickness="3">
                                    <Thumb.Template>
                                        <ControlTemplate TargetType="{x:Type Thumb}">
                                            <controls:SmallPanel>
                                                <Border
                                                    Background="{TemplateBinding Background}"
                                                    BorderBrush="{TemplateBinding BorderBrush}"
                                                    BorderThickness="{TemplateBinding BorderThickness}"
                                                    CornerRadius="{Binding ActualWidth, RelativeSource={RelativeSource Self}, Converter={StaticResource WD.HalfValueConverter}}"
                                                    SnapsToDevicePixels="True">
                                                    <Border.Effect>
                                                        <DropShadowEffect Opacity=".6" ShadowDepth="0" />
                                                    </Border.Effect>
                                                </Border>
                                            </controls:SmallPanel>
                                        </ControlTemplate>
                                    </Thumb.Template>
                                </Thumb>
                            </Canvas>
                            <Grid Grid.Row="1" Margin="6,5,6,0">
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="Auto" />
                                    <ColumnDefinition />
                                </Grid.ColumnDefinitions>
                                <Ellipse
                                    Width="25"
                                    Height="25"
                                    Margin="0,0,4,0"
                                    Fill="{TemplateBinding SelectedColor,
                                                           Converter={StaticResource WD.ColorToBrushConverter}}">
                                    <Ellipse.Effect>
                                        <DropShadowEffect Opacity=".6" ShadowDepth="0" />
                                    </Ellipse.Effect>
                                </Ellipse>
                                <Slider
                                    Name="PART_HueSlider"
                                    Grid.Column="1"
                                    Width="Auto"
                                    IsMoveToPointEnabled="True"
                                    LargeChange="0.01"
                                    Maximum="1"
                                    SmallChange="0.01"
                                    Style="{StaticResource WD.ColorPickerSlider}"
                                    Value="1" />
                            </Grid>
                            <Grid
                                Grid.Row="2"
                                Margin="4"
                                VerticalAlignment="Center">
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="Auto" />
                                    <ColumnDefinition />
                                </Grid.ColumnDefinitions>
                                <Grid.Resources>
                                    <Style TargetType="{x:Type StackPanel}">
                                        <Setter Property="Margin" Value="4,0" />
                                    </Style>
                                    <Style
                                        x:Key="WD.TextBoxColorPicker"
                                        BasedOn="{StaticResource WD.DefaultTextBox}"
                                        TargetType="{x:Type TextBox}">
                                        <Setter Property="VerticalContentAlignment" Value="Center" />
                                        <Setter Property="TextAlignment" Value="Center" />
                                        <Setter Property="Width" Value="50" />
                                    </Style>
                                    <Style BasedOn="{StaticResource WD.NumericBox}" TargetType="{x:Type controls:NumericBox}">
                                        <Setter Property="VerticalContentAlignment" Value="Center" />
                                        <Setter Property="TextAlignment" Value="Center" />
                                        <Setter Property="Width" Value="50" />
                                        <Setter Property="UpDownButtonsWidth" Value="0" />
                                        <Setter Property="Maximum" Value="255" />
                                        <Setter Property="Minimum" Value="0" />
                                    </Style>
                                    <Style TargetType="{x:Type TextBlock}">
                                        <Setter Property="HorizontalAlignment" Value="Center" />
                                        <Setter Property="Foreground" Value="{DynamicResource WD.PrimaryTextSolidColorBrush}" />
                                        <Setter Property="FontSize" Value="10" />
                                    </Style>
                                </Grid.Resources>

                                <Button
                                    Name="PART_Button"
                                    Grid.Column="0"
                                    Width="30"
                                    Height="30"
                                    Margin="0,0,4,0"
                                    helpers:ElementHelper.IsRound="True"
                                    Style="{StaticResource WD.NormalButton}">
                                    <controls:PathIcon Kind="UnfoldMore" />
                                </Button>

                                <UniformGrid
                                    Grid.Column="1"
                                    Rows="1"
                                    Visibility="{Binding ColorType, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource WD.ColorTypeToVisibilityConverter}, ConverterParameter={x:Static controls:ColorTypeEnum.RGB}}">
                                    <StackPanel>
                                        <controls:NumericBox Value="{Binding SelectedColor, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource WD.ColorToRedConverter}}" />
                                        <TextBlock Text="R" />
                                    </StackPanel>
                                    <StackPanel>
                                        <controls:NumericBox Value="{Binding SelectedColor, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource WD.ColorToGreenConverter}}" />
                                        <TextBlock Text="G" />
                                    </StackPanel>
                                    <StackPanel>
                                        <controls:NumericBox Value="{Binding SelectedColor, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource WD.ColorToBlueConverter}}" />
                                        <TextBlock Text="B" />
                                    </StackPanel>
                                </UniformGrid>

                                <UniformGrid
                                    Grid.Column="1"
                                    Rows="1"
                                    Visibility="{Binding ColorType, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource WD.ColorTypeToVisibilityConverter}, ConverterParameter={x:Static controls:ColorTypeEnum.HSL}}">
                                    <StackPanel>
                                        <TextBox
                                            helpers:TextBoxHelper.AllowOnlyNumericInput="True"
                                            helpers:TextBoxHelper.IsEnterUpdateEnabled="True"
                                            helpers:TextBoxHelper.SelectAllOnClick="True"
                                            Style="{StaticResource WD.TextBoxColorPicker}"
                                            Text="{Binding SelectedColor, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource WD.HToColorConverter}}" />
                                        <TextBlock Text="H" />
                                    </StackPanel>
                                    <StackPanel Grid.Column="1">
                                        <TextBox
                                            helpers:TextBoxHelper.IsEnterUpdateEnabled="True"
                                            helpers:TextBoxHelper.SelectAllOnClick="True"
                                            Style="{StaticResource WD.TextBoxColorPicker}"
                                            Text="{Binding SelectedColor, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource WD.SToColorConverter}}" />
                                        <TextBlock Text="S" />
                                    </StackPanel>
                                    <StackPanel Grid.Column="2">
                                        <TextBox
                                            helpers:TextBoxHelper.IsEnterUpdateEnabled="True"
                                            helpers:TextBoxHelper.SelectAllOnClick="True"
                                            Style="{StaticResource WD.TextBoxColorPicker}"
                                            Text="{Binding SelectedColor, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource WD.LToColorConverter}}" />
                                        <TextBlock Text="L" />
                                    </StackPanel>
                                </UniformGrid>
                                <StackPanel
                                    Grid.Column="1"
                                    Margin="12,0"
                                    Visibility="{Binding ColorType, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource WD.ColorTypeToVisibilityConverter}, ConverterParameter={x:Static controls:ColorTypeEnum.HEX}}">
                                    <TextBox
                                        helpers:TextBoxHelper.IsEnterUpdateEnabled="True"
                                        helpers:TextBoxHelper.SelectAllOnClick="True"
                                        MaxLength="9"
                                        Text="{Binding SelectedColor, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource WD.ColorToStringConverter}}"
                                        TextAlignment="Center" />
                                    <TextBlock Text="HEX" />
                                </StackPanel>
                            </Grid>
                        </Grid>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    <Style BasedOn="{StaticResource WD.ColorPicker}" TargetType="{x:Type controls:ColorPicker}" />
</ResourceDictionary>

3)新增颜色转换工具类 ColorUtil.cs 代码如下:

  • 包含了将 HSL 颜色空间转换为 RGB 颜色空间以及将 RGB 颜色空间转换为 HSL 颜色空间的方法。

  • ConvertHSLToColor 方法用于将 HSL 颜色转换为 RGB 颜色。HSLToRgb 方法实现了将 HSL 颜色转换为 RGB 颜色的算法,而 SetColor 方法用于在计算过程中设置颜色值。

  • RgbToHSL 方法用于将 RGB 颜色转换为 HSL 颜色。HsbFromColor 方法则实现了从 RGB 颜色获取 HSB(色相、饱和度、亮度)的算法,并修改了传入的引用类型参数。ColorFromAhsb 和 ColorFromHsb 方法分别用于根据 AHSB(透明度、色相、饱和度、亮度)和 HSB 值创建颜色对象。

using System;
using System.Windows.Media;
using WPFDevelopers.Controls;

namespace WPFDevelopers.Utilities
{
    public static class ColorUtil
    {
        public static Color ConvertHSLToColor(Color color, double h = double.NaN, double sl = double.NaN,
            double l = double.NaN)
        {
            var hsl = RgbToHSL(color);
            if (!double.IsNaN(h))
                hsl.H = h;
            if (!double.IsNaN(sl))
                hsl.S = sl;
            if (!double.IsNaN(l))
                hsl.L = l;
            var rgb = HSLToRgb(hsl);
            return rgb;
        }

        private static Color HSLToRgb(HSL hslColor)
        {
            var rgbColor = new Color();
            if (hslColor.S == 0)
            {
                rgbColor.R = (byte) (hslColor.L * 255);
                rgbColor.G = (byte) (hslColor.L * 255);
                rgbColor.B = (byte) (hslColor.L * 255);
                rgbColor.A = (byte) (hslColor.A * 255);
                return rgbColor;
            }

            double t1;
            if (hslColor.L < 0.5)
                t1 = hslColor.L * (1.0 + hslColor.S);
            else
                t1 = hslColor.L + hslColor.S - hslColor.L * hslColor.S;

            var t2 = 2.0 * hslColor.L - t1;

            var h = hslColor.H / 360;

            var tR = h + 1.0 / 3.0;
            var r = SetColor(t1, t2, tR);

            var tG = h;
            var g = SetColor(t1, t2, tG);

            var tB = h - 1.0 / 3.0;
            var b = SetColor(t1, t2, tB);

            rgbColor.R = (byte) (r * 255);
            rgbColor.G = (byte) (g * 255);
            rgbColor.B = (byte) (b * 255);
            rgbColor.A = (byte) (hslColor.A * 255);

            return rgbColor;
        }

        private static double SetColor(double t1, double t2, double t3)
        {
            if (t3 < 0) t3 += 1.0;
            if (t3 > 1) t3 -= 1.0;

            double color;
            if (6.0 * t3 < 1)
                color = t2 + (t1 - t2) * 6.0 * t3;
            else if (2.0 * t3 < 1)
                color = t1;
            else if (3.0 * t3 < 2)
                color = t2 + (t1 - t2) * (2.0 / 3.0 - t3) * 6.0;
            else
                color = t2;
            return color;
        }

        public static HSL RgbToHSL(Color rgbColor)
        {
            var hslColor = new HSL();
            var r = (double) rgbColor.R / 255;
            var g = (double) rgbColor.G / 255;
            var b = (double) rgbColor.B / 255;
            var a = (double) rgbColor.A / 255;

            var min = Math.Min(r, Math.Min(g, b));
            var max = Math.Max(r, Math.Max(g, b));
            var delta = max - min;


            if (max == min)
            {
                hslColor.H = 0;
                hslColor.S = 0;
                hslColor.L = max;
                return hslColor;
            }

            hslColor.L = (min + max) / 2;

            if (hslColor.L < 0.5)
                hslColor.S = delta / (max + min);
            else
                hslColor.S = delta / (2.0 - max - min);

            if (r == max) hslColor.H = (g - b) / delta;
            if (g == max) hslColor.H = 2.0 + (b - r) / delta;
            if (b == max) hslColor.H = 4.0 + (r - g) / delta;
            hslColor.H *= 60;
            if (hslColor.H < 0) hslColor.H += 360;

            hslColor.A = a;

            return hslColor;
        }
       
        public static void HsbFromColor(Color C, ref double H, ref double S, ref double B)
        {
            var r = C.R / 255d;
            var g = C.G / 255d;
            var b = C.B / 255d;

            var max = Math.Max(Math.Max(r, g), b);
            var min = Math.Min(Math.Min(r, g), b);
            var delta = max - min;

            var hue = 0d;
            var saturation = DoubleUtil.GreaterThan(max, 0) ? delta / max : 0.0;
            var brightness = max;

            if (!DoubleUtil.IsZero(delta))
            {
                if (DoubleUtil.AreClose(r, max))
                    hue = (g - b) / delta;
                else if (DoubleUtil.AreClose(g, max))
                    hue = 2 + (b - r) / delta;
                else if (DoubleUtil.AreClose(b, max))
                    hue = 4 + (r - g) / delta;

                hue = hue * 60;
                if (DoubleUtil.LessThan(hue, 0d))
                    hue += 360;
            }

            H = hue / 360d;
            S = saturation;
            B = brightness;
        }

        public static Color ColorFromAhsb(double a, double h, double s, double b)
        {
            var r = ColorFromHsb(h, s, b);
            r.A = (byte) Math.Round(a * 255d);

            return r;
        }

        public static Color ColorFromHsb(double H, double S, double B)
        {
            double red = 0.0, green = 0.0, blue = 0.0;

            if (DoubleUtil.IsZero(S))
            {
                red = green = blue = B;
            }
            else
            {
                var h = DoubleUtil.IsOne(H) ? 0d : H * 6.0;
                var i = (int) Math.Floor(h);

                var f = h - i;
                var r = B * (1.0 - S);
                var s = B * (1.0 - S * f);
                var t = B * (1.0 - S * (1.0 - f));

                switch (i)
                {
                    case 0:
                        red = B;
                        green = t;
                        blue = r;
                        break;
                    case 1:
                        red = s;
                        green = B;
                        blue = r;
                        break;
                    case 2:
                        red = r;
                        green = B;
                        blue = t;
                        break;
                    case 3:
                        red = r;
                        green = s;
                        blue = B;
                        break;
                    case 4:
                        red = t;
                        green = r;
                        blue = B;
                        break;
                    case 5:
                        red = B;
                        green = r;
                        blue = s;
                        break;
                }
            }

            return Color.FromRgb((byte) Math.Round(red * 255.0), (byte) Math.Round(green * 255.0),
                (byte) Math.Round(blue * 255.0));
        }
    }

}

4)示例 代码如下:

xmlns:wd="https://github.com/WPFDevelopersOrg/WPFDevelopers"
<wd:ColorPicker />
e2fb359ea24427583ee5dc42628d5501.gif

参考资料

[1]

原文链接: https://github.com/WPFDevelopersOrg/WPFDevelopers

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值