Sliding images with finger holding and moving in WPF, just like smartphone - wpf

This is my code:
<Grid Grid.Row="1" >
<Image Stretch="Fill" Source="{Binding Path=MyImage}"/>
</Grid>
I have an image in my view, and I set its surce by binding.
Outside of this image, I have two arrows (left arrow and right). If the user clicks on the right arrow, I'm showing him the next image, if he clicks on the right arrow again - the next one, etc. This works very well.
Now I want to do it to work just like we slide images in our smartphones, by using our thumb, without using arrows. So, I need to make the images slide by clicking the mouse button, holding it and then moving it left or right.
How can I do that?

You could create a Behavior that will handle the scrolling:
public class ScrollViewerTouchScrollBehaviour : Behavior<ScrollViewer>
{
private double? _horizontalOffset;
private Point? _position;
private double _sensitivity = 1;
public double Sensitivity
{
get { return _sensitivity; }
set { _sensitivity = value; }
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.PreviewMouseLeftButtonDown += PreviewMouseLeftButtonDown;
AssociatedObject.PreviewMouseMove += PreviewMouseMove;
AssociatedObject.PreviewMouseLeftButtonUp += PreviewMouseLeftButtonUp;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.PreviewMouseLeftButtonDown -= PreviewMouseLeftButtonDown;
AssociatedObject.PreviewMouseMove -= PreviewMouseMove;
AssociatedObject.PreviewMouseLeftButtonUp -= PreviewMouseLeftButtonUp;
}
private void PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
_horizontalOffset = AssociatedObject.HorizontalOffset;
_position = e.GetPosition(AssociatedObject);
}
private void PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
AssociatedObject.ReleaseMouseCapture();
}
private void PreviewMouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton != MouseButtonState.Pressed || !_horizontalOffset.HasValue || !_position.HasValue)
{
return;
}
var point = e.GetPosition(AssociatedObject);
var diff = point.X / Sensitivity - _position.Value.X / Sensitivity;
AssociatedObject.ScrollToHorizontalOffset(_horizontalOffset.Value - diff);
}
}
The Sensitivity property allows you to tweek the speed of the scrolling. Experiment with different values to see what best suits your application.
Then in your xaml place the content that you want to be able to scroll inside a ScrollViewer and add the behavior:
<ScrollViewer CanContentScroll="True"
HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Hidden"
VerticalAlignment="Stretch">
<!-- Your content here -->
<i:Interaction.Behaviors>
<behaviours:ScrollViewerTouchScrollBehaviour Sensitivity="1" />
</i:Interaction.Behaviors>
</ScrollViewer>
You will of course need to add a reference to Systems.Windows.Interactivty in your behavior and in the xaml.
You could alternatively just handle the events in code-behind if you don't want to use a behavior.

You can handle the mouse events on down and up and calculate the X-value
private Point down;
public MainWindow()
{
InitializeComponent();
}
private void Image_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
down = e.GetPosition(this);
}
private void Image_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
Point up = e.GetPosition(this);
}
On Image_MouseLeftButtonUp you can calculate with down.X and up.X the difference and start your actions.

Related

How can I place one control in a callout that orginates from another control in XAML?

