WPF 简单实现颜色选择器

 WPF 简单实现颜色选择器

控件名:ColorPicker

作   者:WPFDevelopersOrg - 驚鏵

原文链接[1]:https://github.com/yanjinhuagood/ColorPickerSample

  • 框架使用.NET4

  • Visual Studio 2022;

5b18ace2a54a746deaf9b3219df36488.png

1)新增 xaml 代码如下:

  • 定义一个WriteableBitmap用于记录颜色缓冲值。

  • XAML中定义Canvas设置背景为一张图像。

  • Canvas中添加Thumb 是一个可拖动的控件,用于实现交互中的拖动后获取颜色。

<Canvas x:Name="canvas" MouseLeftButtonDown="canvas_MouseLeftButtonDown">
                <Canvas.Background>
                    <ImageBrush ImageSource="{Binding Bitmap}" />
                </Canvas.Background>
                <Thumb
                    x:Name="thumb"
                    Canvas.Left="0"
                    Canvas.Top="0"
                    Width="20"
                    Height="20"
                    Background="Transparent"
                    BorderBrush="Black"
                    BorderThickness="2"
                    DragDelta="Thumb_DragDelta">
                    <Thumb.Template>
                        <ControlTemplate TargetType="Thumb">
                            <Border
                                Background="{TemplateBinding Background}"
                                BorderBrush="{TemplateBinding BorderBrush}"
                                BorderThickness="{TemplateBinding BorderThickness}"
                                CornerRadius="10"
                                SnapsToDevicePixels="True" />
                        </ControlTemplate>
                    </Thumb.Template>
                </Thumb>
            </Canvas>

2)新增 Loaded逻辑 处理代码如下:

  • 首先嵌套循环,用于在图像的每个像素位置上进行操作色值。

  • heightwidth 是图像的高度和宽度。

  • 在外层循环中,变量 y 从 0 开始递增,直到小于 height

  • 在内层循环中,变量 x 从 0 开始递增,直到小于 width

  • 在每个像素位置上,通过计算 normalizedXnormalizedY,将 xy 的值归一化到 [0, 1] 范围内。

  • 使用 HSVToRGB 函数将归一化后的 normalizedXnormalizedY 和 1(表示最大亮度)转换为 RGB 值,并将结果存储在 rgb 变量中。

  • 计算像素在图像数据缓冲区中的偏移量 pixelOffset,其中 stride 是每行像素占用的字节数。

  • 使用 Marshal.WriteByte 方法将 RGB 值写入图像数据缓冲区中的相应位置,同时设置 Alpha 通道为 0xFF(完全不透明)。

IntPtr backBuffer = Bitmap.BackBuffer;
            int stride = Bitmap.BackBufferStride;
            for (int y = 0; y < height; y++)
            {
                for (int x = 0; x < width; x++)
                {
                    byte r, g, b;

                    double normalizedX = (double)x / (width - 1);
                    double normalizedY = (double)y / (height - 1);

                    HSVToRGB(normalizedX, normalizedY, 1, out r, out g, out b);

                    int pixelOffset = y * stride + x * 4;
                    Marshal.WriteByte(backBuffer, pixelOffset + 0, b);
                    Marshal.WriteByte(backBuffer, pixelOffset + 1, g);
                    Marshal.WriteByte(backBuffer, pixelOffset + 2, r);
                    Marshal.WriteByte(backBuffer, pixelOffset + 3, 0xFF);
                }
            }

3)新增 HSVToRGB 方法代码如下:

  • HSVToRGB 方法返回 r 、g、b

  • h 是色相值,取值范围为 [0, 1]。

  • s 是饱和度值,取值范围为 [0, 1]。

  • v 是亮度值,取值范围为 [0, 1]。

  • rgb 是输出参数,用于存储转换后的 RGB 值。

  • 在函数内部,根据 HSV 转换公式进行计算。如果饱和度 s 为 0,则表示灰度色调,此时将 RGB 的三个分量都设置为亮度 v 的值,并乘以 255 转换为字节表示。

  • 如果饱和度 s 不为 0,则根据色相 h 的值确定所处的色相区间,并根据公式计算出对应的 RGB 值。具体步骤如下:

  • 将色相 h 乘以 6,得到一个扩展的色相值 hue

  • hue 的整数部分作为索引 i,表示所处的色相区间。

  • 计算 hue 的小数部分 f

  • 根据公式计算出对应的 RGB 值,其中 p 是亮度 v 与饱和度 s 的乘积,q 是亮度 v 与饱和度 s 以及 f 的乘积,t 是亮度 v 与饱和度 s 以及 (1.0 - f) 的乘积。

  • 根据索引 i 的值,将计算得到的 RGB 值赋给输出参数 rgb

