903 lines
29 KiB
C#
903 lines
29 KiB
C#
|
//
|
|||
|
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
|
|||
|
// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
|||
|
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
|
|||
|
// PURPOSE.
|
|||
|
//
|
|||
|
// License: GNU Lesser General Public License (LGPLv3)
|
|||
|
//
|
|||
|
// Email: p_torgashov@ukr.net.
|
|||
|
//
|
|||
|
// Copyright (C) Pavel Torgashov, 2012-2015.
|
|||
|
|
|||
|
using System;
|
|||
|
using System.Collections.Generic;
|
|||
|
using System.ComponentModel;
|
|||
|
using System.Drawing;
|
|||
|
using System.Text.RegularExpressions;
|
|||
|
using System.Windows.Forms;
|
|||
|
using System.Collections;
|
|||
|
using System.Diagnostics;
|
|||
|
|
|||
|
namespace AutocompleteMenuNS
|
|||
|
{
|
|||
|
[ProvideProperty("AutocompleteMenu", typeof(Control))]
|
|||
|
public class AutocompleteMenu : Component, IExtenderProvider
|
|||
|
{
|
|||
|
private static readonly Dictionary<Control, AutocompleteMenu> AutocompleteMenuByControls =
|
|||
|
new Dictionary<Control, AutocompleteMenu>();
|
|||
|
private static readonly Dictionary<Control, ITextBoxWrapper> WrapperByControls =
|
|||
|
new Dictionary<Control, ITextBoxWrapper>();
|
|||
|
|
|||
|
private ITextBoxWrapper targetControlWrapper;
|
|||
|
private readonly Timer timer = new Timer();
|
|||
|
|
|||
|
private IEnumerable<AutocompleteItem> sourceItems = new List<AutocompleteItem>();
|
|||
|
[Browsable(false)]
|
|||
|
public IList<AutocompleteItem> VisibleItems { get { return Host.ListView.VisibleItems; } private set { Host.ListView.VisibleItems = value;} }
|
|||
|
private Size maximumSize;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Duration (ms) of tooltip showing
|
|||
|
/// </summary>
|
|||
|
[Description("Duration (ms) of tooltip showing")]
|
|||
|
[DefaultValue(3000)]
|
|||
|
public int ToolTipDuration
|
|||
|
{
|
|||
|
get { return Host.ListView.ToolTipDuration; }
|
|||
|
set { Host.ListView.ToolTipDuration = value; }
|
|||
|
}
|
|||
|
|
|||
|
public AutocompleteMenu()
|
|||
|
{
|
|||
|
Host = new AutocompleteMenuHost(this);
|
|||
|
Host.ListView.ItemSelected += new EventHandler(ListView_ItemSelected);
|
|||
|
Host.ListView.ItemHovered += new EventHandler<HoveredEventArgs>(ListView_ItemHovered);
|
|||
|
VisibleItems = new List<AutocompleteItem>();
|
|||
|
Enabled = true;
|
|||
|
AppearInterval = 500;
|
|||
|
timer.Tick += timer_Tick;
|
|||
|
MaximumSize = new Size(180, 200);
|
|||
|
AutoPopup = true;
|
|||
|
|
|||
|
SearchPattern = @"[\w\.]";
|
|||
|
MinFragmentLength = 2;
|
|||
|
}
|
|||
|
|
|||
|
protected override void Dispose(bool disposing)
|
|||
|
{
|
|||
|
if (disposing)
|
|||
|
{
|
|||
|
timer.Dispose();
|
|||
|
Host.Dispose();
|
|||
|
}
|
|||
|
base.Dispose(disposing);
|
|||
|
}
|
|||
|
|
|||
|
void ListView_ItemSelected(object sender, EventArgs e)
|
|||
|
{
|
|||
|
OnSelecting();
|
|||
|
}
|
|||
|
|
|||
|
void ListView_ItemHovered(object sender, HoveredEventArgs e)
|
|||
|
{
|
|||
|
OnHovered(e);
|
|||
|
}
|
|||
|
|
|||
|
public void OnHovered(HoveredEventArgs e)
|
|||
|
{
|
|||
|
if (Hovered != null)
|
|||
|
Hovered(this, e);
|
|||
|
}
|
|||
|
|
|||
|
[Browsable(false)]
|
|||
|
public int SelectedItemIndex { get { return Host.ListView.SelectedItemIndex; }
|
|||
|
internal set { Host.ListView.SelectedItemIndex = value; }
|
|||
|
}
|
|||
|
|
|||
|
internal AutocompleteMenuHost Host { get; set; }
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Called when user selected the control and needed wrapper over it.
|
|||
|
/// You can assign own Wrapper for target control.
|
|||
|
/// </summary>
|
|||
|
[Description("Called when user selected the control and needed wrapper over it. You can assign own Wrapper for target control.")]
|
|||
|
public event EventHandler<WrapperNeededEventArgs> WrapperNeeded;
|
|||
|
|
|||
|
protected void OnWrapperNeeded(WrapperNeededEventArgs args)
|
|||
|
{
|
|||
|
if (WrapperNeeded != null)
|
|||
|
WrapperNeeded(this, args);
|
|||
|
if (args.Wrapper == null)
|
|||
|
args.Wrapper = TextBoxWrapper.Create(args.TargetControl);
|
|||
|
}
|
|||
|
|
|||
|
ITextBoxWrapper CreateWrapper(Control control)
|
|||
|
{
|
|||
|
if (WrapperByControls.ContainsKey(control))
|
|||
|
return WrapperByControls[control];
|
|||
|
|
|||
|
var args = new WrapperNeededEventArgs(control);
|
|||
|
OnWrapperNeeded(args);
|
|||
|
if (args.Wrapper != null)
|
|||
|
WrapperByControls[control] = args.Wrapper;
|
|||
|
|
|||
|
return args.Wrapper;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Current target control wrapper
|
|||
|
/// </summary>
|
|||
|
[Browsable(false)]
|
|||
|
public ITextBoxWrapper TargetControlWrapper
|
|||
|
{
|
|||
|
get { return targetControlWrapper; }
|
|||
|
set {
|
|||
|
targetControlWrapper = value;
|
|||
|
if (value != null && !WrapperByControls.ContainsKey(value.TargetControl))
|
|||
|
{
|
|||
|
WrapperByControls[value.TargetControl] = value;
|
|||
|
SetAutocompleteMenu(value.TargetControl, this);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Maximum size of popup menu
|
|||
|
/// </summary>
|
|||
|
[DefaultValue(typeof(Size), "180, 200")]
|
|||
|
[Description("Maximum size of popup menu")]
|
|||
|
public Size MaximumSize
|
|||
|
{
|
|||
|
get { return maximumSize; }
|
|||
|
set {
|
|||
|
maximumSize = value;
|
|||
|
(Host.ListView as Control).MaximumSize = maximumSize;
|
|||
|
(Host.ListView as Control).Size = maximumSize;
|
|||
|
Host.CalcSize();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Font
|
|||
|
/// </summary>
|
|||
|
public Font Font
|
|||
|
{
|
|||
|
get { return (Host.ListView as Control).Font; }
|
|||
|
set { (Host.ListView as Control).Font = value; }
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Left padding of text
|
|||
|
/// </summary>
|
|||
|
[DefaultValue(18)]
|
|||
|
[Description("Left padding of text")]
|
|||
|
public int LeftPadding
|
|||
|
{
|
|||
|
get {
|
|||
|
if (Host.ListView is AutocompleteListView)
|
|||
|
return (Host.ListView as AutocompleteListView).LeftPadding;
|
|||
|
else
|
|||
|
return 0;
|
|||
|
}
|
|||
|
set {
|
|||
|
if (Host.ListView is AutocompleteListView)
|
|||
|
(Host.ListView as AutocompleteListView).LeftPadding = value;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Colors of foreground and background
|
|||
|
/// </summary>
|
|||
|
[Browsable(true)]
|
|||
|
[Description("Colors of foreground and background.")]
|
|||
|
[TypeConverter(typeof(ExpandableObjectConverter))]
|
|||
|
public Colors Colors
|
|||
|
{
|
|||
|
get { return (Host.ListView as IAutocompleteListView).Colors; }
|
|||
|
set { (Host.ListView as IAutocompleteListView).Colors = value; }
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// AutocompleteMenu will popup automatically (when user writes text). Otherwise it will popup only programmatically or by Ctrl-Space.
|
|||
|
/// </summary>
|
|||
|
[DefaultValue(true)]
|
|||
|
[Description("AutocompleteMenu will popup automatically (when user writes text). Otherwise it will popup only programmatically or by Ctrl-Space.")]
|
|||
|
public bool AutoPopup { get; set; }
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// AutocompleteMenu will capture focus when opening.
|
|||
|
/// </summary>
|
|||
|
[DefaultValue(false)]
|
|||
|
[Description("AutocompleteMenu will capture focus when opening.")]
|
|||
|
public bool CaptureFocus { get; set; }
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Indicates whether the component should draw right-to-left for RTL languages.
|
|||
|
/// </summary>
|
|||
|
[DefaultValue(typeof(RightToLeft), "No")]
|
|||
|
[Description("Indicates whether the component should draw right-to-left for RTL languages.")]
|
|||
|
public RightToLeft RightToLeft {
|
|||
|
get { return Host.RightToLeft; }
|
|||
|
set { Host.RightToLeft = value; }
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Image list
|
|||
|
/// </summary>
|
|||
|
public ImageList ImageList {
|
|||
|
get { return Host.ListView.ImageList; }
|
|||
|
set { Host.ListView.ImageList = value; }
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Fragment
|
|||
|
/// </summary>
|
|||
|
[Browsable(false)]
|
|||
|
public Range Fragment { get; internal set; }
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Regex pattern for serach fragment around caret
|
|||
|
/// </summary>
|
|||
|
[Description("Regex pattern for serach fragment around caret")]
|
|||
|
[DefaultValue(@"[\w\.]")]
|
|||
|
public string SearchPattern { get; set; }
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Minimum fragment length for popup
|
|||
|
/// </summary>
|
|||
|
[Description("Minimum fragment length for popup")]
|
|||
|
[DefaultValue(2)]
|
|||
|
public int MinFragmentLength { get; set; }
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Allows TAB for select menu item
|
|||
|
/// </summary>
|
|||
|
[Description("Allows TAB for select menu item")]
|
|||
|
[DefaultValue(false)]
|
|||
|
public bool AllowsTabKey { get; set; }
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Interval of menu appear (ms)
|
|||
|
/// </summary>
|
|||
|
[Description("Interval of menu appear (ms)")]
|
|||
|
[DefaultValue(500)]
|
|||
|
public int AppearInterval { get; set; }
|
|||
|
|
|||
|
[DefaultValue(null)]
|
|||
|
public string[] Items
|
|||
|
{
|
|||
|
get
|
|||
|
{
|
|||
|
if (sourceItems == null)
|
|||
|
return null;
|
|||
|
var list = new List<string>();
|
|||
|
foreach (AutocompleteItem item in sourceItems)
|
|||
|
list.Add(item.ToString());
|
|||
|
return list.ToArray();
|
|||
|
}
|
|||
|
set { SetAutocompleteItems(value); }
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// The control for menu displaying.
|
|||
|
/// Set to null for restore default ListView (AutocompleteListView).
|
|||
|
/// </summary>
|
|||
|
[Browsable(false)]
|
|||
|
public IAutocompleteListView ListView
|
|||
|
{
|
|||
|
get { return Host.ListView; }
|
|||
|
set
|
|||
|
{
|
|||
|
if (ListView != null)
|
|||
|
{
|
|||
|
var ctrl = value as Control;
|
|||
|
value.ImageList = ImageList;
|
|||
|
ctrl.RightToLeft = RightToLeft;
|
|||
|
ctrl.Font = Font;
|
|||
|
ctrl.MaximumSize = MaximumSize;
|
|||
|
}
|
|||
|
Host.ListView = value;
|
|||
|
Host.ListView.ItemSelected += new EventHandler(ListView_ItemSelected);
|
|||
|
Host.ListView.ItemHovered += new EventHandler<HoveredEventArgs>(ListView_ItemHovered);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
[DefaultValue(true)]
|
|||
|
public bool Enabled { get; set; }
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Updates size of the menu
|
|||
|
/// </summary>
|
|||
|
public void Update()
|
|||
|
{
|
|||
|
Host.CalcSize();
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Returns rectangle of item
|
|||
|
/// </summary>
|
|||
|
public Rectangle GetItemRectangle(int itemIndex)
|
|||
|
{
|
|||
|
return Host.ListView.GetItemRectangle(itemIndex);
|
|||
|
}
|
|||
|
|
|||
|
#region IExtenderProvider Members
|
|||
|
|
|||
|
bool IExtenderProvider.CanExtend(object extendee)
|
|||
|
{
|
|||
|
//find AutocompleteMenu with lowest hashcode
|
|||
|
if (Container != null)
|
|||
|
foreach (object comp in Container.Components)
|
|||
|
if (comp is AutocompleteMenu)
|
|||
|
if (comp.GetHashCode() < GetHashCode())
|
|||
|
return false;
|
|||
|
//we are main autocomplete menu on form ...
|
|||
|
//check extendee as TextBox
|
|||
|
if (!(extendee is Control))
|
|||
|
return false;
|
|||
|
var temp = TextBoxWrapper.Create(extendee as Control);
|
|||
|
return temp!=null;
|
|||
|
}
|
|||
|
|
|||
|
public void SetAutocompleteMenu(Control control, AutocompleteMenu menu)
|
|||
|
{
|
|||
|
if (menu != null)
|
|||
|
{
|
|||
|
if (WrapperByControls.ContainsKey(control))
|
|||
|
{
|
|||
|
var wrapper = WrapperByControls[control];
|
|||
|
if (wrapper == null) return;
|
|||
|
//
|
|||
|
if (control.IsHandleCreated)
|
|||
|
menu.SubscribeForm(wrapper);
|
|||
|
else
|
|||
|
control.HandleCreated += (o, e) => menu.SubscribeForm(wrapper);
|
|||
|
//
|
|||
|
AutocompleteMenuByControls[control] = this;
|
|||
|
//
|
|||
|
wrapper.LostFocus += menu.control_LostFocus;
|
|||
|
wrapper.Scroll += menu.control_Scroll;
|
|||
|
wrapper.KeyDown += menu.control_KeyDown;
|
|||
|
wrapper.MouseDown += menu.control_MouseDown;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
var wrapper = menu.CreateWrapper(control);
|
|||
|
if (wrapper == null) return;
|
|||
|
//
|
|||
|
if (control.IsHandleCreated)
|
|||
|
menu.SubscribeForm(wrapper);
|
|||
|
else
|
|||
|
control.HandleCreated += (o, e) => menu.SubscribeForm(wrapper);
|
|||
|
//
|
|||
|
AutocompleteMenuByControls[control] = this;
|
|||
|
//
|
|||
|
wrapper.LostFocus += menu.control_LostFocus;
|
|||
|
wrapper.Scroll += menu.control_Scroll;
|
|||
|
wrapper.KeyDown += menu.control_KeyDown;
|
|||
|
wrapper.MouseDown += menu.control_MouseDown;
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
AutocompleteMenuByControls.TryGetValue(control, out menu);
|
|||
|
AutocompleteMenuByControls.Remove(control);
|
|||
|
ITextBoxWrapper wrapper = null;
|
|||
|
WrapperByControls.TryGetValue(control, out wrapper);
|
|||
|
WrapperByControls.Remove(control);
|
|||
|
if (wrapper != null && menu != null)
|
|||
|
{
|
|||
|
wrapper.LostFocus -= menu.control_LostFocus;
|
|||
|
wrapper.Scroll -= menu.control_Scroll;
|
|||
|
wrapper.KeyDown -= menu.control_KeyDown;
|
|||
|
wrapper.MouseDown -= menu.control_MouseDown;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// User selects item
|
|||
|
/// </summary>
|
|||
|
[Description("Occurs when user selects item.")]
|
|||
|
public event EventHandler<SelectingEventArgs> Selecting;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// It fires after item was inserting
|
|||
|
/// </summary>
|
|||
|
[Description("Occurs after user selected item.")]
|
|||
|
public event EventHandler<SelectedEventArgs> Selected;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// It fires when item was hovered
|
|||
|
/// </summary>
|
|||
|
[Description("Occurs when user hovered item.")]
|
|||
|
public event EventHandler<HoveredEventArgs> Hovered;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Occurs when popup menu is opening
|
|||
|
/// </summary>
|
|||
|
public event EventHandler<CancelEventArgs> Opening;
|
|||
|
|
|||
|
private void timer_Tick(object sender, EventArgs e)
|
|||
|
{
|
|||
|
timer.Stop();
|
|||
|
if(TargetControlWrapper!=null)
|
|||
|
ShowAutocomplete(false);
|
|||
|
}
|
|||
|
|
|||
|
private Form myForm;
|
|||
|
|
|||
|
void SubscribeForm(ITextBoxWrapper wrapper)
|
|||
|
{
|
|||
|
if (wrapper == null) return;
|
|||
|
var form = wrapper.TargetControl.FindForm();
|
|||
|
if (form == null) return;
|
|||
|
if (myForm != null)
|
|||
|
{
|
|||
|
if (myForm == form)
|
|||
|
return;
|
|||
|
UnsubscribeForm(wrapper);
|
|||
|
}
|
|||
|
|
|||
|
myForm = form;
|
|||
|
|
|||
|
form.LocationChanged += new EventHandler(form_LocationChanged);
|
|||
|
form.ResizeBegin += new EventHandler(form_LocationChanged);
|
|||
|
form.FormClosing += new FormClosingEventHandler(form_FormClosing);
|
|||
|
form.LostFocus += new EventHandler(form_LocationChanged);
|
|||
|
}
|
|||
|
|
|||
|
void UnsubscribeForm(ITextBoxWrapper wrapper)
|
|||
|
{
|
|||
|
if (wrapper == null) return;
|
|||
|
var form = wrapper.TargetControl.FindForm();
|
|||
|
if (form == null) return;
|
|||
|
|
|||
|
form.LocationChanged -= new EventHandler(form_LocationChanged);
|
|||
|
form.ResizeBegin -= new EventHandler(form_LocationChanged);
|
|||
|
form.FormClosing -= new FormClosingEventHandler(form_FormClosing);
|
|||
|
form.LostFocus -= new EventHandler(form_LocationChanged);
|
|||
|
}
|
|||
|
|
|||
|
private void form_FormClosing(object sender, FormClosingEventArgs e)
|
|||
|
{
|
|||
|
Close();
|
|||
|
}
|
|||
|
|
|||
|
private void form_LocationChanged(object sender, EventArgs e)
|
|||
|
{
|
|||
|
Close();
|
|||
|
}
|
|||
|
|
|||
|
private void control_MouseDown(object sender, MouseEventArgs e)
|
|||
|
{
|
|||
|
Close();
|
|||
|
}
|
|||
|
|
|||
|
ITextBoxWrapper FindWrapper(Control sender)
|
|||
|
{
|
|||
|
while (sender != null)
|
|||
|
{
|
|||
|
if (WrapperByControls.ContainsKey(sender))
|
|||
|
return WrapperByControls[sender];
|
|||
|
|
|||
|
sender = sender.Parent;
|
|||
|
}
|
|||
|
|
|||
|
return null;
|
|||
|
}
|
|||
|
|
|||
|
private void control_KeyDown(object sender, KeyEventArgs e)
|
|||
|
{
|
|||
|
TargetControlWrapper = FindWrapper(sender as Control);
|
|||
|
|
|||
|
bool backspaceORdel = e.KeyCode == Keys.Back || e.KeyCode == Keys.Delete;
|
|||
|
|
|||
|
if (Host.Visible)
|
|||
|
{
|
|||
|
if (ProcessKey((char)e.KeyCode, Control.ModifierKeys))
|
|||
|
e.SuppressKeyPress = true;
|
|||
|
else
|
|||
|
if (!backspaceORdel)
|
|||
|
ResetTimer(1);
|
|||
|
else
|
|||
|
ResetTimer();
|
|||
|
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
if (!Host.Visible)
|
|||
|
{
|
|||
|
switch (e.KeyCode)
|
|||
|
{
|
|||
|
case Keys.Up:
|
|||
|
case Keys.Down:
|
|||
|
case Keys.PageUp:
|
|||
|
case Keys.PageDown:
|
|||
|
case Keys.Left:
|
|||
|
case Keys.Right:
|
|||
|
case Keys.End:
|
|||
|
case Keys.Home:
|
|||
|
case Keys.ControlKey:
|
|||
|
case Keys.Escape:
|
|||
|
case Keys.Tab:
|
|||
|
{
|
|||
|
timer.Stop();
|
|||
|
return;
|
|||
|
}
|
|||
|
}
|
|||
|
if (Control.ModifierKeys== Keys.Alt)
|
|||
|
{
|
|||
|
timer.Stop();
|
|||
|
return;
|
|||
|
}
|
|||
|
if (Control.ModifierKeys == Keys.Control && e.KeyCode == Keys.Space)
|
|||
|
{
|
|||
|
ShowAutocomplete(true);
|
|||
|
e.SuppressKeyPress = true;
|
|||
|
return;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
ResetTimer();
|
|||
|
}
|
|||
|
|
|||
|
void ResetTimer()
|
|||
|
{
|
|||
|
ResetTimer(-1);
|
|||
|
}
|
|||
|
|
|||
|
void ResetTimer(int interval)
|
|||
|
{
|
|||
|
if (interval <= 0)
|
|||
|
timer.Interval = AppearInterval;
|
|||
|
else
|
|||
|
timer.Interval = interval;
|
|||
|
timer.Stop();
|
|||
|
timer.Start();
|
|||
|
}
|
|||
|
|
|||
|
private void control_Scroll(object sender, ScrollEventArgs e)
|
|||
|
{
|
|||
|
Close();
|
|||
|
}
|
|||
|
|
|||
|
private void control_LostFocus(object sender, EventArgs e)
|
|||
|
{
|
|||
|
if (!Host.Focused) Close();
|
|||
|
}
|
|||
|
|
|||
|
public AutocompleteMenu GetAutocompleteMenu(Control control)
|
|||
|
{
|
|||
|
if (AutocompleteMenuByControls.ContainsKey(control))
|
|||
|
return AutocompleteMenuByControls[control];
|
|||
|
else
|
|||
|
return null;
|
|||
|
}
|
|||
|
|
|||
|
bool forcedOpened = false;
|
|||
|
|
|||
|
internal void ShowAutocomplete(bool forced)
|
|||
|
{
|
|||
|
if (forced)
|
|||
|
forcedOpened = true;
|
|||
|
|
|||
|
if (TargetControlWrapper != null && TargetControlWrapper.Readonly)
|
|||
|
{
|
|||
|
Close();
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
if (!Enabled)
|
|||
|
{
|
|||
|
Close();
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
if (!forcedOpened && !AutoPopup)
|
|||
|
{
|
|||
|
Close();
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
//build list
|
|||
|
BuildAutocompleteList(forcedOpened);
|
|||
|
|
|||
|
//show popup menu
|
|||
|
if (VisibleItems.Count > 0)
|
|||
|
{
|
|||
|
if (forced && VisibleItems.Count == 1 && Host.ListView.SelectedItemIndex == 0)
|
|||
|
{
|
|||
|
//do autocomplete if menu contains only one line and user press CTRL-SPACE
|
|||
|
OnSelecting();
|
|||
|
Close();
|
|||
|
}
|
|||
|
else
|
|||
|
ShowMenu();
|
|||
|
}
|
|||
|
else
|
|||
|
Close();
|
|||
|
}
|
|||
|
|
|||
|
private void ShowMenu()
|
|||
|
{
|
|||
|
if (!Host.Visible)
|
|||
|
{
|
|||
|
var args = new CancelEventArgs();
|
|||
|
OnOpening(args);
|
|||
|
if (!args.Cancel)
|
|||
|
{
|
|||
|
//calc screen point for popup menu
|
|||
|
Point point = TargetControlWrapper.TargetControl.Location;
|
|||
|
point.Offset(2, TargetControlWrapper.TargetControl.Height + 2);
|
|||
|
point = TargetControlWrapper.GetPositionFromCharIndex(Fragment.Start);
|
|||
|
point.Offset(2, TargetControlWrapper.TargetControl.Font.Height + 2);
|
|||
|
//
|
|||
|
Host.Show(TargetControlWrapper.TargetControl, point);
|
|||
|
if (CaptureFocus)
|
|||
|
{
|
|||
|
(Host.ListView as Control).Focus();
|
|||
|
//ProcessKey((char) Keys.Down, Keys.None);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
(Host.ListView as Control).Invalidate();
|
|||
|
}
|
|||
|
|
|||
|
private void BuildAutocompleteList(bool forced)
|
|||
|
{
|
|||
|
var visibleItems = new List<AutocompleteItem>();
|
|||
|
|
|||
|
bool foundSelected = false;
|
|||
|
int selectedIndex = -1;
|
|||
|
//get fragment around caret
|
|||
|
Range fragment = GetFragment(SearchPattern);
|
|||
|
string text = fragment.Text;
|
|||
|
//
|
|||
|
if (sourceItems != null)
|
|||
|
if (forced || (text.Length >= MinFragmentLength /* && tb.Selection.Start == tb.Selection.End*/))
|
|||
|
{
|
|||
|
Fragment = fragment;
|
|||
|
//build popup menu
|
|||
|
foreach (AutocompleteItem item in sourceItems)
|
|||
|
{
|
|||
|
item.Parent = this;
|
|||
|
CompareResult res = item.Compare(text);
|
|||
|
if (res != CompareResult.Hidden)
|
|||
|
visibleItems.Add(item);
|
|||
|
if (res == CompareResult.VisibleAndSelected && !foundSelected)
|
|||
|
{
|
|||
|
foundSelected = true;
|
|||
|
selectedIndex = visibleItems.Count - 1;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
VisibleItems = visibleItems;
|
|||
|
|
|||
|
if (foundSelected)
|
|||
|
Host.ListView.SelectedItemIndex = selectedIndex;
|
|||
|
else
|
|||
|
Host.ListView.SelectedItemIndex = 0;
|
|||
|
|
|||
|
Host.ListView.HighlightedItemIndex = -1;
|
|||
|
|
|||
|
Host.CalcSize();
|
|||
|
}
|
|||
|
|
|||
|
internal void OnOpening(CancelEventArgs args)
|
|||
|
{
|
|||
|
if (Opening != null)
|
|||
|
Opening(this, args);
|
|||
|
}
|
|||
|
|
|||
|
private Range GetFragment(string searchPattern)
|
|||
|
{
|
|||
|
var tb = TargetControlWrapper;
|
|||
|
|
|||
|
if (tb.SelectionLength > 0) return new Range(tb);
|
|||
|
|
|||
|
string text = tb.Text;
|
|||
|
var regex = new Regex(searchPattern);
|
|||
|
var result = new Range(tb);
|
|||
|
|
|||
|
int startPos = tb.SelectionStart;
|
|||
|
//go forward
|
|||
|
int i = startPos;
|
|||
|
while (i >= 0 && i < text.Length)
|
|||
|
{
|
|||
|
if (!regex.IsMatch(text[i].ToString()))
|
|||
|
break;
|
|||
|
i++;
|
|||
|
}
|
|||
|
result.End = i;
|
|||
|
|
|||
|
//go backward
|
|||
|
i = startPos;
|
|||
|
while (i > 0 && (i - 1) < text.Length)
|
|||
|
{
|
|||
|
if (!regex.IsMatch(text[i - 1].ToString()))
|
|||
|
break;
|
|||
|
i--;
|
|||
|
}
|
|||
|
result.Start = i;
|
|||
|
|
|||
|
return result;
|
|||
|
}
|
|||
|
|
|||
|
public void Close()
|
|||
|
{
|
|||
|
Host.ListView.HideToolTip(Host.ListView.GetParentControl());
|
|||
|
Host.Close();
|
|||
|
forcedOpened = false;
|
|||
|
}
|
|||
|
|
|||
|
public void SetAutocompleteItems(IEnumerable<string> items)
|
|||
|
{
|
|||
|
var list = new List<AutocompleteItem>();
|
|||
|
if (items == null)
|
|||
|
{
|
|||
|
sourceItems = null;
|
|||
|
return;
|
|||
|
}
|
|||
|
foreach (string item in items)
|
|||
|
list.Add(new AutocompleteItem(item));
|
|||
|
SetAutocompleteItems(list);
|
|||
|
}
|
|||
|
|
|||
|
public void SetAutocompleteItems(IEnumerable<AutocompleteItem> items)
|
|||
|
{
|
|||
|
sourceItems = items;
|
|||
|
}
|
|||
|
|
|||
|
public void AddItem(string item)
|
|||
|
{
|
|||
|
AddItem(new AutocompleteItem(item));
|
|||
|
}
|
|||
|
|
|||
|
public void AddItem(AutocompleteItem item)
|
|||
|
{
|
|||
|
if (sourceItems == null)
|
|||
|
sourceItems = new List<AutocompleteItem>();
|
|||
|
|
|||
|
if (sourceItems is IList)
|
|||
|
(sourceItems as IList).Add(item);
|
|||
|
else
|
|||
|
throw new Exception("Current autocomplete items does not support adding");
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Shows popup menu immediately
|
|||
|
/// </summary>
|
|||
|
/// <param name="forced">If True - MinFragmentLength will be ignored</param>
|
|||
|
public void Show(Control control, bool forced)
|
|||
|
{
|
|||
|
SetAutocompleteMenu(control, this);
|
|||
|
this.TargetControlWrapper = FindWrapper(control);
|
|||
|
ShowAutocomplete(forced);
|
|||
|
}
|
|||
|
|
|||
|
internal virtual void OnSelecting()
|
|||
|
{
|
|||
|
if (SelectedItemIndex < 0 || SelectedItemIndex >= VisibleItems.Count)
|
|||
|
return;
|
|||
|
|
|||
|
AutocompleteItem item = VisibleItems[SelectedItemIndex];
|
|||
|
var args = new SelectingEventArgs
|
|||
|
{
|
|||
|
Item = item,
|
|||
|
SelectedIndex = SelectedItemIndex
|
|||
|
};
|
|||
|
|
|||
|
OnSelecting(args);
|
|||
|
|
|||
|
if (args.Cancel)
|
|||
|
{
|
|||
|
SelectedItemIndex = args.SelectedIndex;
|
|||
|
(Host.ListView as Control).Invalidate(true);
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
if (!args.Handled)
|
|||
|
{
|
|||
|
Range fragment = Fragment;
|
|||
|
ApplyAutocomplete(item, fragment);
|
|||
|
}
|
|||
|
|
|||
|
Close();
|
|||
|
//
|
|||
|
var args2 = new SelectedEventArgs
|
|||
|
{
|
|||
|
Item = item,
|
|||
|
Control = TargetControlWrapper.TargetControl
|
|||
|
};
|
|||
|
item.OnSelected(args2);
|
|||
|
OnSelected(args2);
|
|||
|
}
|
|||
|
|
|||
|
private void ApplyAutocomplete(AutocompleteItem item, Range fragment)
|
|||
|
{
|
|||
|
string newText = item.GetTextForReplace();
|
|||
|
//replace text of fragment
|
|||
|
fragment.Text = newText;
|
|||
|
fragment.TargetWrapper.TargetControl.Focus();
|
|||
|
}
|
|||
|
|
|||
|
internal void OnSelecting(SelectingEventArgs args)
|
|||
|
{
|
|||
|
if (Selecting != null)
|
|||
|
Selecting(this, args);
|
|||
|
}
|
|||
|
|
|||
|
public void OnSelected(SelectedEventArgs args)
|
|||
|
{
|
|||
|
if (Selected != null)
|
|||
|
Selected(this, args);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
public void SetColumns(string[] columns, int[] columnsWidth = null)
|
|||
|
{
|
|||
|
ListView.ColumnsTitle = columns;
|
|||
|
ListView.ColumnsWidth = columnsWidth;
|
|||
|
}
|
|||
|
|
|||
|
public void SelectNext(int shift)
|
|||
|
{
|
|||
|
SelectedItemIndex = Math.Max(0, Math.Min(SelectedItemIndex + shift, VisibleItems.Count - 1));
|
|||
|
//
|
|||
|
(Host.ListView as Control).Invalidate();
|
|||
|
}
|
|||
|
|
|||
|
public bool ProcessKey(char c, Keys keyModifiers)
|
|||
|
{
|
|||
|
var page = Host.Height / (Font.Height + 4);
|
|||
|
if (keyModifiers == Keys.None)
|
|||
|
switch ((Keys) c)
|
|||
|
{
|
|||
|
case Keys.Down:
|
|||
|
SelectNext(+1);
|
|||
|
return true;
|
|||
|
case Keys.PageDown:
|
|||
|
SelectNext(+page);
|
|||
|
return true;
|
|||
|
case Keys.Up:
|
|||
|
SelectNext(-1);
|
|||
|
return true;
|
|||
|
case Keys.PageUp:
|
|||
|
SelectNext(-page);
|
|||
|
return true;
|
|||
|
case Keys.Enter:
|
|||
|
OnSelecting();
|
|||
|
return true;
|
|||
|
case Keys.Tab:
|
|||
|
if (!AllowsTabKey)
|
|||
|
break;
|
|||
|
OnSelecting();
|
|||
|
return true;
|
|||
|
case Keys.Left:
|
|||
|
case Keys.Right:
|
|||
|
Close();
|
|||
|
return false;
|
|||
|
case Keys.Escape:
|
|||
|
Close();
|
|||
|
return true;
|
|||
|
}
|
|||
|
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Menu is visible
|
|||
|
/// </summary>
|
|||
|
public bool Visible
|
|||
|
{
|
|||
|
get { return Host != null && Host.Visible; }
|
|||
|
}
|
|||
|
}
|
|||
|
}
|