[UWP]了解模板化控件(8):ItemsControl
原文:[UWP]了解模板化控件(8):ItemsControl

1. 模仿ItemsControl

顾名思义,ItemsControl是展示一组数据的控件,它是UWP UI系统中最重要的控件之一,和展示单一数据的ContentControl构成了UWP UI的绝大部分,ComboBox,ListBox,ListView,FlipView,GridView等控件都继承自ItemsControl。曾经有个说法:了解ContentControl和ItemsControl才能算是了解WPF的控件,这一点在UWP中也是一样的。

以我的经验来说,通过继承ItemsControl来自定义模板化控件十分常见,了解ItemsControl对将来要自定义模板化控件十分有用。但ItemsControl的话题十分庞大,和ContentControl不同,不太适合在这里展开讨论,所以这里就只是稍微讨论核心的思想。

虽然ItemsControl及其派生类很复杂,但核心功能很简单,所以索性自己实现一次。这次用于讨论的SimpleItemsControl直接继承自Control,简单地模仿ItemsControl实现了它基本的功能,通过这个控件可以一窥ItemsControl的原理。在XAML中使用如下,基本上和ItemsControl一样:

<StackPanel Margin="20" HorizontalAlignment="Center"><local:SimpleItemsControl><ContentPresenter Content="this is ContentPresenter" /><Rectangle  Height="50"
                    HorizontalAlignment="Stretch"
                    Fill="Red" /><local:ScoreModel /></local:SimpleItemsControl><local:SimpleItemsControl Margin="0,20,0,0"><local:SimpleItemsControl.ItemTemplate><DataTemplate><TextBlock Text="{Binding Score}" /></DataTemplate></local:SimpleItemsControl.ItemTemplate><local:ScoreModel Score="70" /><local:ScoreModel Score="80" /><local:ScoreModel Score="90" /><local:ScoreModel Score="100" /></local:SimpleItemsControl>
</StackPanel>

SimpleItemsControl除了没有ItemsSource、ItemsPanelTemplate及虚拟化等功能等功能外,拥有ItemsControl基本的功能。

1.1 Items属性

public ICollection<object> Items{get;}

实现这个控件首要的是提供Items属性,Items在构造函数中实例化成ObservableCollection类型,并且订阅它的CollectionChanged事件。注意:TemplatedControl中的集合属性通常都被可以被实例化成O巴塞尔,以便监视事件。

var items = new ObservableCollection<object>();
items.CollectionChanged += OnItemsCollectionChanged;
Items = items;

当然,为了可以在XAML的子节点直接添加元素,别忘了使用ContentPropertyAttribute。

[ContentProperty(Name = "Items")]

1.2 ItemsPanel

在ItemsControl中,ControlTemplate包含一个ItemsPresenter,它根据ItemsControl的ItemsPanelTemplate生成一个Panel,并且把Items中各个元素放入这个Panel。

SimpleItemsControl由于不是继承自ItemsControl,所以直接在ControlTemplate中放一个StackPanel代替。

_itemsPanel = GetTemplateChild(ItemsPanelPartName) as Panel;


<Style TargetType="local:SimpleItemsControl"><Setter Property="Template"><Setter.Value><ControlTemplate TargetType="local:SimpleItemsControl"><StackPanel Background="{TemplateBinding Background}"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}"><StackPanel x:Name="ItemsPanel" /></StackPanel></ControlTemplate></Setter.Value></Setter>
</Style>

ControlTemplate中只需要一个用于承载Items的ItemsPanel。在这个例子中使用StackPanel。

1.3 ItemTemplate属性

接下来需要提供public DataTemplate ItemTemplate { get; set; }属性,它定义了Items中每一项数据如何显示。事实上Items中每一项通常都默认使用ContentControl或ContentPresenter显示(譬如ListBoxItem和ComboxItem),所以ItemTemplate相当于它们的ContentTemplate。熟悉ContentControl的话会更容易理解这个属性。

1.4 GetContainerForItemOverride