private static void HSVToRGB(double h, double s, double v, out byte r, out byte g, out byte b)
        {
            if (s == 0)
            {
                r = g = b = (byte)(v * 255);
            }
            else
            {
                double hue = h * 6.0;
                int i = (int)Math.Floor(hue);
                double f = hue - i;
                double p = v * (1.0 - s);
                double q = v * (1.0 - (s * f));
                double t = v * (1.0 - (s * (1.0 - f)));

                switch (i)
                {
                    case 0:
                        r = (byte)(v * 255);
                        g = (byte)(t * 255);
                        b = (byte)(p * 255);
                        break;
                    case 1:
                        r = (byte)(q * 255);
                        g = (byte)(v * 255);
                        b = (byte)(p * 255);
                        break;
                    case 2:
                        r = (byte)(p * 255);
                        g = (byte)(v * 255);
                        b = (byte)(t * 255);
                        break;
                    case 3:
                        r = (byte)(p * 255);
                        g = (byte)(q * 255);
                        b = (byte)(v * 255);
                        break;
                    case 4:
                        r = (byte)(t * 255);
                        g = (byte)(p * 255);
                        b = (byte)(v * 255);
                        break;
                    default:
                        r = (byte)(v * 255);
                        g = (byte)(p * 255);
                        b = (byte)(q * 255);
                        break;
                }
            }
        }

4)新增 Thumb_DragDelta 代码如下:

  • 在事件处理程序中,首先获取拖动的 Thumb 控件,并计算出新的左侧和顶部位置。通过 Canvas.GetLeft(thumb)Canvas.GetTop(thumb) 方法获取当前 Thumb 控件在 Canvas 中的左侧和顶部位置,然后将其与拖动的变化量 e.HorizontalChangee.VerticalChange 相加,得到新的位置。

  • 计算 Canvas 的右侧和底部边界。通过 canvas.ActualWidth - thumb.ActualWidthcanvas.ActualHeight - thumb.ActualHeight 计算出 Canvas 的右侧和底部边界位置。

  • 对新的左侧和顶部位置进行边界检查。如果新的左侧位置小于 0,则将其设置为 0,以保证 Thumb 控件不会超出 Canvas 的左侧边界。如果新的左侧位置大于 Canvas 的右侧边界位置 canvasRight,则将其设置为 canvasRight,以确保 Thumb 控件不会超出 Canvas 的右侧边界。类似地,对新的顶部位置进行边界检查。

  • 通过 Canvas.SetLeft(thumb, newLeft)Canvas.SetTop(thumb, newTop)Thumb 控件的位置更新为新的左侧和顶部位置。

  • 调用 GetAreaColor() 方法来获取更新后的区域颜色。

private void Thumb_DragDelta(object sender, DragDeltaEventArgs e)
        {
            var thumb = (Thumb)sender;
            double newLeft = Canvas.GetLeft(thumb) + e.HorizontalChange;
            double newTop = Canvas.GetTop(thumb) + e.VerticalChange;
            double canvasRight = canvas.ActualWidth - thumb.ActualWidth;
            double canvasBottom = canvas.ActualHeight - thumb.ActualHeight;

            if (newLeft < 0)
                newLeft = 0;
            else if (newLeft > canvasRight)
                newLeft = canvasRight;

            if (newTop < 0)
                newTop = 0;
            else if (newTop > canvasBottom)
                newTop = canvasBottom;

            Canvas.SetLeft(thumb, newLeft);
            Canvas.SetTop(thumb, newTop);

            GetAreaColor();
        }

5)新增 canvas_MouseLeftButtonDown 代码如下:

  • 实现鼠标左键按下时,将 Thumb 控件移动到鼠标点击位置,并进行边界限制。同时,还获取了鼠标点击位置的颜色信息

private void canvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            var canvasPosition = e.GetPosition(canvas);
            double newLeft = canvasPosition.X - thumb.ActualWidth / 2;
            double newTop = canvasPosition.Y - thumb.ActualHeight / 2;

            double canvasRight = canvas.ActualWidth - thumb.ActualWidth;
            double canvasBottom = canvas.ActualHeight - thumb.ActualHeight;

            if (newLeft < 0)
                newLeft = 0;
            else if (newLeft > canvasRight)
                newLeft = canvasRight;

            if (newTop < 0)
                newTop = 0;
            else if (newTop > canvasBottom)
                newTop = canvasBottom;

            Canvas.SetLeft(thumb, newLeft);
            Canvas.SetTop(thumb, newTop);
            var thumbPosition = e.GetPosition(canvas);
            GetAreaColor(thumbPosition);
        }

