Drawn shape in Picturebox doesn't clear - c#

I am trying to show a train that moves on the map .so let me explain my method ,i draw my map on Picturebox Map,and my trains on another picturebox train,i put the Train PictureBox on map picturebox .
More details:https://stackoverflow.com/a/9158849/2538037
So i use two function here :
public void DrawMap()
{
var graph = Graphics.FromImage(map);
List<Point> lstPointLeft = new List<Point>();
foreach (var t in lstSensorLeft)
{
Point objPoint = new Point(t.XLocation, t.YLocation);
lstPointLeft.Add(objPoint);
Rectangle rectSens = new Rectangle(t.XLocation, t.YLocation, 3, 3);
try
{
graph.FillRectangle(whiteBrush, rectSens);
}
catch (Exception ea)
{
}
if (t.StationId != null)
{
Rectangle rectEhsansq = new Rectangle(t.XLocation - 6, t.YLocation - 6, 12, 12);
graph.FillRectangle(blueBrush, rectEhsansq);
graph.DrawString(ObjStationRepository.FindBy(i => i.Id == t.StationId).First().Name, pictureBoxMetroMap.Font, Brushes.White, t.XLocation +40, t.YLocation +50);
}
}
List<Point> lstPointRight = new List<Point>();
foreach (var t in lstSensorRight)
{
Point objPoint = new Point(t.XLocation + 30, t.YLocation + 30);
lstPointRight.Add(objPoint);
Rectangle rectSens = new Rectangle(t.XLocation + 30, t.YLocation + 30, 3, 3);
graph.FillRectangle(whiteBrush, rectSens);
if (t.StationId != null)
{
Rectangle rectPosition = new Rectangle(t.XLocation + 24, t.YLocation + 24, 12, 12);
graph.FillRectangle(blueBrush, rectPosition);
graph.DrawString(ObjStationRepository.FindBy(i => i.Id == t.StationId).First().Name, pictureBoxMetroMap.Font, Brushes.White, t.XLocation - 50, t.YLocation - 30);
}
}
graph.DrawLines(pLine, lstPointLeft.ToArray());
graph.DrawLines(pLine, lstPointRight.ToArray());
pictureBoxMetroMap.Image = map;
}
This function draws map ,and this function draws my trains on another picturebox:
public void DrawOnlineTrain()
{
var graph = Graphics.FromImage(map);
if (OnlineTrainList.Count > 0)
{
foreach (OnlineTrain t in OnlineTrainList.ToList())
{
// graph.Dispose();
Rectangle rectTrainState = new Rectangle(t.XTrainLocation.Value - 3,
t.YTrainLocation.Value - 3,
7, 7);
graph.FillRectangle(RedBrush, rectTrainState);
}
}
pictureBoxonlineTrain.Image = map;
}
So i use a thread to update the Train picturebox ,i call the thread in form_load :
private void frmMain_Load(object sender, EventArgs e)
{
pictureBoxonlineTrain.Parent = pictureBoxMetroMap;
map= new Bitmap(pictureBoxMetroMap.Size.Width, pictureBoxMetroMap.Size.Height);
UpdateListBox = new UpdateListBoxDelegate(this.UpdateStatus);
// Initialise and start worker thread
workerThread = new Thread(new ThreadStart(this.GetOnlineTrain));
workerThread.Start();
}
So in the thread i start a method that gets the location of online train:
public void GetOnlineTrain()
{
while(true)
{
OnlineTrainRepository objOnlineTrainRepository = new OnlineTrainRepository();
OnlineTrainList = objOnlineTrainRepository.GetAll().ToList();
objOnlineTrainRepository = null;
Invoke(UpdateListBox);
}
}
Here i start UpdateListBox that draw my Train:
private void UpdateStatus()
{
foreach (OnlineTrain onlineTrain in OnlineTrainList.ToList())
{
lstLog.Items.Add("Train Id=" + onlineTrain.TrainId + " | Current x position=" + onlineTrain.XTrainLocation + " | Current y position=" + onlineTrain.YTrainLocation);
pictureBoxonlineTrain.Image = null;
DrawOnlineTrain();
}
}
As you can see here to show movement i have to clear the old location of trains ,and i do that using :
pictureBoxonlineTrain.Image = null;
But it doesn't work ,and every rectangle is remain on my screen ?!!!
Best regards

