背水一战 Windows 10 (52) - 控件(集合类): ItemsControl - 自定义 ItemsControl, 自定义 ContentPresenter
作者:webabcd
介绍
背水一战 Windows 10 之 控件(集合类 - ItemsControl)
- 自定义 ItemsControl(自定义 GirdView 使其每个 item 占用不同大小的空间)
- 自定义 ContentPresenter 实现类似 GridViewItemPresenter 和 ListViewItemPresenter 的效果
示例
1、自定义 ItemsControl(自定义 GirdView 使其每个 item 占用不同大小的空间)
Controls/CollectionControl/ItemsControlDemo/MyItemsControlDemo.xaml
<Page
x:Class="Windows10.Controls.CollectionControl.ItemsControlDemo.MyItemsControlDemo"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Windows10.Controls.CollectionControl.ItemsControlDemo"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"> <Page.Resources>
<DataTemplate x:Key="ItemTemplate">
<Grid Background="{Binding ColorValue}">
<Grid Background="Black" VerticalAlignment="Top" Opacity="0.7">
<TextBlock Text="{Binding ColorName}" />
</Grid>
</Grid>
</DataTemplate>
<Style x:Key="ItemContainerStyle" TargetType="GridViewItem">
<Setter Property="VerticalContentAlignment" Value="Stretch" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Margin" Value="0" />
<Setter Property="Padding" Value="0" />
</Style>
<ItemsPanelTemplate x:Key="ItemsPanel">
<VariableSizedWrapGrid MaximumRowsOrColumns="8" Orientation="Horizontal" ItemWidth="100" ItemHeight="100" />
</ItemsPanelTemplate> </Page.Resources> <Grid Background="Transparent" Margin="10 0 10 10">
<!--
使用 MyGridView 控件,其重写了 GridView 的 PrepareContainerForItemOverride() 方法,详见 MyGridView.cs
-->
<local:MyGridView x:Name="gridView" Width="812" VerticalAlignment="Top" HorizontalAlignment="Left"
ItemTemplate="{StaticResource ItemTemplate}"
ItemContainerStyle="{StaticResource ItemContainerStyle}"
ItemsPanel="{StaticResource ItemsPanel}"
IsItemClickEnabled="False"
SelectionMode="None">
</local:MyGridView>
</Grid>
</Page>
Controls/CollectionControl/ItemsControlDemo/MyItemsControlDemo.xaml.cs
/*
* ItemsControl - 集合控件(继承自 Control, 请参见 /Controls/BaseControl/ControlDemo/)
* protected virtual void PrepareContainerForItemOverride(DependencyObject element, object item); - 为 item 准备 container 时
* element - item 的 container
* item - item
*
*
* 本例用于演示如何使 GirdView 中的每个 item 占用不同大小的空间
* 1、布局控件要使用 VariableSizedWrapGrid(利用其 RowSpan 和 ColumnSpan 来实现 item 占用不同大小的空间),需要注意的是其并非是虚拟化布局控件
* 2、自定义 GridView,并重写 ItemsControl 的 protected override void PrepareContainerForItemOverride(DependencyObject element, object item) 方法
* 然后设置每个 item 的 VariableSizedWrapGrid.RowSpan 和 VariableSizedWrapGrid.ColumnSpan
*/ using System;
using System.Collections.Generic;
using System.Linq;
using Windows.UI;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
using System.Reflection; namespace Windows10.Controls.CollectionControl.ItemsControlDemo
{
public sealed partial class MyItemsControlDemo : Page
{
public MyItemsControlDemo()
{
this.InitializeComponent(); BindData();
} private void BindData()
{
Random random = new Random(); // 获取 Windows.UI.Colors 的全部数据
Type type = typeof(Colors);
List<ColorModel> colors = type.GetRuntimeProperties() // GetRuntimeProperties() 在 System.Reflection 命名空间下
.Select(c => new ColorModel
{
ColorName = c.Name,
ColorValue = new SolidColorBrush((Color)c.GetValue(null)),
ColSpan = random.Next(, ), // 此对象所占网格的列合并数
RowSpan = random.Next(, ) // 此对象所占网格的行合并数
})
.ToList(); // 绑定数据
gridView.ItemsSource = colors;
}
} /// <summary>
/// 用于数据绑定的对象
/// </summary>
public class ColorModel
{
public string ColorName { get; set; }
public SolidColorBrush ColorValue { get; set; } // 此对象所占的网格的列合并数
public int ColSpan { get; set; }
// 此对象所占的网格的行合并数
public int RowSpan { get; set; }
} /// <summary>
/// 自定义 GridView,重写 ItemsControl 的 protected override void PrepareContainerForItemOverride(DependencyObject element, object item) 方法
/// 用于指定 GridView 的每个 item 所占网格的列合并数和行合并数
/// </summary>
public class MyGridView : GridView
{
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
try
{
// 设置每个 item 的 VariableSizedWrapGrid.RowSpan 和 VariableSizedWrapGrid.ColumnSpan, 从而实现每个 item 占用不同大小的空间
// 仅为演示用,由于这里的 ColSpan 和 RowSpan 都是随机计算的,所以可能会出现空白空间 dynamic dynamicItem = item;
element.SetValue(VariableSizedWrapGrid.ColumnSpanProperty, dynamicItem.ColSpan);
element.SetValue(VariableSizedWrapGrid.RowSpanProperty, dynamicItem.RowSpan);
}
catch (Exception ex)
{
var ignore = ex; // 当有异常情况发生时(比如:item 没有 ColSpan 属性或 RowSpan 属性) element.SetValue(VariableSizedWrapGrid.ColumnSpanProperty, );
element.SetValue(VariableSizedWrapGrid.RowSpanProperty, );
}
finally
{
base.PrepareContainerForItemOverride(element, item);
}
}
}
}
2、自定义 ContentPresenter 实现类似 GridViewItemPresenter 和 ListViewItemPresenter 的效果
Controls/CollectionControl/ItemsControlDemo/MyItemPresenter.cs
/*
* 自定义 ContentPresenter 实现类似 GridViewItemPresenter 和 ListViewItemPresenter 的效果
*/ using System;
using Windows.Foundation;
using Windows.UI;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Animation;
using Windows.UI.Xaml.Shapes; namespace Windows10.Controls.CollectionControl.ItemsControlDemo
{
class MyItemPresenter : ContentPresenter
{
Panel _container = null; // item 的容器(即在 DataTemplate 中定义的根元素,在示例 MyItemPresenterDemo.xaml 中用的是 Grid)
Rectangle _pointerOverBorder = null; // 鼠标经过 item 时覆盖在 item 上的 rectangle
Rectangle _focusVisual = null; // 选中 item 时覆盖在 item 上的 rectangle Storyboard _pointerDownStoryboard = null; // 鼠标按下时的动画
Storyboard _pointerUpStoryboard = null; // 鼠标抬起时的动画 public MyItemPresenter()
: base()
{
base.Margin = new Thickness();
} // override OnApplyTemplate() - 应用控件模板时调用
protected override void OnApplyTemplate()
{
base.OnApplyTemplate(); _container = (Panel)VisualTreeHelper.GetChild(this, );
} // override GoToElementStateCore() - VisualState 转换时调用(此方法仅在自定义 ContentPresenter 并将其应用于 GridView 或 ListView 的 ItemContainerStyle 时才会被调用)
// stateName - VisualState 的名字
// useTransitions - 是否使用 VisualTransition 过渡效果
protected override bool GoToElementStateCore(string stateName, bool useTransitions)
{
base.GoToElementStateCore(stateName, useTransitions); switch (stateName)
{
// 正常状态
case "Normal":
HidePointerOverVisuals();
HideFocusVisuals();
if (useTransitions)
{
StopPointerDownAnimation();
}
break; // 选中状态
case "Selected":
case "PointerFocused":
ShowFocusVisuals();
if (useTransitions)
{
StopPointerDownAnimation();
}
break; // 取消选中状态
case "Unfocused":
HideFocusVisuals();
break; // 鼠标经过状态
case "PointerOver":
ShowPointerOverVisuals();
if (useTransitions)
{
StopPointerDownAnimation();
}
break; // 鼠标点击状态
case "Pressed":
case "PressedSelected":
if (useTransitions)
{
StartPointerDownAnimation();
}
break; default: break;
} return true;
} private void StartPointerDownAnimation()
{
if (_pointerDownStoryboard == null)
CreatePointerDownStoryboard(); _pointerDownStoryboard.Begin();
} private void StopPointerDownAnimation()
{
if (_pointerUpStoryboard == null)
CreatePointerUpStoryboard(); _pointerUpStoryboard.Begin();
} private void ShowFocusVisuals()
{
if (!FocusElementsAreCreated())
CreateFocusElements(); _focusVisual.Opacity = ;
} private void HideFocusVisuals()
{
if (FocusElementsAreCreated())
_focusVisual.Opacity = ;
} private void ShowPointerOverVisuals()
{
if (!PointerOverElementsAreCreated())
CreatePointerOverElements(); _pointerOverBorder.Opacity = ;
} private void HidePointerOverVisuals()
{
if (PointerOverElementsAreCreated())
_pointerOverBorder.Opacity = ;
} private void CreatePointerDownStoryboard()
{
/*
* 用这种方式为 item 实现鼠标按下的效果会报错(Attempted to read or write protected memory. This is often an indication that other memory is corrupt.),不知道为什么
* PointerDownThemeAnimation pointerDownAnimation = new PointerDownThemeAnimation();
* Storyboard.SetTarget(pointerDownAnimation, _container);
* Storyboard pointerDownStoryboard = new Storyboard();
* pointerDownStoryboard.Children.Add(pointerDownAnimation);
*/ DoubleAnimation da1 = new DoubleAnimation()
{
To = 0.9,
Duration = TimeSpan.FromMilliseconds()
};
DoubleAnimation da2 = new DoubleAnimation()
{
To = 0.9,
Duration = TimeSpan.FromMilliseconds()
};
Storyboard.SetTarget(da1, _container);
Storyboard.SetTargetProperty(da1, "(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)");
Storyboard.SetTarget(da2, _container);
Storyboard.SetTargetProperty(da2, "(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)");
if (!(_container.RenderTransform is TransformGroup))
{
TransformGroup Group = new TransformGroup();
Group.Children.Add(new ScaleTransform());
_container.RenderTransform = Group;
_container.RenderTransformOrigin = new Point(0.5, 0.5);
} _pointerDownStoryboard = new Storyboard();
_pointerDownStoryboard.Children.Add(da1);
_pointerDownStoryboard.Children.Add(da2);
_pointerDownStoryboard.Begin();
} private void CreatePointerUpStoryboard()
{
DoubleAnimation da1 = new DoubleAnimation()
{
To = ,
Duration = TimeSpan.FromMilliseconds()
};
DoubleAnimation da2 = new DoubleAnimation()
{
To = ,
Duration = TimeSpan.FromMilliseconds()
};
Storyboard.SetTarget(da1, _container);
Storyboard.SetTargetProperty(da1, "(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)");
Storyboard.SetTarget(da2, _container);
Storyboard.SetTargetProperty(da2, "(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)");
if (!(_container.RenderTransform is TransformGroup))
{
TransformGroup Group = new TransformGroup();
Group.Children.Add(new ScaleTransform());
_container.RenderTransform = Group;
_container.RenderTransformOrigin = new Point(0.5, 0.5);
} _pointerUpStoryboard = new Storyboard();
_pointerUpStoryboard.Children.Add(da1);
_pointerUpStoryboard.Children.Add(da2);
_pointerUpStoryboard.Begin();
} private void CreatePointerOverElements()
{
_pointerOverBorder = new Rectangle();
_pointerOverBorder.IsHitTestVisible = false;
_pointerOverBorder.Opacity = ;
// 这里把颜色写死了,仅为演示用,实际写的时候要摘出来写成依赖属性
_pointerOverBorder.Fill = new SolidColorBrush(Color.FromArgb(0x50, 0x50, 0x50, 0x50)); _container.Children.Insert(_container.Children.Count, _pointerOverBorder);
} private void CreateFocusElements()
{
_focusVisual = new Rectangle();
_focusVisual.IsHitTestVisible = false;
_focusVisual.Height = ;
_focusVisual.VerticalAlignment = VerticalAlignment.Bottom;
// 这里把颜色写死了,仅为演示用,实际写的时候要摘出来写成依赖属性
_focusVisual.Fill = new SolidColorBrush(Color.FromArgb(0xff, 0xff, 0x0, 0x0)); _container.Children.Insert(, _focusVisual);
} private bool FocusElementsAreCreated()
{
return _focusVisual != null;
} private bool PointerOverElementsAreCreated()
{
return _pointerOverBorder != null;
}
}
}
Controls/CollectionControl/ItemsControlDemo/MyItemPresenterDemo.xaml
<Page
x:Class="Windows10.Controls.CollectionControl.ItemsControlDemo.MyItemPresenterDemo"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Windows10.Controls.CollectionControl.ItemsControlDemo"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"> <Page.Resources>
<Style x:Key="MyGridViewItemPresenterTemplate" TargetType="GridViewItem">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="GridViewItem">
<!--
自定义 ContentPresenter 实现类似 GridViewItemPresenter 和 ListViewItemPresenter 的效果
-->
<local:MyItemPresenter />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Page.Resources> <Grid Background="Transparent">
<GridView x:Name="gridView" SelectionMode="Single" Margin="10 0 10 10"
ItemContainerStyle="{StaticResource MyGridViewItemPresenterTemplate}">
<GridView.ItemTemplate>
<DataTemplate>
<Grid Height="100" Width="100" Background="Blue">
<TextBlock x:Name="lblName" Text="{Binding Name}" Foreground="Yellow" />
</Grid>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
</Grid>
</Page>
Controls/CollectionControl/ItemsControlDemo/MyItemPresenterDemo.xaml.cs
/*
* 本例用于演示如何自定义 ContentPresenter 实现类似 GridViewItemPresenter 和 ListViewItemPresenter 的效果
*/ using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
using Windows10.Common; namespace Windows10.Controls.CollectionControl.ItemsControlDemo
{
public sealed partial class MyItemPresenterDemo : Page
{
public MyItemPresenterDemo()
{
this.InitializeComponent();
} protected override void OnNavigatedTo(NavigationEventArgs e)
{
gridView.ItemsSource = TestData.GetEmployees();
}
}
}
OK
[源码下载]