5)新增 GetAreaColor 代码如下:

  • Thumb 控件的中心点坐标转换为相对于 Canvas 的坐标。

  • 计算每行像素数据所占的字节数。

  • 创建一个字节数组,用于存储位图的像素数据。

  • 将位图的像素数据复制到字节数组中。

  • 计算要访问的像素在字节数组中的索引位置。

  • Color.FromArgb取其 Alpha、红色、绿色和蓝色通道的值。在这段代码中,它被用于构造一个 Color 对象,表示位图中特定像素的颜色。

    • pixels[pixelIndex + 3] 表示字节数组中的第 pixelIndex + 3 个元素,即 Alpha 通道的值。

    • pixels[pixelIndex + 2] 表示字节数组中的第 pixelIndex + 2 个元素,即红色通道的值。

    • pixels[pixelIndex + 1] 表示字节数组中的第 pixelIndex + 1 个元素,即绿色通道的值。

    • pixels[pixelIndex] 表示字节数组中的第 pixelIndex 个元素,即蓝色通道的值。

void GetAreaColor(Point? thumbPosition = null)
        {
            thumbPosition = thumbPosition == null ? thumbPosition = thumb.TranslatePoint(new Point(thumb.ActualWidth / 2, thumb.ActualHeight / 2), canvas) : thumbPosition;
            int xCoordinate = (int)thumbPosition?.X;
            int yCoordinate = (int)thumbPosition?.Y;

            if (xCoordinate >= 0 && xCoordinate < Bitmap.PixelWidth && yCoordinate >= 0 && yCoordinate < Bitmap.PixelHeight)
            {
                int stride = Bitmap.PixelWidth * (Bitmap.Format.BitsPerPixel / 8);
                byte[] pixels = new byte[Bitmap.PixelHeight * stride];
                Bitmap.CopyPixels(new Int32Rect(0, 0, Bitmap.PixelWidth, Bitmap.PixelHeight), pixels, stride, 0);
                int pixelIndex = (yCoordinate * stride) + (xCoordinate * (Bitmap.Format.BitsPerPixel / 8));
                Color color = Color.FromArgb(pixels[pixelIndex + 3], pixels[pixelIndex + 2], pixels[pixelIndex + 1], pixels[pixelIndex]);
                MyBtn.Background = new SolidColorBrush(color);
            }
        }
891b487e90b8a1b4013fae7d0e0494f3.gif

码云[2]

参考资料

[1]

原文链接: https://github.com/yanjinhuagood/ColorPickerSample

[2]

码云: https://gitee.com/yanjinhua/ColorPickerSample

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: WPF是一种强大的UI框架,它提供了许多控件,其中包括时间选择器控件。但是,由于在特定的应用程序中,需要自定义时间选择器以满足一些个性化需求。在这种情况下,可以使用WPF自定义控件来创建自己的时间选择器控件。 首先,在WPF中创建时间选择器控件,需要使用Calendar控件和TimePicker控件。Calendar控件用于显示日期,而TimePicker控件用于选择时间。时间选择器控件的主体是StackPanel控件。在StackPanel控件中添加了两个控件Calendar和TimePicker,以实现时间选择器的基本功能。 然后,需要在时间选择器控件中定义一些附加属性,例如:选定日期、选定时间等等,以实现一些高级功能。 最后,为时间选择器控件添加样式,并实现一些触发器和动画效果,以使其外观和功能与应用程序的主题相匹配。 实现WPF自定义时间选择器可能需要一些时间和经验,但对于需要一个不寻常的时间选择器的应用程序来说,是值得的。 这样的时间选择器是用户友好的,具有很好的设计和功能,并且以C#编写,可以很容易地与WPF应用程序集成。 ### 回答2: WPF自定义时间选择器是一种功能强大、灵活性高的工具,它可以根据需求自行设计不同的选择器,可以实现小时、分钟、秒数的选择等多种功能。 首先,我们需要使用WPF自带的DatePicker控件和TimePicker控件来实现时间选择器。接下来,我们可以自定义控件的样式和模板,使其更符合我们的设计需求。 在自定义控件的样式时,我们需要设置控件的各个属性,比如控件的边框、背景、字体等。同时,我们可以通过设置样式来调整控件的布局和显示效果。 在时间选择器实现中,需要涉及到一些比较复杂的计算,比如计算时间的差值、时间的格式转换等。我们可以使用C#中的DateTime类和TimeSpan类来实现这些功能。 最后对于自定义时间选择器的控件事件,需要自定义一些控件事件,使其更加符合我们的设计需求。比如增加或减少系统时钟里的时间。 总而言之,实现WPF自定义时间选择器需要对WPF控件、样式、模板、计算和控件事件等各个方面有深入的了解。只有掌握了这些知识,才能够设计出优秀的时间选择器,满足用户的需求。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值