I created a draggaable user control in silverlight, but having issues in moving the control inside page. Actually the issue is, dragged control moves out of page when dragging which i don't want. I want the control should drag inside the parent control only.
Any help/suggestion would be appreciated.
Below is the code used to perform operation:
XAML:
<Grid x:Name="LayoutRoot" >
<Grid.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform/>
<TranslateTransform x:Name="LocalTranslateTransform"/>
</TransformGroup>
</Grid.RenderTransform>
C#:
private void OnToolbarClicked(object sender, MouseButtonEventArgs e)
{
mouseDownInToolbar = true;
DragOffset = e.GetPosition(LayoutRoot);
toolbarBorder.CaptureMouse();
}
private void OnToolbarMoving(object sender, MouseEventArgs e)
{
if (mouseDownInToolbar)
{
// we want to move it based on the position of the mouse
moveUserControl(e);
}
}
private void moveUserControl (MouseEventArgs e)
{
Point mousePos = e.GetPosition(LayoutRoot);
Double newX = LocalTranslateTransform.X + (mousePos.X - DragOffset.X);
Double newY = LocalTranslateTransform.Y + (mousePos.Y - DragOffset.Y);
LocalTranslateTransform.X = newX;
LocalTranslateTransform.Y = newY;
}
private void OnToolbarReleased(object sender, MouseButtonEventArgs e)
{
mouseDownInToolbar = false;
toolbarBorder.ReleaseMouseCapture();
}
When you use TranslateTransform, you're sending instructions to the compositor thread to display the element at X/Y offsets. It does not obey the rules of your panel and any visual tree member. So, you have to tell the compositor thread about the boundaries. Use Clip like this:
<Grid Width="500" Height="500">
<Grid.Clip>
<RectangleGeometry Rect="0,0,500,500" />
</Grid.Clip>
<Grid x:Name="LayoutRoot">
<!-- This is the element being dragged by the mouse -->
</Grid>
</Grid>
The RectangleGeometry will create a boundary where child elements are allowed to appear. Anything outside, will be clipped.
After doing the desired changes this is how it looked.
You need to create a constraint.
Calculate if you're transforming the actual control outside the bounds of the parent.
private void moveUserControl (MouseEventArgs e)
{
Point mousePos = e.GetPosition(LayoutRoot);
Double newX = LocalTranslateTransform.X + (mousePos.X - DragOffset.X);
Double newY = LocalTranslateTransform.Y + (mousePos.Y - DragOffset.Y);
var minX = 0;
var maxX = 500 - ActualWidth; // 500 is parent width
var minY = 0;
var maxY = 500 - ActualHeight; // 500 is parent height
if (newX < minX)
{
newX = minX;
}
else if (newX > maxX)
{
newX = maxX;
}
if (newY < minY)
{
newY = minY;
}
else if (newY > maxY)
{
newY = maxY;
}
LocalTranslateTransform.X = newX;
LocalTranslateTransform.Y = newY;
}
Related
I have a canvas (OuterCanvas) within a canvas (InnerCanvas) and am handling zooming in an out as follows:
private ScaleTransform ScaleTransform = new ScaleTransform();
private void OuterCanvas_MouseWheel(Object sender, MouseWheelEventArgs e)
{
if (e.Delta >= 1)
{
ScaleTransform.ScaleX += 0.03;
ScaleTransform.ScaleY += 0.03;
}
else
{
ScaleTransform.ScaleX -= 0.03;
ScaleTransform.ScaleY -= 0.03;
}
ScaleTransform.CenterX = ActualWidth / 2;
ScaleTransform.CenterY = ActualHeight / 2;
InnerCanvas.RenderTransform = TransformGroup;
}
This zooms in and out quite okay at the centre of the canvas. I'd like to zoom in and out around the mouse pointer, so change the CenterX and CenterY lines as follows:
ScaleTransform.CenterX = e.GetPosition(this).X;
ScaleTransform.CenterY = e.GetPosition(this).Y;
Again this works satisfactory, however if I attempt to move the mouse during two zoom events (mouse wheel events), the whole canvas jumps rather drastically as the scale factor increases or decreases.
Demonstration of problem
This is the same issue as when the CenterX and CenterY properties are set to the ActualWidth/2 and ActualHeight/2 and the window size is changed.
What should the correct handling be of the CenterX and CenterY properties be to avoid this issue?
Here is a more or less complete solution, including the possibility to pan the inner Canvas:
<Canvas x:Name="outerCanvas"
MouseWheel="OnMouseWheel"
MouseMove="OnMouseMove"
MouseLeftButtonDown="OnMouseLeftButtonDown"
MouseLeftButtonUp="OnMouseLeftButtonUp">
<Canvas x:Name="innerCanvas"
Width="400" Height="400" Background="AliceBlue">
<Canvas.RenderTransform>
<MatrixTransform />
</Canvas.RenderTransform>
</Canvas>
</Canvas>
with these event handlers:
private double zoomScaleFactor = 1.1;
private Point? mousePos;
private void OnMouseWheel(object sender, MouseWheelEventArgs e)
{
var pos = e.GetPosition(outerCanvas);
var scale = e.Delta > 0 ? zoomScaleFactor : 1 / zoomScaleFactor;
var transform = (MatrixTransform)innerCanvas.RenderTransform;
var matrix = transform.Matrix;
matrix.ScaleAt(scale, scale, pos.X, pos.Y);
transform.Matrix = matrix;
}
private void OnMouseMove(object sender, MouseEventArgs e)
{
if (mousePos.HasValue)
{
var pos = e.GetPosition(outerCanvas);
var delta = pos - mousePos.Value;
var transform = (MatrixTransform)innerCanvas.RenderTransform;
var matrix = transform.Matrix;
matrix.Translate(delta.X, delta.Y);
transform.Matrix = matrix;
mousePos = pos;
}
}
private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (outerCanvas.CaptureMouse())
{
mousePos = e.GetPosition(outerCanvas);
}
}
private void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
outerCanvas.ReleaseMouseCapture();
mousePos = null;
}
I need to create a control like this.
Click on this link to see the image
How to do that ?
I got some help to start with. But, still not sure how to do it.
In xaml I have something like this:
<Grid Background="White"
MouseUp="ParentOnMouseUp">
<Canvas x:Name="canvas"
Background="Green"
Width="200"
Height="200"
HorizontalAlignment="Center"
VerticalAlignment="Center"
MouseMove="CanvasOnMouseMove">
<Ellipse x:Name="dot"
Width="20"
Height="20"
Fill="Blue"
Loaded="DotOnLoaded"
MouseDown="DotOnMouseDown"/>
</Canvas>
In the code behind file I have the following code
private void DotOnMouseDown(object sender, MouseButtonEventArgs e)
{
_isDraggingDot = true;
}
private void CanvasOnMouseMove(object sender, MouseEventArgs e)
{
if (_isDraggingDot)
{
var mousePos = e.GetPosition(canvas);
var x = mousePos.X;
if (x < 0)
{
x = 0;
}
if (x > canvas.Width)
{
x = canvas.Width;
}
var y = mousePos.Y;
if (y < 0)
{
y = 0;
}
if (y > canvas.Height)
{
y = canvas.Height;
}
dot.SetValue(Canvas.LeftProperty, x - (dot.Width / 2.0)); // offset ensures dot is centred on mouse pointer
dot.SetValue(Canvas.TopProperty, y - (dot.Height / 2.0));
}
}
private void ParentOnMouseUp(object sender, MouseButtonEventArgs e)
{
_isDraggingDot = false;
//CentreDot();
}/
private void CentreDot()
{
dot.SetValue(Canvas.LeftProperty, (canvas.Width / 2.0) - (dot.Width / 2.0));
dot.SetValue(Canvas.TopProperty, (canvas.Height / 2.0) - (dot.Height / 2.0));
}
private void DotOnLoaded(object sender, RoutedEventArgs e)
{
CentreDot();
}
I am not able to draw the horizontal scale of -180 to +180 and the vertical scale of -30 to +30. Also, once the dot is moved to a specific point. I need the co-ordinates of that point.
I want to rotate and afterwards move an image in c#. The image is in a Canvas . My problem is if you rotate the image with the following
private void Schiff_OnMouseWheel(object sender, MouseWheelEventArgs e)
{
Image _schiff = (Image)sender;
if (!_schiff.IsMouseCaptured) return;
Matrix _mat = _schiff.RenderTransform.Value;
Point _mouse = e.GetPosition(_schiff);
if (e.Delta > 0)
{
_mat.RotateAtPrepend(22.5, _mouse.X, _mouse.Y);
}
else
{
_mat.RotateAtPrepend(-22.5, _mouse.X, _mouse.Y);
}
MatrixTransform _mtf = new MatrixTransform(_mat);
_schiff.RenderTransform = _mtf;
}
or RotateTransform
double _angle = 0.0;
_angle += 22.5;
if (_angle == 360.0) _angle = 0.0;
RotateTransform _rotate = new RotateTransform(_angle, _schiff.Width / 2, _schiff.Height / 2);
_schiff.RenderTransform = _rotate;A
you just rotate the "picture", but not it's base. So if you want to move the image with Canvas.GetLeft/GetTop, it behaves like it is still not rotated. So if you set the Top/Left-corner, the actual corner of the rotated image isn't placed where I wanted it to be.
At https://wpf.2000things.com/2013/03/08/772-use-rendertransformorigin-to-change-center-point-for-rotation-transforms/, in the picture you can see what I mean. How can I possibly rotate the "base" with the actual image? I saw it is possible in WinForms, but (how) does it work in WPF?
Thanks in advance, if anything is unclear/wrong I will edit my question.
edit:
https://i.stack.imgur.com/tqhKw.png
You can see the two arrows. They are my images. I rotated them at the center with my above MouseWheelEvent. On the right site there is my movement tab. You can change the speed (the checkbox after "Geschwindigkeit") and then you can either turn left in one section (where - is 0°, | is 22.5° and || is 45° and a section is 69 points in the Canvas) or right and end up in a new location.
You should use coordinates relative to the Canvas for transforming the Image element.
With this Image in a Canvas
<Canvas x:Name="canvas">
<Image Width="100"
Source="C:\Users\Public\Pictures\Sample Pictures\Koala.jpg"
MouseLeftButtonDown="OnMouseLeftButtonDown"
MouseLeftButtonUp="OnMouseLeftButtonUp"
MouseMove="OnMouseMove"
MouseWheel="OnMouseWheel">
<Image.RenderTransform>
<MatrixTransform />
</Image.RenderTransform>
</Image>
</Canvas>
the code would look like shown below.
The important part is to use e.GetPosition(canvas) and not to set the child element's Canvas.Left, Canvas.Top and RenderTransformOrigin properties. All transformations are done with a single Matrix in its RenderTransform.
private Point? mousePos;
private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
((IInputElement)sender).CaptureMouse();
mousePos = e.GetPosition(canvas);
}
private void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
((IInputElement)sender).ReleaseMouseCapture();
mousePos = null;
}
private void OnMouseMove(object sender, MouseEventArgs e)
{
if (mousePos.HasValue)
{
var element = (UIElement)sender;
var transform = (MatrixTransform)element.RenderTransform;
var matrix = transform.Matrix;
var pos = e.GetPosition(canvas);
matrix.Translate(pos.X - mousePos.Value.X, pos.Y - mousePos.Value.Y);
transform.Matrix = matrix;
mousePos = pos;
}
}
private void OnMouseWheel(object sender, MouseWheelEventArgs e)
{
var element = (UIElement)sender;
var transform = (MatrixTransform)element.RenderTransform;
var matrix = transform.Matrix;
var pos = e.GetPosition(canvas);
matrix.RotateAt(e.Delta > 0 ? 22.5 : -22.5, pos.X, pos.Y);
transform.Matrix = matrix;
}
I am implementing functionality to allow user to draw rectangle on a Wpf canvas at run time by dragging mouse.I am currently able to able to draw the rectangle when I drag mouse from top left corner to bottom left, but the rectangle is not visible when I drag mouse from bottom left corner to top.Below is the xaml code that I am using:
<Canvas x:Name="CanvasContainer" MouseLeftButtonDown="CanvasContainer_MouseLeftButtonDown" MouseLeftButtonUp="CanvasContainer_MouseLeftButtonUp" MouseMove="CanvasContainer_MouseMove" >
<Rectangle x:Name="RectangleMarker" Canvas.Left="0" Stroke="Red" Width="0" Height="0" Panel.ZIndex="1"></Rectangle>
<Line x:Name="LineMarker" Stroke="Red" X1="0" Y1="0" X2="0" Y2="0"></Line>
<Image Canvas.Left="0" Canvas.Top="0" x:Name="PdfImage" RenderTransformOrigin="0.5,0.5" MouseWheel="PdfImage_MouseWheel" ClipToBounds="True" Panel.ZIndex="0">
<Image.LayoutTransform>
<ScaleTransform ScaleX="1" ScaleY="1" CenterX="0.5" CenterY="0.5" />
</Image.LayoutTransform>
</Image>
</Canvas>
Below are the event handling to update rectangle's position according to mouse position.
private void CanvasContainer_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
try
{
Point startPoint = Mouse.GetPosition(CanvasContainer);
Canvas.SetLeft(RectangleMarker, startPoint.X);
Canvas.SetTop(RectangleMarker,startPoint.Y);
}
catch (Exception ex)
{
}
}
private void CanvasContainer_MouseMove(object sender, MouseEventArgs e)
{
try
{
if (e.LeftButton == MouseButtonState.Pressed)
{
Point endPoint = Mouse.GetPosition(CanvasContainer);
Point startPoint = new Point((double)RectangleMarker.GetValue(Canvas.LeftProperty), (double)RectangleMarker.GetValue(Canvas.TopProperty));
double x = Math.Min(startPoint.X, endPoint.X);
double y = Math.Min(startPoint.Y, endPoint.Y);
double width = endPoint.X - startPoint.X;
double height = endPoint.Y - startPoint.Y;
if (width < 0)
{
x = startPoint.X + width;
}
if (height < 0)
{
y = startPoint.Y + height;
}
RectangleMarker.Width = Math.Abs(width);
RectangleMarker.Height = Math.Abs(height);
if (x!=startPoint.X)
{
Canvas.SetLeft(RectangleMarker, x);
}
else if(y!=startPoint.Y)
{
Canvas.SetTop(RectangleMarker, y);
}
}
}
catch (Exception ex)
{
}
}
Better use a Path with a RectangleGeometry:
<Canvas Background="Transparent"
MouseLeftButtonDown="CanvasContainer_MouseLeftButtonDown"
MouseLeftButtonUp="CanvasContainer_MouseLeftButtonUp"
MouseMove="CanvasContainer_MouseMove">
<Path Stroke="Red">
<Path.Data>
<RectangleGeometry x:Name="selectionRect"/>
</Path.Data>
</Path>
</Canvas>
Code behind:
private Point? startPoint;
private void CanvasContainer_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var element = (UIElement)sender;
element.CaptureMouse();
startPoint = e.GetPosition(element);
}
private void CanvasContainer_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
((UIElement)sender).ReleaseMouseCapture();
startPoint = null;
}
private void CanvasContainer_MouseMove(object sender, MouseEventArgs e)
{
if (startPoint.HasValue)
{
selectionRect.Rect = new Rect(
startPoint.Value, e.GetPosition((IInputElement)sender));
}
}
As I mentioned earlier in comment your problem is not drawing, but resizing.
When resizing rectangle there are 4 possible directions, 4 sides and 4 corners. So it's kind of complicated.
The easiest way is to simply remember start mouse position and then check where is the new position, drawing rectangle between them. Obviously new mouse position can be either of corners, depending in which direction mouse was moved in relation to start point.
So:
Point _start;
void CanvasContainer_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) =>
_start = Mouse.GetPosition(CanvasContainer);
void CanvasContainer_MouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
var mouse = Mouse.GetPosition(CanvasContainer);
Canvas.SetLeft(RectangleMarker, _start.X > mouse.X ? mouse.X : _start.X);
Canvas.SetTop(RectangleMarker, _start.Y > mouse.Y ? mouse.Y : _start.Y);
RectangleMarker.Width = Math.Abs(mouse.X - _start.X);
RectangleMarker.Height = Math.Abs(mouse.Y - _start.Y);
}
}
Math.Abs will handle resizing for either positive or negative change, while for changing position we still need a conditional check to determine which point, start or new mouse, is the top-left corner.
Demo:
I'm quite new to WPF so I'm learning most of this the hard (but fun) way. I'm constructing a HSV-like colorpicker usercontrol, and would like to obtain the behaviour were the thumb I use as the "selector" is limited to an elliptic area (circle actually). When moved outside, the selector should stick to the side and not move at all. I believe this is the most common GUI-behaviour, so that is how it should behave. Feel free to suggest a better behaviour!
Is there a common, known and recommended solution to this or is it up to everyone to re-invent the wheel each time?
Any good ideas of how to solve this?
Code-behind:
public partial class HSVColorPicker : UserControl
{
public HSVColorPicker()
{
InitializeComponent();
}
void onDragDelta(object sender, DragDeltaEventArgs e)
{
Canvas.SetLeft(thumb, Canvas.GetLeft(thumb) + e.HorizontalChange);
Canvas.SetTop(thumb, Canvas.GetTop(thumb) + e.VerticalChange);
}
}
XAML:
<Grid>
<Canvas x:Name="canvas">
<Image x:Name="wheel" Source="colorwheel.png" Width="300" Margin="5,5,0,0"/>
<Thumb Name="thumb" DragDelta="onDragDelta" Canvas.Left="104" Canvas.Top="68" Template="{StaticResource thumbTemplate}" />
</Canvas>
</Grid>
While I'm in here, the thumb is always dragging behind the cursor, is there another way to create this? As I said, I'm new to WPF and GUI-making altogether so maybe there are obvious solutions that haven't occurred to me ;)
I did some rethinking and dropped the Thumb completely, using a dummy circle (named thumb) instead. Now im listening to, mousedown, mouseup and mousemove on the canvas and determine what should be possible and not. This has the nice feature that the thumb sticks to the colorwheel edge when the mouse goes outside the area, but the area is a bit larger than the colorwheel, to make it easy to get a point on the border. Not complete but it solved my question so I post it as it is at this moment.
private bool mousePressed { get; set; }
private bool mouseWithinArea { get; set; }
private Point circleMiddlePoint { get; set; }
private int margin;
private double mPX;
private double mPY;
private double localXpos;
private double globalXpos
{
get
{
return localXpos + mPX;
}
set
{
localXpos = value - mPX;
Canvas.SetLeft(thumb, value);
}
}
private double localYpos;
private double globalYpos
{
get
{
return mPY - localYpos;
}
set
{
localYpos = mPY - value;
Canvas.SetTop(thumb, value);
}
}
public HSVColorPicker()
{
InitializeComponent();
wheel.Width = 300;
margin = 15;
mPX = 150+margin;
mPY = 150+margin;
circleMiddlePoint = new Point(mPX, mPY);
}
private void CalcPosition(double X, double Y)
{
double radius = wheel.Width / 2.0;
double vectorX = X - mPX;
double vectorY = Y - mPY;
double distance = Math.Sqrt(vectorX * vectorX + vectorY * vectorY);
if (distance > radius)
{
double factor = radius / distance;
vectorX *= factor;
vectorY *= factor;
}
globalXpos = vectorX + mPX;
globalYpos = vectorY + mPY;
}
private void wheel_MouseDown(object sender, MouseButtonEventArgs e)
{
if (mouseWithinArea)
{
mousePressed = true;
Point mousePoint = e.GetPosition(this);
CalcPosition(mousePoint.X, mousePoint.Y);
}
}
private void wheel_MouseMove(object sender, MouseEventArgs e)
{
Point mousePoint = e.GetPosition(this);
double relX = mousePoint.X - mPX;
double relY = mPY - mousePoint.Y;
if (mouseWithinArea)
{
if (Math.Sqrt(relX * relX + relY * relY) > 150+margin)
{
mouseWithinArea = false;
}
else
{
if (mousePressed)
{
CalcPosition(mousePoint.X, mousePoint.Y);
}
}
}
else
{
if (Math.Sqrt(relX * relX + relY * relY) < 150+margin)
{
mouseWithinArea = true;
if (mousePressed)
{
CalcPosition(mousePoint.X, mousePoint.Y);
}
}
}
}
private void wheel_MouseUp(object sender, MouseButtonEventArgs e)
{
mousePressed = false;
}
}
<Canvas x:Name="canvas" Background="Transparent" MouseDown="wheel_MouseDown" MouseMove="wheel_MouseMove" MouseUp="wheel_MouseUp" Width="330" Height="330">
<Image x:Name="wheel" Source="colorwheel.png" Width="300" Margin="15,15,0,0" />
<Ellipse Margin="0,0,0,0"
x:Name="outerEll"
Stroke="Silver"
StrokeThickness="15"
Width="330"
Height="330"/>
<Ellipse Name="thumb" Stroke="Black" Fill="Silver" Canvas.Left="150" Canvas.Top="150" Width="15" Height="15" Margin="-12" />
</Canvas>
You want the center of your thumb to lay within your color wheel.
So, the distance between the center of your thumb and the center of your color wheel (that is, the center of your canvas) must be less than
or equal to the radius of your color wheel (that is, half the side of your canvas).
Untested c# code:
void onDragDelta(object sender, DragDeltaEventArgs e)
{
double radius = canvas.RenderSize.Width / 2.0;
double thumbCenterX = Canvas.GetLeft(thumb) - thumb.RenderSize.Width + e.HorizontalChange;
double thumbCenterY = Canvas.GetTop(thumb) - thumb.RenderSize.Height + e.VerticalChange;
double colorWheelCenterX = canvas.RenderSize.Width / 2.0;
double colorWheelCenterY = canvas.RenderSize.Height / 2.0;
double vectorX = thumbCenterX - colorWheelCenterX;
double vectorY = thumbCenterY - colorWheelCenterY;
double distance = Math.Sqrt(vectorX * vectorX + vectorY * vectorY);
if(distance > radius) {
double factor = radius / distance;
vectorX *= factor;
vectorY *= factor;
}
Canvas.SetLeft(thumb, colorWheelCenterX + vectorX - thumb.RenderSize.Width / 2.0);
Canvas.SetTop(thumb, colorWheelCenterY + vectorY - thumb.RenderSize.Height / 2.0);
}