Hello,
I've been struggling with this for quite a while and can't get it fixed. I'm busy with a GIS app. For this I have two PictureBoxes. The first contains all the polygon shapes. the second, I would like to use as a type of overlay over the first, so that I don't need to redraw all the polygons over and over again. When adding the Overlay PictureBox over the First, I can still see the First through the Overlay PictureBox because the Overlay PictureBox has a BackColor of Transparent. But now, the problem: At the moment I have a MouseMove event for the Second PictureBox to draw a rectangular SelectionBox. The code is as follows :
private void Map_MouseMove(object sender, MouseEventArgs e)
{
mouseMoveX = (float)e.X;
mouseMoveY = (float)e.Y;
selectBoxGraphics = overlayBox.CreateGraphics();
if (e.Button == MouseButtons.Left)
{
selectBoxGraphics.Clear(Color.Transparent);
Pen myPen = new Pen(Color.Red, 1.5f);
myPen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dash;
selectBoxGraphics.DrawRectangle(myPen, GetProperSelectRec());
myPen.Dispose();
}
}
The problem code here is "selectBoxGraphics.Clear(Color.Transparent)". This immediately sets the background of the PictureBox to Black, so I can't see the First PictureBox. Why is this? and How do I fix it?
Any help will be appreciated
Thank you
Nathanael
Loading
christian de wetPosted Aug 13, 2007, 1:52 AM
Thanks again for your help! the example you provided looks good. I'll check it out. I've heard of clipping before but never really knew how it could be used in this context. So things are made a bit more clear to me now. Thanks.
I'll let you know if I have any further questions. There's always some problem that pops up unexpectantly.
Thanks for the help and the effort.
Regards,
Nathanael
Richard BlythePosted Aug 10, 2007, 1:59 PM
I'm glad to see someone who is mindful of cpu slowdowns. As to layers, it isn't necessarily a bad idea but layers are general implemented to help an end user keep things in perspective. You are absolutely right about having to draw "thousands of polygons" when you edit one shape. However, there is a great method called "Clipping" that eliminates this need to draw everything. MS has used it for years and we can use it ourselves. We can implement it without a custom class but writing our own would be much more concise. I haven't got a lot of time but let me explain it like this:
Imagine you have four rectangles that are being drawn directly to a windows Form:
**** ****
*1** *2**
**** ****
**** ****
*3** *4**
**** ****
Now conventionaly speaking, if you wanted to update rectangle 4's color to red you would change the color property to red and call the this.Invalidate() function. The entire form would be repainted and all the rectangles would be updated.
This is where the clipping method can help us. Lets say that rectangle 4's coordinates are 80 pixels from the left and 80 pixels from the top and the width and height are 20 pixels. We can now use the this.Invalidate() function but we will now pass the clipping area to paint: this.Invalidate(new Rectangle(80,80,20,20));
Now only the area inside the bounds of the 4th rectangle will bw repainted. If you had a million objects outside those bounds they would not get updated. Pretty cool huh? Art programs have used this method for years to prevent long refreshes.
The only thing is that all the objects are still sent to the graphics object to be drawn. (even though they will not be). This is where a custom class would be handy. Here is a rough draft of a good custom class. Add a blank code file and insert this class code.
This is fairly lengthy but I think it will help you get started.
//BEGIN CODE (Paste below)
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing.Drawing2D;
using System.Drawing;
public class ShapeManager Shapes = new List();
{
//All shapes will be exposed through this collection
public List
public void PaintShapes(Graphics g)
{
//note: the clipping area should already be set
//in the graphics object through this.Invalidate()
//We will now loop through all the shapes and paint
//only those that are part of the clipping rectangle.
short intCount = ((short)Shapes.Count);
for (short intNum = 0; intNum < intCount; intNum++)
{
//Don't send shape to graphics object if it's bounds
//do not fall inside the clipbounds
if (Shapes[intNum].IsInsideClipBounds(g.ClipBounds))
{
if (Shapes[intNum].Path != null)
{
//Sample paint code
g.FillPath(new SolidBrush(Shapes[intNum].FillColor), Shapes[intNum].Path);
g.DrawPath(new Pen(Shapes[intNum].LineColor, Shapes[intNum].Path));
}
}
}
}
}
public class MyShape
{
#region Properties
private GraphicsPath _Path ;
public GraphicsPath Path
{
get { return _Path; }
set { _Path = value; }
}
private Color _LineColor ;
public Color LineColor
{
get { return _LineColor; }
set { _LineColor = value; }
}
private Color _FillColor;
public Color FillColor
{
get { return _FillColor; }
set { _FillColor = value; }
}
#endregion
public MyShape()
{
//Allows us to create a new shape without having to pass
//any initial properties
}
public MyShape(GraphicsPath gp, Color lineColor, Color fillColor)
{
//Initialize the shape
_Path = gp;
_LineColor = lineColor;
_FillColor = fillColor;
}
public bool IsInsideClipBounds(RectangleF clipBounds)
{
return clipBounds.Contains(_Path.GetBounds());
}
}
//END Code *************************
You can now insert the code below into your forms load event. note: You must create an instance of the ShapeManager class at the top of the form class. I have called mine: "myshapeManager"
//Insert into Form's Load routine
//Create Shape One
GraphicsPath gp = new GraphicsPath();
gp.AddRectangle(new Rectangle(20, 20, 20, 20));
MyShape newShape = new MyShape(gp, Color.Black, Color.Red);
//Create Shape Two.......... (Alternate Method)
MyShape newShape2 = new MyShape();
GraphicsPath gp1 = new GraphicsPath();
gp1.AddRectangle(new Rectangle(80, 20, 20, 20));
newShape2.Path = gp1;
newShape2.LineColor = Color.Black;
newShape2.FillColor = Color.Blue;
//add shapes to collection
myShapeManager.Shapes.Add(newShape);
myShapeManager.Shapes.Add(newShape2);
//End CODE ****************************************
Whew! Were almost through. You now need to trap the form's OnPaint() event.
Copy this code directly below the Form's Load routine:
//Begin Code
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
myShapeManager.PaintShapes(e.Graphics);
}
//End CODE *************************
For testing purposes, create a button and inside it's click event, put a Invalidation (paint) command.
SOmething like:
private void btnTestInvalidation_Click(object sender, EventArgs e)
{
//will trigger the "OnPaint" method
this.Invalidate(new Rectangle(0,0,100,100));
}
I hope this has helped you. If you have any other questions please let me know. The reason for being so lengthy is that I had the same issues some time back and I'm willing to help anybody who struggles with them. You'll probably be teaching me before its all over!
Your programming friend,
Richard
christian de wetPosted Aug 9, 2007, 12:45 PM
Thanks for the reply. I'm currently busy with creating a rendering engine - so to speak - of a GIS app using GDI+. The main problem I have is the speed of rendering objects because they continuously need to be redrawn. I can currently render a map, edit shapes, and draw lines and polygons. All is well until I need to draw objects on a "canvas" object contaning many thousands of polygons. Each time I draw an object it needs to redraw all those polygons. This is mainly due to having extra functionality for editing and selecting the polgons. So I thought it would be better to have three layers: 1st one with the main loaded map; 2nd for added and selected shapes in the map, 3rd for dragging a select box. Each layer would be a PictureBox. This would ensure that each layer will be seperate from each other and all the polygons would not have to be redrawn due to the dynamic moving shapes (added lines, SelectBox).
Thanks for the help. You made mention of a class that I can rather construct instead of using a PictureBox. I would be very greatful if you could help me a bit on this. You can just give me the basic workings of something like this. If I need more info at a later stage I'll keep in touch.
Thanks for your help.
Kind regards,
Nathanael
Richard BlythePosted Aug 8, 2007, 11:05 AM
First of all I don't how you are able to see through one picturebox to another. I have tried and all I get is the standard background color. You must be setting that property in code.
I'm not sure I can answer your question about the Graphics.Clear() problem. However, using a picturebox is really not the best option to begin with. A MUCH better option would be to create your own class that can blow past the functionality of the picturebox by a long shot! If you need help in creating this class, I'll be more than happy to assist you. Once you start using you OWN classes, I think you'll agree there's nothing better!