Fixed dimensions of combo box dropdown

This commit is contained in:
Patrick Simpson 2017-06-28 14:47:12 +02:00
parent 2490952c0b
commit 5371e0ee43
5 changed files with 248 additions and 129 deletions

View File

@ -241,9 +241,6 @@
<Compile Include="Controls\KHintButton.cs"> <Compile Include="Controls\KHintButton.cs">
<SubType>Component</SubType> <SubType>Component</SubType>
</Compile> </Compile>
<Compile Include="Controls\KListBox.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Controls\KProgressBar.cs"> <Compile Include="Controls\KProgressBar.cs">
<SubType>Component</SubType> <SubType>Component</SubType>
</Compile> </Compile>

View File

@ -12,7 +12,7 @@ using System.Windows.Forms;
namespace Acacia.Controls namespace Acacia.Controls
{ {
public abstract class KAbstractComboBox : ContainerControl public abstract class KAbstractComboBox : ContainerControl, IMessageFilter
{ {
#region Properties #region Properties
@ -41,6 +41,19 @@ namespace Acacia.Controls
set { _edit.PlaceholderFont = value; } set { _edit.PlaceholderFont = value; }
} }
protected Control DropControl
{
get
{
return _dropControl;
}
set
{
_dropControl = value;
SetupDropDown();
}
}
#endregion #endregion
@ -64,8 +77,28 @@ namespace Acacia.Controls
_edit.TextChanged += _edit_TextChanged; _edit.TextChanged += _edit_TextChanged;
_edit.LostFocus += _edit_LostFocus; _edit.LostFocus += _edit_LostFocus;
_edit.PreviewKeyDown += _edit_PreviewKeyDown; _edit.PreviewKeyDown += _edit_PreviewKeyDown;
_edit.Leave += _edit_Leave;
_edit.Enter += _edit_Enter;
Application.AddMessageFilter(this);
} }
private void _edit_Enter(object sender, EventArgs e)
{
System.Diagnostics.Trace.WriteLine(string.Format("_edit_Enter"));
}
private void _edit_Leave(object sender, EventArgs e)
{
System.Diagnostics.Trace.WriteLine(string.Format("_edit_Leave"));
DroppedDown = false;
}
protected override void OnHandleDestroyed(EventArgs e)
{
Application.RemoveMessageFilter(this);
base.OnHandleDestroyed(e);
}
#endregion #endregion
@ -96,7 +129,7 @@ namespace Acacia.Controls
if (DroppedDown) if (DroppedDown)
{ {
DroppedDown = false; DroppedDown = false;
e.IsInputKey = false; e.IsInputKey = true;
return; return;
} }
break; break;
@ -113,24 +146,10 @@ namespace Acacia.Controls
OnPreviewKeyDown(e); OnPreviewKeyDown(e);
} }
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Winapi)]
internal static extern IntPtr GetFocus();
private Control GetFocusedControl()
{
Control focusedControl = null;
// To get hold of the focused control:
IntPtr focusedHandle = GetFocus();
if (focusedHandle != IntPtr.Zero)
// Note that if the focused Control is not a .Net control, then this will return null.
focusedControl = Control.FromHandle(focusedHandle);
return focusedControl;
}
private void _edit_LostFocus(object sender, EventArgs e) private void _edit_LostFocus(object sender, EventArgs e)
{ {
System.Diagnostics.Trace.WriteLine("_edit_LostFocus: " + GetFocusedControl()?.Name);
DroppedDown = false; DroppedDown = false;
System.Diagnostics.Trace.WriteLine(string.Format("_edit_LostFocus"));
} }
protected override void OnGotFocus(EventArgs e) protected override void OnGotFocus(EventArgs e)
@ -148,33 +167,49 @@ namespace Acacia.Controls
#region Drop down #region Drop down
public Control DropControl private class DropDownRenderer : ToolStripRenderer
{ {
get private readonly KVisualStyle<COMBOBOXPARTS, State>.Part _style;
public DropDownRenderer(KVisualStyle<COMBOBOXPARTS, State>.Part style)
{ {
return _dropControl; this._style = style;
} }
set
protected override void OnRenderToolStripBorder(ToolStripRenderEventArgs e)
{ {
_dropControl = value; _style.DrawBackground(e.Graphics, State.Pressed, e.AffectedBounds);
SetupDropDown();
} }
} }
private ToolStripDropDown _dropDown; private class DropDown : ToolStripDropDown
{
public DropDown(DropDownRenderer renderer)
{
Renderer = renderer;
}
}
private DropDown _dropDown;
private Control _dropControl; private Control _dropControl;
private ToolStripControlHost _dropListHost; private ToolStripControlHost _dropListHost;
private void SetupDropDown() private void SetupDropDown()
{ {
_dropListHost = new ToolStripControlHost(_dropControl); _dropListHost = new ToolStripControlHost(_dropControl);
_dropListHost.Padding = new Padding(0); _dropListHost.Padding = new Padding(0);
_dropListHost.Margin = new Padding(0); _dropListHost.Margin = new Padding(0);
_dropListHost.AutoSize = false; _dropListHost.AutoSize = true;
_dropListHost.GotFocus += (s, e) => System.Diagnostics.Trace.WriteLine("_dropListHost.GotFocus"); _dropListHost.GotFocus += (s, e) => System.Diagnostics.Trace.WriteLine("_dropListHost.GotFocus");
_dropDown = new ToolStripDropDown(); _dropDown = new DropDown(new DropDownRenderer(_style[COMBOBOXPARTS.CP_BORDER]));
_dropDown.Padding = new Padding(0); _dropDown.Padding = new Padding(0);
using (Graphics graphics = CreateGraphics())
{
Padding insets = _style[COMBOBOXPARTS.CP_BORDER]?.GetMargins(graphics, State.Hot) ?? new Padding();
_dropDown.Padding = insets;
}
_dropDown.Margin = new Padding(0); _dropDown.Margin = new Padding(0);
_dropDown.AutoSize = true; _dropDown.AutoSize = true;
_dropDown.DropShadowEnabled = false; _dropDown.DropShadowEnabled = false;
@ -213,20 +248,27 @@ namespace Acacia.Controls
{ {
if (value) if (value)
{ {
// Calculate the height of the control // Calculate the dimensions of the dropdown
int maxHeight = GetDropDownHeightMax(); int maxHeight = GetDropDownHeightMax();
int minHeight = GetDropDownHeightMin(); int minHeight = GetDropDownHeightMin();
Size prefSize = _dropControl.GetPreferredSize(new Size(Width, maxHeight)); //Size prefSize = new Size(minHeight, maxHeight);
_dropControl.Width = Util.Bound(prefSize.Width, Width, Width * 2); Size prefSize = _dropControl.GetPreferredSize(new Size(Width - _dropDown.Padding.Horizontal, maxHeight - _dropDown.Padding.Vertical));
_dropControl.Height = Util.Bound(prefSize.Height, minHeight, maxHeight); int width = Util.Bound(prefSize.Width, Width - _dropDown.Padding.Horizontal, Width * 2);
int height = Util.Bound(prefSize.Height, minHeight, maxHeight);
System.Diagnostics.Trace.WriteLine(string.Format("DROPDOWN1: {0} - {1} - {2}", prefSize, width,
((ListBox)_dropControl).ItemHeight));
_dropControl.MaximumSize = _dropControl.MinimumSize = new Size(width, height);
// Show the drop down below the current control // Show the drop down below the current control
_dropDown.Show(this.PointToScreen(new Point(0, Height))); _dropDown.Show(this.PointToScreen(new Point(0, Height - 1)));
_dropControl.Capture = true; //_dropListHost.Height = _dropDown.Height - _dropDown.Padding.Vertical;
System.Diagnostics.Trace.WriteLine(string.Format("DROPDOWN2: {0} - {1} - {2} - {3}: {4}",
_dropDown.Width, _dropListHost.Width, _dropControl.Width, width, this.Width));
} }
else else
{ {
_dropDown.Close(); _dropDown.Close();
_dropControl.Capture = false;
} }
_isDroppedDown = value; _isDroppedDown = value;
} }
@ -348,5 +390,60 @@ namespace Acacia.Controls
} }
#endregion #endregion
#region Message filtering
public bool PreFilterMessage(ref Message m)
{
switch ((WM)m.Msg)
{
case WM.LBUTTONDOWN:
case WM.RBUTTONDOWN:
case WM.MBUTTONDOWN:
return CheckMouseDown(m, false);
case WM.NCLBUTTONDOWN:
case WM.NCRBUTTONDOWN:
case WM.NCMBUTTONDOWN:
return CheckMouseDown(m, true);
}
return false;
}
private bool CheckMouseDown(Message m, bool nonClient)
{
if (_dropDown.Visible)
{
//
// When a mouse button is pressed, we should determine if it is within the client coordinates
// of the active dropdown. If not, we should dismiss it.
//
int i = unchecked((int)(long)m.LParam);
short x = (short)(i & 0xFFFF);
short y = (short)((i >> 16) & 0xffff);
Point pt = new Point(x, y);
Point ptOrig = pt;
if (!nonClient)
{
// Map to global coordinates
User32.MapWindowPoints(m.HWnd, IntPtr.Zero, ref pt, 1);
}
System.Diagnostics.Trace.WriteLine(string.Format("MOUSE: {0} - {1} - {2}", pt, _dropDown.Bounds, ptOrig));
if (!_dropDown.Bounds.Contains(pt))
{
System.Diagnostics.Trace.WriteLine(string.Format("CONTAINS1"));
User32.MapWindowPoints(m.HWnd, Handle, ref pt, 1);
if (!ClientRectangle.Contains(pt))
{
DroppedDown = false;
return false;
}
}
}
return false;
}
#endregion
} }
} }

