|
Ihave a custom control consisting of a few picture controls and some labels. What I would like to do is highlight the border of the control when the mouse enters and unhighlight it when the mouse leaves. The problem is that the MouseEnter eventfor the control only fires if the mouse enters the control on the control's background. If it enters the custom control on an edge where one of the picture boxes is, it fires the pic's MouseEnter, but never the controls. I thought if the pic didn't handle the event then it would bubble out to the control, but its not firing.
One workaround I thought of would be to have the pic's MouseEnter event explicitly fire the custom control's event, but then I would have to figure out if the mouse is entering from outside the custom control or just moving from one pic control to another inside the custom control. Seems like it should be eaiser.
I do not have the pic and label controls within a container, they are just on the custom control's design surface.
Any ideas? Thanks.
ck | | CrowKing Saturday, March 07, 2009 3:54 AM | Hi CrowKing,
My suggestion is to handle the child controls' MouseEnter and MouseLeave events from the custom control. Thus when the MouseEnter and MouseLeave events of the child controls occur, the custom control will get notified.
In the event handler, check whether the cursor is within the client rectangle of the custom control or not.If yes,highlight the border ofthe custom control.
The following is a sample. It assumes thatthere're twoPictureBox controlsand two Label controls on thecustomcontrol.
| PublicClassUserControl1 |
|
| DimisEnterAsBoolean=False |
| PrivateSubUserControl1_Load(ByValsenderAsSystem.Object,ByValeAsSystem.EventArgs)HandlesMyBase.Load |
| AddHandlerMe.MouseEnter,AddressOfProcessMouseEvent |
| AddHandlerMe.PictureBox1.MouseEnter,AddressOfProcessMouseEvent |
| AddHandlerMe.Label1.MouseEnter,AddressOfProcessMouseEvent |
| AddHandlerMe.PictureBox2.MouseEnter,AddressOfProcessMouseEvent |
| AddHandlerMe.Label2.MouseEnter,AddressOfProcessMouseEvent |
|
| AddHandlerMe.MouseLeave,AddressOfProcessMouseEvent |
| AddHandlerMe.PictureBox1.MouseLeave,AddressOfProcessMouseEvent |
| AddHandlerMe.Label1.MouseLeave,AddressOfProcessMouseEvent |
| AddHandlerMe.PictureBox2.MouseLeave,AddressOfProcessMouseEvent |
| AddHandlerMe.Label2.MouseLeave,AddressOfProcessMouseEvent |
| EndSub |
|
| PrivateSubProcessMouseEvent(ByValsenderAsObject,ByValeAsEventArgs) |
| If(Me.ClientRectangle.Contains(Me.PointToClient(MousePosition)))Then |
| If(NotisEnter)Then |
| isEnter=True |
| Console.WriteLine("isenter:true") |
| Me.Invalidate() |
| EndIf |
| Else |
| If(isEnter)Then |
| isEnter=False |
| Console.WriteLine("isenter:false") |
| Me.Invalidate() |
| EndIf |
| EndIf |
| EndSub |
|
| PrivateSubUserControl1_Paint(ByValsenderAsObject,ByValeAsSystem.Windows.Forms.PaintEventArgs)HandlesMe.Paint |
| DimrectAsRectangle=e.ClipRectangle |
| rect.Inflate(-2,-2) |
| If(isEnter)Then |
| e.Graphics.DrawRectangle(Pens.Red,rect) |
| Else |
|
| e.Graphics.DrawRectangle(NewPen(BackColor),rect) |
| EndIf |
| EndSub |
| EndClass |
|
The sample code works fine on my side. Pleasetry itin your project to see if it's what you want.
Sincerely, Linda Liu - Marked As Answer byCrowKing Wednesday, March 11, 2009 11:29 PM
-
| | Linda Liu Wednesday, March 11, 2009 11:10 AM | Thanks for the response. It sounds like VS timers are light controls then? I tried something and it seems to work. I created a class level counter that monitors the number of MouseEnter and MouseLeave events within the control. If the MouseEnter= MouseLeave + 1, the control is being entered. If MouseEnter = MouseLeave then the control is being exited. Are there any conditions where you think this wouldn't work (ie an overlapping window or something else that would not fire the mouse events)? I noticed that if I use this as a trigger to change the border style for the user control, it opens tiny gaps beween the constituent controls when the border is changed to something other that what it was when the form was designed. This causesmouse events to fire when the gaps are passed over or hovered on. The solution still works, but it causes the control to go a bit whacky with a lot of repaints. I just changed the effect for mouse enter/leave to change a color for the control's heading, but thought it worth mentioning in case someone else comes along looking for the answer to this question. Code for the class level struct and tracking variable:
| PrivateStructureMouseCount |
| DimenteredAsInteger |
| DimexitedAsInteger |
| EndStructure |
|
| Privatem_MousecountAsMouseCount | Code for Enter/Leave mouse events for each control:
| PrivateSubcompLeft_MouseEnter(ByValsenderAsObject,ByValeAsSystem.EventArgs)HandlescompLeft.MouseEnter |
| UpdateMouseCount(True) |
| EndSub |
|
| PrivateSubcompLeft_MouseLeave(ByValsenderAsObject,ByValeAsSystem.EventArgs)HandlescompLeft.MouseLeave |
| UpdateMouseCount(False) |
| EndSub | Code for monitoring Enter/Leave counts:
| PrivateSubUpdateMouseCount(ByValenteredAsBoolean) |
| IfenteredThen |
| m_Mousecount.entered+=1 |
| Else |
| m_Mousecount.exited+=1 |
| EndIf |
|
| Console.WriteLine("Entered:"&m_Mousecount.entered.ToString&"/Exited:"&m_Mousecount.exited.ToString) |
|
| Ifm_Mousecount.entered=(m_Mousecount.exited+1)Then |
| 'enteringcontrol |
| head.BackColor=Color.Blue |
|
| ElseIfm_Mousecount.entered=m_Mousecount.exitedThen |
| 'leavingcontrol |
| head.BackColor=Color.LightBlue |
| Else |
| 'withincontrol |
|
| EndIf |
| EndSub | ck - Marked As Answer byCrowKing Wednesday, March 11, 2009 11:29 PM
-
| | CrowKing Saturday, March 07, 2009 5:09 PM | Have you tried disabling the picturebox on the control
UserControl11.PictureBox1.Enabled = False
Disabling a conrol also disables the mouse events with the control.
Asgar | | _asgar Saturday, March 07, 2009 7:34 AM | Yes, getting this right is very difficult. The crude but effective way is to use a timer and find out where the mouse is located. For example:
public partial class UserControl1 : UserControl { Timer mTimer; bool mHasMouse;
public UserControl1() { InitializeComponent(); } protected override void OnLoad(EventArgs e) { if (!DesignMode) { mTimer = new Timer(); mTimer.Interval = 250; mTimer.Tick += new EventHandler(mTimer_Tick); mTimer.Enabled = true; } base.OnLoad(e); } void mTimer_Tick(object sender, EventArgs e) { Point pos = this.PointToClient(Control.MousePosition); bool hasMouse = this.ClientRectangle.Contains(pos); if (hasMouse != mHasMouse) { mHasMouse = hasMouse; Invalidate(); } } protected override void OnPaint(PaintEventArgs e) { if (mHasMouse) { Rectangle rc = new Rectangle(0, 0, this.ClientSize.Width, this.ClientSize.Height); ControlPaint.DrawBorder3D(e.Graphics, rc); } base.OnPaint(e); }
}
Hans Passant. | | nobugz Saturday, March 07, 2009 1:28 PM | Thanks for the replies. I havea follow up question, but first a quick background.
I can potentially have a large number of these controls instantiated at run time and I'm trying to keep the processing overhead as low as possible. Each contol represents a database action that can be:
a) a join between two tables b) an aggregrate query on a single table c) a join between a table and the results ofanother instance of this control placed in a temp table (or passed through as SQL) d) a join between two instances ofthis controlas a temp table (or passed through as SQL)
Each control will also run on a thread separate from the UI thread when it executes its database action based on interdependancies and SQL hueristics. Given this, my question is:
Would it be better to implement the timer option above or to leave some space around the outside of the contained controls so the custom control's MouseEnter event isfired? I've tested leaving a gap around the outside of the contained controls and it looks like a mouse hover in this area fires the MouseEnter for the custom control multiple times. This has led me to consider placing a rectangle over the custom control's entire design surface as a background and using that to register the MouseEnter event (assuming rectangles have MouseEnter events), but I don't know if this will also result in multiple firings of the MouseEnter for the rectangle.
I'm also considering building this control from scratch but my skill level with building custom control's isn't quite there yet. I've ordered Pro .NET 2.0 Windows Forms and Custom Controls in VB 2005 and hopefully that will help me out, but in the meantime I'm creating this control form existing controlsso development can continue to move along.
Thanks in advance.
ck | | CrowKing Saturday, March 07, 2009 3:56 PM | There isn't much point in worrying about perf for a timer when you have "a large number of controls". Controls are very expensive objects, painting will start to get noticeably slow when you have 50 of them in a form. Leverage the OnPaint method wherever you can. Hans Passant. | | nobugz Saturday, March 07, 2009 4:17 PM | Thanks for the response. It sounds like VS timers are light controls then? I tried something and it seems to work. I created a class level counter that monitors the number of MouseEnter and MouseLeave events within the control. If the MouseEnter= MouseLeave + 1, the control is being entered. If MouseEnter = MouseLeave then the control is being exited. Are there any conditions where you think this wouldn't work (ie an overlapping window or something else that would not fire the mouse events)? I noticed that if I use this as a trigger to change the border style for the user control, it opens tiny gaps beween the constituent controls when the border is changed to something other that what it was when the form was designed. This causesmouse events to fire when the gaps are passed over or hovered on. The solution still works, but it causes the control to go a bit whacky with a lot of repaints. I just changed the effect for mouse enter/leave to change a color for the control's heading, but thought it worth mentioning in case someone else comes along looking for the answer to this question. Code for the class level struct and tracking variable:
| PrivateStructureMouseCount |
| DimenteredAsInteger |
| DimexitedAsInteger |
| EndStructure |
|
| Privatem_MousecountAsMouseCount | Code for Enter/Leave mouse events for each control:
| PrivateSubcompLeft_MouseEnter(ByValsenderAsObject,ByValeAsSystem.EventArgs)HandlescompLeft.MouseEnter |
| UpdateMouseCount(True) |
| EndSub |
|
| PrivateSubcompLeft_MouseLeave(ByValsenderAsObject,ByValeAsSystem.EventArgs)HandlescompLeft.MouseLeave |
| UpdateMouseCount(False) |
| EndSub | Code for monitoring Enter/Leave counts:
| PrivateSubUpdateMouseCount(ByValenteredAsBoolean) |
| IfenteredThen |
| m_Mousecount.entered+=1 |
| Else |
| m_Mousecount.exited+=1 |
| EndIf |
|
| Console.WriteLine("Entered:"&m_Mousecount.entered.ToString&"/Exited:"&m_Mousecount.exited.ToString) |
|
| Ifm_Mousecount.entered=(m_Mousecount.exited+1)Then |
| 'enteringcontrol |
| head.BackColor=Color.Blue |
|
| ElseIfm_Mousecount.entered=m_Mousecount.exitedThen |
| 'leavingcontrol |
| head.BackColor=Color.LightBlue |
| Else |
| 'withincontrol |
|
| EndIf |
| EndSub | ck - Marked As Answer byCrowKing Wednesday, March 11, 2009 11:29 PM
-
| | CrowKing Saturday, March 07, 2009 5:09 PM | Hi CrowKing,
My suggestion is to handle the child controls' MouseEnter and MouseLeave events from the custom control. Thus when the MouseEnter and MouseLeave events of the child controls occur, the custom control will get notified.
In the event handler, check whether the cursor is within the client rectangle of the custom control or not.If yes,highlight the border ofthe custom control.
The following is a sample. It assumes thatthere're twoPictureBox controlsand two Label controls on thecustomcontrol.
| PublicClassUserControl1 |
|
| DimisEnterAsBoolean=False |
| PrivateSubUserControl1_Load(ByValsenderAsSystem.Object,ByValeAsSystem.EventArgs)HandlesMyBase.Load |
| AddHandlerMe.MouseEnter,AddressOfProcessMouseEvent |
| AddHandlerMe.PictureBox1.MouseEnter,AddressOfProcessMouseEvent |
| AddHandlerMe.Label1.MouseEnter,AddressOfProcessMouseEvent |
| AddHandlerMe.PictureBox2.MouseEnter,AddressOfProcessMouseEvent |
| AddHandlerMe.Label2.MouseEnter,AddressOfProcessMouseEvent |
|
| AddHandlerMe.MouseLeave,AddressOfProcessMouseEvent |
| AddHandlerMe.PictureBox1.MouseLeave,AddressOfProcessMouseEvent |
| AddHandlerMe.Label1.MouseLeave,AddressOfProcessMouseEvent |
| AddHandlerMe.PictureBox2.MouseLeave,AddressOfProcessMouseEvent |
| AddHandlerMe.Label2.MouseLeave,AddressOfProcessMouseEvent |
| EndSub |
|
| PrivateSubProcessMouseEvent(ByValsenderAsObject,ByValeAsEventArgs) |
| If(Me.ClientRectangle.Contains(Me.PointToClient(MousePosition)))Then |
| If(NotisEnter)Then |
| isEnter=True |
| Console.WriteLine("isenter:true") |
| Me.Invalidate() |
| EndIf |
| Else |
| If(isEnter)Then |
| isEnter=False |
| Console.WriteLine("isenter:false") |
| Me.Invalidate() |
| EndIf |
| EndIf |
| EndSub |
|
| PrivateSubUserControl1_Paint(ByValsenderAsObject,ByValeAsSystem.Windows.Forms.PaintEventArgs)HandlesMe.Paint |
| DimrectAsRectangle=e.ClipRectangle |
| rect.Inflate(-2,-2) |
| If(isEnter)Then |
| e.Graphics.DrawRectangle(Pens.Red,rect) |
| Else |
|
| e.Graphics.DrawRectangle(NewPen(BackColor),rect) |
| EndIf |
| EndSub |
| EndClass |
|
The sample code works fine on my side. Pleasetry itin your project to see if it's what you want.
Sincerely, Linda Liu - Marked As Answer byCrowKing Wednesday, March 11, 2009 11:29 PM
-
| | Linda Liu Wednesday, March 11, 2009 11:10 AM | Hi Linda,
Thanks for the suggestion. I tested it after removing the console calls and it missed some of the mouse events. The solution I have in place seems to work Ok, but I appreciate the input.
ck | | CrowKing Wednesday, March 11, 2009 11:27 PM |
|