//
// 摘要:
//     创建或标识用于显示给定项的元素。
//
// 返回结果:
//     用于显示给定项的元素。
protected virtual DependencyObject GetContainerForItemOverride()
{return new ContentPresenter();
}

ItemsControl使用GetContainerForItemOverride函数为Items中每一个item创建它的容器用于在UI上显示,默认是ContentPresenter。对于不是派生自UIElement的Item,它们无法直接在UI上显示,所以Container是必须的。

1.5 IsItemItsOwnContainerOverride

//
// 摘要:
//     确定指定项是否是为自身的容器,或是否可以作为其自身的容器。
//
// 参数:
//   item:
//     要检查的项。
//
// 返回结果:
//     如果项是其自己的容器(或可以作为自己的容器),则为 true;否则为 false。
protected virtual System.Boolean IsItemItsOwnContainerOverride(System.Object item)
{return item is ContentPresenter;
}

对于Items中的每一个item,ItemsControl在为它创建容器前都用这个方法检查它是不是就是容器本身。譬如这段XAML:

<local:SimpleItemsControl><ContentPresenter Content="this is ContentPresenter" /><Rectangle  Height="50"
                Width="200"
                Fill="Red" /><local:ScoreModel />
</local:SimpleItemsControl>

在这段XAML中,ContentPresenter本身就是容器,所以它将直接被放到ItemsPanel中;Rectangle 不是容器,需要创建一个ContentPresenter,将Rectangle 设置为这个ContentPresenter的Content再放到ItemsPanel中。

1.6 PrepareContainerForItemOverride

//
// 摘要:
//     准备指定元素以显示指定项。
//
// 参数:
//   element:
//     用于显示指定项的元素。
//
//   item:
//     要显示的项。
protected virtual void PrepareContainerForItemOverride(DependencyObject element, System.Object item)
{ContentControl contentControl;ContentPresenter contentPresenter;if ((contentControl = element as ContentControl) != null){contentControl.Content = item;contentControl.ContentTemplate = ItemTemplate;}else if ((contentPresenter = element as ContentPresenter) != null){contentPresenter.Content = item;contentPresenter.ContentTemplate = ItemTemplate;}
}

这个方法在Item被呈现到UI前调用,目标是设定ContainerForItem中的某些值,譬如Content及ContentTemplate。其中参数element即之前创建的ContainerForItem(也有可能是Item自己)。在调用这个函数后ContainerForItem将被放到ItemsPanel中。

1.7 UpdateView

private void UpdateView()
{if (_itemsPanel == null)return;_itemsPanel.Children.Clear();foreach (var item in Items){DependencyObject container;if (IsItemItsOwnContainerOverride(item)){container = item as DependencyObject;}else{container = GetContainerForItemOverride();PrepareContainerForItemOverride(container, item);}if (container is UIElement)_itemsPanel.Children.Add(container as UIElement);}
}

这个函数在OnItemsCollectionChanged或OnApplyTemplate后调用,简单地将ItemsPanel.Children清空,然后将所有Item创建容器(或者不创建)然后放进ItemsPanel。实际上ItemsControl的逻辑要复杂很多,这里只是个极端简化的版本。

到这一步一个简单的ItemsControl就完成了,总共只有100多行代码。

看到这里可能会有个疑惑,GetContainerForItemOverride、IsItemItsOwnContainerOverride、PrepareContainerForItemOverride三个函数明明做的是同一件事(为Item创建Container),为什么要将它们分开?这是因为ItemsControl支持使用UI虚拟化技术。

假设Items中包含一万个项,为这一万个项创建容器并放到ItemsPanel上,将会造成巨大的内存消耗。而且拖动ItemsControl的滚动条时由于要将所有一万个容器同时移动,对CPU造成很大的负担。UI虚拟化就是为了解决这两个问题。通常一个ItemsControl能同时显示的Item最多几十个,ItemsControl就只是创建几十个容器,在拖动滚动条时回收移出可视范围的容器,更改容器的内容(因为容器通常是ContentControl,所以就是更改ContentControl.Content),再重新放到可视范围里面。为了实现这个技术,Item和它的Container就不能是一一对应的,所以才会把上述的三个函数分离。

