listbox dragdrop in wpf - wpf

I am currently porting a windows forms application to wpf. There is a listbox with filenames in it. It should be possible to drag (multiple) items to the windows explorer.
This was easy in with the old windows form, but I can't find a way how this can be done in wpf.
This was the code I used with windows forms:
void listView1_ItemDrag(object sender, ItemDragEventArgs e)
{
string[] files = GetSelection();
if (files != null)
{
DoDragDrop(new DataObject(DataFormats.FileDrop, files), DragDropEffects.Copy );
}
}

Ok... I found a solution for my Problem, based on this tutorial:
private void List_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
// Store the mouse position
startPoint = e.GetPosition(null);
}
private void List_MouseMove(object sender, MouseEventArgs e)
{
// Get the current mouse position
Point mousePos = e.GetPosition(null);
Vector diff = startPoint - mousePos;
if (e.LeftButton == MouseButtonState.Pressed &&
Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance &&
Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance)
{
if (listView1.SelectedItems.Count == 0)
{
return;
}
string[] files = GetSelection();
string dataFormat = DataFormats.FileDrop;
DataObject dataObject = new DataObject(dataFormat, files);
DragDrop.DoDragDrop(listView1, dataObject, DragDropEffects.Copy);
}
}

Try checking out Bea Stollnitz' blog for a thorough implementation of drag and drop in WPF.
Bea Stollnitz - How can I drag and drop items between data bound ItemsControls?

