HTML `
控件繼承與核心屬性
public class Marquee : ContentControl {
// 依賴屬性
public static readonly DependencyProperty DirectionProperty = ...; // Left/Right
public static readonly DependencyProperty BehaviorProperty = ...; // Scroll/Slide/Alternate
public static readonly DependencyProperty ScrollAmountProperty = ...; // 像素/步
public static readonly DependencyProperty ScrollDelayProperty = ...; // 毫秒/步
public static readonly DependencyProperty LoopProperty = ...; // 循環次數
}
繼承 ContentControl 使 Content 可放入任何 UIElement (TextBlock、Rectangle、Button 等)。
三種動畫實作策略比較
| 策略 | 優點 | 缺點 | 選擇 |
| Animation + Storyboard | 聲明式、GPU 加速 | 動態參數綁定複雜、ScrollDelay 難控制 | ✗ |
| CompositionTarget.Rendering | 幀級控制 | 高頻率事件、ScrollDelay 需自行計時 | ✗ |
| DispatcherTimer | 精確延遲控制、邏輯直觀 | UI 執行緒定時器、極高頻率可能影響回應 | ✓ |
選擇 DispatcherTimer,Tick 事件中計算位移並更新 TranslateTransform.X。
核心動畫邏輯:OnUpdateContentPosition
protected virtual void OnUpdateContentPosition() {
if (_contentHost == null) return;
double contentWidth = _contentHost.DesiredSize.Width;
double leftBound, rightBound;
// 邊界計算
if (Behavior == MarqueeBehavior.Alternate || Behavior == MarqueeBehavior.Slide) {
leftBound = 0;
rightBound = ActualWidth - contentWidth;
} else { // Scroll
leftBound = -contentWidth; // 完全捲出視野
rightBound = ActualWidth;
}
if (_currentLoop < Loop) {
if (_currentDirection == Direction.Left) {
_contentTranslate.X -= ScrollAmount;
if (_contentTranslate.X <= leftBound) {
if (Behavior == MarqueeBehavior.Scroll || Behavior == MarqueeBehavior.Slide)
_contentTranslate.X = rightBound; // 瞬間跳回另一側
else
_currentDirection = Direction.Right; // Alternate 反向
_currentLoop += _loopCounter;
}
} else { // Direction.Right
_contentTranslate.X += ScrollAmount;
if (_contentTranslate.X >= rightBound) {
if (Behavior == MarqueeBehavior.Scroll || Behavior == MarqueeBehavior.Slide)
_contentTranslate.X = leftBound;
else
_currentDirection = Direction.Left;
_currentLoop += _loopCounter;
}
}
} else {
// 循環結束:確保內容可見
_contentTranslate.X = Math.Max(0, Math.Min(_contentTranslate.X, ActualWidth - contentWidth));
}
}
關鍵點:
- 使用
TranslateTransform 位移 ContentPresenter,不改變佈局
DesiredSize.Width 取得內容實際寬度
- 三種行為的邊界與反轉邏輯分離清晰
- 循環計數器
_loopCounter 處理往返一次算一循環 (Alternate) 或單向一循環 (Scroll/Slide)
使用範例
<!-- 命名空間對應 xmlns:l="clr-namespace:YourNamespace" -->
<l:Marquee Content="123" Direction="Right" Behavior="Scroll" ScrollAmount="20" ScrollDelay="500"/>
<l:Marquee Direction="Right" Behavior="Slide">
<Rectangle Width="20" Height="20" Fill="Blue"/>
</l:Marquee>
<l:Marquee>
<Button Content="Button" Width="50"/>
</l:Marquee>
注意事項與擴充建議
Loop 結束時未停止 Timer,視窗 Resize 會重新播放;建議在 ResetAnimation 中重啟、Stop 方法中停止
- 可擴充
Pause/Resume、MouseEnter 暫停、垂直捲動、無縫循環 (雙份內容)
- 高頻率 Timer (
ScrollDelay < 16ms) 可能影響 UI 回應,考慮 CompositionTarget.Rendering 替代
- 上一篇文章: CLR 反射深度解析:從類型檢查到動態序列化的高效能實踐
- 下一篇文章: Groove 2007 協作平台整合指南:離線表單收集與 Web Services API 自動同步至 SQL Server
Copyright ©
程式碼之家 All Rights Reserved