为ListView添加矩形选择功能
来源:互联网 发布:德国拜发 软件 编辑:程序博客网 时间:2024/04/30 14:26
ListView矩形选择功能预览
也许微软会在以后的版本中加入对矩形选择列表项的支持, 但是现在还没有,所以有必要将其实现并记录下来。
对现有ListView控件添加矩形选择功能的最佳实现应该是使用关联属性,这样可以将使用该功能的复杂性降到最低,具体使用方式应该按照如下所示:
<ListView local:ListBoxExtention.IsRectSelectionEnabled="True">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListViewItem Content="1"/>
<ListViewItem Content="2"/>
<ListViewItem Content="3"/>
<ListViewItem Content="4"/>
<ListViewItem Content="5"/>
<ListViewItem Content="6"/>
<ListViewItem Content="7"/>
<ListViewItem Content="8"/>
</ListView>
Attached Property被定义在ListBoxExtention类上,为什么类名为ListBox*呢?因为我发现ListView其实使用ListBox继承,ListViewItem从ListBoxItem继承,所以对ListBox实现的矩形选择功能,对ListView同样可用。
ListBoxExtention类的定义如下:
public static class ListBoxExtention
{
public static bool GetIsRectSelectionEnabled(DependencyObject obj)
{
return (bool)obj.GetValue(IsRectSelectionEnabledProperty);
}
public static void SetIsRectSelectionEnabled(DependencyObject obj, bool value)
{
obj.SetValue(IsRectSelectionEnabledProperty, value);
}
public static readonly DependencyProperty IsRectSelectionEnabledProperty = DependencyProperty.RegisterAttached(
"IsRectSelectionEnabled", typeof(bool), typeof(ListBoxRectSelection), new FrameworkPropertyMetadata(OnIsRectSelectionEnabledChanged));
private static void OnIsRectSelectionEnabledChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
if(!(sender is ListBox))
{
throw new ArgumentException("sender", "Target should be a ListBox and its sub class instance."); ;
}
bool isEnabled = (bool)e.NewValue;
if (isEnabled)
{
ListBoxRectSelection rectSelection = ListBoxExtention.GetRectSelection(sender) ;
if (rectSelection == null)
{
rectSelection = new ListBoxRectSelection(sender as ListView);
ListBoxExtention.SetRectSelection(sender, rectSelection);
}
rectSelection.IsEnabled = isEnabled;
}
}
private static ListBoxRectSelection GetRectSelection(DependencyObject obj)
{
return (ListBoxRectSelection)obj.GetValue(RectSelectionProperty);
}
private static void SetRectSelection(DependencyObject obj, ListBoxRectSelection value)
{
obj.SetValue(RectSelectionProperty, value);
}
private static readonly DependencyProperty RectSelectionProperty =
DependencyProperty.RegisterAttached("RectSelection", typeof(ListBoxRectSelection), typeof(ListBoxExtention));
}
可以看到,关联属性IsRectSelectionEnabled 主要通过设置ListBoxRectSelection 的IsEnabled属性用来启用或者禁用ListBoxRectSelection 类定义的功能, ListBoxRectSelection 类的对象被存储在一个私有关联属性里面。定义如下:
public class ListBoxRectSelection
{
private ListBox _TargetList;
private RectangleSelectionGesture _RectangleSelectionGesture;
public bool IsEnabled
{
get { return _RectangleSelectionGesture.IsEnabled; }
set { _RectangleSelectionGesture.IsEnabled = value; }
}
public ListBoxRectSelection(ListBox target)
{
_TargetList = target;
_RectangleSelectionGesture = new RectangleSelectionGesture(target);
_RectangleSelectionGesture.RectangleSelectionChanging += RectangleSelectionChanging;
_RectangleSelectionGesture.RectangleSelectionStarted += RectangleSelectionStarted;
}
private void RectangleSelectionStarted(object sender, RecangleSelectionEventArgs e)
{
_TargetList.Focus();
}
private void RectangleSelectionChanging(object sender, RecangleSelectionEventArgs e)
{
_TargetList.SelectedItems.Clear();
VisualTreeHelper.HitTest(_TargetList, ItemHitTestFilter, ItemHitTestCallback, new GeometryHitTestParameters(new RectangleGeometry(e.SelectionRect)));
}
private HitTestFilterBehavior ItemHitTestFilter(DependencyObject visual)
{
if (visual is ListBoxItem)
{
((ListBoxItem)visual).IsSelected = true;
return HitTestFilterBehavior.ContinueSkipChildren;
}
return HitTestFilterBehavior.Continue;
}
private HitTestResultBehavior ItemHitTestCallback(HitTestResult result)
{
return HitTestResultBehavior.Continue;
}
}
ListBoxRectSelection通过使用RectangleSelectionGesture提供的矩形选择功能对ListBox进行点击测试,将命中的ListBoxItem的IsSelected属性设置为True,特别要注意的是要使用HitTestFilterCallback参数来获取命中测试的目标,而不是使用HitTestResultCallback参数。看上去HitTestResultCallback好像能满足我们的需求,但是实际结果并不能如我所愿,MSDN也并没有对HitTestResultCallback进行详尽的注释。VisualTreeHelper 的HitTest方法提供了多种命中测试方法,可以使用Point进行命中测试,也可以进行使用Geometry进行命中测试,对我们来说,使用Geometry参数进行的命中测试最符合我的需求。
在整个选择过程中,最重要的就是RectangleSelectionGesture 类了。该类定义如下:
public class RectangleSelectionGesture
{
#region Static Members
public static ComponentResourceKey SelectionVisualStyleKey { get; private set; }
private static readonly Style _DefaultSelectionVisualStyle;
static RectangleSelectionGesture()
{
_DefaultSelectionVisualStyle = new Style();
_DefaultSelectionVisualStyle.Setters.Add(new Setter(Shape.StrokeProperty, Brushes.Blue));
_DefaultSelectionVisualStyle.Setters.Add(new Setter(Shape.FillProperty, new SolidColorBrush(Color.FromArgb(66, 0, 0, 255))));
SelectionVisualStyleKey = new ComponentResourceKey(typeof(RectangleSelectionGesture), "SelectionVisualStyle");
}
#endregion
private FrameworkElement _TargetElement;
private RectangleGeometry _SelectionRectGeometry;
private Path _SelectionVisual;
private bool _Watching;
private Point _StartPoint;
private bool _IsEnabled;
private CustomAdorner _CustomAdorner;
private AdornerLayer _TargetAdornerLayer;
/// <summary>
/// Deffer to get AdornerLayer of _TargetElement.
/// </summary>
private AdornerLayer TargetAdornerLayer
{
get
{
if (_TargetAdornerLayer == null)
{
_TargetAdornerLayer = AdornerLayer.GetAdornerLayer(_TargetElement);
}
return _TargetAdornerLayer;
}
}
#region Rectangle Selection Events
public event EventHandler<RecangleSelectionEventArgs> RectangleSelectionStarted;
public event EventHandler<RecangleSelectionEventArgs> RectangleSelectionCompleted;
public event EventHandler<RecangleSelectionEventArgs> RectangleSelectionChanging;
protected void OnRectangleSelectionStarted(Rect rect)
{
if (RectangleSelectionStarted != null)
{
RectangleSelectionStarted(this, new RecangleSelectionEventArgs(rect));
}
}
protected void OnRectangleSelectionCompleted(Rect rect)
{
if (RectangleSelectionCompleted != null)
{
RectangleSelectionCompleted(this, new RecangleSelectionEventArgs(rect));
}
}
protected void OnRectangleSelectionChanging(Rect rect)
{
if (RectangleSelectionChanging != null)
{
RectangleSelectionChanging(this, new RecangleSelectionEventArgs(rect));
}
}
#endregion
#region Constructor
public RectangleSelectionGesture(FrameworkElement targetElement, Style selectionVisualStyle)
{
_TargetElement = targetElement;
IntializeSelectionVisual(selectionVisualStyle);
_CustomAdorner = new CustomAdorner(_TargetElement, _SelectionVisual);
}
public RectangleSelectionGesture(FrameworkElement targetElement)
: this(targetElement, null)
{ }
private void IntializeSelectionVisual(Style style)
{
_SelectionVisual = new Path();
if (style != null)
{
_SelectionVisual.Style = style;
}
else
{
style = _TargetElement.TryFindResource(SelectionVisualStyleKey) as Style;
if (style != null)
{
_SelectionVisual.Style = style;
}
else
{
_SelectionVisual.Style = _DefaultSelectionVisualStyle;
}
}
_SelectionRectGeometry = new RectangleGeometry(new Rect(0, 0, 0, 0));
_SelectionVisual.Data = _SelectionRectGeometry;
}
#endregion
private void TargetElementLostMouseCapture(object sender, MouseEventArgs e)
{
_Watching = false;
Adorner[] adorners = TargetAdornerLayer.GetAdorners(_TargetElement);
if (Array.IndexOf(adorners, _CustomAdorner) != -1)
{
TargetAdornerLayer.Remove(_CustomAdorner);
}
}
private void TargetElementMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
_Watching = true;
_StartPoint = e.GetPosition(_TargetElement);
}
private void TargetElementMouseMove(object sender, MouseEventArgs e)
{
if (_Watching)
{
Point curPos = e.GetPosition(_TargetElement);
_SelectionRectGeometry.Rect = new Rect(_StartPoint, curPos);
if (_TargetElement.IsMouseCaptured)
{
OnRectangleSelectionChanging(_SelectionRectGeometry.Rect);
}
else if (_SelectionRectGeometry.Rect.Width > SystemParameters.MinimumHorizontalDragDistance
|| _SelectionRectGeometry.Rect.Height > SystemParameters.MinimumVerticalDragDistance)
{
if (_TargetElement.CaptureMouse())
{
TargetAdornerLayer.Add(_CustomAdorner);
OnRectangleSelectionStarted(_SelectionRectGeometry.Rect);
}
}
}
}
private void TargetElementMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
_Watching = false;
if (_TargetElement.IsMouseCaptured)
{
_TargetElement.ReleaseMouseCapture();
Point curPos = e.GetPosition(_TargetElement);
Rect selectionRect = new Rect(_StartPoint, curPos);
OnRectangleSelectionCompleted(selectionRect);
TargetAdornerLayer.Remove(_CustomAdorner);
}
}
public bool IsEnabled
{
get
{
return _IsEnabled;
}
set
{
if (value != _IsEnabled)
{
_IsEnabled = value;
if (value)
{
_TargetElement.MouseLeftButtonDown += TargetElementMouseLeftButtonDown;
_TargetElement.MouseMove += TargetElementMouseMove;
_TargetElement.MouseLeftButtonUp += TargetElementMouseLeftButtonUp;
_TargetElement.LostMouseCapture += TargetElementLostMouseCapture;
}
else
{
_TargetElement.MouseLeftButtonDown -= TargetElementMouseLeftButtonDown;
_TargetElement.MouseMove -= TargetElementMouseMove;
_TargetElement.MouseLeftButtonUp -= TargetElementMouseLeftButtonUp;
_TargetElement.LostMouseCapture -= TargetElementLostMouseCapture;
}
}
}
}
}
该类的主要功能是使用CustomAdorner类为目标元素表面添加一个矩形元素,该元素能描述用户拖拽产生的选择区域 。该类提供了三个事件,如下:
public event EventHandler<RecangleSelectionEventArgs> RectangleSelectionStarted;
public event EventHandler<RecangleSelectionEventArgs> RectangleSelectionCompleted;
public event EventHandler<RecangleSelectionEventArgs> RectangleSelectionChanging;
三个事件用来通知矩形选择Gesture的开始,变化和结束。一般情况下,接听RectangleSelectionChanging 就能够满足实现矩形选择列表项的需求。类的客户可以从事件参数中得到当前选择矩形的信息,并做相应操作。矩形信息通过RecangleSelectionEventArgs 类传递,该类的定义如下:
public class RecangleSelectionEventArgs : EventArgs
{
public Rect SelectionRect { get; private set; }
public RecangleSelectionEventArgs(Rect selectionRect)
{
SelectionRect = selectionRect;
}
}
在用户在界面上进行矩形选择的时候, 为了使用户可以定制选择矩形的样式, 该类的构造函数接受一个Style参数,该参数实际上必须是针对Path类型的一个Style,用来样式化选择矩形(用Path表示)。如果传递null值,那么该类将会从目标界面元素中查找Key为SelectionVisualStyleKey
的资源并将其转化为Style对象,如果查找失败,那么将使用默认样式,默认样式有静态字段_DefaultSelectionVisualStyle 定义。
使用SelectionVisualStyleKey定义样式如下:
<Style TargetType="{x:Type Path}" x:Key="{x:Static local:RectangleSelectionGesture.SelectionVisualStyleKey}">
<Setter Property="Stroke" Value="Red"/>
<Setter Property="Fill" Value="#3300FF00"/>
</Style>
该样式必须定义在目标界面元素(ListView或者ListBox)或者其祖先的资源当中。
此外该类的IsEnabled属性用来订阅或取消订阅UI对象的鼠标事件。
由于RectangleSelectionGesture类要在VisualTree中添加一个矩形元素,所以最好的办法就是用Adorner在AdornerLayer中添加该矩形元素,这样就可以使添加的矩形元素不会影响目标UI对象的VisualTree,更不会依赖于它。关于Adorner的使用,在 Msdn中有比较详细的描述。本示例中使用了CustomAdorner作为矩形选择元素的宿主。该类允许将任何元素添加到AdornerLayer中,装饰元素将会被ContentPresenter类呈现到AdornerLayer中。该类的定义如下:
public class CustomAdorner : Adorner
{
public CustomAdorner(UIElement adornedElement, UIElement adornerElement)
: base(adornedElement)
{
_VisualChildren = new VisualCollection(this);
_Presenter = new ContentPresenter();
_Presenter.Content = adornerElement;
_VisualChildren.Add(_Presenter);
}
private VisualCollection _VisualChildren;
private ContentPresenter _Presenter;
protected override Visual GetVisualChild(int index)
{
return _VisualChildren[index];
}
protected override int VisualChildrenCount
{
get
{
return _VisualChildren.Count;
}
}
protected override Size ArrangeOverride(Size finalSize)
{
_Presenter.Arrange(new Rect(finalSize));
return _Presenter.RenderSize;
}
}
adornedElement需要传入被装饰的元素,adornerElement需要传入装饰元素,后者会使用前者的左上角作为自己的(0,0)坐标。使用VisualCollection 将_Presenter加入到集合中特别重要,VisualCollection会将集合内的元素自动添加到VisualTree中,这样才能保证UI元素正常的显示。
- 为ListView添加矩形选择功能
- 为JFileChooser添加选择文件验证功能
- 为JTable添加按列选择功能
- JqueryMobile为Listview动态添加、删除查询功能
- 为 JFileChooser 添加选择文件有效验证功能
- Android学习--为ListView添加按钮的相应事件,来处理不同的选择。
- 为ListView添加自动列宽调整和点击列表头自动排序功能
- 为ListView添加自动列宽调整和点击列表头自动排序功能
- 为ListView添加自动列宽调整和点击列表头自动排序功能
- C#为ListView添加自动列宽调整和点击列表头自动排序功能
- Android开发-UI控件:为ListView,GirdView,etc...添加系统自带的下拉刷新功能
- 使用v4包中的SwipeRefreshLayout为ListView和ScrollView添加下拉刷新功能
- 像使用插件一样为listview添加侧滑,上拉加载等功能
- 为listView添加自定义适配器
- 为ListView添加Ripple效果
- 为选择框添加事件
- 给选择屏幕添加功能按钮(以添加下载模板为例)--看完就能直接用了!
- ListView多项选择功能的实现
- 关于ECW文件格式读取
- 又要开始搞手机开发了,这次项目定在Mac OS和iPhone上
- Qt Class Reading, Monday
- Linux环境下的C/C+基础调试技术2——程序控制
- 调用exe文件
- 为ListView添加矩形选择功能
- string.h,signal.h,fcntl.h ,terrnios.h,timer.h,sigcontext.h,stat.h
- 尽量用“传引用”而不用“传值”
- JVM启动参数
- 解决MyEclipse吃内存,让MyEclipse飞起来,MyEclipse速度
- ORA-00990: 权限缺失或无效
- 25 Best Linux Commands 25个最好用的linux的命令
- 面试系列之——C#中的受管制的代码
- XML读取操作