Hi. I'm writing a custom control and I want to be able to click and drag items in it, but have them be just a dotted outline as they are dragged along. This behavior is seen in Photoshop when clicking and dragging layers (See image below displaying this behavior). I tried using a Panel with a transparent background but I found all this means is that it inherits the background color from its parent. I also tried putting code in the paint event, but that paints onto the user control which is not seen because it is covered by other user controls.

Image of a layer being dragged to a new position in Photoshop:
[img]http://img524.imageshack.us/img524/566/photshoplayers.jpg[/img]

Recommended Answers

All 15 Replies

there is a couple ways to do this, the way photoshop does it is drawing to the screen... not the form. here is an example of doing so

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Runtime.InteropServices;

namespace DrawToScreen
{

#region UnmanagedMethods
internal class UnmanagedMethods
{

[DllImport("user32")]
internal static extern IntPtr GetDC(IntPtr hwnd);

[DllImport("User32.dll")]
internal static extern void ReleaseDC(IntPtr dc);

}
#endregion

#region Actual drawing

public class DrawingToScreen
{
public void PaintRectToScreen()
{
IntPtr deskDC = UnmanagedMethods.GetDC(IntPtr.Zero);

Graphics g = Graphics.FromHdc(deskDC);

g.FillRectangle(new SolidBrush(Color.FromArgb(100, Color.Green)), 0,0, 500, 500);

g.EndContainer(cont);

g.Dispose();

UnmanagedMethods.ReleaseDC(deskDC);
}
}
#endregion
}

Not sure where I got it. Kudos to whoever wrote it.

this method works well, but only if you are sure to draw to the screen over your application only, because the desktop will not be invalidated when you expect, so clearing the image won't always go so hot. Its complicated.

A different... yet equally complicated, but less buggy method would be to create a new form, chromeless, that uses exlayerdwindow to display a slightly transparent image following the mouse. this would be much easier to accomplish the look you want, since the form will support full transparency you could make it look however you wanted, even create the image prorammatically in GDI plus, if you wanted too....

either way, Concepts such as though are very complicated, and the simple things like that are truly the hardest features.

Best of luck.

I noticed this link posted by adatapost, which seems to use the dashed outline on the drag you are looking to replicate, but cannot be sure because I did not get to see your IMG tag. Anyway, it's a TreeView drag/drop I believe, but the owner drawn stuff is probably the same with some minor tweaks: http://www.codeproject.com/KB/tree/mwcontrols.aspx?msg=904987

Cheers!

commented: chic +7

there is a couple ways to do this, the way photoshop does it is drawing to the screen... not the form. here is an example of doing so

... Code was here. Removed for readability...
}

Not sure where I got it. Kudos to whoever wrote it.

this method works well, but only if you are sure to draw to the screen over your application only, because the desktop will not be invalidated when you expect, so clearing the image won't always go so hot. Its complicated.

A different... yet equally complicated, but less buggy method would be to create a new form, chromeless, that uses exlayerdwindow to display a slightly transparent image following the mouse. this would be much easier to accomplish the look you want, since the form will support full transparency you could make it look however you wanted, even create the image prorammatically in GDI plus, if you wanted too....

either way, Concepts such as though are very complicated, and the simple things like that are truly the hardest features.

Best of luck.

I tried the first method, as I would only allow dragging within the form, but it yelled at me when trying to compile, saying that cont doesn't exist. It doesn't seem to be declared anywhere... What is that supposed to be?

sorry that line is unnecessary. but scratch that code. its works great just you will never see its effect because the desktop invalidates all the other windows and its just immediately erases it.

[DllImport("User32.dll")]

        public static extern IntPtr GetDC(IntPtr hwnd);

        [DllImport("User32.dll")]

        public static extern void ReleaseDC(IntPtr dc);

        protected override void OnPaint(PaintEventArgs e)
        {

            SolidBrush b = new SolidBrush(Color.Blue);


            IntPtr desktopDC = GetDC(IntPtr.Zero);

            Graphics g = Graphics.FromHdc(desktopDC);

            g.FillEllipse(b, 0, 0, 800, 600);

            g.Dispose();

            ReleaseDC(desktopDC);

        }

just paste this into a new form. add
using System.Drawing.Drawing2D;
using System.Runtime.InteropServices;
to the top of the cs file.

you will see its effect. this will get you started down the long and complicated road. Have fun. sorry for the problematic code I posted before.

This worked, except it seems to cause a memory leak. When I move the selector I am drawing around, I can see the memory usage shoot up, until I finally get an OutOfMemory exception on the line this.Refresh()

Any idea what may be causing this?

you could try a Using statement on the graphics object. but Its clearly disposed in the example I posted. are you sure you didn't omit the g.dispose(); call?

you could try making sure it goes out of scope by using a "using" block example:

[DllImport("User32.dll")]

        public static extern IntPtr GetDC(IntPtr hwnd);

        [DllImport("User32.dll")]

        public static extern void ReleaseDC(IntPtr dc);

        protected override void OnPaint(PaintEventArgs e)
        {


            IntPtr desktopDC = GetDC(IntPtr.Zero);

            Using(Graphics g = Graphics.FromHdc(desktopDC))
{
            SolidBrush b = new SolidBrush(Color.Blue);
            g.FillEllipse(b, 0, 0, 800, 600);

            
}
            ReleaseDC(desktopDC);

        }

that might help, also Im not sure where you are refreshing but I recomend control.Invalidate(true); because it allows windows to decide if its a good time to redraw instead of demanding that it is done now. although refresh might get you a more consistent look. you might try it to see if it increases performance.

best of luck with you Control. tell me how it goes. I always say I am going to do stuff like this but Im lazy, as soon as I figure out how its done, I get bored and do something else.