View File

@ -14,7 +14,111 @@ namespace Acacia.Controls
{ {
public class KComboBox : KAbstractComboBox public class KComboBox : KAbstractComboBox
{ {
protected readonly KListBox _list; private class KListBox : ListBox
{
private readonly KComboBox _owner;
private int _hoverIndex = -1;
public KListBox(KComboBox owner)
{
this._owner = owner;
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
DrawMode = DrawMode.OwnerDrawFixed;
SetStyle(ControlStyles.Selectable, false);
//ItemHeight = 23;
BorderStyle = BorderStyle.None;
}
protected override void OnMouseMove(MouseEventArgs e)
{
int newIndex = IndexFromPoint(PointToClient(Cursor.Position));
if (newIndex != _hoverIndex)
{
int oldIndex = _hoverIndex;
_hoverIndex = newIndex;
InvalidateItem(oldIndex);
InvalidateItem(_hoverIndex);
if (SelectedIndex != oldIndex && SelectedIndex != _hoverIndex)
InvalidateItem(SelectedIndex);
}
}
protected override void OnMouseLeave(EventArgs e)
{
base.OnMouseLeave(e);
_hoverIndex = -1;
}
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
// Perform the select here.
// TODO: this really is for ComboBox, where the list hides before the event is handled
SelectedIndex = IndexFromPoint(PointToClient(Cursor.Position));
}
protected override void OnVisibleChanged(EventArgs e)
{
base.OnVisibleChanged(e);
_hoverIndex = -1;
}
private void InvalidateItem(int index)
{
if (index < 0 || index >= Items.Count)
return;
Invalidate(GetItemRectangle(index));
}
protected override void OnDrawItem(DrawItemEventArgs e)
{
// Create a custom event instance to be able to set the selected state for mouse hover
DrawItemState state = e.State;
if (_hoverIndex >= 0)
{
state = _hoverIndex == e.Index ? DrawItemState.Selected : DrawItemState.None;
}
DrawItemEventArgs draw = new DrawItemEventArgs(e.Graphics, e.Font, e.Bounds, e.Index, state);
draw.DrawBackground();
string text = Items[draw.Index].ToString();
using (StringFormat format = new StringFormat())
{
format.LineAlignment = StringAlignment.Center;
using (Brush brush = new SolidBrush(draw.ForeColor))
{
draw.Graphics.DrawString(text,
draw.Font, brush,
draw.Bounds,
format);
}
}
}
protected override void DefWndProc(ref Message m)
{
const int WM_MOUSEACTIVATE = 0x21;
const int MA_NOACTIVATE = 0x0003;
switch (m.Msg)
{
// Prevent mouse activity from grabbing the focus away from the edit
case WM_MOUSEACTIVATE:
m.Result = (IntPtr)MA_NOACTIVATE;
return;
}
base.DefWndProc(ref m);
}
public override Size GetPreferredSize(Size proposedSize)
{
Size prefSize = base.GetPreferredSize(proposedSize);
return new Size(prefSize.Width, ItemHeight * _owner.MaxDropDownItems);
}
}
private readonly KListBox _list;
private int _ignoreListEvents;
#region Items properties #region Items properties
@ -45,7 +149,7 @@ namespace Acacia.Controls
public KComboBox() public KComboBox()
{ {
MaxDropDownItems = 8; MaxDropDownItems = 8;
_list = new KListBox(); _list = new KListBox(this);
_list.IntegralHeight = true; _list.IntegralHeight = true;
_list.TabStop = false; _list.TabStop = false;
DropControl = _list; DropControl = _list;
@ -62,7 +166,7 @@ namespace Acacia.Controls
private void _list_SelectedIndexChanged(object sender, EventArgs e) private void _list_SelectedIndexChanged(object sender, EventArgs e)
{ {
if (_list.SelectedIndex >= 0) if (_list.SelectedIndex >= 0 && _ignoreListEvents == 0)
{ {
Text = _list.SelectedItem.ToString(); Text = _list.SelectedItem.ToString();
} }
@ -88,7 +192,16 @@ namespace Acacia.Controls
set set
{ {
_list.BindingContext = new BindingContext(); _list.BindingContext = new BindingContext();
_list.DataSource = value; ++_ignoreListEvents;
try
{
_list.DataSource = value;
_list.SelectedIndex = -1;
}
finally
{
--_ignoreListEvents;
}
} }
} }

View File

@ -1,88 +0,0 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Acacia.Controls
{
public class KListBox : ListBox
{
private int _hoverIndex = -1;
public KListBox()
{
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
DrawMode = DrawMode.OwnerDrawFixed;
SetStyle(ControlStyles.Selectable, true);
}
protected override void OnMouseMove(MouseEventArgs e)
{
int newIndex = IndexFromPoint(PointToClient(Cursor.Position));
if (newIndex != _hoverIndex)
{
int oldIndex = _hoverIndex;
_hoverIndex = newIndex;
InvalidateItem(oldIndex);
InvalidateItem(_hoverIndex);
if (SelectedIndex != oldIndex && SelectedIndex != _hoverIndex)
InvalidateItem(SelectedIndex);
}
}
protected override void OnMouseLeave(EventArgs e)
{
base.OnMouseLeave(e);
_hoverIndex = -1;
}
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
// Perform the select here.
// TODO: this really is for ComboBox, where the list hides before the event is handled
SelectedIndex = IndexFromPoint(PointToClient(Cursor.Position));
}
protected override void OnVisibleChanged(EventArgs e)
{
base.OnVisibleChanged(e);
_hoverIndex = -1;
}
private void InvalidateItem(int index)
{
if (index < 0)
return;
Invalidate(GetItemRectangle(index));
}
protected override void OnDrawItem(DrawItemEventArgs e)
{
// Create a custom event instance to be able to set the selected state for mouse hover
DrawItemState state = e.State;
if (_hoverIndex >= 0)
{
state = _hoverIndex == e.Index ? DrawItemState.Selected : DrawItemState.None;
}
DrawItemEventArgs draw = new DrawItemEventArgs(e.Graphics, e.Font, e.Bounds, e.Index, state);
draw.DrawBackground();
string text = Items[draw.Index].ToString();
using (StringFormat format = new StringFormat())
{
format.LineAlignment = StringAlignment.Center;
using (Brush brush = new SolidBrush(draw.ForeColor))
{
draw.Graphics.DrawString(text,
draw.Font, brush,
draw.Bounds,
format);
}
}
}
}
}

View File

@ -218,7 +218,7 @@ namespace Acacia.UI
BeginUpdate(); BeginUpdate();
DataSource = users; DataSource = users;
//SetItemsCore(users); //SetItemsCore(users);
if (dropDown) if (dropDown && text.Length != 0)
DroppedDown = true; DroppedDown = true;
//Cursor.Current = Cursors.Default; //Cursor.Current = Cursors.Default;
//Text = _lastText; //Text = _lastText;