I need to change the RenderTransformOrigin property of some annotation objects, so that they always rotate around the center (y) of the other side (x).
So let's say the user clicks on the right hand side of the object, then the rotation point would be (0.0, 0.5), but if the user wishes to change the rotation from the other side of the item, then he can click on the left hand side of it, in which case the rotation point becomes (1.0, 0.5).
The problem: the reason I am asking for help is that whenever this happens, the item jumps on the screen. How much it moves, depends on the angle of the item (if it is not rotated at all, then it won't move at all). The angle itself does not change during the movement, only the position. (I realise that it is a bit difficult to understand the problem without images, but due to stackoverflow's rules, I am not allowed to attach images to make things easier.) So this only happens when selecting the other side (e.g. clicking on the left hand side after the right hand side), and the movement/jump depends on the angle (the larger the angle, the larger the jump).
What I would like to do is to change the mentioned property for the rotation to happen around the selected edge of the object, without the object keep jumping up and down when changing side.
Thank you for your help!
Edit [2015-03-10_12-17-21]
class XYZ
{
...
private void RotateThumb_DragStarted(object sender, DragStartedEventArgs e) {
m_rotateTransform = m_designerItem.RenderTransform as RotateTransform;
if (m_rotateTransform == null) {
m_designerItem.RenderTransform = new RotateTransform(0);
m_rotateTransform = m_designerItem.RenderTransform as RotateTransform;
}
Point positionWithinAnnotation = Mouse.GetPosition(m_designerItem);
var selectedThumb = GetThumbPosition(m_designerItem, positionWithinAnnotation);
double relativeCenterX = ((selectedThumb == ThumbPosition.Left) ? (1.0D) : (0.0D));
double relativeCenterY = 0.5D;
m_designerItem.RenderTransformOrigin = new Point(relativeCenterX, relativeCenterY);
m_transformOrigin = m_designerItem.RenderTransformOrigin;
m_rotationPoint = m_designerItem.TranslatePoint(
new Point(relativeCenterX * m_designerItem.ActualWidth,
relativeCenterY * m_designerItem.ActualHeight),
m_canvas);
Point startPoint = Mouse.GetPosition(m_canvas);
m_startVector = Point.Subtract(startPoint, m_rotationPoint);
}
private void handleRotate(DragDeltaEventArgs e) {
Point currentPoint = Mouse.GetPosition(m_canvas);
Vector deltaVector = Point.Subtract(currentPoint, m_rotationPoint);
double angle = Vector.AngleBetween(m_startVector, deltaVector);
m_rotateTransform.Angle += Math.Round(angle, 0);
m_startVector = deltaVector;
m_designerItem.InvalidateMeasure();
}
...
}
Without a good, minimal, complete code example it's difficult to know for sure what you need to do to fix the code.
That said, from the description it sounds to me like you should be using the RotateTransform.CenterX and RotateTransform.CenterY properties, instead of setting RenderTransformOrigin.
For example:
private void RotateThumb_DragStarted(object sender, DragStartedEventArgs e) {
m_rotateTransform = m_designerItem.RenderTransform as RotateTransform;
if (m_rotateTransform == null) {
m_designerItem.RenderTransform = new RotateTransform();
m_rotateTransform = m_designerItem.RenderTransform as RotateTransform;
}
Point positionWithinAnnotation = Mouse.GetPosition(m_designerItem);
var selectedThumb = GetThumbPosition(m_designerItem, positionWithinAnnotation);
double relativeCenterX = ((selectedThumb == ThumbPosition.Left) ? (1.0D) : (0.0D));
double relativeCenterY = 0.5D;
m_rotateTransform.CenterX = relativeCenterX;
m_rotateTransform.CenterY = relativeCenterY;
// Without a complete example, I'm not sure I know what this part is
// supposed to be doing. I think it's fine, but maybe it needs to be
// fixed too?
m_rotationPoint = m_designerItem.TranslatePoint(
new Point(relativeCenterX * m_designerItem.ActualWidth,
relativeCenterY * m_designerItem.ActualHeight),
m_canvas);
Point startPoint = Mouse.GetPosition(m_canvas);
m_startVector = Point.Subtract(startPoint, m_rotationPoint);
}
Related
How do you create an Annotation on-the-run and how do you enable end-user placement with Annotation.BeginPlacement()? I've tried to do this in multiple ways, but cannot get it working. It should render itself in real-time after the BeginPlacement() has been called.
Documentations on this subject is little to none - and mostly none - so I'm not able to find any help for this problem.
What I've tried so far, is to create an annotation and place it with AnchorX/Y, set all Allow- flags to true and called BeginPlacement() while mouse is moving, but cannot see the annotation while placing it nor will it go in it's place accordingly. For example, LineAnnotation starts in right position, but doesn't end where I left it. When I move it so it starts from my ChartAreas {0,0}, it will hit the end-point.
What I want to know, is when and how to use these tools available? What I am trying to do, is to let the user draw annotations on a chart and use as tools when analyzing the charts.
You need to calculate the right positions. Remember that the MouseMove will not give you positions (percentages) or values(data) but pixels. You can transform them using the various axis functions. Officially they only work in a xxxPaint event, but during mouse events they also work fine.
Update: There two ways to do the anchoring:
Either by using the 'Positions', i.e. the percentages or the 'Values', i.e. the data values.
Here is an example of the 1st kind:
LineAnnotation laNew = null;
private void chart1_MouseDown(object sender, MouseEventArgs e)
{
if (cbx_drawAnnotation.Checked)
{
Axis ax = chart1.ChartAreas[0].AxisX;
Axis ay = chart1.ChartAreas[0].AxisY;
laNew = new LineAnnotation();
chart1.Annotations.Add(laNew);
double vx = ax.ValueToPosition(ax.PixelPositionToValue(e.X));
double vy = ay.ValueToPosition(ay.PixelPositionToValue(e.Y));
laNew.X = vx;
laNew.Y = vy;
}
}
private void chart1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button.HasFlag(MouseButtons.Left) && cbx_drawAnnotation.Checked)
{
Axis ax = chart1.ChartAreas[0].AxisX;
Axis ay = chart1.ChartAreas[0].AxisY;
double vx = ax.ValueToPosition(ax.PixelPositionToValue(e.X))- laNew.X;
double vy = ay.ValueToPosition(ay.PixelPositionToValue(e.Y)) - laNew.Y;
laNew.Width = Math.Min(100, vx);
laNew.Height = Math.Min(100, vy);
laNew.LineColor = rb_green.Checked ? Color.Green : Color.Red;
laNew.AllowMoving = true; // optional
}
}
This works fine unles you need to rescale the axis in some way, like changing the axis minimum and/or maximum values.
In the case you need to anchor to data values.
First we need to relate the Annotation to the Axes and also set IsSizeAlwaysRelative to false. Then we can calculate the anchor and size values:
private void chart1_MouseDown(object sender, MouseEventArgs e)
{
if (cbx_drawAnnotation.Checked)
{
Axis ax = chart1.ChartAreas[0].AxisX;
Axis ay = chart1.ChartAreas[0].AxisY;
laNew = new LineAnnotation();
chart1.Annotations.Add(laNew);
laNew.IsSizeAlwaysRelative = false;
laNew.AxisX = ax;
laNew.AxisY = ay;
laNew.AnchorX = ax.PixelPositionToValue(e.X);
laNew.AnchorY = ay.PixelPositionToValue(e.Y);
laNew.LineColor = rb_green.Checked ? Color.Green : Color.Red;
laNew.AllowMoving = true;
}
}
private void chart1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button.HasFlag(MouseButtons.Left) && cbx_drawAnnotation.Checked)
{
Axis ax = chart1.ChartAreas[0].AxisX;
Axis ay = chart1.ChartAreas[0].AxisY;
laNew.Width = ax.PixelPositionToValue(e.X) - laNew.AnchorX; // values
laNew.Height = ay.PixelPositionToValue(e.Y) - laNew.AnchorY;
}
}
Note how I now can scale the maximum and also still resize the the chart and the annotations stay with the data points..:
Update: To restrict the line to the ChartArea add this to the definition in the MouseDown event:
laNew.ClipToChartArea = chart1.ChartAreas[0].Name;
To prevent an exception from leaving the Chart, add this to the condition in the MouseMove..:
.. && chart1.ClientRectangle.Contains(e.Location)
I have three question regarding ILPanel in ILNumerics:
How can I disable the double click interaction of ILPanel to reset the view.
How can I increase the speed of panning using right click and drag. The panning is very slow especially when the shape is vary large.
How can access the elements that I add to a camera and modify them.
Here is a simple example. Two spheres are added to a camera and a ILLable, "Hello", is on top of one of them (the blue one). I want the "Hello" label to jump on top of the green sphere if I rotate the scene with mouse and the green sphere become closer to the camera and visa verse. And, how can I change the color of the spheres say by clicking a button?
private void ilPanel1_Load(object sender, EventArgs e)
{
var scene = new ILScene();
var cam = scene.Camera;
var Sphere1 = new ILSphere();
Sphere1.Wireframe.Visible = true;
Sphere1.Fill.Color = Color.Blue;
Sphere1.Wireframe.Color = Color.Blue;
Sphere1.Transform = Matrix4.Translation(0, 0, 0);
var Sphere2 = new ILSphere();
Sphere2.Wireframe.Visible = true;
Sphere2.Transform = Matrix4.Translation(2, 0, 0);
cam.Add(Sphere1);
cam.Add(Sphere2);
cam.Add(new ILLabel("Hello")
{
Font = new System.Drawing.Font(FontFamily.GenericSansSerif, 8),
Position = new Vector3(0, 0, 1.1),
Anchor = new PointF(0, 1),
});
ilPanel1.Scene = scene;
}
Create a label, which is always on top of other 3D objects
cam.Add(
new ILScreenObject() {
Location = new PointF(0.5f, 0.5f),
Children = {
new ILLabel("Hello")
}
});
See also: http://ilnumerics.net/world3d-and-screen2d-nodes.html
Regarding your "jumping sphere" example: if your want your label to jump to another group (spheres are groups actually) at runtime, you will have to implement that logic into a function which is called at runtime. ilPanel1.BeginRenderFrame might be a good place to check for such updates.
Disable default behavior for the first camera:
scene.Camera.MouseDoubleClick += (_, arg) => {
arg.Cancel = true;
};
See: http://ilnumerics.net/mouse-events.html
React on a button press:
button1.Click += (_,args) => {
ilPanel1.Scene.First<ILSphere>().Fill.Color = Color.Red;
ilPanel1.Refresh();
};
Finding objects in the scene:
see: http://ilnumerics.net/nodes-accessing-managing.html
Controlling the panning speed:
There is no easy way to configure the speed currently. The best way would be to implement the corresponding mouse handler on your own:
Override MouseMove for the camera node.
Implement the panning similar to the way it was done in ILNumerics (see: ILCamera.OnMouseMove in the sources).
Increase the scale factor.
Dont forget to cancel the event processing at the end of your handler.
I have a draggable element in a StackPanel in a Windows 8 store app. My goal is simple: drag the item somewhere on the screen and immediately after the user stops dragging it the element should return to its original starting position.
I have the following code which is meant to accomplish this task:
private void grdCover_ManipulationCompleted(object sender, ManipulationCompletedRoutedEventArgs e)
{
Grid coverControl = sender as Grid;
double xOffset = -e.Cumulative.Translation.X;
double yOffset = -e.Cumulative.Translation.Y;
if (coverControl.RenderTransform is TranslateTransform)
{
TranslateTransform existingTransform = coverControl.RenderTransform as TranslateTransform;
existingTransform.X += xOffset;
existingTransform.Y += yOffset;
}
else
{
TranslateTransform transform = new TranslateTransform();
transform.X = xOffset;
transform.Y = yOffset;
coverControl.RenderTransform = transform;
}
}
The code sort of works. The screen looks like this upon application start:
The top element, which looks like a H is well aligned with the bottom element which looks like a U. When I first drag the H element it jumps back to its well aligned position, or at least any misalignment is so little that it's hardly perceivable to the naked eye. As I keep dragging and releasing the H element it gets more and more misaligned like this:
After 15-20 dragging moves the H element gets completely misplaced:
The problem might be due to some rounding error of the double values in the Point objects when they are translated into pixels, but that's only a wild guess. Also, on a high resolution device, such as my PC it takes more moves for the misalignment to become visible than on a lower resolution one, such as the Windows Simulator in Visual Studio.
Some other code that may be related to this issue:
I perform the dragging operation with the following code:
private void Grid_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
{
TranslateTransform translateTransform = (sender as Grid).RenderTransform as TranslateTransform;
translateTransform.X += e.Delta.Translation.X;
translateTransform.Y += e.Delta.Translation.Y;
}
I also have the following event handler in place to stop the element from moving too much on the screen after the user is done dragging the element:
private void grdCover_ManipulationInertiaStarting(object sender, ManipulationInertiaStartingRoutedEventArgs e)
{
e.TranslationBehavior.DesiredDeceleration = 10000000000000000;
e.Handled = true;
}
In case you're wondering I wanted to set the desired deceleration to Double.Max to block any further "sliding" movement on the screen but I got an exception saying it was out of bounds. So instead I chose this arbitrary large value.
I have no extra margins or padding on the layout root or the element to be moved or its container.
Any ideas are welcome.
Thanks, Andras
OK, I got it to work. The problem was that there was a slight mismatch between the Point coordinates in the following code bits:
TranslateTransform translateTransform = (sender as Grid).RenderTransform as TranslateTransform;
translateTransform.X += e.Delta.Translation.X;
translateTransform.Y += e.Delta.Translation.Y;
and
double xOffset = -e.Cumulative.Translation.X;
double yOffset = -e.Cumulative.Translation.Y;
The solution was to retrieve the value of
e.Delta.Translation.X
and
e.Delta.Translation.Y
and assign their values to the variables xOffset and yOffset. Retrieving the values of
e.Cumulative.Translation.X
and
e.Cumulative.Translation.Y
gave slightly different coordinates so the element kept jumping back to the wrong location.
I'm busy with a small application in which I want to display information at the location of the cursor when it hoovers over a Canvas. The Canvas in question is a custom one (inherited from Canvas) which provides functionality to add DrawingVisuals (as shown in basically every tutorial on displaying large amounts of geometric shapes on a canvas).
I would like to display a vertical line and horizontal line as well as the local coordinates (p in the code below) which are directly derived from the canvas coordinates (v). At the moment I'm rendering these objects at position (0,0) and use offset during the OnMouseMove event to update their location.
The horizontal and vertical lines are rendered in the DrawingVisual _cursor and the location in local y,z-coordinates in _info.
private void oCanvas_MouseMove(object sender, MouseEventArgs e)
{
#region 1. Get location data
System.Windows.Vector v = (System.Windows.Vector)e.GetPosition(oCanvas);
// point in YZ coordinates
BSMath.DoubleXY p = new BSMath.DoubleXY();
p.X = (oCanvas.OriginY - v.Y) / oCanvas.ZoomFactor;
p.Y = (oCanvas.OriginX - v.X) / oCanvas.ZoomFactor;
#endregion
#region 2. Update cursor and info
if (oSettings.ShowInformation)
{
_info.Info = p.X.ToString("0.0") + " | " + p.Y.ToString("0.0");
_info.Render(0, 0);
_info.Visual.Offset = v;
}
// move cursor
_cursor.Visual.Offset = v;
}
Using the mousemove event seems to be creating a lot of overhead and I can see that there are issues tracking the mouse movements when I move the mouse quickly.
Can anyone recommend a better way of creating the same effect?
example http://www.iccg.be/test/images/canvas.jpg
Edit:
I investigated it a bit further and the problem seems to occur when the resolution of the canvas is bigger. If it is a 600x400 canvas then there is no delay, but when it is around 1000x800 I get the problem with delays when hoovering. The performance also improves if I use user drawn crosshairs instead of the lines that have the full width/ height of the canvas.
I recently have built something similar and haven't had any performance issues.
Did it the very simple way by adding the stuff directly on the canvas.
The drawn items are in a second canvas behind the mouse position canvas. Both reside in a Grid.
This is for sure not the most sophisticated way to solve this, but it works quite well for me.
Here's the code:
private Point _previous;
private Point _current;
private Line _xLine;
private Line _yLine;
private TextBlock _displayTextBlock;
private void Canvas_MouseMove(object sender, MouseEventArgs e)
{
_current = e.GetPosition(myCanvas);
if (_previous != _current)
{
if (_xLine == null)
{
_xLine = new Line() {X1 = 0, X2 = myCanvas.ActualWidth, Stroke = new SolidColorBrush(Colors.Black)};
_yLine = new Line() {Y1 = 0, Y2 = myCanvas.ActualHeight, Stroke = new SolidColorBrush(Colors.Black)};
_displayTextBlock = new TextBlock();
myCanvas.Children.Add(_xLine);
myCanvas.Children.Add(_yLine);
myCanvas.Children.Add(_displayTextBlock);
}
_displayTextBlock.SetValue(Canvas.TopProperty, _current.Y);
_displayTextBlock.SetValue(Canvas.LeftProperty, _current.X);
_displayTextBlock.Text = _current.X.ToString() + " | " + _current.Y.ToString();
_xLine.Y1 = _current.Y;
_xLine.Y2 = _current.Y;
_yLine.X1 = _current.X;
_yLine.X2 = _current.X;
_previous = _current;
}
}
I am experiencing a weird problem with a render transform in WPF. The project I'm working on needs to display a clicked user point over an image. When the user clicks a point, a custom control is placed at the location of their click. The image should then be able to be scaled around any point using the mouse wheel, and the custom control should be translated (not scaled) to the correct location.
To do this, I follow the MouseWheel event as follows:
private void MapPositioner_MouseWheel(object sender, MouseWheelEventArgs e)
{
Point location = Mouse.GetPosition(MainWindow.Instance.imageMap);
MainWindow.Instance.imageMap.RenderTransform = null;
ScaleTransform st = new ScaleTransform(scale + (e.Delta < 0 ? -0.2 : 0.2), scale += (e.Delta < 0 ? -0.2 : 0.2));
st.CenterX = location.X;
st.CenterY = location.Y;
TransformGroup tg = new TransformGroup();
tg.Children.Add(st);
//tg.Children.Add(tt);
MainWindow.Instance.imageMap.RenderTransform = tg;
if (scale <= 1)
{
MainWindow.Instance.imageMap.RenderTransform = null;
}
if (TransformationChanged != null)
TransformationChanged();
}
Then, I implemented an event handler in the custom control for the TransformationChanged event seen at the end of the above code block as follows:
private void Instance_TransformationChanged()
{
//check image coords
//
if (MainWindow.Instance.imageMap.RenderTransform != null)
{
if (MainWindow.Instance.imageMap.RenderTransform != Transform.Identity)
{
Transform st = MainWindow.Instance.imageMap.RenderTransform;
Point image = MainWindow.VideoOverlayCanvas.TransformToVisual(MainWindow.Instance.MapImage).Transform(loc2);
Point trans = st.Transform(image);
Point final = MainWindow.Instance.MapImage.TransformToVisual(MainWindow.VideoOverlayCanvas).Transform(trans);
// selected = anchor2;
// final = ClipToOverlay(final);
// selected = null;
connector.X2 = final.X;
connector.Y2 = final.Y;
Canvas.SetLeft(anchor2, final.X);
Canvas.SetTop(anchor2, final.Y);
}
}
else
{
connector.X2 = loc2.X;
connector.Y2 = loc2.Y;
Canvas.SetLeft(anchor2, loc2.X);
Canvas.SetTop(anchor2, loc2.Y);
}
}
This way, I can ensure that the custom control's position is updated only after the new transform is set. Note that since I am applying the transform to the point, there is no scaling done to the control, the effect is that it is translated to the point it should. This works fine as long as the user is only scaling around one point. If they change that point, it doesnt work.
Here are some images that show the problem:
User clicks a point
user zooms out, what happened here?
after zooming out (all the way out in this case) it looks ok
I've been messing with this for about two days now, so I apologize if my code looks messy. I know this is a pretty obscure question so any help would be appreciated.
Thanks,
Max
If anyone is looking for an answer to this, because of deadlines, I had to write a workaround by having the user pan with the right mouse button and zoom with the mouse wheel. This way zooming always happens around the center of the image, so the controls are always lined up. I'm still looking for answers to the original question though if anyone can figure it out
Thanks,
Max
I'm not sure what's wrong with your transform, but have you considered an alternate approach? For example, you might want to add a transparent canvas set to stay at the same size as the image, z-order above the image (explicitly set or just put the Canvas element just after the image element). Then you can just use Canvas.SetLeft and Canvas.SetTop to place the user control where the user clicked, and to move it around. A lot easier than using a transform.