A nice extension exists for ZedGraph to plot markers/points (PointObj.cs). However, I am having trouble rendering the point on the graph.
When I call the ZedGraph.Invalidate() function, the marker is drawn momentarily but then disappears.
In the following code, the variable zedGraph is the visible graph object on the form UI.
// Create point
ZedGraph.PointObj point = new ZedGraph.PointObj(5, 10000, 50, 50, ZedGraph.SymbolType.Square, Color.Green);
ZedGraph.PaneBase paneBase = zedGraph.GraphPane;
point.Fill = new ZedGraph.Fill(Color.Green);
System.Drawing.Graphics graphics = zedGraph.CreateGraphics();
// Draw point to graph
point.Draw(graphics, paneBase, paneBase.CalcScaleFactor());
// Re-draw graph, but point only flashes momentarily.
zedGraph.Invalidate();
EDIT: I realise there are other ways of adding "points", such as described here (Labelling and circle a specific point in zedgraph). But it would be still good to know why this doesn't work.
Try adding the point to the GraphObjList after creation
zedGraph.GraphPane.GraphObjList.Add(point);
Related
What I'm doing
I am working on a C#/.NET 4.7.2/WinForms app that draws a significant number of filled rectangles on a form using Graphics.FillRectangle.
Currently, the rectangles are drawn in the form's Paint event. After all the rectangles are drawn, a crosshair is drawn based on mouse position.
Whenever the mouse moves, Invalidate is called on the form to force a repaint so that the crosshair appears in its new position.
The problem
This is inefficient because the rectangles don't change, only the crosshair position, yet the rectangles are being redrawn every time. The CPU usage during mouse move is significant.
What next
I believe that the solution to this problem is to draw the rectangles to a buffer first (outside of the Paint event). Then, the Paint event only needs to render the buffer plus draw a crosshair on top.
Since I am new to GDI+ and manual buffering, I am wary of going down the wrong track. Google searches reveal plenty of articles on manual buffering, but each article seems to take a different approach which adds to my confusion.
I would be grateful for suggested approaches that favour simplicity and efficiency. If there is an idiomatic .NET way of doing this — the way it's meant to be done — I'd love to know.
Here's a quick and easy solution that doesn't require any buffering. To replicate this, start with a fresh Windows Forms project. I only draw two rectangles, but you can have as many as you want.
If you create a new WinForms project with these two member variables and these two handlers, you will get a working sample.
First, a couple of member variables for your form:
private bool _started = false;
private Point _lastPoint;
The started flag will turn to true after the first mouse move. The _lastPoint field will track the point at which the last cross-hairs was drawn (that's mostly why _started exists).
The Paint handler will draw the cross hairs every time it's called (you'll see why this is ok with the MouseMove handler):
private void Form1_Paint(object sender, PaintEventArgs e)
{
var graphics = e.Graphics;
var clientRectangle = this.ClientRectangle;
//draw a couple of rectangles
var firstRectangle = clientRectangle;
firstRectangle.Inflate(-20, -40);
graphics.FillRectangle(Brushes.Aqua, firstRectangle);
var secondRectangle = clientRectangle;
secondRectangle.Inflate(-100, -4);
graphics.FillRectangle(Brushes.Red, secondRectangle);
//draw Cross-Hairs
if (_started)
{
//horizontal
graphics.DrawLine(Pens.LightGray, new Point(clientRectangle.X, _lastPoint.Y),
new Point(ClientRectangle.Width + clientRectangle.X, _lastPoint.Y));
//vertical
graphics.DrawLine(Pens.LightGray, new Point(_lastPoint.X, clientRectangle.Y),
new Point(_lastPoint.X, ClientRectangle.Height + clientRectangle.Y));
}
}
Now comes the MouseMove handler. It's where the magic happens.
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
var clientRectangle = this.ClientRectangle;
var position = e.Location;
if (clientRectangle.Contains(position))
{
Rectangle horizontalInvalidationRect;
Rectangle verticalInvalidationRect;
if (_started)
{
horizontalInvalidationRect = new Rectangle(clientRectangle.X,
Math.Max(_lastPoint.Y - 1, clientRectangle.Y), clientRectangle.Width, 3);
verticalInvalidationRect = new Rectangle(Math.Max(_lastPoint.X - 1, clientRectangle.X),
clientRectangle.Y, 3, clientRectangle.Height);
Invalidate(horizontalInvalidationRect);
Invalidate(verticalInvalidationRect);
}
_started = true;
_lastPoint = position;
horizontalInvalidationRect = new Rectangle(clientRectangle.X,
Math.Max(_lastPoint.Y - 1, clientRectangle.Y), clientRectangle.Width, 3);
verticalInvalidationRect = new Rectangle(Math.Max(_lastPoint.X, clientRectangle.X - 1),
clientRectangle.Y, 3, clientRectangle.Height);
Invalidate(horizontalInvalidationRect);
Invalidate(verticalInvalidationRect);
}
}
If the cursor is within the form, I do a bunch of work. First I declare two rectangles that I will be using for invalidate. The horizontal one will be a rectangle that fills the width of the client rectangle, but is only 3 pixels high, centered on the Y coordinate of the area that I want to invalidate. The vertical one is as high as the client rectangle, but only 3 pixels wide. It's centered on the X coordinate of the area that I want to invalidate.
When the Paint handler runs, it virtually paints the entire client area, but only the pixels in the total invalidated area actually get drawn on the screen. Anything outside the invalidate area is left alone.
So, when the mouse moves, I create two rectangles (one vertical, one horizontal) that surround where the last set of cross-hairs were (so that when the pixels in those rectangles are drawn (including the background), the old cross-hairs are effectively erased) and then I create two new rectangles surrounding where the current cross-hairs should go (causing the background and the new cross-hairs to be drawn).
You are going to want to learn about invalidation rectangles if you have a complicated drawing app. For example, when the form is resized, what you want to do is invalidate only the newly unveiled rectangle(s), so that the whole drawing doesn't need to be rendered.
This works, but picking a color (or a brush) for the cross-hairs so that they always show can be difficult. Using my other suggestion (that you draw the lines twice (one to erase, one to draw) using an INVERT (i.e. XOR) brush is faster, and it always shows.
I'm trying to make something similar to paint. I'm trying to figure out how make different brush styles. Like in Paint 3D you get a certain line fills when using the pen tool vs using the paint brush tool.
I have no idea where to even start. I've spent a good portion of the day looking through documentations, and watching YouTube videos. I'm more lost than when I started. The closest thing I came across was line caps, but that's definitely not what I'm looking for.
!!See the UPDATE below!!
Hans' link should point you in the right direction, namely toward TextureBrushes.
To help you further here a few points to observe:
TextureBrush is a brush, not a pen. So you can't follow a path, like the mouse movements to draw along that curve. Instead, you need to find an area to fill with the brush.
This also implies that you need to decide how and when to trigger the drawing; basic options are by time and/or by distance. Usually, the user can set parameters for these often called 'flow' and 'distance'..
Instead of filling a simple shape and drawing many of those, you can keep adding the shapes to a GraphicsPath and fill that path.
To create a TextureBrush you need a pattern file that has transparency. You can either make some or download them from the web where loads of them are around, many for free.
Most are in the Photoshop Brush format 'abr'; if they are not too recent (<=CS5) you can use abrMate to convert them to png files.
You can load a set of brushes to an ImageList, set up for large enough size (max 256x256) and 32bpp to allow alpha.
Most patterns are black with alpha, so if you want color you need to create a colored version of the current brush image (maybe using a ColorMatrix).
You may also want to change its transparency (best also with the ColorMatrix).
And you will want to change the size to the current brush size.
Update
After doing a few tests I have to retract the original assumption that a TextureBrush is a suitable tool for drawing with textured tips.
It is OK for filling areas, but for drawing free-hand style it will not work properly. There are several reasons..:
one is that the TextureBrush will always tile the pattern in some way, flipped or not and this will always look like you are revealing one big underlying pattern instead of piling paint with several strokes.
Another is that finding the area to fill is rather problematic.
Also, tips may or may not be square but unless you fill with a rectangle there will be gaps.
See here for an example of what you don't want at work.
The solution is really simple and much of the above still applies:
What you do is pretty much regular drawing but in the end, you do a DrawImage with the prepared 'brush' pattern.
Regular drawing involves:
A List<List<Point>> curves that hold all the finished mouse paths
A List<Point> curentCurve for the current path
In the Paint event you draw all the curves and, if it has any points, also the current path.
For drawing with a pattern, it is necessary to also know when to draw which pattern version.
If we make sure not to leak them we can cache the brush patterns..:
Bitmap brushPattern = null;
List<Tuple<Bitmap,List<Point>>> curves = new List<Tuple<Bitmap,List<Point>>>();
Tuple<Bitmap, List<Point>> curCurve = null;
This is a simple/simplistic caching method. For better efficiency you could use a Dictionary<string, Bitmap> with a naming scheme that produces a string from the pattern index, size, color, alpha and maybe a rotation angle; this way each pattern would be stored only once.
Here is an example at work:
A few notes:
In the MouseDown we create a new current curve:
curCurve = new Tuple<Bitmap, List<Point>>(brushPattern, new List<Point>());
curCurve.Item2.Add(e.Location);
In the MouseUp I add the current curve to the curves list:
curves.Add(new Tuple<Bitmap, List<Point>>(curCurve.Item1, curCurve.Item2.ToList()));
Since we want to clear the current curve, we need to copy its points list; this is achieved by the ToList() call!
In the MouseMove we simply add a new point to it:
if (e.Button == MouseButtons.Left)
{
curCurve.Item2.Add(e.Location);
panel1.Invalidate();
}
The Paint goes over all curves including the current one:
for (int c = 0; c < curves.Count; c++)
{
e.Graphics.TranslateTransform(-curves[c].Item1.Width / 2, -curves[c].Item1.Height / 2);
foreach (var p in curves[c].Item2)
e.Graphics.DrawImage(curves[c].Item1, p);
e.Graphics.ResetTransform();
}
if (curCurve != null && curCurve.Item2.Count > 0)
{
e.Graphics.TranslateTransform(-curCurve.Item1.Width / 2, -curCurve.Item1.Height / 2);
foreach (var p in curCurve.Item2)
e.Graphics.DrawImage(curCurve.Item1, p);
e.Graphics.ResetTransform();
}
It makes sure the patterns are drawn centered.
The ListView is set to SmallIcons and its SmallImageList points to a smaller copy of the original ImageList.
It is important to make the Panel Doublebuffered! to avoid flicker!
Update: Instead of a Panel, which is a Container control and not really meant to draw onto you can use a Picturebox or a Label (with Autosize=false); both have the DoubleBuffered property turned on out of the box and support drawing better than Panels do.
Btw: The above quick and dirty example has only 200 (uncommented) lines. Adding brush rotation, preview, a stepping distance, a save button and implementing the brushes cache takes it to 300 lines.
I'm trying to use the following code to copy a portion of the screen to a new location on my Windows form.
private void Form1_Paint(object sender, PaintEventArgs e)
{
var srcPoint = new Point(0,0);
var dstPoint = new Point(Screen.PrimaryScreen.Bounds.Width/2, Screen.PrimaryScreen.Bounds.Height/2);
var copySize = new Size(100, 100);
e.Graphics.CopyFromScreen(srcPoint, dstPoint, copySize, CopyPixelOperation.SourceCopy);
}
The CopyFromScreen function appears to ignore any clips set before it.
e.SetClip(new Rectangle(srcPoint.X, srcPoint.Y, 20, 20));
Am I doing something wrong or is this just the wrong approach.
For context: I'm trying to mitigate a UI widescreen game issue by copying the HUD at the edges and to be centered closer to the middle.
I am aware of FlawlessWidescreen, but it doesn't support many less popular games. I suppose poking around in memory (what flawless does) could also work but is almost always against TOS.
Edit: final goal is to copy some arbitrary path as the shape rather than a simple rectangle (I was hoping from an image mask).
Edit #2:
So I have an irregular shape being drawn every 100ms. It turns out it just bogs the game down until I slow it down to every 500ms. But still the game isn't smooth. Is this operation of copying and drawing an image just going to be too heavy of an operation in GDI+? I was thinking it was simple enough to not bog anything down.
Thoughts before I mark the answer as accepted?
I guess it is indeed the wrong approach.
The ClippingRegion is only used for clipping the DrawXXX and FillXXX commands, including DrawImage (!).
The CopyFromScreen however will use the given Points and Size and not clip the source.
For a Rectangle region this is no problem since you can achieve the same result by choosing the right Point and Size values.
But once you aim at using more interesting clipping regions you will have to use an intermediate Bitmap onto which you copy from the screen and from which you can then use DrawImage into the clipped region.
For this you can create more or less complicated GraphicsPaths.
Here is a code example:
After putting the cliping coordinates into a Rectangleor GraphicsPath clip you can write something like this:
e.Graphics.SetClip(clip);
using (Bitmap bitmap = new Bitmap(ClientSize.Width, ClientSize.Height))
{
using (Graphics G = Graphics.FromImage(bitmap))
G.CopyFromScreen(dstPoint, srcPoint,
copySize, CopyPixelOperation.SourceCopy);
e.Graphics.DrawImage(bitmap, 0, 0);
}
Shouldn't it be
e.Graphics.Clip = myRegion;
I'm gerenerally new to coding and i would like to seek some help to guide me about how to make a drawable triangle on the inkcanvas. I've created a button. Upon clicking on the button, i'm supposed to click in the inkcanvas and drag to form the triangle. (Work the same as any shapes to be drawn on the paint application in windows and microsoft words.)
I'm quite lost about how to go about doing this as i need to do a few other random shapes as well. I was hoping to be able to know how to create a triangle and use that knowledge i get from here to try to do the remaining other shapes that i will be doing.
Thanks so much for your time!
You can use mouse click event to catch the points -> Class: Point(x,y);
Then you can use DrawLine() to draw line between two Points.
E.g
Graphics g = e.Graphics;
Pen p = new Pen(Color.Red);
Point p1 = new Point(50,50);
Point p2 = new Point(1,1);
g.DrawLine(p, p1, p2);
g.Dispose();
Here are two good examples:
http://www.techotopia.com/index.php/Drawing_Graphics_in_C_Sharp
http://www.java2s.com/Code/CSharp/2D-Graphics/Drawshapestothebitmapinmemory.htm
I exhaustively used the search but have not been able to find a satisfying solution to my problem.
I programmed a data visualization using a chart (datavisualization.charting.chart). This chart changes steadily since it shows some kind of simulation results.
I would like to draw for example a line upon the chart (depending on the mouse position) in order to show context sensitive information.
Therefore, I tried two implementations, none of both works exactly as I would like it to:
plotLine is called on MouseMove- Event
gGraph is created from the underlying graph using: chartCreateGraphics();
protected void plotLine(object sender, System.EventArgs e)
{
if (this.chart.Series.Count > 0) //ensure that the chart shows data
{
plotChart(); // this plots the underlying chart
penGraph = new Pen(Color.Black);
Point point1 = new Point(Form1.MousePosition.X - chart.Location.X, 0);
Point point2 = new Point(Form1.MousePosition.X - chart.Location.X,
chart.Location.Y + chart.Size.Height);
gGraph.DrawLine(penGraph, point1, point2);
penGraph.Dispose();
}
}
Here, the line disappears each time immediately after being plotted, but should remain as long as the mouse is not moved.
protected void plotLine(object sender, System.EventArgs e)
{
penGraph = new Pen(Color.Black);
Point point1 = new Point(Form1.MousePosition.X - chart.Location.X, 0);
Point point2 = new Point(Form1.MousePosition.X - chart.Location.X,
chart.Location.Y + chart.Size.Height);
gGraph.DrawLine(penGraph, point1, point2);
penGraph.Dispose();
}
Here, all plotted lines remain in the graphics surface as long as the chart is not plotted by new. (only the last line should remain in order to indicate the mouse position)
Can anybody help me?
You should be drawing in the OnPaint event. You are circumventing the update model and are seeing the effects of doing so. Sure, you do some drawing in MouseMove, but when the Paint event fires it will simply be erased.
Put your code in OnPaint first. On mouse move simply record whatever data you need to (i.e., mouse position) and then call Invalidate() on the chart. When you do that the Paint event will be called and your drawing code will fire.
Rule of thumb; never draw from anywhere but the Paint event.