5 people like it.

Auto-completion in any winforms text box

The standard windows.forms text box supports auto-completion, but only for single-line text boxes. The code below can be used to add auto-completion against a fixed set of words to any text box that inherits from TextBoxBase.

  1: 
  2: 
  3: 
  4: 
  5: 
  6: 
  7: 
  8: 
  9: 
 10: 
 11: 
 12: 
 13: 
 14: 
 15: 
 16: 
 17: 
 18: 
 19: 
 20: 
 21: 
 22: 
 23: 
 24: 
 25: 
 26: 
 27: 
 28: 
 29: 
 30: 
 31: 
 32: 
 33: 
 34: 
 35: 
 36: 
 37: 
 38: 
 39: 
 40: 
 41: 
 42: 
 43: 
 44: 
 45: 
 46: 
 47: 
 48: 
 49: 
 50: 
 51: 
 52: 
 53: 
 54: 
 55: 
 56: 
 57: 
 58: 
 59: 
 60: 
 61: 
 62: 
 63: 
 64: 
 65: 
 66: 
 67: 
 68: 
 69: 
 70: 
 71: 
 72: 
 73: 
 74: 
 75: 
 76: 
 77: 
 78: 
 79: 
 80: 
 81: 
 82: 
 83: 
 84: 
 85: 
 86: 
 87: 
 88: 
 89: 
 90: 
 91: 
 92: 
 93: 
 94: 
 95: 
 96: 
 97: 
 98: 
 99: 
100: 
101: 
102: 
103: 
104: 
105: 
106: 
107: 
108: 
109: 
110: 
111: 
112: 
113: 
114: 
115: 
116: 
117: 
118: 
119: 
120: 
121: 
122: 
123: 
124: 
125: 
126: 
127: 
128: 
129: 
130: 
131: 
132: 
133: 
134: 
135: 
136: 
137: 
138: 
139: 
140: 
141: 
142: 
143: 
#r "System.Windows.Forms"

