#E6E6E6 【VB 物件】PictureScroll 實現圖片循環捲動 作者:吳文成

  在我的經驗裡撰寫過許多影像特效的函數,例如將影像作交錯顯示、雨滴顯示、移動顯示、淡出與淡入、拉幕技巧、放大縮小、螺旋方塊顯[[img src=think/poem_all.jpg height=259 width=346 align=left]]示
、螺旋圓形顯示,還包括影像任意角度旋轉,我在控件 CommonBox 裡實現了這些功能。另一些影像處理的技巧,也是大家很有興趣的,例如將影像給灰階化、調整影像的明暗度與對比度等等,而影像的透背處理(指定透明顏色而去除背景)更是貼圖一族的最愛,我在控件
TransImage 便實現了這項功能 , 而在我新開發的控件 FlatButton(平面按鈕)也需要運用到特定的影像灰階化處理。這些控件,我應該會找機會釋出給大家使用。與上面這些特效比較起來,圖片循環捲動是比較簡單的技巧,它的(流程圖)構思不失為一個好的入門範例,所以我將在這裡介紹它。

   一般來說 , Visual Basic 的影像處理速度是不符合程式開發者的要求的 , 例如使用 PaintPicture 方法( 或 ListImage.Draw 方法 )來貼圖,你會看到「 慢動作 」, 這時候我們會使用 Window API 或者 DirectX 的相關函數來取而代之 。BitBlt 函數是 Window API 裡最主要的位圖繪製函數 , 它是 PaintPicture 方法的原始祖宗 ,原始祖宗總是講求簡潔與速度 , 所以敬老尊賢在這裡是必要的。如果選擇使用 BitBlt 這個函數,我們必須輸入參數:目的裝置與來源裝置的圖形裝置代碼(hDC,週邊設定內容物件代碼
)。 表單與控件 PictureBox 都有這個代碼 , 然而標準物件 StdPicture 卻沒有 ,這時候我們便需要利用 StdPicture 的 Handle 與 CreateCompatibleDC 函數、SelectObject 函數來取得其暫時的、相容的 hDC。 BitBlt 的貼圖功能其實就是把來源裝置之指定位置開始的影像,複製轉貼到目的裝置的指定位置與區域上,因而它也兼具有影像放大或縮小的功能。

   我所撰寫的 PictureScroll 物件類別便是使用 BitBlt 函數 , 來實現指定控件裡圖片的循環捲動,包括方向與速度自訂的水平捲動或垂直捲動。我們需要在物件裡動態新增控件: 一個 Timer 與 PictureBox( 我們也可以用標準物件 StdPicture 來取代 PictureBox,不過這樣會多使用到上一段提到的那幾個 Window API)。Timer 用來按照時間間隔以執行圖片捲動與重繪,PictureBox( 範例裡的 picSrc 物件 )則用來儲存與保留目的裝置的原始圖片。圖片循環捲動,最簡單、最視覺化的貼圖流程是這樣:根據每次捲動[[img src=computer/PicScroll.gif height=159 width=202 align=left]]的移動量(ScrollStep)直接將目的裝置的影像分成兩部分,然後再交換分貼到目的裝置自己身上。

  這樣講起來似乎很簡單,但是裡面卻有很多想像不到的細節,這些細節可能使得我們修正原來的構想,例如這裡牽涉到不同方向的捲動問題,圖片大小與目的裝置大小不一致的問題,原圖狀態如何保留的問題,是否真的可以不須藉助媒介而貼圖的問題。這些細節的考慮都增加了若干複雜度。在勢必藉助媒介(範例裡的 picSrc 物件)與考量貼圖速度,以及演算法推廣化,等等相關的因素下,使得我們必須改用圖片目前捲動到的位置( ScrollLocation),來將來源裝置( picSrc )的圖片分成兩部分,再轉貼到目的裝置。也就是說,原本的視覺化的、類比式的構思,有時不能成為真正的程式流程規劃,因為後者才會遭遇到那些非簡化的邏輯程序問題。例如自己分割自己,然後再不經任何媒介而交換貼自己,這聽起來很自然,可是在程式撰寫上這是做不到的。這就是類比的與數位的本質差別

  看看以下的程式碼,大概可以挖掘出問題所在,也許你會想出比我更好、更簡潔或更有效率的技巧 。限於篇幅,我只列舉出 PictureScroll.cls 的部分關鍵的程式碼,有需要請下載列於文末的完整源碼:

Option Explicit

' 將一幅位圖從某個裝置複製重繪到另一個裝置
Private Declare Function BitBlt Lib "gdi32" (ByVal hDestDC As Long, ByVal x As Long, ByVal y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal hSrcDC As Long, ByVal XSrc As Long, ByVal YSrc As Long, ByVal dwRop As Long) As Long

' 宣告圖片捲動模式的列舉型態
Public Enum PicScrollMode
 [無捲動] = 0  ' 預設捲動方式
 [水平捲動] = 1 ' 當 m_ScrollStep > 0 則由右向左捲動,反之相反
 [垂直捲動] = 2 ' 當 m_ScrollStep > 0 則由下向上捲動,反之相反
