So let's say I have stackpanel on the left edge with width 200px. Now I want to handle horizontal swipe from left to right on this panel and show additional panel. Then handle swipe from right to left to hide it.
I tried handling page's ManipulationStarted and ManipulationDelta events, but it doesn't seem to have any effect at least with mouse. Any ideas, what could be an easy way to implement that?
What I've tried:
Handle page's swipe events and on the beginning of swipe check, if it was started in bounds of stackpanel, else I ignore it.
If swipe's delta was more then positive 40, looks like swipe was from left to right.
My XAML file:
// standard stuff of page
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<StackPanel Orientation="Vertical" Width="200" HorizontalAlignment="Left" Background="White" x:Name="Panel"/>
</Grid>
// continue standard stuff
C# file:
public MainPage()
{
this.InitializeComponent();
this.ManipulationDelta += MainPage_ManipulationDelta;
this.ManipulationStarted += MainPage_ManipulationStarted;
}
private void MainPage_ManipulationStarted(object sender, ManipulationStartedRoutedEventArgs e)
{
System.Diagnostics.Debug.WriteLine("heh");
if (e.Position.X < 200)
{
initialPoint = e.Position;
isSwiping = true;
}
}
private void MainPage_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
{
if (e.IsInertial && isSwiping)
{
Point currentPoint = e.Position;
if (currentPoint.X - initialPoint.X >= 40)
{
isSwiping = false;
e.Complete();
System.Diagnostics.Debug.WriteLine("finished swipe :)");
}
}
}
private Point initialPoint;
private Boolean isSwiping;
(Again omitted default empty page)
You need to set ManipulationMode on a control e.g. ManipulationMode="TranslateX" and have the control respond to hit-testing (i.e. if it does not have a background - set the Background to Transparent) to receive manipulation events.
Then again - why not just use a ListView that has built-in support for swipes?
Related
I've created an application that can read an XML file and display the rail network specified within a WPF Canvas.
I've been struggling for a while (months) to get the following functionality working and looking for any clue on how to proceed:
Zoom using mousewheel (zooming in makes the icons and tracks bigger whilst zooming out can let me see all the network).
Panning - Using the middle mouse button to drag the view screen.
I ended up having to disable the mouse wheel as it just moved the scrollviewer. In order for me to scroll currently, I have to use the scrollbar arrows.
Usually I would upload code samples but I've also uploaded everything to GitHub with everything needed in case you need to dive into the code or compile.
https://github.com/Legolash2o/NMViewer
XAML
<ScrollViewer x:Name="svCanvas" Grid.Row="2" HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Visible" MouseMove="CCanvas_OnMouseMove" PreviewMouseWheel="SvCanvas_OnPreviewMouseWheel" MouseWheel="SvCanvas_OnPreviewMouseWheel" MouseDown="SvCanvas_OnMouseDown">
<Canvas x:Name="cMain" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Width="36000" Height="58000" >
<Canvas.RenderTransform>
<ScaleTransform x:Name="st"/>
</Canvas.RenderTransform>
</Canvas>
</ScrollViewer>
C#
private void SvCanvas_OnPreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
e.Handled = true;
}
private void SvCanvas_OnMouseDown(object sender, MouseButtonEventArgs e)
{
if (e.MiddleButton == MouseButtonState.Pressed)
{
st.ScaleX = 1;
st.ScaleY = 1;
}
}
private void CCanvas_OnMouseMove(object sender, MouseEventArgs e)
{
if (drawing)
return;
lblBar.Content =
$"H[{svCanvas.HorizontalOffset}, V{svCanvas.VerticalOffset}] Mouse: {Mouse.GetPosition(cMain)}";
}
I would like to enable holding down SHIFT on the keyboard while scrolling to scroll a ScrollViewer horizontally.
I learned from here that the PointerWheelChanged event is the one I am looking for. Handling that, however, doesn't work because the ScrollViewer handles it internally, so my handler is never called. To get around that, I used the AddHandler method, as described in the "Routed Events Overview" article.
This works... but appears to be running my code AFTER the ScrollViewerruns its internal code. The result of this is that the ScrollViewer content pans vertically, then horizontally. They appear to happen in that order, and setting e.Handled = true doesnt stop it.
Is there a way to "intercept" the scroll, so I can handle it with my own logic, thus allowing the ScrollViewer to pan horizontally if SHIFT is pressed? I recently asked a similar question (involving intercepting input to a control so I could handle it with my own logic) here, where the answer involved handling a different event, where that event took place prior to control running its own logic. I do not see a similar "pre stuff happening" event for the pointer scroll.
The code I have follows. Note that the ScrollViewer can scroll BOTH horizontally and vertically, as well as zoom:
<!-- Contained in Grid in a UserControl, if that's relevant -->
<ScrollViewer Name="MyCanvasScrollViewer"
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto"
ZoomMode="Enabled"
ZoomSnapPointsType="Optional"
PointerWheelChanged="MyCanvasScrollViewer_PointerWheelChanged">
<!-- Content to pan -->
</ScrollViewer>
Code-behind:
// Constructor for the user contol.
public MyControl()
{
// Add the scroll wheel event handler and force it to run.
this.MyCanvasScrollViewer.AddHandler(ScrollViewer.PointerWheelChangedEvent, new PointerEventHandler(this.MyCanvasScrollViewer_PointerWheelChanged), true);
// Other un-related stuff omitted here...
}
// Event handler for the Pointer Wheel Changed event.
private void MyCanvasScrollViewer_PointerWheelChanged(object sender, PointerRoutedEventArgs e)
{
// If SHIFT is pressed...
var keyState = CoreWindow.GetForCurrentThread().GetKeyState(VirtualKey.Shift);
if ((keyState & CoreVirtualKeyStates.Down) == CoreVirtualKeyStates.Down)
{
// Get the amount to scroll.
PointerPoint pointer = e.GetCurrentPoint(this.WallCanvasScrollViewer);
double scrollWheelDelta = pointer.Properties.MouseWheelDelta;
// Change the view in the scroll viewer.
this.MyCanvasScrollViewer.ChangeView(scrollWheelDelta, null, null, true);
// Mark event as handled.
e.Handled = true;
}
}
You could simply disable VerticalScrollMode in shift keydown and enable it on keyup. No need for pointerwheelchanged itself. It works perfectly fine.
Xaml
<ScrollViewer ZoomMode="Enabled" x:Name="MyScrollViewer" HorizontalScrollMode="Enabled" HorizontalScrollBarVisibility="Visible" >
<Image Height="600" Width="500" Source="/Assets/1.jpg"></Image>
</ScrollViewer>
//C# code
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
CoreWindow.GetForCurrentThread().KeyDown += MainPage_KeyDown;
CoreWindow.GetForCurrentThread().KeyUp += MainPage_KeyUp; ;
}
private void MainPage_KeyUp(CoreWindow sender, KeyEventArgs args)
{
if (args.VirtualKey == VirtualKey.Shift)
{
MyScrollViewer.IsScrollInertiaEnabled = true;
MyScrollViewer.VerticalScrollMode = ScrollMode.Enabled;
}
}
private void MainPage_KeyDown(CoreWindow sender, KeyEventArgs args)
{
if (args.VirtualKey == VirtualKey.Shift)
{
MyScrollViewer.IsScrollInertiaEnabled = false;
MyScrollViewer.VerticalScrollMode = ScrollMode.Disabled;
}
}
}
I'm now just trying to create a simple image viewer with panning and zooming, for studying WPF.
I wrote my XAML code like this:
<Window x:Class="PanningImageTest.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:PanningImageTest"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Border Name="panBorder" ClipToBounds="True">
<Canvas Name="panImage" MouseLeftButtonDown="Image_MouseLeftButtonDown"
MouseMove="Image_MouseMove"
MouseLeftButtonUp="Image_MouseLeftButtonUp">
<!-- Two Image is intended, as in real situation,
they will use different sources -->
<Image Source="Wallpaper.jpg" Stretch="None"/>
<Image Source="Wallpaper.jpg" Stretch="None"/>
</Canvas>
</Border>
</Grid>
</Window>
And wrote my code like this:
using System.Windows;
using System.Windows.Media;
namespace PanningImageTest
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private Point _start;
private Point _origin;
private void Image_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
this.panImage.CaptureMouse();
_start = e.GetPosition(this.panBorder);
_origin.X = this.panImage.RenderTransform.Value.OffsetX;
_origin.Y = this.panImage.RenderTransform.Value.OffsetY;
}
private void Image_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
{
if (!this.panImage.IsMouseCaptured)
{
return;
}
Point mousePos = e.GetPosition(this.panBorder);
Matrix matrix = this.panImage.RenderTransform.Value;
matrix.OffsetX = _origin.X + (mousePos.X - _start.X);
matrix.OffsetY = _origin.Y + (mousePos.Y - _start.Y);
this.panImage.RenderTransform = new MatrixTransform(matrix);
}
private void Image_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
this.panImage.ReleaseMouseCapture();
}
}
}
If I call this.panImage.CaptureMouse() BEFORE the _start = ... etc ... lines, All the Images just teleports to the position of the mouse as soon as I clicked somewhere on the image.
Like this:
Call before
But if I call the same function after the lines, like the following, it just works as intended:
_start = e.GetPosition(this.panBorder);
_origin.X = this.panImage.RenderTransform.Value.OffsetX;
_origin.Y = this.panImage.RenderTransform.Value.OffsetY;
this.panImage.CaptureMouse();
Like this:
Call After
If I use only one Image tag in Canvas, it works nicely in both cases.
I tried changing .NET versions, moving events to Border instead of Canvas, but everything just failed to explain these results.
I have no idea why this happens. Can anyone give some explanations?
MouseMove event has started to be fired even the MouseLeftButtonDown method has not yet finished.
So you call CaptureMouse() method as the last line, and use the following check as a guard to prevent the image from being dragged when you have not yet gotten the value of _origin.
if (!this.panImage.IsMouseCaptured)
{
return;
}
You need to set the start position and the coordinates before you start to calculate the offsets. Otherwise you get the offsets wrong. So it makes no sense to capture the mouse before you have done this.
Note that the MouseMove event will be raised as soon as you move the mouse(all the time basically) but until you have captured it, the event handler will just return without moving the image by setting its RenderTransform property.
So you should set the start position and the X and Y coordinates and then move the image based on these. Not the other way around.
Is it possible to move a Tooltip or something like that with cursor when mouse goes on a specific control?
I tried TextBlock, but Margin property not work.
private TextBlock tooltip = new TextBlock();
private void imgRoom_MouseEnter(object sender, MouseEventArgs e)
{
Point position = e.GetPosition((IInputElement)sender);
tooltip.Visibility = System.Windows.Visibility.Visible;
tooltip.Margin = new Thickness(position.X, position.Y, 0, 0);
tooltip.Width = 100;
tooltip.Height = 100;
tooltip.Background = new SolidColorBrush(Colors.Red);
}
private void imgRoom_MouseMove(object sender, MouseEventArgs e)
{
Point position = e.GetPosition((IInputElement)sender);
tooltip.Margin = new Thickness(position.X, position.Y, 0, 0);
}
You can achieve the effect using a Popup and some simple properties upon it. From window code...
<Window x:Class="WpfApplication3.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">
<Grid>
<Rectangle Name="rect" Margin="50,50,0,0" Width="100" Height="100" Fill="LightBlue" MouseMove="Rectangle_MouseMove" MouseLeave="Rectangle_MouseLeave" />
<Popup Name="floatingTip" AllowsTransparency="True" Placement="Relative" PlacementTarget="{Binding ElementName=rect}">
<TextBlock>Look At Me</TextBlock>
</Popup>
</Grid>
</Window>
And this is what the codebehind would look like.
...
private void Rectangle_MouseMove(object sender, MouseEventArgs e)
{
if (!floatingTip.IsOpen) { floatingTip.IsOpen = true; }
Point currentPos = e.GetPosition(rect);
// The + 20 part is so your mouse pointer doesn't overlap.
floatingTip.HorizontalOffset = currentPos.X + 20;
floatingTip.VerticalOffset = currentPos.Y;
}
private void Rectangle_MouseLeave(object sender, MouseEventArgs e)
{
floatingTip.IsOpen = false;
}
...
So from the XAML you can see that the popup placement is relative to the rectangle. When you go mousing over the rectangle, it becomes visible, and its position is updated as the mouse moves. Naturally this is a very basic solution, but with some minor tweaks, handling events like 'MouseEnter' and property adjustment you can come up with some really neat effects.
I don't know if this is a best practice, or if it performs well, but you could use an Adorner.
I've created a proof of concept before, but haven't used it in a production scenario (yet). The adorner can be used to create something like this (tooltip), or a custom mouse cursor or drop target icons.
Make sure you set IsHitTestVisible = false if the adorner doesn't need to be hit tested and might appear directly under the mouse location.
UPDATE
Just fully read the description of adorners:
Common applications for adorners include:
Adding functional handles to a UIElement that enable a user to manipulate the element in some way (resize, rotate, reposition, etc.).
Provide visual feedback to indicate various states, or in response to various events.
Overlay visual decorations on a UIElement.
Visually mask or override part or all of a UIElement.
This moves the tooltip around with the mouse cursor.
private void OnMouseMoveHandler(object sender, MouseEventArgs args)
{
if ((sender as FrameworkElement).ToolTip == null)
(sender as FrameworkElement).ToolTip = new ToolTip() { Placement = PlacementMode.Relative };
double x = args.GetPosition((sender as FrameworkElement)).X;
double y = args.GetPosition((sender as FrameworkElement)).Y;
var tip = ((sender as FrameworkElement).ToolTip as ToolTip);
tip.Content = tooltip_text;
tip.HorizontalOffset = x + 10;
tip.VerticalOffset = y + 10;
}
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);