I'm trying to conform to the MVVM structure when getting the mouse position when it is over an image in a wpf application. The mouse position should be converted to a pixel location with respect to the image.
I have this working when Image_MouseMove is in ImagePositionView.xaml.cs, but I'm at a bit of a loss (even after trying to read other threads) as to how to achieve this using the MVVM structure.
I have added a reference to MVVMLight in hopes that this will make this task easier, but I have never used to before..
This is what I have so far:
View:
I have added these references based on what I have seen:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:cmd="http://www.galasoft.ch/mvvmlight"
<UserControl x:Class="ImagePixelLocation.View.ImagePositionView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:cmd="http://www.galasoft.ch/mvvmlight"
xmlns:local="clr-namespace:ImagePixelLocation"
mc:Ignorable="d"
d:DesignHeight="600" d:DesignWidth="1000" Background="White">
<Grid>
<Viewbox HorizontalAlignment="Center">
<Grid Name="ColorImage">
<Image x:Name="ImageOnDisplay" Source="{Binding ColourImage}" Stretch="UniformToFill" />
</Grid>
</Viewbox>
</Grid>
</UserControl>
ViewModel:
ViewModelBase exposes INofityPropertyChanged and IDisposable
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using GalaSoft.MvvmLight;
namespace ImagePixelView.ViewModel
{
class ImagePositionViewModel : ViewModelBase
{
private WriteableBitmap colourBitmap = null;
public ImageSource ColourImage
{
get
{
return this.colourBitmap;
}
}
public ManualSelectionViewModel()
{
// Open image to writeablebitmap
string path = #"C:\Some\Path\To\ColorImage.png";
Stream imageStreamSource = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
var decoder = new PngBitmapDecoder(imageStreamSource, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
BitmapSource source = decoder.Frames[0];
int width = source.PixelWidth;
int height = source.PixelHeight;
int stride = source.Format.BitsPerPixel / 8 * width;
byte[] data = new byte[stride * height];
source.CopyPixels(data, stride, 0);
this.colourBitmap = new WriteableBitmap(width, height, 96.0, 96.0, source.Format, null);
this.colourBitmap.WritePixels(new Int32Rect(0, 0, width, height), data, stride, 0);
}
private void Image_MouseMove(object sender, MouseEventArgs e)
{
BitmapSource bitmapImage = (BitmapSource)this.ColourImage;
string xCoord = (e.GetPosition(ImageOnDisplay).X * bitmapImage.PixelWidth / ImageOnDisplay.ActualWidth).ToString();
string yCoord = (e.GetPosition(ImageOnDisplay).Y * bitmapImage.PixelHeight / ImageOnDisplay.ActualHeight).ToString();
System.Diagnostics.Debug.WriteLine("mouse location is X:" + xCoord + ", Y:" + yCoord);
}
}
}
I guess the main thing is how do I get access to the view element ImageOnDisplay from within ImagePositionViewModel.
I do this with a behaviour. First I declare an interface that my view model will implement:
public interface IMouseCaptureProxy
{
event EventHandler Capture;
event EventHandler Release;
void OnMouseDown(object sender, MouseCaptureArgs e);
void OnMouseMove(object sender, MouseCaptureArgs e);
void OnMouseUp(object sender, MouseCaptureArgs e);
}
public class MouseCaptureArgs
{
public double X {get; set;}
public double Y { get; set; }
public bool LeftButton { get; set; }
public bool RightButton { get; set; }
}
And here's a behaviour that uses it:
public class MouseCaptureBehavior : Behavior<FrameworkElement>
{
public static readonly DependencyProperty ProxyProperty = DependencyProperty.RegisterAttached(
"Proxy",
typeof(IMouseCaptureProxy),
typeof(MouseCaptureBehavior),
new PropertyMetadata(null, OnProxyChanged));
public static void SetProxy(DependencyObject source, IMouseCaptureProxy value)
{
source.SetValue(ProxyProperty, value);
}
public static IMouseCaptureProxy GetProxy(DependencyObject source)
{
return (IMouseCaptureProxy)source.GetValue(ProxyProperty);
}
private static void OnProxyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.OldValue is IMouseCaptureProxy)
{
(e.OldValue as IMouseCaptureProxy).Capture -= OnCapture;
(e.OldValue as IMouseCaptureProxy).Release -= OnRelease;
}
if (e.NewValue is IMouseCaptureProxy)
{
(e.NewValue as IMouseCaptureProxy).Capture += OnCapture;
(e.NewValue as IMouseCaptureProxy).Release += OnRelease;
}
}
static void OnCapture(object sender, EventArgs e)
{
var behavior = sender as MouseCaptureBehavior;
if (behavior != null)
behavior.AssociatedObject.CaptureMouse();
}
static void OnRelease(object sender, EventArgs e)
{
var behavior = sender as MouseCaptureBehavior;
if (behavior != null)
behavior.AssociatedObject.ReleaseMouseCapture();
}
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.PreviewMouseDown += OnMouseDown;
this.AssociatedObject.PreviewMouseMove += OnMouseMove;
this.AssociatedObject.PreviewMouseUp += OnMouseUp;
}
protected override void OnDetaching()
{
base.OnDetaching();
this.AssociatedObject.PreviewMouseDown -= OnMouseDown;
this.AssociatedObject.PreviewMouseMove -= OnMouseMove;
this.AssociatedObject.PreviewMouseUp -= OnMouseUp;
}
private void OnMouseDown(object sender, MouseButtonEventArgs e)
{
var proxy = GetProxy(this);
if (proxy != null)
{
var pos = e.GetPosition(this.AssociatedObject);
var args = new MouseCaptureArgs {
X = pos.X,
Y = pos.Y,
LeftButton = (e.LeftButton == MouseButtonState.Pressed),
RightButton = (e.RightButton == MouseButtonState.Pressed)
};
proxy.OnMouseDown(this, args);
}
}
private void OnMouseMove(object sender, MouseEventArgs e)
{
var proxy = GetProxy(this);
if (proxy != null)
{
var pos = e.GetPosition(this.AssociatedObject);
var args = new MouseCaptureArgs {
X = pos.X,
Y = pos.Y,
LeftButton = (e.LeftButton == MouseButtonState.Pressed),
RightButton = (e.RightButton == MouseButtonState.Pressed)
};
proxy.OnMouseMove(this, args);
}
}
private void OnMouseUp(object sender, MouseButtonEventArgs e)
{
var proxy = GetProxy(this);
if (proxy != null)
{
var pos = e.GetPosition(this.AssociatedObject);
var args = new MouseCaptureArgs
{
X = pos.X,
Y = pos.Y,
LeftButton = (e.LeftButton == MouseButtonState.Pressed),
RightButton = (e.RightButton == MouseButtonState.Pressed)
};
proxy.OnMouseUp(this, args);
}
}
}
To use this behaviour you add it to the target UI element and bind to an object that implements the proxy interface. In this case I made the MainViewModel implement the interface so I just bind to that:
<!-- Canvas must have a background, even if it's Transparent -->
<Canvas Background="White" xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity">
<i:Interaction.Behaviors>
<behaviors:MouseCaptureBehavior Proxy="{Binding}" />
</i:Interaction.Behaviors>
The view model now needs to provide mouse handlers which the behaviour will call, it also needs to provide the Capture/Release events which the behaviour will respond to when raised by the view model:
public class MainViewModel : ViewModelBase, IMouseCaptureProxy
{
public event EventHandler Capture;
public event EventHandler Release;
public void OnMouseDown(object sender, MouseCaptureArgs e) {...}
public void OnMouseMove(object sender, MouseCaptureArgs e) {...}
public void OnMouseUp(object sender, MouseCaptureArgs e) {...}
}
UPDATE: It should be self-evident, but just in case not: the sender that you pass in to the Capture and Release events should be the same one you received via the MouseDown/Move/Up handlers. The event args passed to Capture/Receive aren't used and can be null.
Solution for Loading image and display its xy coordinates with rgb values when mouse move on image using MVVM..
Hope this will be help full for some one..
ViewModel:
class MainWindowViewModel : ViewModelBase
{
private Bitmap Img;
public ICommand OpenImg { get; set; }
public MainWindowViewModel()
{
OpenImg = new RelayCommand(openImg, (obj) => true);
}
private void openImg(object obj = null)
{
OpenFileDialog op = new OpenFileDialog();
op.Title = "Select a picture";
op.Filter = "All supported graphics|*.jpg;*.jpeg;*.png;*.bmp;*.tiff|" +
"JPEG (*.jpg;*.jpeg)|*.jpg;*.jpeg|" +
"Portable Network Graphic (*.png)|*.png";
if (op.ShowDialog() == true)
{
ImgPath = op.FileName;
Img = new Bitmap(ImgPath);
}
}
private string _ImgPath;
public string ImgPath
{
get
{
return _ImgPath;
}
set
{
_ImgPath = value;
OnPropertyChanged("ImgPath");
}
}
private ICommand _mouseMoveCommand;
public ICommand MouseMoveCommand
{
get
{
if (_mouseMoveCommand == null)
{
_mouseMoveCommand = new RelayCommand(param => ExecuteMouseMove((MouseEventArgs)param));
}
return _mouseMoveCommand;
}
set { _mouseMoveCommand = value; }
}
private void ExecuteMouseMove(MouseEventArgs e)
{
System.Windows.Point p = e.GetPosition(((IInputElement)e.Source));
XY = String.Format("X: {0} Y:{1}", (int)p.X, (int)p.Y);
BitmapData bd = Img.LockBits(new Rectangle(0, 0, Img.Width, Img.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
unsafe
{
byte* ptr = (byte*)bd.Scan0;
int x = (int)p.X * 3;
int y = (int)p.Y * bd.Stride;
RGB = "R: "+ptr[x + y + 2].ToString() + " G: " + ptr[x + y + 1].ToString() + " B: " + ptr[x + y].ToString();
}
Img.UnlockBits(bd);
}
private string xy;
public string XY
{
get { return xy; }
set
{
xy = value;
OnPropertyChanged("XY");
}
}
private string rgb;
public string RGB
{
get { return rgb; }
set
{
rgb = value;
OnPropertyChanged("RGB");
}
}
}
MainWindow.xaml
<Window.Resources>
<vm:MainWindowViewModel x:Key="MainWindowViewModel"/>
</Window.Resources>
<Grid DataContext="{StaticResource MainWindowViewModel}">
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="*" />
<RowDefinition Height="25"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Menu FontSize="20">
<MenuItem Header="File">
<MenuItem Header="Open" Command="{Binding OpenImg}"/>
</MenuItem>
</Menu>
</Grid>
<Grid Grid.Row="1" Background="LightGray">
<Viewbox Margin="3,3,3,3">
<Image x:Name="img" Stretch="None" Source="{Binding ImgPath}"
Model:MouseBehaviour.MouseMoveCommand="{Binding MouseMoveCommand}">
</Image>
</Viewbox>
</Grid>
<Grid Grid.Row="2">
<StackPanel Orientation="Horizontal">
<TextBox Focusable="False" Text="{Binding XY}" Width="100"/>
<TextBox Focusable="False" Text="{Binding RGB}" Width="115"/>
</StackPanel>
</Grid>
</Grid>
Related
Is it possible to Animate a picture which i get through binding? As i am new to C# and WPF i am trying to use the Features like binding. I want the old picture fading out (Opacity probaply, but is this an option with Binding?) and the new one fading in. Or is it better to handle everything in the code behind, where i get the image from the folder and binding it afterwards.
<UserControl x:Class="Screensaver.ScreensaverControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Screensaver"
mc:Ignorable="d" DataContext="{Binding RelativeSource={RelativeSource Self}}"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Image Visibility="Visible" Source="{Binding DisplayedImagePath}" Name="Bild" Stretch="Uniform" />
</Grid>
</UserControl>
using System;
using System.IO;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
namespace Screensaver
{
/// <summary>
/// Interaction logic for ScreensaverControl.xaml
/// </summary>
public partial class ScreensaverControl : UserControl
{
private Timer _timer;
public ScreensaverControl()
{
InitializeComponent();
this.Loaded += ScreensaverControl_Loaded;
this.Unloaded += ScreensaverControl_Unloaded;
}
private void ScreensaverControl_Loaded(object sender, RoutedEventArgs e)
{
_timer = new Timer(OnTimer, _timer, 10, Timeout.Infinite);
}
private void ScreensaverControl_Unloaded(object sender, RoutedEventArgs e)
{
_timer.Dispose();
_timer = null;
}
private int _index = -1;
private void OnTimer(object state)
{
try
{
var files = Directory.GetFiles(#"C:\Users\mhj\source\repos\Screensaver\Fotos\", "*.png");
if (files.Length > 0)
{
_index++;
if (_index >= files.Length)
_index = 0;
this.Dispatcher.Invoke(() => DisplayedImagePath = files[_index]);
}
}
catch (Exception ex)
{
}
finally
{
if (_timer != null)
_timer.Change(10000, Timeout.Infinite);
}
}
public string DisplayedImagePath
{
get { return (string)GetValue(DisplayedImagePathProperty); }
set { SetValue(DisplayedImagePathProperty, value); }
}
// Using a DependencyProperty as the backing store for DisplayedImagePath. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DisplayedImagePathProperty =
DependencyProperty.Register("DisplayedImagePath", typeof(string), typeof(ScreensaverControl), new PropertyMetadata(null));
}
}
Here is the complete code of a simple screen saver control. It has a dependency property for the image directory. You should add another dependency property for the change interval, and perhaps also a second Image for a blending effect.
XAML:
<UserControl x:Class="ScreenSaverControlTest.ScreenSaverControl" ...>
<Grid>
<Image x:Name="image1"/>
<Image x:Name="image2"/>
</Grid>
</UserControl>
and code behind:
public partial class ScreenSaverControl : UserControl
{
public static readonly DependencyProperty ImageDirectoryProperty =
DependencyProperty.Register(
nameof(ImageDirectory), typeof(string), typeof(ScreenSaverControl),
new PropertyMetadata(ImageDirectoryPropertyChanged));
private readonly DispatcherTimer timer = new DispatcherTimer();
private string[] imagePaths = new string[0];
private int currentImageIndex = -1;
public ScreenSaverControl()
{
InitializeComponent();
timer.Interval = TimeSpan.FromSeconds(10);
timer.Tick += (s, e) => NextImage();
timer.Start();
}
public string ImageDirectory
{
get { return (string)GetValue(ImageDirectoryProperty); }
set { SetValue(ImageDirectoryProperty, value); }
}
private static void ImageDirectoryPropertyChanged(
DependencyObject o, DependencyPropertyChangedEventArgs e)
{
var ssc = (ScreenSaverControl)o;
var directory = (string)e.NewValue;
if (!string.IsNullOrEmpty(directory) && Directory.Exists(directory))
{
ssc.imagePaths = Directory.GetFiles(directory, "*.jpg");
}
else
{
ssc.imagePaths = new string[0];
}
ssc.currentImageIndex = -1;
ssc.NextImage();
}
private void NextImage()
{
if (imagePaths.Length > 0)
{
if (++currentImageIndex >= imagePaths.Length)
{
currentImageIndex = 0;
}
SetImage(new BitmapImage(new Uri(imagePaths[currentImageIndex])));
}
}
private void SetImage(ImageSource imageSource)
{
var fadeOut = new DoubleAnimation(0d, TimeSpan.FromSeconds(1));
var fadeIn = new DoubleAnimation(1d, TimeSpan.FromSeconds(1));
var newImage = image1;
var oldImage = image2;
if (image1.Source != null)
{
newImage = image2;
oldImage = image1;
}
fadeOut.Completed += (s, e) => oldImage.Source = null;
oldImage.BeginAnimation(OpacityProperty, fadeOut);
newImage.BeginAnimation(OpacityProperty, fadeIn);
newImage.Source = imageSource;
}
}
Use it like this:
<local:ScreenSaverControl ImageDirectory="C:\Users\Public\Pictures\Sample Pictures"/>
I'm pretty new to the WPF/Binding world, but now I'm using it for some time with some degrees of success.
Now I'm stuck with a problem very similar to the one described in this question, but regarding a class instead of an IEnumerable. I'm not sure if the behavior is intentional also for a class or if there is a way to solve it.
Let's say I have my simple custom class "Vector3" that contains 3 doubles
Vector3.cs
public class Vector3
{
public double X { get; set; }
public double Y { get; set; }
public double Z { get; set; }
public Vector3(double x, double y, double z)
{
X = x;
Y = y;
Z = z;
}
public Vector3(Vector3 vec)
{
X = vec.X;
Y = vec.Y;
Z = vec.Z;
}
public override bool Equals(object obj)
{
if (!(obj is Vector3))
return false;
Vector3 other = obj as Vector3;
return X == other.X && Y == other.Y && Z == other.Z;
}
public override int GetHashCode()
{
unchecked
{
return (X.GetHashCode() * 42) ^ Y.GetHashCode() + Z.GetHashCode();
}
}
}
And I have a user-control that exposes a DependencyProperty of this type
ucVector3.xaml.cs
public partial class ucVector3 : UserControl
{
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value",
typeof(Vector3), typeof(ucVector3),
new FrameworkPropertyMetadata(null, new PropertyChangedCallback(_OnModelChanged)));
private static void _OnModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Console.WriteLine("Binding worked! I've received a new Vector3 " +
"with value X = " + (e.NewValue as Vector3).X);
}
public Vector3 Value
{
get
{
return (Vector3)GetValue(ValueProperty);
}
set
{
SetValue(ValueProperty, value);
}
}
...
Then I try to use this user-control binding the Value property like in this example:
MainWindow.xaml
<Window x:Class="StackOverflow.Example.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:StackOverflow.Example"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid Background="Azure">
<Grid.RowDefinitions>
<RowDefinition Height="5*"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Button Content="Update" Margin="5" Grid.Row="1" Grid.ColumnSpan="2" Click="Button_Click" />
<local:ucVector3 Value="{Binding SameReference}" Margin="5" />
<local:ucVector3 Value="{Binding NewReference}" Margin="5" Grid.Column="1" />
</Grid>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window, INotifyPropertyChanged
{
#region INotifyPropertyChanged
SynchronizationContext uiContext = SynchronizationContext.Current;
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
if (uiContext != SynchronizationContext.Current)
{
uiContext.Send(_ =>
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}, null);
}
else
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
private Vector3 newValue = new Vector3(0, 0, 0);
public Vector3 SameReference { get; set; }
public Vector3 NewReference { get; set; }
public MainWindow()
{
this.DataContext = this;
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
newValue.X = newValue.X + 2;
newValue.Y = newValue.Y + 3;
newValue.Z = newValue.Z + 4;
SameReference = newValue;
NewReference = new Vector3(newValue);
OnPropertyChanged("SameReference"); //successful notify, ignored values
OnPropertyChanged("NewReference"); //successful notify, processed values
}
}
Pressing the button the first time both user-controls will be updated with (3,4,5), but from the second time, only the "NewReference" property will be updated in the right user-control.
I get that for IEnumerables WPF will propagate the OnPropertyChanged event only if the reference is different, and changes in the IEnumerable need to call the INotifyCollectionChanged event instead.
Why my "OnPropertyChanged("SameReference")" is not propagated? I changed the values, I want the event to propagate to update the interface, or I wouldn't call the event...
Is this behavior intentional? Why it checks the object reference and not maybe if it is Equals? Is there a way to "force" the event to go through? Or how should I organize my classes in this case?
Here you can download the example solution described in this question, created in VisualStudio2015.
Thanks to all for your time.
from i have seen in your project, you have to comment these lines in your ucVector3.xaml.cs, these lines avoid to update your usercontrol:
if (value.X == lastNudValue.X && value.Y == lastNudValue.Y && value.Z == lastNudValue.Z)
return;
I have collection of images in listBox
<ListBox Name="lb" HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Row="0" ScrollViewer.HorizontalScrollBarVisibility="Auto"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding}" >
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</Trigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Image Source="{Binding}" Width="150" Height="100"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Visible item is only selected one.
How can I slide or scroll images? Or how manage what item should be selected if I drag cursor on visible image to the left or to the right?
I implemented something similar in past project
Here are some bits of the code. More or less it will work as expected, but you may always adjust it as per your needs
This implements behavior of bringing selected item to view by scrolling to desired position and also implement sliding of item/image to left or right.
Listbox/ItemsControl (Binding is not important but useful for dynamic items)
<ItemsControl ItemsSource="{Binding}">
<i:Interaction.Behaviors>
<behavior:BringSelectionToViewBehavior SelectedItem="{Binding CurrentPage,Mode=TwoWay}" />
</i:Interaction.Behaviors>
<ItemsControl.Template>
<ControlTemplate TargetType="{x:Type ItemsControl}">
<ScrollViewer HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Hidden">
<ItemsPresenter />
</ScrollViewer>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"
IsItemsHost="True" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
BringSelectionToViewBehavior.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Animation;
using System.Windows.Media;
using System.Windows.Threading;
using System.Windows.Input;
using Microsoft.Expression.Interactivity;
namespace MyProject.Behaviors
{
public class BringSelectionToViewBehavior : Behavior<ItemsControl>
{
Queue<Point> velocityPoints = new Queue<Point>(4);
Point PreviousPoint { get; set; }
DispatcherTimer valocityTimer = new DispatcherTimer();
public object SelectedItem
{
get { return GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
// Using a DependencyProperty as the backing store for SelectedItem. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register("SelectedItem", typeof(object), typeof(BringSelectionToViewBehavior), new UIPropertyMetadata(null, OnSelectedItemChanged));
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.AddHandler(ItemsControl.SizeChangedEvent, (SizeChangedEventHandler)OnWidthChanged);
AssociatedObject.LayoutUpdated += new EventHandler(AssociatedObject_LayoutUpdated);
AssociatedObject.ReleaseMouseCapture();
valocityTimer.Interval = TimeSpan.FromMilliseconds(20);
valocityTimer.Tick += valocityTimer_Tick;
}
ItemsPresenter itemsPresenter;
void AssociatedObject_LayoutUpdated(object sender, EventArgs e)
{
if (itemsPresenter == null)
{
itemsPresenter = AssociatedObject.FindChildrenByType<ItemsPresenter>().ToList().First();
AssociatedObject.MouseMove += AssociatedObject_MouseMove;
itemsPresenter.AddHandler(UIElement.MouseLeftButtonDownEvent, new MouseButtonEventHandler(ItemPresenter_MouseLeftButtonDown), false);
itemsPresenter.AddHandler(UIElement.MouseLeftButtonUpEvent, new MouseButtonEventHandler(ItemPresenter_MouseLeftButtonUp), true);
}
}
public void OnWidthChanged(object s, SizeChangedEventArgs e)
{
Dispatcher.BeginInvoke((Action<ItemsControl, object>)QueueAnimation, DispatcherPriority.Render, new[] { AssociatedObject, SelectedItem });
}
private void QueueAnimation(ItemsControl control, object item)
{
BringItemToView(item);
}
public static void OnSelectedItemChanged(DependencyObject s, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue == null)
return;
BringSelectionToViewBehavior behavior = s as BringSelectionToViewBehavior;
if (!behavior.valocityOverride.HasValue)
behavior.BringItemToView(e.NewValue);
}
private void BringItemToView(object item, EasingMode easeMode = EasingMode.EaseInOut)
{
if (item == null || AssociatedObject == null)
return;
FrameworkElement element = AssociatedObject.ItemContainerGenerator.ContainerFromItem(item) as FrameworkElement;
BringElementToView(element, easeMode);
}
private void BringElementToView(FrameworkElement element, EasingMode easeMode)
{
if (element == null)
return;
ScrollViewer scrollViewer = element.FindAncestorByType<ScrollViewer>();
if (scrollViewer != null)
{
Point relativePoint = element.TransformToAncestor(scrollViewer).Transform(new Point(0, 0));
ScrollToPosition(scrollViewer, relativePoint.X, relativePoint.Y, easeMode);
}
else
{
element.BringIntoView();
}
}
private void ScrollToPosition(ScrollViewer viewer, double x, double y, EasingMode easeMode)
{
Storyboard sb = new Storyboard();
if (y != 0)
{
DoubleAnimation vertAnim = new DoubleAnimation();
//if (applyEase)
vertAnim.EasingFunction = new CubicEase() { EasingMode = easeMode };
vertAnim.From = viewer.VerticalOffset;
vertAnim.By = y;
vertAnim.Duration = GetAnimationDuration(y);
sb.Children.Add(vertAnim);
Storyboard.SetTarget(vertAnim, viewer);
Storyboard.SetTargetProperty(vertAnim, new PropertyPath(BringSelectionToViewBehavior.VerticalOffsetProperty));
}
if (x != 0)
{
DoubleAnimation horzAnim = new DoubleAnimation();
horzAnim.From = viewer.HorizontalOffset;
horzAnim.By = x;
horzAnim.Duration = GetAnimationDuration(x);
horzAnim.EasingFunction = new CubicEase() { EasingMode = easeMode };
sb.Children.Add(horzAnim);
Storyboard.SetTarget(horzAnim, viewer);
Storyboard.SetTargetProperty(horzAnim, new PropertyPath(BringSelectionToViewBehavior.HorizontalOffsetProperty));
}
//overrideDuretion = false;
sb.Completed += new EventHandler(sb_Completed);
sb.Begin();
}
void sb_Completed(object sender, EventArgs e)
{
((sender as ClockGroup).Timeline as Storyboard).Remove();
valocityOverride = null;
}
double? valocityOverride;
private Duration GetAnimationDuration(double distanceToTravel)
{
double animTime = Math.Abs(distanceToTravel) * .7;
if (valocityOverride.HasValue && valocityOverride > 50)
{
animTime = Math.Abs(distanceToTravel) * (valocityOverride.Value / 1000);
}
if (animTime > 1500)
animTime = 1500;
if (animTime < 250)
animTime = 250;
return new Duration(TimeSpan.FromMilliseconds(animTime));
}
public static double GetVerticalOffset(DependencyObject obj)
{
return (double)obj.GetValue(VerticalOffsetProperty);
}
public static void SetVerticalOffset(DependencyObject obj, double value)
{
obj.SetValue(VerticalOffsetProperty, value);
}
// Using a DependencyProperty as the backing store for VerticalOffset. This enables animation, styling, binding, etc...
public static readonly DependencyProperty VerticalOffsetProperty =
DependencyProperty.RegisterAttached("VerticalOffset", typeof(double), typeof(BringSelectionToViewBehavior), new PropertyMetadata(new PropertyChangedCallback(OnVerticalChanged)));
public static double GetHorizontalOffset(DependencyObject obj)
{
return (double)obj.GetValue(HorizontalOffsetProperty);
}
public static void SetHorizontalOffset(DependencyObject obj, double value)
{
obj.SetValue(HorizontalOffsetProperty, value);
}
// Using a DependencyProperty as the backing store for HorizontalOffset. This enables animation, styling, binding, etc...
public static readonly DependencyProperty HorizontalOffsetProperty =
DependencyProperty.RegisterAttached("HorizontalOffset", typeof(double), typeof(BringSelectionToViewBehavior), new PropertyMetadata(new PropertyChangedCallback(OnHorizontalChanged)));
private static void OnVerticalChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ScrollViewer viewer = d as ScrollViewer;
viewer.ScrollToVerticalOffset((double)e.NewValue);
}
private static void OnHorizontalChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ScrollViewer viewer = d as ScrollViewer;
viewer.ScrollToHorizontalOffset((double)e.NewValue);
}
void valocityTimer_Tick(object sender, EventArgs e)
{
Point currentPoint = Mouse.GetPosition(AssociatedObject);
velocityPoints.Enqueue(currentPoint);
if (velocityPoints.Count > 4)
velocityPoints.Dequeue();
}
private void AssociatedObject_MouseMove(object sender, MouseEventArgs e)
{
if (itemsPresenter.IsMouseCaptured)
{
Point CurrentPoint = e.GetPosition(AssociatedObject);
Vector offset = PreviousPoint - CurrentPoint;
PreviousPoint = CurrentPoint;
if (offset.X != 0)
{
Viewer.ScrollToHorizontalOffset(Viewer.HorizontalOffset + offset.X);
}
}
}
public ScrollViewer Viewer { get; set; }
/// <summary>
/// Handles the MouseLeftButtonDown event of the ItemPresenter control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.Windows.Input.MouseButtonEventArgs"/> instance containing the event data.</param>
private void ItemPresenter_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
//Viewer.ApplyAnimationClock(BringSelectionToViewBehavior.HorizontalOffsetProperty, null);
Border bd = (e.OriginalSource as FrameworkElement).FindAncestorByType<Border>();
if (bd == null || (bd != null && bd.Name != "GripBarElement"))
{
Viewer = AssociatedObject.FindChildrenByType<ScrollViewer>().ToList().First();
PreviousPoint = e.GetPosition(AssociatedObject);
itemsPresenter.CaptureMouse();
velocityPoints.Clear();
valocityTimer.Start();
}
}
private double CalculateMouseSpeed()
{
if (velocityPoints.Count > 1)
{
Point first = velocityPoints.Dequeue();
return velocityPoints.Aggregate<Point, Point, double>(first, (s, pt) => new Point(s.X, pt.X), pt => pt.X - pt.Y);
}
return 0;
}
/// <summary>
/// Handles the MouseLeftButtonUp event of the ItemPresenter control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.Windows.Input.MouseButtonEventArgs"/> instance containing the event data.</param>
private void ItemPresenter_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (itemsPresenter.IsMouseCaptured)
{
if (Viewer == null)
return;
valocityTimer.Stop();
itemsPresenter.ReleaseMouseCapture();
double MouseSpeed = CalculateMouseSpeed() / 3;
double newtotal = 5 * MouseSpeed;
double newOffset = Viewer.HorizontalOffset + newtotal;
double maxOffset = itemsPresenter.ActualWidth - (itemsPresenter.ActualWidth / AssociatedObject.Items.Count);
if (newOffset < 0)
{
newtotal += 0 - newOffset;
}
else if (newOffset > maxOffset)
{
newtotal -= newOffset - maxOffset;
}
newOffset = Viewer.HorizontalOffset + newtotal;
int newIndex = (int)System.Math.Round(newOffset / (itemsPresenter.ActualWidth / AssociatedObject.Items.Count));
object objToSet = AssociatedObject.ItemsSource.OfType<object>().ElementAt(newIndex);
if (objToSet != null)
{
valocityOverride = MouseSpeed;
if (!object.Equals(SelectedItem, objToSet))
SelectedItem = objToSet;
BringItemToView(objToSet, EasingMode.EaseOut);
}
}
}
}
}
Give it a try and see if this can solve your issue.
I'm trying to build Inventory like interface for project that I'm creating.
Idea is to have list of images that can be dragged to players as shown below:
Images are loaded from directory and displayed inside ListView, list of players is displayed in ListBox.
My XAML looks like this:
<Window x:Class="DynamicImagesDrag.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:dynamicImagesDrag="clr-namespace:DynamicImagesDrag"
Title="MainWindow"
Height="405"
Width="719.162"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Window.Resources>
<dynamicImagesDrag:StringToImageConverter x:Key="StringToImageConverter" />
</Window.Resources>
<Grid>
<ListView Name="MyList"
ItemsSource="{Binding Images}"
PreviewMouseLeftButtonDown="UIElement_OnPreviewMouseLeftButtonDown"
PreviewMouseMove="UIElement_OnPreviewMouseMove"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
VerticalAlignment="Stretch"
HorizontalAlignment="Left"
Margin="10" Width="250">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListViewItem">
<ContentPresenter/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<DockPanel Width="50" Height="50">
<DockPanel.Background>
<ImageBrush ImageSource="BG1.png"/>
</DockPanel.Background>
<Image Source="{Binding Path, Converter={StaticResource StringToImageConverter} }"
Height="32"
Width="32" />
</DockPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ListBox ItemsSource="{Binding People}" HorizontalAlignment="Right" HorizontalContentAlignment="Stretch" Margin="10" VerticalAlignment="Stretch"
Width="200">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical" AllowDrop="True" PreviewDrop="UIElement_OnPreviewDrop">
<TextBlock Text="{Binding Name}" FontWeight="Bold" />
<ProgressBar Height="20" Value="{Binding Points}" Margin="0" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
Code behind:
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace DynamicImagesDrag
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow
{
private readonly ObservableCollection<MyImage> _images = new ObservableCollection<MyImage>();
public ObservableCollection<MyImage> Images
{
get { return _images; }
}
private readonly ObservableCollection<Person> _people = new ObservableCollection<Person>();
public ObservableCollection<Person> People { get { return _people; } }
public MainWindow()
{
InitializeComponent();
_people.Add(new Person() { Name = "Person1", Points = 10 });
_people.Add(new Person() { Name = "Person2", Points = 0 });
_people.Add(new Person() { Name = "Person3", Points = 40 });
string appPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
if (appPath != null)
{
string imagePath = Path.Combine(appPath, "Images");
if (Directory.Exists(imagePath))
{
var images = Directory
.EnumerateFiles(imagePath)
.Where(file => file.ToLower().EndsWith("jpg") || file.ToLower().EndsWith("png"))
.ToList();
foreach (string image in images)
{
_images.Add(new MyImage
{
Name = Path.GetFileName(image),
Path = image,
Points = Convert.ToInt32(Path.GetFileNameWithoutExtension(image))
});
}
}
}
}
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool GetCursorPos(ref Win32Point pt);
[StructLayout(LayoutKind.Sequential)]
internal struct Win32Point
{
public Int32 X;
public Int32 Y;
};
public static Point GetMousePosition()
{
Win32Point w32Mouse = new Win32Point();
GetCursorPos(ref w32Mouse);
return new Point(w32Mouse.X, w32Mouse.Y);
}
private void UIElement_OnPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
_startPoint = e.GetPosition(null);
}
#region Field and Properties
private bool _dragHasLeftScope;
private Point _startPoint;
public bool IsDragging { get; set; }
DragAdorner _adorner;
AdornerLayer _layer;
public FrameworkElement DragScope { get; set; }
#endregion // Field and Properties
private void UIElement_OnPreviewMouseMove(object sender, MouseEventArgs e)
{
// Ensure that the user does not drag by accident
if (e.LeftButton == MouseButtonState.Pressed && !IsDragging)
{
Point position = e.GetPosition(null);
if (Math.Abs(position.X - _startPoint.X) > SystemParameters.MinimumHorizontalDragDistance ||
Math.Abs(position.Y - _startPoint.Y) > SystemParameters.MinimumVerticalDragDistance)
{
StartDragInProcAdorner(e);
}
}
}
void DragScope_DragLeave(object sender, DragEventArgs e)
{
if (e.OriginalSource == DragScope)
{
Point p = e.GetPosition(DragScope);
Rect r = VisualTreeHelper.GetContentBounds(DragScope);
if (!r.Contains(p))
{
_dragHasLeftScope = true;
e.Handled = true;
}
}
}
void Window1_DragOver(object sender, DragEventArgs args)
{
if (_adorner == null) return;
_adorner.LeftOffset = args.GetPosition(DragScope).X /* - _startPoint.X */ ;
_adorner.TopOffset = args.GetPosition(DragScope).Y /* - _startPoint.Y */ ;
}
void DragScope_QueryContinueDrag(object sender, QueryContinueDragEventArgs e)
{
if (_dragHasLeftScope)
{
e.Action = DragAction.Cancel;
e.Handled = true;
}
}
private void StartDragInProcAdorner(MouseEventArgs e)
{
DragScope = Application.Current.MainWindow.Content as FrameworkElement;
bool previousDrop = DragScope.AllowDrop;
DragScope.AllowDrop = true;
try
{
DragEventHandler draghandler = Window1_DragOver;
DragScope.PreviewDragOver += draghandler;
DragEventHandler dragleavehandler = DragScope_DragLeave;
DragScope.DragLeave += dragleavehandler;
QueryContinueDragEventHandler queryhandler = DragScope_QueryContinueDrag;
DragScope.QueryContinueDrag += queryhandler;
DragScope.GiveFeedback+=DragScope_GiveFeedback;
FrameworkElement dr = MyList.ItemContainerGenerator.ContainerFromItem(MyList.SelectedItem) as FrameworkElement;
if (dr == null)
return;
_adorner = new DragAdorner(DragScope, dr, true, 0.5);
_layer = AdornerLayer.GetAdornerLayer(DragScope);
_layer.Add(_adorner);
IsDragging = true;
_dragHasLeftScope = false;
DataObject data = new DataObject(MyList.SelectedItem as MyImage);
DragDropEffects de = DragDrop.DoDragDrop(MyList, data, DragDropEffects.Move);
DragScope.AllowDrop = previousDrop;
AdornerLayer.GetAdornerLayer(DragScope).Remove(_adorner);
_adorner = null;
DragScope.DragLeave -= dragleavehandler;
DragScope.QueryContinueDrag -= queryhandler;
DragScope.PreviewDragOver -= draghandler;
IsDragging = false;
}
catch
{
DragScope.AllowDrop = previousDrop;
AdornerLayer.GetAdornerLayer(DragScope).Remove(_adorner);
_adorner = null;
IsDragging = false;
}
}
private void DragScope_GiveFeedback(object sender, GiveFeedbackEventArgs e)
{
}
private void UIElement_OnPreviewDrop(object sender, DragEventArgs e)
{
var stackPanel = sender as StackPanel;
if (stackPanel == null) return;
var student = stackPanel.DataContext as Person;
MyImage myImage = e.Data.GetData(typeof(MyImage)) as MyImage;
if (student != null) student.Points += myImage.Points;
}
}
public class StringToImageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
try
{
return new BitmapImage(new Uri((string)value));
}
catch
{
return new BitmapImage();
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class Person : INotifyPropertyChanged
{
private string _name;
private int _points;
public string Name
{
get { return _name; }
set
{
if (value == _name) return;
_name = value;
OnPropertyChanged();
}
}
public int Points
{
get { return _points; }
set
{
if (value == _points) return;
_points = value;
if (_points >= 100)
{
_points -= 100;
Debug.WriteLine("100!");
}
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public class MyImage
{
public string Path { get; set; }
public string Name { get; set; }
public int Points { get; set; }
}
}
and DragAdorner (taken from http://www.infragistics.com/community/blogs/alex_fidanov/archive/2009/07/28/drag-amp-drop-with-datapresenter-family-controls.aspx)
class DragAdorner : Adorner
{
public DragAdorner(UIElement owner) : base(owner) { }
public DragAdorner(UIElement owner, UIElement adornElement, bool useVisualBrush, double opacity)
: base(owner)
{
_owner = owner;
VisualBrush _brush = new VisualBrush
{
Opacity = opacity,
Visual = adornElement
};
DropShadowEffect dropShadowEffect = new DropShadowEffect
{
Color = Colors.Black,
BlurRadius = 15,
Opacity = opacity
};
Rectangle r = new Rectangle
{
RadiusX = 3,
RadiusY = 3,
Fill = _brush,
Effect = dropShadowEffect,
Width = adornElement.DesiredSize.Width,
Height = adornElement.DesiredSize.Height
};
XCenter = adornElement.DesiredSize.Width / 2;
YCenter = adornElement.DesiredSize.Height / 2;
_child = r;
}
private void UpdatePosition()
{
AdornerLayer adorner = (AdornerLayer)Parent;
if (adorner != null)
{
adorner.Update(AdornedElement);
}
}
#region Overrides
protected override Visual GetVisualChild(int index)
{
return _child;
}
protected override int VisualChildrenCount
{
get
{
return 1;
}
}
protected override Size MeasureOverride(Size finalSize)
{
_child.Measure(finalSize);
return _child.DesiredSize;
}
protected override Size ArrangeOverride(Size finalSize)
{
_child.Arrange(new Rect(_child.DesiredSize));
return finalSize;
}
public override GeneralTransform GetDesiredTransform(GeneralTransform transform)
{
GeneralTransformGroup result = new GeneralTransformGroup();
result.Children.Add(base.GetDesiredTransform(transform));
result.Children.Add(new TranslateTransform(_leftOffset, _topOffset));
return result;
}
#endregion
#region Field & Properties
public double scale = 1.0;
protected UIElement _child;
protected VisualBrush _brush;
protected UIElement _owner;
protected double XCenter;
protected double YCenter;
private double _leftOffset;
public double LeftOffset
{
get { return _leftOffset; }
set
{
_leftOffset = value - XCenter;
UpdatePosition();
}
}
private double _topOffset;
public double TopOffset
{
get { return _topOffset; }
set
{
_topOffset = value - YCenter;
UpdatePosition();
}
}
#endregion
}
Drag works almost fine:
except adorner is visible only in source list and target list, it isn't displayed during whole drag.
My questions are:
How can I fix drag and drop to see adorner whole time?
How can I display image instead selectedItem inside adorner? Right now inside adorner is that brown background, I'd like to get only transparent image.
How can I show if dragtarget is correct inside adorner instead of changing cursor? I'd like to change opacity of adorner if target is correct.
I'd like to get drag and drop working with touch events, #KOTIX suggested using Gong WPF dragdrop, will it work fine on touch enabled screens?
Currently I'm setting AllowDrop on StackPanel inside ListBox ItemTemplate, should it stay there or maybe I should set in on ListBox?
I've searched over internet (including SO) for solution, but I couldn't find anything that fits my needs.
I found some great articles:
http://www.codeproject.com/Articles/37161/WPF-Drag-and-Drop-Smorgasbord
http://www.zagstudio.com/blog/488#.VgHPyxHtmkp
http://nonocast.cn/adorner-in-wpf-part-5-drag-and-drop/
https://blogs.claritycon.com/blog/2009/03/generic-wpf-drag-and-drop-adorner/
Last one was very interesting, but I wasn't able to modify it in a way to add points to players instead of moving items. In my case I want items on left to stay, I just want to update list on right based on dragged item.
I don't have all the answers yet but it may lead you to your goals.
How can I fix drag and drop to see adorner whole time?
Your DragLayer is Transparent, just set a value for the Background property of your Grid
How can I display image instead selectedItem inside adorner? Right now
inside adorner is that brown background, I'd like to get only
transparent image.
The best I've come to is to explore the VisualTree to get the Image inside the template :
//Get the `ListViewItem`
FrameworkElement dr = MyList.ItemContainerGenerator.ContainerFromItem(MyList.SelectedItem) as FrameworkElement;
//Explore the VisualTree to get the grand-child
//This should be refactored to a Func<UIElement,UIElement> to accord to templates changes
UIElement el = VisualTreeHelper.GetChild(VisualTreeHelper.GetChild(dr, 0), 0) as UIElement;
//Create the DragAdorner using the found UIElement
_adorner = new DragAdorner(DragScope, el, true, 1d);
How can I show if dragtarget is correct inside adorner instead of
changing cursor? I'd like to change opacity of adorner if target is
correct.
To show some feedback, you need to... GiveFeedBack
Here is your handler :
private void DragScope_GiveFeedback(object sender, GiveFeedbackEventArgs e)
{
if (_adorner == null) return;
if (e.Effects == DragDropEffects.Copy)
{
_adorner.Opacity = 1d;
e.Handled = true;
}
else
{
_adorner.Opacity = 0.5d;
e.Handled = true;
}
}
Now, you have to set the desired effect in 2 places : the Person template and the DragLayer :
private void StackPanel_DragOver(object sender, DragEventArgs e)
{
e.Effects = DragDropEffects.Copy;
e.Handled = true;
}
void Window1_DragOver(object sender, DragEventArgs args)
{
if (_adorner == null) return;
_adorner.LeftOffset = args.GetPosition(DragScope).X;
_adorner.TopOffset = args.GetPosition(DragScope).Y;
if (!args.Handled)
args.Effects = DragDropEffects.Move;
}
In order for these to work, you have to allow those 2 effects when initiating the DragDrop :
DragDropEffects de = DragDrop.DoDragDrop(MyList, data, DragDropEffects.Move | DragDropEffects.Copy);
Also, to avoid cumulative opacity reduction, use 1 for the DragAdorner opacity in constructor.
I'd like to get drag and drop working with touch events, #KOTIX
suggested using Gong WPF dragdrop, will it work fine on touch enabled
screens?
This solution is fully native, you should get it to work with touch devices.
Currently I'm setting AllowDrop on StackPanel inside ListBox
ItemTemplate, should it stay there or maybe I should set in on
ListBox?
If your goal is to add MyImage.Points to the drop target, the AllowDrop must be set on the StackPanel.
I would suggest using Gong WPF dragdrop
This library always worked great for me and way better than built-in support. It also adds great support for MVVM design.
Good luck!
I'm trying to make a collection of tabs move to where ever I click on a canvas. I managed to get it working with fixed values, but cannot get it to work with a left mouse button click.
Here's my code:
public partial class MainWindow : Window
{
User Player = new User();
ThicknessConverter perimeter = new ThicknessConverter();
Inventory Inventory = new Inventory();
BreadCrumb Crumb = new BreadCrumb();
Locations Locations = new Locations();
PointAnimation myPointAnimation = new PointAnimation();
ThicknessAnimation myThicknessAnimation = new ThicknessAnimation();
DoubleAnimation da = new DoubleAnimation();
Point p = new Point();
private void Hansel_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
da.From = Canvas.GetLeft(Hansel);
da.From = Canvas.GetTop(Hansel);
p.X = Mouse.GetPosition(PlayArea);
da.To = p.X; //Convert.ToDouble(PointToScreen(Mouse.GetPosition(this.)));
da.Duration = new Duration(TimeSpan.FromSeconds(2));
Hansel.BeginAnimation(Canvas.LeftProperty, da);
}
}
So, how would I go about getting the mouse position and converting those points to double coordinates?
<Window x:Class="MiscSamples.ClickToMove"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ClickToMove" Height="300" Width="300">
<ItemsControl ItemsSource="{Binding}" PreviewMouseDown="ItemsControl_PreviewMouseDown"
Background="#05FFFFFF">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding X}"/>
<Setter Property="Canvas.Top" Value="{Binding Y}"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Rectangle Stroke="Black" StrokeThickness="2" Fill="Blue"
Height="20" Width="20"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Window>
Code Behind:
public partial class ClickToMove : Window
{
public List<MovableObject> Objects { get; set; }
public ClickToMove()
{
InitializeComponent();
Objects = new List<MovableObject>
{
new MovableObject() {X = 100, Y = 100}
};
DataContext = Objects;
}
private void ItemsControl_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
var position = e.GetPosition(this);
Objects.First().MoveToPosition(position.X, position.Y);
}
}
ViewModel:
public class MovableObject: INotifyPropertyChanged
{
private double _x;
public double X
{
get { return _x; }
set
{
_x = value;
OnPropertyChanged("X");
}
}
private double _y;
public double Y
{
get { return _y; }
set
{
_y = value;
OnPropertyChanged("Y");
}
}
private System.Threading.Timer MoveTimer;
private double DestinationX;
private double DestinationY;
public void MoveToPosition(double x, double y)
{
DestinationX = x;
DestinationY = y;
if (MoveTimer != null)
MoveTimer.Dispose();
MoveTimer = new Timer(o => MoveStep(), null, 0, 10);
}
private void MoveStep()
{
if (Math.Abs(X - DestinationX) > 5)
{
if (X < DestinationX)
X = X+5;
else if (X > DestinationX)
X = X-5;
}
if (Math.Abs(Y - DestinationY) > 5)
{
if (Y < DestinationY)
Y = Y + 5;
else if (Y > DestinationY)
Y = Y - 5;
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
Application.Current.Dispatcher.BeginInvoke((Action)(() =>
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}));
}
}