How to manipulate a line with the mouse? - c#

Is it possible to create a “resilient” line using C# in a wpf which can be manipulated by the mouse while their starting and ending points remain stable? In particular I would like it to be resizable and its curve to change its direction following the move of the cursor which has previously captured the line? It is something you meet very often in graphical interfaces of desktop applications.
Is the manipulationDelta Class suitable for that? I want something like what is described in http://msdn.microsoft.com/en-us/library/ee649090.aspx but with mouse manipulation, not touch.

Yes it is very possible.
In a real application I would probably use some custom controls for the thumbs to handle the positioning & mouse capturing pieces so they're reusuable.
Here's a basic example of something like this.
Here's the XAML
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Canvas Name="myCanvas" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Line Name="myLine" X1="100" Y1="100" X2="200" Y2="200" Stroke="Black" />
<Ellipse Name="thumb1" Fill="Blue" Width="10" Height="10" Canvas.Left="95" Canvas.Top="95" Cursor="Hand" MouseLeftButtonDown="OnPoint1Down" MouseLeftButtonUp="OnPoint1Up" MouseMove="OnPoint1Move" />
</Canvas>
</Window>
And here's the code behind
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
}
private void OnPoint1Down( object sender, MouseButtonEventArgs e ) {
Mouse.Capture( thumb1 );
}
private void OnPoint1Up( object sender, MouseButtonEventArgs e ) {
Mouse.Capture( null );
}
private void OnPoint1Move( object sender, MouseEventArgs e ) {
if ( e.LeftButton == MouseButtonState.Pressed ) {
var point = e.GetPosition( myCanvas );
myLine.X1 = point.X;
myLine.Y1 = point.Y;
Canvas.SetLeft( thumb1, point.X - thumb1.Width/2 );
Canvas.SetTop( thumb1, point.Y - thumb1.Height/2 );
}
}
}

Related

WPF ScrollViewer and Panning

I have a Window with a ScrollViewer and inside the ScrollViewer there is a Rectangle. Now I added code to drag the Rectangle which works fine. But I have no idea how to show the Scrollbars when the Rectangle is moved outside the view. I thought this will happen automatically which is not the case?
Here is my XAML:
<Window x:Class="WpfApp4.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp4"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<ScrollViewer Name="_scrollViewer" CanContentScroll="True"
HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<Rectangle Name="_myRect" Width="100" Height="100" Fill="Blue"/>
</ScrollViewer>
</Window>
And the code behind:
public partial class MainWindow : Window
{
private Point _origin;
private Point _start;
private ScaleTransform _scaleTransform = new ScaleTransform();
private TranslateTransform _translateTransform = new TranslateTransform();
public MainWindow()
{
InitializeComponent();
var group = new TransformGroup();
group.Children.Add(_scaleTransform);
group.Children.Add(_translateTransform);
_myRect.RenderTransform = group;
// Hook up events
_myRect.MouseLeftButtonDown += _myRect_MouseLeftButtonDown;
_myRect.MouseLeftButtonUp += _myRect_MouseLeftButtonUp;
_myRect.MouseMove += _myRect_MouseMove;
}
private void _myRect_MouseMove(object sender, MouseEventArgs e)
{
if (_myRect.IsMouseCaptured)
{
Vector v = _start - e.GetPosition(this);
_translateTransform.X = _origin.X - v.X;
_translateTransform.Y = _origin.Y - v.Y;
}
}
private void _myRect_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
_myRect.ReleaseMouseCapture();
this.Cursor = Cursors.Arrow;
}
private void _myRect_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
_start = e.GetPosition(this);
_origin = new Point(_translateTransform.X, _translateTransform.Y);
Cursor = Cursors.Hand;
_myRect.CaptureMouse();
}
}
[UPDATED]:
So based on the input I got I change XAML and the code behind to the following - but still no scrollbars?
<Window x:Class="WpfApp4.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp4"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<ScrollViewer Name="_scrollViewer" CanContentScroll="True"
HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Visible">
<Canvas Name="_myCanvas">
<Rectangle Name="_myRect" Width="100" Height="100" Fill="Blue" Canvas.Left="305" Canvas.Top="129"/>
</Canvas>
</ScrollViewer>
</Window>
Code Behind:
public partial class MainWindow : Window
{
private Point _start;
public MainWindow()
{
InitializeComponent();
// Hook up events
_myRect.MouseLeftButtonDown += _myRect_MouseLeftButtonDown;
_myRect.MouseLeftButtonUp += _myRect_MouseLeftButtonUp;
_myRect.MouseMove += _myRect_MouseMove;
}
private void _myRect_MouseMove(object sender, MouseEventArgs e)
{
if (_myRect.IsMouseCaptured)
{
var canvasRelativePosition = e.GetPosition(_myCanvas);
Debug.WriteLine($"New Position: {canvasRelativePosition}");
Canvas.SetTop(_myRect, canvasRelativePosition.Y - _start.Y);
Canvas.SetLeft(_myRect, canvasRelativePosition.X - _start.X);
}
}
private void _myRect_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
_myRect.ReleaseMouseCapture();
this.Cursor = Cursors.Arrow;
}
private void _myRect_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
_start = e.GetPosition(_myRect);
Debug.WriteLine($"Start Position: {_start}");
Cursor = Cursors.Hand;
_myRect.CaptureMouse();
}
}
If you want your transform to affect layout, you have to use LayoutTransofrm. RenderTransform only changes apperance.
Any transformations associated with an elements LayoutTransform
property will have an impact on the subsequent Measure and Arrange
steps. Whereas a RenderTransform will not have any impact on the
layout process and will only effect rendering.
Read more here.
However, LayoutTransform ignores TranslateTransform
LayoutTransform ignores TranslateTransform operations. This is because
the layout system behavior for child elements of a FrameworkElement
auto-corrects any offsets to the position of a scaled or rotated
element into the layout and coordinate system of the parent element.
Read more here
All that means, to achieve moving your element, you can not use Transforms. You could try to change position of your element by hand (Margin, Canvas.Left/Right or other ideas).