try this code that help u to solve your problem
public partial class MainWindow : Window
{
List<string> file = new List<string>();
public MainWindow()
{
InitializeComponent();
file.Add(#"D:\file1.txt");
file.Add(#"D:\folder1");
file.Add(#"D:\folder2");
file.Add(#"D:\folder3");
lstTest.DataContext = file;
}
private Point start;
private void lstTest_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
this.start = e.GetPosition(null);
}
private void lstTest_MouseMove(object sender, MouseEventArgs e)
{
Point mpos = e.GetPosition(null);
Vector diff = this.start - mpos;
if (e.LeftButton == MouseButtonState.Pressed && Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance && Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance)
{
if (this.lstTest.SelectedItems.Count == 0)
{
return;
}
string[] Files = new string[file.Count] ;
for (int i = 0; i < file.Count; i++)
{
Files[i] = file[i];
}
string dataFormat = DataFormats.FileDrop;
DataObject dataObject = new DataObject(dataFormat, (lstTest.SelectedItems.Cast<string>()).ToArray<string>());
DragDrop.DoDragDrop(this, dataObject, DragDropEffects.Copy);
}
}
}

make sure you checked ( checkbox ) the multiple selection option

Related

ListView Scrolling when Item Added to Collection on one machine but not another

I have an WPF app using MVVM that shows a log in a ListView control. I have it bound to an ObservableCollection and the control updates when items are added.
I have it coded so that when it starts, it automatically scrolls to the top when an item is inserted into the collection at position 0 so it always shows the latest log message. This works on all machines I have tested.
When a user does something on the ListView (clicks or scrolls), the automatic scrolling is turned off so the user can look at any part of the log they want. When they are finished looking at the log, they can click a button to turn the automatic scrolling back on. Everything works except on one of my test machines, the view changes as items are added. On my dev machine and another test machine, the ListView window does not change when things are added to the collection. Same code/config files are used for all systems.
For example:
The user scrolls to show "My Item" at the top of the ListView.
Another log message is added.
I want "My Item" to still show at the top of the ListView.
Dev machine and one Test machine: "My Item" shows at the top of the ListView.
Another Test Machine: "My Item" is now in the second row of the ListView.
The original change request was because this scrolling was not working on some machines but not others. After much time, there seems to be a difference in the machines themselves that is effecting this.
Is there some system setting that would control this?
XAML:
<ListView Grid.Row="1" Grid.Column="9" Name="messagesListView"
Grid.ColumnSpan="3" Margin="8,0,40,0"
ItemsSource="{Binding StatusMessagesList}"
SelectionChanged="messagesListView_SelectionChanged"
PreviewMouseWheel="messagesListView_PreviewMouseWheel"
MouseDoubleClick="batchesListView_MouseDoubleClick"
PreviewMouseDown="messagesListView_MouseDown"
HorizontalAlignment="Stretch">
</ListView>
Code Behind:
private void ScrollToTop()
{
scrollPos = 0;
ScrollSpot.Text = scrollPos.ToString();
ScrollToPos();
}
private void ScrollToPos()
{
ScrollViewer scrollViewer = GetScrollViewer(messagesListView) as ScrollViewer;
if (scrollViewer != null && _viewState)
scrollViewer.ScrollToVerticalOffset(scrollPos);
}
private void autoScrollButton_Click(object sender, RoutedEventArgs e)
{
messagesListView.SelectedItem = null;
autoScrollButton.Visibility = Visibility.Hidden;
_viewState = true;
ScrollToTop();
}
private void ListBox_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null && e.NewItems.Count > 0)
{
ScrollViewer scrollViewer = GetScrollViewer(messagesListView) as ScrollViewer;
if (scrollViewer != null && _viewState)
{
ScrollToTop();
}
if(scrollViewer != null && !_viewState)
{
scrollPos += e.NewItems.Count;
ScrollSpot.Text = scrollPos.ToString();
ScrollToPos();
}
}
}
private void messagesListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
_viewState = false;
autoScrollButton.Visibility = Visibility.Visible;
}
private void messagesListView_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
_viewState = false;
autoScrollButton.Visibility = Visibility.Visible;
}
private void messagesListView_Scroll(object sender, System.Windows.Controls.Primitives.ScrollEventArgs e)
{
_viewState = false;
autoScrollButton.Visibility = Visibility.Visible;
}
private void messagesListView_MouseDown(object sender, MouseButtonEventArgs e)
{
if (e.OriginalSource.GetType().ToString().IndexOf("Rectangle") >= 0)
{
_viewState = false;
autoScrollButton.Visibility = Visibility.Visible;
}
}
Code to Add to Collection:
{
if (_statusMessagesList == null)
StatusMessagesList = new ObservableCollection<string>();
string stMsg = string.Format("{0} {1} {2}", DateTime.Now.ToShortDateString(), DateTime.Now.ToShortTimeString(), message);
StatusMessagesList.Insert(0, stMsg);
Thanks,
Brad P.
Update: I never found out why one system worked and the other did not. However, the solution I found that worked on both was to set messagesListView.CanContentScroll = False
and then manually keep track of the Offset Position on the ScrollViewer:
{
SetUpManualScroll(0);
}
private void messagesListView_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
double scrollAmt = (e.Delta / 120) * -48;
SetUpManualScroll(scrollAmt);
}
private void messagesListView_Scroll(object sender, System.Windows.Controls.Primitives.ScrollEventArgs e)
{
SetUpManualScroll(e.NewValue);
}
private void messagesListView_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
SetUpManualScroll(0);
}
private void messagesListView_PreviewMouseUp(object sender, MouseButtonEventArgs e)
{
if (e.OriginalSource.GetType().ToString().IndexOf("TextBlock") < 0 && e.OriginalSource.GetType().ToString().IndexOf("Border") < 0)
{
if (_scrollviewer.VerticalOffset != 0)
SetUpManualScroll(0);
else
{
messagesListView.SelectedItem = null;
autoScrollButton.Visibility = Visibility.Hidden;
_viewState = true;
ScrollToTop();
}
}
}
private void SetUpManualScroll(double d)
{
_viewState = false;
autoScrollButton.Visibility = Visibility.Visible;
_scrollviewer.UpdateLayout();
double newPos = _scrollviewer.VerticalOffset + d;
if (newPos < 0)
newPos = 0;
ScrollPos = newPos;
}
private void ScrollToTop()
{
ScrollPos = 0;
ScrollToPos();
}
private void ScrollToPos()
{
if (_scrollviewer == null)
{
_scrollviewer = GetScrollViewer(messagesListView);
}
_scrollviewer.UpdateLayout();
_scrollviewer.ScrollToVerticalOffset(ScrollPos);
}

ScrollViewer inside ScrollViewer WPF

