GridView 作為 WinRT 最強大的集合控件之一,內建支援 項目拖曳重新排序、拖出刪除、可變項目尺寸、資料分組顯示 等進階功能。本文將透過三個完整範例,逐一解析這些特性的實作細節與關鍵 API。
核心屬性:
CanDragItems="True":允許項目被拖曳CanReorderItems="True":允許在 GridView 內透過拖曳排序AllowDrop="True":允許項目被放置(GridView 內或外部容器)DragItemsStarting 事件:拖曳開始時取得被拖曳項目<Page.Resources>
<DataTemplate x:Key="ItemTemplate">
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Name}" FontSize="14.667" HorizontalAlignment="Left"/>
<TextBlock Text="{Binding Age}" FontSize="14.667" HorizontalAlignment="Left"/>
</StackPanel>
</DataTemplate>
<Style x:Key="ItemContainerStyle" TargetType="GridViewItem">
<Setter Property="Width" Value="292"/>
<Setter Property="Height" Value="80"/>
<Setter Property="Margin" Value="0"/> <!-- 無法完全移除間距,需設 -4 -->
<Setter Property="Background" Value="Blue"/>
</Style>
<ItemsPanelTemplate x:Key="ItemsPanel">
<WrapGrid MaximumRowsOrColumns="3" Orientation="Vertical"
VerticalChildrenAlignment="Top" HorizontalChildrenAlignment="Left"/>
</ItemsPanelTemplate>
</Page.Resources>
<Grid Background="Transparent">
<StackPanel Margin="120 0 0 0">
<TextBlock x:Name="lblMsg" FontSize="14.667"
Text="透過拖曳 GridView 中的 Item 進行排序"/>
<GridView x:Name="gridView"
VerticalAlignment="Top" Margin="0 10 10 0"
BorderThickness="1" BorderBrush="Red" Background="LightBlue"
ItemTemplate="{StaticResource ItemTemplate}"
ItemContainerStyle="{StaticResource ItemContainerStyle}"
ItemsPanel="{StaticResource ItemsPanel}"
IsSwipeEnabled="True" IsItemClickEnabled="True"
CanDragItems="True" CanReorderItems="True" AllowDrop="True"
DragItemsStarting="gridView_DragItemsStarting"/>
<!-- 拖曳到此區域刪除項目 -->
<Grid x:Name="gridDelete" Margin="0 10 0 0"
AllowDrop="True"
Drop="gridDelete_Drop"
DragEnter="gridDelete_DragEnter"
DragLeave="gridDelete_DragLeave"
DragOver="gridDelete_DragOver">
<Rectangle Width="300" Height="100"
StrokeThickness="1" StrokeDashArray="2"
Stroke="Red" Fill="Blue"/>
<TextBlock FontSize="26.667" Text="拖曳到此處以刪除"
TextAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</StackPanel>
</Grid>
public sealed partial class DragItem : Page
{
private ObservableCollection<Employee> _dataSource;
private Employee _draggingEmployee;
public DragItem()
{
this.InitializeComponent();
_dataSource = new ObservableCollection<Employee>(TestData.GetEmployees());
gridView.ItemsSource = _dataSource;
// 監聽 ItemsChanged 以在 Drop 後更新資料源順序
gridView.ItemContainerGenerator.ItemsChanged += ItemContainerGenerator_ItemsChanged;
}
void ItemContainerGenerator_ItemsChanged(object sender, ItemsChangedEventArgs e)
{
if (e.OldPosition.Index > -1)
{
var oldIndex = _dataSource.IndexOf(_draggingEmployee);
var newIndex = e.Position.Index + e.Position.Offset;
_dataSource.Move(oldIndex, newIndex); // ObservableCollection 擴充方法
_draggingEmployee = null;
}
}
private void gridView_DragItemsStarting(object sender, DragItemsStartingEventArgs e)
{
_draggingEmployee = e.Items.First() as Employee;
// e.Data 可設定 DataPackage 跨應用拖曳,本例用欄位簡化
}
private void gridDelete_Drop(object sender, DragEventArgs e)
{
_dataSource.Remove(_draggingEmployee);
_draggingEmployee = null;
Debug.WriteLine("Drop");
}
private void gridDelete_DragEnter(object sender, DragEventArgs e) => Debug.WriteLine("DragEnter");
private void gridDelete_DragLeave(object sender, DragEventArgs e) => Debug.WriteLine("DragLeave");
private void gridDelete_DragOver(object sender, DragEventArgs e) => Debug.WriteLine("DragOver");
}
DragItemsStarting 取得被拖曳物件,暫存於欄位ItemContainerGenerator.ItemsChanged 事件在 Drop 完成後觸發,透過 e.Position.Index + e.Position.Offset 計算新索引AllowDrop="True" 並處理 Drop 事件移除資料源項目DataPackage 可用於跨應用拖曳/剪貼簿/Share Contract,本例為簡化使用私有欄位傳遞預設 GridView 項目等寬等高。若需每個項目佔據不同列/行合併數,必須:
VariableSizedWrapGrid 作為 ItemsPanelGridView 並覆寫 PrepareContainerForItemOverride,依資料物件屬性設定 VariableSizedWrapGrid.ColumnSpanProperty / RowSpanPropertyclass ColorModel
{
public string ColorName { get; set; }
public SolidColorBrush ColorValue { get; set; }
public int ColSpan { get; set; } // 列合併數
public int RowSpan { get; set; } // 行合併數
}
public class MyGridView : Windows.UI.Xaml.Controls.GridView
{
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
try
{
dynamic d = item;
element.SetValue(VariableSizedWrapGrid.ColumnSpanProperty, d.ColSpan);
element.SetValue(VariableSizedWrapGrid.RowSpanProperty, d.RowSpan);
}
catch (Exception ex)
{
Debug.WriteLine(ex);
element.SetValue(VariableSizedWrapGrid.ColumnSpanProperty, 1);
element.SetValue(VariableSizedWrapGrid.RowSpanProperty, 1);
}
finally
{
base.PrepareContainerForItemOverride(element, item);
}
}
}
<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="-4"/> <!-- 移除項目間距 -->
</Style>
<ItemsPanelTemplate x:Key="ItemsPanel">
<VariableSizedWrapGrid MaximumRowsOrColumns="4"
Orientation="Vertical"
ItemWidth="100" ItemHeight="100"
Height="400"/>
</ItemsPanelTemplate>
</Page.Resources>
<local:MyGridView x:Name="gridView" Height="400"
Background="Yellow"
ItemTemplate="{StaticResource ItemTemplate}"
ItemContainerStyle="{StaticResource ItemContainerStyle}"
ItemsPanel="{StaticResource ItemsPanel}"
IsItemClickEnabled="False" IsSwipeEnabled="False"
SelectionMode="None"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollBarVisibility="Auto"/>
private void BindData()
{
var random = new Random();
var colors = typeof(Colors)
.GetRuntimeProperties()
.Select(c => new ColorModel
{
ColorName = c.Name,
ColorValue = new SolidColorBrush((Color)c.GetValue(null)),
ColSpan = random.Next(1, 3),
RowSpan = random.Next(1, 3)
}).ToList();
gridView.ItemsSource = colors;
}
VariableSizedWrapGrid 不繼承 VirtualizingPanel,大量資料時無虛擬化,效能較 WrapGrid 差ItemWidth/ItemHeight 定義基礎網格尺寸,ColumnSpan/RowSpan 為倍數HorizontalContentAlignment=Stretch、VerticalContentAlignment=Stretch 才能填滿合併區域分組功能源自 ItemsControl 基類,透過 CollectionViewSource.IsSourceGrouped=true、ItemsPath 指定子集合屬性,GridView.GroupStyle 定義分組標題、容器、面板。完整範例請參考本系列 第 13 篇 SemanticZoom 的 Index.xaml 與 Index.xaml.cs。
GridView 進階三大特性:
CanDragItems、CanReorderItems、AllowDrop、DragItemsStarting、ItemContainerGenerator.ItemsChangedVariableSizedWrapGrid + 覆寫 PrepareContainerForItemOverride 設定 ColumnSpan/RowSpanCollectionViewSource + GroupStyle(HeaderTemplate、ContainerStyle、Panel)善用這些特性,可打造極具互動性與視覺豐富度的集合介面。