ScrollViewer 是所有捲動容器的基石(ListView、GridView、TextBox、RichEditBox 內部皆內建)。掌握其核心屬性、內部元件(ScrollBar、ScrollContentPresenter)與程式碼控制捲動,是開發複雜捲動介面的關鍵。本文將從基礎應用切入,層層深入。
| 屬性 | 說明 |
|---|---|
Content | 捲動內容(單一子元素) |
IsDeferredScrollingEnabled | 延遲捲動:大量內容時拖曳捲動條不即時重繪,提升效能(預設 false) |
HorizontalScrollMode / VerticalScrollMode | Disabled/Enabled/Auto:捲動條行為模式 |
HorizontalScrollBarVisibility / VerticalScrollBarVisibility | Disabled/Auto/Hidden/Visible:捲動條可見性 |
ViewChanged | 捲動發生時觸發(含慣性、程式碼捲動) |
| 屬性 | 含義 |
|---|---|
ComputedHorizontalScrollBarVisibility | 實際水平捲動條可見性(Auto 時動態判斷) |
ComputedVerticalScrollBarVisibility | 實際垂直捲動條可見性 |
ExtentWidth / ExtentHeight | 內容總寬/高 |
ViewportWidth / ViewportHeight | 可視區寬/高 |
HorizontalOffset / VerticalOffset | 目前捲動位移 |
ScrollableWidth / ScrollableHeight | 可捲動範圍 = Extent - Viewport |
ScrollToHorizontalOffset(double offset)ScrollToVerticalOffset(double offset)<Page x:Class="XamlDemo.Controls.ScrollViewer.Demo" ...>
<Grid Background="Transparent">
<StackPanel Margin="120 0 0 0">
<TextBlock x:Name="lblMsg"/>
<ScrollViewer x:Name="scrollViewer" Width="400" Height="400"
Margin="0 10 0 0" HorizontalAlignment="Left"
IsDeferredScrollingEnabled="False"
ViewChanged="scrollViewer_ViewChanged"
HorizontalScrollMode="Enabled" VerticalScrollMode="Enabled"
HorizontalScrollBarVisibility="Visible"
VerticalScrollBarVisibility="Visible">
<ScrollViewer.Content>
<Image Source="/Assets/Logo.png" Width="1000"/>
</ScrollViewer.Content>
</ScrollViewer>
<StackPanel Orientation="Horizontal">
<Button Content="居中" Click="Button_Click"/>
</StackPanel>
</StackPanel>
</Grid>
</Page>
public sealed partial class Demo : Page
{
public Demo() { this.InitializeComponent(); }
private void scrollViewer_ViewChanged(object sender, ScrollViewerViewChangedEventArgs e)
{
lblMsg.Text = "";
lblMsg.Text += "ComputedHorizontalScrollBarVisibility: " + scrollViewer.ComputedHorizontalScrollBarVisibility + "\n";
lblMsg.Text += "ComputedVerticalScrollBarVisibility: " + scrollViewer.ComputedVerticalScrollBarVisibility + "\n";
lblMsg.Text += "ExtentWidth: " + scrollViewer.ExtentWidth + "\n";
lblMsg.Text += "ExtentHeight: " + scrollViewer.ExtentHeight + "\n";
lblMsg.Text += "ViewportWidth: " + scrollViewer.ViewportWidth + "\n";
lblMsg.Text += "ViewportHeight: " + scrollViewer.ViewportHeight + "\n";
lblMsg.Text += "HorizontalOffset: " + scrollViewer.HorizontalOffset + "\n";
lblMsg.Text += "VerticalOffset: " + scrollViewer.VerticalOffset + "\n";
lblMsg.Text += "ScrollableWidth: " + scrollViewer.ScrollableWidth + "\n";
lblMsg.Text += "ScrollableHeight: " + scrollViewer.ScrollableHeight + "\n";
}
private void Button_Click(object sender, RoutedEventArgs e)
{
scrollViewer.ScrollToHorizontalOffset(scrollViewer.ScrollableWidth / 2);
scrollViewer.ScrollToVerticalOffset(scrollViewer.ScrollableHeight / 2);
}
}
ScrollBar 繼承自 RangeBase,核心屬性:Value、Minimum、Maximum、ViewportSize、Orientation。事件:ValueChanged。
<Page x:Class="XamlDemo.Controls.ScrollViewer.ScrollBarDemo" ...>
<Grid Background="Transparent">
<StackPanel Margin="120,0,0,0">
<TextBlock x:Name="lblMsg" FontSize="14.667" HorizontalAlignment="Left"/>
<ScrollViewer x:Name="scrollViewer" Width="400" Height="200"
HorizontalAlignment="Left">
<Image Source="/Assets/Logo.png" Width="1000"/>
</ScrollViewer>
</StackPanel>
</Grid>
</Page>
public sealed partial class ScrollBarDemo : Page
{
public ScrollBarDemo()
{
this.InitializeComponent();
this.Loaded += ScrollBarDemo_Loaded;
}
void ScrollBarDemo_Loaded(object sender, RoutedEventArgs e)
{
// Helper.GetVisualChild 為通用視覺樹搜尋方法
var scrollBar = Helper.GetVisualChild<ScrollBar>(scrollViewer, "VerticalScrollBar");
scrollBar.ValueChanged += scrollBar_ValueChanged;
}
void scrollBar_ValueChanged(object sender, RangeBaseValueChangedEventArgs e)
{
lblMsg.Text = e.NewValue.ToString();
}
}
public static T GetVisualChild<T>(DependencyObject parent, string name = null) where T : DependencyObject
{
int count = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < count; i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
if (child is T t && (name == null || (child as FrameworkElement).Name == name))
return t;
var result = GetVisualChild<T>(child, name);
if (result != null) return result;
}
return null;
}
ScrollContentPresenter 專責呈現 ScrollViewer.Content,類似 ContentPresenter(ContentControl)、ItemsPresenter(ItemsControl)。可透過視覺樹存取並調整其 Margin、RenderTransform 等屬性,實現進階捲動視差、固定標題等效果。
<Page x:Class="XamlDemo.Controls.ScrollViewer.ScrollContentPresenterDemo" ...>
<Grid Background="Transparent">
<StackPanel Margin="120 0 0 0">
<ScrollViewer x:Name="scrollViewer" Width="400" Height="400"
Background="Blue" HorizontalAlignment="Left"
HorizontalScrollMode="Enabled" VerticalScrollMode="Enabled"
HorizontalScrollBarVisibility="Visible"
VerticalScrollBarVisibility="Visible">
<ScrollViewer.Content>
<Image Source="/Assets/Logo.png" Width="1000"/>
</ScrollViewer.Content>
</ScrollViewer>
</StackPanel>
</Grid>
</Page>
public sealed partial class ScrollContentPresenterDemo : Page
{
public ScrollContentPresenterDemo()
{
this.InitializeComponent();
this.Loaded += ScrollContentPresenterDemo_Loaded;
}
void ScrollContentPresenterDemo_Loaded(object sender, RoutedEventArgs e)
{
var presenter = Helper.GetVisualChild<ScrollContentPresenter>(scrollViewer, "ScrollContentPresenter");
presenter.Margin = new Thickness(100); // 內容整體偏移 100 像素
}
}
Extent/Viewport/Offset/Scrollable 四組尺寸屬性,善用 ScrollToHorizontal/VerticalOffset 程式碼控制捲動ValueChanged理解這三者關係,能讓你在需要自定義捲動行為時遊刃有餘。