Here is a setup: I have a textbox with a numberic value. According to the requirements every time anybody changes that value an accompanying comment needs to be provided. So visually there must be another textbox for the comment that should be displayed right next to the first one. Ideally the comment textbox needs to be placed in a callout that originates from the value textbox and displayed on the right from it overlaying anything what's underneath of it just like on this picture:
I know how to do easily it in CSS and HTML.
I have to do the same in Silverlight now.
Unfortunately I am not very strong in it, so what I am specifically asking about is how having 2 textboxes make one of them appear next to another (on the right overlaying whatever controls are underneath it) with as less XAML and code as possible.
Use a ToolTip, and set the Placement such that it appears to the right. in XAML, you can template your ToolTip to look however you want, even if that means mimicking the TextBox appearance.
This is the purpose of the ToolTip, and I feel strongly that you should always use the right tool for the right job. :)
I hope this helps. Let us know if you need code samples.
EDIT: Added the following code samples:
<TextBox ToolTipService.Placement="Right">
<ToolTipService.ToolTip>
<TextBox Text="{Binding CalloutText, Mode=OneWay}" IsReadOnly="True"/>
</ToolTipService.ToolTip>
</TextBox>
Ok, I ended up writing my own behaviour
namespace MyNamespace
{
public class CommentBehavior : Behavior<TextBox>
{
private readonly TimeSpan howLongWeWaitBeforePopupCloses = TimeSpan.FromMilliseconds(200);
private DispatcherTimer popupClosingTimer;
public static DependencyProperty PopupProperty = DependencyProperty.Register("Popup", typeof(Popup), typeof(CommentBehavior), new PropertyMetadata(null));
public Popup Popup
{
get { return (Popup)this.GetValue(PopupProperty); }
set { this.SetValue(PopupProperty, value); }
}
protected override void OnAttached()
{
this.popupClosingTimer = new DispatcherTimer();
this.popupClosingTimer.Stop();
this.popupClosingTimer.Interval = howLongWeWaitBeforePopupCloses;
this.popupClosingTimer.Tick += this.ClosePopup;
this.AssociatedObject.GotFocus += this.GotFocus;
this.AssociatedObject.LostFocus += this.LostFocus;
this.Popup.Child.GotFocus += PopupChild_GotFocus;
this.Popup.Child.LostFocus += PopupChild_LostFocus;
}
private void PopupChild_LostFocus(object sender, RoutedEventArgs e)
{
this.popupClosingTimer.Start();
}
private void PopupChild_GotFocus(object sender, RoutedEventArgs e)
{
this.popupClosingTimer.Stop();
}
protected override void OnDetaching()
{
this.AssociatedObject.GotFocus -= this.GotFocus;
this.AssociatedObject.LostFocus -= this.LostFocus;
this.Popup.GotFocus -= PopupChild_GotFocus;
this.popupClosingTimer.Tick -= this.ClosePopup;
this.popupClosingTimer = null;
}
private void ClosePopup(object sender, EventArgs e)
{
this.Popup.IsOpen = false;
this.popupClosingTimer.Stop();
}
protected void GotFocus(object sender, RoutedEventArgs e)
{
this.popupClosingTimer.Stop();
this.Popup.IsOpen = true;
var at = this.CalculatePopupPosition();
this.Popup.HorizontalOffset = at.X;
this.Popup.VerticalOffset = at.Y;
}
private Point CalculatePopupPosition()
{
var owner = this.AssociatedObject;
var transformation = owner.TransformToVisual(Application.Current.RootVisual);
var at = transformation.Transform(new Point(owner.ActualWidth, 0));
return at;
}
protected void LostFocus(object sender, RoutedEventArgs e)
{
this.popupClosingTimer.Start();
}
}
}
And the following XAML
<Grid x:Name="LayoutRoot" Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Background="Red">
<TextBox Width="200" Text="0.01">
<i:Interaction.Behaviors>
<local:CommentBehavior>
<local:CommentBehavior.Popup>
<Popup>
<TextBox Text="Comment" />
</Popup>
</local:CommentBehavior.Popup>
</local:CommentBehavior>
</i:Interaction.Behaviors>
</TextBox>
</StackPanel>
</Grid>

moving any control in wpf

I am trying to move control in wpf using Canvas
This is the XAML
<Canvas Grid.Column="1" Grid.Row="0" x:Name="DropCanvas" AllowDrop="True" DragOver="DropCanvas_DragOver"
Drop="Canvas_Drop" DragEnter="Canvas_DragEnter" Background="#12000000" >
<TextBox Canvas.Left="162" Canvas.Top="188" Height="23" Name="textBox1" Width="120"
PreviewMouseMove="textBox1_PreviewMouseMove"
PreviewMouseLeftButtonDown="textBox1_PreviewMouseLeftButtonDown"
PreviewMouseUp="textBox1_PreviewMouseUp" />
</Canvas>
and this is the Code
Point p = new Point();
private void textBox1_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Control control = sender as Control;
control.CaptureMouse();
p = e.GetPosition(control);
}
private void textBox1_PreviewMouseMove(object sender, MouseEventArgs e)
{
Control control = sender as Control;
Point x = e.GetPosition(control);
if (e.LeftButton == MouseButtonState.Pressed)
{
Canvas.SetLeft(control, Canvas.GetLeft(control) + (x.X - p.X));
Canvas.SetTop(control, Canvas.GetTop(control) + (x.Y - p.Y));
}
p = x;
}
private void textBox1_PreviewMouseUp(object sender, MouseButtonEventArgs e)
{
Control control = sender as Control;
control.ReleaseMouseCapture();
activated = false;
}
The code is working, but when it moves, the control shakes.
What is the proplem
When you call GetPosition you should use DropCanvas as the parameter instead of the control. You're seeing vibrations because the TextBox keeps moving and you need something fixed.
Alternatively, you can use the MouseDragElementBehavior available in Expression Blend SDK to move objects in a container.
Also, this project can be useful to you: http://www.codeproject.com/Articles/24681/WPF-Diagram-Designer-Part-4
public void dragme(object sender, MouseButtonEventArgs e)
{
if (_Move.IsChecked == true)
db.Attach((DependencyObject)sender);
}
//// MouseDragElementBehavior db;
private void canvass_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
if (_Move.IsChecked == true && filmgrid.Visibility == Visibility.Visible)// == true)
{
filmgrid.PreviewMouseDown += new MouseButtonEventHandler(dragme);
}

