GDI+ Drag and Drop

furjaw
06-28-2006, 10:23 PM
Visual Basic.NET 2005

I want to use GDI+ to drag and drop images onto a PictureBox.
I also need to rotate the images. Where can a find a code sample?

Deadalus
06-29-2006, 03:21 AM
This might help for the rotation:
http://www.codeproject.com/vb/net/rimage.asp

I haven't tried it myself, so don't shoot me if it sucks. :)

jo0ls
06-29-2006, 08:00 AM
You've asked this question in a few places (and in this forum a while back), so I know that what you want is actually much more complicated.

I guess no one has done exactly what you want so there is no code sample out there. You will have to do this from scratch yourself.

From various posts here and there I gather that you are dragging one image (wasn't it teeth or something) onto another control. There you want to be able to rotate and possibly reposition and possibly delete the image once it has been dropped.
To code this you need to understand how to do custom controls, drag and drop and how to draw things with GDI.

The way I would do it...

Lets say we are dragging chess pieces onto a board (and you can rotate them, this is strange chess). We have one control (lets call it sourceBox) holding the images of the pieces for the user to drag over to the board. This source control is responsible for initiating drag and drop.

You need something to drag and drop, so we need a custom class to represent the object being dragged (call it dragObject). This class has properties for the image to be used during the drag, and it also has an event - GiveFeedback that chains together the giveFeedback event in the source control and the target control.

The target Control is complicated (call it targetBox). When an item is dragged in, we check it is one of our custom class dragobject. If it is we can use the dragObject.GiveFeedback event to set the cursor as we drag over the control. Once the item is dropped we need to create an object to represent the image on this control:

droppedItem class: represents one item dropped on the target box. Stores orientation, location and provides a suitably orientated image.

So when we drop on the target box we create another droppedImage instance and add it to a collection. The target box needs to draw itself - so it should override the paint event, and draw a background, then go through the collection of images and draw those (it will ask each droppedItem for its image).
Now you also want to be able to rotate the pieces once they are dropped on the board. This means the target control needs to check when you mouse-down to see which of the pieces in the list is clicked. Then you need to highlight the selected piece and wait for keypresses. Once a keypress occurs you need to call a method in your dropItem class to rotate the piece. Then you call invlidate to redraw the whole control. It's complicated as you might want to drag the piece elsewhere on the board rather than rotate it.

All this is pretty complicated, and of course takes time. You need to break it into smaller parts and get them working one bit at a time. I'd look into it further but I don't have the time at the moment. Attached is another example - of some of the drag and drop stuff mentioned above.

furjaw
06-29-2006, 01:57 PM
I double-clicked "WindowsApplication49.sln". Then I pressed [F5] to execute, and I got the following error message:

"An error occurred creating the form. See Exception.InnerException for details. The error is: Request for the permission of type 'System.Security.Permissions.UIPermission, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' failed."

jo0ls
06-30-2006, 04:09 AM
Try this repack.
If it is not working then create a new windows forms application, use add existing item to add the *.vb files (for the form1.vb you can copy and paste the code instead as the new app will already have a form1). You'll need the pngs available too, either copy them to the bin/debug and bin/release folders or put them somewhere else and modify the path in the code.

furjaw
06-30-2006, 12:14 PM
What happened to the attachment? It is gone!

jo0ls
06-30-2006, 04:37 PM
was in a rush, sorry:

jo0ls
07-02-2006, 09:20 PM
Ok, after I did all the thinking for the reply before, I figured I could flesh out that example a bit. See attached zip...

jwakeman
07-03-2006, 08:20 AM
Love it!

furjaw
07-04-2006, 12:02 AM
Holy ****!

You did it!

furjaw
07-04-2006, 12:57 AM
When I substituted my spring.png (32x32) for your bee.png, it looks correct on the form, but the dragged image is very tiny in a corner of the box. After dropping it on the background, it is the same very tiny size.

jo0ls
07-04-2006, 03:18 AM
Ah, it's probably the resolution of the image. If you right click the png file in windows explorer and look at the properties, the horizontal and vertical resolutions should be 96 - the same as most monitors. If you have an image taken with a digital camera, they tend to have much higher resoultions.

Say you have 32x32 pixels @ 96 dpi. So the width will be 32/96 = 0.33" (the actual size depends on the size of the monitor and the windows desktop resolution). Comapred with 32x32 @ 600 dpi, width = 32/600 = 0.05333". GDI+ is reolution independent, I guess cursor.fromimage is not (same goes for bitmap.GetThumbnailImage - they shrink with high resolutions).

The fix is to ensure the resolution is 96x96 in the source control:


Public Class DragSourceControl
Inherits Control

Protected Overrides Sub OnMouseDown(ByVal e As System.Windows.Forms.MouseEventArgs)
If Me.BackgroundImage Is Nothing Then Exit Sub
Dim imageOut As Bitmap = Me.BackgroundImage.Clone
imageOut.SetResolution(96, 96)
Dim outDragObject As DragObject = New DragObject(e.Location, imageOut.Clone)
imageOut.Dispose()
AddHandler Me.GiveFeedback, AddressOf outDragObject.RaiseGiveFeedback

furjaw
07-04-2006, 11:37 AM
96 dpi did the trick!
How do I pass the target background image combined with any dropped images to another form?

jo0ls
07-04-2006, 01:41 PM
You can get the combined image by asking the control to DrawToBitmap, as all the drawing has been done in the paint event. So on Form1, you can get the bitmap with, e.g.
Public Function GetCombinedImage() As Bitmap
Dim combinedImage As New Bitmap(uxTarget.Width, uxTarget.Height)
uxTarget.DrawToBitmap(combinedImage, New Rectangle(0, 0, combinedImage.Width, combinedImage.Height))
Return combinedImage
End Function

Passing values between forms:
http://www.xtremevbtalk.com/showthread.php?t=147563

Are you going to be printing it? If so it would be better to keep the high resolution (background and drag object), and have the target control return a hi res image in a function.

furjaw
07-04-2006, 03:10 PM
You say that the above code goes in Form1.vb. How is it invoked?
Then, I would think that something also has to be added to DropTargetControl.vb.

jo0ls
07-04-2006, 05:54 PM
You want to pass the combined image to another form. The link explains how to pass things between forms. The function is just an example of how to get the combined image. The details of how to get the image to the other form depend on your project - you probably have

EITHER: A main form (say it is called frmMain), which opens another form with the drag and drop controls on (frmDrawing), you want to pass the combined image back to frmMain when frmDrawing closes.
OR: You start at the form with the drag and drop controls on (frmDrawing) and want to open another form (frmAnother) from it and pass the combined image.


In the first case, you have frmMain (set as the startup form) with:

Public Class frmMain

Private m_combinedImage As Bitmap

Private Sub btnOpenOtherForm_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnOpenOtherForm.Click
Dim otherForm As New frmDragDrop
Dim result As Windows.Forms.DialogResult = otherForm.ShowDialog
' we continue here when the other form closes.
' the other form still exists, so call the sub to get the image.
m_combinedImage = otherForm.GetCombinedImage
' just to show we got it:
Me.BackgroundImage = m_combinedImage

End Sub

End Class

and frmDragDrop has the public function GetCombinedImage I put in the post above (as well as the drag and drop controls...).

The other way, where the form with the drag and drop controls is opening the other form. You just alter the constructor (i.e. Sub New) so that it accepts a parameter:

Public Class frmAnother

Dim m_combinedImage As Bitmap

Sub New(ByVal combinedImage As Bitmap)
InitializeComponent()
m_combinedImage = combinedImage
' show we recieved it:
Me.BackgroundImage = m_combinedImage
End Sub
End Class

This allows you to pass it the image on creation back on the form with the drag and drop controls:

Private Sub btnOpenOtherForm_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnOpenOtherForm.Click
Dim otherForm As New frmAnother(GetCombinedImage)
otherForm.Show()
End Sub

We don't need to add anything to the DropTarget control, as we can just use the DrawToBitmap method - which it inherits from the Control class.

furjaw
07-05-2006, 12:02 AM
My dragged source objects are coming out too small at 32x32. It looks like I am going to have to go to about 60x60. So, I will probably have to give up on seeing the object as it is being dragged because a cursor has to be 32x32.

furjaw
07-05-2006, 01:18 AM
If you don't place the cursor over the center of the source object, you can't see it as you drag (no icon). Is there any way to increase that area?

jo0ls
07-05-2006, 06:07 AM
because a cursor has to be 32x32

It works with larger cursors for me. I have windows xp, it works with graphics acceleration turned off too. 128x128, 256x256 it doesn't seem to worry. The code doesn't mind what size is used.

If you don't place the cursor over the center of the source object, you can't see it as you drag (no icon).

I can't reproduce this, if the mousedown occurs anywhere in the source control, it will initiate the drag and drop. Are you seeing no cursor at all, or the no-drop curosr? (circle with a line through it). The no-drop icon means the user can't drop the item being dragged in the present location. I'd originally thought about having two cursors, the one that is there now, and a custom NoDrop cursor with the image on, but it was too complicated and not really worth it - you have to raise givefeedback in all controls on the form, and the form itself so that it switches to the custom cursor.

There is a problem with the selection rectangle. If the paint event fires then it clears the selection rectangle if it is within the bounds of the paint events eventargs. So it is cleared if the form is obscured or goes off the edge of the screen. Later we try to clear it by drawing it again (that's the way it works), and it does draw it again. So you end up with multiple selection rectangles. I'll think up a fix.

furjaw
07-05-2006, 10:32 AM
I am running Windows XP Pro. My problem is no cursor at all unless I mousedown right in the center. I have 3 test source objects - 32x32, 42x42, 52x52. The 32x32 works best, that is, it has the largest area that gives a visible cursor. Although it will give no cursor if I mousedown too far off center. The 52x52 has a REALLY small area that will work. It will always do a drag and drop, it is just that there is no cursor.

At first, I thought that I got a custom cursor only for 32x32 sources because I got no cursor when I went larger, but, now I see that the problem really is, the larger the source, the smaller the area that will give a custom cursor.

I have not encountered the multiple selection rectangles.

jo0ls
07-06-2006, 06:46 AM
I tried the code that you sent on four computers and it worked.
I think it must be due to graphics card driver differences. To see if this is the problem, you could try disabling graphics card acceleration:

right click desktop, properties, settings, advanced, troubleshoot -> slide the slider to disable acceleration. (It works without acceleration on my computers).

You could update your graphics card driver, and try that.

Better though would be to simplify the routine that creates the cursors so that it works without asking the users to mess about with drivers.
At the moment we pad the cursor with transparency to get the hotspot at the point clicked on the image. I'd bet that the transparency is causing the problem with the driver, or possibly it is because the padded image creating the cursor is not always square.
It will be worth trying two approaches:
- Discard all the padding and hotspot stuff, the custom cursor will be the same size as the image, with the normal cursor on top. The image will jump when you start the drag, but the drop will be precise.
- Keep the padding, make sure the cursor is square. (It might need to be even more complicated - make sure it is a certain size of square).

The framework definately needs better support for cursors (and icons, cursors are just icons with hotspots).

The previous selection rectangle does persist sometimes with your code.
I'll fix the problems in my example and attach it later...

jo0ls
07-06-2006, 01:38 PM
Ok, here's the simplified version. I've stopped using the drawReversibleRectangle, instead we just draw a rectangle in the paint event. Doing it this way means it gets redrawn when required.
The cursor is just the image...

furjaw
07-06-2006, 02:23 PM
It works perfectly, now!!!

In DragObject.vb, I commented out CreateCursors() and added the 2 lines after it.

Public Sub New(ByVal hotspot As Point, ByVal image As Bitmap)
m_image = image
m_hotspot = hotspot
'CreateCursors()
m_normalCursor = New Cursor(m_image.GetHicon)
m_size = m_image.Size

End Sub

This program does it all! And it works perfectly! GREAT JOB!!!

furjaw
07-18-2006, 10:57 AM
When my objects are rotated, they lose some of their sharpness. Can bicupic interpolation be specified? That would solve the problem.

EZ Archive Ads Plugin for vBulletin Copyright 2006 Computer Help Forum