+
+ class AutoCompleteListBox : Control
+ {
+ TextBox owner;
+ VScrollBar vscroll;
+ int top_item;
+ int last_item;
+ internal int page_size;
+ int item_height;
+ int highlighted_index = -1;
+ bool user_defined_size;
+ bool resizing;
+ Rectangle resizer_bounds;
+
+ const int DefaultDropDownItems = 7;
+
+ public AutoCompleteListBox (TextBox tb)
+ {
+ owner = tb;
+ item_height = FontHeight + 2;
+
+ vscroll = new VScrollBar ();
+ vscroll.ValueChanged += VScrollValueChanged;
+ Controls.Add (vscroll);
+
+ is_visible = false;
+ InternalBorderStyle = BorderStyle.FixedSingle;
+ }
+
+ protected override CreateParams CreateParams {
+ get {
+ CreateParams cp = base.CreateParams;
+
+ cp.Style ^= (int)WindowStyles.WS_CHILD;
+ cp.Style ^= (int)WindowStyles.WS_VISIBLE;
+ cp.Style |= (int)WindowStyles.WS_POPUP;
+ cp.ExStyle |= (int)WindowExStyles.WS_EX_TOPMOST | (int)WindowExStyles.WS_EX_TOOLWINDOW;
+ return cp;
+ }
+ }
+
+ public int HighlightedIndex {
+ get {
+ return highlighted_index;
+ }
+ set {
+ if (value == highlighted_index)
+ return;
+
+ if (highlighted_index != -1)
+ Invalidate (GetItemBounds (highlighted_index));
+ highlighted_index = value;
+ if (highlighted_index != -1)
+ Invalidate (GetItemBounds (highlighted_index));
+
+ if (highlighted_index != -1)
+ EnsureVisible (highlighted_index);
+ }
+ }
+
+ public void Scroll (int lines)
+ {
+ int max = vscroll.Maximum - page_size + 1;
+ int val = vscroll.Value + lines;
+ if (val > max)
+ val = max;
+ else if (val < vscroll.Minimum)
+ val = vscroll.Minimum;
+
+ vscroll.Value = val;
+ }
+
+ public void EnsureVisible (int index)
+ {
+ if (index < top_item) {
+ vscroll.Value = index;
+ } else {
+ int max = vscroll.Maximum - page_size + 1;
+ int rows = Height / item_height;
+ if (index > top_item + rows - 1) {
+ index = index - rows + 1;
+ vscroll.Value = index > max ? max : index;
+ }
+ }
+ }
+
+ internal override bool ActivateOnShow {
+ get {
+ return false;
+ }
+ }
+
+ void VScrollValueChanged (object o, EventArgs args)
+ {
+ if (top_item == vscroll.Value)
+ return;
+
+ top_item = vscroll.Value;
+ last_item = GetLastVisibleItem ();
+ Invalidate ();
+ }
+
+ int GetLastVisibleItem ()
+ {
+ int top_y = Height;
+
+ for (int i = top_item; i < owner.auto_complete_matches.Count; i++) {
+ int pos = i - top_item; // relative to visible area
+ if ((pos * item_height) + item_height >= top_y)
+ return i;
+ }
+
+ return owner.auto_complete_matches.Count - 1;
+ }
+
+ Rectangle GetItemBounds (int index)
+ {
+ int pos = index - top_item;
+ Rectangle bounds = new Rectangle (0, pos * item_height, Width, item_height);
+ if (vscroll.Visible)
+ bounds.Width -= vscroll.Width;
+
+ return bounds;
+ }
+
+ int GetItemAt (Point loc)
+ {
+ if (loc.Y > (last_item - top_item) * item_height + item_height)
+ return -1;
+
+ int retval = loc.Y / item_height;
+ retval += top_item;
+
+ return retval;
+ }
+
+ void LayoutListBox ()
+ {
+ int total_height = owner.auto_complete_matches.Count * item_height;
+ page_size = Math.Max (Height / item_height, 1);
+ last_item = GetLastVisibleItem ();
+
+ if (Height < total_height) {
+ vscroll.Visible = true;
+ vscroll.Maximum = owner.auto_complete_matches.Count - 1;
+ vscroll.LargeChange = page_size;
+ vscroll.Location = new Point (Width - vscroll.Width, 0);
+ vscroll.Height = Height - item_height;
+ } else
+ vscroll.Visible = false;
+
+ resizer_bounds = new Rectangle (Width - item_height, Height - item_height,
+ item_height, item_height);
+ }
+
+ public void HideListBox (bool set_text)
+ {
+ if (set_text)
+ owner.Text = owner.auto_complete_matches [HighlightedIndex];
+
+ Capture = false;
+ Hide ();
+ }
+
+ public void ShowListBox ()
+ {
+ if (!user_defined_size) {
+ // This should call the Layout routine for us
+ int height = owner.auto_complete_matches.Count > DefaultDropDownItems ?
+ DefaultDropDownItems * item_height : (owner.auto_complete_matches.Count + 1) * item_height;
+ Size = new Size (owner.Width, height);
+ } else
+ LayoutListBox ();
+
+ vscroll.Value = 0;
+ HighlightedIndex = -1;
+
+ Show ();
+ // make sure we are on top - call the raw routine, since we are parentless
+ XplatUI.SetZOrder (Handle, IntPtr.Zero, true, false);
+ Invalidate ();
+ }
+
+ protected override void OnResize (EventArgs args)
+ {
+ base.OnResize (args);
+
+ LayoutListBox ();
+ Refresh ();
+ }
+
+ protected override void OnMouseDown (MouseEventArgs args)
+ {
+ base.OnMouseDown (args);
+
+ if (!resizer_bounds.Contains (args.Location))
+ return;
+
+ user_defined_size = true;
+ resizing = true;
+ Capture = true;
+ }
+
+ protected override void OnMouseMove (MouseEventArgs args)
+ {
+ base.OnMouseMove (args);
+
+ if (resizing) {
+ Point mouse_loc = Control.MousePosition;
+ Point ctrl_loc = PointToScreen (Point.Empty);
+
+ Size new_size = new Size (mouse_loc.X - ctrl_loc.X, mouse_loc.Y - ctrl_loc.Y);
+ if (new_size.Height < item_height)
+ new_size.Height = item_height;
+ if (new_size.Width < item_height)
+ new_size.Width = item_height;
+
+ Size = new_size;
+ return;
+ }
+
+ Cursor = resizer_bounds.Contains (args.Location) ? Cursors.SizeNWSE : Cursors.Default;
+
+ int item_idx = GetItemAt (args.Location);
+ if (item_idx != -1)
+ HighlightedIndex = item_idx;
+ }
+
+ protected override void OnMouseUp (MouseEventArgs args)
+ {
+ base.OnMouseUp (args);
+
+ int item_idx = GetItemAt (args.Location);
+ if (item_idx != -1 && !resizing)
+ HideListBox (true);
+
+ owner.OnAutoCompleteValueSelected (EventArgs.Empty); // internal
+ resizing = false;
+ Capture = false;
+ }
+
+ internal override void OnPaintInternal (PaintEventArgs args)
+ {
+ Graphics g = args.Graphics;
+ Brush brush = ThemeEngine.Current.ResPool.GetSolidBrush (ForeColor);
+
+ int highlighted_idx = HighlightedIndex;
+
+ int y = 0;
+ int last = GetLastVisibleItem ();
+ for (int i = top_item; i <= last; i++) {
+ Rectangle item_bounds = GetItemBounds (i);
+ if (!item_bounds.IntersectsWith (args.ClipRectangle))
+ continue;
+
+ if (i == highlighted_idx) {
+ g.FillRectangle (SystemBrushes.Highlight, item_bounds);
+ g.DrawString (owner.auto_complete_matches [i], Font, SystemBrushes.HighlightText, item_bounds);
+ } else
+ g.DrawString (owner.auto_complete_matches [i], Font, brush, item_bounds);
+
+ y += item_height;
+ }
+
+ ThemeEngine.Current.CPDrawSizeGrip (g, SystemColors.Control, resizer_bounds);
+ }
+ }