I have a custom WinForms user control that looks like a combobox but instead opens a ToolStripDropDown that contains another custom user control, called NumericFilterPanel, that has a checkbox, a combobox, and a textbox.
The problem is that when the user click-selects an option for the combobox embedded in the dropdown control, it causes the parent dropdown to hide.
I have set ToolStripDropDown.AutoClose = false, which fixes the original problem, but now I am having difficulty detecting all the situations where the dropdown loses focus, such as when the user clicks on the parent form or switches programs. Sometimes the dropdown remains visible and topmost.
Is there a way to either keep AutoClose = true and prevent the embedded combobox from closing the parent dropdown, or is there a way to always detect when the dropdown has lost focus so I can manually close it?
using System;
using System.Drawing;
using System.Windows.Forms;
namespace mviWinControls
{
public partial class NumericRangeDropDown : UserControl
{
private const int ARROW_HEIGHT = 4;
private Brush arrowBrush = new SolidBrush(Color.FromArgb(77, 97, 133));
private ToolStripDropDown _dropdown;
private ToolStripControlHost _host;
private NumericFilterPanel _filter;
public NumericRangeDropDown()
{
InitializeComponent();
_filter = new NumericFilterPanel();
_filter.DropDown = this;
_host = new ToolStripControlHost(_filter);
_host.Margin = Padding.Empty;
_host.Padding = Padding.Empty;
_dropdown = new ToolStripDropDown();
_dropdown.Margin = Padding.Empty;
_dropdown.Padding = Padding.Empty;
_dropdown.AutoClose = false; // Use this because panel has a combobox. https://social.msdn.microsoft.com/Forums/windows/en-US/dd95b982-820e-4807-8a1f-79c74acab3f8/two-problems-toolstripdropdown?forum=winforms
_dropdown.Items.Add(_host);
_dropdown.Leave += new System.EventHandler(this.DropDown_Leave);
this.Leave += new System.EventHandler(this.DropDown_Leave);
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (components != null) components.Dispose();
if (_dropdown != null) _dropdown.Dispose();
}
base.Dispose(disposing);
}
public override string Text
{
get
{
return base.Text;
}
set
{
base.Text = value;
_filter.SetValue(value);
}
}
protected override void OnPaint(PaintEventArgs e)
{
//base.OnPaint(e);
TextBox _txtDraw = new TextBox();
_txtDraw.Width = this.Width;
using (Bitmap bmp = new Bitmap(_txtDraw.Width, _txtDraw.Height))
{
_txtDraw.DrawToBitmap(bmp, new Rectangle(0, 0, _txtDraw.Width, _txtDraw.Height));
e.Graphics.DrawImage(bmp, 0, 0);
}
StringFormat format = new StringFormat();
format.Alignment = StringAlignment.Near;
format.FormatFlags = StringFormatFlags.NoWrap;
format.LineAlignment = StringAlignment.Center;
using (Brush b = new SolidBrush(this.ForeColor))
e.Graphics.DrawString(this.Text, this.Font, b, this.DisplayRectangle, format);
Point[] arrowPoints = new Point[] { new Point(this.Width - ARROW_HEIGHT * 3 - 2, (this.Height - ARROW_HEIGHT) / 2),
new Point(this.Width - ARROW_HEIGHT + 1 - 2, (this.Height - ARROW_HEIGHT) / 2),
new Point(this.Width - ARROW_HEIGHT * 2 - 2, this.Height - (this.Height - ARROW_HEIGHT) / 2) };
e.Graphics.FillPolygon(arrowBrush, arrowPoints );
}
private void DropDown_Leave(object sender, EventArgs e)
{
HideDropDown();
this.Text = _filter.SummaryText();
}
private void NumericRangeDropDown_Click(object sender, EventArgs e)
{
if (_dropdown.Visible)
HideDropDown();
else
ShowDropDown();
}
public void ShowDropDown()
{
_dropdown.Show(this, new Point(0, this.Height), ToolStripDropDownDirection.Default);
_dropdown.BringToFront();
//_dropdown.Focus();
_filter.Select();
_filter.Focus();
}
public void HideDropDown()
{
_dropdown.Close();
this.Invalidate();
}
}
}
Here's a combobox that can automatically disable and enable the AutoClose property on the host control for you.
Source(I modified it for a combobox versus the DatePicker in their example): http://www.queasy.me/programming/questions/13919634/tool+strip+toolstripdropdownbutton+close+and+lose+window+focus
public partial class CComboBox : ComboBox
{
private bool savedAutoClose;
public CComboBox()
{
InitializeComponent();
}
protected override void OnDropDownClosed(EventArgs e)
{
if (this.Parent != null)
{
var dropDownHost = this.Parent.Parent as ToolStripDropDown; // recursive instead?
if (dropDownHost != null)
dropDownHost.AutoClose = savedAutoClose; // restore the parent's AutoClose preference
}
base.OnDropDownClosed(e);
}
protected override void OnDropDown(EventArgs e)
{
if (this.Parent != null)
{
var dropDownHost = this.Parent.Parent as ToolStripDropDown; // recursive instead?
if (dropDownHost != null)
{
savedAutoClose = dropDownHost.AutoClose;
// ensure that our parent doesn't close while the calendar is open
dropDownHost.AutoClose = false;
}
}
base.OnDropDown(e);
}
}
Having taken a good look at the source code, the bug (and it is a bug) lies in the fact that ToolStripManager, which sets up a message filter to catch mouse-clicks outside the active ToolStrip, checks if the click is within the bounds of a child window.
The problem is that it uses activeToolStrip.ClientRectangle to verify this, and does not check child windows of the window that was clicked. In the case of a ComboBox, the drop-down is a separate child window which floats above everything, and can actually be out of the bounds of the main combo window if the drop-down is large.
The relevant line is:
if (!activeToolStrip.ClientRectangle.Contains(pt.x, pt.y)) {
I have found another solution to temporarily disable the automatic close while the dropdown is open.
Ideally, you are supposed to use a ToolStripComboBox within a ToolStrip rather than just a bare ComboBox. However if you would like to just us a bare one, you can add events to call the relevant private methods to suspend and resume the message filter.
static class ToolStripComboBoxFilter
{
private static Action SuspendMenuMode = (Action) typeof(ToolStripManager)
.GetNestedType("ModalMenuFilter", BindingFlags.NonPublic)
.GetMethod(nameof(SuspendMenuMode), BindingFlags.NonPublic | BindingFlags.Static)
.CreateDelegate(typeof(Action));
private static Action ResumeMenuMode = (Action)typeof(ToolStripManager)
.GetNestedType("ModalMenuFilter", BindingFlags.NonPublic)
.GetMethod(nameof(ResumeMenuMode), BindingFlags.NonPublic | BindingFlags.Static)
.CreateDelegate(typeof(Action));
public static void AddToolStripFilterEvents(this ComboBox combo)
{
combo.DropDown += OnDropDown;
combo.DropDownClosed += OnDropDownClosed;
}
private static void OnDropDown(object sender, EventArgs e)
{
SuspendMenuMode();
}
private static void OnDropDownClosed(object sender, EventArgs e)
{
ResumeMenuMode();
}
}
You can use it like this
myComboBox.AddToolStripFilterEvents();
Related
I have a graph control that plots data points. The data points are plotted as 1 point per pixel. If the number of data points get larger than a certain amount, or the size of the window is increased the performance of the plotting when you move your mouse over the control suffers. If you move quickly the plotting actually stops during the motion.
Is there a way to disable all the messages when the mouse is over that control except for button clicks?
I have not been able to find anything.
Based on your description, I believe that it should be sufficient to filter out MouseMove messages sent to the control. This can be accomplished by having the Form implement IMessageFilter similar to the example presented below. Returning true from IMessageFilter.PreFilterMessage prevents the message from being sent to the control (a panel in the example). A registered filter is active application wide, so it is added/removed when the form actives/deactivates.
public partial class Form1 : Form, IMessageFilter
{
private Panel pnl;
public Form1()
{
InitializeComponent();
pnl = new Panel { Size = new Size(200, 200), Location = new Point(20, 20), BackColor = Color.Aqua };
Controls.Add(pnl);
pnl.Click += panel_Click;
pnl.MouseMove += panel_MouseMove;
pnl.MouseHover += panel_MouseHover;
}
private void panel_MouseHover(sender As Object, e As EventArgs)
{
// this should not occur
throw new NotImplementedException();
}
private void panel_MouseMove(object sender, MouseEventArgs e)
{
// this should not occur
throw new NotImplementedException();
}
private void panel_Click(object sender, EventArgs e)
{
MessageBox.Show("panel clicked");
}
protected override void OnActivated(EventArgs e)
{
base.OnActivated(e);
// install message filter when form activates
Application.AddMessageFilter(this);
}
protected override void OnDeactivate(EventArgs e)
{
base.OnDeactivate(e);
// remove message filter when form deactivates
Application.RemoveMessageFilter(this);
}
bool IMessageFilter.PreFilterMessage(ref Message m)
{
bool handled = false;
if (m.HWnd == pnl.Handle && (WM) m.Msg == WM.MOUSEMOVE)
{
handled = true;
}
return handled;
}
public enum WM : int
{
#region Mouse Messages
MOUSEFIRST = 0x200,
MOUSEMOVE = 0x200,
LBUTTONDOWN = 0x201,
LBUTTONUP = 0x202,
LBUTTONDBLCLK = 0x203,
RBUTTONDOWN = 0x204,
RBUTTONUP = 0x205,
RBUTTONDBLCLK = 0x206,
MBUTTONDOWN = 0x207,
MBUTTONUP = 0x208,
MBUTTONDBLCLK = 0x209,
MOUSELAST = 0x209
#endregion
}
}
Hello I made a custom text box with an associated label.
and I have a custom Form.
when on a form if I drag drop my custom textbox from the toolbox and set it's properties I can see that it works. However what I would like to do is when I'm on my custom control where I have a TableLayoutPanel (with 3 rows)
on row index 1(mid row) I would like to add my custom controls programatically.
when I do this the text label is somewhere else then the textbox.
My Code and the Image to my problem is below:
MyCustomTextBox:
public class MyLbTextBox : TextBox
{
#region CustomProperties
private Label AssociatedLabel = new Label();
private string _myLbText;
public string MyTextLabel
{
get => _myLbText;
set
{
_myLbText = value;
AssociatedLabel.Text = _myLbText ?? _myBindingField;
Size s = TextRenderer.MeasureText(AssociatedLabel.Text, AssociatedLabel.Font);
AssociatedLabel.Location =
new Point(Location.X - s.Width - AssociatedLabel.Padding.Right, Location.Y);
var MyMargin = this.Margin;
MyMargin.Left = 100;
this.Margin = MyMargin;
}
}
#endregion
private string _myBindingField;
public string MyBindingField
{
get { return _myBindingField; }
set
{
_myBindingField = value;
}
}
private MyJoins.MyExpressions _myExpression;
public MyJoins.MyExpressions MyExpression
{
get => _myExpression;
set => _myExpression = value;
}
public MyLbTextBox()
{
_myExpression = MyJoins.MyExpressions.Equals;
ParentChanged += MyLbTextBox_ParentChanged;
LocationChanged += MyLbTextBox_LocationChanged;
Disposed += MyLbTextBox_Disposed;
}
private void MyLbTextBox_Disposed(object sender, EventArgs e)
{
AssociatedLabel.Dispose();
}
private void MyLbTextBox_LocationChanged(object sender, EventArgs e)
{
Size s = TextRenderer.MeasureText(AssociatedLabel.Text, AssociatedLabel.Font);
AssociatedLabel.Location =
new Point(Location.X - s.Width - AssociatedLabel.Padding.Right, Location.Y);
}
private void MyLbTextBox_ParentChanged(object sender, EventArgs e)
{
AutoAddAssociatedLabel();
}
private void AutoAddAssociatedLabel()
{
if (Parent == null) return;
AssociatedLabel.Padding = new Padding(3);
Size s = TextRenderer.MeasureText(AssociatedLabel.Text, AssociatedLabel.Font);
AssociatedLabel.Location =
new Point(Location.X - s.Width - AssociatedLabel.Padding.Right, Location.Y);
Parent.Controls.Add(AssociatedLabel);
}
}
By the way, this is how I add my controls:
after adding my controls through the property grid
this is how I set them on the screen
private void _mySearchFields_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (_mySearchFields == null) return;
foreach (var searchField in _mySearchFields)
{
if (MySearchFieldsPanel.Contains(searchField.MyControl)) continue;
MySearchFieldsPanel.Controls.Add(searchField.MyControl, 1, 0);
}
var myHeight = MySearchFieldsPanel.Controls.Cast<Control>().Sum(variable => variable.Height);
MyBdPanel.RowStyles[1].Height = myHeight + 40;
}
I appreciate any help
This line is a reason of all problems:
Parent.Controls.Add(AssociatedLabel);
This is bad idea if you are creating composite controls (composite = consisting from several real controls). That will cause layout problems you have experienced and more.
Instead consider either:
Utilize UserControl to create composition.
Create custom control (like you do) but without more controls. If you need label - draw it as text in OnPaint while allocating some space: fixed with margin, adjustable with some property or dynamic with measuring text.
I found a strange behavior with VScrollBar (vertical scrollbar available in Visual Studio tool box). The problem is "if I swipe down on the scrollbar, it moves up. If I swipe up, it moves down".
Steps to replicate Bug or behavior - 1
1) Add VScrollBar as a child to any user control.
2) Swipe up or down on the user control (not on scrollbar). Vertical scrollbar moves in opposite direction even if there isn't any programmatical connection between content and VScrollBar
Steps to replicate Bug or behavior - 2
1) Add VScrollBar as a child to any user control.
2) Swipe on scrollbar, it will move up during swipe up and down during swipe down (correct behavior)
3) Swipe up or down on the user control. Vertical scrollbar moves in opposite direction
4) Now swipe up or down on the vertical scrollbar. Vertical scrollbar starts moving in opposite direction (Buggy behavior, happens only after bug no: 1)
Simple control with vertical scrollbar to replicate this behavior
public class QuickViewer : Control
{
public QuickViewer()
{
// Designer generated code
// Copy pasted for illustration alone
this.vScrollBar1 = new System.Windows.Forms.VScrollBar();
this.SuspendLayout();
//
// vScrollBar1
//
this.vScrollBar1.Location = new System.Drawing.Point(420, 4);
this.vScrollBar1.Name = "vScrollBar1";
this.vScrollBar1.Size = new Size(this.vScrollBar1.Width, 292);
//
// QuickViewer
//
this.Controls.Add(this.vScrollBar1);
this.Name = "QuickViewer";
this.Size = new System.Drawing.Size(441, 296);
this.vScrollBar1.Value = 5;
this.ResumeLayout(false);
}
protected override void OnPaint(PaintEventArgs e)
{
//My actual control is different. I prepared a simple control to replicate the buggy behavior of VScrollBar
//Control border
Pen borderPen = new Pen(Color.LawnGreen, 5);
e.Graphics.DrawRectangle(borderPen, ClientRectangle);
borderPen.Dispose();
//View area
Rectangle rect = new Rectangle(ClientRectangle.Location, ClientRectangle.Size);
rect.Inflate(-25, -10);
e.Graphics.FillRectangle(Brushes.White, rect);
e.Graphics.DrawRectangle(Pens.Black, rect);
this.Font = new System.Drawing.Font("Segoe UI", 12, FontStyle.Bold);
StringFormat format = new StringFormat() { Alignment = StringAlignment.Center };
e.Graphics.DrawString("Quick viewer", this.Font, Brushes.Black, rect, format);
string content = "This is a control created to illustrate the bug in VScrollBar." +
"\n Control area refers to the area with white background" +
"\n Control and Vertical Scrollbar are not programatically connected with each other."
+ "But still VScrollBar moves if you swipe on control area";
Font font = new System.Drawing.Font("Segoe UI", 12, FontStyle.Italic);
rect.Y += 20;
e.Graphics.DrawString(content, font, Brushes.Black, rect, format);
font.Dispose();
format.Dispose();
base.OnPaint(e);
}
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
private System.Windows.Forms.VScrollBar vScrollBar1;
}
Question:
Is there any way to overcome this behavior or bug ? I want the scrollbar to move down while swiping down and move up while swiping up. There should not be any scrolling when swiping over the content
I want the scrollbar to move down while swiping down and move up while swiping up.
As per Hans Passants comment its just a system setting (in the form of a registry key):
The answer is actually over at SuperUser:
https://superuser.com/questions/310681/inverting-direction-of-mouse-scroll-wheel
In C# as you wanted:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Microsoft.Win32;
using System.Diagnostics;
using System.Security.Principal;
namespace WindowsFormsApplication1 {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
private Flippable[] flippable;
private void Form1_Load(object sender, EventArgs e) {
WindowsPrincipal pricipal = new WindowsPrincipal(WindowsIdentity.GetCurrent());
bool hasAdministrativeRight = pricipal.IsInRole(WindowsBuiltInRole.Administrator);
if (!hasAdministrativeRight) {
RunElevated(Application.ExecutablePath);
this.Close();
Application.Exit();
}
//probably only want to flip mice.
flippable = getFlippable("hid.mousedevice");
dgv_flippable.DataSource = flippable;
foreach (var col in dgv_flippable.Columns.OfType<DataGridViewCheckBoxColumn>()) {
col.TrueValue = true;
col.FalseValue = false;
col.IndeterminateValue = null;
}
}
private static bool RunElevated(string fileName)
{
//MessageBox.Show("Run: " + fileName);
ProcessStartInfo processInfo = new ProcessStartInfo();
processInfo.UseShellExecute = true;
processInfo.Verb = "runas";
processInfo.FileName = fileName;
try
{
Process.Start(processInfo);
return true;
}
catch (Win32Exception)
{
//Do nothing. Probably the user canceled the UAC window
}
return false;
}
private Flippable[] getFlippable(string filter) {
List<Flippable> flips = new List<Flippable>();
using (RegistryKey hid = Registry.LocalMachine.OpenSubKey(#"SYSTEM\CurrentControlSet\Enum\HID\",false)) {
foreach (string devicekn in hid.GetSubKeyNames()) {
using (RegistryKey device = hid.OpenSubKey(devicekn,false)) {
foreach (string devicekn2 in device.GetSubKeyNames()) {
using (RegistryKey device2 = device.OpenSubKey(devicekn2,false)) {
using (RegistryKey devparam = device2.OpenSubKey("Device Parameters",true)) {
if (devparam != null) {
flips.Add(new Flippable(new string[] { devicekn, devicekn2 }, device2, devparam, tmr_popup));
}
}
}
}
}
}
}
if (filter != null) {
return flips.Where(f=>f.name.Contains(filter)).ToArray();
}
return flips.ToArray();
}
private void dgv_flippable_MouseUp(object sender, MouseEventArgs e) {
dgv_flippable.EndEdit();
}
private void button1_Click(object sender, EventArgs e) {
flippable = getFlippable(null);
dgv_flippable.DataSource = flippable;
}
private void btn_flip_Click(object sender, EventArgs e) {
foreach (var f in flippable) {
f.vertical = true;
f.horizontal = true;
}
dgv_flippable.DataSource = null;
dgv_flippable.DataSource = flippable;
}
private void btn_normal_Click(object sender, EventArgs e) {
foreach (var f in flippable) {
f.vertical = false;
f.horizontal = false;
}
dgv_flippable.DataSource = null;
dgv_flippable.DataSource = flippable;
}
private void tmr_popup_Tick(object sender, EventArgs e) {
tmr_popup.Enabled = false;
notifyIcon1.ShowBalloonTip(99999999);
}
}
public class Flippable {
public Flippable(string[] keyPath, RegistryKey deviceKey, RegistryKey devparam, Timer timer) {
this._keyPath = keyPath;
IEnumerable<bool?> flipValues = Flippable.valueNames
.Select(v => onlyIntBool(devparam.GetValue(v, null)));
this.name = (string)deviceKey.GetValue("DeviceDesc");
this._vertical = flipValues.ElementAt(0);
this._horizontal = flipValues.ElementAt(1);
this._timer = timer;
}
private bool? onlyIntBool(object value) {
try {
return value == null ? null : (bool?)(((int)value) != 0);
} catch {
return null;
}
}
public static string[] valueNames = new string[] { "FlipFlopWheel", "FlipFlopHScroll" };
public string name { get; private set; }
private string[] _keyPath;
private bool? _vertical;
private bool? _horizontal;
Timer _timer;
public bool? vertical { set { flip(Flippable.valueNames[0], value); _vertical = value; } get { return _vertical; } }
public bool? horizontal { set { flip(Flippable.valueNames[1], value); _horizontal = value; } get { return _horizontal; } }
public void flip(string valueName, bool? value) {
using (RegistryKey hid = Registry.LocalMachine.OpenSubKey(#"SYSTEM\CurrentControlSet\Enum\HID\", false)) {
using (RegistryKey device = hid.OpenSubKey(_keyPath[0], false)) {
using (RegistryKey device2 = device.OpenSubKey(_keyPath[1], false)) {
using (RegistryKey devparam = device2.OpenSubKey("Device Parameters", true)) {
if (value == null) {
devparam.DeleteValue(valueName);
} else {
devparam.SetValue(valueName, value == true ? 1 : 0);
_timer.Enabled = true;
}
}
}
}
}
}
}
}
REF: https://github.com/jamie-pate/flipflop-windows-wheel/blob/master/Form1.cs
Disclaimer: normally this question would get closed as a duplicate but because there is a bounty on it and the duplicate is over at SuperUser I've chosen to share that answer here. Full credit to the original author: https://superuser.com/users/108033/richard and https://superuser.com/users/132069/jamie-pate
I think what you want is a ViewPort.
Essentially you put a Control inside a PictureBox. The Control has a larger height than the PictureBox making it a ViewPort.
Before
You'll need to change the form designer code to get the control inside the PictureBox:
'
'PictureBox1
'
Me.PictureBox1.Location = New System.Drawing.Point(96, 87)
Me.PictureBox1.Name = "PictureBox1"
Me.PictureBox1.Size = New System.Drawing.Size(231, 195)
Me.PictureBox1.TabIndex = 0
Me.PictureBox1.TabStop = False
'
'VScrollBar1
'
Me.VScrollBar1.Location = New System.Drawing.Point(330, 88)
Me.VScrollBar1.Name = "VScrollBar1"
Me.VScrollBar1.Size = New System.Drawing.Size(34, 194)
Me.VScrollBar1.TabIndex = 2
'
'TextBox1
'
Me.TextBox1.Location = New System.Drawing.Point(0, 0)
Me.TextBox1.Multiline = True
Me.TextBox1.Name = "TextBox1"
Me.TextBox1.Size = New System.Drawing.Size(211, 251)
Me.TextBox1.TabIndex = 3
'
'Form1
'
Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!)
Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font
Me.ClientSize = New System.Drawing.Size(522, 392)
Me.Controls.Add(Me.VScrollBar1)
Me.Controls.Add(Me.PictureBox1)
'======= THIS IS THE CRITICAL CHANGE =======
PictureBox1.Controls.Add(Me.TextBox1)
After
Then manually place a ScrollBar to the right of the PictureBox and facilitate the behaviour yourself, eg:
//set the VScroll the difference
VScroll.Max = Textbox.Height - PictureBox.Height;
In the VScroll event:
TextBox.Top = -VScroll.Value;
This will save you from mucking around with system settings in order to produce a QuickViewer custom control.
You can add smarts to it like programming PictureBox drag events to set the ScrollBar (and subsequently the inside controls Top). For most inside controls you'll just need to work out the Height which is easy using a for loop, eg:
foreach(var ctrl in PictureBox.Controls) {
// tally up the controls height
...
For inside Textbox controls you can work out the Height based on Fontsize and number of lines. There are plenty of examples online showing how to do that. Since you're doing Textbox's with graphics, eg e.Graphics.DrawString it should be easy enough having the inside control as a innerPictureBox.
To swap the scroll/swipe direction set the VScroll default starting Value to its Max value and set the inside controls Top = VScroll.Value (no minus sign needed)
I am trying to make my custom ComboBox inheriting from ContainerControl. I used this article as a base but rewrote it, but I use a ToolStripControlHost, my own custom ListBox & a ToolStripDropDown.
Now the ComboBox is a button where you click on to show the DropDowncontaining my ListBox, works fine with overriding OnMouseClick.
The problems starts when I try to close the DropDown, with the DropDown's 'AutoClose' property to true, the DropDown closes if you click somewhere outside the DropDown (including the button) ...
protected override void OnMouseClick(MouseEventArgs e)
{
base.OnMouseClick(e);
/* listboxControl = ToolStripDropDown */
if (!listboxControl.Visible)
{
listboxControl.Show(this, GetDropLocation(), ToolStripDropDownDirection.BelowRight);
//listbox.Capture = true;
}
}
This is the code for the click on the button .. so what happens if you click it ?
If the DropDown is shown, it first closes the DropDown, then it fires the OnMouseClick event. Meaning: listboxControl.Visible is already false & it will show the DropDown again. All of this causing a quick close-open.
I have been stuck with this problem for some time now and google doesn't seem to know a lot about this subject (that article on CodeProject has the same bug).
What I have tried is disabling AutoClose and capturing the mouse after I show the DropDown, this works partially but it affects the working of my hosted ListBox. The ListBox contains a set of controls (the items), these items have a hover paint effect. Capturing the mouse in the ListBox control prevents the OnMouseEnter to be fired.
All input would be greatly appreciated !
You need a variable to track the cursor position when the DropDown is closing.
Here is a quick and dirty example control:
public class CustomDropBox : Control {
private ListBox box = new ListBox() { IntegralHeight = false };
private ToolStripControlHost host;
private ToolStripDropDown drop;
private bool wasShowing = false;
public CustomDropBox() {
box.MinimumSize = new Size(120, 120);
box.MouseUp += box_MouseUp;
box.KeyPress += box_KeyPress;
box.Items.AddRange(new string[] { "aaa", "bbb", "ccc" });
host = new ToolStripControlHost(box) { Padding = Padding.Empty };
drop = new ToolStripDropDown() { Padding = Padding.Empty };
drop.Closing += drop_Closing;
drop.Items.Add(host);
}
private Rectangle GetDownRectangle() {
return new Rectangle(this.ClientSize.Width - 16, 0, 16, this.ClientSize.Height);
}
void drop_Closing(object sender, ToolStripDropDownClosingEventArgs e) {
if (e.CloseReason == ToolStripDropDownCloseReason.AppClicked) {
wasShowing = GetDownRectangle().Contains(this.PointToClient(Cursor.Position));
} else {
wasShowing = false;
}
}
void box_KeyPress(object sender, KeyPressEventArgs e) {
if (e.KeyChar == (char)Keys.Enter && box.SelectedIndex > -1) {
drop.Close();
}
}
void box_MouseUp(object sender, MouseEventArgs e) {
int index = box.IndexFromPoint(e.Location);
if (index > -1) {
drop.Close();
}
}
protected override void OnMouseDown(MouseEventArgs e) {
if (e.Button == MouseButtons.Left && GetDownRectangle().Contains(e.Location)) {
if (wasShowing) {
wasShowing = false;
} else {
drop.Show(this, new Point(0, this.Height));
}
}
base.OnMouseDown(e);
}
protected override void OnPaint(PaintEventArgs e) {
e.Graphics.Clear(Color.White);
ControlPaint.DrawComboButton(e.Graphics, GetDownRectangle(), ButtonState.Normal);
base.OnPaint(e);
}
}
I was wondering how could I do this. I know I can use the button component but it has the little gray stuff around it when I give it a image. With image button how could I show another image for the hover effect
You want to create a button with no border but displays different images when the user hovers over it with the mouse? Here's how you can do it:
Add an ImageList control to your form at add two images, one for the button's normal appearance and one for when the mouse is hovering over.
Add your button and set the following properties:
FlatStyle = Flat
FlatAppearance.BorderColor (and maybe MouseOverBackColor & MouseDownBackColor) to your form's background color
ImageList = the ImageList you added to the form
ImageIndex to the index value of your normal image
Code the MouseHover and MouseLeave events for the button like this:
// ImageList index value for the hover image.
private void button1_MouseHover(object sender, EventArgs e) => button1.ImageIndex = 1;
// ImageList index value for the normal image.
private void button1_MouseLeave(object sender, EventArgs e) => button1.ImageIndex = 0;
I believe that will give you the visual effect you're looking for.
Small summary (Border, MouseDownBackColor, MouseOverBackColor)
FlatApperance
BorderColor = Black or what ever you want
BorderSize = can be set to 0
MouseDownBackColor = Transparent
MouseOverBackColor = Transparent
Text = none
For MouseDown:
// ImageList index value for the mouse down image.
private void button1_MouseDown(object sender, MouseEventArgs e) => button1.ImageIndex = 2;
You can assign the BackgroundImage property for the button. You can also use the OnMouseEnter and OnMouseExit events to change the background as per your request.
See BackgroundImage OnMouseEnter OnMouseLeave
I also needed an image button, but I wanted one like the ToolstripMenuButton.
With the correct borders and colors on hover.
So I made a custom control to do just that:
using System;
using System.ComponentModel;
using System.Windows.Forms;
namespace LastenBoekInfrastructure.Controls.Controls
{
[DefaultEvent("Click")]
public class ImageButton : UserControl
{
public string ToolTipText
{
get { return _bButton.ToolTipText; }
set { _bButton.ToolTipText = value; }
}
public bool CheckOnClick
{
get { return _bButton.CheckOnClick; }
set { _bButton.CheckOnClick = value; }
}
public bool DoubleClickEnabled
{
get { return _bButton.DoubleClickEnabled; }
set { _bButton.DoubleClickEnabled = value; }
}
public System.Drawing.Image Image
{
get { return _bButton.Image; }
set { _bButton.Image = value; }
}
public new event EventHandler Click;
public new event EventHandler DoubleClick;
private ToolStrip _tsMain;
private ToolStripButton _bButton;
public ImageButton()
{
InitializeComponent();
}
private void InitializeComponent()
{
var resources = new ComponentResourceManager(typeof(ImageButton));
_tsMain = new ToolStrip();
_bButton = new ToolStripButton();
_tsMain.SuspendLayout();
SuspendLayout();
//
// tsMain
//
_tsMain.BackColor = System.Drawing.Color.Transparent;
_tsMain.CanOverflow = false;
_tsMain.Dock = DockStyle.Fill;
_tsMain.GripMargin = new Padding(0);
_tsMain.GripStyle = ToolStripGripStyle.Hidden;
_tsMain.Items.AddRange(new ToolStripItem[] {
_bButton});
_tsMain.Location = new System.Drawing.Point(0, 0);
_tsMain.Name = "_tsMain";
_tsMain.Size = new System.Drawing.Size(25, 25);
_tsMain.TabIndex = 0;
_tsMain.Renderer = new ImageButtonToolStripSystemRenderer();
//
// bButton
//
_bButton.DisplayStyle = ToolStripItemDisplayStyle.Image;
_bButton.Image = ((System.Drawing.Image)(resources.GetObject("_bButton.Image")));
_bButton.ImageTransparentColor = System.Drawing.Color.Magenta;
_bButton.Name = "_bButton";
_bButton.Size = new System.Drawing.Size(23, 22);
_bButton.Click += bButton_Click;
_bButton.DoubleClick += bButton_DoubleClick;
//
// ImageButton
//
Controls.Add(_tsMain);
Name = "ImageButton";
Size = new System.Drawing.Size(25, 25);
_tsMain.ResumeLayout(false);
_tsMain.PerformLayout();
ResumeLayout(false);
PerformLayout();
}
void bButton_Click(object sender, EventArgs e)
{
if (Click != null)
{
Click(this, e);
}
}
void bButton_DoubleClick(object sender, EventArgs e)
{
if(DoubleClick != null)
{
DoubleClick(this, e);
}
}
public class ImageButtonToolStripSystemRenderer : ToolStripSystemRenderer
{
protected override void OnRenderToolStripBorder(ToolStripRenderEventArgs e)
{
//base.OnRenderToolStripBorder(e);
}
}
}
}