My code looks like this. It's modified a bit so that it gives the behavior I wanted but still pretty much the same.

[DllImport("User32.dll")]

public static extern IntPtr GetDC(IntPtr hwnd);

[DllImport("User32.dll")]

public static extern void ReleaseDC(IntPtr dc);

protected override void OnPaint(PaintEventArgs e)
{
   if (mouseDown_)
   {
      Pen p = new Pen(Color.Orange);
      IntPtr desktopDC = GetDC(IntPtr.Zero);
      Graphics g = Graphics.FromHdc(desktopDC);

      int drawTop = Cursor.Position.Y - (lstLayerDesc[0].Height / 2);

      // Restrict drawing to user control boundaries.
      int minTop = PointToScreen(new Point(this.Left, this.Top)).Y - this.Top + pnAddNew.Height;
      int maxTop = PointToScreen(new Point(this.Left, this.Top)).Y + this.ClientSize.Height - lstLayerDesc[0].Height - (lstLayerDesc[0].Height / 2);

      if (drawTop < minTop)
      {
          drawTop = minTop;
      }

      if (drawTop > maxTop)
      {
          drawTop = maxTop;
      }

      g.DrawRectangle(p, PointToScreen(new Point(this.Left, this.Top)).X - this.Left, drawTop, lstLayerDesc[0].Width, lstLayerDesc[0].Height);
                
      g.Dispose();
      ReleaseDC(desktopDC);
      p.Dispose();
   }
}

I am doing the refresh in the MouseMove, so that the selector is rendered as it is clicked and dragged. Also I think it is worth noting that the memory only increases when I actually am doing the dragging. This would lead me to believe that the memory leak is in the MouseMove code, but I'm really not doing very much in there. Here is the code from my MouseMove.

private void ldLayer_MouseMove(object sender, EventArgs e)
{
    if (mouseDown_)
    {
        //LayerDescriptor ldTemp = (LayerDescriptor)sender;

       // int newTop = Control.MousePosition.Y - parentTop_ - ldTemp.Top - (ldTemp.Height / 2);

        //if (newTop < pnAddNew.Bottom)
        //{
        //    newTop = pnAddNew.Bottom;
        //}

        //pnPosition.Top = newTop;
        //repositionY_ = newTop;

        this.Refresh();
    }            
}

Is this the correct way to get the properties from the sender? I tried to Dispose it, but that was a bad idea because it killed off my sender control haha.

Edit: I just realized most of the code in the MouseDown was old code I don't even use anymore. I commented it out (in my project and in the above snippet), and it is still happening... So now I think again it is in the OnPaint, since the Refresh is causing the OnPaint to get called, correct?

Oh yes, I also tried the using block as you suggest but still no difference.

Can you upload a sample project? I can't compile the code -- I'm missing references to the controls. You should also use using() blocks to ensure instances of IDisposable are cleaned up when they go out of scope.

Also why don't you use the graphics provided in e.Graphics ? in the OnPaint()?

Can you upload a sample project? I can't compile the code -- I'm missing references to the controls. You should also use using() blocks to ensure instances of IDisposable are cleaned up when they go out of scope.

Sure thing. I've isolated just the control with the problem and put it on a form and programatically added a couple layers to it, so all you need to do to reproduce the issue is click and drag one of the layers, and move the mouse around until the error happens. Though it's a bit weird, I tried making the problem happen on this sample but this time I get ArgumentNullException when on the line using (Graphics g = Graphics.FromHdc(desktopDC)) . desktopDC is 0 for some reason. Regardless, I will upload this project as an attachment.

I don't know... you got me. That sample app has a lot of issues with it. One tip for painting -- you cannot have exceptions being thrown while painting. Even though you handle it an exception still causes a ~0.25 seconds pause or so to pull together all of the exception information.

Scott he's not using the form's graphics object because he needs to draw in front of them so he's drawing to the screen.

back to the OP.
I messed with you example the object not set to an instance of an object error is something trivial happened when moving the code probably, wouldn't worry about that.

I couldn't seem to force an out of memory exception though. just didn't' happen. and I messed with it a lot.

as for all your flicker, you will need to user draw all the user controls that are being refreshed and double buffer them. but that's not your problem at hand.

1 thing you have the using block and still call G.Dispose, not necessary but shouldn't cause a problem.

another when you call refresh in the mouse move it is actually refreshing the entire control and all the child controls it contains. it could be any of the child controls causing the problem when they are painted. as many of these controls appear to be custom drawn you might want to go back and check all of their paint events to see if the leak is there.

I hope this helps. seems you are doing a great job so far.

Thanks for the all tips. I noticed that when I built it in Release mode I couldn't make it happen either so maybe this is a non-issue... I hope.

About the double buffering... I've done this before using DirectX by creating two surfaces, but how is it done with the GDI? Do I need to draw everything to some invisible panel first or something?

not even that complicated. if your control inherits from Control, or UserControl just throw this code in the constructor of your controls.

this.SetStyle(ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer | 
    ControlStyles.AllPaintingInWmPaint | ControlStyles.SupportsTransparentBackColor,
    true);

feel free to remove any of those flags you want, the OptimizedDoubleBuffer is the one that does the magic, of course all the others help make things look nice too. but the transparentbackcolor you might not need in this instance.

.net then handles all the double buffering for you, of course you could do it manually, but why bother when .net does it for you.

I just wanted to mention, if you still were having troubles with this you could look into the class drawreversibleframe, is what is used to draw selection rectangles, so long as you are only drawing rectangles it will draw on top of controls. although you don't get definite choice of color...

I solved the problem by throwing out the custom layer descriptor control I had written and instead rendered the entire thing as one picture box, and used variables to track where objects being rendered were and what was being clicked on.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.