WPF ScrollViewer and Panning - c#

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).

Related

WPF-InkCanvas StrokeErased event not firing when I used inkcanvas1.Strokes.Erase statement in C#

When I use inkcanvas1.EditingMode is InkCanvasEditingMode.EraseByPoint it is erasing and also StrokeErased event firing.
but when I erase stroke through inkcanvas1.Strokes.Erase(); function then StrokeErased event is not firing. then how can I identify which stroke erased and which strokes are newly created. consider on my inkcanvas thousands of strokes. so I'm not able to maintain every added and removed strokes.
is there any other event or any solution.
I have a below sample WPF Inkcanvas code
XAML Code
<Window x:Class="MyWhiteBoard.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:MyWhiteBoard"
mc:Ignorable="d"
WindowState="Maximized"
Title="MainWindow" >
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
</Grid.RowDefinitions>
<InkCanvas x:Name="inkcanvas1" Grid.Row="0" StrokeErased="inkcanvas1_StrokeErased" MouseMove="inkcanvas1_MouseMove"></InkCanvas>
<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Center">
<Button Width="100" Height="50" Background="WhiteSmoke" Content="Draw" Click="drawInkClick"></Button>
<Button Width="100" Height="50" Background="WhiteSmoke" Content="Erase by Point" Click="EraseByPointsClick"></Button>
<Button Width="100" Height="50" Background="WhiteSmoke" Content="Stroke Erase" Click="StrokeEraseClick"></Button>
<Button Width="100" Height="50" Background="WhiteSmoke" Content="clear all" Click="clearAllClick"></Button>
</StackPanel>
</Grid>
</Window>
Code Behind C# code
public partial class MainWindow : Window
{
private bool IsStrokeEraseCalled = false;
public MainWindow()
{
InitializeComponent();
}
private void inkcanvas1_StrokeErased(object sender, RoutedEventArgs e)
{
// why this event is not calling when I use
//inkcanvas1.Strokes.Erase
}
private void EraseByPointsClick(object sender, RoutedEventArgs e)
{
inkcanvas1.EditingMode = InkCanvasEditingMode.EraseByPoint;
}
private void StrokeEraseClick(object sender, RoutedEventArgs e)
{
inkcanvas1.EditingMode = InkCanvasEditingMode.Select;
IsStrokeEraseCalled = !IsStrokeEraseCalled;
}
private void clearAllClick(object sender, RoutedEventArgs e)
{
inkcanvas1.Strokes.Clear();
}
private void inkcanvas1_MouseMove(object sender, MouseEventArgs e)
{
if (IsStrokeEraseCalled)
{
Point currentPoint = e.GetPosition((IInputElement)sender);
List<Point> enumrator = new List<Point>();
enumrator.Add(new Point(currentPoint.X, currentPoint.Y));
StylusShape EraseShape = (StylusShape)new RectangleStylusShape(100, 100, 0);
inkcanvas1.Strokes.Erase(enumrator, EraseShape);
}
}
private void drawInkClick(object sender, RoutedEventArgs e)
{
inkcanvas1.EditingMode = InkCanvasEditingMode.Ink;
}
}
Replace your StrokeEraseClick event with following code:
private void StrokeEraseClick(object sender, RoutedEventArgs e)
{
inkcanvas1.EditingMode = InkCanvasEditingMode.EraseByPoint;
inkcanvas1.EraserShape = new RectangleStylusShape(100, 100);
var editMode = inkcanvas1.EditingMode;
inkcanvas1.EditingMode = InkCanvasEditingMode.None;
inkcanvas1.EditingMode = editMode;
}
It will leave the current mode to InkCanvasEditingMode.EraseByPoint and allow you to erase defined area stokes. StrokeErased event will be fired every time you erase strokes using this approach.
try this
public MainWindow()
{
InitializeComponent();
inkcanvas1.Strokes.StrokesChanged += Strokes_StrokesChanged;
}
private void Strokes_StrokesChanged(object sender, StrokeCollectionChangedEventArgs e)
{
StrokeCollection newlyAddedStroke = e.Added;
StrokeCollection oldRemovedStroke = e.Removed;
}
When you erase stroke through inkcanvas1.Strokes.Erase(); function then the StrokeErased event will not fire by default.
If you want to maintain added and removed strokes then you can handle it in inkcanvas1.Strokes.StrokesChanged event.
Or you should be in EraseByPoint Mode and use statement
inkcanvas1.EraserShape = new RectangleStylusShape(100, 100);
If you have bulk strokes on your inkcanvas then for maintaining every strokecollection every time is heavy then you have choice to add unique id to every stroke using ProperyData property.

MouseMove Event Registers False Inputs

I have an ellipse in the code below which has a mouse move event. Now everything is good unless I use mouse.capture on the element.
The mouse move event gets fired immediately I apply mouse.capture on the element even if I don't move my mouse, and also moving the position of the element by using setleft property also fires the mouse move event even if I don't move my mouse at all! Is this supposed to happen? And if there's a solution I badly need it..
The code below will generate the exact problem...
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
namespace Test
{
public partial class MainWindow : Window
{
public Ellipse elp;
public Point clickPoint;
public MainWindow()
{
InitializeComponent();
testcanv.Background = Brushes.Transparent;
}
private void down(object sender, MouseButtonEventArgs e)
{
clickPoint = e.GetPosition(testcanv);
if (e.ChangedButton == MouseButton.Left)
{
elp = new Ellipse
{
Stroke = Brushes.Blue,
StrokeThickness = 2,
Width = 200,
Height = 200,
Margin = new Thickness(-250)
};
Canvas.SetLeft(elp, clickPoint.X);
Canvas.SetTop(elp, clickPoint.Y);
elp.MouseMove += circle_move;
testcanv.Children.Add(elp);
}
}
private void circle_move(object sender, MouseEventArgs e)
{
text.Text += "Moved,";
}
private async void click(object sender, RoutedEventArgs e)
{
Mouse.Capture(elp, CaptureMode.Element);
await Task.Delay(1000);
Canvas.SetLeft(elp, 100);
Canvas.SetTop(elp, 100);
await Task.Delay(500);
Mouse.Capture(null);
}
}
}
And here is my XAML
<Window x:Class="Test.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:balchal"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="50"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<Canvas x:Name="testcanv" Grid.Column="0" Grid.Row="0" MouseDown="down"/>
<Button Grid.Column="1" Grid.Row="1" Content="Button" Click="click"/>
<TextBox x:Name="text" Grid.Row="1" Grid.Column="0" HorizontalAlignment="Center" Width="300" Margin="10"/>
</Grid>
</Window>
Thanks in advance... I really appreciate any kind of help...
I may be wrong, but I believe that the MouseMove event always fires when capturing
The simplest solution, if it's happening consistently, is probably just to store the state in the click event and check for it at the start of the move event: returning immediately and resetting the var.
bool CapturedFlag = false;
private void MoveEvent(object sender, MouseButtonEventArgs e)
{
if(CapturedFlag = true)
{
CapturedFlag = false;
return;
}
// other code
}
private async void CaptureEvent(object sender, RoutedEventArgs e)
{
CapturedFlag = true;
// other code
}

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.

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...

How to manipulate a line with the mouse?

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 );
}
}
}

Categories