[WM]gif 的動畫不動怎麼辦 & 繪製進度列

參考資料:
Creating a Microsoft .NET Compact Framework-based Animation Control

在以往開發應用程式時,有時候會用到gif圖檔來做簡單的動畫效果,但是同樣的gif圖檔在裝置上卻是不動如山,這..這..這該怎麼辦..
首先要先說明一下,能夠讓裝置播放gif動畫嗎?答案是不行(路人A:來亂的阿,不行?那你接下來想講什麼)。只能夠去模擬出那樣的效果;如同參考的資料來源中所說,gif動畫的圖檔如果利用gif editor去開啟的話,會發現它是連續的影格(frame)組合起來的,接下來要說明的也是這樣的方式。
首先我們要先準備一張長長的圖檔,裡面就是所有的frame了,例如這邊筆者做了下面這樣的圖

每個frame是72px,所以其實要模擬出動畫的效果,就是一格一格把它切出來,之後定時更新到畫面上。
接下來就來看看程式碼的部分,首先要先加入一個"元件"到方案當中,而元件的程式碼會像下面這樣
''reference:http://msdn.microsoft.com/en-us/library/aa446483.aspx
Imports System.Drawing

Public Class AnimationCtl
Inherits System.Windows.Forms.Control
''儲存要繪製的圖片
Private _bmp As Bitmap
''儲存每個frame(畫格)的寬度
Private _frameWidth As Integer
''儲存每個frame(畫格)的高度
Private _frameHeight As Integer
''繪製圖形用
Private _gGraphic As Graphics
''定時重新繪製畫面的Timer
Private _drawTimer As Timer
''動畫要輪播幾次
Private _intLoopTimes As Integer
''目前動畫已經輪播過的次數
Private _intCurrentLoopCount As Integer
''目前繪製到哪一個frame
Private _currentFrame As Integer
''總共有幾個frame
Private _frameTotalCount As Integer

'''
''' 取得或設定要繪製的圖形
'''

'''
'''
'''
Public Property [Bitmap]() As Bitmap
Get
Return _bmp
End Get
Set(ByVal value As Bitmap)
_bmp = value
End Set
End Property

'''
''' 開始動畫繪製作業
'''

''' 每個frame(影格)的寬度
''' 切換到下一個frame的延遲時間
''' 動畫要輪播幾次
'''
Public Sub StartAnimation(ByVal fWidth As Integer, ByVal iDelay As Integer, ByVal iLoopTimes As Integer)
_frameWidth = fWidth

_intLoopTimes = iLoopTimes
_currentFrame = 0
_frameTotalCount = _bmp.Width / _frameWidth
_frameHeight = _bmp.Height

Me.Size = New Size(_frameWidth, _frameHeight)
_drawTimer.Interval = iDelay
_drawTimer.Enabled = True

End Sub

'''
''' 畫面繪製動作
'''

'''
'''
Private Sub Draw(ByVal iFrame As Integer)
Dim pX As Integer = iFrame * _frameWidth
Dim destrect As Rectangle = New Rectangle(0, 0, _frameWidth, _frameHeight)
Dim rect As Rectangle = New Rectangle(pX, 0, _frameWidth, _frameHeight)
Dim imgAttr As New Imaging.ImageAttributes
imgAttr.SetColorKey(_bmp.GetPixel(1, 1), _bmp.GetPixel(1, 1))
'Dim c As Color = Color.FromArgb(252, 0, 249)
'imgAttr.SetColorKey(c, c)
_gGraphic.Clear(Me.Parent.BackColor)
_gGraphic.DrawImage(_bmp, destrect, pX, 0, _frameWidth, _frameHeight, GraphicsUnit.Pixel, imgAttr)
End Sub

'''
''' 繪製frame
'''

'''
Private Sub DrawFrame()
If (_currentFrame < _frameTotalCount) Then
_currentFrame += 1
Else
_intCurrentLoopCount += 1
_currentFrame = 0
End If
Draw(_currentFrame)
End Sub

