542 lines
17 KiB
C#
542 lines
17 KiB
C#
/******************************************************************************
|
||
* SunnyUI 开源控件库、工具类库、扩展类库、多页面开发框架。
|
||
* CopyRight (C) 2012-2024 ShenYongHua(沈永华).
|
||
* QQ群:56829229 QQ:17612584 EMail:SunnyUI@QQ.Com
|
||
*
|
||
* Blog: https://www.cnblogs.com/yhuse
|
||
* Gitee: https://gitee.com/yhuse/SunnyUI
|
||
* GitHub: https://github.com/yhuse/SunnyUI
|
||
*
|
||
* SunnyUI.dll can be used for free under the GPL-3.0 license.
|
||
* If you use this code, please keep this note.
|
||
* 如果您使用此代码,请保留此说明。
|
||
******************************************************************************
|
||
* 文件名称: UICheckBoxGroup.cs
|
||
* 文件说明: 多选框组
|
||
* 当前版本: V3.1
|
||
* 创建日期: 2020-01-01
|
||
*
|
||
* 2020-04-19: V2.2.3 增加单元
|
||
* 2020-04-25: V2.2.4 更新主题配置类
|
||
* 2020-07-03: V2.2.6 修正调整ItemSize无效的Bug
|
||
* 2020-07-04: V2.2.6 可以设置初始选中值
|
||
* 2022-06-30: V3.2.0 设置条目状态前判断是否创建
|
||
* 2022-11-21: V3.2.9 修复未显示时切换节点文本为空的问题
|
||
* 2023-04-19: V3.3.5 设置选择项ForeColor
|
||
* 2023-06-27: V3.3.9 内置条目关联值由Tag改为TagString
|
||
* 2023-11-07: V3.5.2 重写UICheckBoxGroup
|
||
* 2023-12-04: V3.6.1 增加属性可修改图标大小
|
||
* 2024-09-09: V3.7.0 更改计算节点位置的方法,解决问题:#IAPY94
|
||
* 2024-11-29: V3.8.0 修复TitleTop为0时,条目显示错位的问题 #IB7STO
|
||
******************************************************************************/
|
||
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.ComponentModel;
|
||
using System.Drawing;
|
||
using System.Drawing.Design;
|
||
using System.Windows.Forms;
|
||
|
||
namespace Sunny.UI
|
||
{
|
||
/// <summary>
|
||
/// 多选框组
|
||
/// </summary>
|
||
[DefaultProperty("Items")]
|
||
[DefaultEvent("ValueChanged")]
|
||
public class UICheckBoxGroup : UIGroupBox
|
||
{
|
||
/// <summary>
|
||
/// 值切换事件
|
||
/// </summary>
|
||
/// <param name="sender">控件</param>
|
||
/// <param name="index">索引</param>
|
||
/// <param name="text">文字</param>
|
||
/// <param name="isChecked">是否选中</param>
|
||
public delegate void OnValueChanged(object sender, CheckBoxGroupEventArgs e);
|
||
|
||
/// <summary>
|
||
/// 值切换事件
|
||
/// </summary>
|
||
public event OnValueChanged ValueChanged;
|
||
|
||
/// <summary>
|
||
/// 构造函数
|
||
/// </summary>
|
||
public UICheckBoxGroup()
|
||
{
|
||
items.CountChange += Items_CountChange;
|
||
ForeColor = UIStyles.Blue.CheckBoxForeColor;
|
||
checkBoxColor = UIStyles.Blue.CheckBoxColor;
|
||
hoverColor = UIStyles.Blue.ListItemHoverColor;
|
||
}
|
||
|
||
private Color checkBoxColor;
|
||
|
||
private void Items_CountChange(object sender, EventArgs e)
|
||
{
|
||
InitRects();
|
||
Invalidate();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取和设置条目值
|
||
/// </summary>
|
||
/// <param name="index"></param>
|
||
/// <returns></returns>
|
||
public bool this[int index]
|
||
{
|
||
get => GetItemCheckState(index);
|
||
set
|
||
{
|
||
SetItemCheckState(index, value);
|
||
ValueChanged?.Invoke(this, new CheckBoxGroupEventArgs(index, Items[index].ToString(), this[index], SelectedIndexes.ToArray()));
|
||
Invalidate();
|
||
}
|
||
}
|
||
|
||
private Dictionary<int, bool> CheckStates = new Dictionary<int, bool>();
|
||
private Dictionary<int, Rectangle> CheckBoxRects = new Dictionary<int, Rectangle>();
|
||
private int _imageSize = 16;
|
||
|
||
[DefaultValue(16)]
|
||
[Description("图标大小"), Category("SunnyUI")]
|
||
[Browsable(true)]
|
||
public int CheckBoxSize
|
||
{
|
||
get => _imageSize;
|
||
set
|
||
{
|
||
_imageSize = Math.Max(value, 16);
|
||
_imageSize = Math.Min(value, 64);
|
||
Invalidate();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清除所有条目
|
||
/// </summary>
|
||
public void Clear()
|
||
{
|
||
Items.Clear();
|
||
CheckStates.Clear();
|
||
CheckBoxRects.Clear();
|
||
Invalidate();
|
||
ValueChanged?.Invoke(this, new CheckBoxGroupEventArgs(-1, "", false, SelectedIndexes.ToArray()));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 条目列表
|
||
/// </summary>
|
||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
|
||
[Localizable(true)]
|
||
[Editor("System.Windows.Forms.Design.ListControlStringCollectionEditor, System.Design, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(UITypeEditor))]
|
||
[MergableProperty(false)]
|
||
[Description("获取该多选框组中项的集合"), Category("SunnyUI")]
|
||
public UIObjectCollection Items => items;
|
||
|
||
private readonly UIObjectCollection items = new UIObjectCollection();
|
||
|
||
/// <summary>
|
||
/// 设置主题样式
|
||
/// </summary>
|
||
/// <param name="uiColor">主题样式</param>
|
||
public override void SetStyleColor(UIBaseStyle uiColor)
|
||
{
|
||
base.SetStyleColor(uiColor);
|
||
checkBoxColor = uiColor.CheckBoxColor;
|
||
ForeColor = uiColor.CheckBoxForeColor;
|
||
hoverColor = uiColor.ListItemHoverColor;
|
||
}
|
||
|
||
private Color hoverColor;
|
||
|
||
/// <summary>
|
||
/// 填充颜色,当值为背景色或透明色或空值则不填充
|
||
/// </summary>
|
||
[Description("鼠标滑过填充颜色"), Category("SunnyUI")]
|
||
[DefaultValue(typeof(Color), "155, 200, 255")]
|
||
public Color HoverColor
|
||
{
|
||
get => hoverColor;
|
||
set
|
||
{
|
||
hoverColor = value;
|
||
Invalidate();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 填充颜色,当值为背景色或透明色或空值则不填充
|
||
/// </summary>
|
||
[Description("填充颜色"), Category("SunnyUI")]
|
||
[DefaultValue(typeof(Color), "80, 160, 255")]
|
||
public Color CheckBoxColor
|
||
{
|
||
get => checkBoxColor;
|
||
set
|
||
{
|
||
checkBoxColor = value;
|
||
Invalidate();
|
||
}
|
||
}
|
||
|
||
private void InitRects()
|
||
{
|
||
int startX = StartPos.X;
|
||
int startY = TitleTop + StartPos.Y;
|
||
for (int i = 0; i < Items.Count; i++)
|
||
{
|
||
string text = Items[i].ToString();
|
||
int rowIndex = i / ColumnCount;
|
||
int columnIndex = i % ColumnCount;
|
||
int left = startX + ItemSize.Width * columnIndex + ColumnInterval * columnIndex;
|
||
int top = startY + ItemSize.Height * rowIndex + RowInterval * rowIndex;
|
||
Rectangle rect = new Rectangle(left, top, ItemSize.Width, ItemSize.Height);
|
||
if (CheckBoxRects.NotContainsKey(i))
|
||
CheckBoxRects.Add(i, rect);
|
||
else
|
||
CheckBoxRects[i] = rect;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 重载绘图
|
||
/// </summary>
|
||
/// <param name="e">绘图参数</param>
|
||
protected override void OnPaint(PaintEventArgs e)
|
||
{
|
||
base.OnPaint(e);
|
||
if (TitleTop == 0 && Text.IsValid()) Text = "";
|
||
if (Items.Count == 0) return;
|
||
InitRects();
|
||
|
||
if (activeIndex >= 0 && CheckBoxRects.TryGetValue(activeIndex, out Rectangle boxRect))
|
||
{
|
||
e.Graphics.FillRectangle(hoverColor, boxRect);
|
||
}
|
||
|
||
int startX = StartPos.X;
|
||
int startY = TitleTop + StartPos.Y;
|
||
|
||
for (int i = 0; i < Items.Count; i++)
|
||
{
|
||
string text = Items[i].ToString();
|
||
int rowIndex = i / ColumnCount;
|
||
int columnIndex = i % ColumnCount;
|
||
int left = startX + ItemSize.Width * columnIndex + ColumnInterval * columnIndex;
|
||
int top = startY + ItemSize.Height * rowIndex + RowInterval * rowIndex;
|
||
Rectangle rect = new Rectangle(left, top, ItemSize.Width, ItemSize.Height);
|
||
int ImageSize = CheckBoxSize;
|
||
|
||
//图标
|
||
top = rect.Top + (rect.Height - ImageSize) / 2;
|
||
left = rect.Left + 6;
|
||
Color color = Enabled ? checkBoxColor : foreDisableColor;
|
||
|
||
if (this[i])
|
||
{
|
||
e.Graphics.FillRoundRectangle(color, new Rectangle((int)left, (int)top, ImageSize, ImageSize), 1);
|
||
color = BackColor.IsValid() ? BackColor : Color.White;
|
||
Point pt2 = new Point((int)(left + ImageSize * 2 / 5.0f), (int)(top + ImageSize * 3 / 4.0f) - (ImageSize.Div(10)));
|
||
Point pt1 = new Point((int)left + 2 + ImageSize.Div(10), pt2.Y - (pt2.X - 2 - ImageSize.Div(10) - (int)left));
|
||
Point pt3 = new Point((int)left + ImageSize - 2 - ImageSize.Div(10), pt2.Y - (ImageSize - pt2.X - 2 - ImageSize.Div(10)) - (int)left);
|
||
|
||
PointF[] CheckMarkLine = { pt1, pt2, pt3 };
|
||
using Pen pn = new Pen(color, 2);
|
||
e.Graphics.SetHighQuality();
|
||
e.Graphics.DrawLines(pn, CheckMarkLine);
|
||
e.Graphics.SetDefaultQuality();
|
||
}
|
||
else
|
||
{
|
||
using Pen pn = new Pen(color, 1);
|
||
e.Graphics.DrawRoundRectangle(pn, new Rectangle((int)left + 1, (int)top + 1, ImageSize - 2, ImageSize - 2), 1);
|
||
e.Graphics.DrawRectangle(pn, new Rectangle((int)left + 2, (int)top + 2, ImageSize - 4, ImageSize - 4));
|
||
}
|
||
|
||
e.Graphics.DrawString(text, Font, ForeColor, rect, ContentAlignment.MiddleLeft, ImageSize + 10, 0);
|
||
|
||
}
|
||
}
|
||
|
||
int activeIndex = -1;
|
||
|
||
protected override void OnMouseMove(MouseEventArgs e)
|
||
{
|
||
base.OnMouseMove(e);
|
||
|
||
int index = -1;
|
||
foreach (var item in CheckBoxRects)
|
||
{
|
||
if (e.Location.InRect(item.Value))
|
||
{
|
||
index = item.Key;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (activeIndex != index)
|
||
{
|
||
activeIndex = index;
|
||
Invalidate();
|
||
}
|
||
}
|
||
|
||
protected override void OnMouseLeave(EventArgs e)
|
||
{
|
||
base.OnMouseLeave(e);
|
||
|
||
activeIndex = -1;
|
||
Invalidate();
|
||
}
|
||
|
||
protected override void OnMouseClick(MouseEventArgs e)
|
||
{
|
||
base.OnMouseClick(e);
|
||
|
||
foreach (var pair in CheckBoxRects)
|
||
{
|
||
if (e.Location.InRect(pair.Value) && pair.Key >= 0 && pair.Key < items.Count)
|
||
{
|
||
this[pair.Key] = !this[pair.Key];
|
||
Invalidate();
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 选中状态列表
|
||
/// </summary>
|
||
[Browsable(false)]
|
||
public List<int> SelectedIndexes
|
||
{
|
||
get
|
||
{
|
||
List<int> indexes = new List<int>();
|
||
for (int i = 0; i < Items.Count; i++)
|
||
{
|
||
if (this[i]) indexes.Add(i);
|
||
}
|
||
|
||
return indexes;
|
||
}
|
||
set
|
||
{
|
||
for (int i = 0; i < Items.Count; i++)
|
||
{
|
||
SetItemCheckState(i, false);
|
||
}
|
||
|
||
foreach (int i in value)
|
||
{
|
||
if (i >= 0 && i < Items.Count)
|
||
{
|
||
SetItemCheckState(i, true);
|
||
}
|
||
}
|
||
|
||
ValueChanged?.Invoke(this, new CheckBoxGroupEventArgs(-1, "", false, SelectedIndexes.ToArray()));
|
||
Invalidate();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置条目状态
|
||
/// </summary>
|
||
/// <param name="index">索引</param>
|
||
/// <param name="isChecked">是否选中</param>
|
||
private void SetItemCheckState(int index, bool isChecked)
|
||
{
|
||
if (index >= 0 && index < Items.Count && CheckStates.NotContainsKey(index))
|
||
{
|
||
CheckStates.Add(index, isChecked);
|
||
}
|
||
|
||
CheckStates[index] = isChecked;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取条目状态
|
||
/// </summary>
|
||
/// <param name="index">索引</param>
|
||
/// <returns>是否选中</returns>
|
||
private bool GetItemCheckState(int index)
|
||
{
|
||
if (index >= 0 && index < items.Count && CheckStates.ContainsKey(index))
|
||
return CheckStates[index];
|
||
|
||
return false;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 所有选中条目列表
|
||
/// </summary>
|
||
[Browsable(false)]
|
||
public List<object> SelectedItems
|
||
{
|
||
get
|
||
{
|
||
List<object> objects = new List<object>();
|
||
|
||
for (int i = 0; i < Items.Count; i++)
|
||
{
|
||
if (this[i]) objects.Add(Items[i]);
|
||
}
|
||
|
||
return objects;
|
||
}
|
||
}
|
||
|
||
private int columnCount = 1;
|
||
|
||
/// <summary>
|
||
/// 显示列的个数
|
||
/// </summary>
|
||
[DefaultValue(1)]
|
||
[Description("显示列的个数"), Category("SunnyUI")]
|
||
public int ColumnCount
|
||
{
|
||
get => columnCount;
|
||
set
|
||
{
|
||
columnCount = value;
|
||
InitRects();
|
||
Invalidate();
|
||
}
|
||
}
|
||
|
||
private Size _itemSize = new Size(150, 29);
|
||
|
||
/// <summary>
|
||
/// 显示项的大小
|
||
/// </summary>
|
||
[DefaultValue(typeof(Size), "150, 29")]
|
||
[Description("显示项的大小"), Category("SunnyUI")]
|
||
public Size ItemSize
|
||
{
|
||
get => _itemSize;
|
||
set
|
||
{
|
||
_itemSize = value;
|
||
InitRects();
|
||
Invalidate();
|
||
}
|
||
}
|
||
|
||
private Point startPos = new Point(12, 12);
|
||
|
||
/// <summary>
|
||
/// 显示项的起始位置
|
||
/// </summary>
|
||
[DefaultValue(typeof(Point), "12, 12")]
|
||
[Description("显示项的起始位置"), Category("SunnyUI")]
|
||
public Point StartPos
|
||
{
|
||
get => startPos;
|
||
set
|
||
{
|
||
startPos = value;
|
||
InitRects();
|
||
Invalidate();
|
||
}
|
||
}
|
||
|
||
|
||
private int columnInterval = 6;
|
||
|
||
/// <summary>
|
||
/// 显示项列之间的间隔
|
||
/// </summary>
|
||
[DefaultValue(6)]
|
||
[Description("显示项列之间的间隔"), Category("SunnyUI")]
|
||
public int ColumnInterval
|
||
{
|
||
get => columnInterval;
|
||
set
|
||
{
|
||
columnInterval = value;
|
||
InitRects();
|
||
Invalidate();
|
||
}
|
||
}
|
||
|
||
private int rowInterval = 2;
|
||
|
||
/// <summary>
|
||
/// 显示项行之间的间隔
|
||
/// </summary>
|
||
[DefaultValue(2)]
|
||
[Description("显示项行之间的间隔"), Category("SunnyUI")]
|
||
public int RowInterval
|
||
{
|
||
get => rowInterval;
|
||
set
|
||
{
|
||
rowInterval = value;
|
||
InitRects();
|
||
Invalidate();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 全部选择
|
||
/// </summary>
|
||
public void SelectAll()
|
||
{
|
||
for (int i = 0; i < Items.Count; i++)
|
||
{
|
||
SetItemCheckState(i, true);
|
||
}
|
||
|
||
ValueChanged?.Invoke(this, new CheckBoxGroupEventArgs(-1, "", false, SelectedIndexes.ToArray()));
|
||
Invalidate();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 全部不选
|
||
/// </summary>
|
||
public void UnSelectAll()
|
||
{
|
||
for (int i = 0; i < Items.Count; i++)
|
||
{
|
||
SetItemCheckState(i, false);
|
||
}
|
||
|
||
ValueChanged?.Invoke(this, new CheckBoxGroupEventArgs(-1, "", false, SelectedIndexes.ToArray()));
|
||
Invalidate();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 反转选择
|
||
/// </summary>
|
||
public void ReverseSelected()
|
||
{
|
||
for (int i = 0; i < Items.Count; i++)
|
||
{
|
||
SetItemCheckState(i, !this[i]);
|
||
}
|
||
|
||
ValueChanged?.Invoke(this, new CheckBoxGroupEventArgs(-1, "", false, SelectedIndexes.ToArray()));
|
||
Invalidate();
|
||
}
|
||
}
|
||
|
||
public class CheckBoxGroupEventArgs : EventArgs
|
||
{
|
||
public int Index { get; set; }
|
||
|
||
public string Text { get; set; }
|
||
public bool Checked { get; set; }
|
||
|
||
public int[] SelectedIndexes { get; set; }
|
||
|
||
public CheckBoxGroupEventArgs(int index, string text, bool isChecked, int[] indexes)
|
||
{
|
||
Index = index;
|
||
Text = text;
|
||
Checked = isChecked;
|
||
SelectedIndexes = indexes;
|
||
}
|
||
}
|
||
} |