注意: UWP中ItemsControl默认没有启用UI虚拟化,但它的派生类有。

1.8 完整的代码

[TemplatePart(Name = ItemsPanelPartName, Type = typeof(Panel))]
[ContentProperty(Name = "Items")]
public class SimpleItemsControl : Control
{private const string ItemsPanelPartName = "ItemsPanel";public SimpleItemsControl(){this.DefaultStyleKey = typeof(SimpleItemsControl);var items = new ObservableCollection<object>();items.CollectionChanged += OnItemsCollectionChanged;Items = items;}/// <summary>/// 获取或设置ItemTemplate的值/// </summary>  public DataTemplate ItemTemplate{get { return (DataTemplate)GetValue(ItemTemplateProperty); }set { SetValue(ItemTemplateProperty, value); }}/// <summary>/// 标识 ItemTemplate 依赖属性。/// </summary>public static readonly DependencyProperty ItemTemplateProperty =DependencyProperty.Register("ItemTemplate", typeof(DataTemplate), typeof(SimpleItemsControl), new PropertyMetadata(null, OnItemTemplateChanged));private static void OnItemTemplateChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args){SimpleItemsControl target = obj as SimpleItemsControl;DataTemplate oldValue = (DataTemplate)args.OldValue;DataTemplate newValue = (DataTemplate)args.NewValue;if (oldValue != newValue)target.OnItemTemplateChanged(oldValue, newValue);}protected virtual void OnItemTemplateChanged(DataTemplate oldValue, DataTemplate newValue){UpdateView();}public ICollection<object> Items{get;}private Panel _itemsPanel;protected override void OnApplyTemplate(){base.OnApplyTemplate();_itemsPanel = GetTemplateChild(ItemsPanelPartName) as Panel;UpdateView();}private void OnItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e){UpdateView();}//// 摘要://     创建或标识用于显示给定项的元素。//// 返回结果://     用于显示给定项的元素。protected virtual DependencyObject GetContainerForItemOverride(){return new ContentPresenter();}//// 摘要://     确定指定项是否是为自身的容器,或是否可以作为其自身的容器。//// 参数://   item://     要检查的项。//// 返回结果://     如果项是其自己的容器(或可以作为自己的容器),则为 true;否则为 false。protected virtual System.Boolean IsItemItsOwnContainerOverride(System.Object item){return item is ContentPresenter;}//// 摘要://     准备指定元素以显示指定项。//// 参数://   element://     用于显示指定项的元素。////   item://     要显示的项。protected virtual void PrepareContainerForItemOverride(DependencyObject element, System.Object item){ContentControl contentControl;ContentPresenter contentPresenter;if ((contentControl = element as ContentControl) != null){contentControl.Content = item;contentControl.ContentTemplate = ItemTemplate;}else if ((contentPresenter = element as ContentPresenter) != null){contentPresenter.Content = item;contentPresenter.ContentTemplate = ItemTemplate;}}private void UpdateView(){if (_itemsPanel == null)return;_itemsPanel.Children.Clear();foreach (var item in Items){DependencyObject container;if (IsItemItsOwnContainerOverride(item)){container = item as DependencyObject;}else{container = GetContainerForItemOverride();PrepareContainerForItemOverride(container, item);}if (container is UIElement)_itemsPanel.Children.Add(container as UIElement);}}
}

2. 扩展ItemsControl

了解过ItemsControl的原理,或通过继承ItemsControl自定义控件就很简单了。譬如要实现这个功能:一个事件列表,自动为事件添加上触发的时间。效果如下:

通过重载GetContainerForItemOverride、IsItemItsOwnContainerOverride、PrepareContainerForItemOverride这三个函数,很简单就能实现这个需求:

