How do you draw a control?
I'm not talking about UserControl/Custom Control (or am I?), but I am talking about using GDI to draw my own custom shape, and give it properties and events, like myNotSoStraightThickLine.Clicked, or myNotSoStraightThickLine.Color, etc.
How do you make things that you've drawn with GDI+ clickable, selectable, movable, with events and properties that other controls inherit, as well as making it Disposable whenever needed?
You'll have to draw each thing that you want, and capture mouse move events and mouse click events to determine whether you have clicked a part of each element. Which will get tricky if you are dealing with diagonals and ellipses, you may have to determine a full list of possible x + y coordinates (on a per pixel basis) for each of your elements possible positions on creation and compare those on the mouse move/click events.
And as for recording colours, you could get the properties of the PictureBox content at the location of your move/click event, or perhaps it would be better to same some objects with a mirror of the properties of the controls that you are drawing, that way you could actually create your objects and then take their properties for drawing them, which could be simpler.
Related
I am facing a problem that have kind of already been asked here before, as far as I have seen.
I read most of these but didn't find the right answer.
This is what I need:
Complex shaped zone (i.e: countries, states..)
Having possibility to get events on it (especially mouse's)
Possibility to place some conrols in it (i.e: images, buttons)
I saw that some uses usercontrols, controltemplates.. But how should I do for that kind of shape ? Most of questions was for some basic mix of standard shapes.
(I saw with Blend that we can make path object with a pen, is there a way to use this to define the shape of a zone ?)
Thanks.
Create your own control and have its main container be a Canvas. Have the canvas background be transparent and then you can place all the controls you want on it. You can use a Polygon as your main shape inside the Canvas and place all the controls you want on the Canvas (but I guess for your purpose, you would want to make sure to not place them outside of the area covered by the polygon.
Let each object you use handle the mousedown event then you can individually drag them. When mousedown occurs, use the CaptureMouse() method on the object you clicked so that all dragging (on or off of the main window) will still be captured by the object you clicked. Make sure to call ReleaseMouse() in the mouseup event.
You can create as many instances of your control as you need. All with different shapes defined by the polygon each one contains. You can slap all of these objects on a single grid or canvas, and you're good to go.
You can use the PathPanel class that is provided in the Expression Blend SDK.
More information here
I want to change the colour of certain parts of a control which is using GDI+ to draw itself, like when certain objects are hovered/clicked, for example.
How can I re-draw only the necessary parts? It seems bad to re-draw thousands of pixels when I only need to make a change to a few of them.
To be specific, I have drawn anywhere from 1 to 128 rectangles, and I will need to re-draw anywhere from 1 to 128 rectangles in various events. I don't want to re-draw 128 rectangles in order to make a change to only 1-127.
I have read that it is a bad idea to use drawing code in places which are not the on paint event. But, is it possible to do this when relying on the paint event?
You can use one of the Control.Invalidate methods to specify the region you want to redraw.
Then in the Paint event hander you can check the invalidated region with the e.ClipRectangle property.
I have a panel (here called parent), where I draw a calculated picture.
Some rectangular areas of this picture shall higlight by hoovering over.
It is the same behaviour like on a web page using , and , e. g.
the german map on the right upper side.
At hoovering the according rectangle shall be covered by a half transparent blue .
(And depending on keys like Alt, Ctrl and/or Shift in other colors, and clickable).
The first solution was a single instance of a transparent Panel - inherited from the Panel class.
In the hoovering event of the parent I moved and resized the single instance to the right place, changing the color.
This had some problems:
* moving and resizing (SetBounds()) fired MouseLeave event of the parent and a MouseEnter event of the single panel. The events had to be adapted accordingly to get it working correctly, I did it, but it is was very slow, due to finding the right map area from the list.
The second solution was to generate dynamically an instance of a transparent panel for each map area.
Each transparent panel had to set the e. g. Color.FromArgb(50, Color.Blue) at entering, and
remove it at leaving the panel.
But it seems to be even slower than before.
If the mouse hurries over several maps, they are all drawn like hoovered, and slowly get transparent again.
Does anybody know a good solution for this requirements:
at picture resize in parent panel, map etc. has to be changed as well
partly transparent highlight hoovered rectangle area.
detection of Ctrl/Shift/Alt as events for an area and change of the color.
detect click events there
Are there other controls I better use for this purpose?
Thanks for on practice based ideas.
PS: The world map with satellite pictures shows better what I want to do:
At hoovering the background is still visible more or less.
But in my case the parent image, its size and the maps are calculated at runtime
(after settings are completed by the user).
Solution
Description
I found now a solution, that reacts sufficiently fast to hoovering areas with the mouse.
The Main pictures is drawn in a PictureBox instance, in more detail its property Image is assigned.
The SizeMode property is set to Zoom, that automatically centers and resizes the image with keeping the aspect ratio of the assigned image.
I use a dynamically created Picturebox instance for each map area (childs), that is invisible, if the mouse does not hoover over it.
At hoovering over the map area the child shall appear - this is done in the mouse move event of the parent PictureBox,
where I iterate over the children, detecting whether the mouse position is in the bounds of a child.
The found child is set visible. Therefore the mouse enters this child control.
In the leave event of the child control I set it invisible again.
I experienced losses of mouse leave events for the child controls, if the mouse is moved too fast over all map areas.
I assume, if the mouse pointer already left the area before it has been set visible, the event is never raised.
The solution is, that all (other) child controls are set invisible, if in the mouse move event handler of the parent control does find no (a) child control.
Steps to implement
What to do, to implement my solution:
Use a designed parent PictureBox instance.
Assign the (dynamically) drawn picture to the Image property
Set SizeMode to Zoom
a list of Picturebox instances as form field
At assignment of a newly calculated image to the parent Picturebox instance:
remove all child controls, its event handler, its entry from the list, if they already exist.
create dynamically a PictureBox instance for each map area and add it to a list.
add it to the parent PictureBox instance as a child control.
set its Tag property points to a data object containing the origin map bounds and the object represented by the rectangular map area.
set the bounds to the map area scaled and centered according to the bounds of the parent Picturebox instance, that it suits the automatically zoomed image.
register a mouse click event
register a mouse leave event
set background color to e. g. semi transparent green
set Visible to false
In the parent Picturebox instance the mouse over event handler does:
finding the child Picturebox instance, where the mouse points at
if found it sets the found child visible
set all (other) child Picturebox invisible
In the parent Picturebox instance the resize event handler does:
scale/move all the map area Picturebox instances according to the bounds of the parent image and the bounds of the parent Picturebox instance.
The mouse leave event of each map area Picturebox instance set itself invisible (losses of events previously mentionned).
The mouse click event of each map area Picturebox instance makes whatever shall be done by clicking the map area.
Here playing a sine tone of the right chromatic pitch.
Pictures
The pictures below show the prototype with the map areas (not yet correctly aligned, some offset):
The first picture is for illustration of parent picture and all map areas.
The main picture (scale) and all map areas (dynamically created child Picturebox instances) are drawn in the first picture (by disabling the invisible action for map areas).
The second image is productive, where the mouse hoovers over tone G4.
In the second image the form has been resized - therefore the parent image is automatically been centered and resized.
The map areas were simply changed in their Bound property in the resize event handler of the parent PictureBox.
And the invisible action has been enabled for the map areas.
I checked the ImageMap:
It is a user control, that contains a static image, that was drawn before compile time.
At runtime the clickable areas are added – rectangle, polygon, ellipse are possible.
It uses a PictureBox, that is created as child of the ImageMap (that is inherited from UserControl).
It registers a Click, a Move and a Leave event to its event handlers.
In these event handlers it checks the current position against the list of paths in a GraphicsPath instance and returns an index (int).
The ImageMap keeps track of the last selected index (-1 = no object selected), sets the cursor accordingly (hand or default), sets or removes the tooltip.
But it has no hoover event, it does not change the area at hoovering, like I need it too.
Therefore I can need ImageMap it for proper detection of area,
but my picture is drawn at runtime!
And still I have to switch on and off the rectangles with its semi transparent layer.
I got the idea to use the property Visible to switch on and off the controls for the areas.
That it is easier to draw, I will set the background to the part of the parent image,
covered with the semi transparent color - this is a workaround, that is possible,
because the maps are relatively fixed to the parent picture.
If I have time, I will test this solution idea - it is a private project, therefore I can not work fulltime :-).
I need to create a form with two panels:
1. Destination
2. Source
On the source panel there will be picture boxes. I need to be able to move it from source to point at destination panel with mouse.
I have a problem connected with different coordinates of the panels.
Please, help with advice or an idea what to do.
Moving those controls requires changing their Parent property. That's not easy to do, there is no good time to do this while the user is dragging with the mouse. You'll also get the effect of the panel clipping the control, you cannot display it on both with half of the control on one and the other half on the other panel. And yes, you have to change the Location property of the control when you change the parent or it will jump.
Punt the problem, don't use two panels. It only has to look like a panel, easily done by drawing one in the form's Paint method (or OnPaint override, better). Use e.Graphics.DrawRectangle or FillRectangle.
I have a custom Canvas control (inherited from Canvas) overlaid over a large area of User Controls. The idea is to draw paths between user controls (i.e. connector lines).
To capture mouse movement, I call Mouse.Capture(theCanvas) on MouseDown. This works beautifully, but the user controls under the canvas obviously no longer receive mouse events. Mouse.DirectlyOver always shows the canvas, so I can't really fake it by peeking at the current position and seeing which user control it's over.
So, I still need the Canvas for drawing paths, but how can I solve this one of the following ways:
Peek under the Canvas and see what the topmost control is right under it?
Get this MouseDown -> Track MouseMoves -> MouseUp workflow to work on the canvas without mouse captures?
Any other ideas welcome...
I'd agree that those are your two options. If you want to only forward some clicks to your usercontrols, then go with option 1, and hit test the controls under the canvas.
If you need your usercontrols to behave as though there is nothing covering them (textboxes, buttons etc), then i'd recommend using the PreviewMouseMove event on the user control's parent, as this can pick up and optionally "handle" events before the controls get at the event, but it won't block the event if you don't set handled to true