1. To address your problem directly
you never clear the bitmap! Note that you are drawing on top of everything that is already there when using Graphics.FromImage
you use one and the same Bitmap object for all drawing. So (in combination with the previous point) you basically have the entire "scene" in map all the time - no need to have several PictureBoxes in that case!
be careful: if a PictureBox refresehes while you're drawing, the unfinished state will be visible! You are manipulating the very image that is shown.
2. What I would do
render everything to one buffer (you are using Bitmap which is fine, but maybe consider using BufferedGraphics)
render that to a control whenever you like (or it's Paint event fires) instead of using a PictureBox

Related

Draw circle in new position without removing previous circle?

In fact , I want to draw circle in new position each time double-click and without remove before circle ,It should be noted that, I used PictureBox.
public Point postionCursor { get; set; }
List<Point> points = new List<Point>();
private void pictureBox1_DoubleClick(object sender, EventArgs e)
{
postionCursor = this.PointToClient(new Point(Cursor.Position.X - 25, Cursor.Position.Y - 25));
points.Add(postionCursor);
pictureBox1.Invalidate();
pictureBox1.Paint += new PaintEventHandler(pic_Paint);
}
private void pic_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
g.SmoothingMode = SmoothingMode.AntiAlias;
foreach (Point pt in points)
{
Pen p = new Pen(Color.Tomato, 2);
SolidBrush myb = new SolidBrush(Color.White);
g.DrawEllipse(p, postionCursor.X, postionCursor.Y, 20, 20);
g.FillEllipse(myb, postionCursor.X, postionCursor.Y, 20, 20);
p.Dispose();
}
}
You're not using the pt variable in the foreach loop.
foreach (Point pt in points)
{
using(Pen p = new Pen(Color.Tomato, 2))
using(SolidBrush myb = new SolidBrush(Color.White))
{
g.FillEllipse(myb, pt.X, pt.Y, 20, 20);
g.DrawEllipse(p, pt.X, pt.Y, 20, 20);
}
}
In your code, you were just overwriting the circle in the same location for every Point in the points list.
Also, as Reza mentioned in the comments, you don't need to attach the PaintEventHandler event hanlder every time the PictureBox is clicked, you just need to do it once.
So I got to thinking, and then Visual Studio-ing, that perhaps we don't even need the foreach loop. I still maintain a List so we know where the user has clicked, but there's no need to loop through it and redraw everything every time.
I realize this doesn't handle the case where the underlying list is modified, but nor does the original sample. Here's my entire Form1 class:
public partial class Form1 : Form
{
private const int CircleDiameter = 20;
private const int PenWidth = 2;
private readonly List<Point> _points = new List<Point>();
public Form1()
{
InitializeComponent();
pictureBox1.Paint += (sender, args) =>
{
_points.ForEach(p => DrawPoint(p, args.Graphics));
};
}
private void pictureBox1_DoubleClick(object sender, EventArgs e)
{
var cursorLocation = pictureBox1.PointToClient(Cursor.Position);
_points.Add(cursorLocation);
var circleArea = new Rectangle(
cursorLocation.X - CircleDiameter/2 - PenWidth,
cursorLocation.Y - CircleDiameter/2 - PenWidth,
CircleDiameter + PenWidth*2,
CircleDiameter + PenWidth*2);
pictureBox1.Invalidate(circleArea);
}
private static void DrawPoint(Point point, Graphics graphics)
{
point.X -= CircleDiameter / 2;
point.Y -= CircleDiameter / 2;
using (var pen = new Pen(Color.Tomato, PenWidth))
using (var brush = new SolidBrush(Color.White))
{
graphics.SmoothingMode = SmoothingMode.AntiAlias;
graphics.DrawEllipse(pen, point.X, point.Y, CircleDiameter, CircleDiameter);
graphics.FillEllipse(brush, point.X, point.Y, CircleDiameter, CircleDiameter);
}
}
}
Update 1:
So I updated the code to use the Paint event which has the foreach loop. However, I don't Invalidate (and Paint) every time a circle is added - there's no need for that. Just adding a circle by drawing means the control only invalidates and re-paints the region where the new circle was added.
Try setting a breakpoint on the DrawAllPoints method. You'll see it only happens during full invalidation operations such as minimizing and restoring.
Update 2:
After further chat, I agree the Invalidate method is superior. Code updated to use Invalidate with a rectangle to invalidate.
And now it's looking very much like the OP :)

