簡易繪制方式
先看下面的代碼, 產生一個Panel, 然後要在Panel上畫一條線.
首先, 由Panel取得一個wxClientDC物件, 然後再對這個dc物件進行繪制. 這就好比dc就是Panel上的畫布(Canvas).
繪制的時機, 必需在整個Frame形成ready後才可以開始繪制. 所以如果在 __init__()建構子就開始繪制的話, 是看不到線條的. 因為必需使用wx.CallLater(), 一秒鐘後才由UI主執行緒繪制
請注意, wx.ClientDC不可長時間保存, 必需在繪圖前才產生. 也就是說, dc不能為物件變數.
import wx import random class MainFrame(wx.Frame): def __init__(self, parent): wx.Frame.__init__(self, parent) self.panel=wx.Panel(self) self.panel.SetBackgroundColour("#ffff00") box=wx.BoxSizer(wx.VERTICAL) self.SetSizer(box) box.Add(self.panel, 1, wx.EXPAND,1) wx.CallLater(1000, self.DrawLine) def DrawLine(self): x1=random.randint(0,800) y1 = random.randint(0, 800) x2 = random.randint(0, 800) y2 = random.randint(0, 800) dc=wx.ClientDC(self.panel) #ClientDC必需在繪圖前才產生, 不可事先產生 self.dc.DrawLine(x1, y1, x2, y2) app=wx.App() frame=MainFrame(None) frame.Show() app.MainLoop()
上述使用CallLater事實上並不太正確. 正確使用方式是在Frame ready後, 才開始畫. 當Frame Ready時, 會傳送Paint事件, 所以可以在接收到Paint event後, 開始繪圖, 如下
import wx import random class MainFrame(wx.Frame): def __init__(self, parent): wx.Frame.__init__(self, parent) self.panel=wx.Panel(self) self.panel.SetBackgroundColour("#ffff00") box=wx.BoxSizer(wx.VERTICAL) self.SetSizer(box) box.Add(self.panel, 1, wx.EXPAND,1) self.Bind(wx.EVT_PAINT, self.OnFormLoaded) def OnFormLoaded(self, event): x1=random.randint(0,800) y1 = random.randint(0, 800) x2 = random.randint(0, 800) y2 = random.randint(0, 800) dc=wx.ClientDC(self.panel) dc.SetBrush(wx.Brush(wx.RED)) dc.DrawLine(x1, y1, x2, y2) dc.DrawCircle(100,100,50) app=wx.App() frame=MainFrame(None) frame.Show() app.MainLoop()
閃爍問題
上面的代碼中, 如果把DrawLine()換成DrawCircle()畫出圓形, 則圓形會有鋸齒的現像, 非常的醜. 另外, 如果換成DrawImage畫圖片時, 則會出現閃爍的情形.
首先由panel產生wxClientDC canvas, 然後開啟一個self.buffer =wx.Bitmap(size) , 此為空白的圖檔緩衝區. 再使用wx.BufferedDC()將canvas與buffer連結. 這樣就可以直接對buffer畫圖了. 但為什麼直接對buffer畫圖就不會閃爍呢? 原因是此時直接針對buffer繪制的圖形, 等到繪制完成後, 把整個buffer覆蓋在canvas上. 因為覆蓋的動作非常快速, 所以就會讓我們感覺不到閃爍的現像.
如同ClientDC, BufferedDC亦不可長時間保存, 必需在需要繪圖時, 才產生. 畫完覆蓋後, 它就會自動被刪除了。
反鋸齒
接下來, 紅色的圓形周圍都有鋸齒的狀況. 這顯的相當醜陃. 還好, 可以使用wx.GCDC()來消除艍齒. 不過請注意, wx.GCDC並不支援直接對wxClientDC 控制, 而是必需對wxBufferedDC控制. 所以就可以使用如下方式產生dc. 再對dc繪制圖形
dc=wx.GCDC(wx.BufferedDC(wx.ClientDC(self.panel), self.buffer))
wx.GCDC在清除背景前, 必需先設定Background顏色, 這樣Clear才會有效的運作
import wx import random import time, threading class MainFrame(wx.Frame): def __init__(self, parent): wx.Frame.__init__(self, parent,style=wx.DEFAULT_FRAME_STYLE | wx.MAXIMIZE | wx.TAB_TRAVERSAL) self.panel=wx.Panel(self) self.panel.SetBackgroundColour("#ffff00") box=wx.BoxSizer(wx.VERTICAL) self.SetSizer(box) box.Add(self.panel, 1, wx.EXPAND,1) t=threading.Thread(target=self.DrawThread) self.Layout() t.start() self.Bind(wx.EVT_SIZE, self.Resize) def Resize(self, event): self.w, self.h=self.GetSize() self.buffer = wx.Bitmap(self.w, self.h) def DrawThread(self): while True: wx.CallAfter(self.DrawLine); time.sleep(2) def DrawLine(self): dc = wx.GCDC(wx.BufferedDC(wx.ClientDC(self.panel), self.buffer)) dc.SetBackground(wx.Brush("#ffff00")) dc.Clear() x1=random.randint(0,self.w) y1 = random.randint(0, self.h) radius=random.randint(20,100) pen=wx.Pen("#ff0000") brush=wx.Brush("#ff0000") dc.SetPen(pen) dc.SetBrush((brush)) dc.DrawCircle(x1, y1, radius) app=wx.App() frame=MainFrame(None) frame.Show() app.MainLoop()
彈跳球
import wx
import random
import time, threading
class MainFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent,style=wx.DEFAULT_FRAME_STYLE | wx.MAXIMIZE | wx.TAB_TRAVERSAL)
self.panel=wx.Panel(self)
self.panel.SetBackgroundColour("#ffff00")
box=wx.BoxSizer(wx.VERTICAL)
self.SetSizer(box)
box.Add(self.panel, 1, wx.EXPAND,1)
self.runFlag=True;
self.panel.Bind(wx.EVT_SIZE, self.Resize)
self.Bind(wx.EVT_CLOSE, self.Close)
self.x=50
self.y=50;
self.xspeed=25;
self.yspeed=17;
self.xdir=1;
self.ydir=1;
self.radius=20;
t=threading.Thread(target=self.DrawThread)
self.Layout()
t.start()
def Resize(self, event):
self.w, self.h=self.panel.GetSize()
self.buffer = wx.Bitmap(self.w, self.h)
def DrawThread(self):
while self.runFlag:
wx.CallAfter(self.DrawLine);
time.sleep(0.02)
def DrawLine(self):
dc = wx.GCDC(wx.BufferedDC(wx.ClientDC(self.panel), self.buffer))
dc.SetBackground(wx.Brush(wx.Colour(0, 0, 0)))
dc.Clear()
#以下的寫法, 就不需clear了, 因為重建buffer
#buffer=wx.Bitmap(self.GetClientSize())
#dc = wx.GCDC(wx.BufferedDC(wx.ClientDC(self), buffer))
dc.SetPen(wx.Pen(wx.Colour(255,255,0), 5))
r = random.randint(0,255)
g = random.randint(0, 255)
b = random.randint(0, 255)
dc.SetBrush(wx.Brush(wx.Colour(r, g, b)))
self.x+=self.xspeed*self.xdir
self.y+=self.yspeed*self.ydir
if self.x>=self.w-self.radius:
self.x=self.w-self.radius
self.xdir*=-1
elif self.x<self.radius:
self.x=self.radius
self.xdir*=-1
if self.y>=self.h-self.radius:
self.y=self.h-self.radius
self.ydir*=-1
elif self.y<self.radius:
self.y=self.radius
self.ydir*=-1
dc.DrawCircle(self.x, self.y, self.radius)
def Close(self, event):
self.runFlag=False
app.ExitMainLoop()
app=wx.App()
frame=MainFrame(None)
frame.Show()
app.MainLoop()