WinForms equivalent of WPF's IsHitTestVisible - c#

I have a button override that has a Label as a child. I have MouseEnter and Leave events attached to the button control.
When the mouse enters the label the button's events are nullified (which is natural). My question is how can I disable the label's hit testing without actually disabling the label.
I want it to retain it's color and I want it to be able to change colors (MouseEnter on button for example), but when the mouse is over the label, the hit test to be considered on the button.
P.S: I know I can add Mouse Enter and Leave on the Label and handle those cases but I want the control to be self sufficient such that if the parameters change outside of it (the colors on mouse enter and leave), the control will still function properly.

Ran across this question while looking for other information and don't believe the accepted answer is really correct.
You can extend the label and alter the hittest response in WndProc. Something along these lines:
public class HTTransparentLabel : Label
{
private const int WM_NCHITTEST = 0x84;
private const int HTTRANSPARENT = -1;
protected override void WndProc(ref Message message)
{
if ( message.Msg == (int)WM_NCHITTEST )
message.Result = (IntPtr)HTTRANSPARENT;
else
base.WndProc( ref message );
}
}

The short answer is that you cannot. Both the button and the label are in fact windows, so when the mouse leave one for the other, mouseenter and mouseleave events are generated.
The real question is, why do you need a label on a button?

Related

When Clicking a Textbox, How to Give it Focus but Not Change the Cursor Location Inside It [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 7 years ago.
Improve this question
I created an event handler for the Textbox.Enter event, which selects a part of the text inside the textbox, like this:
TextBox1.Select(3,5);
The result looks like this:
When the Textbox is entered via the Keyboard via [Tab] or [Shift]-[Tab],
the part of the text that needs to be selected is selected well, just like in the screenshot above.
However if the textbox is entered not via the keyboard but via the mouse,
then nothing is selected:
It seems that what happens is this:
When we enter the textbox with the mouse,
the click indeed raises the Enter event, but the mouseclick also sets the location of the cursor, to the location the user clicked with the mouse, inside the textbox.
And this setting of the cursor happens right after the event handler was ran.
So this means, that the selection that the event handler performed (with the TextBox1.Select(3,5); line), is overridden by the mouse's location,
and that's why entering the textbox with the mouse, appears to not select anything.
My question:
How can I make the mouse indeed raise the Enter event, yet not change the cursor position inside the textbox?
So I will then be able to have my selection (that happens in code) remain and not be overridden..
Edit:
The purpose of this is to enable easy selection of the MM:SS part of the time, which is usually the part that is being edited (the HH or mmm parts are rarely changed).
A bit strange request, but if you really want to do so, you can use a well known general method for scheduling an action to be executed later (after normal windows message/event processing) by using the Control.BeginInvoke method.
Something like this
void textBox_Enter(object sender, EventArgs e)
{
BeginInvoke(new Action(() => textBox.Select(3, 5)));
}
Update: As per your comment, if you want to prevent the default mouse down behavior, you need to handle the WM_MOUSEACTIVATE message by creating and using your own TextBox subclass like this
class MyTextBox : TextBox
{
protected override void WndProc(ref Message m)
{
const int WM_LBUTTONDOWN = 0x0201;
const int WM_MOUSEACTIVATE = 0x0021;
const int MA_ACTIVATEANDEAT = 2;
const int MA_NOACTIVATEANDEAT = 4;
if (m.Msg == WM_MOUSEACTIVATE && !Focused)
{
int mouseMsg = unchecked((int)((uint)(int)m.LParam >> 16)); // LOWORD(m.LParam)
if (mouseMsg == WM_LBUTTONDOWN)
{
bool activated = Focus();
m.Result = (IntPtr)(activated ? MA_ACTIVATEANDEAT : MA_NOACTIVATEANDEAT);
return;
}
}
base.WndProc(ref m);
}
}
I would like to suggest another way:
In the beginning of the Control.Enter event handler, I put this:
while( (Control.MouseButtons&MouseButtons.Left)!=0 )
Application.DoEvents();
This simply waits for the mouse Left button to be released..
Only when the mouse Left button is released, then we continue to the next line of code, which is
TextBox1.Select(3,5);
and it then works well - the selection happens, without being overridden.

WinForms disable double clicks and accept all mouse clicks?

How do you get all clicks to go through the event? I noticed if you click too fast it thinks you are double clicking and doesn't send the clicks to the event handler. Is there a way to get all the clicks?
Not that sure why this question got a bounty, the accepted answer ought to be already pretty close to a solution. Except that you ought to use MouseUp instead of MouseDown, your user typically expects a click action to take effect when he releases the button. Which provides the back-out "oops, didn't mean to click it, move the mouse so it gets ignored" option.
Nevertheless, for the built-in Winforms controls, like PictureBox, this is configurable with the Control.SetStyle() method. Add a new class to your project and paste the code shown below. Compile. Drop the new control from the top of the toolbox:
using System;
using System.Windows.Forms;
class MyPictureBox : PictureBox {
public MyPictureBox() {
this.SetStyle(ControlStyles.StandardDoubleClick, false);
}
}
Do beware however that this won't work for the .NET classes that wrap an existing native Windows control. Like TextBox, ListBox, TreeView, etc. The underlying configuration for that is the WNDCLASSEX.style member, CS_DBLCLKS style flag. The code that sets that style flag is baked into Windows and cannot be changed. You'd need different kind of surgery to turn a double-click back into a single click. You can do so by overriding the WndProc() method, I'll give an example for TextBox:
using System;
using System.Windows.Forms;
class MyTextBox : TextBox {
protected override void WndProc(ref Message m) {
// Change WM_LBUTTONDBLCLK to WM_LBUTTONCLICK
if (m.Msg == 0x203) m.Msg = 0x201;
base.WndProc(ref m);
}
}
Just change the class name if you want to do it for other controls. Commandeering Winforms to make it work the way you want never takes much code, just Petzold :)
You want to use the controls MouseDown event instead of the Click event. MouseDown will be called every single time the mouse is "pressed" on that control. Click may not get called if the system thinks it was a double click. A DoubleClick event would be raised instead.
I think you want to count all click.
If you want to count click then set one counter variable and increase it in click event.
May be this help to you....