How to smoothly drag an image box in WPF?

I'm working with WPF 4 and VB.net 2010. My project consists of full-screen windows with a 640x480 grid in the center.
In this project, I want to have various image boxes (which will have the item .png images in them), that the user can drag around and drop in various places on the grid.
In essence, I need to be able to make it possible for the item to be clicked and dragged around the grid, with the image box still visible and the same size as the user moves it around. It should never be able to leave the grid. I also need to be able to determine if the object is over another object, so when the mouse button is released, the dragged object is "dropped", and it triggers a particular block of code.
How do I do this?
Something similar to your 'project', but using C#
From http://pastie.org/1498237
// XAML
<Window x:Class="MyProject.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="500" Width="500">
<Grid>
<Canvas x:Name="mycanv">
<Image Width="150" x:Name="myimg" Source="some_source.png"/>
</Canvas>
</Grid>
</Window>
//C#
private Point mouseClick;
private double canvasLeft;
private double canvasTop;
public Window1()
{
InitializeComponent();
foreach (object obj in mycanv.Children)
{
try
{
Image img = (Image)obj;
img.PreviewMouseDown += new MouseButtonEventHandler(myimg_MouseDown);
img.PreviewMouseMove += new MouseEventHandler(myimg_MouseMove);
img.PreviewMouseUp += new MouseButtonEventHandler(myimg_MouseUp);
img.TextInput += new TextCompositionEventHandler(myimg_TextInput);
img.LostMouseCapture += new MouseEventHandler(myimg_LostMouseCapture);
img.SetValue(Canvas.LeftProperty, 0.0);
img.SetValue(Canvas.TopProperty, 0.0);
}
catch
{
//do something
}
}
}
void myimg_LostMouseCapture(object sender, MouseEventArgs e)
{
((Image)sender).ReleaseMouseCapture();
}
void myimg_TextInput(object sender, TextCompositionEventArgs e)
{
((Image)sender).ReleaseMouseCapture();
}
void myimg_MouseUp(object sender, MouseButtonEventArgs e)
{
((Image)sender).ReleaseMouseCapture();
}
void myimg_MouseMove(object sender, MouseEventArgs e)
{
if (((Image)sender).IsMouseCaptured)
{
Point mouseCurrent = e.GetPosition(null);
double Left = mouseCurrent.X - canvasLeft;
double Top = mouseCurrent.Y - canvasTop;
((Image)sender).SetValue(Canvas.LeftProperty, canvasLeft + Left);
((Image)sender).SetValue(Canvas.TopProperty, canvasTop + Top);
canvasLeft = Canvas.GetLeft(((Image)sender));
canvasTop = Canvas.GetTop(((Image)sender));
}
}
void myimg_MouseDown(object sender, MouseButtonEventArgs e)
{
mouseClick = e.GetPosition(null);
canvasLeft = Canvas.GetLeft(((Image)sender));
canvasTop = Canvas.GetTop(((Image)sender));
((Image)sender).CaptureMouse();
}

Keeping keyboard focus on a single control while still beeing able to use a ListBox

