前一段时间开发上位机功能需要用到颜色选择器功能,但是C#自带的颜色选择器操作繁琐,而且嵌入到上位机着实不好看,然后我在网上找了很久,发现很少有色轮形式的颜色选择器,所以我上github寻找,发现了一个宝藏——一个非常好看炫酷而且操作简单的的颜色选择器控件,推荐给大家。
【首先贴上github链接:适用于 Windows 窗体的 Cyotek 拾色器控件】
首先贴出效果图看看是不是你想要的:
一、简单使用教程
此处是我使用Cyotek.Windows.Forms.ColorPickers库部分功能的一些经验和思路总结,没有太多深入地去学习,如果有哪位大佬有深入学习使用的话,可以推给我大家一起学习一下。
好了回到正文
1. 简单复习项目创建以及库的安装
(1)点击新建项目->选择Windows窗体应用->点击下一步->填写好项目名称和什么位置->点击创建
(2)打开NuGet 右键:ColorPicker->管理NuGet程序包
在浏览界面输入Cyotek.Windows.Forms.ColorPicker
然后直接点击下载
(3)回到Form1.cs[设计],可以看到工具箱里面多了一个选项卡Cyotek Color Picker Controls
2. 实操
控件
控件类型 | 名称 |
---|---|
3个主要控件 | ColorWheel ColorEditor ColorGrid |
5 个实用程序控件 | ScreenColorPicker RgbaColorSlider HueColorSlider LightnessColorSlider SaturationColorSlider |
1个管理组件 | ColorEditorManager |
1个对话框 | ColorPickerDialog |
(1)把需要用到的控件拖到Form1里面
(2)拖入colorEditorManger控件,然后按F4,绑定上面三个控件
运行效果:
(3)小小拓展一下
将想要的颜色通过串口发送出去
[效果演示]
上面所演示的效果是简单地在串口上位机加上一些颜色选择控件,然后获取到RGB值,并将RGB值放进通信协议里面,最后将数据通过串口发送出去,这样可以实现利用上位机控制RGB灯的颜色的效果。
实现步骤:
- 先写好串口上位机,教程可以看我的另一篇推文
- 把各种颜色选择控件按照上面的教程拉到窗体里面
- 绑定事件(即当颜色发送变化时触发的事件)
(1)在左下角找到下面这个控件,按下F4
(2)点击闪电标志,双击ColorChanged旁边的空格子(这样就会自动生成一个函数,当颜色发送改变是会触发这个函数)
- 事件代码实现
/**************************************************************************/
#region 颜色选择工具拓展
private void colorEditorManager1_ColorChanged(object sender, EventArgs e)
{
int r;
int g;
int b;
//将获取到的RGB值进行数据类型转换
r = Convert.ToInt32(colorWheel1.Color.R);
g = Convert.ToInt32(colorWheel1.Color.G);
b = Convert.ToInt32(colorWheel1.Color.B);
//将RGB值嵌进通信协议(示例)
byte[] temp = new byte[8];
temp[0] = 0x66;
temp[1] = 0x04;
temp[2] = 0x00;
temp[3] = (byte)r;
temp[4] = (byte)g;
temp[5] = (byte)b;
temp[6] = (byte)((0x04 ^ 0x00 ^ (byte)r ^ (byte)g ^ (byte)b) & 0xFF); //检验
temp[7] = 0x99;
richTextBox1.BackColor = Color.FromArgb(r,g,b); //将颜色显示在richTextBox上面
//打印数据
richTextBox_ReceiveBox.Text += "发送的RGB值:" + colorWheel1.Color.R + "," + colorWheel1.Color.G + "," + colorWheel1.Color.B + "\n";
richTextBox_ReceiveBox.Text += "数据流:";
for (int i = 0; i < temp.Length; i++)
{
richTextBox_ReceiveBox.Text += temp[i].ToString("X2") + " ";
}
richTextBox_ReceiveBox.Text += "\n";
Serial_SendData(temp); //将字节数组通过串口发送
}
//串口发送函数
void Serial_SendData(byte[] buff)
{
try
{
serialPort1.Write(buff, 0, buff.Length);
}
catch
{
MessageBox.Show("串口通讯错误", "提示");
serialPort1.Close();
return;
}
}
#endregion
/**************************************************************************/
完整的代码
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO.Ports;
using System.IO;
namespace ColorPicker
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
/**************************************************************************/
#region 初始化窗体
private void Form1_Load(object sender, EventArgs e) //加载界面程序
{
try
{
string[] str = SerialPort.GetPortNames(); //获取连接到电脑的串口号并存进数组
comboBox_PortNames.Items.Clear(); //清除串口号下拉框的内容
comboBox_PortNames.Items.AddRange(str); //将串口号添加到下拉框
if (str.Length > 0)
{
comboBox_PortNames.SelectedIndex = 0; //设置ComboBox框的初始值
comboBox_BaudRate.SelectedIndex = 7;
comboBox_DataBit.SelectedIndex = 3;
comboBox_StopBit.SelectedIndex = 1;
comboBox_Parity.SelectedIndex = 2;
}
else
{
MessageBox.Show("当前无串口连接!");
}
}
catch
{
MessageBox.Show("无串口设备!/r/n请检查是否连接设备!/r/n请检查设备驱动!");
}
}
#endregion
/**************************************************************************/
/**************************************************************************/
#region 搜索串口(与初始化函数一样)
private void button_Serach_Click(object sender, EventArgs e)
{
try
{
string[] str = SerialPort.GetPortNames();
comboBox_PortNames.Items.Clear();
comboBox_PortNames.Items.AddRange(str);
if (str.Length > 0)
{
comboBox_PortNames.SelectedIndex = 0;
comboBox_BaudRate.SelectedIndex = 7;
comboBox_DataBit.SelectedIndex = 3;
comboBox_StopBit.SelectedIndex = 1;
comboBox_Parity.SelectedIndex = 2;
}
else
{
MessageBox.Show("当前无串口连接!");
}
}
catch
{
MessageBox.Show("无串口设备!/r/n请检查是否连接设备!/r/n请检查设备驱动!");
}
}
#endregion
/**************************************************************************/
/**************************************************************************/
#region 打开串口
private void button_OpenOrClose_Click(object sender, EventArgs e)
{
if (!serialPort1.IsOpen)
{
if (comboBox_PortNames.SelectedItem == null)
{
MessageBox.Show("请选择正确的串口", "提示");
return;
}
//设置串口参数
serialPort1.PortName = comboBox_PortNames.Text.ToString(); //serialPort1是serialPort组件的Name
serialPort1.BaudRate = Convert.ToInt32(comboBox_BaudRate.SelectedItem.ToString());
serialPort1.DataBits = Convert.ToInt32(comboBox_DataBit.SelectedItem.ToString());
//设置停止位
if (comboBox_StopBit.Text == "One")
{
serialPort1.StopBits = StopBits.One;
}
else if (comboBox_StopBit.Text == "Two")
{
serialPort1.StopBits = StopBits.Two;
}
else if (comboBox_StopBit.Text == "OnePointFive")
{
serialPort1.StopBits = StopBits.OnePointFive;
}
else if (comboBox_StopBit.Text == "None")
{
serialPort1.StopBits = StopBits.None;
}
//设置奇偶校验位
if (comboBox_Parity.Text == "Odd")
{
serialPort1.Parity = Parity.Odd;
}
else if (comboBox_Parity.Text == "Even")
{
serialPort1.Parity = Parity.Even;
}
else if (comboBox_Parity.Text == "None")
{
serialPort1.Parity = Parity.None;
}
try
{
//禁止操作组件
comboBox_PortNames.Enabled = false;
comboBox_BaudRate.Enabled = false;
comboBox_DataBit.Enabled = false;
comboBox_StopBit.Enabled = false;
comboBox_Parity.Enabled = false;
button_Serach.Enabled = false;
serialPort1.Open(); //设置完参数后打开串口
button_OpenOrClose.Text = "Close"; //更改Open按钮文本内容
}
catch
{
MessageBox.Show("串口打开失败!");
}
//事件绑定方法,在DataReceived
serialPort1.DataReceived += new SerialDataReceivedEventHandler(SerialDataReceive); //打开串口后绑定数据接收
}
else if (button_OpenOrClose.Text == "Close")
{
try
{
//允许操作组件
comboBox_PortNames.Enabled = true;
comboBox_BaudRate.Enabled = true;
comboBox_DataBit.Enabled = true;
comboBox_StopBit.Enabled = true;
comboBox_Parity.Enabled = true;
button_Serach.Enabled = true;
serialPort1.DiscardInBuffer(); //清除缓冲区的数据
serialPort1.Close();
button_OpenOrClose.Text = "Open"; //更改Close按钮文本内容
}
catch
{
MessageBox.Show("串口打开失败!");
}
}
}
#endregion
/**************************************************************************/
/**************************************************************************/
#region 接收串口数据
/*
开辟缓存区 根据具体协议内容获取一帧数据进行接收中断处理
*/
//变量
List<byte> sp_buffer = new List<byte>(4096); //串口缓存区
int sp_buffer_max = 4096; //串口缓存区最大缓存字节数
private void SerialDataReceive(object sender, SerialDataReceivedEventArgs e)
{
if (serialPort1.IsOpen == false)
{
serialPort1.Close();
return;
}
int Byte_len = serialPort1.BytesToRead; //读取缓存的数据长度
byte[] Rc_byte = new byte[Byte_len];
serialPort1.Read(Rc_byte, 0, Byte_len); //将缓存数据存储进字节数组里面
if (sp_buffer.Count > sp_buffer_max) //缓存超过字节数 先丢弃前面的字节
sp_buffer.RemoveRange(0, sp_buffer_max); //丢弃前面的字节0到sp_buffer_max
sp_buffer.AddRange(Rc_byte); //存入缓存区
byte[] ruffer = new byte[9192]; //用来存放缓冲区的数据流
//对数据流进行筛选,缓冲区每一组数据个数大于4则为我们想要的数据流
if (sp_buffer.Count > 4)
{
sp_buffer.CopyTo(0, ruffer, 0, sp_buffer.Count);
Task.Run(() => printf_data(ruffer, sp_buffer.Count, 1)); //打印数据流
}
}
#endregion
/**************************************************************************/
/**************************************************************************/
#region 打印数据流
void printf_data(byte[] Frame, int Length, int T_R) //打印串口数据
{
Int16 i_len;
StringBuilder s = new StringBuilder();
if (T_R == 0)
s.Append("发送:");
else
s.Append("接收:");
for (i_len = 0; i_len < Length; i_len++) //打印字符串
{
s.Append(Frame[i_len].ToString("X2"));
s.Append(" ");
}
s.Append("[" + DateTime.Now.ToString("HH:mm:ss fff") + "]");
s.Append("\r\n");
string str_show = s.ToString();
MethodInvoker mi = new MethodInvoker(() =>
{
if (richTextBox_ReceiveBox.Lines.Count() > 20)
richTextBox_ReceiveBox.Clear();
richTextBox_ReceiveBox.AppendText(str_show);
});
BeginInvoke(mi);
/*
textBox_com_data.Focus(); //获取焦点
textBox_com_data.Select(textBox_com_data.TextLength, 0);//光标
textBox_com_data.ScrollToCaret(); //滚动条*/
}
#endregion
/**************************************************************************/
/**************************************************************************/
#region 发送数据
private void button_Send_Click(object sender, EventArgs e)
{
if (serialPort1.IsOpen)
{
string[] sendbuff = richTextBox_send.Text.Split(); //分割输入的字符串,判断有多少个字节需要发送
int Buff_Len = sendbuff.Length;
byte[] buff = new byte[Buff_Len];
for (int i = 0; i < sendbuff.Length; i++)
{
buff[i] = byte.Parse(sendbuff[i], System.Globalization.NumberStyles.HexNumber); //格式化字符串为十六进制数值
}
try
{
serialPort1.Write(buff, 0, buff.Length); //写数据
Task.Run(() => printf_data(buff, Buff_Len, 0));
}
catch
{
MessageBox.Show("发送失败!!");
}
}
else
{
MessageBox.Show("串口未打开!");
}
}
#endregion
/**************************************************************************/
/**************************************************************************/
#region 清除输出框
private void Clear_Click(object sender, EventArgs e)
{
richTextBox_ReceiveBox.Clear(); //清楚数据接收框数据
}
#endregion
/**************************************************************************/
/**************************************************************************/
#region 颜色选择工具拓展
private void colorEditorManager1_ColorChanged(object sender, EventArgs e)
{
int r;
int g;
int b;
r = Convert.ToInt32(colorWheel1.Color.R);
g = Convert.ToInt32(colorWheel1.Color.G);
b = Convert.ToInt32(colorWheel1.Color.B);
//将RGB值嵌进通信协议(示例)
byte[] temp = new byte[8];
temp[0] = 0x66;
temp[1] = 0x04;
temp[2] = 0x00;
temp[3] = (byte)r;
temp[4] = (byte)g;
temp[5] = (byte)b;
temp[6] = (byte)((0x04 ^ 0x00 ^ (byte)r ^ (byte)g ^ (byte)b) & 0xFF); //检验
temp[7] = 0x99;
richTextBox1.BackColor = Color.FromArgb(r,g,b); //将颜色显示在richTextBox上面
//打印数据
richTextBox_ReceiveBox.Text += "发送的RGB值:" + colorWheel1.Color.R + "," + colorWheel1.Color.G + "," + colorWheel1.Color.B + "\n";
richTextBox_ReceiveBox.Text += "数据流:";
for (int i = 0; i < temp.Length; i++)
{
richTextBox_ReceiveBox.Text += temp[i].ToString("X2") + " ";
}
richTextBox_ReceiveBox.Text += "\n";
Serial_SendData(temp); //将字节数组通过串口发送
}
//串口发送函数
void Serial_SendData(byte[] buff)
{
try
{
serialPort1.Write(buff, 0, buff.Length);
}
catch
{
MessageBox.Show("串口通讯错误", "提示");
serialPort1.Close();
return;
}
}
#endregion
/**************************************************************************/
}
}
二、官方介绍
Cyotek.Windows.Forms.ColorPickers库包含一系列自定义控件和实用程序类,适用于使用颜色的 Windows 窗体应用程序。控件可用于通过 HSL 进行颜色选择ColorWheel,ColorGrid 具有许多自定义选项,ColorEditor用于通过 RGB 或 HSL 输入颜色,以及ScreenColorPicker用于从正在运行的应用程序中捕获颜色。
调色板可以以多种不同的格式加载和保存,包括 Adobe PhotoShop 色板文件、JASC 调色板、Gimp 调色板等(请参阅下面的调色板和外部调色板文件)。
有关这些控件的更多信息,请参阅cyotek.com 上带有标签的文章。colorpicker
(1)获取Lib
获取库的最简单方法是通过NuGet。
Install-Package Cyotek.Windows.Forms.ColorPicker
如果您不使用 NuGet,可以从GitHub 发布页面获取预编译的二进制文件。
当然,您可以随时获取源代码并自己构建!
(2)控件
控件类型 | 名称 |
---|---|
3个主要控件 | ColorWheel ColorEditor ColorGrid |
5 个实用程序控件 | ScreenColorPicker RgbaColorSlider HueColorSlider LightnessColorSlider SaturationColorSlider |
1个管理组件 | ColorEditorManager |
1个对话框 | ColorPickerDialog |
1. 颜色网格控件 ColorGrid Controls
此控件显示颜色网格,并支持主调色板和自定义调色板。有几个属性可用于配置控件的显示,也有一些行为选项,例如内置的颜色编辑和支持自动添加不在主调色板中的新颜色。
2.色轮控制 ColorWheel
此控件显示 RGB 颜色的径向轮,并允许从轮中的任何点进行选择。ShowAngleArrow、ShowCenterLines 和ShowSaturationRing属性可用于显示有用的装饰,而Lightness和Alpha属性可用于组成最终颜色,但不能在轮子上直接编辑。
SecondaryColors和SecondarySelectionSize属性允许您在轮盘上显示其他颜色例如用于显示关系。
3.颜色滑块控件 ColorSlider
一组控件(从单个基类继承),允许通过彩色条选择值。与 TrackBar控件类似,您有几个选项可用于指定拖动手柄的位置和条形方向。您还可以自定义填充和轮廓颜色,或将其完全替换为自定义图像。
4.颜色编辑器控件 ColorEditor
此控件允许通过标准界面编辑 RGB 或 HSL 颜色。您还可以通过 6 或 8 个字符的十六进制表示法输入颜色,或从命名的 Web 和系统颜色中进行选择。
可以通过 ShowAlphaChannel和PreserveAlphaChannel属性配置 alpha 通道的使用。ShowHex、ShowHsl和ShowRgb属性可用于显示或隐藏编辑器组件或者,NubSizeNubColor和NubOutlineColor 属性可用于自定义滑块的外观。
5.ScreenColorPicker 控件
此控件允许用户从屏幕上显示的任何像素中选择颜色。用户可以通过单击并拖动控件来触发操作,也可以通过该CaptureMouse方法以编程方式完成,允许通过其他操作(例如热键)触发选择。该Zoom属性可用于设置预览的网格大小。
6.颜色选择器对话框窗体 ColorPickerDialog
此表单将前面的控件放在一个随时可用的对话框中。
通过该属性支持自定义颜色CustomColors,用户还可以将外部调色板文件加载或保存到其中。您可以使用ShowLoad和ShowSave属性来启用或禁用此功能,并使用CustomColorsLoading和CustomColorsSaving事件来覆盖内置行为并提供您自己的逻辑。
4.颜色编辑器管理器 ColorEditorManager
这是一个非 GUI 组件,您可以将其拖放到表单上,并绑定到此库中的其他控件。当Color 一个控件的属性发生变化时,它会反映在其他控件中,而无需抬起手指。如果您从多个控件创建复合显示,这很有用。
调色板和外部调色板文件
ColorGrid控件有CustomColors和Colors属性能够返回一个颜色集合。这两个属性使开发人员可以更轻松地保持单独的主调色板,同时具有自定义颜色的灵活性,尽管它确实使控件的内部逻辑有点复杂!如果您尝试将控件设置为当前未定义的值,网格将自动填充自定义颜色。
除了手动填充颜色集合实例外,您还可以加载外部调色板文件。支持以下调色板格式:
Adobe 颜色表 (.act)
Adobe PhotoShop 色板 (.aco)
GIMP (.gpl)
豪华油漆 (.bbm; .lbm) [只读]
JASC (.pal)
Paint.NET (.txt)
原始 RGB 三元组 (.pal)
除了 ILBM 图像格式之外,所有其他格式都可以导出和导入。
通过添加实现IPaletteSerializer. (或任何使用静态方法的 ColorPickerDialog自定义代码 PaletteSerializer)将通过反射自动检测并提供自定义调色板。
键盘支持
所有 GUI 组件, ScreenColorPicker包括完整的键盘/焦点支持除外。许多控件支持SmallChange和LargeChange影响导航键处理方式的属性。虽然在这种情况下ColorWheel它并不是真正的奖励…但这就是ColorEditor控制最适合的!