Is there any way to detect a mouseclick outside a user control?

I'm creating a custom dropdown box, and I want to register when the mouse is clicked outside the dropdown box, in order to hide it. Is it possible to detect a click outside a control? or should I make some mechanism on the containing form and check for mouseclick when any dropdownbox is open?
So I finally understand that you only want it to close when the user clicks outside of it. In that case, the Leave event should work just fine... For some reason, I got the impression you wanted it to close whenever they moved the mouse outside of your custom dropdown. The Leave event is raised whenever your control loses the focus, and if the user clicks on something else, it will certainly lose focus as the thing they clicked on gains the focus.
The documentation also says that this event cascades up and down the control chain as necessary:
The Enter and Leave events are hierarchical and will cascade up and down the parent chain until the appropriate control is reached. For example, assume you have a Form with two GroupBox controls, and each GroupBox control has one TextBox control. When the caret is moved from one TextBox to the other, the Leave event is raised for the TextBox and GroupBox, and the Enter event is raised for the other GroupBox and TextBox.
Overriding your UserControl's OnLeave method is the best way to handle this:
protected override void OnLeave(EventArgs e)
{
// Call the base class
base.OnLeave(e);
// When this control loses the focus, close it
this.Hide();
}
And then for testing purposes, I created a form that shows the drop-down UserControl on command:
public partial class Form1 : Form
{
private UserControl1 customDropDown;
public Form1()
{
InitializeComponent();
// Create the user control
customDropDown = new UserControl1();
// Add it to the form's Controls collection
Controls.Add(customDropDown);
customDropDown.Hide();
}
private void button1_Click(object sender, EventArgs e)
{
// Display the user control
customDropDown.Show();
customDropDown.BringToFront(); // display in front of other controls
customDropDown.Select(); // make sure it gets the focus
}
}
Everything works perfectly with the above code, except for one thing: if the user clicks on a blank area of the form, the UserControl doesn't close. Hmm, why not? Well, because the form itself doesn't want the focus. Only controls can get the focus, and we didn't click on a control. And because nothing else stole the focus, the Leave event never got raised, meaning that the UserControl didn't know it was supposed to close itself.
If you need the UserControl to close itself when the user clicks on a blank area in the form, you need some special case handling for that. Since you say that you're only concerned about clicks, you can just handle the Click event for the form, and set the focus to a different control:
protected override void OnClick(EventArgs e)
{
// Call the base class
base.OnClick(e);
// See if our custom drop-down is visible
if (customDropDown.Visible)
{
// Set the focus to a different control on the form,
// which will force the drop-down to close
this.SelectNextControl(customDropDown, true, true, true, true);
}
}
Yes, this last part feels like a hack. The better solution, as others have mentioned, is to use the SetCapture function to instruct Windows to capture the mouse over your UserControl's window. The control's Capture property provides an even simpler way to do the same thing.
Technically, you'll need to p/invoke SetCapture() in order to receive click events that happen outside of your control.
But in your case, handling the Leave event, as #Martin suggests, should be sufficient.
EDIT: While looking for an usage example for SetCapture(), I came across the Control.Capture property, of which I was not aware. Using that property means you won't have to p/invoke anything, which is always a good thing in my book.
So, you'll have to set Capture to true when showing the dropdown, then determine if the mouse pointer lies inside the control in your click event handler and, if it doesn't, set Capture to false and close the dropdown.
UPDATE:
You can also use the Control.Focused property to determine if the control has got or lost focus when using a keyboard or mouse instead of using the Capture with the same example provided in the MSDN Capture page.
Handle the Form's MouseDown event, or override the Form's OnMouseDown
method:
enter code here
And then:
protected override void OnMouseDown(MouseEventArgs e)
{
if (!theListBox.Bounds.Contains(e.Location))
{
theListBox.Visible = false;
}
}
The Contains method old System.Drawing.Rectangle can be used to indicate if
a point is contained inside a rectangle. The Bounds property of a Control is
the outer Rectangle defined by the edges of the Control. The Location
property of the MouseEventArgs is the Point relative to the Control which
received the MouseDown event. The Bounds property of a Control in a Form is
relative to the Form.
You are probably looking for the leave event:
http://msdn.microsoft.com/en-us/library/system.windows.forms.control.leave.aspx
Leave occurs when the input focus leaves the control.
I just wanted to share this. It is probably not a good way of doing it that way, but looks like it works for drop down panel that closes on fake "MouseLeave", I tried to hide it on Panel MouseLeave but it does not work because moving from panel to button leaves the panel because the button is not the panel itself. Probably there is better way of doing this but I am sharing this because I used about 7 hours figuring out how to get it to work. Thanks to #FTheGodfather
But it works only if the mouse moves on the form. If there is a panel this will not work.
private void click_to_show_Panel_button_MouseDown(object sender, MouseEventArgs e)
{
item_panel1.Visible = true; //Menu Panel
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (!item_panel1.Bounds.Contains(e.Location))
{
item_panel1.Visible = false; // Menu panel
}
}
I've done this myself, and this is how I did it.
When the drop down is opened, register a click event on the control's parent form:
this.Form.Click += new EventHandler(CloseDropDown);
But this only takes you half the way. You probably want your drop down to close also when the current window gets deactivated. The most reliable way of detecting this has for me been through a timer that checks which window is currently active:
[System.Runtime.InteropServices.DllImport("user32.dll")]
static extern IntPtr GetForegroundWindow();
and
var timer = new Timer();
timer.Interval = 100;
timer.Tick += (sender, args) =>
{
IntPtr f = GetForegroundWindow();
if (this.Form == null || f != this.Form.Handle)
{
CloseDropDown();
}
};
You should of course only let the timer run when the drop down is visible. Also, there's probably a few other events on the parent form you'd want to register when the drop down is opened:
this.Form.LocationChanged += new EventHandler(CloseDropDown);
this.Form.SizeChanged += new EventHandler(CloseDropDown);
Just don't forget to unregister all these events in the CloseDropDown method :)
EDIT:
I forgot, you should also register the Leave event on you control to see if another control gets activated/clicked:
this.Leave += new EventHandler(CloseDropDown);
I think I've got it now, this should cover all bases. Let me know if I'm missing something.
If you have Form, you can simply use Deactivate event just like this :
protected override void OnDeactivate(EventArgs e)
{
this.Dispose();
}