module Trie =
    type Impl<'Char> when 'Char : comparison =
    | Multi of Map<'Char, Node<'Char>>

    and Node<'Char> when 'Char : comparison =
        { impl : Impl<'Char>
          someWordEndsHere : bool }
    
    let leaf = { impl = Multi Map.empty; someWordEndsHere = false }

    let rec insert node (word : 'Char seq) =
        if Seq.isEmpty word then
            { node with someWordEndsHere = true }
        else
            let first, rest = Seq.head word, Seq.skip 1 word
            match node.impl with
            | Multi m ->
                let c, next =
                    match Map.tryFind first m with
                    | Some next ->
                        (first, insert next rest)
                    | None ->
                        (first, insert leaf rest)
                let m =
                    Map.add c next m
                { node with impl = Multi m }

    let rec tryFindNode node (prefix : 'Char seq) =
        if not <| Seq.isEmpty prefix then
            let first, rest = Seq.head prefix, Seq.skip 1 prefix
            match node.impl with
            | Multi m ->
                match Map.tryFind first m with
                | Some next ->
                    tryFindNode next rest
                | None ->
                    None
        else
            Some node

    let rec getSuffixes node =
        seq {
            if node.someWordEndsHere then
                yield []
            match node.impl with
            | Multi m ->
                for (c, next) in Map.toSeq m do
                    yield!
                        getSuffixes next
                        |> Seq.map (fun s -> c :: s)
        }

    let findSuffixes node prefix =
        let results =
            tryFindNode node prefix
            |> Option.bind(fun node ->
                getSuffixes node
                |> Some)
        match results with
        | None -> Seq.empty
        | Some s -> s


module Extensions =
    open System.Windows.Forms

    type System.String with
        member this.ReverseAt(idx) =
            seq {
                for i in (idx - 1) .. -1 .. 0 -> this.[i]
            }

        static member Reverse(s : char seq) =
            let s = Seq.toArray s
            let sb = System.Text.StringBuilder()
            for i in s.Length - 1 .. -1 .. 0 do
                sb.Append(s.[i])
                |> ignore
            sb.ToString()

        static member ofSeq(s : char seq) =
            let sb = System.Text.StringBuilder()
            for c in s do
                sb.Append(c)
                |> ignore
            sb.ToString()
            
    type System.Windows.Forms.TextBoxBase with
        member this.GetWordBeforeCarret(isValidInWord) =
            this.Text.ReverseAt(this.SelectionStart)
            |> Seq.takeWhile isValidInWord
            |> System.String.Reverse

        member this.AddAutoCompletion(words) =
            let isValidInWord = System.Char.IsLetterOrDigit
            let trie =
                words
                |> Seq.fold Trie.insert Trie.leaf

            let handleKey (kp : KeyEventArgs) =
                if kp.Control && kp.KeyCode = Keys.Space then
                    kp.SuppressKeyPress <- true
                    let prefix = this.GetWordBeforeCarret(isValidInWord)
                    let pos = this.SelectionStart
                    let before = this.Text.[0 .. pos - 1]
                    let after = this.Text.[pos .. ]
                    let insertSuffix s =
                        this.Text <- before + s + after
                        this.SelectionStart <- pos
                        this.SelectionLength <- s.Length
                    let suffixes =
                        Trie.findSuffixes trie prefix
                        |> Seq.map (System.String.ofSeq)
                        |> Seq.sort
                        |> Seq.truncate 10
                        |> Seq.toArray

                    match suffixes with                    
                    | [||] -> ()
                    | [|s|] ->
                        insertSuffix s
                    | _ ->
                        let cms = new ContextMenuStrip()
                        for s in suffixes do
                            cms.Items.Add(prefix + s, null, fun _ _ -> insertSuffix s)
                            |> ignore
                        let pos = this.GetPositionFromCharIndex(pos)
                        let pos = System.Drawing.Point(pos.X, pos.Y + cms.Font.Height)
                        cms.Show(this, pos)

            this.KeyDown.Subscribe handleKey


open System.Windows.Forms
open Extensions

let form = new Form()
let tb = new RichTextBox(Dock = DockStyle.Fill)
tb.AddAutoCompletion(["C#" ; "F#" ; "C++"; "C"; "Ocaml"; "Haskell"])
form.Controls.Add tb
form.Show()
type Impl<'Char (requires comparison)> = | Multi of Map<'Char,Node<'Char>>

Full name: Script.Trie.Impl<_>
union case Impl.Multi: Map<'Char,Node<'Char>> -> Impl<'Char>
Multiple items
module Map

from Microsoft.FSharp.Collections

--------------------
type Map<'Key,'Value (requires comparison)> =
  interface IEnumerable
  interface IComparable
  interface IEnumerable<KeyValuePair<'Key,'Value>>
  interface ICollection<KeyValuePair<'Key,'Value>>
  interface IDictionary<'Key,'Value>
  new : elements:seq<'Key * 'Value> -> Map<'Key,'Value>
  member Add : key:'Key * value:'Value -> Map<'Key,'Value>
  member ContainsKey : key:'Key -> bool
  override Equals : obj -> bool
  member Remove : key:'Key -> Map<'Key,'Value>
  ...

Full name: Microsoft.FSharp.Collections.Map<_,_>

--------------------
new : elements:seq<'Key * 'Value> -> Map<'Key,'Value>
type Node<'Char (requires comparison)> =
  {impl: Impl<'Char>;
   someWordEndsHere: bool;}

Full name: Script.Trie.Node<_>
Node.impl: Impl<'Char>
Node.someWordEndsHere: bool
type bool = System.Boolean

Full name: Microsoft.FSharp.Core.bool
val leaf : Node<'a> (requires comparison)

Full name: Script.Trie.leaf
val empty<'Key,'T (requires comparison)> : Map<'Key,'T> (requires comparison)

Full name: Microsoft.FSharp.Collections.Map.empty
val insert : node:Node<'Char> -> word:seq<'Char> -> Node<'Char> (requires comparison)

Full name: Script.Trie.insert
val node : Node<'Char> (requires comparison)
val word : seq<'Char> (requires comparison)
Multiple items
val seq : sequence:seq<'T> -> seq<'T>

Full name: Microsoft.FSharp.Core.Operators.seq

--------------------
type seq<'T> = System.Collections.Generic.IEnumerable<'T>

Full name: Microsoft.FSharp.Collections.seq<_>
module Seq

from Microsoft.FSharp.Collections
val isEmpty : source:seq<'T> -> bool

Full name: Microsoft.FSharp.Collections.Seq.isEmpty
val first : 'Char (requires comparison)
val rest : seq<'Char> (requires comparison)
val head : source:seq<'T> -> 'T

Full name: Microsoft.FSharp.Collections.Seq.head
val skip : count:int -> source:seq<'T> -> seq<'T>

Full name: Microsoft.FSharp.Collections.Seq.skip
val m : Map<'Char,Node<'Char>> (requires comparison)
val c : 'Char (requires comparison)
val next : Node<'Char> (requires comparison)
val tryFind : key:'Key -> table:Map<'Key,'T> -> 'T option (requires comparison)

Full name: Microsoft.FSharp.Collections.Map.tryFind
union case Option.Some: Value: 'T -> Option<'T>
union case Option.None: Option<'T>
val add : key:'Key -> value:'T -> table:Map<'Key,'T> -> Map<'Key,'T> (requires comparison)

Full name: Microsoft.FSharp.Collections.Map.add
val tryFindNode : node:Node<'Char> -> prefix:seq<'Char> -> Node<'Char> option (requires comparison)

Full name: Script.Trie.tryFindNode
val prefix : seq<'Char> (requires comparison)
val not : value:bool -> bool

Full name: Microsoft.FSharp.Core.Operators.not
val getSuffixes : node:Node<'a> -> seq<'a list> (requires comparison)

Full name: Script.Trie.getSuffixes
val node : Node<'a> (requires comparison)
Node.impl: Impl<'a>
val m : Map<'a,Node<'a>> (requires comparison)
val c : 'a (requires comparison)
val next : Node<'a> (requires comparison)
val toSeq : table:Map<'Key,'T> -> seq<'Key * 'T> (requires comparison)

Full name: Microsoft.FSharp.Collections.Map.toSeq
val map : mapping:('T -> 'U) -> source:seq<'T> -> seq<'U>

Full name: Microsoft.FSharp.Collections.Seq.map
val s : 'a list (requires comparison)
val findSuffixes : node:Node<'a> -> prefix:seq<'a> -> seq<'a list> (requires comparison)

Full name: Script.Trie.findSuffixes
val prefix : seq<'a> (requires comparison)
val results : seq<'a list> option (requires comparison)
module Option

from Microsoft.FSharp.Core
val bind : binder:('T -> 'U option) -> option:'T option -> 'U option

Full name: Microsoft.FSharp.Core.Option.bind
val empty<'T> : seq<'T>

Full name: Microsoft.FSharp.Collections.Seq.empty
val s : seq<'a list> (requires comparison)
namespace System
namespace System.Windows
namespace System.Windows.Forms
Multiple items
type String =
  new : value:char -> string + 7 overloads
  member Chars : int -> char
  member Clone : unit -> obj
  member CompareTo : value:obj -> int + 1 overload
  member Contains : value:string -> bool
  member CopyTo : sourceIndex:int * destination:char[] * destinationIndex:int * count:int -> unit
  member EndsWith : value:string -> bool + 2 overloads
  member Equals : obj:obj -> bool + 2 overloads
  member GetEnumerator : unit -> CharEnumerator
  member GetHashCode : unit -> int
  ...

Full name: System.String

--------------------
System.String(value: nativeptr<char>) : unit
System.String(value: nativeptr<sbyte>) : unit
System.String(value: char []) : unit
System.String(c: char, count: int) : unit
System.String(value: nativeptr<char>, startIndex: int, length: int) : unit
System.String(value: nativeptr<sbyte>, startIndex: int, length: int) : unit
System.String(value: char [], startIndex: int, length: int) : unit
System.String(value: nativeptr<sbyte>, startIndex: int, length: int, enc: System.Text.Encoding) : unit
val this : System.String
member System.String.ReverseAt : idx:int -> seq<char>

Full name: Script.Extensions.ReverseAt
val idx : int
val i : int
static member System.String.Reverse : s:seq<char> -> string

Full name: Script.Extensions.Reverse
val s : seq<char>
Multiple items
val char : value:'T -> char (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.char

--------------------
type char = System.Char

Full name: Microsoft.FSharp.Core.char
val s : char []
val toArray : source:seq<'T> -> 'T []

Full name: Microsoft.FSharp.Collections.Seq.toArray
val sb : System.Text.StringBuilder
namespace System.Text
Multiple items
type StringBuilder =
  new : unit -> StringBuilder + 5 overloads
  member Append : value:string -> StringBuilder + 18 overloads
  member AppendFormat : format:string * arg0:obj -> StringBuilder + 4 overloads
  member AppendLine : unit -> StringBuilder + 1 overload
  member Capacity : int with get, set
  member Chars : int -> char with get, set
  member Clear : unit -> StringBuilder
  member CopyTo : sourceIndex:int * destination:char[] * destinationIndex:int * count:int -> unit
  member EnsureCapacity : capacity:int -> int
  member Equals : sb:StringBuilder -> bool
  ...

Full name: System.Text.StringBuilder

--------------------
System.Text.StringBuilder() : unit
System.Text.StringBuilder(capacity: int) : unit
System.Text.StringBuilder(value: string) : unit
System.Text.StringBuilder(value: string, capacity: int) : unit
System.Text.StringBuilder(capacity: int, maxCapacity: int) : unit
System.Text.StringBuilder(value: string, startIndex: int, length: int, capacity: int) : unit
property System.Array.Length: int
System.Text.StringBuilder.Append(value: char []) : System.Text.StringBuilder
   (+0 other overloads)
System.Text.StringBuilder.Append(value: obj) : System.Text.StringBuilder
   (+0 other overloads)
System.Text.StringBuilder.Append(value: uint64) : System.Text.StringBuilder
   (+0 other overloads)
System.Text.StringBuilder.Append(value: uint32) : System.Text.StringBuilder
   (+0 other overloads)
System.Text.StringBuilder.Append(value: uint16) : System.Text.StringBuilder
   (+0 other overloads)
System.Text.StringBuilder.Append(value: decimal) : System.Text.StringBuilder
   (+0 other overloads)
System.Text.StringBuilder.Append(value: float) : System.Text.StringBuilder
   (+0 other overloads)
System.Text.StringBuilder.Append(value: float32) : System.Text.StringBuilder
   (+0 other overloads)
System.Text.StringBuilder.Append(value: int64) : System.Text.StringBuilder
   (+0 other overloads)
System.Text.StringBuilder.Append(value: int) : System.Text.StringBuilder
   (+0 other overloads)
val ignore : value:'T -> unit

Full name: Microsoft.FSharp.Core.Operators.ignore
System.Text.StringBuilder.ToString() : string
System.Text.StringBuilder.ToString(startIndex: int, length: int) : string
static member System.String.ofSeq : s:seq<char> -> string

Full name: Script.Extensions.ofSeq
val c : char
type TextBoxBase =
  inherit Control
  member AcceptsTab : bool with get, set
  member AppendText : text:string -> unit
  member AutoSize : bool with get, set
  member BackColor : Color with get, set
  member BackgroundImage : Image with get, set
  member BackgroundImageLayout : ImageLayout with get, set
  member BorderStyle : BorderStyle with get, set
  member CanUndo : bool
  member Clear : unit -> unit
  member ClearUndo : unit -> unit
  ...

Full name: System.Windows.Forms.TextBoxBase
val this : TextBoxBase
member TextBoxBase.GetWordBeforeCarret : isValidInWord:(char -> bool) -> string

Full name: Script.Extensions.GetWordBeforeCarret
val isValidInWord : (char -> bool)
property TextBoxBase.Text: string
member System.String.ReverseAt : idx:int -> seq<char>
property TextBoxBase.SelectionStart: int
val takeWhile : predicate:('T -> bool) -> source:seq<'T> -> seq<'T>

Full name: Microsoft.FSharp.Collections.Seq.takeWhile
static member System.String.Reverse : s:seq<char> -> string
member TextBoxBase.AddAutoCompletion : words:seq<#seq<char>> -> System.IDisposable

Full name: Script.Extensions.AddAutoCompletion
val words : seq<#seq<char>>
type Char =
  struct
    member CompareTo : value:obj -> int + 1 overload
    member Equals : obj:obj -> bool + 1 overload
    member GetHashCode : unit -> int
    member GetTypeCode : unit -> TypeCode
    member ToString : unit -> string + 1 overload
    static val MaxValue : char
    static val MinValue : char
    static member ConvertFromUtf32 : utf32:int -> string
    static member ConvertToUtf32 : highSurrogate:char * lowSurrogate:char -> int + 1 overload
    static member GetNumericValue : c:char -> float + 1 overload
    ...
  end

Full name: System.Char
System.Char.IsLetterOrDigit(c: char) : bool
System.Char.IsLetterOrDigit(s: string, index: int) : bool
val trie : Trie.Node<char>
val fold : folder:('State -> 'T -> 'State) -> state:'State -> source:seq<'T> -> 'State

Full name: Microsoft.FSharp.Collections.Seq.fold
module Trie

from Script
val insert : node:Trie.Node<'Char> -> word:seq<'Char> -> Trie.Node<'Char> (requires comparison)

Full name: Script.Trie.insert
val leaf : Trie.Node<'a> (requires comparison)

Full name: Script.Trie.leaf
val handleKey : (KeyEventArgs -> unit)
val kp : KeyEventArgs
Multiple items
type KeyEventArgs =
  inherit EventArgs
  new : keyData:Keys -> KeyEventArgs
  member Alt : bool
  member Control : bool
  member Handled : bool with get, set
  member KeyCode : Keys
  member KeyData : Keys
  member KeyValue : int
  member Modifiers : Keys
  member Shift : bool
  member SuppressKeyPress : bool with get, set

Full name: System.Windows.Forms.KeyEventArgs

--------------------
KeyEventArgs(keyData: Keys) : unit
property KeyEventArgs.Control: bool
property KeyEventArgs.KeyCode: Keys
type Keys =
  | KeyCode = 65535
  | Modifiers = -65536
  | None = 0
  | LButton = 1
  | RButton = 2
  | Cancel = 3
  | MButton = 4
  | XButton1 = 5
  | XButton2 = 6
  | Back = 8
  ...

Full name: System.Windows.Forms.Keys
field Keys.Space = 32
property KeyEventArgs.SuppressKeyPress: bool
val prefix : string
member TextBoxBase.GetWordBeforeCarret : isValidInWord:(char -> bool) -> string
val pos : int
val before : string
val after : string
val insertSuffix : (string -> unit)
val s : string
property TextBoxBase.SelectionLength: int
property System.String.Length: int
val suffixes : string []
val findSuffixes : node:Trie.Node<'a> -> prefix:seq<'a> -> seq<'a list> (requires comparison)

Full name: Script.Trie.findSuffixes
static member System.String.ofSeq : s:seq<char> -> string
val sort : source:seq<'T> -> seq<'T> (requires comparison)

Full name: Microsoft.FSharp.Collections.Seq.sort
val truncate : count:int -> source:seq<'T> -> seq<'T>

Full name: Microsoft.FSharp.Collections.Seq.truncate
val cms : ContextMenuStrip
Multiple items
type ContextMenuStrip =
  inherit ToolStripDropDownMenu
  new : unit -> ContextMenuStrip + 1 overload
  member SourceControl : Control

Full name: System.Windows.Forms.ContextMenuStrip

--------------------
ContextMenuStrip() : unit
ContextMenuStrip(container: System.ComponentModel.IContainer) : unit
property ToolStrip.Items: ToolStripItemCollection
ToolStripItemCollection.Add(value: ToolStripItem) : int
ToolStripItemCollection.Add(image: System.Drawing.Image) : ToolStripItem
ToolStripItemCollection.Add(text: string) : ToolStripItem
ToolStripItemCollection.Add(text: string, image: System.Drawing.Image) : ToolStripItem
ToolStripItemCollection.Add(text: string, image: System.Drawing.Image, onClick: System.EventHandler) : ToolStripItem
val pos : System.Drawing.Point
TextBoxBase.GetPositionFromCharIndex(index: int) : System.Drawing.Point
namespace System.Drawing
Multiple items
type Point =
  struct
    new : sz:Size -> Point + 2 overloads
    member Equals : obj:obj -> bool
    member GetHashCode : unit -> int
    member IsEmpty : bool
    member Offset : p:Point -> unit + 1 overload
    member ToString : unit -> string
    member X : int with get, set
    member Y : int with get, set
    static val Empty : Point
    static member Add : pt:Point * sz:Size -> Point
    ...
  end

Full name: System.Drawing.Point

--------------------
System.Drawing.Point()
System.Drawing.Point(sz: System.Drawing.Size) : unit
System.Drawing.Point(dw: int) : unit
System.Drawing.Point(x: int, y: int) : unit
property System.Drawing.Point.X: int
property System.Drawing.Point.Y: int
property ToolStripDropDown.Font: System.Drawing.Font
property System.Drawing.Font.Height: int
ToolStripDropDown.Show() : unit
ToolStripDropDown.Show(screenLocation: System.Drawing.Point) : unit
ToolStripDropDown.Show(x: int, y: int) : unit
ToolStripDropDown.Show(position: System.Drawing.Point, direction: ToolStripDropDownDirection) : unit
ToolStripDropDown.Show(control: Control, position: System.Drawing.Point) : unit
ToolStripDropDown.Show(control: Control, x: int, y: int) : unit
ToolStripDropDown.Show(control: Control, position: System.Drawing.Point, direction: ToolStripDropDownDirection) : unit
event Control.KeyDown: IEvent<KeyEventHandler,KeyEventArgs>
member System.IObservable.Subscribe : callback:('T -> unit) -> System.IDisposable
System.IObservable.Subscribe(observer: System.IObserver<KeyEventArgs>) : System.IDisposable
module Extensions

from Script
val form : Form

Full name: Script.form
Multiple items
type Form =
  inherit ContainerControl
  new : unit -> Form
  member AcceptButton : IButtonControl with get, set
  member Activate : unit -> unit
  member ActiveMdiChild : Form
  member AddOwnedForm : ownedForm:Form -> unit
  member AllowTransparency : bool with get, set
  member AutoScale : bool with get, set
  member AutoScaleBaseSize : Size with get, set
  member AutoScroll : bool with get, set
  member AutoSize : bool with get, set
  ...
  nested type ControlCollection

Full name: System.Windows.Forms.Form

--------------------
Form() : unit
val tb : RichTextBox

Full name: Script.tb
Multiple items
type RichTextBox =
  inherit TextBoxBase
  new : unit -> RichTextBox
  member AllowDrop : bool with get, set
  member AutoSize : bool with get, set
  member AutoWordSelection : bool with get, set
  member BackgroundImage : Image with get, set
  member BackgroundImageLayout : ImageLayout with get, set
  member BulletIndent : int with get, set
  member CanPaste : clipFormat:Format -> bool
  member CanRedo : bool
  member DetectUrls : bool with get, set
  ...

Full name: System.Windows.Forms.RichTextBox

--------------------
RichTextBox() : unit
type DockStyle =
  | None = 0
  | Top = 1
  | Bottom = 2
  | Left = 3
  | Right = 4
  | Fill = 5

Full name: System.Windows.Forms.DockStyle
field DockStyle.Fill = 5
member TextBoxBase.AddAutoCompletion : words:seq<#seq<char>> -> System.IDisposable
property Control.Controls: Control.ControlCollection
Control.ControlCollection.Add(value: Control) : unit
Control.Show() : unit
Form.Show(owner: IWin32Window) : unit
Raw view Test code New version

More information

Link:http://fssnip.net/ak
Posted:12 years ago
Author:Johann Deneux
Tags: auto-completion , textbox , winforms