專案需求:讓 ListBox 的項目在 Canvas 中從左向右依序滑入、滑出,並支援滑鼠點擊暫停/繼續。本文實作一個可重用的 CanvasItemBehavior 附加行為,透過附加屬性即可套用至任何 Canvas。
ItemsSource 變更(新增/移除項目)Duration<Canvas local:CanvasItemBehavior.MoveMode="LeftToRight"
local:CanvasItemBehavior.Duration="0:0:5">
<ListBox ItemsSource="{Binding Items}" />
</Canvas>
MoveMode、Duration、BubbleR、內部 Storyboard 儲存。INotifyCollectionChanged 監聽資料源變更,為每個 ListBoxItem 註冊 IsVisibleChanged 與 PreviewMouseDown。MoveMode 設定 DoubleAnimation 的 From/To(Canvas.Left 或 Canvas.Top),完成時隱藏項目並清理 Storyboard。static void item_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if ((bool)e.NewValue)
{
var item = sender as UIElement;
if (GetStoryboard(item) == null)
{
var canvas = VisualTreeHelper.GetParent(item) as Canvas;
var s = new Storyboard();
var ani = new DoubleAnimation();
Storyboard.SetTarget(ani, item);
var mode = GetMoveMode(canvas);
if ((int)mode < 3) // 水平
{
ani.From = -item.RenderSize.Width;
ani.To = canvas.ActualWidth;
ani.Duration = GetDuration(canvas) > TimeSpan.Zero ? GetDuration(canvas)
: TimeSpan.FromSeconds(canvas.ActualWidth / 100);
Storyboard.SetTargetProperty(ani, new PropertyPath(Canvas.LeftProperty));
}
else // 垂直
{
ani.From = -item.RenderSize.Height;
ani.To = canvas.ActualHeight;
ani.Duration = GetDuration(canvas) > TimeSpan.Zero ? GetDuration(canvas)
: TimeSpan.FromSeconds(canvas.ActualHeight / 100);
Storyboard.SetTargetProperty(ani, new PropertyPath(Canvas.TopProperty));
}
s.Children.Add(ani);
s.Completed += (ss, ee) => { item.Visibility = Visibility.Hidden; SetStoryboard(item, null); };
SetStoryboard(item, s);
s.Begin();
}
}
}
完整原始碼包含 MoveOrientaion 列舉、VisualHelper 輔助類別,已整理為可直接引用的類別庫。