UserControl Keyboard Focus

I got a form with a number of buttons on it (assume 20). In the middle, I got a User Control which is completely empty. The question is: how can I make it so that when the User Control is clicked, it will get keyboard focus?
Reason: I paint shapes in that User Control, with my mouse. The shapes are actually other User Controls. What I want to do is be able to use the keyboard to move those shapes. But I cannot seem to correctly be able to grab the Keyboard focus. The Key_Down events just don't reach my main (drawing into) User Control.
So, in other words, how can we have keyboard focus in a control has no focusable items on it? How can one make an keyboard-unfocusable control, catch those events? Any way of grabbing these events window-wide, other than going raw-WIN32 API hardcore?
A UserControl was very much designed to be a container control for other controls. It abhors getting the focus and tries to pass it off first chance it gets. You should not be using a UserControl here, given that you don't put any controls inside of it. A Panel control will suffice. Which has the exact same problem, it doesn't want to get focus either.
Surgery is required to override its behavior. Everything you need is in this answer.
Add this to your user control code to capture keyboard input:
/// <summary>Keys which can generate OnKeyDown event.</summary>
private static readonly Keys[] InputKeys = new []
{ Keys.Left, Keys.Up, Keys.Right, Keys.Down, };
protected override void OnPreviewKeyDown(PreviewKeyDownEventArgs e)
{
if(Array.IndexOf<Keys>(InputKeys, e.KeyCode) != -1)
{
e.IsInputKey = true;
}
base.OnPreviewKeyDown(e);
}
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
// just to illustrate this works
MessageBox.Show("KeyDown: " + e.KeyCode);
}
See http://msdn.microsoft.com/en-us/library/aa969768.aspx. You can assign keyboard focus by
1. Set the usercontrol.Focusable=true;
2. Use Keyboard.Focus(usercontrol).
You need to set the CommandRouting flag on your control to true.
The command routing dependency property is defined in a public API (MS.VS.Editor.dll) and your adornment will want to use that API to indicate that it is handling its commands instead of allowing the containing text view handle them. You can do this with from your control’s initialization.
Microsoft.VisualStudio.Editor.CommandRouting.SetInterceptsCommandRouting(this, true);