public class EventListView : ListView
{public EventListView(){_items = new Dictionary<object, DateTime>();}private Dictionary<object, DateTime> _items;protected override DependencyObject GetContainerForItemOverride(){return new HeaderedContentControl();}protected override bool IsItemItsOwnContainerOverride(object item){return item is HeaderedContentControl;}protected override void PrepareContainerForItemOverride(DependencyObject element, object item){base.PrepareContainerForItemOverride(element, item);var control = element as HeaderedContentControl;control.Content = item;if (_items.ContainsKey(item)){var time = _items[item];control.Header = time.ToString("HH:mm:ss")+": ";}}protected override void OnItemsChanged(object e){base.OnItemsChanged(e);foreach (var item in Items){if (_items.ContainsKey(item) == false)_items.Add(item, DateTime.Now);}}
}

public sealed class EventListViewItem : ListViewItem
{public EventListViewItem(){this.DefaultStyleKey = typeof(EventListViewItem);}public object Header{get { return (object)GetValue(HeaderProperty); }set { SetValue(HeaderProperty, value); }}// Using a DependencyProperty as the backing store for Header.  This enables animation, styling, binding, etc...public static readonly DependencyProperty HeaderProperty =DependencyProperty.Register("Header", typeof(object), typeof(EventListViewItem), new PropertyMetadata(null));}

3. 集合类型属性

在XAML中使用集合类型属性,通常不会这样:

<ItemsControl><ItemsControl.Items><ItemCollection><local:ScoreModel Score="70" /><local:ScoreModel Score="80" /><local:ScoreModel Score="90" /><local:ScoreModel Score="100" /></ItemCollection></ItemsControl.Items>
</ItemsControl>

而是这样:

<ItemsControl><ItemsControl.Items><local:ScoreModel Score="70" /><local:ScoreModel Score="80" /><local:ScoreModel Score="90" /><local:ScoreModel Score="100" /></ItemsControl.Items>
</ItemsControl>

因为集合类型属性通常定义为只读的,不必也不可以对它赋值,只可以向它添加内容。

控件中的集合属性一般遵循以下做法:

3.1 只读属性

public IList<HubSection> Sections { get; }

这是Hub的Section属性,模板化控件中的集合类型属性基本都定义成这样的CLR属性。

3.2 监视更改通知

如果需要监视集合项更改,可以将属性定义为继承INotifyCollectionChanged 自的集合类型,譬如 ObservableCollection。

3.3 不使用依赖属性

因为集合属性通常不会使用动画,或者通过Style中的Setter赋值,而且依赖属性标识符是静态的,集合属性的初始值有可能引起单例的问题。集合属性通常在构造函数中初始化。

3.4 绑定到集合属性

通常不会绑定到集合属性,更常见的做法是如ItemsControl那样,绑定到ItemsSource。

posted on 2018-01-25 23:48 NET未来之路 阅读(...) 评论(...) 编辑 收藏

转载于:https://www.cnblogs.com/lonelyxmas/p/8353745.html

[UWP]了解模板化控件(8):ItemsControl相关推荐

  1. [UWP]了解模板化控件(7):支持Command

    原文:[UWP]了解模板化控件(7):支持Command 以我的经验来说,要让TemplatedControl支持Command的需求不会很多,大部分情况用附加属性解决这个需求会更便利些,譬如UWPC ...

  2. [UWP]了解模板化控件(4):TemplatePart

    原文:[UWP]了解模板化控件(4):TemplatePart 1. TemplatePart TemplatePart(部件)是指ControlTemplate中的命名元素.控件逻辑预期这些部分存在 ...

  3. [UWP]了解模板化控件(6):使用附加属性

    原文:[UWP]了解模板化控件(6):使用附加属性 1. 基本需求 之前的ContentView2添加了PointerOver等效果,和TextBox等本来就有Header的控件放在一起反而变得鹤立鸡 ...

  4. [UWP]了解模板化控件(9):UI指南

    原文:[UWP]了解模板化控件(9):UI指南 1. 使用TemplateSettings统一外观 TemplateSettings提供一组只读属性,用于在新建ControlTemplate时使用这些 ...