In my WPF page I need to scroll vertically and inside (this ScrollViewer) I need several horizontal scrolls. At the beginning i had the problem was that when I pointed with my mouse on the inside ScrollViewer area I cant scroll the page (vertically).I found a post:
https://serialseb.com/blog/2007/09/03/wpf-tips-6-preventing-scrollviewer-from/
it resolved my mouse problem, but now I have the same problem on Touch display. I can't use my finger to vertically scroll on the inside scrollviewer. Anyone has any idea to change the below code to let it work for touch display? Thanks
public class ScrollViewerCorrector
{
public static bool GetFixScrolling(DependencyObject obj)
{
return (bool)obj.GetValue(FixScrollingProperty);
}
public static void SetFixScrolling(DependencyObject obj, bool value)
{
obj.SetValue(FixScrollingProperty, value);
}
public static readonly DependencyProperty FixScrollingProperty =
DependencyProperty.RegisterAttached("FixScrolling", typeof(bool), typeof(ScrollViewerCorrector), new FrameworkPropertyMetadata(false, ScrollViewerCorrector.OnFixScrollingPropertyChanged));
public static void OnFixScrollingPropertyChanged(object sender, DependencyPropertyChangedEventArgs e)
{
ScrollViewer viewer = sender as ScrollViewer;
if (viewer == null)
throw new ArgumentException("The dependency property can only be attached to a ScrollViewer", "sender");
if ((bool)e.NewValue == true)
viewer.PreviewMouseWheel += HandlePreviewMouseWheel;
else if ((bool)e.NewValue == false)
viewer.PreviewMouseWheel -= HandlePreviewMouseWheel;
}
private static List<MouseWheelEventArgs> _reentrantList = new List<MouseWheelEventArgs>();
private static void HandlePreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
var scrollControl = sender as ScrollViewer;
if (!e.Handled && sender != null && !_reentrantList.Contains(e))
{
var previewEventArg = new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta)
{
RoutedEvent = UIElement.PreviewMouseWheelEvent,
Source = sender
};
var originalSource = e.OriginalSource as UIElement;
_reentrantList.Add(previewEventArg);
originalSource.RaiseEvent(previewEventArg);
_reentrantList.Remove(previewEventArg);
// at this point if no one else handled the event in our children, we do our job
if (!previewEventArg.Handled && ((e.Delta > 0 && scrollControl.VerticalOffset == 0)
|| (e.Delta <= 0 && scrollControl.VerticalOffset >= scrollControl.ExtentHeight - scrollControl.ViewportHeight)))
{
e.Handled = true;
var eventArg = new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta);
eventArg.RoutedEvent = UIElement.MouseWheelEvent;
eventArg.Source = sender;
var parent = (UIElement)((FrameworkElement)sender).Parent;
parent.RaiseEvent(eventArg);
}
}
}
}

DependencyPropertyListener() for WPF

I would like to operate a project planned for windows phone to be used in WPF.
Here is the link :
Loading Data when the User Scrolls to the End
By repeating the code , I perceived, that WPF does not know DependencyPropertyListener() in this method :
static void element_Loaded(object sender, RoutedEventArgs e)
{
FrameworkElement element = (FrameworkElement)sender;
element.Loaded -= element_Loaded;
ScrollViewer scrollViewer = FindChildOfType<ScrollViewer>(element);
if (scrollViewer == null)
{
throw new InvalidOperationException("ScrollViewer not found.");
}
var listener = new DependencyPropertyListener();
listener.Changed
+= delegate
{
bool atBottom = scrollViewer.VerticalOffset
>= scrollViewer.ScrollableHeight;
if (atBottom)
{
var atEnd = GetAtEndCommand(element);
if (atEnd != null)
{
atEnd.Execute(null);
}
}
};
Binding binding = new Binding("VerticalOffset") { Source = scrollViewer };
listener.Attach(scrollViewer, binding);
}
Would there be a way around this object ?
You can listen to dependency property value change using a DependencyPropertyDescriptor.
static void element_Loaded(object sender, RoutedEventArgs e)
{
FrameworkElement element = (FrameworkElement)sender;
element.Loaded -= element_Loaded;
ScrollViewer scrollViewer = FindChildOfType<ScrollViewer>(element);
if (scrollViewer == null)
{
throw new InvalidOperationException("ScrollViewer not found.");
}
var dpd = DependencyPropertyDescriptor.FromProperty(ScrollViewer.VerticalOffsetProperty, typeof(ScrollViewer));
dpd.AddValueChanged(scrollViewer, delegate(object o, EventArgs args)
{
bool atBottom = scrollViewer.VerticalOffset
>= scrollViewer.ScrollableHeight;
if (atBottom)
{
var atEnd = GetAtEndCommand(element);
if (atEnd != null)
{
atEnd.Execute(null);
}
}
});
}

Drag multiple items from WPF listview

