I wrote a code to draw a line using the mouse.
On mouse down i save where the user clicked.
bool mZgc_MouseDownEvent(ZedGraphControl sender, MouseEventArgs e)
{
GraphPane graphPane = mZgc.GraphPane;
graphPane.ReverseTransform(e.Location, out mMouseDownX, out mMouseDownY);
return false;
}
On mouse up i draw the line:
bool zgc_MouseUpEvent(ZedGraphControl sender, MouseEventArgs e)
{
GraphPane graphPane = mZgc.GraphPane;
double x, y;
graphPane.ReverseTransform(e.Location, out x, out y);
LineObj threshHoldLine = new LineObj(Color.Red, mMouseDownX, mMouseDownY, x, y);
graphPane.GraphObjList.Add(threshHoldLine);
mZgc.Refresh();
return false;
}
The problem is that while the mouse is down, the user don't see the line (because i draw it only on "up" event).
How can i solve it ?
Technically I can use "on hover" and draw/remove the line from the graph each second and refresh the graph, but it's a bit crazy.
Is there a "normal" way to do this ?
Thanks
Ok, I've solved the problem I'll post the answer for future generations.
First of all we need to make a minor change in the zedgraph code to allow ourselves to change lines after they created, It probably breaks some "immutable" paradigm that the creators are trying to achieve, but if you want to avoid creating and deleting lines every time the mouse is moving that's the way.
In the file Location.cs edit the X2,Y2 properties (set was missing):
public double X2
{
get { return _x+_width; }
set { _width = value-_x; }
}
public double Y2
{
get { return _y+_height; }
set { _height = value-_y; }
}
from here it is easy, i won't post all the code but will explain the steps:
Add a member to your class : private LineObj mCurrentLine;
On mouse down create a line mCurrentLine = new LineObj(Color.Red, mMouseDownX, mMouseDownY, mMouseDownX, mMouseDownY);
On mouse move change the line X2,Y2 coordinates mCurrentLine.Location.X2 = x;
On mouse up just stop that drawing process (to avoid changing the line in "on mouse move"
If someone actually going to use it, and needs better explanation, just comment.
Related
I am making an application in which I load a bunch of 256x256px chunks of a map and stitch them together with magick.net to then display them in a picture box. I added a feature for slewing the image (and loading new picture chunks as I do) using mouse events on the picture box control like so:
// Set true when mouse should slew the picture in the picture box
private bool isSlewing = false;
// last position of the mouse in map space when slewing
private float lastX = 0;
private float lastY = 0;
private void Pb_MouseMove(object sender, MouseEventArgs e)
{
// slew while mouse is down.
if (isSlewing)
{
Tuple<float, float> mouseMapCoordinates = MouseToMap(e.X, e.Y);
Slew(mouseMapCoordinates.Item1 - lastX, mouseMapCoordinates.Item2 - lastY);
lastX = mouseMapCoordinates.Item1;
lastY = mouseMapCoordinates.Item2;
}
}
private void Pb_MouseDown(object sender, MouseEventArgs e)
{
isSlewing = true;
Tuple<float, float> mouseMapCoordinates = MouseToMap(e.X, e.Y);
lastX = mouseMapCoordinates.Item1;
lastY = mouseMapCoordinates.Item2;
}
private void Pb_MouseUp(object sender, MouseEventArgs e)
{
isSlewing = false;
}
// Slews map image by dx and dY
public void Slew(float dX, float dY)
{
X -= dX;
Y -= dY;
}
// Converts mouse coordinates into map coordinates
private Tuple<float,float> MouseToMap(int mouseX, int mouseY)
{
// works
}
X and Y are properties that invalidate the picture box by setting a boolean needsUpdate to true. A timer is run and every 200ms it calls the update function which builds the picture based on X and Y . There also is a zoom but that's unimportant for now and works fine.
The issue is that when I click and drag the map it works in principle but every so often the map "jumps" back to a previous mouse position.
I just don't know how to go about trying to find out what's causing it or how to fix it.
I would like the whole program to be a bit more responsive to begin with, it feels sluggish, which is why I introduced the timer to begin with, so it doesn't draw updates on every little change dozens of times per second as I move the mouse a tiny amount.
I think it has to do with some kind of race condition that sets lastX and lastY between one slew and the next with the update drawing things in between. The update function takes a few milliseconds to complete, as it loads the images from disk and composites them and finally draws them back to the picture box.
The whole code is available at github. Map data is not included, but can be downloaded with the script in Assets. For testing I recommend deleting all but one map from the json file there. All map data in total is about 3GB.
I tried putting lock() around the block inside the if statement in Pb_MouseMove, but that didn't help anything.
The issue was with converting the mouse coordinates into map space outside of slew because the map space moves with the viewport. To fix the issue you save the raw mouse position in lastX and lastY, put the raw delta for the mouse coordinate into the slew function and then multiply by a scaling factor. The scaling factor is how many map coordinate units are used per pixel.
IsMouseOverMarker property detects clicking on marker just fine, but when trying to use IsMouseOverPolygon property of GMap Control to detect if user clicked on polygon line - it doesn't seem to be working.
Note: PolygonEnabled property of GMap control is set to True.
The OnPolygonClick event doesn't even fire:
private void gMap_OnPolygonClick(GMapPolygon item, MouseEventArgs e) {
double pLat = item.From.Value.Lat;
}
Map Click event does fire, but the 'IsMouseOverPolygon` never gets True value:
private void gMap_Click(object sender, EventArgs e) {
if (gMap.IsMouseOverMarker) {
MessageBox.Show("Clicked on marker and it works!");
}
if (gMap.IsMouseOverPolygon) {
MessageBox.Show("clicked on line - never works");
}
}
I wonder if there is something wrong in a way I'm adding polygons or is it because in my case it's just lines:
GMapOverlay polyOverlay = new GMapOverlay("polygons");
gMap.Overlays.Add(polyOverlay);
List<PointLatLng> points = new List<PointLatLng>();
points.Add(start);
points.Add(end);
polygon = new GMapPolygon(points, "mypolygon");
polygon.Stroke = new Pen(Color.Blue, 5);
polyOverlay.Polygons.Add(polygon);
So, the question is: how should I go about detecting mouse click on those lines?
I can see two issues within the code. First you need to explicitely define the polygon as HitTestVisible:
polygon.IsHitTestVisible = true;
Second, to set up a polygon add at least three points that are not aligned and actually spawn an area. I've found that the click will only be noticed on an actual area, where in theory a polygon can consist of two points.
With the hints above the check for gMap.IsMouseOverPolygon should return true then.
I'm trying to make a little graphics program that has a circle of diameter 100 on the screen and from the center of it, a line is coming out of it that is always attached to the mouse pointer until such time that the user does a click, and then the line is permanently drawn. It's exactly like MSPaint's line, except that starting point is always the center of the circle.
I tried a few things that DON'T work.
I can get the line to appear only after a mouse-click. That's not what I want. I want the line to always be present and pivoting from the circle-center until the mouse is clicked and then it's then permanently on the screen.
I can get a smeary thing where the line is always being drawn. It makes a sort of star shape, but that's not what I want either.
Basically, I want the same functionality that you have in MSPaint when you draw a line. What am I supposed to do? Draw the line and then erase it a second later, and then draw it again when the mouse is in a new position? I tried something like that, but it does a thing where it erases the background a little bit, and then the line is only drawn when the mouse is in motion, but not when the mouse is stationary.
If anyone can provide a code snippet, that'd be great. Or just some pseudo-code.
Is this the right pseudo code?
Start:
Left click and a line appears from center of circle to mouse tip
Line stays there until a new mouse coordinate is made (how do I keep track)?
Line from center of circle to original location gets erased
New line is made to new location of mouse coordinates.
I think this something of a state-machine to use what I learned in digital class. How are states implemented in C#?
Any help would be appreciated, and thanks to everyone that can understand my question even though I'm probably not using the proper terminology.
So short answer is you will need some custom painting. The longer answer involves custom drawing, and event handling.
The other piece of code you need is a list of some sort to hold all of the lines. The code below creates a user control and does the custom painting without relying on a state machine. To test it, create a new project add a user control called UserControl1, and add it to a form. Make sure you tie into the listed events.
I tried to comment the relevant sections and this shows a quick and dirty way to do what you appear to be trying to do.
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
namespace CustomDrawingAndEvents
{
public partial class UserControl1 : UserControl
{
private struct MyLine
{
public Point mStart;
public Point mEnd;
public MyLine(Point xStart, Point xEnd)
{
mStart = xStart;
mEnd = xEnd;
}
}
private List<MyLine> mLines;
private Point mCircleCenter;
private Point mMousePosition;
public UserControl1()
{
InitializeComponent();
mLines = new List<MyLine>();
//Double Buffer to prevent flicker
DoubleBuffered = true;
//Create the center for our circle. For this just put it in the center of
//the control.
mCircleCenter = new Point(this.Width / 2, this.Height / 2);
}
private void UserControl1_MouseClick(object sender, MouseEventArgs e)
{
//User clicked create a new line to add to the list.
mLines.Add(new MyLine(mCircleCenter, e.Location));
}
private void UserControl1_MouseMove(object sender, MouseEventArgs e)
{
//Update mouse position
mMousePosition = e.Location;
//Make the control redraw itself
Invalidate();
}
private void UserControl1_Paint(object sender, PaintEventArgs e)
{
//Create the rect with 100 width/height (subtract half the diameter to center the rect over the circle)
Rectangle lCenterRect = new Rectangle(mCircleCenter.X - 50, mCircleCenter.Y - 50, 100, 100);
//Draw our circle in the center of the control with a diameter of 100
e.Graphics.DrawEllipse(new Pen(Brushes.Black), lCenterRect);
//Draw all of our saved lines
foreach (MyLine lLine in mLines)
e.Graphics.DrawLine(new Pen(Brushes.Red), lLine.mStart, lLine.mEnd);
//Draw our active line from the center of the circle to
//our mouse location
e.Graphics.DrawLine(new Pen(Brushes.Blue), mCircleCenter, mMousePosition);
}
}
}
Hello it might be a silly question but i can't figure out the problem here.. here is my code to fill a form with a single block:
private void drawBackground()
{
Graphics g = genPan.CreateGraphics();
Image Block = Image.FromFile(#"C:\Users\Administrator\Desktop\movment V1\movment V1\images\BrownBlock.png");
float recWidth = Block.Width;
// rectangle width didnt change the name from previous code, it's picture width.
float recHeight = Block.Height;
// rectangle Heightdidnt change the name from previous code, it's picture Height.
float WinWidth = genPan.Width; // genPan is a panel that docked to the form
float WinHeight = genPan.Height;
float curWidth = 0; //indicates where the next block will be placed int the X axis
float curHeight = 0;//indicates where the next block will be placed int the Y axis
while ((curHeight + recHeight) <= WinHeight)
{
if (curWidth >= WinWidth / 3 || curWidth <= WinWidth / 1.5 ||
curHeight >= WinHeight / 3 || curHeight <= WinHeight / 1.5)
{
g.DrawImage(Block, curWidth, curHeight, recWidth , recHeight );
}
curWidth += recWidth;
if ((WinWidth - curWidth) < recWidth)
{
curWidth = 0;
curHeight += 50;
}
}
}
If I launch this func through a button it will work perfectly fine. But if I launch the func after the InitializeComponent(); method in the constructor OR in a FORM shown event, while the button is still on the form it will execute the func however the block backgroud wont be visible but the grey color will be. but if i remove the button the background will be visible. =\
I cant understand why is it happening, how to fix it and what am I doing wrong.. can anyone explain please..?
You can't really do that using your current logic. The problem is that the control (genPan panel in your case) has its own Paint event that when called, overwriting any graphics you used on it.
Even when you draw in button click it works only until the form is repainted e.g. try focus other window and focus your form again: you will lose what you have drawn.
Proper way to do such things is to write your own class that inherit from some basic control (Panel in your case) then overriding its OnPaint event and draw whatever you want there.
So first, have such class:
public class BlockBackgroundPanel : Panel
{
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
Image Block = Image.FromFile(#"C:\Users\Administrator\Desktop\movment V1\movment V1\images\BrownBlock.png");
float recWidth = Block.Width;
//rest of your code, replace "genPan" with "this" as you are inside the Panel
}
}
Then in your .Designer.cs file (you can open it in the Studio) change the code so that genPan will be of your new class instance:
private BlockBackgroundPanel genPan;
//...
this.genPan = new BlockBackgroundPanel ();
If you need to draw background only based on some condition/action/user interaction...
Put the call to this funciton into the forms OnPaint method and enable it only if some bollean variale equals true. And that boolean becomes true, only on button click.
Some hypothetical example:
protected override OnPaint(...) //FORMS ONPAINT OVERRIDE
{
if(needBackGround) //INITIAL VALUE AT STARTUP IS FALSE
drawBackground();
}
public void ButtonClickHandler(...)
{
needBackGround= !needBackGround; //INVERSE THE VALUE OF BOOLEAN
}
This is clearly just a sniplet to give you a hint and not a real code. There could be other problems you will need to face, like: flickering, handling resize, performance... but this is just a point to start.
I'm using C# chart control to draw a nyquist plot. Now i want data points appear on the curve each time the user moves the mouse on it. So i used hit test method in GetToolTipText event.
private void BodePlot_GetToolTipText(object sender, ToolTipEventArgs e)
{
HitTestResult result = BodePlot.HitTest(e.X, e.Y);
selectDataPoint = null;
if (result.ChartElementType == ChartElementType.DataPoint)
{
selectDataPoint = (DataPoint)result.Object;
e.Text = selectDataPoint.ToString();
}
{
The problem is only a part of the curve shows values, others don't. When i use e.Text = result.Object.ToString(); to get the object on which the mouse is pointing to, here what i found :
Instead of showing the data points, the text on tooltip show custom label. So i guess the reason is that the curve is covered by the labels of x and y axis.
The only solution that i found is disabling the x and y axis, with that everything works fine. But i want to keep those axes, so how can i make those labels hide under the curve.
Your analysis is likely correct. The way to go around this would be to provide HitTest() with the optional third argument which define the desired element type.
public HitTestResult HitTest (
int x,
int y,
ChartElementType requestedElement
)
This should return underlying data points even if other elements are overlapping them.