C#ListView的单元格支持添加基本及自定义任意控件

功能说明

使用ListView时,希望可以在单元格显示图片或其他控件,发现原生的ListView不支持,于是通过拓展,实现ListView可以显示任意控件的功能,效果如下:

实现方法

本来想着在单元格里面实现控件的自绘的,但是没找到办法,最后是通过在单元格的表面显示对应控件的,浮于表面达到目的。

实现要点如下:

  • ListView需要设置OwnerDraw=true,并重载自绘函数OnDrawColumnHeaderOnDrawItemOnDrawSubItem
  • 支持按单元格添加对应的控件,其Parent设置为列表ListView
  • 列表界面调整后,包括大小、列表头、滚动等,需重新绘制单元格的控件

实现源码

c# 复制代码
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace MyListView.Ui
{
    #region ListViewEx
    public class ListViewEx : ListView
    {
        #region 内部属性
        /// <summary>
        /// 存放单元格控件
        /// </summary>
        List<Control> _CellControls = new List<Control>();
        /// <summary>
        /// 界面是否发生变化
        /// </summary>
        bool IsViewChanged { get; set; }
        #endregion

        #region 方法
        /// <summary>
        /// 构造函数
        /// </summary>
        public ListViewEx()
        {
            #region 初始化
            #endregion

            #region 事件
            this.ColumnReordered += ColumnWidthChangedHandler;
            this.ColumnWidthChanged += ColumnWidthChangedHandler;
            #endregion
        }

        /// <summary>
        /// 添加控件
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="row"></param>
        /// <param name="col"></param>
        /// <param name="c"></param>
        /// <returns></returns>
        public T Add<T>(int row, int col, T c) where T : Control
        {
            if(row >= this.Items.Count || col >= this.Columns.Count)
            {
                return null;
            }

            var index = (row * this.Columns.Count) + col;
            for (var i = _CellControls.Count; i <= index; i++)
            {
                _CellControls.Add(null);
            }
            var oc = _CellControls[index];
            if (oc != null)
            {
                oc.Dispose();
            }
            OwnerDraw = true;
            IsViewChanged = true;
            c.Parent = this;
            _CellControls[index] = c;
            return c;
        }

        /// <summary>
        /// 设置行高度
        /// </summary>
        /// <param name="height"></param>
        public void SetItemHeight(int height)
        {
            if(this.SmallImageList == null)
            {
                this.SmallImageList = new ImageList();
            }
            this.SmallImageList.ImageSize = new Size(1, height);
        }
        #endregion

        #region 内部函数
        void ColumnWidthChangedHandler(object s, EventArgs e)
        {
            IsViewChanged = true;
        }
        /// <summary>
        /// 显示控件到目标单元格
        /// </summary>
        /// <param name="c"></param>
        /// <param name="item"></param>
        void ShowCellControl(Control c, ListViewItem.ListViewSubItem item)
        {
            int margin = 2;
            c.Text = item.Text;
            c.Visible = true;
            c.Bounds = new Rectangle(item.Bounds.X + margin,
                item.Bounds.Top + margin,
                item.Bounds.Width - 2 * margin,
                item.Bounds.Height - 2 * margin);
        }

        /// <summary>
        /// 显示单元格控件
        /// </summary>
        /// <param name="e"></param>
        /// <returns></returns>
        Control GetCellControl(DrawListViewSubItemEventArgs e)
        {
            Control c = null;
            #region 获取控件
            var index = (e.ItemIndex * this.Columns.Count) + e.ColumnIndex;
            if (index >= _CellControls.Count)
            {
                return null;
            }
            c = _CellControls[index];
            #endregion
            return c;
        }


        protected override void WndProc(ref Message m)
        {
            #region 事件定义
            const int WM_SIZE = 0x0005;
            const int WM_PAINT = 0x000F;
            const int WM_HSCROLL = 0x114;
            const int WM_VSCROLL = 0x115;
            const int WM_MOUSEWHEEL = 0x020A;
            #endregion

            #region 重绘显示控件
            if (m.Msg == WM_PAINT && IsViewChanged)
            {
                if(this.Columns.Count > 0)
                {
                    for (var i = 0; i < _CellControls.Count; i++)
                    {
                        var cell_control = _CellControls[i];
                        if (cell_control == null)
                        {
                            continue;
                        }
                        cell_control.Visible = false;
                        var row = i / this.Columns.Count;
                        var col = i % this.Columns.Count;
                        if(row >= Items.Count || col >= this.Columns.Count)
                        {
                            continue;
                        }
                        var item = this.Items[row];
                        if(item.Bounds.Y < 0 || item.Bounds.Y >= this.Height)
                        {
                            continue;
                        }
                        if(item.SubItems[col].Bounds.X < 0 || item.SubItems[col].Bounds.X >= this.Width)
                        {
                            continue;
                        }
                        ShowCellControl(cell_control, item.SubItems[col]);
                    }
                    IsViewChanged = false;
                }
            }
            else if(m.Msg == WM_HSCROLL || m.Msg == WM_VSCROLL || m.Msg == WM_MOUSEWHEEL || m.Msg == WM_SIZE)
            {
                IsViewChanged = true;
            }
            #endregion
            base.WndProc(ref m);
        }

        protected override void OnDrawColumnHeader(DrawListViewColumnHeaderEventArgs e)
        {
            e.DrawDefault = true;
        }

        protected override void OnDrawItem(DrawListViewItemEventArgs e)
        {
            // 已经在OnDrawSubItem处理过了
            // e.DrawText(TextFormatFlags.Default);
        }

        protected override void OnDrawSubItem(DrawListViewSubItemEventArgs e)
        {
            if(GetCellControl(e) != null)
            {
                return;
            }
            else
            {
                e.DrawDefault = true;
            }
        }
        #endregion
    }
    #endregion
}

