I am using OxyPlot for charting in my application. I want to extend the OxyPlot library to include a rubber-banding line series, similar to how a polyline might rubber-band in a CAD application.
I have written an example for this.
[Example("LineSeries rubberbanding")]
public static PlotModel MouseRubberbandingEvent()
{
var model = new PlotModel("Rubberbanding",
"Left click to add line and press Esc to end.")
{
LegendSymbolLength = 40
};
// Add a line series
var s1 = new LineSeries("LineSeries1")
{
Color = OxyColors.SkyBlue,
MarkerType = MarkerType.Circle,
MarkerSize = 6,
MarkerStroke = OxyColors.White,
MarkerFill = OxyColors.SkyBlue,
MarkerStrokeThickness = 1.5
};
model.Series.Add(s1);
s1.Points.Add(new DataPoint(10,
10));
IDataPoint tempDataPoint = new DataPoint(0,0);
s1.Points.Add(tempDataPoint);
// Remember to refresh/invalidate of the plot
model.RefreshPlot(false);
bool isRubberbanding = false;
// Subscribe to the mouse down event on the line series
model.MouseDown += (s, e) =>
{
// only handle the left mouse button (right button can still be used to pan)
if (e.ChangedButton == OxyMouseButton.Left)
{
s1.Points.Add(s1.InverseTransform(e.Position));
isRubberbanding = true;
// Remember to refresh/invalidate of the plot
model.RefreshPlot(false);
// Set the event arguments to handled - no other handlers will be called.
e.Handled = true;
}
};
// Subscribe to the mouse down event on the line series
s1.MouseDown += (s, e) =>
{
// only handle the left mouse button (right button can still be used to pan)
if (
(e.ChangedButton == OxyMouseButton.Left)
&&
(isRubberbanding)
)
{
s1.Points.Add(s1.InverseTransform(e.Position));
isRubberbanding = true;
// Remember to refresh/invalidate of the plot
model.RefreshPlot(false);
// Set the event arguments to handled - no other handlers will be called.
e.Handled = true;
}
};
model.MouseMove += (s, e) =>
{
if (isRubberbanding)
{
var point = s1.InverseTransform(new ScreenPoint(e.Position.X-8,
e.Position.Y-8));
tempDataPoint.X = point.X;
tempDataPoint.Y = point.Y;
s1.Points.Remove(tempDataPoint);
s1.Points.Add(tempDataPoint);
model.RefreshPlot(false);
}
};
model.MouseUp += (s, e) =>
{
if (isRubberbanding)
{
s1.LineStyle = LineStyle.Solid;
model.RefreshPlot(false);
e.Handled = true;
}
};
return model;
}
The rubber-banding works fine when I offset the mouse cursor by 8 pixels. However, when I place the cursor to be just beneath the rubber-banding line, OxyPlot does not fire on the mouse down event for either the plot model or for the line series.
Please suggest why the mouse down event is not firing. I have raised the same question to OxyPlot, but there is no reply to this.
Related
The following behavior allows you to drag a popup around with the mouse.
How can it be modified to constrain the element being dragged to some parent element, eg the Application window?
I don't want to allow the popup to be dragged out of the main window.
public class MouseDragPopupBehavior : Behavior<Popup>
{
private bool mouseDown;
private Point oldMousePosition;
protected override void OnAttached()
{
AssociatedObject.MouseLeftButtonDown += (s, e) =>
{
mouseDown = true;
oldMousePosition = AssociatedObject.PointToScreen(e.GetPosition(AssociatedObject));
AssociatedObject.Child.CaptureMouse();
};
AssociatedObject.MouseMove += (s, e) =>
{
if (!mouseDown) return;
var newMousePosition = AssociatedObject.PointToScreen(e.GetPosition(AssociatedObject));
var offset = newMousePosition - oldMousePosition;
oldMousePosition = newMousePosition;
AssociatedObject.HorizontalOffset += offset.X;
AssociatedObject.VerticalOffset += offset.Y;
};
AssociatedObject.MouseLeftButtonUp += (s, e) =>
{
mouseDown = false;
AssociatedObject.Child.ReleaseMouseCapture();
};
}
}
It's better to constrain popup not to the window itself, but to the root control of the window, because you likely want to restrict it to the client area of the window (without titlebar and such), and the easiest way I'm aware to get that client area is to just work with the root control inside window.
In case of popup, it's also easier to work with root element inside, because interactions between popup itself and visual tree are complicated.
What you need is to get the bounding rectangle of your popup in screen coordinates, the bounding rectange of contraining control (the root of the window) in screen coordinates, and check if one contains the other. Here is sample code:
public class MouseDragPopupBehavior : Behavior<Popup>
{
private bool mouseDown;
private Point oldMousePosition;
private FrameworkElement constraint;
protected override void OnAttached() {
// obtain root of the window (might fail in general case, adjust to your specific case if necessary)
constraint = (FrameworkElement) Application.Current.MainWindow.Content;
AssociatedObject.MouseLeftButtonDown += (s, e) =>
{
mouseDown = true;
oldMousePosition = AssociatedObject.PointToScreen(e.GetPosition(AssociatedObject));
AssociatedObject.Child.CaptureMouse();
};
AssociatedObject.MouseMove += (s, e) =>
{
if (!mouseDown) return;
var newMousePosition = AssociatedObject.PointToScreen(e.GetPosition(AssociatedObject));
var offset = newMousePosition - oldMousePosition;
// get root control of popup (might fail in general case, adjust if necessary)
var popupRoot = (FrameworkElement)AssociatedObject.Child;
// obtain (x,y) of popup in screen coordinates
var popupLeftTop = popupRoot.PointToScreen(new Point(0, 0));
// obtain bounds
var popupBounds = new Rect(popupLeftTop.X + offset.X, popupLeftTop.Y + offset.Y, popupRoot.ActualWidth, popupRoot.ActualHeight);
// do the same with contraint control
var constraintLeftTop = constraint.PointToScreen(new Point(0, 0));
var constraintBounds = new Rect(constraintLeftTop.X, constraintLeftTop.Y, constraint.ActualWidth, constraint.ActualHeight);
// if bounding rect of constraint does not contain popup - that means popup is not fully inside the constraint, then do not apply offset
if (!constraintBounds.Contains(popupBounds))
return;
// otherwise continue
oldMousePosition = newMousePosition;
AssociatedObject.HorizontalOffset += offset.X;
AssociatedObject.VerticalOffset += offset.Y;
};
AssociatedObject.MouseLeftButtonUp += (s, e) =>
{
mouseDown = false;
AssociatedObject.Child.ReleaseMouseCapture();
};
}
}
I am trying to create such that when I click RMB a drop down list/menu is created. My definition of the drop down menu will be attached below. I am stuck at the event handler, I filtered if the even is a LMB or RMB and then I am having issues from here.
Firstly my requirement, I have designed a map when I click RMB I need a drop down so I can select suitable options.
My Drop down definition
this.UserControl_MissionPlan_MapPanel_MourseRightClick = new ComboBox();
this.UserControl_MissionPlan_MapPanel_MourseRightClick.Location = new System.Drawing.Point(700, 700);
this.UserControl_MissionPlan_MapPanel_MourseRightClick.Size = new System.Drawing.Size(120, 120);
this.UserControl_MissionPlan_MapPanel_MourseRightClick.DropDownStyle = ComboBoxStyle.DropDownList;
this.UserControl_MissionPlan_MapPanel_MourseRightClick.MaxLength = 5;
this.UserControl_MissionPlan_MapPanel_MourseRightClick.Items.Add(1);
this.UserControl_MissionPlan_MapPanel_MourseRightClick.Items.Add(2);
this.UserControl_MissionPlan_MapPanel_MourseRightClick.Items.Add(3);
this.UserControl_MissionPlan_MapPanel_MourseRightClick.Items.Add(4);
this.UserControl_MissionPlan_MapPanel_MourseRightClick.Items.Add(5);
And finally my event handler
private void UserControl_MissionPlan_MapPanel_MouseClick(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
double WaypointLatitude = UserControl_MissionPlan_MapPanel.FromLocalToLatLng(e.X, e.Y).Lat;
double WaypointLongitude = UserControl_MissionPlan_MapPanel.FromLocalToLatLng(e.X, e.Y).Lng;
// MessageBox.Show(Convert.ToString(WaypointLatitude));
// MessageBox.Show(Convert.ToString(WaypointLongitude));
// GMapOverlay NavigationWayPoint = new GMapOverlay("NavigationWayPoint");
// GMapMarker WayPoint = new GMarkerGoogle(new PointLatLng(WaypointLatitude, WaypointLongitude), GMarkerGoogleType.orange_small);
// NavigationWayPoint.Markers.Add(WayPoint);
// UserControl_MissionPlan_MapPanel.Overlays.Add(NavigationWayPoint);
}
else if (e.Button == System.Windows.Forms.MouseButtons.Right)
{
// MessageBox.Show("Right Mouse button Mouse clicked");
}
}
Windows Forms ZedGraph control in WPF application. The data points are auto-generated and attached to the chart every N seconds. When new data point is added to the chart I shift (pan) chart one point to the left, so there is always no more than last 50 points visible in the viewport. Overall, it works pretty good, except for two things.
Issues
If user tries to zoom in or out, the viewport stops following the last data point and chart goes outside of the screen, so panning stops working
I would like to pan or shift chart using mouse event, without scrolling, but when I press right mouse button and try to move it to the left, it tries to zoom chart instead of panning
protected void CreateChart(ZedGraphControl control)
{
_rand = new Random();
var curve = control.GraphPane.AddJapaneseCandleStick("Demo", new StockPointList());
curve.Stick.IsAutoSize = true;
curve.Stick.Color = Color.Blue;
control.AutoScroll = true; // Always shift to the last data point when new data comes in
control.IsEnableHPan = true; // I assume this should allow me to move chart to the left using mouse
control.IsEnableVPan = true;
control.IsEnableHZoom = true;
control.IsEnableVZoom = true;
control.IsShowPointValues = true;
control.IsShowHScrollBar = false;
control.IsShowVScrollBar = false;
control.IsAutoScrollRange = true; // Always shift to the last data point when new data comes in
control.IsZoomOnMouseCenter = false;
control.GraphPane.XAxis.Type = AxisType.DateAsOrdinal;
control.AxisChange();
control.Invalidate();
var aTimer = new Timer();
aTimer.Elapsed += new ElapsedEventHandler(OnTime);
aTimer.Interval = 100;
aTimer.Enabled = true;
}
protected XDate _xDate = new XDate(2006, 2, 1);
protected double _open = 50.0;
protected Random _rand = new Random();
// Auto generate data points
protected void OnTime(object source, ElapsedEventArgs e)
{
var control = FormCharts;
var x = _xDate.XLDate;
var close = _open + _rand.NextDouble() * 10.0 - 5.0;
var hi = Math.Max(_open, close) + _rand.NextDouble() * 5.0;
var low = Math.Min(_open, close) - _rand.NextDouble() * 5.0;
var pt = new StockPt(x, hi, low, _open, close, 100000);
_open = close;
_xDate.AddDays(1.0);
if (XDate.XLDateToDayOfWeek(_xDate.XLDate) == 6)
{
_xDate.AddDays(2.0);
}
(control.GraphPane.CurveList[0].Points as StockPointList).Add(pt);
control.GraphPane.XAxis.Scale.Min = control.GraphPane.XAxis.Scale.Max - 50; // Hide all points except last 50, after mouse zooming this line stops working
//control.GraphPane.XAxis.Scale.Max = control.GraphPane.XAxis.Scale.Max + 1;
control.AxisChange();
control.Invalidate();
}
Kind of solved it.
First issue with broken auto-scroll after zooming
It happens because zooming sets these parameters to FALSE.
area.XAxis.Scale.MinAuto = false;
area.XAxis.Scale.MaxAuto = false;
To fix it, either set it back to TRUE every time new data point comes. Another way to fix it, is to keep them always as FALSE and move chart manually
protected void MoveChart(GraphPane pane, int pointsToMove, int pointsToShow)
{
pane.XAxis.Scale.Max = pane.XAxis.Scale.Max - pointsToMove;
pane.XAxis.Scale.Min = pane.XAxis.Scale.Max - Math.Abs(pointsToShow);
}
...
// Example : shift one point to the left and show only 50 last points
MoveChart(control.MasterPane.PaneList["Quotes"], -1, 50);
Second issue, implementing custom panning without scrollbar using mouse events.
protected int _mouseX = -1;
protected int _mouseY = -1;
...
control.MouseUpEvent += OnMouseUp;
control.MouseDownEvent += OnMouseDown;
control.MouseMoveEvent += OnMouseMove;
...
// Example : remember X and Y on mouse down and move chart until mouse up event
protected bool OnMouseUp(object sender, System.Windows.Forms.MouseEventArgs e)
{
_mouseX = -1; // unset X on mouse up
_mouseY = -1;
return true;
}
protected bool OnMouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
{
_mouseX = e.X; // remember last X on mouse down
_mouseY = e.Y;
return true;
}
protected bool OnMouseMove(ZedGraphControl sender, System.Windows.Forms.MouseEventArgs e)
{
if (_mouseX >= 0) // if X was saved after mouse down
{
foreach (var pane in sender.MasterPane.PaneList) // move synced chart panels
{
MoveChart(pane, _mouseX > e.X ? -1 : 1, 50); // if mouse move is increasing X, move chart to the right, and vice versa
}
_mouseX = e.X; // update X to new position
_mouseY = e.Y;
}
return true;
}
the user should be able to draw a straight line on a panel similar to drawing a straight line in paint .
the user clicks on the panel and when he moves the mouse the line should also move along with the mouse (i.e similar to drawing a staright line in paint) and when the user releases the mouse the line should have been drawn from the original point of click to this release point .
i.e not a free hand line.
is there any animation for this ?
How about this? :
public class LinePanel : Panel
{
public LinePanel()
{
this.MouseDown += (src, e) => { LineStartPos = LineEndPos = e.Location; Capture = true; Invalidate(); };
this.MouseMove += (src, e) => { if (Capture) { LineEndPos = e.Location; Invalidate(); } };
this.MouseUp += (src, e) => { if (Capture) { LineEndPos = e.Location; } Capture = false; Invalidate(); };
}
private Point LineStartPos, LineEndPos;
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
if (LineStartPos != LineEndPos)
e.Graphics.DrawLine(new Pen(Color.Black, 2), LineStartPos, LineEndPos);
}
}
To test you can just add a new LinePanel() to the Controls collection of your form, and set location/size or anchor / dock paramaters to size it.
I have two controls on the same form. Both controls contain an ObjectListView control. The one listview was created with the graphical editor in visual studio. This one is not causing any issues. The other listview is created programmatically at run-time. I have defined an event handler for each control that gets called when the hot item changes and they are both firing when they should. Both event handlers call the same code to update a picturebox control. The problem is that the picturebox does not get updated when the programmatically defined listview is asking it to. I am positive the event handler is getting called because my code writes to a text file as well as updating the picture box. The text file gets updated but the picture box does not. I have tried updating, invalidating, and refreshing the PicutureBox as well as the parent form, but I just can not get it to update.
I am not sure if this is an ObjectListView issue or a standard WinForms problem. I realize my question is very vague but I am not sure how to clarify it without posting all my code. Any advice would be appreciated.
Here is the code that the event handler calls:
public void ShowBitmap(object sender, HotItemChangedEventArgs e, ObjectListView lv, string type)
{
ObjectListView olv = sender as ObjectListView;
if (sender == null)
{
return;
}
switch (e.HotCellHitLocation)
{
case HitTestLocation.Nothing:
break;
case HitTestLocation.Group:
break;
case HitTestLocation.GroupExpander:
break;
default:
if (e.HotColumnIndex == 0)
{
pictureBox1.Hide();
pictureBox1.BorderStyle = BorderStyle.FixedSingle;
int rowIndex = e.HotRowIndex;
string text = "";
if (type == "Main Parts")
{
TypedObjectListView<MainRadanProjectPartsPart> tlist = new TypedObjectListView<MainRadanProjectPartsPart>(lv);
text = tlist.Objects[rowIndex].Symbol;
}
else if (type == "Parts")
{
TypedObjectListView<RadanProjectPartsPart> tlist = new TypedObjectListView<RadanProjectPartsPart>(lv);
text = tlist.Objects[rowIndex].Symbol;
}
else if (type == "Nests")
{
TypedObjectListView<MainRadanProjectNestsNest> tlist = new TypedObjectListView<MainRadanProjectNestsNest>(lv);
text = tlist.Objects[rowIndex].FileName;
}
if (text != null)
{
Point screenCoords = Cursor.Position;
Point controlRelatedCoords = lv.PointToClient(screenCoords);
if (controlRelatedCoords.Y < oldCursorPosition.Y)
{
pictureBox1.Location = controlRelatedCoords;
int xPos = controlRelatedCoords.X;
int yPos = controlRelatedCoords.Y + 60;
pictureBox1.Location = new Point(xPos, yPos);
}
else if (controlRelatedCoords.Y > oldCursorPosition.Y)
{
pictureBox1.Location = controlRelatedCoords;
int xPos = controlRelatedCoords.X;
//int yPos = controlRelatedCoords.Y - pictureBox1.Height;
int yPos = controlRelatedCoords.Y - pictureBox1.Height + 30;
pictureBox1.Location = new Point(xPos, yPos);
}
pictureBox1.Show();
pictureBox1.BringToFront();
olvTreeViewMainParts.Focus();
lv.Focus();
pictureBox1.Visible = true;
DrawSymbol(text);
oldCursorPosition = controlRelatedCoords; // save the cursor position to track cursor direction between calls
}
else
{
DrawSymbol("");
}
}
else
{
pictureBox1.Hide();
}
break;
}
}
Here is the event handler for the programmaticaly defined listview:
// track the cursor as it moves over the items in the listview
private void olvPartsListView_HotItemChanged(object sender, HotItemChangedEventArgs e)
{
ShowBitmap(sender, e, olvPartsListView, "Parts");
}