I find some solution how select listbox item on right mouse button click, but any solution does not work after I scroll listbox. It possible select listbox on right mouse button, if I scroll also listbox? Any advance?
My code is here:
<ListBox Name="friendsListBox"
ItemsSource="{Binding}"
SelectedItem="Key"
Style="{DynamicResource friendsListStyle}"
PreviewMouseRightButtonUp="ListBox_PreviewMouseRightButtonUp"
PreviewMouseRightButtonDown="ListBox_PreviewMouseRightButtonDown"
Grid.Row="1" MouseRightButtonDown="FriendsListBoxMouseRightButtonDown">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<EventSetter Event="MouseDoubleClick" Handler="ListBoxItem_MouseDoubleClick"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ContextMenu>
<ContextMenu x:Name="FriendContextMenu">
<MenuItem Name="SendRp" Header="Pošli Rp" Click="FriendContextMenuItem_Click" />
<MenuItem Name="SendMsg" Header="Pošli poštu" Click="FriendContextMenuItem_Click"/>
<MenuItem Name="DeleteFriend" Header="Vymaž" Click="FriendContextMenuItem_Click"/>
</ContextMenu>
</ListBox.ContextMenu>
</ListBox>
Code behind:
private void ListBox_PreviewMouseRightButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
SelectItemOnRightClick(e);
e.Handled = true;
}
private void ListBox_PreviewMouseRightButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
SelectItemOnRightClick(e);
FriendContextMenu.PlacementTarget = sender as UIElement;
FriendContextMenu.IsOpen = true;
}
private void FriendsListBoxMouseRightButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
SelectItemOnRightClick(e);
}
private void SelectItemOnRightClick(System.Windows.Input.MouseButtonEventArgs e)
{
Point clickPoint = e.GetPosition(friendsListBox);
var listBoxItem =
friendsListBox.ItemContainerGenerator.ContainerFromIndex(0) as ListBoxItem;
if (listBoxItem != null)
{
var nPotenialIndex = (int)(clickPoint.Y / listBoxItem.ActualHeight);
if (nPotenialIndex > -1 && nPotenialIndex < friendsListBox.Items.Count)
{
friendsListBox.SelectedItem = friendsListBox.Items[nPotenialIndex];
}
}
}
private void ListBoxItem_MouseDoubleClick(object sender, RoutedEventArgs e)
{
if (friendsListBox.SelectedItem!=null)
{
var selectedFriend = (KeyValuePair<string, FriendData>)friendsListBox.SelectedItem;
//MessageBox.Show(selectedFriend.Value.ProfilePhoto.UriSource.OriginalString);
OpenWindow(FriendsData[selectedFriend.Value.Nick.ToLower()]);
}
}
To select an item on Right click, you can do this instead. It works with scroll
private void SelectItemOnRightClick(System.Windows.Input.MouseButtonEventArgs e)
{
Point clickPoint = e.GetPosition(friendsListBox);
object element = friendsListBox.InputHitTest(clickPoint);
if (element != null)
{
ListBoxItem clickedListBoxItem = GetVisualParent<ListBoxItem>(element);
if (clickedListBoxItem != null)
{
friendsListBox.SelectedItem = clickedListBoxItem.Content;
}
}
}
public T GetVisualParent<T>(object childObject) where T : Visual
{
DependencyObject child = childObject as DependencyObject;
while ((child != null) && !(child is T))
{
child = VisualTreeHelper.GetParent(child);
}
return child as T;
}
Related
I have a DataGrid on a WPF page and want to prevent the user from selecting cells. As this feature is needed just for testing, I don't want to change everything in code.
After my DataGrid is filled, I make sure all of its rows are selected. Now I want to make sure that user cannot select/unselect rows.
I tried setting IsEnabled = false and IsHitTestVisible = "False" but both of these solutions disable scrollbars.
Is there any way to do this?
Why not just set IsHitTestVisible="False" for your DataGridRow or DataGridCell objects only?
That's easy to do using an implicit style in the <DataGrid.Resources>, and should only disable hit-testing on the rows or cells, which should leave the other areas of the DataGrid functional, such as the Headers or ScrollBars
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="IsHitTestVisible" Value="False" />
</Style>
</DataGrid.Resources>
You have two choices:
You disable selection in style (in this case you turn off only color in style, but physically SelectedItem or SelectedItems will change). You can easily find out how you can turn off selection style.
You can disable changing selection without changing SelectedItem or SelectedItems (in this case your selection style will not change too).
In WPF i don't like to override standard controls. So, we need a Behavior:
public class DisableSelectionDataGridBehavior : Behavior<DataGrid>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.PreviewMouseLeftButtonDown += AssociatedObjectOnPreviewMouseLeftButtonDown;
}
private void AssociatedObjectOnPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var dependencyObject = AssociatedObject.InputHitTest(e.GetPosition(AssociatedObject)) as DependencyObject;
if (dependencyObject == null) return;
var elements = dependencyObject.GetParents().OfType<FrameworkElement>().Where(DataGridCellExtended.GetIsDisableSelection).ToList();
if (!elements.Any()) return;
e.Handled = true;
var args = new MouseButtonEventArgs(e.MouseDevice, e.Timestamp, e.ChangedButton, e.StylusDevice);
args.RoutedEvent = UIElement.MouseLeftButtonDownEvent;
args.Source = e.Source;
elements.ForEach(item =>
{
item.RaiseEvent(args);
var children = item.GetChildren<FrameworkElement>();
children.ForEach(child => child.RaiseEvent(args));
});
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.PreviewMouseLeftButtonDown -= AssociatedObjectOnPreviewMouseLeftButtonDown;
}
}
Second, you need an Extended class:
public class DataGridCellExtended
{
public static readonly DependencyProperty IsDisableSelectionProperty = DependencyProperty.RegisterAttached("IsDisableSelection", typeof(Boolean), typeof(DataGridCellExtended));
public static Boolean GetIsDisableSelection(DependencyObject o)
{
return (Boolean)o.GetValue(IsDisableSelectionProperty);
}
public static void SetIsDisableSelection(DependencyObject o, Boolean value)
{
o.SetValue(IsDisableSelectionProperty, value);
}
}
And finally in XAML you need something like this:
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="{x:Type items:YourViewModel}">
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Button Margin="0"
extends:DataGridCellExtended.IsDisableSelection="True">
<Path Data="M5,0L3,2 1,0 0,1 2,3 0,5 1,6 3,4 5,6 6,5 4,3 6,1z"
Fill="{Binding Foreground, RelativeSource={RelativeSource FindAncestor, AncestorType=DataGridCell}}"
Width="12"
Height="12"
Stretch="Uniform"/>
</Button>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
You can write your logic for extended class.
public static IEnumerable<DependencyObject> GetParents(this DependencyObject element)
{
if (element != null)
{
while (true)
{
var parent = element.GetParent();
var dependencyObject = parent;
element = parent;
if (dependencyObject == null)
{
break;
}
yield return element;
}
yield break;
}
else
{
throw new ArgumentNullException("element");
}
}
private static IEnumerable<DependencyObject> GetChildrenRecursive(this DependencyObject element)
{
if (element != null)
{
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++)
{
var dependencyObject = VisualTreeHelper.GetChild(element, i);
yield return dependencyObject;
foreach (var childrenRecursive in dependencyObject.GetChildrenRecursive())
{
yield return childrenRecursive;
}
}
}
else
{
throw new ArgumentNullException("element");
}
}
I am using telrik RadDragAndDrop Tool with the ListBox. I am using silverlight with mvvm light. My question is that how should I use this code in ViewModel. This is a code behind file.
public Construtor()
{
InitializeComponent();
RadDragAndDropManager.AddDragQueryHandler(this, OnDragQuery);
RadDragAndDropManager.AddDragInfoHandler(this, OnDragInfo);
RadDragAndDropManager.AddDropQueryHandler(this, OnDropQuery);
RadDragAndDropManager.AddDropInfoHandler(this, OnDropInfo);
}
private void OnDropInfo(object sender, DragDropEventArgs e)
{
ItemsControl box = e.Options.Destination as ItemsControl;
IList<Section> itemsSource = box.ItemsSource as IList<Section>;
Section section = e.Options.Payload as Section;
if (e.Options.Status == DragStatus.DropComplete)
{
if (!itemsSource.Contains(section))
{
itemsSource.Add(section);
}
}
}
void OnDropQuery(object sender, DragDropQueryEventArgs e)
{
ItemsControl box = e.Options.Destination as ItemsControl;
IList<Section> itemsSource = box.ItemsSource as IList<Section>;
Section section = e.Options.Payload as Section;
e.QueryResult = section != null && !itemsSource.Contains(section);
}
void OnDragInfo(object sender, DragDropEventArgs e)
{
ListBoxItem listBoxItem = e.Options.Source as ListBoxItem;
ListBox box = ItemsControl.ItemsControlFromItemContainer(listBoxItem) as ListBox;
IList<Section> itemsSource = box.ItemsSource as IList<Section>;
Section section = e.Options.Payload as Section;
if (e.Options.Status == DragStatus.DragComplete)
{
if (section != null && itemsSource.Contains(section))
{
itemsSource.Remove(section);
}
}
}
protected virtual void OnDragQuery(object sender, DragDropQueryEventArgs e)
{
ListBoxItem listBoxItem = e.Options.Source as ListBoxItem;
ListBox box = ItemsControl.ItemsControlFromItemContainer(listBoxItem) as ListBox;
if (e.Options.Status == DragStatus.DragQuery && box != null)
{
e.Options.Payload = box.SelectedItem;
ContentControl cue = new ContentControl();
cue.Content = box.SelectedItem;
e.Options.DragCue = cue;
}
e.QueryResult = true;
}
private void button1_Click(object sender, RoutedEventArgs e)
{
SelectingQuestionsWindow window = new SelectingQuestionsWindow();
window.Show();
this.radExpander1.Visibility = System.Windows.Visibility.Visible;
}
<*XAML*>
This is my Xaml.
<ListBox x:Name="SectionListBoxMain" Height="165" Width="200" SelectedItem="{Binding SelectedSectionList}"
DisplayMemberPath="SectionName" ItemsSource="{Binding SectionList}" ItemContainerStyle="{StaticResource draggableItemStyle}">
<telerik:RadDragAndDropManager.AllowDrop>
true
</telerik:RadDragAndDropManager.AllowDrop>
</ListBox>
<TextBlock Width="20" />
<ListBox x:Name="SectionListBox" Height="167" Width="200" ItemsSource="{Binding SelectedSectionList}" telerik:RadDragAndDropManager.AllowDrop="True" DisplayMemberPath="SectionName" ItemContainerStyle="{StaticResource draggableItemStyle}">
</ListBox>
</StackPanel>
This is view logic that does not really belong in your ViewModel. It is probably better suited to a Behavior.
See this example: http://www.telerik.com/help/silverlight/raddraganddrop-within-radgridview.html
They use a Behavior and attach it to a grid to enable row reordering. You could start with something like:
public partial class ListDragDropBehavior : Behavior<ListBox>
You would need to add some dependency properties to bind to the other list box.
You can then use this Behavior on other list boxes by simply attaching it to the list box (i use blend to attach behaviors)
How can I incorporate an auto-tab when the MaxLength property is reached into a XAML Trigger, DataTrigger, PropertyTrigger, Style.Trigger, etc. Below are two such options for how I have already accomplished this with a TextBox via code-behind. I'm looking to apply it in a XAML style as well. Thanks.
XAML:
<TextBox x:Name="MyTextBox"
Text="{Binding Path=MyProperty}"
Style="{StaticResource TextBoxStyle}"
MaxLength="5"
TextChanged="MyTextBox_TextChanged">
</TextBox>
Code-Behind for WPF:
private void MyTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
if (MyTextBox.Text.Length == MyTextBox.MaxLength)
{
Keyboard.Focus(NextTextBox);
}
}
private void MyTextBox_PreviewKeyDown(object sender, KeyEventArgs e)
{
// Auto-tab when maxlength is reached
if (((TextBox)sender).MaxLength == ((TextBox)sender).Text.Length)
{
// move focus
var ue = e.OriginalSource as FrameworkElement;
e.Handled = true;
ue.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
}
}
}
simply do this in your Shell.xaml
<Style TargetType="TextBox">
<EventSetter Event="TextChanged" Handler="MyTextBox_PreviewKeyDown"/>
</Style>
and in your shell.xaml.cs
private void MyTextBox_PreviewKeyDown(object sender, KeyEventArgs e)
{
// Auto-tab when maxlength is reached
if (((TextBox)sender).MaxLength == ((TextBox)sender).Text.Length)
{
// move focus
var ue = e.OriginalSource as FrameworkElement;
e.Handled = true;
ue.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
}
}
}
For a simple test I want to drag a Button to a TextBox. I can start dragging the Button, but the Drop event is not raised. What am I missing?
Xaml:
<Window x:Class="DayPlanner.View.DnDTest"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DnDTest" Height="200" Width="200">
<StackPanel>
<Button Name="button"
Content="OK"
PreviewMouseLeftButtonDown="button_PreviewMouseLeftButtonDown"
PreviewMouseMove="button_PreviewMouseMove"/>
<TextBox Name="textBox"
AllowDrop="True"
DragEnter="textBox_DragEnter"
Drop="textBox_Drop"/>
</StackPanel>
</Window>
Code:
public partial class DnDTest : Window
{
public DnDTest()
{
InitializeComponent();
}
private Point dragStartPoint;
private void button_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
dragStartPoint = e.GetPosition(null);
}
private static bool IsDragging(Point dragStartPoint, MouseEventArgs e)
{
var diff = e.GetPosition(null) - dragStartPoint;
return
e.LeftButton == MouseButtonState.Pressed &&
(Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance ||
Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance);
}
private void button_PreviewMouseMove(object sender, MouseEventArgs e)
{
if (IsDragging(dragStartPoint, e))
{
DragDrop.DoDragDrop(button, new DataObject("Button", button), DragDropEffects.Move);
e.Handled = true;
}
}
private void textBox_DragEnter(object sender, DragEventArgs e)
{
e.Handled = true;
}
private void textBox_Drop(object sender, DragEventArgs e)
{
var button = (Button)e.Data.GetData("Button");
textBox.Text = string.Format("[0]", button.Content.ToString());
e.Handled = true;
}
}
This might be some strange case, but to fix it, I needed to handle or dragging events, including the Preview versions.
Here's how to make it work.
Xaml:
<Window x:Class="DayPlanner.View.DnDTestBasic"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DnDTestBasic" Height="200" Width="200">
<StackPanel>
<Button Name="button"
Content="OK"
PreviewMouseLeftButtonDown="button_PreviewMouseLeftButtonDown"
PreviewMouseMove="button_PreviewMouseMove"/>
<TextBox Name="textBox"
AllowDrop="True"
PreviewDragEnter="textBox_Dragging"
DragEnter="textBox_Dragging"
PreviewDragOver="textBox_Dragging"
DragOver="textBox_Dragging"
Drop="textBox_Drop"/>
<TextBlock Name="status"
Text="No dragging"/>
</StackPanel>
</Window>
Code:
public partial class DnDTestBasic : Window
{
public DnDTestBasic()
{
InitializeComponent();
}
private Point dragStartPoint;
private void button_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
dragStartPoint = e.GetPosition(null);
status.Text = "New drag start position";
}
private static bool IsDragging(Point dragStartPoint, MouseEventArgs e)
{
var diff = e.GetPosition(null) - dragStartPoint;
return
e.LeftButton == MouseButtonState.Pressed &&
(Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance ||
Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance);
}
private void button_PreviewMouseMove(object sender, MouseEventArgs e)
{
if (IsDragging(dragStartPoint, e))
{
status.Text = "Starting drag...";
DragDrop.DoDragDrop(button, new DataObject("Button", button), DragDropEffects.Copy);
status.Text = "Dragging done.";
e.Handled = true;
}
}
private void textBox_Dragging(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent("Button"))
e.Effects = DragDropEffects.Copy;
else
e.Effects = DragDropEffects.None;
e.Handled = true;
}
private void textBox_Drop(object sender, DragEventArgs e)
{
var button = (Button)e.Data.GetData("Button");
textBox.Text = string.Format("[{0}]", button.Content.ToString());
e.Handled = true;
}
}
I believe it has to do with the fact that when you start the drag event, the button control is capturing mouse input. Any mouse movements you do after that are registered to the button instead of to the application
I actually had a similar problem and ended up using MouseEnter/Leave events instead of the built in WPF drag/drop framework.
I've almost got this working apart from one little annoying thing...
Because the ListBox selection happens on mouse down, if you start the drag with the mouse down when selecting the last item to drag it works fine, but if you select all the items to drag first and then click on the selection to start dragging it, the one you click on gets unselected and left behind after the drag.
Any thoughts on the best way to get around this?
<DockPanel LastChildFill="True">
<ListBox ItemsSource="{Binding SourceItems}"
SelectionMode="Multiple"
PreviewMouseLeftButtonDown="HandleLeftButtonDown"
PreviewMouseLeftButtonUp="HandleLeftButtonUp"
PreviewMouseMove="HandleMouseMove"
MultiSelectListboxDragDrop:ListBoxExtension.SelectedItemsSource="{Binding SelectedItems}"/>
<ListBox ItemsSource="{Binding DestinationItems}"
AllowDrop="True"
Drop="DropOnToDestination"/>
<DockPanel>
...
public partial class Window1
{
private bool clickedOnSourceItem;
public Window1()
{
InitializeComponent();
DataContext = new WindowViewModel();
}
private void DropOnToDestination(object sender, DragEventArgs e)
{
var viewModel = (WindowViewModel)
e.Data.GetData(typeof(WindowViewModel));
viewModel.CopySelectedItems();
}
private void HandleLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var sourceElement = (FrameworkElement)sender;
var hitItem = sourceElement.InputHitTest(e.GetPosition(sourceElement))
as FrameworkElement;
if(hitItem != null)
{
clickedOnSourceItem = true;
}
}
private void HandleLeftButtonUp(object sender, MouseButtonEventArgs e)
{
clickedOnSourceItem = false;
}
private void HandleMouseMove(object sender, MouseEventArgs e)
{
if(clickedOnSourceItem)
{
var sourceItems = (FrameworkElement)sender;
var viewModel = (WindowViewModel)DataContext;
DragDrop.DoDragDrop(sourceItems, viewModel, DragDropEffects.Move);
clickedOnSourceItem = false;
}
}
}
I've found a very simple way to enable Windows Explorer like drag/drop behaviour when having multiple items selected. The solution replaces the common ListBox with a little derived shim that replaces the ListBoxItem with a more intelligent version. This way, we can encapsulate the click state at the right level and call into the protected selection machinery of the ListBox. Here is the relevant class. For a complete example, see my repo on github.
public class ListBoxEx : ListBox
{
protected override DependencyObject GetContainerForItemOverride()
{
return new ListBoxItemEx();
}
class ListBoxItemEx : ListBoxItem
{
private bool _deferSelection = false;
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
if (e.ClickCount == 1 && IsSelected)
{
// the user may start a drag by clicking into selected items
// delay destroying the selection to the Up event
_deferSelection = true;
}
else
{
base.OnMouseLeftButtonDown(e);
}
}
protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
{
if (_deferSelection)
{
try
{
base.OnMouseLeftButtonDown(e);
}
finally
{
_deferSelection = false;
}
}
base.OnMouseLeftButtonUp(e);
}
protected override void OnMouseLeave(MouseEventArgs e)
{
// abort deferred Down
_deferSelection = false;
base.OnMouseLeave(e);
}
}
}
So...having become the proud owner of a tumbleweed badge, I've got back on to this to try & find a way around it. ;-)
I'm not sure I like the solution so I'm still very much open to any better approaches.
Basically, what I ended up doing is remember what ListBoxItem was last clicked on & then make sure that gets added to the selected items before a drag. This also meant looking at how far the mouse moves before starting a drag - because clicking on a selected item to unselect it could sometimes result in it getting selected again if mouse bounce started a little drag operation.
Finally, I added some hot tracking to the listbox items so, if you mouse down on a selected item it'll get unselected but you still get some feedback to indicate that it will get included in the drag operation.
private void HandleLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var source = (FrameworkElement)sender;
var hitItem = source.InputHitTest(e.GetPosition(source)) as FrameworkElement;
hitListBoxItem = hitItem.FindVisualParent<ListBoxItem>();
origPos = e.GetPosition(null);
}
private void HandleLeftButtonUp(object sender, MouseButtonEventArgs e)
{
hitListBoxItem = null;
}
private void HandleMouseMove(object sender, MouseEventArgs e)
{
if (ShouldStartDrag(e))
{
hitListBoxItem.IsSelected = true;
var sourceItems = (FrameworkElement)sender;
var viewModel = (WindowViewModel)DataContext;
DragDrop.DoDragDrop(sourceItems, viewModel, DragDropEffects.Move);
hitListBoxItem = null;
}
}
private bool ShouldStartDrag(MouseEventArgs e)
{
if (hitListBoxItem == null)
return false;
var curPos = e.GetPosition(null);
return
Math.Abs(curPos.Y-origPos.Y) > SystemParameters.MinimumVerticalDragDistance ||
Math.Abs(curPos.X-origPos.X) > SystemParameters.MinimumHorizontalDragDistance;
}
XAML changes to include hot tracking...
<Style TargetType="ListBoxItem">
<Setter Property="Margin" Value="1"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Grid>
<Border Background="{TemplateBinding Background}" />
<Border Background="#BEFFFFFF" Margin="1">
<Grid>
<Grid.RowDefinitions>
<RowDefinition /><RowDefinition />
</Grid.RowDefinitions>
<Border Margin="1" Grid.Row="0" Background="#57FFFFFF" />
</Grid>
</Border>
<ContentPresenter Margin="8,5" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="PowderBlue" />
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="True" />
<Condition Property="IsSelected" Value="False"/>
</MultiTrigger.Conditions>
<Setter Property="Background" Value="#5FB0E0E6" />
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
One option would be not to allow ListBox or ListView to remove selected items until MouseLeftButtonUp is triggered.
Sample code:
List<object> removedItems = new List<object>();
private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.RemovedItems.Count > 0)
{
ListBox box = sender as ListBox;
if (removedItems.Contains(e.RemovedItems[0]) == false)
{
foreach (object item in e.RemovedItems)
{
box.SelectedItems.Add(item);
removedItems.Add(item);
}
}
}
}
private void ListBox_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (removedItems.Count > 0)
{
ListBox box = sender as ListBox;
foreach (object item in removedItems)
{
box.SelectedItems.Remove(item);
}
removedItems.Clear();
}
}
I'm surprised that the behaviour difference between ListBox and the Windows Explorer has not been taken care of after 4 years across 3 major updates of the .NET framework.
I ran in to this problem back in Silverlight 3. I ended up overriding the mouse down and mouse up event handler to fully simulate the Windows Explorer behaviour.
I don't have the source code any more but the logic should be:
When mouse down
if the target item is not selected, clear existing selection
if Ctrl key is down, add target item to selection
if Shift key is down
if there is a previously selected item, add all items between target item and previous item to selection
else only add target item to selection
if the target item is selected de-select only if Ctrl key is down
When mouse up (on the same item)
if the target item is selected
if Ctrl key is down, remove item from selection
if Shift key is down
if there is a previously selected item, remove all items between target item and previous item from selection
else only remove target item from selection
However
This should really be Microsoft's job to update the behaviour to be consistent to the operating system and to be more intuitive. I've submitted it as a bug to Microsoft if any body wants to vote for it: http://connect.microsoft.com/VisualStudio/feedback/details/809192/
I had a similar problem. I Started with the basic implementation from https://www.c-sharpcorner.com/uploadfile/dpatra/drag-and-drop-item-in-listbox-in-wpf/
and Modified to something like this:
ListBox dragSource = null;
ObservableCollection<String> dragItems;
private void ListBox_Drop(object sender, DragEventArgs e)
{
ListBox parent = (ListBox)sender;
// check if the Items are from an different source
if(dragSource != parent)
{
// Add and remove the Items of both sources
foreach (var item in dragItems)
{
((ObservableCollection<String>)dragSource.ItemsSource).Remove(item);
((ObservableCollection<String>)parent.ItemsSource).Add(item);
}
}
}
private void ListBox_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
// Check if Modifiers for Selection modes are pressed
if(Keyboard.Modifiers != ModifierKeys.Control && Keyboard.Modifiers != ModifierKeys.Shift)
{
ListBox parent = (ListBox)sender;
dragSource = parent;
object data = GetDataFromListBox(dragSource, e.GetPosition(parent));
dragItems = new ObservableCollection<String>();
for(int i = 0; i < parent.SelectedItems.Count; i++)
{
dragItems.Add(parent.SelectedItems[i] as String);
}
//If the Data is currently selected drop whole selection
if(dragItems.Contains(data as String))
{
DragDrop.DoDragDrop(parent, parent.SelectedItems, DragDropEffects.Move);
}
// The data is not selected, so clear selection and try to drop the current Item
else
{
dragItems.Clear();
dragItems.Add(data as String);
parent.SelectedItems.Clear();
DragDrop.DoDragDrop(parent, data, DragDropEffects.Move);
}
}
}
private static object GetDataFromListBox(ListBox source, Point point)
{
UIElement element = source.InputHitTest(point) as UIElement;
if (element != null)
{
object data = DependencyProperty.UnsetValue;
while (data == DependencyProperty.UnsetValue)
{
data = source.ItemContainerGenerator.ItemFromContainer(element);
if (data == DependencyProperty.UnsetValue)
{
element = VisualTreeHelper.GetParent(element) as UIElement;
}
if (element == source)
{
return null;
}
}
if (data != DependencyProperty.UnsetValue)
{
return data;
}
}
return null;
}
Hope this helps anyone to sumples across this Thread