using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Windows.Forms;
using JD.Kernel.StaticHelpers;
namespace JD.UserControls.QuickOpenDialogs
{
///
/// Prompts the user with a list of possible items to select and allows to filter with text.
///
public class QuickOpenForm
{
#region Constants
private const int MainBoxHeight = 43;
private const int TextBoxHeight = 18;
private const int MainBoxWidth = 440;
private static readonly Size DefaultImageSize = new Size(16, 16);
private static readonly Pen ms_borderPen = new Pen(System.Drawing.Color.FromArgb(0x33, 0x99, 0xFF));
private static readonly StringFormat ms_hintFormat = new StringFormat { Alignment = StringAlignment.Far, Trimming = StringTrimming.EllipsisPath };
private static readonly Size TextPadding = new Size(2, 1);
private static readonly Size FormPadding = new Size(5, 3);
#endregion
#region Variables
private readonly int m_maxListHeight;
private Size m_imageSize;
private T m_selectedItem;
private Icon m_image;
private string m_hintText;
private IEnumerable m_originalItems;
private List m_items;
private readonly QuickOpenControl m_control;
private readonly Popup m_popup;
private bool m_showFrame;
#endregion
#region Constructor
public QuickOpenForm(IEnumerable items)
{
Throw.IfNull(items, "items");
m_originalItems = items;
m_control = new QuickOpenControl();
m_control.Activated += m_control_Activated;
m_popup = new Popup(m_control)
{
UseFadeEffect = false,
DisposeOnClose = true,
Width = MainBoxWidth
};
m_maxListHeight = ListBox.Height;
ListBox.DrawItem += listBox_DrawItem;
ListBox.MouseDown += ListBox_MouseDown;
ListBox.SelectedIndexChanged += ListBox_SelectedIndexChanged;
TextBox.TextChanged += textBox_TextChanged;
m_control.Paint += m_control_Paint;
SetListVisibility(false);
ImageSize = DefaultImageSize;
ResizeTextBox();
ListBox.Top = MainBoxHeight;
ListBox.Left = 0;
// Redirect the mouse wheel to the list box so the user doesnt have to click in the list.
TextBox.MouseWheel += (s, e) => ListBox.Focus();
NameProvider = item => item != null ? item.ToString() : "";
SortItems = true;
SortOrderProvider = input => 0;
ShowOnEmptySearchProvider = input => false;
ItemMatcher = DefaultItemMatcher;
// Force the list to be initialized when it pops (useful when we specified that we want the list to be filled when the search text is empty).
m_control.Load += (s, e) => textBox_TextChanged(null, EventArgs.Empty);
}
#endregion
#region Properties
public T SelectedItem
{
get
{
var item = ListBox.SelectedItem as ListBoxItem;
return item != null ? item.Item : default(T);
}
set
{
if (!Equals(value, null))
{
var itemName = NameProvider(value);
var item = ListBox.Items.OfType().FirstOrDefault(search => Equals(search.Item, value)) ??
ListBox.Items.OfType().FirstOrDefault(search => search.Text == itemName);
if (item != null)
{
ListBox.SelectedItem = item;
}
}
}
}
public IEnumerable Items
{
get { return m_originalItems; }
set
{
m_originalItems = value;
m_items = null;
textBox_TextChanged(null, null);
}
}
public Size ImageSize
{
get { return m_imageSize; }
set
{
m_imageSize = value;
ListBox.ItemHeight = System.Math.Max(ListBox.ItemHeight, value.Height);
}
}
///
/// Image paint on lower left corner.
///
public Icon Image
{
get { return m_image; }
set
{
m_image = value;
ResizeTextBox();
}
}
///
/// Text written on title of form.
///
public string Caption
{
get;
set;
}
public string HintText
{
get { return m_hintText; }
set
{
if (m_hintText != value)
{
m_hintText = value;
m_control.Invalidate(new Rectangle(0, 0, m_control.Width, TextBox.Location.Y));
}
}
}
public string SearchText
{
get { return TextBox.Text; }
set { TextBox.Text = value; }
}
public bool ShowAllOnEmptyText { get; set; }
///
/// True if the item should be visible even when the user did not enter any search.
///
/// You can also show all on an empty search with the flag
public Converter ShowOnEmptySearchProvider
{
get;
set;
}
public Converter ProcessDialogKey
{
get { return m_control.ProcessDialogKeyEvent; }
set { m_control.ProcessDialogKeyEvent = value; }
}
private Converter _nameProvider;
public Converter NameProvider
{
get { return _nameProvider; }
set
{
if (value != null)
_nameProvider = value;
}
}
public Converter HintProvider
{
get;
set;
}
public Converter SortOrderProvider
{
get;
set;
}
public delegate TextQuery.MatchResult ItemMatcherDelegate(T item, TextQuery query);
public ItemMatcherDelegate ItemMatcher
{
get;
set;
}
public Converter ImageProvider
{
get;
set;
}
public event Action ItemClick;
public event Action ItemAccepted;
public event Action ItemBackgroundPaint;
public event EventHandler Closed
{
add { m_popup.Disposed += value; }
remove { m_popup.Disposed -= value; }
}
public event EventHandler SearchTextChanged
{
add { TextBox.TextChanged += value; }
remove { TextBox.TextChanged -= value; }
}
private TextQuery.MatchResult DefaultItemMatcher(T item, TextQuery query)
{
return query.Matches(NameProvider(item));
}
public System.Drawing.Color BackgroundColor
{
get;
set;
}
#endregion
#region Multi Selection
public IEnumerable SelectedItems
{
get { return ListBox.SelectedItems.OfType().Select(result => result.Item); }
}
public void ClearSelectedItems()
{
ListBox.SelectedItems.Clear();
}
void ListBox_SelectedIndexChanged(object sender, EventArgs e)
{
SelectionChanged?.Invoke();
}
///
/// Triggered when items are selected in the list.
///
public event Action SelectionChanged;
public SelectionMode SelectionMode
{
get { return ListBox.SelectionMode; }
set { ListBox.SelectionMode = value; }
}
#endregion
#region Private Properties
private ListBox ListBox
{
get { return m_control.ListBox; }
}
private TextBox TextBox
{
get { return m_control.textBox; }
}
public bool ShowFrame
{
get { return m_showFrame; }
set
{
m_showFrame = value;
textBox_TextChanged(null, null);
}
}
public bool SortItems { get; set; }
public void Show()
{
Show(false);
}
public void Show(bool activate)
{
var screen = Form.ActiveForm != null ? Screen.FromControl(Form.ActiveForm) : Screen.FromPoint(Control.MousePosition);
int x = screen.Bounds.X + (screen.Bounds.Width - m_control.Width) / 2;
int y = screen.Bounds.Y + (screen.Bounds.Height - MainBoxHeight) / 3;
if (activate)
{
NativeMethods.SetForegroundWindowEx(m_popup.Handle);
}
m_popup.Show(x, y);
}
private void ResizeTextBox()
{
TextBox.AutoSize = false;
TextBox.Left = m_image != null ? FormPadding.Width + m_imageSize.Width + FormPadding.Width : FormPadding.Width;
TextBox.Width = MainBoxWidth - TextBox.Left - FormPadding.Width;
TextBox.Height = TextBoxHeight;
}
private static void DrawClearRectangle(Graphics graphics, Rectangle bounds)
{
graphics.FillRectangle(new SolidBrush(System.Drawing.Color.FromArgb(0xFC, 0xFC, 0xFC)), bounds);
}
private static void DrawSelectionRectangle(Graphics graphics, Rectangle bounds)
{
bounds = new Rectangle(bounds.X, bounds.Y, bounds.Width - 1, bounds.Height - 1);
graphics.FillRectangle(new SolidBrush(System.Drawing.Color.FromArgb(75, 0x33, 0x99, 0xFF)), bounds);
graphics.DrawRectangle(new Pen(System.Drawing.Color.FromArgb(0x33, 0x99, 0xFF)), bounds);
}
private void SetListVisibility(bool visible)
{
ListBox.Visible = visible;
Region region;
if (m_showFrame)
{
region = new Region(m_control.Bounds);
}
else
{
region = new Region(new Rectangle(0, 0, MainBoxWidth, MainBoxHeight));
if (visible)
{
region.Union(ListBox.Bounds);
}
}
m_control.Region = region;
}
private bool AcceptSelection()
{
int current = ListBox.SelectedIndex;
if (!ListBox.Visible || current < 0)
{
return AcceptEmptySelection(TextBox.Text);
}
m_selectedItem = ((ListBoxItem)ListBox.Items[current]).Item;
m_popup.Close();
ItemAccepted?.Invoke(m_selectedItem);
return true;
}
protected virtual bool AcceptEmptySelection(string searchQuery)
{
return false;
}
public void InvalidateListBox()
{
ListBox.Invalidate();
}
public void Invalidate(T item)
{
for (int i = 0; i < ListBox.Items.Count; i++)
{
if (Equals(((ListBoxItem)ListBox.Items[i]).Item, item))
{
ListBox.Invalidate(ListBox.GetItemRectangle(i));
}
}
}
#endregion
#region Event Handlers
void ListBox_MouseDown(object sender, MouseEventArgs e)
{
var index = ListBox.IndexFromPoint(e.Location);
if (index >= 0)
{
var clickedItem = (ListBoxItem)ListBox.Items[index];
ItemClick?.Invoke(clickedItem.Item);
}
}
private Brush CreateBackgroundBrush(Rectangle rectangle)
{
if (!BackgroundColor.IsEmpty)
return new SolidBrush(BackgroundColor);
return new LinearGradientBrush(rectangle, System.Drawing.Color.FromArgb(251, 251, 251), System.Drawing.Color.FromArgb(240, 240, 240), LinearGradientMode.Vertical);
}
private void m_control_Paint(object sender, PaintEventArgs e)
{
var graphics = e.Graphics;
var rectangle = new Rectangle(0, 0, MainBoxWidth - 1, MainBoxHeight - 1);
if (ShowFrame)
{
rectangle = m_control.Bounds;
}
using (var backBrush = CreateBackgroundBrush(rectangle))
{
graphics.FillRectangle(backBrush, rectangle);
}
graphics.DrawRectangle(ms_borderPen, rectangle);
graphics.SmoothingMode = SmoothingMode.HighQuality;
string caption = Caption.NonEmpty("Enter filter:");
graphics.DrawString(caption, m_control.Font, GetBestBrushForBackground(System.Drawing.Color.Black, System.Drawing.Color.White), FormPadding.Width, FormPadding.Height);
if (!string.IsNullOrEmpty(HintText))
{
var captionWidth = graphics.MeasureString(caption, m_control.Font).Width;
// Some spacing
captionWidth += 20;
var hintTextWidth = graphics.MeasureString(HintText, m_control.Font).Width;
if (captionWidth + hintTextWidth < m_control.Width)
graphics.DrawString(HintText, m_control.Font, GetBestBrushForBackground(System.Drawing.Color.LightGray, System.Drawing.Color.DarkGray), m_control.Width - FormPadding.Width - hintTextWidth, FormPadding.Height);
}
if (m_image != null)
{
graphics.DrawIcon(m_image, new Rectangle(FormPadding.Width, TextBox.Top, 16, 16));
}
}
private void m_control_Activated(object sender, CancelEventArgs e)
{
e.Cancel = !AcceptSelection();
}
private IEnumerable GetItemsToSearchIn()
{
if (m_items == null)
{
m_items = new List();
foreach (var item in m_originalItems)
{
m_items.Add(new ListBoxItem(item)
{
Text = NameProvider(item),
SortOrder = SortOrderProvider(item),
});
}
}
return m_items;
}
private void textBox_TextChanged(object sender, EventArgs e)
{
ListBox.BeginUpdate();
try
{
ListBox.Items.Clear();
var query = new TextQuery(TextBox.Text);
List selection = new List();
bool onlyItemsOfEmptySearch = string.IsNullOrEmpty(TextBox.Text) && !ShowAllOnEmptyText;
foreach (var item in GetItemsToSearchIn())
{
if (onlyItemsOfEmptySearch)
{
if (!ShowOnEmptySearchProvider(item.Item))
{
continue;
}
}
item.MatchResult = ItemMatcher(item.Item, query);
if (item.IsMatch)
selection.Add(item);
if (selection.Count >= 1000)
break;
}
if (SortItems)
{
selection.Sort();
}
ListBox.Items.AddRange(selection.Cast