  5. [UWP]了解模板化控件(5):VisualState

    1. 功能需求 使用TemplatePart实现上篇文章的两个需求(Header为空时隐藏HeaderContentPresenter,鼠标没有放在控件上时HeaderContentPresent半透 ...

  6. UWP Composition API - RadialMenu

    原文:UWP Composition API - RadialMenu 用Windows 8.1的童鞋应该知道OneNote里面有一个RadialMenu.如下图,下图是WIn10应用Drawboar ...

  7. [UWP]做个调皮的BusyIndicator

    1. 前言 最近突然想要个BusyIndicator.做过WPF开发的程序员对BusyIndicator应该不陌生,Extended WPF Toolkit 提供了BusyIndicator的开源实现 ...

  8. [UWP]创建一个ProgressControl

    原文:[UWP]创建一个ProgressControl 1. 前言 博客园终于新增了UWP的分类,我来为这个分类贡献第一篇博客吧. UWP有很多问题,先不说生态的事情,表单.验证.输入.设计等等一堆基 ...

  9. UWP 颜色选择器(ColorPicker) 和 自定义的Flyout(AdvancedFlyout)

    原文:UWP 颜色选择器(ColorPicker) 和 自定义的Flyout(AdvancedFlyout) ColorPicker 故事背景 项目里面需要一个像Winfrom里面那样的颜色选择器,如 ...

  10. 如何将GridViewEX升级到UWP(Universal Windows Platform)平台

    引言 上一篇文章中,我们主要讲解了如何在保证GridView控件的用户体验基础上,扩展GridView生成GridViewEx控件,增加动态添加新分组功能等,本文在上文的基础上,介绍如何在Window ...

最新文章

  1. oracle的to_char中的fm
  2. 抛物线运动JavaScript实现
  3. Python处理小学体育中的跑步计时数据并统计得分
  4. Angular应用里的tsconfig.app.json
  5. 【CodeForces - 27E】Number With The Given Amount Of Divisors (数论,数学,反素数)
  6. 【华为云技术分享】MongoDB经典故障系列四:调整oplog大小,引起从库宕机怎么办?
  7. 网易严选退出双十一:“抵制”鼓吹过度消费
  8. Node.js路径操作
  9. matlab最小费用最大流函数,Matlab最小费用最大流算法通用程序
  10. FFmpeg源代码简单分析:avcodec_open2()
  11. excel表中怎么插入visio_Microsoft Visio2007中vsdx文件怎么打开|Visio插入表格方法
  12. python自动排版公众号_GitHub - qfwc258/maxpress: MaxPress:MarkDown+Python实现微信公众号一键排版...
  13. 一览数据异步加载的解决方案
  14. gulp简单入坑介绍
  15. lol黑屏显示服务器,LOL英雄联盟进去开始就黑屏,英雄服务器中断进不去
  16. OpenCV C++案例实战六《绿幕视频背景替换》
  17. [cesium] | 视频融合 | 自定义视频投放
  18. 如何控制企业成本?控制成本有什么作用!
  19. Hgame-Week3
  20. 黑白照片修复彩色可以用什么软件?这些工具就可以

热门文章

  1. C++中algorithm头文件中一些函数使用记录
  2. 如何用cv2.imread()读取falsk接收的图片
  3. TypeError: can‘t convert CUDA tensor to numpy. Use Tensor.cpu() to copy the tensor to host memory fi
  4. Centos7下基于Pseudo-Distributed的Hadoop环境搭建
  5. python 基于滑动平均思想实现缺失数据填充
  6. 纪念学海生涯的最后一次盲审抽签
  7. 【OpenCV入门指南】第一篇 安装OpenCV
  8. 2021年各省高考试成绩查询,2021年各省高考成绩查询时间 什么时候出分
  9. c语言标识符等价类测试,c语言基础测试题--牧码南山招新检测题
  10. word页码怎么从第三页开始设置为第一页_如何让页码从指定页开始,而不是第一页?...