How can I retrieve coordinates on a mouse click with ViewportPointToLocation?

I'm using Bing map WPF control SDK to try retrieving coordinates and printing them
I've managed to retrieve the coordinates of the center of the current LocationRect using an EventHandler associated with pressing the arrows
I've tried employing the same concept with mouse clicking event handlers but it didn't work, first I've registered the event using the += notation as follows:
public MainWindow()
{
InitializeComponent();
MainMap.Mode = new AerialMode(true);
MainMap.Focus();
MainMap.Culture = "ar-sa";
MainMap.MouseDoubleClick += new MouseButtonEventHandler(MapWithPushpins_MouseDoubleClick);
}
private void MapWithPushpins_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
e.Handled = true;
Point mousePosition = e.GetPosition(this);
Location pinLocation = MainMap.ViewportPointToLocation(mousePosition);
Pushpin pin = new Pushpin();
pin.Location = pinLocation;
Coordinates.Text = pinLocation.Longitude.ToString();
MainMap.Children.Add(pin);
}
And here's the XAML file:
<Window x:Class="Ornina.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:m="clr-namespace:Microsoft.Maps.MapControl.WPF;assembly=Microsoft.Maps.MapControl.WPF"
xmlns:local="clr-namespace:Ornina"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid VerticalAlignment="Top" >
<m:Map CredentialsProvider="AqCitpgSjIz_Sxd6AyI9Zm1rs1uRSG_G3Y7ebfok69ufB8W8uRdUtvheaRbz_10t" x:Name="MainMap" Center="36,38" ZoomLevel="16" Mode="AerialWithLabels" HorizontalAlignment="Center" VerticalAlignment="Top" Height="300" Width="500">
</m:Map>
</Grid>
<TextBlock x:Name="Coordinates">Coordinations</TextBlock>
</Grid>
</Window>
The program isn't responding with any thing, no exceptions, no errors
Your TextBlock is in front of your map, so the map doesn't receive the MouseDoubleClick event.
You can change the TextBlock to:
<TextBlock x:Name="Coordinates"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Text="Coordinations" />
So it's only in the top left corner and not in front of the whole map.
Or you can move it outside of the map entirely, in a different grid row or column.

Moving a triangle using given coordinate