Working on a TouchScreen application which also has a keyboard attached, I have the following problem:
The WPF window has a TextBox, which should receive ALL keyboard input. There are also Buttons and a ListBox, which are solely used by the TouchScreen(=Mouse).
A very simple example looks like this:
<Window x:Class="KeyboardFocusTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1">
<StackPanel>
<TextBox Text="{Binding Input, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
PreviewLostKeyboardFocus="TextBox_PreviewLostKeyboardFocus"/>
<Button Click="Button_Click">Add</Button>
<ListBox ItemsSource="{Binding Strings}" />
</StackPanel>
</Window>
To keep the TextBox always focused, I just do:
private void TextBox_PreviewLostKeyboardFocus(object sender, System.Windows.Input.KeyboardFocusChangedEventArgs e)
{
e.Handled = true;
}
So far so good - the problem now is, that I can't select items from the ListBox anymore. This only seems to work, if the ListBox has the keyboard focus. But if I loose the keyboard focus on the TextBox, I can't enter text anymore without clicking it first.
Any ideas, comments suggestions are welcome!
There might be a more elegant solution for this, but you could always handle the PreviewKeyDown event at the Window level, and pass focus to the TextBox if it doesn't already have it, instead of preventing it from losing focus in the first place. That way, the ListBox can use focus as is normal, but as soon as a key is pressed focus jumps right to the TextBox. In addition, you can filter out keys that you don't want to switch focus - the arrow keys come to mind, which could then be used to move up and down in the ListBox.
Adding an event handler like the following should do the trick:
private void Window_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (!textBox.IsFocused)
{
textBox.Focus();
}
}
Based on Nicholas' suggestion (thx!), here's a markup extension, which is used like:
<TextBox Helpers:KeyboardFocusAttractor.IsAttracted="true" />
It seems to work, and ANTS didn't show any memory leaks. But when it comes to WPF and especially events and bindings, you never know, so use with care!
public static class KeyboardFocusAttractor
{
public static readonly DependencyProperty IsAttracted = DependencyProperty.RegisterAttached("IsAttracted",
typeof (bool), typeof (KeyboardFocusAttractor), new PropertyMetadata(false, OnIsAttracted));
private static void OnIsAttracted(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var isAttracted = (bool) e.NewValue;
var controlWithInputFocus = d as Control;
if (controlWithInputFocus != null)
{
if (isAttracted)
{
new KeyboardInputFocusEventManager(controlWithInputFocus);
}
}
}
public static void SetIsAttracted(DependencyObject dp, bool value)
{
dp.SetValue(IsAttracted, value);
}
public static bool GetIsAttracted(DependencyObject dp)
{
return (bool) dp.GetValue(IsAttracted);
}
private class KeyboardInputFocusEventManager
{
private readonly Control _control;
private Window _window;
public KeyboardInputFocusEventManager(Control control)
{
_control = control;
_control.Loaded += ControlLoaded;
_control.IsVisibleChanged += ControlIsVisibleChanged;
_control.Unloaded += ControlUnloaded;
}
private void ControlLoaded(object sender, RoutedEventArgs e)
{
_window = Window.GetWindow(_control);
if (_window != null)
{
_control.Unloaded += ControlUnloaded;
_control.IsVisibleChanged += ControlIsVisibleChanged;
if (_control.IsVisible)
{
_window.PreviewKeyDown += ParentWindowPreviewKeyDown;
}
}
}
private void ControlUnloaded(object sender, RoutedEventArgs e)
{
_control.Unloaded -= ControlUnloaded;
_control.IsVisibleChanged -= ControlIsVisibleChanged;
}
private void ControlIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (_window != null)
{
_window.PreviewKeyDown -= ParentWindowPreviewKeyDown;
}
if (_control.IsVisible)
{
_window = Window.GetWindow(_control);
if (_window != null)
{
_window.PreviewKeyDown += ParentWindowPreviewKeyDown;
}
}
}
private void ParentWindowPreviewKeyDown(object sender, KeyEventArgs e)
{
Keyboard.Focus(_control);
}
}
}

Interpret enter as tab WPF

