zoukankan      html  css  js  c++  java
  • C#实现智能提示(提示补全)功能

    近段时间在帮朋友做一个短信发送管理的软件,其中有一个常用短语的功能。大家都知道用手机发送短信的时候一般都有常用短语的功能,朋友的意思也是按着手机那样传统的形式做就算了。但我觉得其中手机的常用短语功能其实并不常用,因为在手机上这功能比较鸡肋。但如果在电脑上,发挥的空间就大了很多,于是我便打算做成像IDE的智能提示(或叫提示补全)的形式。

          在百度和Google上搜索了一下,竟然没发现多少有用的资料。不过我觉得也没必要做到像IDE的智能提示那样的完美,因此按自己的想法做估计也不会太复杂。

          首先建立一个TipsListBox类,其作用是显示提示的信息,此类继承了ListBox,以便我们可以自己控制。将DrawMode属性设为OwnerDrawFixed。添加一个类型为string的属性Prefix。此属性的作用以后会提到。最后我们重写DrawItem事件。代码如下:

    privatevoid TipsListBox_DrawItem(objectsender, DrawItemEventArgse)

           {

               if (e.Index < 0)

                   return;

               //是否选中了该项

               bool selected = (e.State &DrawItemState.Selected) ==DrawItemState.Selected ?true : false;

     

               e.DrawBackground();

     

               System.Reflection.Assemblyasm = System.Reflection.Assembly.GetExecutingAssembly();

               if (selected)

               {

                   Image img =Image.FromStream(asm.GetManifestResourceStream("MM.App.Resources.focus.gif"));

                   Brush b =new TextureBrush(img);

                   //参数中,e.Bounds表示当前选项在整个listbox中的区域

                   e.Graphics.FillRectangle(b,e.Bounds);

               }

               else

               {

                   //在背景上画空白

                   e.Graphics.FillRectangle(Brushes.White,e.Bounds);

                   Image img =Image.FromStream(asm.GetManifestResourceStream("MM.App.Resources.line.bmp"));

                   Brush b =new TextureBrush(img);

                   //底下线的图片,参数中,23和是根据图片来的,因为需要在最下面显示线条

                   e.Graphics.FillRectangle(b,e.Bounds.X,e.Bounds.Y + 23,e.Bounds.Width, 1);

               }

     

               //最后把要显示的文字画在背景图片上

               e.Graphics.DrawString(this.Items[e.Index].ToString(),this.Font, Brushes.Black, e.Bounds.X + 15,e.Bounds.Y + 6,StringFormat.GenericDefault);

     

               //再画一下边框

               ControlPaint.DrawBorder(e.Graphics,this.ClientRectangle,

                                           Color.Beige, 2,ButtonBorderStyle.Solid,

                                           Color.Beige, 2,ButtonBorderStyle.Solid,

                                           Color.Beige, 2,ButtonBorderStyle.Solid,

                                           Color.Beige, 2,ButtonBorderStyle.Solid);

     

           }

    发送短信的界面是这样的:

    在发送内容的输入框里输入要发送的短信,系统应该能提取用户最后输入的字串,然后将此字串放到预定义的常用短语库里匹配,将匹配到的短语列表显示在一个ListBox中。我这里暂时采取的规则比较简单,只提取以空格切分的最后一串的字符,然后匹配常用短语库中以这字串开头的短语。以后再根据客户需要进行扩展修改。

          首先重写短信内容的文本框(RichTextBox)的事件:

    privatevoid txtMessageContent_TextChanged(objectsender, EventArgse)

           {

               //提示框的name

               const stringcontrolKey = "lstTips";

               RichTextBox tb = ((RichTextBox)sender);

               //以空格切分

               string[] array =tb.Text.Split(" ".ToCharArray());

               if (array !=null &&  array.Length > 0)

               {

                   TipsListBox lstTips =null;

                   if(tb.Controls.ContainsKey(controlKey))

                   {

                       lstTips = (TipsListBox)tb.Controls[controlKey];

                   }

                   else

                   {

                       lstTips = newTipsListBox();

                       lstTips.Name ="lstTips";

                       //我们要重写这两个事件

                       lstTips.KeyDown +=new KeyEventHandler(lstTips_KeyDown);

                       lstTips.Click +=new EventHandler(lstTips_Click);

                   }

                   //这个前缀就是放到常用短语库中去匹配的

                   string prefix =array[array.Length - 1];

                   if (string.IsNullOrEmpty(prefix))

                   {

                       lstTips.Hide();

                       return;

                   }

                   //从常用短语库中查找

                   List<GeneralPhraseInfo>list = GeneralPhrasePool.Search(prefix);

                   if (list ==null)

                       return;

                   //将此前缀保存起来

                   lstTips.Prefix =prefix;

                   lstTips.Items.Clear();

                   foreach (GeneralPhraseInfop in list)

                   {

                       lstTips.Items.Add(p.Phrase);

                   }

                   lstTips.Show();

     

                   lstTips.Width = 200;

                   lstTips.TabIndex = 100;

                   //让提示框跟随光标

                   lstTips.Location =tb.GetPositionFromCharIndex(tb.SelectionStart);

                   lstTips.Left += 10;

                   lstTips.SelectedIndex = 0;

                   if (!tb.Controls.ContainsKey(controlKey))

                       tb.Controls.Add(lstTips);

               }

           }

    用户在短信输入文本框里按中了键盘的下方向键的话,就将焦点移到ListBox提示框里。

    privatevoid txtMessageContent_KeyDown(objectsender, KeyEventArgse)

           {

               RichTextBox tb = ((RichTextBox)sender);

               const stringcontrolKey = "lstTips";

               if (e.KeyCode ==Keys.Down && tb.Controls.ContainsKey(controlKey))

               {

                   TipsListBox lstTips = (TipsListBox)tb.Controls[controlKey];

                   if(lstTips.Visible)

                   {

                       lstTips.Focus();

                   }

               }

                   

           }

    然后重写ListBox的两个事件,比较简单,直接上代码:

    voidlstTips_Click(objectsender, EventArgse)

           {

               TipsListBox lstTips = (TipsListBox)sender;

               if(lstTips.SelectedIndex > -1)

               {

                   string tips =lstTips.SelectedItem.ToString();

                   txtMessageContent.AppendText(tips =tips.Substring(lstTips.Prefix.Length,tips.Length - lstTips.Prefix.Length));

                   lstTips.Hide();

                   txtMessageContent.Focus();

               }

           }

     

           void lstTips_KeyDown(object sender, KeyEventArgs e)

           {

               if (e.KeyCode ==Keys.Enter)

               {

                   //如果敲的是回车,就选定短语

                   TipsListBox lstTips = (TipsListBox)sender;

                   if (lstTips.SelectedIndex > -1)

                   {

                       string tips =lstTips.SelectedItem.ToString();

                       txtMessageContent.AppendText(tips =tips.Substring(lstTips.Prefix.Length,tips.Length - lstTips.Prefix.Length));

                       lstTips.Hide();

                       txtMessageContent.Focus();

                   }

                   return;

               }

               //只允许在ListBox上操作上键和下键,其它键都使焦点返回到短信输入框

               if (e.KeyCode !=Keys.Down && e.KeyCode != Keys.Up)

                   txtMessageContent.Focus();

           }

    到了这里,大家该差不多明白其中的流程了。不过可能对这一句有点疑惑:List<GeneralPhraseInfo>list = GeneralPhrasePool.Search(prefix);

    为了提高性能,我预先将常用短语提取出来排好序,然后放到内存中。排序非常简单,用一条sql就可以搞定:Select * from dbo.GeneralPhrase order by Phrase

    GeneralPhrase表里只有两个字段,一个是自增型主键,另一个就是Phrase类型为varchar。

    既然已经排好序了,那当然用二分查找法。

    publicstatic List<GeneralPhraseInfo>Search(string prefix)

           {

               if (list ==null || list.Count == 0)

                   return null;

               int start = 0;

               int end =list.Count - 1;

               int middle = (start +end) / 2;

               int first = 0;

               int last = 0;

               while (start <=end)

               {

                   if (list[middle].Phrase.StartsWith(prefix,StringComparison.OrdinalIgnoreCase))

                   {

                       //好,找到了一个

                       first = middle;

                       if(middle >start)

                       {

                           --middle;

                           //只要这个之前的也符合

                           while (list[middle].Phrase.StartsWith(prefix,StringComparison.OrdinalIgnoreCase))

                           {

                               first = middle;

                               if (middle > -1)

                                   --middle;

                               else

                                   break;

                           }

                       }

                       //重置索引

                       middle = (start +end) / 2;

                       last = middle;

                       if(middle <end)

                       {

                           ++middle;

                           //只要这个之后的也符合

                           while (list[middle].Phrase.StartsWith(prefix,StringComparison.OrdinalIgnoreCase))

                           {

                               last = middle;

                               if (middle <list.Count)

                                   ++middle;

                               else

                                   break;

                           }

                       }

                       

                       return list.GetRange(first,last - first + 1);

                   }

                   else if (list[middle].Phrase.ToLower().CompareTo(prefix) < 0 )

                   {

                       start = middle + 1;

                   }

                   else

                   {

                       end = middle - 1;

                   }

                   middle = (start +end) / 2;

               }

               //找不到

               return null;

           }

    最后的效果如下:

  • 相关阅读:
    POJ--3667 Hotel
    Dragon Balls
    Popular Cows
    Tunnel Warfare [HDU--1540]
    CompletableFuture
    Future<V>
    分布式架构知识体系
    异步I/O和非阻塞I/O(轮询)
    同步异步阻塞非阻塞及并发级别
    volatile
  • 原文地址:https://www.cnblogs.com/harry0914/p/3022187.html
Copyright © 2011-2022 走看看