I'm working on a project and I want to animate a rectangle to move a certain amount of coordinate that i have set earlier.
Ex: My rectangle is at the position (x=0,y=0). I want with a click of a button to make it move at position (x=150, y=230) in interval of 100 milliseconds. So with one click, it would go to (10,25) at first 100 milliseconds,(20,35) for second 100 milliseconds and so on until the rectangle reach the final position(x=150, y=230)...
I'm working with visual studio and blend on Visual C# wpf.
This is my progress of codes in code-behind file:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, System.Windows.RoutedEventArgs e)
{
var myRect = (Rectangle)this.FindName("myRect");
double x = Canvas.GetLeft(myRect);
double y = Canvas.GetTop(myRect);
Canvas.SetLeft(myRect,x+10);
Canvas.SetTop(myRect,y);
And this is in XAML code:
<Window x:Class="move.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:move"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<Border BorderThickness="1" BorderBrush="Aqua">
<Canvas Name="PointCanvas" Width="500" Height="294" Margin="9,0,6,0">
<Rectangle x:Name="myRect" Fill="#FFF5F4F5" Height="39" Canvas.Left="170" Stroke="Black" Canvas.Top="89" Width="89"/>
</Canvas>
</Border>
<Button Name="Move" Click="Button_Click">Move</Button>
</StackPanel>

How to limit child window's movement within parent boundaries?

I was wondering is it possible to limit child window's ability to be moved around to only within parent's panel boundaries ? Suppose I create a child window with a button click:
<UserControl x:Class="ChildWindowTest.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:Child="clr-namespace:ChildWindowTest"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400" >
<Grid x:Name="LayoutRoot" >
<StackPanel Width="500" Height="500">
<Button Width="100" Height="25" Click="Button_Click" Content="Child Window"/>
</StackPanel>
</Grid>
</UserControl>
<controls:ChildWindow
x:Class="ChildWindowTest.ChildWindow1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"
Width="400" Height="300" Title="ChildWindow1" >
<Grid x:Name="LayoutRoot" Margin="2">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
</Grid>
</controls:ChildWindow>
I can move the generated child to left, right and down off screen (clipped off). I want to avoid that, basically set up a boundary within which child window is allowed to be moved (within StackPanel boundary)
Thank you for any suggestion ..
I tried to keep my solution as simple as possible, but still it was quite challenging to achieve the desired behaviour.
Basically, if you leave the ChildWindow's ControlTemplate alone, the following code should work for you:
private void Button_Click(object sender, RoutedEventArgs e)
{
ChildWindow wnd = new ChildWindow();
wnd.Width = 800;
wnd.Height = 600;
wnd.Title = "Test";
wnd.MouseLeftButtonUp += wnd_MouseLeftButtonUp;
wnd.Loaded += wnd_Loaded;
wnd.Show();
}
private void wnd_Loaded(object sender, RoutedEventArgs e)
{
var wnd = sender as ChildWindow;
myApplicationActualWidth = Application.Current.Host.Content.ActualWidth;
myApplicationActualHeight = Application.Current.Host.Content.ActualHeight;
//This call might be necessary to make sure the visual tree of wnd is constructed and can be inspected
wnd.UpdateLayout();
//wnd is guaranteed to have at least one child here
myRoot = (FrameworkElement)VisualTreeHelper.GetChild(wnd, 0);
myContentRoot = myRoot.FindName("ContentRoot") as FrameworkElement;
//this is the title bar part
myChrome = myRoot.FindName("Chrome") as FrameworkElement;
myChrome.AddHandler(MouseLeftButtonUpEvent, new MouseButtonEventHandler(wnd_MouseLeftButtonUp), true);
myTransform = (myContentRoot.RenderTransform as TransformGroup).Children.OfType<TranslateTransform>().First();
}
private void wnd_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
Point rootMousePosition = e.GetPosition(sender as ChildWindow);
Point contentRootMousePosition = e.GetPosition(myContentRoot);
Point currentOffset = new Point(rootMousePosition.X - contentRootMousePosition.X, rootMousePosition.Y - contentRootMousePosition.Y);
TransformChildWindowToValidPosition(currentOffset);
}
private void TransformChildWindowToValidPosition(Point currentPosition)
{
// handle left side
if (currentPosition.X < 0)
{
myTransform.X = myTransform.X - currentPosition.X;
}
// handle top
if (currentPosition.Y < 0)
{
myTransform.Y = myTransform.Y - currentPosition.Y;
}
// handle right side
if (currentPosition.X + myContentRoot.ActualWidth > ActualWidth)
{
myTransform.X = myTransform.X - (currentPosition.X + myContentRoot.ActualWidth - myApplicationActualWidth);
}
// handle bottom
if (currentPosition.Y + myContentRoot.ActualHeight > ActualHeight)
{
myTransform.Y = myTransform.Y - (currentPosition.Y + myContentRoot.ActualHeight - myApplicationActualHeight);
}
}
private TranslateTransform myTransform;
private FrameworkElement myRoot;
private FrameworkElement myContentRoot;
private FrameworkElement myChrome;
private double myApplicationActualWidth;
private double myApplicationActualHeight;
This code basically does not allow you the move your ChildWindow outside the boundaries of the current SL application, but it should be a piece of cake to get your hands on the dimensions of the "parent" control of the window and readjust the values for all the edges.
Hope I could help...

