Excel like drag selection in Wpf itemscontrol
I am looking to make a selection rectangle in Wpf items control just as in MS Excel.I have looked at decorators and adoners but got little help.I need the thick border around the cells that 开发者_运维知识库i select using mouse(check screenShot)
I have a solution for you, that seems to work fairly well with just an attached property for you to attach to an items control such as a listbox. The code makes guesses as to whether or not selected items are contiguous by just checking that they are contiguous by index. With say a uniformgrid with some row count that won't work, but this should get you started.
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Documents;
using System.Windows.Media;
namespace WpfApplication2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
public sealed class SelectionAdorner : Adorner
{
private static readonly Dictionary<Selector, SelectionAdorner> _dictionary = new Dictionary<Selector, SelectionAdorner>();
private static readonly Pen _pen = CreatePen();
public static readonly DependencyProperty UseAdornerProperty = DependencyProperty.RegisterAttached("UseAdorner", typeof(bool), typeof(SelectionAdorner),
new PropertyMetadata(OnUseAdornerChanged));
static void AttachToScrollViewer(Selector selector)
{
ScrollViewer viewer = GetScrollViewer(selector);
if (viewer != null)
{
viewer.Tag = selector;
viewer.ScrollChanged += Viewer_ScrollChanged;
}
}
private static Pen CreatePen()
{
Pen pen = new Pen(Brushes.Black, 2.0);
pen.Freeze();
return pen;
}
private static void DetachAdorner(Selector selector)
{
SelectionAdorner adorner;
if (_dictionary.TryGetValue(selector, out adorner))
{
adorner._adornerLayer.Remove(adorner);
_dictionary.Remove(selector);
}
}
private static SelectionAdorner GetAdorner(Selector selector)
{
SelectionAdorner adorner;
if (!_dictionary.TryGetValue(selector, out adorner))
{
_dictionary.Add(selector, adorner = new SelectionAdorner(selector));
}
return adorner;
}
private static Rect GetBounds(Selector selector, UIElement containerElement)
{
Rect bounds = VisualTreeHelper.GetDescendantBounds(containerElement);
return new Rect(containerElement.TranslatePoint(bounds.TopLeft, selector), bounds.Size);
}
private static ScrollViewer GetScrollViewer(DependencyObject d)
{
List<DependencyObject> list = new List<DependencyObject>();
foreach (DependencyObject child in Enumerable.Range(0, VisualTreeHelper.GetChildrenCount(d)).Select(index => VisualTreeHelper.GetChild(d, index)))
{
ScrollViewer viewer = child as ScrollViewer;
if (viewer != null)
{
return viewer;
}
list.Add(child);
}
return list.Select(GetScrollViewer).FirstOrDefault(viewer => viewer != null);
}
private static void OnUseAdornerChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Selector selector = d as Selector;
if (selector != null)
{
if (((bool)e.NewValue))
{
selector.SelectionChanged += Selector_SelectionChanged;
if (!selector.IsLoaded)
{
selector.Loaded += Selector_Loaded;
}
else
{
AttachToScrollViewer(selector);
}
ProcessSelection(selector);
}
else
{
selector.SelectionChanged -= Selector_SelectionChanged;
if (!selector.IsLoaded)
{
selector.Loaded -= Selector_Loaded;
}
else
{
ScrollViewer viewer = GetScrollViewer(selector);
if (viewer != null)
{
viewer.ScrollChanged -= Viewer_ScrollChanged;
}
}
DetachAdorner(selector);
}
}
}
private static void ProcessSelection(Selector selector)
{
ListBox listBox = selector as ListBox;
if (listBox != null && listBox.SelectionMode != SelectionMode.Single)
{
if (listBox.SelectedItems.Count != 0)
{
object[] selectedItems = new object[listBox.SelectedItems.Count];
listBox.SelectedItems.CopyTo(selectedItems, 0);
ProcessSelection(selector, selectedItems);
return;
}
}
else if (selector.SelectedItem != null)
{
ProcessSelection(selector, new[] { selector.SelectedItem });
return;
}
DetachAdorner(selector);
}
private static void ProcessSelection(Selector selector, IEnumerable<object> selectedItems)
{
List<DependencyObject> containers = new List<DependencyObject>();
List<int> indices = new List<int>();
foreach (DependencyObject container in selectedItems
.Select(selectedItem => selector.ItemContainerGenerator.ContainerFromItem(selectedItem))
.Where(container => container != null))
{
int containerIndex = selector.ItemContainerGenerator.IndexFromContainer(container);
int index = indices.BinarySearch(containerIndex);
containers.Insert(~index, container);
indices.Insert(~index, containerIndex);
}
for (int i = 1; i < indices.Count; i++)
{
if (indices[i] != (indices[i - 1] + 1))
{
// Not contiguous
DetachAdorner(selector);
return;
}
}
Rect bounds = GetBounds(selector, (UIElement)containers[0]);
for (int i = 1; i < containers.Count; i++)
{
bounds.Union(GetBounds(selector, (UIElement)containers[i]));
}
GetAdorner(selector).Update(bounds);
}
static void Selector_Loaded(object sender, RoutedEventArgs e)
{
var selector = (Selector)sender;
AttachToScrollViewer(selector);
selector.Loaded -= Selector_Loaded;
}
private static void Selector_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ProcessSelection((Selector)sender);
}
static void Viewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
ProcessSelection((Selector)((FrameworkElement)sender).Tag);
}
public static bool GetUseAdorner(Selector control)
{
return (bool)control.GetValue(UseAdornerProperty);
}
public static void SetUseAdorner(Selector control, bool useAdorner)
{
control.SetValue(UseAdornerProperty, useAdorner);
}
private readonly AdornerLayer _adornerLayer;
private Rect _bounds;
private void Update(Rect bounds)
{
this._bounds = bounds;
this.InvalidateVisual();
}
protected override void OnRender(DrawingContext drawingContext)
{
drawingContext.DrawRectangle(null, _pen, this._bounds);
}
private SelectionAdorner(Selector selector)
: base(selector)
{
if ((_adornerLayer = AdornerLayer.GetAdornerLayer(selector)) != null)
{
_adornerLayer.Add(this);
}
}
}
}
Usage in XAML:
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sl="clr-namespace:System.Linq;assembly=System.Core"
xmlns:s="clr-namespace:System;assembly=mscorlib"
xmlns:WpfApplication2="clr-namespace:WpfApplication2" Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<ObjectDataProvider x:Key="Range" MethodName="Range" ObjectType="{x:Type sl:Enumerable}">
<ObjectDataProvider.MethodParameters>
<s:Int32>0</s:Int32>
<s:Int32>40</s:Int32>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</Window.Resources>
<Grid>
<ListBox ItemsSource="{Binding Source={StaticResource Range}}" SelectionMode="Extended" HorizontalContentAlignment="Stretch"
WpfApplication2:SelectionAdorner.UseAdorner="true">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Horizontal" IsItemsHost="True" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Margin" Value="3" />
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</Grid>
</Window>
精彩评论