How do I draw a transparent rectangle?

I am nearly there with this ... :)
I have implemented my own double buffer ...
So I create a bitmap:
if (_Bitmap != null) _Bitmap.Dispose();
if (_Graphics != null) _Graphics.Dispose();
_Bitmap = new Bitmap(Bounds.Width, Bounds.Height);
_Bitmap.MakeTransparent(Color.Transparent);
_Graphics = Graphics.FromImage(_Bitmap);
_Graphics.FillRectangle(Brushes.Transparent, Bounds);
I thought that I might have to manually set the bitmap as transparent.
In my handlers OnPaint method it does this:
protected override void OnPaint(PaintEventArgs e)
{
if (_pDevice != null)
{
try
{
_pDevice.update();
_Graphics.ReleaseHdc();
if (_bZoomWindow)
{
//_Graphics.DrawRectangle(_selectionPen, _rcRubberBand);
using (GraphicsPath gp = new GraphicsPath())
{
gp.AddRectangle(_rcRubberBand);
gp.Widen(_selectionPen);
_Graphics.FillPath(Brushes.WhiteSmoke, gp);
}
}
OdRxDictionary Properties = _graphicsDevice.properties();
//if (helperDevice.UnderlyingDevice.Properties.Contains("WindowHDC"))
// helperDevice.UnderlyingDevice.Properties.AtPut("WindowHDC", new RxVariant((Int32)graphics.GetHdc()));
if (Properties.ContainsKey("WindowHDC"))
Properties.putAt("WindowHDC", new OdRxVariantValue(_Graphics.GetHdc().ToInt32())); // hWnd necessary for DirectX device
}
catch (System.Exception ex)
{
_Graphics.DrawString(ex.ToString(), new Font("Arial", 16), new SolidBrush(Color.Black), new PointF(150.0F, 150.0F));
}
e.Graphics.DrawImageUnscaled(_Bitmap, 0, 0);
}
}
The problem is that the rectangle is drawing with a black background. So it is obliterating the drawing underneath that is on the bitmap:
How do I draw just the rectangle? What am I missing? I am sorry if this is a dumb question!
Painting with transparency is unfortunately only supported in one way: By applying the RGB channels in the strenght of the alpha value.
With alpha = 0 nothing happens.
Other modes are desirable but not supported in GDI+ drawing.
One simple workaround is to turn off antialiasing, draw in a weird color you don't need and then call MakeTransparent.
Bitmap bmp = new Bitmap(244, 244, PixelFormat.Format32bppArgb);
Color funnyColor = Color.FromArgb(255, 123, 45, 67);
using (Graphics g = Graphics.FromImage(bmp))
{
g.Clear(Color.LawnGreen);
using (SolidBrush br = new SolidBrush(funnyColor ))
{
// no anti-aliased pixels!
g.SmoothingMode = SmoothingMode.None;
// draw your stuff..
g.FillEllipse( br , 14, 14, 88, 88);
}
bmp.MakeTransparent(funnyColor );
// do what you want..
bmp.Save(someFileName, ImageFormat.Png);
}
Of course you can use all DrawXXX methods including FillPath or DrawRectangle.
The result is a green bitmap with a transparent hole in it. Here it is in Paint.Net:
For other modes, that maybe would copy the alpha channel or mix it with the previous value you would have to write routines of your own or find a lib that has it, but I think this should be all you need atm.
Edit by Andrew Truckle
The proposed answer is really good. However, since I was using Teigha.Net as the basis of the application, in the end I went with this code:
protected override void OnMouseMove(MouseEventArgs e)
{
if (_bZoomWindowing)
UpdateRubberBandRectangle(e.Location);
if (_bPanWindowMode)
UpdateRubberBandLine(e.Location);
base.OnMouseMove(e);
}
private void UpdateRubberBandRectangle(Point Location)
{
// Do we need to erase the old one?
if (!_rcLastRubberBand.IsEmpty)
{
using (Region r = new Region(Rectangle.Inflate(_rcLastRubberBand, 2, 2)))
{
r.Exclude(Rectangle.Inflate(_rcLastRubberBand, -2, -2));
_pDevice.invalidate(new OdGsDCRect(_rcLastRubberBand.Left - 2, _rcLastRubberBand.Right + 2,
_rcLastRubberBand.Top - 2, _rcLastRubberBand.Bottom + 2));
Invalidate(r);
}
}
// Draw the new one
if (!_selectionStart.IsEmpty && !_selectionEnd.IsEmpty && _selectionEnd != Location)
{
_rcLastRubberBand = _rcRubberBand;
_selectionEnd = Location;
_rcRubberBand = GetSelectionRectangle(_selectionStart, _selectionEnd);
using (Region r = new Region(Rectangle.Inflate(_rcRubberBand, 2, 2)))
{
r.Exclude(Rectangle.Inflate(_rcRubberBand, -2, -2));
_pDevice.invalidate(new OdGsDCRect(_rcRubberBand.Left - 2, _rcRubberBand.Right + 2,
_rcRubberBand.Top - 2, _rcRubberBand.Bottom + 2));
Invalidate(r);
}
}
}
private void UpdateRubberBandLine(Point Location)
{
// Do we need to erase the last rubber band line? (Rectangle already expanded by 2 pixels)
if (!_rcLastRubberBandLine.IsEmpty)
{
using (Region r = new Region(_rcLastRubberBandLine))
{
_pDevice.invalidate(new OdGsDCRect(_rcLastRubberBandLine.Left, _rcLastRubberBandLine.Right,
_rcLastRubberBandLine.Top, _rcLastRubberBandLine.Bottom));
Invalidate(r);
}
}
// Draw the new one now
_RubberLineEnd = Location;
_rcLastRubberBandLine = GetSelectionRectangle(_RubberLineStart, _RubberLineEnd);
_rcLastRubberBandLine.Inflate(2, 2);
using (Region r = new Region(_rcLastRubberBandLine))
{
_pDevice.invalidate(new OdGsDCRect(_rcLastRubberBandLine.Left, _rcLastRubberBandLine.Right,
_rcLastRubberBandLine.Top, _rcLastRubberBandLine.Bottom));
Invalidate(r);
}
}
Notice that I am making use of Region objects. Also, the invalidating is being handled by OdGsDevice _pDevice which is a Teigha object. In my situation this worked fabulously.

