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.