SemanticZoom 是 Windows 8 獨有的導航控件,讓使用者在 「詳細視圖」 與 「概覽視圖」 之間以語義關係無縫切換。典型場景:長清單的字母分組導航、相簿的年份/月份縮覽、新聞分類的主題跳轉。本文將完整示範標準用法,並深入剖析如何透過實作 ISemanticZoomInformation 介面,讓任意控件(如 FlipView)成為可語義縮放的視圖。
| 屬性/事件 | 說明 |
|---|---|
ZoomedInView | 放大視圖:顯示詳細資料(如 GridView、ListView) |
ZoomedOutView | 縮小視圖:顯示概覽/導航項(如分組標題) |
IsZoomedInViewActive | 目前顯示哪個視圖 |
CanChangeViews | 是否允許切換 |
ToggleActiveView() | 程式碼切換視圖 |
ViewChangeStarted / ViewChangeCompleted | 視圖切換動畫事件 |
利用 CollectionViewSource 對資料分組,GridView 內建支援 ISemanticZoomInformation,兩視圖自動關聯。
class NavigationModel
{
public string Title { get; set; }
public string Url { get; set; }
public List<NavigationModel> Items { get; set; } // 子項集合
}
<SemanticZoom x:Name="semanticZoom" HorizontalAlignment="Left">
<!-- 詳細視圖:分組 GridView -->
<SemanticZoom.ZoomedInView>
<GridView x:Name="gridViewDetails"
SelectionMode="None" IsSwipeEnabled="false"
Padding="120 0 0 0">
<GridView.ItemTemplate>
<DataTemplate>
<Grid Background="#022a56" Width="200" Height="65"
Tapped="Grid_Tapped" Tag="{Binding}">
<TextBlock Text="{Binding Title}"
FontSize="14.667" Foreground="White"
Padding="5 0" VerticalAlignment="Center"/>
</Grid>
</DataTemplate>
</GridView.ItemTemplate>
<GridView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Title}"
FontSize="26.67" Height="30" Margin="0 0 0 20"/>
</DataTemplate>
</GroupStyle.HeaderTemplate>
<GroupStyle.ContainerStyle>
<Style TargetType="GroupItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="GroupItem">
<Border BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ContentPresenter Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Margin="{TemplateBinding Padding}"/>
<ItemsControl x:Name="ItemsControl"
ItemsSource="{Binding GroupItems}"
Grid.Row="1"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
<GroupStyle.Panel>
<ItemsPanelTemplate>
<VariableSizedWrapGrid Orientation="Vertical"
Margin="0 0 50 0"
ItemHeight="75"/>
</ItemsPanelTemplate>
</GroupStyle.Panel>
</GroupStyle>
</GridView.GroupStyle>
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</GridView.ItemsPanel>
</GridView>
</SemanticZoom.ZoomedInView>
<!-- 概覽視圖:分組標題 GridView -->
<SemanticZoom.ZoomedOutView>
<GridView x:Name="gridViewSummary"
HorizontalAlignment="Left" Padding="120 0 0 0">
<GridView.ItemTemplate>
<DataTemplate>
<Grid Background="#022a56" Width="100" Height="100">
<TextBlock Text="{Binding Group.Title}"
Foreground="White" Opacity="0.9"
FontSize="14.667" Margin="5"
TextAlignment="Center"
VerticalAlignment="Center"
HorizontalAlignment="Center"
TextWrapping="Wrap"/>
</Grid>
</DataTemplate>
</GridView.ItemTemplate>
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<WrapGrid MaximumRowsOrColumns="8"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Horizontal"/>
</ItemsPanelTemplate>
</GridView.ItemsPanel>
<GridView.ItemContainerStyle>
<Style TargetType="GridViewItem">
<Setter Property="Margin" Value="5 5 5 30"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
</Style>
</GridView.ItemContainerStyle>
</GridView>
</SemanticZoom.ZoomedOutView>
</SemanticZoom>
public sealed partial class Index : Page
{
private double _scrollViewerHorizontalOffset = 0;
public Index()
{
this.InitializeComponent();
LoadDataAndBind();
this.NavigationCacheMode = NavigationCacheMode.Required; // 必須緩存頁面
gridViewDetails.Loaded += gridViewDetails_Loaded;
}
private void LoadDataAndBind()
{
var root = XElement.Load("SiteMap.xml");
var items = LoadData(root); // 遞迴解析 XML 產生巢狀 NavigationModel
var groupedData = new CollectionViewSource
{
IsSourceGrouped = true,
Source = items,
ItemsPath = new PropertyPath("Items") // 指定子集合屬性
};
// 詳細視圖綁定完整分組檢視
gridViewDetails.ItemsSource = groupedData.View;
// 概覽視圖綁定分組集合
gridViewSummary.ItemsSource = groupedData.View.CollectionGroups;
}
private List<NavigationModel> LoadData(XElement root)
{
if (root == null) return null;
return (from n in root.Elements("node")
select new NavigationModel
{
Title = (string)n.Attribute("title"),
Url = (string)n.Attribute("url"),
Items = LoadData(n)
}).ToList();
}
// 導航離開前保存水平捲動位置
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
var sv = Helper.GetVisualChild<ScrollViewer>(gridViewDetails);
_scrollViewerHorizontalOffset = sv.HorizontalOffset;
}
// 導航返回時恢復捲動位置
void gridViewDetails_Loaded(object sender, RoutedEventArgs e)
{
var sv = Helper.GetVisualChild<ScrollViewer>(gridViewDetails);
sv.ScrollToHorizontalOffset(_scrollViewerHorizontalOffset);
}
// 項目點擊導航到對應 Demo 頁
private void Grid_Tapped(object sender, TappedRoutedEventArgs e)
{
var model = (NavigationModel)((FrameworkElement)sender).Tag;
MainPage.Current.SubTitle = model.Title;
MainPage.Current.Container.Navigate(Type.GetType(model.Url));
}
}
GridView/ListView 內建實作 ISemanticZoomInformation,但若想用 FlipView、ListBox 或自定義控件作為視圖,必須自行實作該介面。
| 方法 | 呼叫時機 | 核心任務 |
|---|---|---|
StartViewChangeTo(source, destination) | ZoomedOut → ZoomedIn 開始 | 從 source.Item 取得被選分組,將其子項指派給 destination.Item |
MakeVisible(item) | 視圖切換完成後 | 重設視圖資料源、捲動到對應項目 |
StartViewChangeFrom / CompleteViewChangeFrom | ZoomedIn → ZoomedOut | 通常無需特殊處理 |
InitializeViewChange / CompleteViewChange | 切換初始化/完成 | 通常留空 |
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Data;
namespace XamlDemo.Controls.SemanticZoom
{
public class MyFlipView : FlipView, ISemanticZoomInformation
{
public void CompleteViewChange() { }
public void CompleteViewChangeFrom(SemanticZoomLocation source, SemanticZoomLocation destination) { }
public void CompleteViewChangeTo(SemanticZoomLocation source, SemanticZoomLocation destination) { }
public void InitializeViewChange() { }
public bool IsActiveView { get; set; }
public bool IsZoomedInView { get; set; }
public SemanticZoom SemanticZoomOwner { get; set; }
public void StartViewChangeFrom(SemanticZoomLocation source, SemanticZoomLocation destination) { }
// 關鍵:ZoomedOutView 選中項 → 傳遞其子項給 ZoomedInView
public void StartViewChangeTo(SemanticZoomLocation source, SemanticZoomLocation destination)
{
// source.Item 為被選中的 NavigationModel(父項)
var model = source.Item as NavigationModel;
// 將父項的所有子項傳遞給 destination
destination.Item = model.Items;
}
// 切換完成後:重設 FlipView 的 ItemsSource
public void MakeVisible(SemanticZoomLocation item)
{
this.ItemsSource = item.Item; // item.Item 即為 model.Items
}
}
}
<SemanticZoom x:Name="semanticZoom" IsZoomedInViewActive="False" Margin="120 0 0 0">
<SemanticZoom.ZoomedInView>
<local:MyFlipView x:Name="flipView" Width="600" Height="300"
HorizontalAlignment="Left">
<FlipView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Title}" FontSize="26.667"/>
</DataTemplate>
</FlipView.ItemTemplate>
<FlipView.ItemContainerStyle>
<Style TargetType="FlipViewItem">
<Setter Property="Background" Value="Blue"/>
</Style>
</FlipView.ItemContainerStyle>
</local:MyFlipView>
</SemanticZoom.ZoomedInView>
<SemanticZoom.ZoomedOutView>
<GridView x:Name="gridView" HorizontalAlignment="Left">
<GridView.ItemTemplate>
<DataTemplate>
<Grid Background="#022a56" Width="100" Height="100">
<TextBlock Text="{Binding Title}"
Foreground="White" Opacity="0.9"
FontSize="14.667" Margin="5"
TextAlignment="Center"
VerticalAlignment="Center"
HorizontalAlignment="Center"
TextWrapping="Wrap"/>
</Grid>
</DataTemplate>
</GridView.ItemTemplate>
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<WrapGrid MaximumRowsOrColumns="8"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Horizontal"/>
</ItemsPanelTemplate>
</GridView.ItemsPanel>
<GridView.ItemContainerStyle>
<Style TargetType="GridViewItem">
<Setter Property="Margin" Value="5 5 5 30"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
</Style>
</GridView.ItemContainerStyle>
</GridView>
</SemanticZoom.ZoomedOutView>
</SemanticZoom>
public sealed partial class CustomView : Page
{
public CustomView()
{
this.InitializeComponent();
var root = XElement.Load("SiteMap.xml");
gridView.ItemsSource = LoadData(root); // 直接綁定巢狀資料
}
private List<NavigationModel> LoadData(XElement root)
{
if (root == null) return null;
return (from n in root.Elements("node")
select new NavigationModel
{
Title = (string)n.Attribute("title"),
Url = (string)n.Attribute("url"),
Items = LoadData(n)
}).ToList();
}
}
public class NavigationModel
{
public string Title { get; set; }
public string Url { get; set; }
public List<NavigationModel> Items { get; set; }
}
SemanticZoom 呼叫 ZoomedInView (MyFlipView).StartViewChangeTo(source, destination)MyFlipView 從 source.Item 取得被選 NavigationModel,將其 Items 子集合指派給 destination.ItemMyFlipView.MakeVisible(item)MyFlipView 將 ItemsSource = item.Item,顯示該分組下的所有子項SemanticZoom 以 語義關係 連結兩個視圖,核心在於:
CollectionViewSource + GridView/ListView 內建 ISemanticZoomInformation,零程式碼實現關聯切換ISemanticZoomInformation,重點在 StartViewChangeTo 傳遞資料與 MakeVisible 重設資料源Items 子集合),配合 ItemsPath 指定子屬性名稱掌握此機制,即可為任何集合控件賦予語義縮放能力,打造極具 Windows 風格的導航體驗。