|
I am trying to find a way to paint a selection border around a control whenever it is active at run-time. I would like the border to be as similar as possible to the selection frame that is displayed around a selected control at design-time.
The way I am currently doing this is by hooking the MouseDown event of the control. In my event handler, I figure out what the parent of the control that was clicked is, and then use the parent's graphics region to paint a border around the clicked control using the ControlPaint.DrawSelectionFrame method. This works relatively well, but the selection frame that's drawn is behind any other controls on the graphics surface. This can result in an incomplete focus rectangle being drawn around the selected control if other controls are near by.
I would like to find a way to draw the focus rectangle over top of anything else that happens to be on the graphics region similar to the way the VS forms designer does. The ControlPaint.DrawSelectionFrame method draws the closest thing I have found yet to the designer selection frame, and I would like to continue using it if at all possible.
Can anyone suggest a way to handle this?
Thanks. |
| MigrationUser 1 Tuesday, June 03, 2003 4:59 PM |
Not sure if this will help you, but this is what I did for drawing a border on a control i made at design time.
Use the "OnPaintAdornments" Event
From the MSDN Help: "Called when the control that the designer is managing has painted its surface so the designer can paint any additional adornments on top of the control."
Here's the code I used:
Protected Overrides Sub OnPaintAdornments(ByVal pe As System.Windows.Forms.PaintEventArgs) MyBase.OnPaintAdornments(pe)
Dim Temp As New Pen(Color.Black, 1)
Temp.DashStyle = Drawing2D.DashStyle.Dash
pe.Graphics.DrawRectangle(Temp, 0, 0, Me.Control.Width - Temp.Width, Me.Control.Height - Temp.Width)
Temp.Dispose() End Sub
HTH |
| MigrationUser 1 Sunday, June 08, 2003 4:01 AM |
i have no idea how OnPaintAdornments can be used at runtime, but this is how to draw a selection around any control at runtime, and you need framework 1.1 for that, use the controlpaint.drawreversibleFrame or controlpaint.fillreversiblerectangle. This method take the Screen coordinates, and when called twice will erase itself. Works pretty good. \Now a question of my own. I have a button, which has a property called dragable. It this property is set to true, my user can drag the button at runtime wherever he wants. The problem is that implementing dragging requires us to put code in the mouse_down event, which is fine, but a button will have a click event handler for sure and i cant find at all how to disable this click handler when in drag mode. Example: i have a butt1 which when clicked will display a hello msgbox. What i want is to ignore the click event and handler whenever the butt1.dragable=true. I cannot ignore the WM_LBUTTONDOWN message in WndProc since my dragging which uses the mouse down will not work anymore! i tried control.setstyle(controlstyles.standardclick,false) which supposedly should ignore click event on a control but it does not work too! So now i am stuck with a control, which when i try to drag the message box will popup!
|
| MigrationUser 1 Thursday, June 12, 2003 11:40 AM |
Actually, OnPaintAdornments is available in 1.0. However, you are correct in that it is a design time only sort of thing (or a design "surface," more specifically).
With your custom button, you can accomplish this by Overriding the Protected OnClick method. Something like this will result in the button is being dragged:
Protected Overrides Sub OnClick(ByVal e As EventArgs) If m_CurrentlyDragging = False Then MyBase.OnClick(e) End If End Sub
|
| MigrationUser 1 Thursday, June 12, 2003 2:50 PM |
HumanCompiler needs to learn to read. I thought the original poster wanted it at design time...my bad! :( |
| MigrationUser 1 Thursday, June 12, 2003 2:54 PM |
Yeah, I believe the OnPaintAdornments will only be useful at design-time. To my understanding, a custom control designer will not even be created, and hence called, at run-time. Thanks for the tip on calling DrawReversibleFrame a second time to erase a rectangle. That makes the appearance much nicer.
Now mazz, on to your problem. You don't mention whether or not your button is sub-classed from the Button class. I'm guessing not based on your description.
If you are inheriting from the button class and you implement true .NET framework drag-drop functionality you should be able to override the OnMouseDown method of your button and simply dispose of the call to base.OnMouseDown if the draggable property is true. This will prevent the control from raising any further mouse event messages. In this method you can also call the control's DoDragDrop method to initiate the dragging of the control. The code below demonstrates what I'm talking about.
protected override void OnClick(EventArgs e) { if (this.dragable) { //Don't call base.OnClick method here - this short-circuits further event processing of mouse events for this call.
//Call DoDragDrop method to initiate dragging. this.DoDragDrop(....); } else { //Not in design mode, so call base class to process mouse event as usual. base.OnClick(e); } }
If you are not inheriting from the Button class then things get a little harder. The only way I have found to stop controls from processing their events is with a message filter added to the application's message pump.
The one problem with this approach is that it receives notification of all windows messages for any form in your application which, given your scenario, probably isn't ideal. You'll be receiving way more messages than you want, but at least you'll get the messages you need. Just keep the code as efficient as possible to prevent deteriorating the speed of your application. Once you are done with the filter, you should also remove it.
To filter messages from your application you will need to create a class that implements the IMessageFilter interface. This interface only has one method called PreFilterMessage. In this method you can examine the type of message being generated and the window (control) that generated it. If you return true from this method, it prevents the message from being processed further. If you return false, windows will continue processing the message as usual. The code below demonstrates how to do all this.
public class DesignTimeMessageFilter : IMessageFilter { //Windows Message Constants from winuser.h private const int WM_MOUSEACTIVATE = 0x0021; private const int WM_LBUTTONDOWN = 0x0201; private const int WM_LBUTTONUP = 0x0202; private const int WM_LBUTTONDBLCLK = 0x0203; private const int WM_RBUTTONDOWN = 0x0204; private const int WM_RBUTTONUP = 0x0205; private const int WM_RBUTTONDBLCLK = 0x0206; private const int WM_MBUTTONDOWN = 0x0207; private const int WM_MBUTTONUP = 0x0208; private const int WM_MBUTTONDBLCLK = 0x0209; private const int WM_MOUSEWHEEL = 0x020A; private const int WM_XBUTTONDOWN = 0x020B; private const int WM_XBUTTONUP = 0x020C; private const int WM_XBUTTONDBLCLK = 0x020D;
private const int XBUTTON1 = 0x0001; private const int XBUTTON2 = 0x0002;
public DesignTimeMessageFilter() { }
public bool PreFilterMessage(ref Message m) { bool retVal = false;
//Get reference to the window (control) that is raising this event. Control target = Control.FromChildHandle(m.HWnd);
if (target != null) { switch (m.Msg) { case WM_LBUTTONDOWN : { //Arrest the mouse down event to prevent the control from acting on it. retVal = true; break; } } } return retVal; } }
You can insert additional code to see if the control calling this method is one that you are interested in (Remember, you're going to get calls for all windows in your application using this approach). To insert the message filter defined above into the message pump use the following...
//Add message filter to message pump. Application.AddMessageFilter(new DesignTimeMessageFilter());
If there are other messages you want to capture, you'll need the windows header files (primarily winuser.h). You can get this file and documentation on the windows messages in the Core Platform SDK available from Microsoft. Here's a link...
http://www.microsoft.com/msdownload/platformsdk/sdkupdate/
There may be other ways to do this, but this is the only one I've had success implementing.
|
| MigrationUser 1 Thursday, June 12, 2003 3:18 PM |
thnks, right after i wrote the message i realised that just overriding the click event is what i needed. If i am in drag mode i just return from the click event.
I have however a small maybe stupid question: In my application i have my main form, which has an active form (the one we are working on) and another form which is the property from that should display (at runtime) some properties of the control that have the focus. Is there an easy way to populate this property form whenever a control on the active form gets focus? or is clicked for example? ie in my property form i tried the following:
For Each ctl In Activeform.controls AddHandler ctl.Click, AddressOf testing Next
Private Sub testing(ByVal sender As Object, ByVal e As EventArgs) MsgBox("hey from :" & sender.GetType.ToString) End Sub
now whenever i click on any control in my active form, click event is handled by my property form correctly (which can capture now the control and get all the properties needed)
This works great but remember that i override the click event in case i am in design mode! so this will not work! Is there some other event i can handle? or is there maybe an easier way to do this whole property form that i am missing? |
| MigrationUser 1 Friday, June 13, 2003 10:50 AM |
mazz, I have a suggestion that will work. If you have sub-classed the form that the controls you will design are sited on then you can add a new event in the form class called say, DesignerActiveControlChanged. You can then add a public method to the form class called say, RaiseDesignerActiveControlChanged which will simply call the form's protected OnDesignerActiveControlChanged method that you will create during the process of defining the new event.
In the click event handler that you are already hooking for the form controls, you can call the form's RaiseDesignerActiveControlChanged method which, in turn, will raise the DesignerActivateControlChanged event. Your designer form can then simply hook this single event to know when the active control on the form has changed. In the controls you have sub-classed where you dispose of the Click event, you can still call the form's RaiseDesignerActiveControlChanged method so the property grid on your designer form will know to show the properties of the newly activated control. Of course, in cases where you dispose of the Click event, the control isn't really active, but the designer won't know or care about this. It simply needs to know which control to show the properties for.
Now, if you don't have a sub-classed form or you don't want to go that route, you could create a new class that exposes the DesignerActiveControlChanged event and simply have all the controls call it instead. Then you just need to hook your designer grid into this event.
I like the event approach because it isolates the designer form's logic from the form being designed. If you decide later to revise the way controls become active on the designed form, you won't need to change any code in the designer. |
| MigrationUser 1 Friday, June 13, 2003 11:38 AM |
argg.. i am not subclassing a form and i hate the second method! What i will do is, and since all my controls in my application are custom subclassed controls (since they could be dragged and resized at runtime) i will just add to each control an event called ControlSelected and i will just do the following:
Protected Overrides Sub OnClick(ByVal e As System.EventArgs) RaiseEvent ControlSelected(Me) If MyDesignMode Then Return Else MyBase.OnClick(e) End If End Sub
this way i will get rid of the click event in desing mode and at the same time whenever i click a control the controlselected can be raised. The upside is that minimal code is added to the control, nothing is added to the form the control are sited on. The downside is that in my property form i have to cast the control properly to their type..or better yet put the event in an interface and just cast them to this interface when adding handler (not sure if this will work) right now i am stuck to doing, in my property form init:
For Each ctl In activeform.controls If TypeOf ctl Is .MyButton Then AddHandler CType(ctl, MyButton).ControlSelected, AddressOf testing End If if typeof ctl is ..... if typeof ctl is ..... Next
however i can probably solve that by doing: For Each ctl In activeform.controls If TypeOf ctl Is .MyButton Then AddHandler CType(ctl, ISomething).ControlSelected, AddressOf testing End If Next
|
| MigrationUser 1 Friday, June 13, 2003 2:41 PM |
err any idea how do i know when a user click outside a panel?
The reason is when i click anywhere in a panel, i draw a selector around a panel using the drawreverserectangle function, now for any other control i draw this selector on the gotfocus event and redraw it (ie erase it) on the lostfocus event. Apparently a panel (and i think groupbox and any container) does not get the onfocus and lostfocus event... they never get fired. Any idea how i can know when the panel has looses focus (ie a user click on something outside the panel or on a control inside the panel?) any event that i am missing?
|
| MigrationUser 1 Tuesday, June 17, 2003 9:51 AM |
How about OnEnter and OnLeave ? I think the GotFocus and LostFocus events are being deprecated so they may not be fully supported. I know that the docs recommend using Enter and Leave. |
| MigrationUser 1 Tuesday, June 17, 2003 10:48 AM |
these events won't work too, When you click anywhere in a panel the enter event does not fire, when you cick outside the panel the leave event does not fire too (try it)
I am looking most probably for the right window message that will do the following: When i click anywhere in the panel i need to know this (this is fine with the onclick event) when i click anywhere OUTSIDE the panel i need to know this too in my panel. There should be some kind of window message that i can intercept in my wndproc to know when a mouse is clicked anywhere outside the panel. |
| MigrationUser 1 Tuesday, June 17, 2003 11:14 AM |
Yeah, I don't know what I was thinking... Since a panel isn't a "selectable" control, those events won't fire.
You could do something kind of slimy, I suppose. I am think something that looks like this:
Public Class MyPanel Inherits Panel
Protected Overrides Sub OnClick(ByVal e As System.EventArgs) MyBase.OnClick(e) 'Custom painting/selection code here. Dim c As Control For Each c In CType(Me.TopLevelControl, Form).Controls If Not c Is Me Then 'you will need to accommodate for nested controls. Probably a recursive routine will do the trick AddHandler c.Click, AddressOf Junk End If Next End Sub
Private Sub Junk(ByVal sender As Object, ByVal e As EventArgs) 'Clear Selection Here. Debug.WriteLine("Junk") Dim c As Control For Each c In CType(Me.TopLevelControl, Form).Controls If Not c Is Me Then 'you will need to accommodate for nested controls. Probably a recursive routine will do the trick RemoveHandler c.Click, AddressOf Junk End If Next End Sub End Class
|
| MigrationUser 1 Tuesday, June 17, 2003 1:20 PM |
lol that is really a nice workaround!
I am going to search around or wait for another cleaner solution response, but if all else fails ill use this one. Thnks |
| MigrationUser 1 Tuesday, June 17, 2003 1:47 PM |
Why don't you use the focus command, when the mouse enteres/leaves the control check the focus instead. |
| MigrationUser 1 Sunday, June 05, 2005 10:48 PM |
mazz - Why don't you just check the value of dragable in your Click event handler and either execute or don't execute your code depending on its value. |
| MigrationUser 1 Monday, June 06, 2005 12:15 AM |
I need Design-time functionality at run-time. I drag a database field name form the treeview control. Thch changes into an approprait control. I want to resize that control, move it. But starts to be handled like the contrl behaves at run time, where as I want the functionality of design time. Please tell me how to do that. |
| MigrationUser 1 Saturday, June 18, 2005 1:18 AM |
Why not just implement a host designer?
There are a few articles in the articles section of this site for hosting forms.
Though I have done a little of this before I got deep into hosted forms.
http://www.planet-source-code.com/vb/scripts/ShowCode.asp?txtCodeId=1181&lngWId=10 |
| MigrationUser 1 Saturday, June 25, 2005 9:04 AM |
Hello,
I faced the same problem. I solved it by not using the parent control handle as the graphic for ControlPaint to draw on. Instead I created a transparent panel on top of everything, which is then used for drawing. Works smoothly :)
Here is the code for the UserControl used as the topmost transparent draw layer:
public partial class TopLayer : UserControl { public TopLayer() { InitializeComponent(); }
protected override void OnPaint(PaintEventArgs e) { SetStyle(ControlStyles.Opaque, true); SetStyle(ControlStyles.SupportsTransparentBackColor, true); BackColor = Color.FromArgb(20,Color.White); }
protected override CreateParams CreateParams { get { CreateParams cp = base.CreateParams; cp.ExStyle |= 0x00000020; return cp; } } } |
| Aietes Sunday, February 26, 2006 10:59 PM |