|
When applying the code to a scrollable panel, the code for MouseUp needs to be modified from
mouseDown = false;
mouseMoving = false;
haveRect = true;
To :
if (e.Button == MouseButtons.Left)
{
mouseDown = false;
mouseMoving = false;
haveRect = true;
}
Without this, when you right click to erase rectangles, then scroll, the last drawn rectangle is redrawn by the Paint code due to the true haveRect value.
Hope this helps someone.
|
|
|
|
|
I want to draw a rubberband rectangle and capture coordinates in a text boxes.For this I used a picture box..and one more problem is i have drawn rubberband lines and captured those values in labels(working fine) but as i have placed scrolling for the from when ever i draw a line and scroll down line gets erased.i am working in VB.NET
can any one help me in solving this problem...
Sowjanya
|
|
|
|
|
I seem to remember just using the graphics.drawrectangle using a 1px width brush with (I think) a cross hatch brush with one transparent color and one white or black color for the rubber band.
Please correct me if this is not the same as what you are looking for.
|
|
|
|
|
yeah, that seems to have produced the same effect for me too with about 10% the hassle
|
|
|
|
|
Are you talking about redrawing the image each time then? This XOR solution is for when it's expensive to redraw the image. I can't see how your cross hatch brush will help with this.
Also, DrawReversibleFrame(), which is the recommended route, suffers from some serious problems, especially when activating/deactivating the control, resizing, and moving the window around. This can be made to work with a rubber band rectangle, mostly, but is no good for a cursor line, which is what I need.
This solution is the best I've seen, although note that the correction for background works, and is required for solid lines.
I've added the fix, added a method for drawing a line instead of a rectangle, and made the class static, below, if this helps anyone.
public static class XorGdi
{
public static int PS_SOLID = 0;
public static int PS_DASH = 1;
public static int PS_DOT = 2;
public static int PS_DASHDOT = 3;
public static int PS_DASHDOTDOT = 4;
public static void DrawLine(int penStyle, int penWidth, Color col,
Graphics grp, int X1, int Y1, int X2, int Y2)
{
// Extract the Win32 HDC from the Graphics object supplied.
IntPtr hdc = grp.GetHdc();
// Create a pen.
IntPtr gdiPen = CreatePen(penStyle, penWidth, RGB(col.R, col.G, col.B));
SetROP2(hdc, R2_XORPEN);
SetBkMode(hdc, TRANSPARENT);
// Set the ROP cdrawint mode to XOR.
SetROP2(hdc, R2_XORPEN);
// Select the pen into the device context.
IntPtr oldPen = SelectObject(hdc, gdiPen);
// Draw the line.
MoveToEx(hdc, X1, Y1, IntPtr.Zero);
LineTo(hdc, X2, Y2);
// Put the old stuff back where it was.
SelectObject(hdc, oldPen);
DeleteObject(gdiPen);
// Return the device context to Windows.
grp.ReleaseHdc(hdc);
}
public static void DrawRectangle(int penStyle, int penWidth, Color col,
Graphics grp, int X1, int Y1, int X2, int Y2)
{
// Extract the Win32 HDC from the Graphics object supplied.
IntPtr hdc = grp.GetHdc();
// Create a pen.
IntPtr gdiPen = CreatePen(penStyle, penWidth, RGB(col.R, col.G, col.B));
SetROP2(hdc, R2_XORPEN);
SetBkMode(hdc, TRANSPARENT);
// Set the ROP cdrawint mode to XOR.
SetROP2( hdc, R2_XORPEN );
// Select the pen into the device context.
IntPtr oldPen = SelectObject( hdc, gdiPen );
// Create a stock NULL_BRUSH brush and select it into the device
// context so that the rectangle isn't filled.
IntPtr oldBrush = SelectObject( hdc, GetStockObject( NULL_BRUSH ) );
// Now XOR the hollow rectangle on the Graphics object with
// a dotted outline.
Rectangle( hdc, X1, Y1, X2, Y2 );
// Put the old stuff back where it was.
SelectObject( hdc, oldBrush ); // no need to delete a stock object
SelectObject( hdc, oldPen );
DeleteObject( gdiPen ); // but we do need to delete the pen
// Return the device context to Windows.
grp.ReleaseHdc( hdc );
}
private static int NULL_BRUSH = 5;
private static int R2_XORPEN = 7;
private static int TRANSPARENT = 1;
// C# version of Win32 RGB macro
private static int RGB(int R, int G, int B)
{
return (R | (G << 8) | (B << 16));
}
// Use Interop to call the corresponding Win32 GDI functions
[System.Runtime.InteropServices.DllImportAttribute( "gdi32.dll" )]
private static extern int SetROP2(
IntPtr hdc, // Handle to a Win32 device context
int enDrawMode // Drawing mode
);
[System.Runtime.InteropServices.DllImportAttribute( "gdi32.dll" )]
private static extern IntPtr CreatePen(
int enPenStyle,
int nWidth, // Width of pen
int crColor // Color of pen
);
[System.Runtime.InteropServices.DllImportAttribute( "gdi32.dll" )]
private static extern bool DeleteObject(
IntPtr hObject // Win32 GDI handle to object to delete
);
[System.Runtime.InteropServices.DllImportAttribute( "gdi32.dll" )]
private static extern IntPtr SelectObject(
IntPtr hdc, // Win32 GDI device context
IntPtr hObject // Win32 GDI handle to object to select
);
[System.Runtime.InteropServices.DllImportAttribute( "gdi32.dll" )]
private static extern void Rectangle(
IntPtr hdc, // Handle to a Win32 device context
int X1, // x-coordinate of top left corner
int Y1, // y-cordinate of top left corner
int X2, // x-coordinate of bottom right corner
int Y2 // y-coordinate of bottm right corner
);
[System.Runtime.InteropServices.DllImportAttribute( "gdi32.dll" )]
private static extern IntPtr GetStockObject(
int brStyle // Selected from the WinGDI.h BrushStyles enum
);
[System.Runtime.InteropServices.DllImportAttribute("gdi32.dll")]
private static extern int SetBkMode(IntPtr hdc, int iBkMode);
[System.Runtime.InteropServices.DllImportAttribute("gdi32.dll")]
public static extern void LineTo(IntPtr hdc, int x, int y);
[System.Runtime.InteropServices.DllImportAttribute("gdi32.dll")]
public static extern bool MoveToEx(IntPtr hdc, int x, int y, IntPtr lpPoint);
}
|
|
|
|
|
I am wondering is it possible to draw Rubber Band Rectangles on top of WebBrowser Control, which is in the form?
I had a form with web browser control in it. I am thinking about allowing users to draw rectangles on top of browser control.
Appreciate any help.
|
|
|
|
|
Balaji,
I haven't tried it, but I would expect that any object for which you can extract a Graphics object should work as well.
Carl
|
|
|
|
|
How to change background of Rubber Band Rectangle?
For example make it grayscale?
Thanks
|
|
|
|
|
I can't think of a simple way, off hand. You could of course copy out the part of the bitmap within the rectangle, convert it to grayscale, and then copy it back. The rectangle was, after all, originally meant to provide the region of interest for image processing, But I don't know of a simple command, GDI or GDI+.
I looked up ICM as a possible alternative, but that doesn't appear any simpler that what I suggested above.
Carl
|
|
|
|
|
Can you send api sample?
Many,
thanks
|
|
|
|
|
Not sure what you want a sample of. For references on ICM, looking it up on MSDN online is the simplest approach. For converting the region of interest to a grayscale subimage, there is not an API in either GDI or GDI+ that I'm aware of. It's either roll your own with .NET or Win32 or use somebody's library: LeadTools, perhaps, or the Intel Image Processing library.
If you want to do your own, a reasonable starting point is
http://www.codeproject.com/cs/media/csharpgraphicfilters11.asp
"Image Processing for Dummies with C# and GDI+ Part 1 - Per Pixel Filters" By Christian Graus
here in the Code Project. He covers grayscale images in this part. You'll need to use the components of the selection rectangle from the original image and use those to limit the area of the image that gets converted. Note also that he deals with 24 bit images. If you need to work with 32 bit images, do p += 4; instead of p += 3; and the last term in the definition of nOffset is multiplied by 4 rather than 3.
Carl
|
|
|
|
|
Your code defines the following:
private const int NULL_BRUSH = 5;
private const int R2_XORPEN = 7;
private const int BLACK_PEN = 0;
However, in GDI.h (VC7 platform SDK) I see this:
#define WHITE_BRUSH 0
#define LTGRAY_BRUSH 1
#define GRAY_BRUSH 2
#define DKGRAY_BRUSH 3
#define BLACK_BRUSH 4
#define NULL_BRUSH 5
#define HOLLOW_BRUSH NULL_BRUSH
#define WHITE_PEN 6
#define BLACK_PEN 7
#define NULL_PEN 8
Notice that BLACK_PEN is defined the same as WHITE_BRUSH!
I wonder if your code just happens to work because a black pen is typically the default. And because you seem to rely on the background being white to get the XOR effect (if you draw with black in XOR mode, nothing should show up on the screen!)
|
|
|
|
|
Oops, I guess I'm mistaken (although not completely...). You call
IntPtr gdiPen = CreatePen(penStyle, lineWidth, BLACK_PEN);
But what you want here is not the GDI constant BLACK_PEN, but rather the RGB value 0,0,0:
IntPtr gdiPen = CreatePen(penStyle, lineWidth, RGB(0,0,0));
But it doesn't matter in your code because both work out to 0.
|
|
|
|
|
Anyway, I still have a criticism of this code. It draws an XOR rectangle with a black pen, and of course, black is color 0, and if you XOR anything with 0, nothing happens!
The only reason this code works is that it uses a PS_DOT line, which causes part of the line to be drawn with the BACKGROUND color, which is white by default. If you change the line style to PS_SOLID, nothing shows up on the screen!
Instead of
IntPtr gdiPen = CreatePen(penStyle, 1, BLACK_PEN);
SetROP2(hdc, R2_XORPEN);
The code should say
IntPtr gdiPen = CreatePen(penStyle, 1, RGB(255,255,255));
SetROP2(hdc, R2_XORPEN);
SetBkMode(hdc, TRANSPARENT);
And the following definitions need to be added:
[System.Runtime.InteropServices.DllImportAttribute( "gdi32.dll" )]
private static extern int SetBkMode(IntPtr hdc, int iBkMode);
private const int TRANSPARENT = 1;
private const int OPAQUE = 2;
I have tested the above code to verify that it works with both PS_DOT and PS_SOLID.
|
|
|
|
|
David,
Sorry it took so long to get back to you, but I didn't get a notification of your message.
At any rate, I haven't checked your addon, but will take your word for it.
Thanx for the diligent input.
Carl
|
|
|
|
|
|
Just a quick observation that I haven't research at all. If I change teh brush type to PS_SOLID there is no output. Any clues?
|
|
|
|
|
Dave,
If you mean you changed NULL_BRUSH in GetStockObject() to PenStyles.PS_SOLID, then that is equivalent to using WHITE_BRUSH (= 0) instead of NULL_BRUSH. I do get an output with that substitution (adding the definition to the class, of course), but it is a white rectangle XORed with the background rectangles with a dotted border, as it should be with that substitution.
If you mean that you changed the PenStyles to PenStyles.PS_SOLID, then you have run afoul of the XOR operation itself through a combination of attribute settings in the CreatePen call - namely a pen width of 1 pixel and a black pen. With pen style set to PenStyles.PS_DOT, if you redefine pen width > 1, you get nothing. With pen width = 1 and redefine BLACK_PEN = 255, you get the white dotted rectangle as normal plus a red solid rectangle XORed with the background and offset vertically one pixel. If you now set the pen width > 1 again, you get a solid red rectangle border XORed with the underlying rectangles. If you change the pen style to anything else as defined, you get the same result; the pen style setting is ignored. Go figure.
Carl
|
|
|
|
|
Oops, right, PenStyle was what I meant to say. Don't know where the brush came from. I was really hopeful when I saw your code Carl. I am trying to perform rubberbanding in Matrox Imaging's ActiveMil Display control. I can get this to work a couple of ways under VC6 or VB6 (GDI and using Matrox graphics primatives) but it seems to be a no-go using GDI under .Net. GDI+ Works though Ever use Active MIL?
Thanks for the artical.
|
|
|
|
|
Dave,
>> Ever use Active MIL? <<
Can't say as I have. Is it a COM or .NET control?
Carl
|
|
|
|
|
thank you for your great article.
Now, I want to use your technique to draw a dash rectange in a Form, which is srollable form, I down know How the rectangle being drawn if I move the mouse exceed the window.
DO you have any idea?
PS. can you tell me how I can find more information that teaching me how to call API in C# ??
Thank you very much!!
|
|
|
|
|
The way my code is written, the rectangle be virtually drawn outside the window, but the part ouside the window will be invisible. I haven't checked, but the coordinates will probably wrap around to the opposite side. I'm not sure what will happen if you now scroll the windows, but I suspect that the rectangle will be drawn in the same place since the coordinates are ,easured from the upper left corner of the client area which won't change with scrolling. Maybe somebody else can comment; if I get a chance, I'll check it.
If you look in the documentation for the .NET SDK, in the MSDN library CD, or in MSDN Online for anything on P/Invoke (Platform Invoke), you should be able to get what you need. It's really simple - the only trick, sometimes, is proper selection of the parameter in C# to match those in the Win32 API calls, but that's covered in the docs pretty well. You may have to experiment if the match isn't good to what you want to do. It's a lot like COM. the parameters are marshaled in the call to the API and back. When you set up the prototypes with the DLLImportAttribute, you use the C# types that match best to the Win32 types and usually you'll be right.
Good luck,
cthomas
|
|
|
|
|
One more point that occurred to me this morning:
If your maouse leaves the displayed area of the image, you can also use the MouseLeave event to automatically set the mobile part of the selection rectangle to the extreme of the display area where the mouse exited. You could also turn off the flag that indicates a selection is in progress so that the coordinates would be final and not changed by the MouseUp event. I just preferred to do it all in the MouseUp event since it's simpler logic.
Carl
|
|
|
|
|
thank you so much for your reply.
I'm trying my best to implement my project.
Actually, My winForm contains 8 picture boxes, 4 top and 4 below. Ofcourse they are not able to show within the screen at the same time, so I'm using Scroll control to implement the scrolling method rather than using AutoScroll.
After I read your article, I hope to draw dash rectangle as same as window selection effect, i.e. if I drag mouse reaching right or bottom side, the scroll bar moves and also the rectangle move.
I'm finding the method to do right now. I'll be very happy if you have simple example for this problem.
But anyway, thank you so much~~
|
|
|
|
|
No, I don't have any code for that. I'm not even sure I understand what you are trying to do. If you are operating in only one PictureBox and are using an offscreen Bitmap to feed it that's bigger than the PictureBox, you can use the distance your mouse is outside of the visible area of the PictureBox to provide the scroll distance. You would need to convert current mouse coordinates to the memory Bitmap coordinates and draw the rectangle in the memory Bitmap before moving the portion you want to be visible to the PictureBox. That way the rectangle moves with the scroll since the scroll effects the PictureBox-sized rectangle actually displayed. But since I'm not sure how all your PictureBoxes interact, I'm not sure this would work for you.
Good luck,
Carl
|
|
|
|
|