I want to interpret Enter key as Tab key in whole my WPF application, that is, everywhere in my application when user press Enter I want to focus the next focusable control,except when button is focused. Is there any way to do that in application life circle? Can anyone give me an example?
Thanks a lot!
You can use my EnterKeyTraversal attached property code if you like. Add it to the top-level container on a WPF window and everything inside will treat enter as tab:
<StackPanel my:EnterKeyTraversal.IsEnabled="True">
...
</StackPanel>
Based on Richard Aguirre's answer, which is better than the selected answer for ease of use, imho, you can make this more generic by simply changing the Grid to a UIElement.
To change it in whole project you need to do this
In App.xaml.cs:
protected override void OnStartup(StartupEventArgs e)
{
EventManager.RegisterClassHandler(typeof(UIElement), UIElement.PreviewKeyDownEvent, new KeyEventHandler(Grid_PreviewKeyDown));
base.OnStartup(e);
}
private void Grid_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
var uie = e.OriginalSource as UIElement;
if (e.Key == Key.Enter)
{
e.Handled = true;
uie.MoveFocus(
new TraversalRequest(
FocusNavigationDirection.Next));
}
}
Compile.
And done it. Now you can use enter like tab.
Note: This work for elements in the grid
I got around woodyiii's issue by adding a FrameworkElement.Tag (whose value is IgnoreEnterKeyTraversal) to certain elements (buttons, comboboxes, or anything I want to ignore the enter key traversal) in my XAML. I then looked for this tag & value in the attached property. Like so:
if (e.Key == Key.Enter)
{
if (ue.Tag != null && ue.Tag.ToString() == "IgnoreEnterKeyTraversal")
{
//ignore
}
else
{
e.Handled = true;
ue.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
}
}
woodyiii, There is a function in the UIElement called PredictFocus() which by its name know its function, then you can check if that element is enabled or not so as to move the focus to it or not...
Here is Matt Hamilton's code, if anyone is wondering since his site is down apparently:
public class EnterKeyTraversal
{
public static bool GetIsEnabled(DependencyObject obj)
{
return (bool)obj.GetValue(IsEnabledProperty);
}
public static void SetIsEnabled(DependencyObject obj, bool value)
{
obj.SetValue(IsEnabledProperty, value);
}
static void ue_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
var ue = e.OriginalSource as FrameworkElement;
if (e.Key == Key.Enter)
{
e.Handled = true;
ue.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
}
}
private static void ue_Unloaded(object sender, RoutedEventArgs e)
{
var ue = sender as FrameworkElement;
if (ue == null) return;
ue.Unloaded -= ue_Unloaded;
ue.PreviewKeyDown -= ue_PreviewKeyDown;
}
public static readonly DependencyProperty IsEnabledProperty =
DependencyProperty.RegisterAttached("IsEnabled", typeof(bool),
typeof(EnterKeyTraversal), new UIPropertyMetadata(false, IsEnabledChanged));
static void IsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var ue = d as FrameworkElement;
if (ue == null) return;
if ((bool)e.NewValue)
{
ue.Unloaded += ue_Unloaded;
ue.PreviewKeyDown += ue_PreviewKeyDown;
}
else
{
ue.PreviewKeyDown -= ue_PreviewKeyDown;
}
}
}
Another, a more on/off implementation approach would be to use behaviors:
public class TextBoxEnterFocusesNextBehavior :
Behavior<TextBox>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.PreviewKeyDown += AssociatedObjectOnPreviewKeyDown;
}
protected override void OnDetaching()
{
AssociatedObject.PreviewKeyDown -= AssociatedObjectOnPreviewKeyDown;
base.OnDetaching();
}
private void AssociatedObjectOnPreviewKeyDown(object sender, KeyEventArgs args)
{
if (args.Key != Key.Enter) { return; }
args.Handled = true;
AssociatedObject.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
}
}
Usage example:
<UserControl xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
xmlns:behaviors="clr-namespace:Your.Namespace.To.Behaviors"
...>
<DockPanel>
<TextBox x:Name="TextBoxWithBehavior"
DockPanel.Dock="Top">
<b:Interaction.Behaviors>
<behaviors:TextBoxEnterFocusesNextBehavior />
</b:Interaction.Behaviors>
</TextBox>
<TextBox x:Name="TextBoxWithoutBehavior"
DockPanel.Dock="Top" />
<TextBox x:Name="AnotherTextBoxWithBehavior"
DockPanel.Dock="Top">
<b:Interaction.Behaviors>
<behaviors:TextBoxEnterFocusesNextBehavior />
</b:Interaction.Behaviors>
</TextBox>
</DockPanel>
</UserControl>
My solution:
public class MoveToNext : TriggerAction<DependencyObject>
{
protected override void Invoke(object parameter)
{
if (parameter is RoutedEventArgs routedEventArgs && routedEventArgs.OriginalSource is FrameworkElement element)
{
routedEventArgs.Handled = true;
element.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
}
}
}
Usage:
<StackPanel>
<i:Interaction.Triggers>
<i:KeyTrigger Key="Return">
<util:MoveToNext/>
</i:KeyTrigger>
</i:Interaction.Triggers>
<!-- put your controls here -->
</StackPanel>
If you want the behavior to be attached to only one control instead of all controls within a layouter, simply add the <i:Interaction.Triggers block to that specific control.

Resources