End Enum

' 此物件的初始化方法,必須指定放置圖片的目的控制項
' 圖片捲動的方式、每次移動單位(像素)與間隔時間(毫秒),則為選擇性參數

Public Sub Initialize(PicContainer As Object, Optional ByVal ScrollMode As PicScrollMode, Optional ByVal ScrollStep As Long, Optional ByVal DelayInterval As Long)
 On Error GoTo errorLabel
 
 ' 動態新增控件,Timer 與 PictureBox 各一個
 creatControl PicContainer

 m_ScrollLocation = 0
 m_ScrollMode = ScrollMode

 ' 設置控件 tmrScroll 的屬性值
 With tmrScroll
  If DelayInterval <= 0 Then DelayInterval = 50
  .Interval = DelayInterval
  If ScrollStep = 0 Then ScrollStep = 2
  m_ScrollStep = ScrollStep
  .Enabled = False
 End With
 
 ' 設置控件 picSrc 的屬性值
 With picSrc
  .ScaleMode = vbPixels
  .AutoSize = True
  .AutoRedraw = True
  .Visible = False
 End With
 
 ' 改變目的裝置 PicContainer 的屬性值
 Set picCtn = PicContainer
 With picCtn
  ' 在 picSrc 中紀錄原本目的裝置的圖片
  Set picSrc.Picture = .Picture
  picSrc.Tag = .AutoRedraw
  .AutoRedraw = False
  .ScaleMode = vbPixels
 End With
Exit Sub
errorLabel:
 Err.Raise 513, , "目標控制項不支援 PictureScroll 的功能"
End Sub

' 按照計時間隔與指定的捲動方式,來執行圖片捲動與重繪
Private Sub tmrScroll_Timer()
 ' 分別紀錄目的裝置與來源裝置的長寬
 Dim wPicCtn As Long, hPicCtn As Long
 Dim wPicSrc As Long, hPicSrc As Long
 wPicCtn = picCtn.ScaleWidth
 hPicCtn = picCtn.ScaleHeight
 wPicSrc = picSrc.ScaleWidth
 hPicSrc = picSrc.ScaleHeight
 ' 表示 m_ScrollLocation 上邊或左邊的圖像
 Dim part1 As Long
 ' 表示 m_ScrollLocation 下邊或右邊的圖像
 Dim part2 As Long

 Select Case m_ScrollMode
 Case 垂直捲動   ' 當 m_ScrollStep > 0 則由下向上捲動,反之相反
  part1 = hPicSrc - m_ScrollLocation
  part2 = m_ScrollLocation
  
  If m_ScrollLocation + hPicCtn <= hPicSrc Then
   ' 直接繪製 part2 區域
   BitBlt picCtn.hDC, 0, 0, wPicCtn, hPicCtn, picSrc.hDC, 0, part2, vbSrcCopy
  Else
   ' 將來源裝置的 part2 區域繪製到目的裝置
   BitBlt picCtn.hDC, 0, 0, wPicCtn, part1, picSrc.hDC, 0, part2, vbSrcCopy
   ' 將來源裝置的 part1 區域繪製到目的裝置
   BitBlt picCtn.hDC, 0, part1, wPicCtn, part2, picSrc.hDC, 0, 0, vbSrcCopy
  End If
  ' 依序改變圖片目前捲動到的位置
  m_ScrollLocation = m_ScrollLocation + m_ScrollStep
  If m_ScrollLocation >= hPicSrc Then m_ScrollLocation = m_ScrollLocation - hPicSrc
  If m_ScrollLocation < 0 Then m_ScrollLocation = m_ScrollLocation + hPicSrc
 Case 水平捲動   ' 當 m_ScrollStep > 0 則由右向左捲動,反之相反
  part1 = wPicSrc - m_ScrollLocation
  part2 = m_ScrollLocation
  
  If m_ScrollLocation + wPicCtn <= wPicSrc Then
   ' 直接繪製 part2 區域
   BitBlt picCtn.hDC, 0, 0, wPicCtn, hPicCtn, picSrc.hDC, part2, 0, vbSrcCopy
  Else
   ' 將來源裝置的 part2 區域繪製到目的裝置
   BitBlt picCtn.hDC, 0, 0, part1, hPicCtn, picSrc.hDC, part2, 0, vbSrcCopy
   ' 將來源裝置的 part1 區域繪製到目的裝置
   BitBlt picCtn.hDC, part1, 0, part2, hPicCtn, picSrc.hDC, 0, 0, vbSrcCopy
  End If
  ' 依序改變圖片目前捲動到的位置
  m_ScrollLocation = m_ScrollLocation + m_ScrollStep
  If m_ScrollLocation >= wPicSrc Then m_ScrollLocation = m_ScrollLocation - wPicSrc
  If m_ScrollLocation < 0 Then m_ScrollLocation = m_ScrollLocation + wPicSrc
 End Select
End Sub

範例源碼下載
2004/10/09