How to pass messages from a child user-control to the parent

This is a Windows Forms / .Net C# question.
I have a borderless windows whose transparency key and background color make it completely transparent. Inside the window are a couple of user controls.
I want to be able to move the window. I know how to do this on the parent window, but my problem is that the child controls are the only thing visible and thus the only thing click-able.
The question is: how can I pass certain messages up to the Parent so the Parent can move when the right mouse button is down and the mouse is moving on any one of the child controls?
Or maybe you can suggest another way?
Thanks for the help.
Mark
You can achieve your goal even without SendMessage using System.Windows.Forms.Message class. If you have done dragging I guess you are familiar with WM_NCLBUTTONDOWN message. Send it to you parent from your control's MouseDown event.
Here is an example for moving the form clicking on control label1. Note the first line where sender is used to release the capture from clicked control. This way you can set this handler to all controls intended to move your form.
This is complete code to move the form. Nothing else is needed.
public const int WM_NCLBUTTONDOWN = 0xA1;
public const int HT_CAPTION = 0x2;
private void label1_MouseDown(object sender, MouseEventArgs e)
{
(sender as Control).Capture = false;
Message msg = Message.Create(Handle, WM_NCLBUTTONDOWN, (IntPtr)HT_CAPTION, IntPtr.Zero);
base.WndProc(ref msg);
}
Hope this helps.
I think the easiest way is to add this event to your child controls:
/// <summary>
/// The event that you will throw when the mouse hover the control while being clicked
/// </summary>
public event EventHandler MouseRightClickedAndHoverChildControl;
After, all the parent have to do is to subscribe to those events and make the operations to move the Parent:
ChildControl.MouseRightClickedAndHoverChildControl += OnMouseHoverChildControl;
private void OnMouseHoverChildControl(object sender, EventArgs e)
{
//do foo...
}
You need to call the SendMessage API function to send mouse messages to your parent control.
It would probably be easiest to do this by overriding your control's WndProc method.
I had exactly this question... but came up with a different answer.
If you have the Message in your WndProc, you can just change the handle to your Parent's handle and then pass it along.
I needed to do this in our derived TextBox... TextBox eats scroll wheel events even when its ScrollBars are set to None. I wanted those to propagate on up to the Form. So, I simply put this inside the WndProc for my derived TextBox:
case 0x020A: // WM_MOUSEWHEEL
case 0x020E: // WM_MOUSEHWHEEL
if (this.ScrollBars == ScrollBars.None && this.Parent != null)
m.HWnd = this.Parent.Handle; // forward this to your parent
base.WndProc(ref m);
break;
default:
base.WndProc(ref m);
break;

Categories

Resources