I have a listview that displays files from a directory.
The user can drag each item in the listview to a folder/ the desktop and the associated file is copied there.
This works fine. The problem is- I want to do so for multiple items- so the user can select multiple listviewitems and drag and copy them together.
The ListView is set to SelectedMode=Multiple- but it doesn't copy all of the selected items.
Here's my code:
private void FileView_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
this.start = e.GetPosition(null);
}
private void FileView_MouseMove(object sender, MouseEventArgs e)
{
Point mpos = e.GetPosition(null);
Vector diff = this.start - mpos;
if (e.LeftButton == MouseButtonState.Pressed &&
Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance &&
Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance)
{
if (this.FileView.SelectedItems.Count == 0)
{
return;
}
// right about here you get the file urls of the selected items.
// should be quite easy, if not, ask.
string[] files = new String[1];
files[0] = "C:\\Users\\MyName\\Music\\My playlist\\" + FileView.SelectedValue.ToString();
string dataFormat = DataFormats.FileDrop;
DataObject dataObject = new DataObject(dataFormat, files);
DragDrop.DoDragDrop(this.FileView, dataObject, DragDropEffects.Copy);
}
}
Thanks!
private List<object> _selItems = new List<object>();
private void FileView_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
this._start = e.GetPosition(null);
_selItems.Clear();
_selItems.AddRange(FileView.SelectedItems.Cast<object>());
}
restore on MouseMove
foreach (object selItem in _selItems)
{
if (!FileView.SelectedItems.Contains(selItem))
FileView.SelectedItems.Add(selItem);
}
The problem here is that you are using SelectedValue for a multiselect, so you get one file. What you want is something more like this:
private void FileView_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
this.start = e.GetPosition(null);
}
private void FileView_MouseMove(object sender, MouseEventArgs e)
{
Point mpos = e.GetPosition(null);
Vector diff = this.start - mpos;
if (e.LeftButton == MouseButtonState.Pressed &&
(Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance ||
Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance)
)
{
if (this.FileView.SelectedItems.Count == 0)
return;
// right about here you get the file urls of the selected items.
// should be quite easy, if not, ask.
string[] files = new String[FileView.SelectedItems.Count];
int ix = 0;
foreach (object nextSel in FileView.SelectedItems)
{
files[ix] = "C:\\Users\\MyName\\Music\\My playlist\\" + nextSel.ToString();
++ix;
}
string dataFormat = DataFormats.FileDrop;
DataObject dataObject = new DataObject(dataFormat, files);
DragDrop.DoDragDrop(this.FileView, dataObject, DragDropEffects.Copy);
}
}
I'd like to point out an small error in the code
Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance &&
Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance)
should be
Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance ||
Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance)
Otherwise a straight Horizontal or Vertical will do nothing. Chances of that happening are small, but still..

Drag and Drop between 2 list boxes

Trying to implement drag and drop between 2 listboxes and all examples I've seen so far don't really smell good.
Can someone point me to or show me a good implementation?
Here's a sample form. Get started with a new WF project and drop two list boxes on the form. Make the code look like this:
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
listBox1.Items.AddRange(new object[] { "one", "two", "three" });
listBox1.MouseDown += new MouseEventHandler(listBox1_MouseDown);
listBox1.MouseMove += new MouseEventHandler(listBox1_MouseMove);
listBox2.AllowDrop = true;
listBox2.DragEnter += new DragEventHandler(listBox2_DragEnter);
listBox2.DragDrop += new DragEventHandler(listBox2_DragDrop);
}
private Point mDownPos;
void listBox1_MouseDown(object sender, MouseEventArgs e) {
mDownPos = e.Location;
}
void listBox1_MouseMove(object sender, MouseEventArgs e) {
if (e.Button != MouseButtons.Left) return;
int index = listBox1.IndexFromPoint(e.Location);
if (index < 0) return;
if (Math.Abs(e.X - mDownPos.X) >= SystemInformation.DragSize.Width ||
Math.Abs(e.Y - mDownPos.Y) >= SystemInformation.DragSize.Height)
DoDragDrop(new DragObject(listBox1, listBox1.Items[index]), DragDropEffects.Move);
}
void listBox2_DragEnter(object sender, DragEventArgs e) {
DragObject obj = e.Data.GetData(typeof(DragObject)) as DragObject;
if (obj != null && obj.source != listBox2) e.Effect = e.AllowedEffect;
}
void listBox2_DragDrop(object sender, DragEventArgs e) {
DragObject obj = e.Data.GetData(typeof(DragObject)) as DragObject;
listBox2.Items.Add(obj.item);
obj.source.Items.Remove(obj.item);
}
private class DragObject {
public ListBox source;
public object item;
public DragObject(ListBox box, object data) { source = box; item = data; }
}
}
the proper way to do a drag-drop control in .net is by running code in the 2nd control's DragDrop event handler.
It may "smell" weird, but this is how it works in .NET.
Google gave this: http://www.codeproject.com/KB/dotnet/csdragndrop01.aspx
It seems a pretty reasonable tutorial. If it smells bad, I think that's more to do with the API for drag and drop being awkward to use rather than the tutorial itself being poor.

Resources