Click and drag selection box in WPF

Is it possible to implement mouse click and drag selection box in WPF. Should it be done through simply drawing a rectangle, calculating coordinates of its points and evaluating position of other objects inside this box? Or are there some other ways?
Could you give a bit of sample code or a link?
Here is sample code for a simple technique that I have used in the past to draw a drag selection box.
XAML:
<Window x:Class="DragSelectionBox.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300"
>
<Grid
x:Name="theGrid"
MouseDown="Grid_MouseDown"
MouseUp="Grid_MouseUp"
MouseMove="Grid_MouseMove"
Background="Transparent"
>
<Canvas>
<!-- This canvas contains elements that are to be selected -->
</Canvas>
<Canvas>
<!-- This canvas is overlaid over the previous canvas and is used to
place the rectangle that implements the drag selection box. -->
<Rectangle
x:Name="selectionBox"
Visibility="Collapsed"
Stroke="Black"
StrokeThickness="1"
/>
</Canvas>
</Grid>
</Window>
C#:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
bool mouseDown = false; // Set to 'true' when mouse is held down.
Point mouseDownPos; // The point where the mouse button was clicked down.
private void Grid_MouseDown(object sender, MouseButtonEventArgs e)
{
// Capture and track the mouse.
mouseDown = true;
mouseDownPos = e.GetPosition(theGrid);
theGrid.CaptureMouse();
// Initial placement of the drag selection box.
Canvas.SetLeft(selectionBox, mouseDownPos.X);
Canvas.SetTop(selectionBox, mouseDownPos.Y);
selectionBox.Width = 0;
selectionBox.Height = 0;
// Make the drag selection box visible.
selectionBox.Visibility = Visibility.Visible;
}
private void Grid_MouseUp(object sender, MouseButtonEventArgs e)
{
// Release the mouse capture and stop tracking it.
mouseDown = false;
theGrid.ReleaseMouseCapture();
// Hide the drag selection box.
selectionBox.Visibility = Visibility.Collapsed;
Point mouseUpPos = e.GetPosition(theGrid);
// TODO:
//
// The mouse has been released, check to see if any of the items
// in the other canvas are contained within mouseDownPos and
// mouseUpPos, for any that are, select them!
//
}
private void Grid_MouseMove(object sender, MouseEventArgs e)
{
if (mouseDown)
{
// When the mouse is held down, reposition the drag selection box.
Point mousePos = e.GetPosition(theGrid);
if (mouseDownPos.X < mousePos.X)
{
Canvas.SetLeft(selectionBox, mouseDownPos.X);
selectionBox.Width = mousePos.X - mouseDownPos.X;
}
else
{
Canvas.SetLeft(selectionBox, mousePos.X);
selectionBox.Width = mouseDownPos.X - mousePos.X;
}
if (mouseDownPos.Y < mousePos.Y)
{
Canvas.SetTop(selectionBox, mouseDownPos.Y);
selectionBox.Height = mousePos.Y - mouseDownPos.Y;
}
else
{
Canvas.SetTop(selectionBox, mousePos.Y);
selectionBox.Height = mouseDownPos.Y - mousePos.Y;
}
}
}
}
I wrote an article about this:
https://www.codeproject.com/Articles/148503/Simple-Drag-Selection-in-WPF
You can get this functionality pretty easily by adding an InkCanvas and set its EditingMode to Select. Although it's primarily intended for Tablet PC ink collection and rendering, it's very easy to use it as a basic designer surface.
<Window Width="640" Height="480" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<InkCanvas EditingMode="Select">
<Button Content="Button" Width="75" Height="25"/>
<Button Content="Button" Width="75" Height="25"/>
</InkCanvas>
</Window>
This project created a custom MultiSelector which supports several selection methods including a rectangular "lasso" style:
Developing a MultiSelector by Teofil Cobzaru
It is far too long to reproduce here. The key elements of the design, IIRC, were to create a custom ItemContainer which knows how to interact with its MultiSelector parent. This is analagous to ListBoxItem / ListBox.
This is probably not the simplest possible approach, however if you are already using some type of ItemsControl to host the items which may need to be selected, it could fit into that design pretty easily.
MouseDown logic:
MouseRect.X = mousePos.X >= MouseStart.X ? MouseStart.X : mousePos.X;
MouseRect.Y = mousePos.Y >= MouseStart.Y ? MouseStart.Y : mousePos.Y;
MouseRect.Width = Math.Abs(mousePos.X - MouseStart.X);
MouseRect.Height = Math.Abs(mousePos.Y - MouseStart.Y);

Categories