winform自定义自动完成控件(代码片段)

shanfeng1000 shanfeng1000     2022-12-19     370

关键词:

  做过前端的朋友肯定很清楚自动完成控件,很多优秀的前端框架都会带有自动完成控件,同样的,winform也有,在我们的TextBox和ComboBox中,只需要设置AutoCompleteSource属性为CustomSource,然后将值加入到AutoCompleteCustomSource中就可以了

  比如:

  技术图片
            string[] dataSource=new string[]"apple","orange","banner";
            textBox1.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
            textBox1.AutoCompleteCustomSource.AddRange(dataSource);
            textBox1.AutoCompleteSource = AutoCompleteSource.CustomSource;
View Code

  貌似很好用,但是这个功能很鸡肋:

  第一,因为要使用自动完成,我们需要事先将所有值都放到AutoCompleteCustomSource属性中,如果数据少还好说,数据多了就不现实了,往往需要采用异步去读取

  第二,自带的这个自动完成功能,只能从头开始匹配,但我们往往输入的可能是包含的含义,比如上面的例子,我们第一个输入a或者o或者b才有提示,而输入其它的是没有提示的,如输入p,但是apple单次里面包含了p字母

  第三、在国内,如果要在一个框里面输入一个中文的城市名称,我们更希望输入城市的拼音首字母直接带出来而不是打开输入法去敲中文,这个是自带的自动完成功能实现不了的

  当我们碰到类似上面的需求时,怎么办?没辙了么?当然不是,我们可以自己写一个自动完成功能,但是完成一个自定义控件主要有如下问题:

  1、自动完成控件像什么?太像一个ListBox,那么我们可以重写ListBox,设置一些属性,使它更像自动完成控件,比如默认是不显示的

  2、控件的原型有了,如果页面上有多个地方要使用,如果都定义这么一个控件就麻烦了,所有尽可能使控件是可复用的

  3、因为控件要做成可复用了,所有我们需要不停的指定控件的大小和位置,不应该出现你在这里输入,控件在那里显示情况,那么什么时候重置这些属性?

  4、控件的数据是可变的,最好是使用委托实现

  5、最后是控件的显示隐藏问题,什么时候显示,时候时候隐藏

  解决这几个问题,自动完成控件就可以开工了,设计思路如下:

  1、我们要使用重写ListBox模拟自动完成控件,如果要使控件可重复使用,那么我们需要一个属性来指明当前控件服务的对象

  2、为了要设置这个对象,我们需要一个绑定方法,当调用绑定方法是,设置服务对象,同时可以充值自动完成控件的位置及大小,什么时候调用绑定方法?当然是服务对象获得焦点的时候了

  3、当服务对象文本发生改变时,调用一个方法去生成自动完成控件的数据,把它绑定后再显示

  4、当选中自动完成控件数据后,将内容填到服务对象的Text中去

  5、当服务空间和自动完成控件都失去焦点时,自动完成控件应该被隐藏,并解绑自动完成控件

  基于上面的思路,笔者写了一个自动完成控件类,当然里面还有一些细节的东西,读者可以细细体会,现在贴上自动完成控件类的代码:  

  技术图片
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApplication1

    public class AutoCompleteBox : ListBox, IMessageFilter
    
        /// <summary>
        /// 当前服务控件的对象
        /// </summary>
        public Control Target  get; private set; 
        /// <summary>
        /// 显示元素个数
        /// </summary>
        public int MaxCount  get; set; 

        EventHandler texthander = null;//text_changed事件
        EventHandler leavehander = null;//leave事件
        KeyEventHandler keyuphander = null;//keyup事件
        MouseEventHandler parenthander = null;//父窗体获得焦点事件

        bool mouseIsOver = false;//鼠标是否在自动完成控件上        
        bool isUserChange = true;//是否是用户输入
        Action<string, Action<string[]>> action;//根据输入的内容,自定义下拉绑定数据

        public AutoCompleteBox()
        
            DrawMode = System.Windows.Forms.DrawMode.OwnerDrawVariable;
            Font = new System.Drawing.Font("宋体", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
            FormattingEnabled = true;
            HorizontalScrollbar = true;
            ItemHeight = 15;
            Location = new System.Drawing.Point(0, 0);
            Size = new System.Drawing.Size(150, 115);
            DrawItem += new System.Windows.Forms.DrawItemEventHandler(this.listBox_DrawItem);
            MeasureItem += new System.Windows.Forms.MeasureItemEventHandler(this.listBox_MeasureItem);
            KeyUp += new System.Windows.Forms.KeyEventHandler(this.listBox_KeyUp);
            Leave += new System.EventHandler(this.listBox_Leave);
            MouseDoubleClick += new System.Windows.Forms.MouseEventHandler(this.listBox_MouseDoubleClick);
            MouseEnter += new System.EventHandler(this.listBox_MouseEnter);
            MouseLeave += new System.EventHandler(this.listBox_MouseLeave);
            MouseMove += new System.Windows.Forms.MouseEventHandler(this.listBox_MouseMove);
            Visible = false;//控件默认不显示

            texthander = new EventHandler(Target_TextChanged);
            leavehander = new EventHandler(Target_Leave);
            keyuphander = new KeyEventHandler(Target_KeyUp);
            parenthander = new MouseEventHandler(Parent_MouseClick);
        

        #region 服务控件事件
        /// <summary>
        /// 服务控件文本改变状态
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Target_TextChanged(object sender, EventArgs e)
        
            if (Target == null || action == null) return;

            //非用户输入
            if (!isUserChange)
            
                return;
            

            //先获取最新的文本
            string text = Target.Text;
            if (!string.IsNullOrEmpty(text))
            
                action(text, array =>
                
                    Action callback = () =>
                    
                        //根据文本去获取列表
                        if (MaxCount > 0)
                        
                            DataSource = array.Take(MaxCount).ToArray();
                        
                        else
                        
                            DataSource = array;
                        
                        if (array != null && array.Length > 0)//如果列表为空就不显示了
                        
                            Display();
                        
                        else
                        
                            Hidden();
                        
                    ;
                    if (Thread.CurrentThread.GetApartmentState() != ApartmentState.STA)//需要在IO线程中执行
                    
                        if (Target != null && Target.IsHandleCreated)
                        
                            BeginInvoke(new Action(() => callback()));
                        
                        return;
                    

                    callback();
                );
            
            else
            
                Hidden();
            
        
        /// <summary>
        /// 服务控件失去焦点事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Target_Leave(object sender, EventArgs e)
        
            if (Target == null) return;

            if (!mouseIsOver && !Target.Focused)//只有当鼠标不在自动完成控件上,且服务控件也没有焦点时卸载自动完成控件
            
                Unbind();
                Hidden();
            
        
        /// <summary>
        /// 服务控件上下移动,且自动完成控件是显示的,则自动控件获得焦点
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Target_KeyUp(object sender, KeyEventArgs e)
        
            if (e.Alt || e.Control || e.Shift) return;
            if ((e.KeyCode == Keys.Up || e.KeyCode == Keys.Down) && Visible)
            
                //这里处理一下,避免控件失去焦点后列表隐藏了
                var current = mouseIsOver;
                mouseIsOver = true;
                Focus();
                mouseIsOver = current;
            
        
        /// <summary>
        /// 父窗体点击事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Parent_MouseClick(object sender, EventArgs e)
        
            if (Visible)
            
                Focus();//控件获得焦点,会导致服务对象失去焦点从而隐藏控件
            
        
        #endregion
        #region 自动完成控件
        /// <summary>
        /// 自动控件显示
        /// </summary>
        public void Display()
        
            this.BringToFront();//将控件提到最前面
            this.Visible = true;
        
        /// <summary>
        /// 自动控件隐藏
        /// </summary>
        public void Hidden()
        
            this.SendToBack();//将控件置于底层
            this.Visible = false;
        
        /// <summary>
        /// 将光标置于服务控件最后
        /// </summary>
        public void FocusTarget()
        
            if (Target == null) return;

            Target.Focus();
            if (Target is TextBox)
            
                (Target as TextBox).SelectionStart = Target.Text.Length;
            
            else
            
                (Target as ComboBox).SelectionStart = Target.Text.Length;
            
        

        /// <summary>
        /// 绑定自动完成控件
        /// </summary>
        /// <param name="c"></param>
        /// <param name="func"></param>
        public void AutoComplete(Control c, Func<string, string[]> func)
        
            AutoComplete(c, (text, action) =>
            
                var array = func(text);
                action(array);
            );
        
        /// <summary>
        /// 绑定自动完成控件
        /// </summary>
        /// <param name="c"></param>
        /// <param name="action"></param>
        public void AutoComplete(Control c, Action<string, Action<string[]>> action)
        
            if (!(c is TextBox || c is ComboBox))//仅支持TextBox和ComboBox
            
                throw new Exception("only TextBox and ComboBox is supported");
            

            c.Enter += new EventHandler((s1, e1) =>
            
                Bind(c, action);
            );
        
        /// <summary>
        /// 同步加载控件
        /// </summary>
        /// <param name="c">服务控件</param>
        /// <param name="func">根据输入的值返回绑定数据的委托</param>
        public void Bind(Control c, Func<string, string[]> func)
        
            Bind(c, (text, action) =>
            
                var array = func(text);
                action(array);
            );
        
        /// <summary>
        /// 加载控件,可以使用异步
        /// </summary>
        /// <param name="c">服务控件</param>
        /// <param name="action">根据输入的值并绑定数据的委托</param>
        public void Bind(Control c, Action<string, Action<string[]>> action)
        
            if (Target != null && Target == c) return;

            if (!(c is TextBox || c is ComboBox))//仅支持TextBox和ComboBox
            
                throw new Exception("only TextBox and ComboBox is supported");
            


            Target = c;
            this.action = action;

            //重置控件的位置
            var thisParent = this.Parent;
            var cParent = c;
            int x = 0, y = 0;
            while (cParent != null && cParent != thisParent)
            
                x += cParent.Location.X;
                y += cParent.Location.Y;
                cParent = cParent.Parent;

                cParent.MouseClick += parenthander;
            
            Location = new Point(x, y + c.Size.Height + 3);

            Size = new Size(c.Size.Width, Size.Height);//控件的大小

            c.TextChanged += texthander;//给控件绑定text_changed事件
            c.Leave += leavehander;//给控件绑定leave事件
            c.KeyUp += keyuphander;//给控件绑定keyup事件

            Hidden();
            Application.AddMessageFilter(this);//注册
        
        /// <summary>
        /// 卸载自动完成控件
        /// </summary>
        public void Unbind()
        
            if (Target != null)
            
                Target.TextChanged -= texthander;
                Target.Leave -= leavehander;
                Target.KeyUp -= keyuphander;

                //取消父窗体的绑定事件
                var thisParent = this.Parent;
                var cParent = Target;
                while (cParent != null && cParent != thisParent)
                
                    cParent = cParent.Parent;
                    cParent.MouseClick -= parenthander;
                

                Target = null;

                Application.RemoveMessageFilter(this);//取消注册
            
        
        /// <summary>
        /// 完成选中,填内容到Text文本中
        /// </summary>
        private void Complete()
        
            if (Target == null) return;

            var item = SelectedItem as string;
            if (item != null)
            
                isUserChange = false;
                Target.Text = item;
                isUserChange = true;
            

            FocusTarget();//光标后置
            Hidden();
        
        #endregion

        #region 自动控件事件
        /// <summary>
        /// 当列表被双击时,将文本内容置于服务控件中
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void listBox_MouseDoubleClick(object sender, MouseEventArgs e)
        
            Complete();
        
        /// <summary>
        /// 光标进入列表事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void listBox_MouseEnter(object sender, EventArgs e)
        
            mouseIsOver = true;
        
        /// <summary>
        /// 光标在列表之上
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void listBox_MouseLeave(object sender, EventArgs e)
        
            mouseIsOver = false;
        
        /// <summary>
        /// 光标在列表上移动
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void listBox_MouseMove(object sender, MouseEventArgs e)
        
            mouseIsOver = true;
        
        /// <summary>
        /// 列表失去焦点
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void listBox_Leave(object sender, EventArgs e)
        
            Target_Leave(Target, e);
        
        /// <summary>
        /// 计算列表高度
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void listBox_MeasureItem(object sender, MeasureItemEventArgs e)
        
            e.ItemHeight = this.ItemHeight;
        
        /// <summary>
        /// 绘制列表
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void listBox_DrawItem(object sender, DrawItemEventArgs e)
        
            e.Graphics.FillRectangle(new SolidBrush(e.BackColor), e.Bounds);
            if (e.Index >= 0 && this.Items.Count > 0)
            
                StringFormat sStringFormat = new StringFormat();
                sStringFormat.LineAlignment = StringAlignment.Center;
                string item = ((ListBox)sender).Items[e.Index].ToString();
                e.Graphics.DrawString(item, e.Font, new SolidBrush(e.ForeColor), e.Bounds, sStringFormat);
            
            e.DrawFocusRectangle();
        
        /// <summary>
        /// 处理Enter键
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void listBox_KeyUp(object sender, KeyEventArgs e)
        
            if (e.Alt || e.Control || e.Shift) return;

            if (e.KeyCode == Keys.Enter)
            
                Complete();
            
        
        #endregion

        /// <summary>
        /// win32消息过滤
        /// </summary>
        /// <param name="m"></param>
        /// <returns></returns>
        public bool PreFilterMessage(ref Message m)
        
            if (Target == null) return false;

            if (m.Msg == MsgIds.WM_KEYDOWN && (((Keys)(int)m.WParam & Keys.KeyCode) == Keys.Down || ((Keys)(int)m.WParam & Keys.KeyCode) == Keys.Up))
            
                var c = FromChildHandle(m.HWnd);
                if (m.HWnd == Target.Handle || c == Target)//这里是禁用ComboBox时使用上下键选择
                
                    return true;
                
            
            return false;
        
    

    public sealed class MsgIds
    
        /// <summary>
        /// 键盘按下消息
        /// </summary>
        public const int WM_KEYDOWN = 0x100;
    
AutoCompleteBox

  现在,我们在创建个窗体测试一下:

  先托几个控件,画个简单的页面:

  技术图片

  然后把我们自定义的控件也拖进去,最好是拖到窗体里,而不是拖到里面的群组里

  接着再Load时间实现功能:  

  技术图片
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApplication1

    public partial class Form1 : Form
    
        public Form1()
        
            InitializeComponent();
        

        private void Form1_Load(object sender, EventArgs e)
        
            //直接
            string[] allDept = new string[]  "研发部", "人事部" ;
            autoCompleteBox1.AutoComplete(tbDept, text => allDept.Where(d => d.Contains(text)).ToArray());

            //远程
            string[] allGroup = new string[]  "研发组", "测试组", "专家组", "顾问组" ;
            cbGroup.DataSource = allGroup;
            autoCompleteBox1.AutoComplete(cbGroup, (text, next) =>
            
                this.Cursor = Cursors.WaitCursor;
                new Thread(() =>
                
                    Thread.Sleep(100);//模拟远程访问
                    var array = allGroup.Where(d => d.Contains(text)).ToArray();
                    next(array);

                    BeginInvoke(new Action(() =>
                    
                        this.Cursor = Cursors.Default;
                    ));
                ).Start();
            );

            //拼音
            ListBoxItem[] list = new ListBoxItem[]  
                new ListBoxItem()  Text="北京",Value="bj",
                new ListBoxItem()Text="上海",Value="sh",
                new ListBoxItem()Text="广州",Value="gz",
                new ListBoxItem()Text="深圳",Value="sz"
            ;
            cbCity.DataSource = list;
            cbCity.DisplayMember = "Text";
            cbCity.ValueMember = "Value";
            autoCompleteBox1.AutoComplete(cbCity, text => list.Where(l => l.Value.Contains(text) || l.Text.Contains(text)).Select(l => l.Text).ToArray());
        
    

    public class ListBoxItem
    
        public string Text  get; set; 
        public string Value  get; set; 
    
Form1

  按F1或者点击启动按钮,看看我们的功能是否正确:

  技术图片技术图片技术图片

  另外,提醒一下,从第三个图我们看到,控件部分被隐藏了,所以我们控件还是有不足的,开发过程中还需要注意这一点

 

wpf使用winform自定义控件(代码片段)

...引用WindowsFormsIntegration.dllSystem.Windows.Forms.dll2、在要使用WinForm控件的WPF窗体的XAML文件中添加如下内容:xmlns:wf="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"xmlns:wfi="clr-namespace:System.Windows.Forms.Integration;assembly=WindowsFormsIntegration... 查看详情

c#winform开发(代码片段)

文章目录C#WinForm开发1.创建C#WinForm项目a.进入项目界面b.项目结构c.自定义一个Form2.给控件添加事件3.显示时间小项目4.控件5.几种布局a.FlowLayoutPanel流式布局b.TableLayoutPanel表格布局c.可以自定义控件6.文本框7.CheckBox复选框8.其他的一... 查看详情

多个控件到单个控件c#winforms上(代码片段)

有没有办法让控制像Panel,并插入其他几个组件,如Label?我已经制作了一个自定义控件,并将工具箱中的一些控件添加到它的[Designer]中,但是在将自定义控件插入主项目时这些项目是不可见的。答案我终于弄明白了。基本上做... 查看详情

在winform界面使用自定义用户控件及tabelpanel和stackpanel布局控件(代码片段)

...,用户控件同时也可以封装处理一些简单的逻辑。在开发Winform各种类型项目,我都时不时需要定制一些特殊的用户控件,以方便在界面模块中反复使用。我们一般是在自定义的用户控件里面,添加各种各样的界面控件元素,或... 查看详情

javascript自动完成自定义响应(代码片段)

查看详情

[wpf自定义控件库]让form在加载后自动获得焦点(代码片段)

1.需求加载后让第一个输入框或者焦点是个很基本的功能,典型的如“登录”对话框。一般来说“登录”对话框加载后“用户名”应该马上获得焦点,用户只需输入用户名,点击Tab,再输入密码,点击回车就完成了登录操作。在W... 查看详情

安装winform程序时自动安装windows服务(代码片段)

项目中遇到一个需求:安装winform程序时自动安装windows服务,且windows服务运行时反过来检测winform程序是否启动。如果没有则启动。经过一番查阅已在win10下实现并运行正常。在此记录便于以后查看实现思路:利用打包插件VSinstall... 查看详情

如何在 Material-UI 自动完成控件中自定义填充?

】如何在Material-UI自动完成控件中自定义填充?【英文标题】:HowcanIcustomizethepaddinginaMaterial-UIautocompletecontrol?【发布时间】:2020-06-1706:32:53【问题描述】:我需要自定义material-ui自动完成控件,使其不那么高。我找到了一个示例... 查看详情

sttextbox-一个纯gdi开发的开源winform控件(代码片段)

简介STTextBox是一个开源的WinFrom控件,是在我朋友的支持下完成的,纯GDI绘制,支持Emoji表情符号、所有颜色支持Alpha、并且支持自定义文本样式等。本应当还有更多的功能,但是由于一些其他原因暂停了开发,... 查看详情

sttextbox-一个纯gdi开发的开源winform控件(代码片段)

简介STTextBox是一个开源的WinFrom控件,是在我朋友的支持下完成的,纯GDI绘制,支持Emoji表情符号、所有颜色支持Alpha、并且支持自定义文本样式等。本应当还有更多的功能,但是由于一些其他原因暂停了开发,... 查看详情

自定义显示提示一段时间自动消失showmsgtext控件(代码片段)

publicpartialclassShowMsgText:LabelpublicstringTextMsggetreturnText;settimer1.Enabled=true;Text=value;publicShowMsgText()InitializeComponent();Text="";///<summary>///五秒后自动为空///</summ 查看详情

winform窗体及其控件的自适应(代码片段)

3步骤:1.在需要自适应的Form中实例化全局变量  AutoSizeFormClass.cs源码在下方    AutoSizeFormClassasc=newAutoSizeFormClass();2.Form_Load事件中    asc.controllInitializeSize(this) 查看详情

wpf自定义控件の扩展控件(代码片段)

原文:WPF自定义控件(三)の扩展控件    扩展控件,顾名思义就是对已有的控件进行扩展,一般继承于已有的原生控件,不排除继承于自定义的控件,不过这样做意义不大,因为既然都自定义了,为什么不一步到... 查看详情

winform控件随页面大小进行自适应(代码片段)

这个功能网上很多人在问,也有不少人给出过答案,经过实际使用,觉得网上这段代码实现的效果比较好,记录一下核心代码就是下面这个类1usingSystem;2usingSystem.Collections.Generic;3usingSystem.Linq;4usingSystem.Text;5usingSystem.Windows.Forms;67nam... 查看详情

一个winform下datagridview控件外观的自定义类

...、关于起因最近非常频繁的使用DataGridView控件,VS提供的Winform下DataGridView的默认样式非常难看。如果做美化处理的话,需要调整的地方非常多,且该控件的很多设置属性非常的晦涩,不是很人性化。每次进行设置都煞费脑筋,并... 查看详情

winform创建自定义控件

虽然VS为我们提供了很多控件可以使用,但有时候这些控件仍然不能满足我们的要求,比如我们要对部分控件进行一些个性化的定制,例如美化控件,这时候就需要自己绘制控件,或是在原有控件的基础上进行修改自定义控件分... 查看详情

c#winform编程自定义combobx控件,将treeview控件嵌入combobox中

不能简单的嵌进去。你可以在打开下拉选择框的时候,让comboBox不显示下拉类别,你动态的生成一个TreeView控件,放到正确的位置,使其看上去像是ComboBox的下拉列表。当这个TreeView失去焦点时自动将其隐藏。上面的方法应该可以... 查看详情

c#做的winform窗体程序把一个form给为自定义控件?

比如把Form2给成UserControl2要怎么做??修改你的类,从继承自Form改为继承自UserControl他就变成UserControl了参考技术Aform窗体是继承了windows.form,将此处改为CONTROL即可。或者重新创建一个控件,将窗体的控件及代码内容复制到自定义... 查看详情