测试代码

C# 复制代码
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace MyListView
{
    public partial class Form1 : Form
    {
        #region 函数
        public Form1()
        {
            #region 布局初始化
            InitializeComponent();
            var lv = new Ui.ListViewEx()
            {
                Dock = DockStyle.Fill,
                View = View.Details,
                GridLines = true,
            };
            this.Controls.Add(lv);
            var headers = new string[] { "序号", "名称", "年龄", "住址", "荣誉", "岗位", "头像" };
            foreach(var v in headers)
            {
                lv.Columns.Add(v, 100, HorizontalAlignment.Center);
            }
            lv.SetItemHeight(40);
            for(var i=0; i<50; i++)
            {
                var lvi = lv.Items.Add($"数据{i + 1}");
                for(var j=1; j<lv.Columns.Count; j++)
                {
                    lvi.SubItems.Add($"数据{i + 1}-{j}");
                    switch(j)
                    {
                        case 1:
                            lv.Add(i, j, new Label());
                            break;
                        case 2:
                            lv.Add(i, j, new Button());
                            break;
                        case 3:
                            lv.Add(i, j, new TextBox()
                            {
                                Font = new Font("宋体", 18)
                            });
                            break;
                        case 4:
                            lv.Add(i, j, new ComboBox()
                            {
                                Font = new Font("宋体", 18)
                            });
                            break;
                        case 6:
                            {
                                var pic = lv.Add(i, j, new PictureBox());
                                pic.Image = LoadImage($"logo{i%7}.jpg");
                                pic.SizeMode = PictureBoxSizeMode.StretchImage;
                            }
                            break;
                    }
                }
            }
            #endregion
        }

        Image LoadImage(string name)
        {
            var file = Path.GetFullPath(Path.Combine(@"..\..\Data\IMG", name));
            if(!File.Exists(file))
            {
                return null;
            }
            return Image.FromFile(file);
        }
        #endregion
    }
}
相关推荐
一点媛艺1 小时前
Kotlin函数由易到难
开发语言·python·kotlin
姑苏风1 小时前
《Kotlin实战》-附录
android·开发语言·kotlin
奋斗的小花生2 小时前
c++ 多态性
开发语言·c++
魔道不误砍柴功2 小时前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python
闲晨2 小时前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
老猿讲编程3 小时前
一个例子来说明Ada语言的实时性支持
开发语言·ada
Chrikk3 小时前
Go-性能调优实战案例
开发语言·后端·golang
幼儿园老大*3 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
canyuemanyue3 小时前
go语言连续监控事件并回调处理
开发语言·后端·golang
杜杜的man4 小时前
【go从零单排】go语言中的指针
开发语言·后端·golang