Select drawn figure within panel box

I am working on an 'use-case-diagram-form' where an user can select an element and a modus
Just a simple form. It al works fine, made a class for each actor element and each use-case element. Both are added in a list after beeing created.
But somehow I just can't figure out how to select a created element and after do something with it.
classes i made:
class Actor
{
private static int _id;
private Panel _panel;
public string Name { get; private set; }
public int X { get; private set; }
public int Y { get; private set; }
public Actor(Panel panel, string name, int x, int y)
{
_id++;
_panel = panel;
Name = name;
X = x;
Y = y;
}
public void DrawActor()
{
// draw Actor
var graphics = _panel.CreateGraphics();
var pen = new Pen(Color.Black, 2);
graphics.DrawEllipse(pen, X - 10, Y - 30, 20, 20);
graphics.DrawLine(pen, X, Y - 10, X, Y + 20);
graphics.DrawLine(pen, X - 15, Y, X + 15, Y);
graphics.DrawLine(pen, X, Y + 20, X - 15, Y + 35);
graphics.DrawLine(pen, X, Y + 20, X + 15, Y + 35);
// rectangle around actor
graphics.DrawRectangle(pen, (X - 20), (Y - 30), 40, 80);
// setup font
var stringFont = new Font("Arial", 10);
// measure string
var textWith = graphics.MeasureString(Name, stringFont).Width;
// label
var label = new Label();
var actorText = (_id < 10 ? "0" : "") + _id.ToString() + "-" + Name;
label.Text = actorText;
label.Location = new Point(X - (Convert.ToInt32(textWith)/2), Y + 40);
label.AutoSize = true;
label.BorderStyle = BorderStyle.FixedSingle;
_panel.Controls.Add(label);
}
class UseCase
{
private static int _id;
private Panel _panel;
private string _name;
private int _x;
private int _y;
public UseCase(Panel panel, string name, int x, int y)
{
_id++;
_panel = panel;
_name = name;
_x = x;
_y = y;
}
public void DrawUseCase()
{
var graphics = _panel.CreateGraphics();
var pen = new Pen(Color.Black, 2);
graphics.DrawEllipse(pen, _x , _y , 120, 50);
// setup font
var stringFont = new Font("Arial", 10);
// measure string
var textWith = graphics.MeasureString(_name, stringFont).Width;
// label
var label = new Label();
var useCaseText = (_id < 10 ? "0" : "") + _id.ToString() + "-" + _name;
label.Text = useCaseText;
label.Location = new Point(_x - (Convert.ToInt32(textWith) / 2) + 60, _y + 20);
label.AutoSize = true;
label.BorderStyle = BorderStyle.FixedSingle;
_panel.Controls.Add(label);
}
}
Github repository:
https://github.com/JimVercoelen/use-case-helper
Thanks
Your code has several issues, all of which will go away once you learn how to draw properly in winforms!
There are many posts describing it but what you need to understand that you really have these two options:
Draw onto the surface of the control. This is what you do, but you do it all wrong.
Draw into a Bitmap which is displayed in the control, like the Picturbox's Image or a Panel's BackgroundImage.
Option two is best for graphics that slowly add up and won't need to be corrected all the time.
Option one is best for interactive graphics, where the user will move things around a lot or change or delete them.
You can also mix the options by caching drawn graphics in a Bitmap when they get too numerous.
Since you started with drawing onto the surface let's see how you should do it correctly:
The Golden Rule: All drawing needs to be done in the control's Paint event or be triggered from there always using only the Paint event's e.Graphics object!
Instead you have created a Graphics object by using control.CreateGraphics. This is almost always wrong.
One consequence of the above rule is that the Paint event needs to be able to draw all objects the user has created so far. So you will need to have class level lists to hold the necessary data: List<ActorClass> and List<UseCaseClass>. Then it can do maybe a
foreach(ActorClass actor in ActorList) actor.drawActor(e.Graphics)
etc.
Yes this fully repainting everything looks like a waste but it won't be a problem until you need to draw several hundreds of object.
But if you don't do it this way, nothing you draw persists.
Test it by running your present code and doing a Minimize/Maximize sequence. Poof, all drawings are gone..
Now back to your original question: How to select an e.g. Actor?
This really gets simple as you can can iterate over the ActorList in the MouseClick event (do not use the Click event, as it lacks the necessary parameters):
foreach (ActorClass actor in ActorList)
if (actor.rectangle.Contains e.Location)
{
// do stuff
break;
}
This is a simple implementation; you may want to refine it for the case of overlapping objects..
Now you could do things like maybe change the color of the rectangle or add a reference to the object in a currentActor variable.
Whenever you have made any changes to your lists of things to draw, like adding or deleting a object or moving it or changing any (visible) properties you should trigger an update via the Paint event by calling Invalidate.
Btw: You asked about a PictureBox in the title but use only a Panel in the code. Using a PictureBoxis recommended as it is doublebuffered and also combines two Bitmaps to let you use both a caching Image and a BackgroundImage with maybe a nice paper..
As far as I can see your code so far lacks the necessary classes. When you write them add a Draw routine and either a reference to the Label you add or simply use DrawString to draw the text yourself..
Update:
After looking at your project, here a the minimal changes to make the drawing work:
// private Graphics graphics; // delete!!
Never try to cache a Graphics object!
private void pl_Diagram_Paint(object sender, PaintEventArgs e)
{
pen = new Pen(Color.Black, 1);
DrawElements(e.Graphics); // pass out the 'good' object
//graphics = pl_Diagram.CreateGraphics(); // delete!
}
The same; pass the real Graphics object into the drawing routine instead!
// actor
if (rb_Actor.Checked)
{
if (e.X <= 150)
{
var actor = new Actor(name, e.X, e.Y);
_actors.Add(actor);
pl_Diagram.Invalidate(); // trigger the paint event
//DrawElements();
}
}
// use case
if (rb_Use_Cases.Checked)
{
var useCase = new UseCase(name, e.X, e.Y);
_useCases.Add(useCase);
pl_Diagram.Invalidate(); // trigger the paint event
//DrawElements();
}
Instead of calling the routine directly we trigger the Paint event, which then can pass a good Graphics object to it.
public void DrawElements(Graphics graphics)
{
foreach (var actor in _actors)
{
DrawActor(graphics, actor);
}
foreach (var useCase in _useCases)
{
DrawUseCase(graphics, useCase);
}
}
We receive the Graphics object and pass it on..
private void DrawActor(Graphics graphics, Actor actor)
and
graphics.DrawEllipse(pen, (useCase.X - 60), (useCase.Y - 30), 120, 60);
After these few changes the drawing persists.
Replacing the Panel by a Picturebox is still recommended to avoid flicker during the redraw. (Or replace by a double-buffered Panel subclass..)

Clear drawn rectangle on picturebox to show movement

I try to simulate trains movement in my application ,so i create trains map using this code :
public void DrawMap()
{
Bitmap map = new Bitmap(pictureBoxMetroMap.Size.Width, pictureBoxMetroMap.Size.Height);
var graph = Graphics.FromImage(map);
List<Point> lstPointLeft = new List<Point>();
foreach (var t in lstSensorLeft)
{
Point objPoint = new Point(t.XLocation, t.YLocation);
lstPointLeft.Add(objPoint);
Rectangle rectSens = new Rectangle(t.XLocation, t.YLocation, 3, 3);
try
{
graph.FillRectangle(whiteBrush, rectSens);
}
catch (Exception ea)
{
}
if (t.StationId != null)
{
Rectangle rectEhsansq = new Rectangle(t.XLocation - 6, t.YLocation - 6, 12, 12);
graph.FillRectangle(blueBrush, rectEhsansq);
Label ObjLable = new Label();
ObjLable.ForeColor = Color.Transparent;
ObjLable.Location = new Point(t.XLocation+40, t.YLocation +50);
ObjLable.Text = ObjStationRepository.FindBy(i => i.Id == t.StationId).First().Name;
ObjLable.BackColor = Color.Transparent;
ObjLable.Width = 70;
pictureBoxMetroMap.Controls.Add(ObjLable);
}
}
List<Point> lstPointRight = new List<Point>();
foreach (var t in lstSensorRight)
{
Point objPoint = new Point(t.XLocation + 30, t.YLocation + 30);
lstPointRight.Add(objPoint);
Rectangle rectSens = new Rectangle(t.XLocation + 30, t.YLocation + 30, 3, 3);
graph.FillRectangle(whiteBrush, rectSens);
if (t.StationId != null)
{
Rectangle rectPosition = new Rectangle(t.XLocation + 24, t.YLocation + 24, 12, 12);
graph.FillRectangle(blueBrush, rectPosition);
Label ObjLable = new Label();
ObjLable.ForeColor = Color.Transparent;
ObjLable.Location = new Point(t.XLocation - 50, t.YLocation - 30);
ObjLable.Text = ObjStationRepository.FindBy(i => i.Id == t.StationId).First().Name;
ObjLable.BackColor = Color.Transparent;
ObjLable.Width = 70;
pictureBoxMetroMap.Controls.Add(ObjLable);
}
}
graph.DrawLines(pLine, lstPointLeft.ToArray());
graph.DrawLines(pLine, lstPointRight.ToArray());
pictureBoxMetroMap.Image = map;
// ShowOnlineTrain();
//Thread newThread = new Thread(new ThreadStart(ShowOnlineTrain));
//newThread.Start();
}
So as you can see the DramMap draws my trains map ,i call this function in page_load of my application as you can see here :
private void frmMain_Load(object sender, EventArgs e)
{
UpdateListBox = new UpdateListBoxDelegate(this.UpdateStatus);
// Initialise and start worker thread
workerThread = new Thread(new ThreadStart(this.ShowOnlineTrain));
workerThread.Start();
DrawMap();
}
So as you can see above i call my function and i create a thread in my pageload, so the thread do a vital operation ,it calls a function ShowOnlineTrain ,this function fetch the locations of online trains and i should show these trains on my map :
List<OnlineTrain> OnlineTrainList = new List<OnlineTrain>();
public void ShowOnlineTrain()
{
OnlineTrainRepository objOnlineTrainRepository = new OnlineTrainRepository();
while(true)
{
OnlineTrainList = objOnlineTrainRepository.GetAll().ToList();
Invoke(UpdateListBox);
}
}
private void UpdateStatus()
{
lstLog.Items.Add("Train Id=" + OnlineTrainList.First().TrainId + " | Current x position=" + OnlineTrainList.First().XTrainLocation + " | Current y position=" + OnlineTrainList.First().YTrainLocation);
}
This function fetches the location of online trains .so OnlineTrainList ** has the locations of online trains (i.e x and y and trainId).so i have to show the trains on my map .I call the **Paint event of my picturebox :
private void pictureBoxMetroMap_Paint(object sender, PaintEventArgs e)
{
if (OnlineTrainList.Count > 0)
{
foreach (OnlineTrain t in OnlineTrainList)
{
var g = pictureBoxMetroMap.CreateGraphics();
Rectangle rectTrainState = new Rectangle(t.XTrainLocation.Value - 3,
t.YTrainLocation.Value - 3,
7, 7);
g.FillRectangle(RedBrush, rectTrainState);
}
}
}
It gets all positions of **OnlineTrainList ** and draws them ,but i have a big problem here ,i need to show the movement of my train ,i should clear the old location of my train ,but i don't know how can i do that??? and all positions of my trains is drawn on my picturebox !!any idea ?
Best regards
What if you create 2 same sized PictureBoxes: another one for the map and another over it for the trains, using transparency on the top one. Since they are the same size, the locations match, too: https://stackoverflow.com/a/9158849/2538037
When the form has loaded, you start a BackgroundWorker, which updates trains and locations to the top PictureBox via ProgressChanged-event. You could use it to draw the image and then set it to the top PictureBox. You could use some timing on it, so it would calculate a new updated train-image like every 2 seconds. You should also use Dispose() to the old image after changing in the new one. Remember to use Invalidate() so the PictureBoxes are updated.
If you have assigned an image to the PictureBox using pictureBoxMetroMap.Image =, then you should be able to draw onto the PictureBox surface without disturbing that image.
Now, if you are then drawing additional "map" graphics manually, then you must continue to do so on every Paint cycle. Meaning that Paint must handle redrawing the "map" detail, as well as refreshing the train graphics to simulate movement.
Make sure that the code responsible for recognize or recording train movement data is calling pictureBoxMetroMap.Invalidate(); to trigger Paint calls.

What points to be used for Drawing Hexagonal Shaped Button

Using below code I am able to draw arrow shaped button(shown below) ,but I want to draw hexagone(shown below as result image) ,so that I can use png image of size 175x154 as button image ,What Points I need to use to draw this ? and i need to to draw 6 such buttons ,how do i achieve this ?
private void Parent_Load(object sender, EventArgs e)
{
// Define the points in the polygonal path.
Point[] pts = {
new Point( 20, 60),
new Point(140, 60),
new Point(140, 20),
new Point(220, 100),
new Point(140, 180),
new Point(140, 140),
new Point( 20, 140)
};
// Make the GraphicsPath.
GraphicsPath polygon_path = new GraphicsPath(FillMode.Winding);
polygon_path.AddPolygon(pts);
// Convert the GraphicsPath into a Region.
Region polygon_region = new Region(polygon_path);
// Constrain the button to the region.
btnExam.Region = polygon_region;
// Make the button big enough to hold the whole region.
btnExam.SetBounds(
btnExam.Location.X,
btnExam.Location.Y,
pts[3].X + 5, pts[4].Y + 5);
}
The input should be a Rectangle which contains the Hexagonal shape, from that input we will calculate the Points for your Hexagonal shape, something like this:
public Point[] GetPoints(Rectangle container){
Point[] points = new Point[6];
int half = container.Height / 2;
int quart = container.Width/4;
points[0] = new Point(container.Left + quart, container.Top);
points[1] = new Point(container.Right - quart, container.Top);
points[2] = new Point(container.Right, container.Top + half);
points[3] = new Point(container.Right - quart, container.Bottom);
points[4] = new Point(container.Left + quart, container.Bottom);
points[5] = new Point(container.Left, container.Top + half);
return points;
}
private void Parent_Load(object sender, EventArgs e) {
//This should be placed first
// Make the button big enough to hold the whole region.
btnExam.SetBounds( btnExam.Location.X, btnExam.Location.Y, 100, 100);
// Make the GraphicsPath.
GraphicsPath polygon_path = new GraphicsPath(FillMode.Winding);
polygon_path.AddPolygon(GetPoints(btnExam.ClientRectangle));
// Convert the GraphicsPath into a Region.
Region polygon_region = new Region(polygon_path);
// Constrain the button to the region.
btnExam.Region = polygon_region;
}
You should update the Region whenever your btnExam's Size changes, so you should define some method called UpdateRegion and call it in a SizeChanged event handler:
private void UpdateRegion(){
GraphicsPath polygon_path = new GraphicsPath(FillMode.Winding);
polygon_path.AddPolygon(GetPoints(btnExam.ClientRectangle));
btnExam.Region = new Region(polygon_path);
}
//SizeChanged event handler for your btnExam
private void btnExam_SizeChanged(object sender, EventArgs e){
UpdateRegion();
}
//Then you just need to change the size of your btnExam in Parent_Load
private void Parent_Load(object sender, EventArgs e) {
//The button should be square
btnExam.SetBounds( btnExam.Location.X, btnExam.Location.Y, 100, 100);
}
Is this what you mean?
var xDisp = 10;
var yDisp = 10;
var length = 10;
var ls32 = (int)(length * Math.Sqrt(3) / 2.0);
var half = (int)(length / 2.0);
var points = new[]
{
new Point(xDisp + length, yDisp),
new Point(xDisp + half, yDisp + ls32),
new Point(xDisp - half, yDisp + ls32),
new Point(xDisp - length, yDisp),
new Point(xDisp - half, yDisp - ls32),
new Point(xDisp + half, yDisp - ls32)
};

Categories

Resources