Private Sub Timer_Tick(ByVal sender As Object, ByVal e As EventArgs)
If (_intLoopTimes = -1) Then
Me.DrawFrame()
Else
If (_intCurrentLoopCount = _intLoopTimes) Then
_drawTimer.Enabled = False
Else
Me.DrawFrame()
End If
End If
End Sub
End Class
而初始化的地方也要做一些設定,例如下面這樣
  _
Public Sub New()
MyBase.New()

'此為元件設計工具所需的呼叫。
InitializeComponent()
''
_gGraphic = Me.CreateGraphics
''
_drawTimer = New Timer
AddHandler _drawTimer.Tick, AddressOf Timer_Tick
End Sub
而呼叫的範例會像是下面這樣的方式
 Dim A As AnimationCtl
A = New AnimationCtl
A.Bitmap = New Bitmap(GetAppPath() & "\test.png")
A.Location = New Point(10, 10)
Me.Controls.Add(A)
A.StartAnimation(72, 300, 10)

最終執行的結果就可以在畫面上看到1~5數字的輪播了

這樣子就可以做到模擬gif動畫的效果了,而利用這個範例變化一下,來試試看畫個進度列吧,首先也是要先準備一個圖檔,筆者用盡所有美術天分搞了一張下面簡單的圖檔

之後,同樣的,在方案總管中加入一個新的元件,而元件的程式碼如下
Imports System.Drawing

Public Class myProgess
Inherits System.Windows.Forms.Control
''完成度
Private _SuccessPercent As Integer
''
Private _bmp As Bitmap

'''
''' 取得或設定要繪製的圖形
'''

'''
'''
'''
Public Property [Bitmap]() As Bitmap
Get
Return _bmp
End Get
Set(ByVal value As Bitmap)
_bmp = value
End Set
End Property

Public Property SuccessPercent() As Integer
Get
Return _SuccessPercent
End Get
Set(ByVal value As Integer)
_SuccessPercent = value
DrawCtl()
End Set
End Property

Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
DrawCtl()
MyBase.OnPaint(e)
End Sub

Private Sub DrawCtl()
Dim g As Graphics = Me.CreateGraphics
Dim destRect As New Rectangle(0, 0, Me.Width * (SuccessPercent / 100), Me.Height)
Dim imgAttr As New Imaging.ImageAttributes
imgAttr.SetColorKey(_bmp.GetPixel(1, 1), _bmp.GetPixel(1, 1))
g.DrawImage(_bmp, destRect, 0, 0, _bmp.Width * (SuccessPercent / 100), _bmp.Height, GraphicsUnit.Pixel, imgAttr)

imgAttr.Dispose()
imgAttr = Nothing
g.Dispose()
g = Nothing
End Sub
End Class
呼叫的範例會像下面這樣
Dim B As myProgess

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
B = New myProgess
B.Bitmap = New Bitmap(GetAppPath() & "\progess.png")
B.Location = New Point(5, 150)
B.Size = New Size(200, 50)
Me.Controls.Add(B)

Timer1.Enabled = True
End Sub

Public Function GetAppPath() As String
Return System.IO.Path.GetDirectoryName(Reflection.Assembly.GetExecutingAssembly.GetName.CodeBase.ToString)
End Function

Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
If count < 100 Then
count += 1
Else
Timer1.Enabled = False
End If
B.SuccessPercent = count
End Sub
這樣子,就可以"假裝"出一條新的進度列了

不過美中不足的是這樣的方式對於來源圖檔的邊邊還是沒有處理的很好(會有白邊的情形),這個等日後有機會再接續下去研究了。

留言

這個網誌中的熱門文章

開啟cshtml檔案時,出現『並未將物件參考設定為物件執行個體』的錯誤訊息

無法設定中斷點 尚未載入符號檔

DataGridView欄位計算總合