说明
最近无聊花了2天时间写了一个左侧菜单控件,测试648万条数据,数据占内存2.9GB,滚动数据无任何卡顿。
控件是完全绘制的,没有叠加其他的控件。因为只是简单写了一些,所以功能上可能不是很完善,需要自己修改,如果没有能力修改的,那就只能这样使用了。
刚大概浏览了下代码,由于写出来有几天时间了,当时还写了一个功能是:选择框模式,即可以像TreeView一样提供选择功能,功能虽然写了,但在绘制选择框图标时,会多次绘制图标,一直出现内存流的错误,这个没修改过来,就取消了这个功能删除了响应的代码。刚才浏览代码发现还有部分的未删除,但不影响控件的正常运行。
所有代码都在后面。
截图
代码
由于我不是专业的程序员,完全是自学的,写出的代码可能存在命名奇怪,单词错误等,请见谅。
ClickItemEventArgs.cs 文件
namespace XiaoHeiControls.CollapseMenu
{
public class ClickItemEventArgs : EventArgs
{
/// <summary>
/// 点击的鼠标
/// </summary>
public MouseButtons Button {
get; set; }
/// <summary>
/// 点击的项
/// </summary>
public TreeMenuItem TreeMenuItem {
get; set; }
public ClickItemEventArgs(MouseButtons buttons, TreeMenuItem item)
{
this.Button = buttons;
this.TreeMenuItem = item;
}
}
}
FoldingIcon.cs 文件
namespace XiaoHeiControls.CollapseMenu
{
/// <summary>
/// 折叠项图标样式
/// </summary>
public enum FoldingIcon
{
None,
/// <summary>
/// 实心△图标
/// </summary>
Caret,
/// <summary>
/// 圆圈内有个">"的图标
/// </summary>
ChevronCircle,
/// <summary>
/// ">"符号图标
/// </summary>
Angle,
/// <summary>
/// ">"符号加粗图标
/// </summary>
Chevron,
}
}
ItemStyle.cs 文件
namespace XiaoHeiControls.CollapseMenu
{
/// <summary>
/// 列表项的样式
/// </summary>
public class ItemStyle
{
/// <summary>
/// 列表项的级别
/// </summary>
public int Level {
get; private set; } = 1;
/// <summary>
/// 列表项字体格式
/// </summary>
public Font Font {
get; set; } = new Font("Microsoft YaHei UI", 9f, unit: GraphicsUnit.Point);
/// <summary>
/// 列表项字体颜色
/// </summary>
public Color ForeColor {
get; set; } = Color.Black;
/// <summary>
/// 列表项背景颜色
/// </summary>
public Color BackColor {
get; set; } = Color.Transparent;
/// <summary>
/// 圆形矩形的弧度
/// <para>当<=0时,背景或选中都是矩形</para>
/// </summary>
public int Radius {
get; set; } = 5;
public ItemStyle(int level = 1)
{
Level = level;
}
public ItemStyle()
{
Level = 1;
}
}
}
ListItem.cs 文件
namespace XiaoHeiControls.CollapseMenu
{
/// <summary>
/// 重定义的列表泛型,此类主要是不能直接添加元素,不能直接删除元素
/// </summary>
/// <typeparam name="T"></typeparam>
public class ListItem<T>:IEnumerable<T>
{
private List<T> items = new List<T>();
public T this[int index] {
get => this.items[index]; }
public int Count {
get {
return this.items.Count; } }
/// <summary>
/// 筛选数据
/// </summary>
/// <param name="predicate"></param>
/// <returns></returns>
public ListItem<T> Where(Func<T, bool> predicate)
{
return new ListItem<T>(this.items.Where(predicate).ToList());
}
/// <summary>
/// ForEach 委托
/// </summary>
/// <param name="action"></param>
public void ForEach(Action<T> action)
{
this.items.ForEach(action);
}
public ListItem() {
}
private ListItem(List<T> items)
{
this.items = items;
}
public IEnumerator<T> GetEnumerator()
{
return this.items.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
TreeMenuItemCollection.cs 文件
namespace XiaoHeiControls.CollapseMenu
{
public class TreeMenuItemCollection:IList<TreeMenuItem>
{
/// <summary>
/// 定义数组
/// </summary>
private List<TreeMenuItem> _items;
protected TreeMenuItem _root;
internal TreeMenuItem Root {
get {
return _root; } }
/// <summary>
/// 绑定的控件
/// </summary>
private TreeMenu _menu;
public TreeMenuItemCollection(TreeMenu owner)
{
_menu = owner;
_items = new List<TreeMenuItem>();
_root = new TreeMenuItem("根节点");
_root._indexs = new List<int>();
_root.SubItem = _items;
}
/// <summary>
/// 获取指定索引的项(仅仅能获取一级)
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public TreeMenuItem this[int index] {
get => this._items[index];
set {
this._items[index] = value;
this._items[index]._indexs = new List<int>() {
index };
this._items[index].ResetIndexs();
}
}
public TreeMenuItem? FindItem(string id)
{
var ids = this.DecomposeId(id);
TreeMenuItem item;
if (this._items.Count < ids[0]) item = this._items[0];
else return null;
for (int i = 1; i < ids.Count; i++)
{
if (item.SubItem.Count < ids[i])
{
item = item.SubItem[i];
}
else return null;
}
return item;
}
/// <summary>
/// 分解Id
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
protected List<int> DecomposeId(string id)
{
List<int> result = new List<int>();
List<string> ids = id.Split('-').ToList();
ids.ForEach(x => result.Add(int.Parse(x)));
return result;
}
/// <summary>
/// 获取所有子项的数量
/// </summary>
public int Count {
get => this._items.Count; }
public bool IsReadOnly => throw new NotImplementedException();
/// <summary>
/// 添加项
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public int Add(TreeMenuItem value)
{
value._indexs = new List<int>() {
this._items.Count }; // 这里还没有添加到列表。因此不用+1
value._parent = this._root;
value.ResetIndexs();
this._items.Add(value);
return this._items.Count - 1;
}
/// <summary>
/// 添加项
/// </summary>
/// <param name="name"></param>
/// <param name="value"></param>
/// <param name="icon"></param>
/// <returns></returns>
public TreeMenuItem Add(string name, string value = "", IconType? icon = null)
{
TreeMenuItem item = new TreeMenuItem(name, value, icon);
this.Add(item);
return item;
}
/// <summary>
/// 清空所有
/// </summary>
public void Clear()
{
this._items.Clear();
}
/// <summary>
/// 判断是否包含,只对Id判断。判断Id是否存在
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public bool Contains(TreeMenuItem value)
{
var item = this.FindItem(value.Id);
return item != null;
}
/// <summary>
/// 复制到Array
/// </summary>
/// <param name="array"></param>
/// <param name="index"></param>
public void CopyTo(Array array, int index)
{
Array arr = this._items.ToArray();
arr.CopyTo(array, index);
}
/// <summary>
/// 复制到TreeMenuItem[]
/// </summary>
/// <param name="array"></param>
/// <param name="arrayIndex"></param>
public void CopyTo(TreeMenuItem[] array, int arrayIndex)
{
this._items.CopyTo(array, arrayIndex);
}
/// <summary>
/// 查找,仅支持查找顶级
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public int IndexOf(TreeMenuItem value)
{
for (int i = 0; i < this._items.Count; i++)
{
if (value.Id == this._items[i].Id) return i;
}
return -1;
}
public void Insert(int index, TreeMenuItem value)
{
this._items.Insert(index, value);
throw new InvalidOperationException();
}
public void Remove(TreeMenuItem? value)
{
throw new NotImplementedException();
}
public void RemoveAt(int index)
{
throw new NotImplementedException();
}
void ICollection<TreeMenuItem>.Add(TreeMenuItem item)
{
this.Add(item);
}
bool ICollection<TreeMenuItem>.Remove(TreeMenuItem item)
{
this.Remove(item);
return true;
}
public IEnumerator<TreeMenuItem> GetEnumerator()
{
throw new NotImplementedException();
}
IEnumerator IEnumerable.GetEnumerator()
{
throw new NotImplementedException();
}
/// <summary>
/// 根据当前项,向后查找下一个需要显示的项(只考虑需要显示的,并不考虑可视和折叠分组问题)
/// </summary>
/// <param name="item"></param>
/// <returns>返回null则是已经查找到最后了</returns>
internal TreeMenuItem? FindNextItem(TreeMenuItem item)
{
TreeMenuItem? newItem;
if (item.HasChlid == true)
{
newItem = item.SubItem.First();
}
else
{
// 如果父类为null 则已经找到root级别
if (item.IsRoot == true) return null;
newItem = item.Next(); // 获取下一个
// 当为null 则是已经到了最后一个,再查找就需要查找父类的下一个
if (newItem == null)
{
newItem = item; // 重新定义,方便循环
bool flg = true;
while (flg)
{
// 如果当前项为root 则退出
if (newItem.IsRoot == true) return null;
// 当前项不是root,则需要查找其父类的下一个
var res = newItem.Parent.Next(); // 获取当前对象父类的下一个(同时也父类不是root)
// 父类的下一个为null,则说明当前项父类已经是最后一个,需要查找更为高一个父类的下一个
if (res == null) // 当这个获取还是null,说明已经没有下一项了
{
// 将父类重新赋值给newItem,方便下次循环时调用
newItem = newItem.Parent;
/// 需要再次验证,尤其是1级最后一项
/// 因为如果是最后一项,向前循环查到1级时,这个时候它还不是root,但是root的最后一项
if (newItem.Level == 1 && newItem.IsLast)
{
return null;
}
// 这里没有使用IsLastItem判断是因为,使用此方法,则每一个级别的最后一个元素都要调用一次,会导致增加循环次数
}
else
{
// 找到了则标记并返回
flg = false;
newItem = res;
}
}
}
}
return newItem;
}
internal TreeMenuItem? FindPreviousItem(TreeMenuItem item)
{
if (item.Level == 1 && item.IsFrist) return null; // 当前项已经是可显示的第一个了
TreeMenuItem? newItem;
if (item.IsFrist == true)
{
return item.Parent;
}
else
{
newItem = item.Previous();
if (newItem?.HasChlid == true)
{
return this.GetLastChildItem(newItem);
}
else
{
return newItem;
}
}
return newItem;
}
/// <summary>
/// 判断传入项是否为最后一个项
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
public bool IsLastItem(TreeMenuItem item)
{
TreeMenuItem newItem = item;
if(newItem.IsLast == false) return false; // 必须是所有同级别中最后一个
while (true)
{
if (newItem.Parent != null)
{
newItem = newItem.Parent;
if (newItem.IsLast == false) return false; // 必须是所有同级别中最后一个
}
else
{
return true;
}
}
}
/// <summary>
/// 获取指定项中所有子项、子子项中最后一个项
/// </summary>
/// <param name="item">指定的项,当为null,获取所有项的最后一个项</param>
/// <returns>当不存在节点时返回null</returns>
public TreeMenuItem? GetLastChildItem(TreeMenuItem? item = null)
{
if (item == null) item = this._root;
if (item.HasChlid == false) return null; // 当不存在子项,直接返回null
TreeMenuItem? record = item.LastChild; // 记录当前正在查找的项
if (record == null) return null; // 因为肯定有子项,因此不会是null
TreeMenuItem? res = record.LastChild; // 记录查找当前项最后一个子项的结果
while (true)
{
if (res == null)
{
return record;
}
else
{
record = res; // 重新赋值
res = res.LastChild; // 再次查找子项
}
}
}
}
}
ShowItem.cs 文件
namespace XiaoHeiControls.CollapseMenu
{
/// <summary>
/// 当前显示项
/// </summary>
public class ShowItem
{
/// <summary>
/// 显示的项
/// </summary>
public TreeMenuItem Item {
get; set; }
/// <summary>
/// 整个项的区域
/// </summary>
public Rectangle Rect {
get; set; }
/// <summary>
/// 整个项左侧图标区域
/// </summary>
public Rectangle LeftIconRect {
get; set; }
/// <summary>
/// 右侧折叠图标区域
/// </summary>
public Rectangle FoldIconRect {
get; set; }
/// <summary>
/// 标题文字绘图区域
/// </summary>
public Rectangle StringRect {
get; set; }
/// <summary>
/// 整个项的背景区域(一般会必绘图区小一点)
/// </summary>
public Rectangle BackgroundRect {
get; set; }
/// <summary>
/// 整个项的绘图区域
/// </summary>
public Rectangle DrawArea {
get; set; }
/// <summary>
/// 当前项的绘制样式
/// </summary>
public ItemStyle Style {
get; set; }
public ShowItem(TreeMenuItem item, Rectangle rect)
{
Item = item;
Rect = rect;
}
}
}
TreeMenu.cs 文件
namespace XiaoHeiControls.CollapseMenu
{
public partial class TreeMenu : UserControl
{
/// <summary>
/// 项的最小高度
/// </summary>
public const int MIN_ITEM_HEIGHT = 24;
/// <summary>
/// 子项的最小缩进间距
/// </summary>
public const int MIN_SINDENT = 8;
#region ===成员===
/// <summary>
/// 第一个显示的控件
/// <para>X坐标表示父类项,Y表示其子项</para>
/// </summary>
private TreeMenuItem? _fristItem = null;
/// <summary>
/// 控件中的列表项
/// </summary>
private TreeMenuItemCollection _items;
/// <summary>
/// 项高度
/// </summary>
private int _itemHight = 28;
/// <summary>
/// 列表项默认样式
/// </summary>
private ItemStyle _defaultItemStyle;
/// <summary>
/// 列表项样式
/// </summary>
private List<ItemStyle> _itemStyle = new List<ItemStyle>();
/// <summary>
/// 选中项的样式
/// </summary>
private ItemStyle _selectItemStyle;
/// <summary>
/// 光标悬停经过时项的样式
/// </summary>
private ItemStyle _hoverItemStyle ;
/// <summary>
/// 折叠图标
/// </summary>
private FoldingIc