下面內容是參考A Newbie Guide to pygame的一些重點節錄 :
1. 了解什麼是Surface, 以及一些Surface重要的functions
pygame.display.set_mode : 建立一個代表螢幕的Surface, 而且只能建立一個 (SDL的限制)
image.load : 讀取一個圖片, 並且將它建立成Surface
font.render() : 把一段文字建立成Surface
blit : 在螢幕的Surface上面, 畫上其他Surface
fill, set_at, get_at, ...
2. 當用image.load讀取圖片的時候, 利用convert將圖片進行轉換, 以加速執行.
ex : my_surface = pygame.image.load("my_pic.bmp").convert()
3. 畫完Surface記得要呼叫pygame.display.update()來更新畫面.
4. 使用pygame裡面的Rect資料型態來表示一個物件, 因為Rect裡面有很多function能幫助你處理
物件的碰撞
5. 檢查碰撞時, 可以用比原本物件小的Rect來幫助加速判斷
6. event的處理有分成兩種 :
1. state system : 利用pygame.mouse.get_pos()和pygame.key.get_pressed(), 取得目 前發生的event
優點 : 檢查同時按下容易, 準確的知道目前的event為何
缺點 : 如果在呼叫function之前有event產生又結束, 則這個event會lost
2. SDL event queue : event.poll(), event.wait()
優,缺點和state system相反
至於使用時機則是看應用而定, 如果呼叫event function的delay很短, 使用state system會比較方便. 如果不在乎輸入有delay, event產生的完整性比較重要的情形 (ex : 使用者輸入字串), 則適用SDL event queue.
7. Colorkey : 設定Colorkey之後, 可以讓Surface裡面的某個顏色不被畫出.
ex : surface.set_colorkey((0,0,0)), 會把surface裡面的黑色(0,0,0)不畫出, 而呈現背景的顏色.
8. Alpha : 設定Surface的半透明程度, 數值越大代表接近背景的比例越大, 如果數值為255, 則會呈 現完全透明.
參考資料 :
http://www.pygame.org/docs/tut/newbieguide.html
2008年5月30日 星期五
[+/-] |
Pygame給新手的建議 |
2008年5月29日 星期四
[+/-] |
寫註解的13個建議 |
在看別人的code時候, 往往因為文件不清楚, 或是註解不明確, 造成要了解程式必須要慢慢的去推敲過程. 這是非常浪費時間的事情, 因為往往只要知道程式流程, 就能掌握程式的精神. 對於細節的部分是不需要太去了解的, 除非程式存在bug.
所以寫好的註解可以幫助提升看程式的效率, 大多時候一個程式不會是從無到有. 而是會建立在別人的程式之上, 所以看程式的效率提升了, 寫程式的效率自然也會提升.
13 Tips to Comment Your Code這篇文章裡面就介紹了建議如何寫好的注解的方法.
除了註解以外, 文件也是很重要的. 除了大方向的程式運作之外, functions之間的運作流程也是幫助閱讀程式的好幫手. 尤其是寫event-driven的視窗程式, 如果能有各個功能的function流程, 能夠很快的幫助看程式的人了解程式運作, 而不需要自己去摸索猜測.
參考資料 :
13 Tips to Comment Your Code
http://www.cnblogs.com/oomusou/archive/2008/04/26/1172208.html
2008年5月28日 星期三
[+/-] |
MFC中的List Control如何刪除Item |
在List Control裡面要移除一個想要刪除的Item
- 可以在加入LVITEM Item時, 設定lParam這個變數, 在找尋Item的時候就可以用GetItemData取出這個Item的IParam值, 根據IParam來判斷是否為想要刪除的Item.
- 如果Item裡面有獨一無二的值, 也可以直接取出pszText來判斷. 利用GetItemText(Item_num, SubItem_num)來取得. SubItem_num是指在這個Item裡面的第幾個欄位.
for (int nItem = 0; nItem < m_lServerList.GetItemCount(); )
{
// if (m_lServerList.GetItemState(nItem, LVIS_SELECTED) == LVIS_SELECTED)
// m_lServerList.DeleteItem(nItem);
CString text;
if(text = m_lServerList.GetItemText(nItem,0) == "Delete Item")
m_lServerList.DeleteItem(nItem);
else
++nItem;
}
上面的code是先利用GetItemCount取得這個List Control總共有多少Item. 再重頭走過這些Item, 取出這些Item的第一個欄位(欄位0), 如果欄位的值為"Delete Item"這個字串的話, 就把這個Item刪除.
要注意的地方在於++nItem只有在沒有執行刪除的時候做, 因為如果進行刪除動作以後, 所有的Item都會被往後移動一個位子, 因此不需要++nItem.
而註解的地方則是如果一想要在這個Item被選取的時候刪除的做法.
參考資料 :
http://www.codeguru.com/forum/showthread.php?t=388818
http://hi.baidu.com/lufa2014/blog/item/0a5c069bbc1017b0c9eaf4fd.html
[+/-] |
改變MFC中CFormView的視窗式樣, 以及改變視窗標題 |
利用MFC Wizard建立一個MFC專案, 並且選擇產生CFrameWnd的模式, MFC Wizard會幫你產生以下檔案 :
- 繼承CWinApp的檔案 (專案名稱.cpp .h)
- 繼承CDocument的檔案(專案名稱Doc.cpp .h)
- 繼承CFormView or CView的檔案(專案名稱View.cpp .h)
- 繼承CFrameWnd的檔案 (MainFrm.cpp MainFrm.h)
- stdafx.cpp stdafx.h
- 資源檔
在CWinApp裡面會根據你選擇是SDI(Single Document Interface)或是MDI(Multiple Document Interface)產生控制Doc, View和MainFrm的元件.
因為控制主框架(視窗)是由MainFrm所控制的, 所以改變視窗式樣也是在這裡面改變.
在MainFrm.cpp裡面的PreCreateWindow(CREATESTRUCT& cs), cs這個變數就是用來改變視窗式樣的變數. 它是由多個控制視窗的flag所組成的.
例如 :
cs.style = WS_OVERLAPPED | WS_CAPTION | FWS_ADDTOTITLE
| WS_THICKFRAME | WS_MINIMIZEBOX | WS_SYSMENU;
由這樣的方式就能控制視窗式樣.
改變視窗的標題則是在View裡面取得Doc的變數然後進行設定 :
CServerMonitorDoc* pMyDoc = GetDocument();
pMyDoc->SetTitle("My Application Name");
其中的CServerMonitorDoc是看你Doc的class名稱為何.
參考資料 :
http://www.microsoft.com/msj/0198/c0198.aspx
2008年5月27日 星期二
[+/-] |
IP和Host Name的轉換 |
在設計網路連線的程式裡面, 常常會需要輸入ip或是host name. 設計良好的程式應該是要能接受這兩種不同的格式, 然後自行轉換.
下面就介紹如何轉換ip到host name以及host name到ip.
//----------------------
// Declare and initialize variables
hostent* remoteHost;
char* host_name;
unsigned int addr;
//----------------------
// User inputs name of host
printf("Input name of host: ");
host_name = (char*) malloc(sizeof(char*)*16);
fgets(host_name, 16, stdin);
// If the user input is an alpha name for the host, use gethostbyname()
// If not, get host by addr (assume IPv4)
if (isalpha(host_name[0])) { /* host address is a name */
host_name[strlen(host_name)-1] = '\0'; /* NULL TERMINATED */
remoteHost = gethostbyname(host_name);
}
else {
addr = inet_addr(host_name);
remoteHost = gethostbyaddr((char *) &addr, 4, AF_INET);
}
上面的程式碼主要功能在於輸入host name轉成ip, 或是輸入ip轉成host name.
if (isalpha(host_name[0])) { /* host address is a name */
host_name[strlen(host_name)-1] = '\0'; /* NULL TERMINATED */
remoteHost = gethostbyname(host_name);
}
這一段是先判斷輸入的內容是否有英文字母, 如果是英文字母則判斷為host name.
下面就用gethostbyname進行轉換.
else {
addr = inet_addr(host_name);
remoteHost = gethostbyaddr((char *) &addr, 4, AF_INET);
}
而這一段是判斷為ip address, 先將ip address用inet_addr轉成internal address形式,
再用gethostbyaddr取得host資訊.
取得host資訊以後, 再以remoteHost->h_name取得host name或是ip address.
如此一來就能在使用者輸入ip或是host name時同時取得兩種資訊.
參考資料 :
MSDN
2008年5月26日 星期一
[+/-] |
wxPython自訂event |
下面介紹如何自訂一個自己的event :
class TwoButtonEvent(wx.PyCommandEvent): #自訂event
def __init__(self, evtType, id):
wx.PyCommandEvent.__init__(self, evtType, id)
self.clickCount = 0
def GetClickCount(self):
return self.clickCount
def SetClickCount(self, count):
self.clickCount = count
myEVT_TWO_BUTTON = wx.NewEventType() #建立新event
EVT_TWO_BUTTON = wx.PyEventBinder(myEVT_TWO_BUTTON, 1) #建立新event binder
上面的部分為定義event和event binder. 在要產生event的地方則是以下面的code來產生event :
evt = TwoButtonEvent(myEVT_TWO_BUTTON, self.GetId())
evt.SetClickCount(self.clickCount)
self.GetEventHandler().ProcessEvent(evt)
利用ProcessEvent來將event產生到queue裡面等待處理.
參考資料 :
http://www.pythontik.com/blog/article.asp?id=192
[+/-] |
wxPython的Event |
要讓widget元件根據使用者的行為做不同的反應, 就需要建立event. event的功能在於使用者所做的行為: 滑鼠點擊, 鍵盤輸入, ... 以及其他視窗狀態要改變時, 通知視窗做改變.
主要流程可以簡單看成以下幾步 :
- 對每個widget元件榜定某個event發生時所要執行的functino(例如:button在按下時執行某個function, self.Bind(wx.EVT_BUTTON, self.OnClick, function) )
- 程式開始執行時, 在視窗產生以後, 會執行一個無限的迴圈. 在迴圈裡面會檢查事件是否發生, 如果發生就去執行對應的function
- 一直到程式結束的event發生才會離開無限迴圈, 進而結束程式
例如滑鼠的event(wx.MouseEvent)它的binder有以下這些 :
wx.EVT_LEFT_DOWN
wx.EVT_LEFT_UP
wx.EVT_LEFT_DCLICK
wx.EVT_MIDDLE_DOWN
wx.EVT_MIDDLE_UP
wx.EVT_MIDDLE_DCLICK
wx.EVT_RIGHT_DOWN
wx.EVT_RIGHT_UP
wx.EVT_RIGHT_DCLICK
這些分別是各種不同滑鼠event的event binder. 如果要榜定所有的滑鼠event執行同個function, 則可以用wx.EVT_MOUSE_EVENTS.
還有一些比較常用的event binder, 像是button被按下(wx.EVT_BUTTON), menu被按下(wx.EVT_MENU)等等.
而榜定event的方式有兩種 :
- wx.EvtHandler裡面的Bind function, 所有的widget都有繼承wx.EvtHandler, 所以都有Bind function可以使用.
Bind(event, handler, source=None, id=wx.ID_ANY, id2=wx.ID_ANY)
例子 : self.Bind(wx.EVT_BUTTON, self.OnClick, button) - 把event binder當成函數傳入參數使用.
例子 : wx.EVT_BUTTON(self, self.button.GetId(), self.OnClick)
- AddPendingEvent(event):把event加入處理的queue中, 不會馬上處理, 用於非同步的時候 使用
- Bind(event, handler, source=None, id=wx.ID_ANY, id2=wx.ID_ANY) : 把event和binder綁在一起
- GetEvtHandlerEnabled()
- SetEvtHandlerEnabled( boolean)
- ProcessEvent(event):把event加入處理的queue, 並且馬上處理
參考資料 :
http://www.pythontik.com/blog/article.asp?id=188
http://www.pythontik.com/blog/article.asp?id=189
2008年5月23日 星期五
[+/-] |
在MFC裡讓元件隨著視窗改變大小 |
使用視窗的時候, 我們常常會去改變視窗的大小. 而應用程式在視窗改變大小的同時, 也會把顯示在視窗的元件隨著視窗而改變. 在wxWidget裡面, 我們可以把元件加入Sizer來達到這樣的效果. 可是在MFC中就沒這樣的預設元件可以使用, 我們必須在視窗改變大小的時候(WM_SIZE)去自行重繪.
下面介紹如何簡單的讓一個List Control不論視窗如何改變都能和視窗保持伊個邊界的方法.
- 在你主要視窗的Class裡面加入一個WM_SIZE觸發時可以執行的function OnSize (名稱可以自訂, 但是這樣的名子比較具有可讀性)
- 在視窗改變大小時就會去呼叫OnSize, 這時候會先取得視窗目前的大小 (GetClientRect)
- 取得要變更大小的元件 (GetDlgItem), 並且檢查是否取得成功
- 計算新位置
- 移動視窗到新位置並且重繪 (MoveWindow, RedrawWindow)
void CServerMonitorView::OnSize(UINT nType, int cx, int cy)
{
CFormView::OnSize(nType, cx, cy);
int TopLeft_x = 10;
int TopLeft_y = 25;
// Declare a CRect to get the co-ordinates
CRect l_formRect;
GetClientRect(&l_formRect);
// get pointer to the control to be resized dynamically
CListCtrl* pListCtrl;
pListCtrl = (CListCtrl *)GetDlgItem(IDC_LIST1);
if(!pListCtrl)
return ;
//Calculate the Width and TopLeft position of the control to be resized
long newX = l_formRect.TopLeft().x + TopLeft_x;
long newY = l_formRect.TopLeft().y + TopLeft_y;
long newWidth = l_formRect.Width() - 10 - TopLeft_x;
long newHeight = l_formRect.Height() - 10 - TopLeft_y;
// Now resize the control dynamically by calling MoveWindow
pListCtrl->MoveWindow((long)newX, (long)newY, (long)newWidth, (long)newHeight, TRUE);
// repaint control
pListCtrl->RedrawWindow();
}
TopLeft_x和TopLeft_y是設定List Control元件的top left位置.
這只是簡單的Resize設定, 在有多個元件的時候重新設定位置會比較複雜. 不過好處是你可以決定要怎麼改變你的元件, 比較有彈性 (麻煩?).
參考資料 :
http://www.codersource.net/mfc_resize_controls.html
2008年5月22日 星期四
[+/-] |
blogger裡面的tag |
在blogger裡面有自己的tag語法來做版面設定, 在Widget Tag for Layout裡面有詳細的說明.
雖然不用完全了解它的意義, 但是為了要使用別人的排版元件, 還是要知道如何去加在自己的版面設定裡面. 像是在文章顯示的時候, 要以什麼樣的排版顯示, 要顯示哪些資訊等等, 都可以自己設定.
主要的版面設定的tag是 <div class="'blog-posts">, 在底下的code就是決定在顯示文章時如何做排版.
有兩個常常會看到的tag, 一個是include, 一個是includable.
- include :
<b:include data="'include_var'" name="'includable_id'/">
include的用途在於使用一個被定義好的includable tag. 把name填入includable的id, data則是要傳入includable的var裡面的變數.
用這樣的方式來呼叫includable, 就可以利用定義好的元件來產生想要的版面 - includable :
<b:includable id="'main'" var="'var'"></b:includable>
...
</b:includable>
定義一串版面排版, 再利用include來呼叫, 用來讓版面的設計更容易閱讀.
data tag :
用來產生資料的tag, 可以用blogger裡面的一些global data來使用. Layouts Data Tags介紹data tag要如何使用, 以及一些global的data像是 : blog, posts, labels, ... 利用這些global data就能讓我們自由的對資料做排版.
參考資料 :
海芋小站 - http://inote.tw/search/label/Blogger
Widget Tag for Layout - http://help.blogger.com/bin/answer.py?answer=46995&hlrm=en
Fun New Run High - http://skyvee.net/
[+/-] |
將blogger的主頁顯示標題, 以及點選展開看內文 |
最近用了blogger發現到它預設將文章完全展開的方式非常不利於閱讀, 尤其在找某篇文章的時候很麻煩. 因此我去網路上找到了這篇文章讓blogger(blogspot) 文章也有伸縮自如的效果, 它能讓你的文章只顯示標題, 然後點選[+/-]的時候會產生縮放文章內文的效果.
這個blog裡面的blogger tips標籤有很多有趣的技巧, 有時間值得來仔細看看.
參考資料 :
Fun New Run High
2008年5月21日 星期三
[+/-] |
Load相對應的圖片到pygame的image裡面 |
在遊戲裡面常常利用圖片的方式來呈現一個元素, 玩家角色, 敵人角色, 或是背景地圖等等. 如果把所有的圖片畫成一張大圖檔 (每個圖片以方形來區分界線), 該如何取得想要的部分呢 ?
下面這段code就是用來取得相對應的圖形的方法 :
rect = Rect(rect)
sheet = pygame.image.load(image_file).convert()
image = pygame.Surface(rect.size).convert()
image.blit(sheet, (0,0), rect)
- 先將圖檔讀入形成一個Surface (sheet)
- 產生讀取圖形的Surface (image)
- 最後將對應的位址從sheet這個Surface的rect中, 填入image這個Surface, 並且從(0,0)起始
參考資料 :
http://www.pygame.org/
[+/-] |
wxPython中的Dialog : wx.MessageDialog, wx.TextEntryDialog, wx.SingleChoiceDialog |
wx.MessageDialog : 簡單的yes/no對話框.
語法 :
wx.MessageDialog(parent, message,
caption="Message box",
style=wx.OK | wx.CANCEL,
pos=wx.DefaultPosition)
範例:
dlg = wx.MessageDialog(None, 'Is this the coolest thing ever!',
'MessageDialog', wx.YES_NO | wx.ICON_QUESTION)
result = dlg.ShowModal()
if result == wx.ID_YES:
#do yes
else:
#do no
dlg.Destroy()
解釋 :
ShowModal()會將視窗產生出來, 而當你點下了視窗的button以後, ShowModal會回傳wx裡面定義的巨集變數 : wx.ID_YES, wx.ID_NO, wx.ID_CANCEL, wx.ID_OK. 這些都是一個整數, 用來分辨ShowModal回傳的結果.
wx.TextEntryDialog: Dialog裡面有一個可以輸入Text的區域, 用來取得User的Input Text
dlg = wx.TextEntryDialog(None, "Who is buried in Grant's tomb?",
'A Question', 'Cary Grant')
if dlg.ShowModal() == wx.ID_OK:
#取得Dialog裡面的Text, SetValue(text)可以改變Dialog裡面Text的內容
response = dlg.GetValue()
wx.SingleChoiceDialog: 可以限定User從你指定的list裡面, 選擇一個想要的選項
dlg = wx.SingleChoiceDialog(None,
'What version of Python are you using?',
'Single Choice',
['1.5.2', '2.0', '2.1.3', '2.2', '2.3.1'],
if dlg.ShowModal() == wx.ID_OK:
#取得選擇選項的字串, GetSelection()可以取得選擇選項的index
response = dlg.GetStringSelection()
參考資料 :
http://www.pythontik.com/blog/article.asp?id=185
2008年5月20日 星期二
[+/-] |
asyncore : Python裡面的非同步Socket |
MFC裡面有個Class叫做CAsyncsocket, 它是用實作非同步的socket.
在實際的應用裡面, 一個應用程式很少會固定等待socket連線, 或是傳送資料, 通常都是在突然的情況下一個socket要進行連線, 或是要傳送資料. 所以非同步的socket的好處就在這裡, 你可以指定在socket發生連線或是傳送資料的時候進行某個動作, 而CAsyncsocket就是把這樣的非同步行為包成一個class讓我們只要繼承它就能達到這樣的效果.
而在Python裡面也有個非同步的module叫做asyncore. 我們實作的socket只要繼承asyncore.dispatcher就能進行非同步的socket運作.
下面就有Client和Server的範例code, 主要用途在於Client能連上Server, 然後Client和Server之間就能進行非同步的溝通, Client可以輸入訊息給Server, Server也能輸入訊息給Client.
Server程式 :
# -*- coding: cp950 -*-
import socket
import asyncore
import threading
MAX_RECV = 4096
#負責接受client socket連線
class AgentServer(asyncore.dispatcher):
def __init__(self, port):
#asyncore.dispatcher的constructor
asyncore.dispatcher.__init__(self)
#client socket
self.clientSocket = None
#server port
self.port = port
#建立等待的socket
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.bind(('', self.port))
self.listen(5)
def handle_accept(self):
#接受client socket的連線
self.clientSocket, address = self.accept()
print 'New client from : ' + address[0]
#將和client連線的socket加上一些功能 (自訂socket)
self.clientSocket = ClientAgent(self.clientSocket)
#自訂的client連線socket
class ClientAgent(asyncore.dispatcher):
def __init__(self, socket):
asyncore.dispatcher.__init__(self, socket)
#要送出的data
self.SendData = ""
#從client收到的data
def handle_read(self):
self.RecvData = self.recv(MAX_RECV)
if len(self.RecvData) > 0:
print "recv : " + self.RecvData
#送出data到client
def handle_write(self):
send_byte = self.send(self.SendData)
#一次可能不會全部送完(一次最多送出512 bytes ?)
if send_byte > 0:
send_out = self.SendData[:send_byte]
self.SendData = self.SendData[send_byte:]
self.handle_write()
else:
print "send all!!"
self.SendData = ""
#不自動執行handle_write
def writable(self):
return False
def handle_close(self):
print "close connection : " + self.getpeername()[0]
self.close()
#產生等待client連線的thread
class listen_client_thread(threading.Thread):
def __init__(self,port):
self.agentServer = AgentServer(port)
threading.Thread.__init__ ( self )
def run(self):
print "Listen Client ..."
asyncore.loop()
#產生處理輸入的thread
class input_thread(threading.Thread):
def __init__(self,listen_thread):
self.listen_thread = listen_thread
threading.Thread.__init__ ( self )
def run(self):
while 1:
send_data = raw_input()
self.listen_thread.agentServer.clientSocket.SendData = send_data
self.listen_thread.agentServer.clientSocket.handle_write()
if __name__ == "__main__":
#產生等待client連線的thread
listen_thread = listen_client_thread(111)
listen_thread.start()
#產生處理輸入的thread
input_thread(listen_thread).start()
Client程式 :
# -*- coding: cp950 -*-
import asyncore, socket
import threading
#收到data最大長度
MAX_RECV = 4096
#連線server的socket
class client(asyncore.dispatcher):
def __init__(self, host, port):
asyncore.dispatcher.__init__(self)
self.SendData = ""
self.RecvData = ""
#和server建立連線
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.connect( (host, port) )
def handle_connect(self):
print 'connect!!'
def handle_close(self):
print "disconnection : " + self.getpeername()[0]
self.close()
#收到的data
def handle_read(self):
self.RecvData = self.recv(MAX_RECV)
if len(self.RecvData) > 0:
print "recv : " + self.RecvData
#送出data
def handle_write(self):
send_byte = self.send(self.SendData)
if send_byte > 0:
send_out = self.SendData[:send_byte]
self.SendData = self.SendData[send_byte:]
print "send : " + send_out
self.handle_write()
else:
print "send all!!"
self.SendData = ""
#自動偵測送出永遠失敗
def writable(self):
return False
#等待server傳送訊息的thread
class send_server_thread(threading.Thread):
def __init__(self,host,port):
self.client = client(host, port)
threading.Thread.__init__ ( self )
def run(self):
try:
asyncore.loop()
except:
pass
#等待user input的thread
class input_thread(threading.Thread):
def __init__(self,client_thread):
self.client_thread = client_thread
threading.Thread.__init__ ( self )
def run(self):
while 1:
send_data = raw_input()
self.client_thread.client.SendData = send_data
self.client_thread.client.handle_write()
#主程式
if __name__ == "__main__":
#建立和server連線, 並且等待溝通
client_thread = send_server_thread("localhost",111)
client_thread.start()
#等待user input
input_thread(client_thread).start()
參考資料 :
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/483732
http://www.chmhome.com/Python_Nutshell/index.html?page=0596001886_pythonian-chp-19-sect-3.html
http://effbot.org/librarybook/asyncore.htm
2008年5月19日 星期一
[+/-] |
wxPython : wx.FileDialog |
wxPython有很多進階的widget元件可以使用, 利用這些元件很容易就能達到我們想要的UI.
File Dialog是常常會需要用到的元件, 我們要求使用者開啟某個檔案, 或是選擇某些檔案可以用這個元件來達成.
file_dialog = wx.FileDialog(self,style=wx.OPEN | wx.MULTIPLE | wx.CHANGE_DIR)
if file_dialog.ShowModal() == wx.ID_OK:
print file_dialog.GetPaths()
file_dialog.Destroy()
- 建立一個FileDialog (style可以選擇各種模式, wx.MULTIPLE可以選擇多個檔案),
- 顯示出file_dialog並且等待使用者選取
- 判斷是否選擇開啟的選項, 如果是的話就印出選擇的檔案
[+/-] |
wxPython : List Control |
List Control和List Box不同的地方在於, List Control的一筆資料可以有多個欄位, 這樣也讓我們的資料呈現方式更有彈性.
下面就介紹它的使用方法 :
self.list = wx.ListCtrl(panel, -1, style=wx.LC_REPORT)
建立List Control元件
self.list.InsertColumn(0, 'name', width=140)
self.list.InsertColumn(1, 'place', width=130)
self.list.InsertColumn(2, 'year', wx.LIST_FORMAT_RIGHT, 90)
定義List Control元件的Column名稱跟長度
index = self.list.InsertStringItem(sys.maxint, i[0])
self.list.SetStringItem(index, 1, i[1])
self.list.SetStringItem(index, 2, i[2])
把資料插入List Control裡面,
InsertStringItem : 將資料插入第sys.maxint row, 用sys.maxint確保資料從最後面開始插入,
function回傳值為插入的row的index, 第二個參數是第一個欄位的內容.
SetStringItem : 將資料插入index row的第1和第2個欄位, 裡面的內容分別為i[1], i[2]
參考資料 :
The wxPython tutorial
[+/-] |
wxPython : Context Menu, ListBox |
Context Menu就是我們按下滑鼠右鍵會跳出的Menu, 用來幫助我們選擇功能用的.
而在wxPython設定Context Menu的方法如下 :
- 定義wx.Menu元件
- 在要顯示Context Menu的widget元件裡面, wx.EVT_RIGHT_DOWN事件發生時, 執行下面指令 :
self.PopupMenu(MyContextMenu(self), event.GetPosition())
其中的MyContextMenu就是我們自己定義的Menu
其中主要的兩種event為:
- wx.EVT_COMMAND_LISTBOX_SELECTED : event binder wx.EVT_LISTBOX_DCLICK
是當ListBox裡面有元素被選取時(selected)產生的event - wx.EVT_COMMAND_LISTBOX_SELECTED : event binder wx.EVT_LISTBOX
當ListBox的元素被double click時產生的event
wx.ListBox(wx.Window parent, int id=-1, wx.Point pos=wx.DefaultPosition,
wx.Size size=wx.DefaultSize,list choices=[], long style=0,
wx.Validator validator=wx.DefaultValidator,string name=wx.ListBoxNameStr)
sel = listbox.GetSelection()
text = listbox.GetString(sel)
listbox.Delete(sel)
listbox.Clear()
listbox.Append(text)
listbox.Insert(text, sel)
以上為ListBox的一些operation.
參考資料 :
The wxPython tutorial
[+/-] |
wxWidget的介面設計IDE : wxGlade |
wxGalde是用來設計wxWidget的IDE工具, 可以利用它產生wxWidget的介面, 然後產生各種語言的code, 或是產生XRC file.
XRC file是把介面的排版用xml語言來描述, 程式只要透過xrc的api就可以將介面讀入程式中. 根據需要取出要加入code的widget元件. 可以把介面的設計和程式碼的部分分開, 有點類似設計網頁的感覺.這樣的好處就是能靠設計介面的高手將介面設計好, 而設計程式的人只要將程式碼套入介面的元件裡面程式就能完成.
設計一個視窗的程式, 當然要了解程式碼是如何運作的才能完整的應用它來達到自己想要的效果. 可是在設計時如果能有一個圖形化的IDE工具用視覺化的方式來輔助, 就能夠大大的提升效率. 所以在設計wxPython介面時, 希望能以wxGlade為主, 將介面拖拉出來, 不足的地方在以程式碼來補足. 更可以產生XRC file, 試著將介面和程式碼分開的方式來設計程式, 增加程式碼的可讀性.
[+/-] |
wxPython的Sizer : wx.GridBagSizer |
根據教學說明, wx.GridBagSizer可以再加入元件時指定要加在哪個格子裡, 對於UI排版應該會比較方便. 因為就不需要照順序加入Sizer, 而且對於規劃好的介面只要切好格子就可以知道要排在哪.
以下對他的function做說明
wx.GridBagSizer(integer vgap, integer hgap)
建立wx.GridBagSizer, 指定格子之間的垂直間隙(vgap), 和水平間隙(hgap)
Add(self, item, tuple pos, tuple span=wx.DefaultSpan, integer flag=0, integer border=0, userData=None)
加入一個widget item到Sizer裡面,
pos代表widget放入的格子座標(0,0)代表第一格,(0,1)代表往水平方向第一格, ...
span代表要讓widget佔據多少row多少col : (1,2)表示widget佔據1 row, 2 cols.
AddGrowableRow(int row, int propotion)
AddGrowableCol(int col, int propotion)
指定那一個row, col可以隨著視窗縮放而改變, propotion是改變大小的比例.
參考資料 :
The wxPython tutorial
2008年5月16日 星期五
[+/-] |
wxPython如何Layout GUI |
在wxPython裡面有兩種Layout方式 :
- Absolute positioning (絕對位址) :
指定widget的絕對位址就可以顯示在你想要的地方, 缺點是 :
1. window改變大小時, 用絕對位址指定的widget不會改變位址跟大小, 跟我們一般認知 不太一樣.
2. Layout要大改的時候, 程式通常都需要大改.
3. 在不同platform可能會看起來不太一樣.
優點則是很簡單完成, 不用考慮到每個元件的相對位址. - Sizer(相對位址) :
利用一個BoxSizer包在widget元件的外面, 所以當window改變的時候, BoxSizer會跟著改變, 當然裡面的widget元件也會跟著改變.
Sizer
- wx.BoxSizer : 基本的Sizer, 可以決定widget元件要水平排放還是垂直排放
- wx.StaticBoxSizer : 在wx.BoxSizer外面加入Text來說明裡面的元件的意義
- wx.GridSizer : 可以加入像格子一樣的Sizer, 可以決定格子的長寬是多少, 加入元件的順序是
由上而下, 由左而右. - wx.FlexGridSizer : 可以讓GridSizer的格子大小隨著Window size改變而變大變小, 使用function : AddGrowableRow, AddGrowableCol 來指定row還是col可以隨著視窗而改變
- wx.GridBagSizer : 可以不先指定GridSizer格子數, 而在加入元件時, 指定要加入在哪個(row,col)的格子裡, 方便對於介面做設計
# -*- coding: cp950 -*-
# border.py
import wx
class Border(wx.Frame):
def __init__(self, parent, id, title):
wx.Frame.__init__(self, parent, id, title, size=(250, 200))
#建立最外層的panel
panel = wx.Panel(self, -1)
panel.SetBackgroundColour('#4f5049')
#建立一個垂直的BoxSizer
vbox = wx.BoxSizer(wx.VERTICAL)
#建立內層的panel
midPan = wx.Panel(panel, -1)
midPan.SetBackgroundColour('#ededed')
#把內層的panel加入BoxSizer中
vbox.Add(midPan, 1, wx.EXPAND | wx.ALL, 5)
#建立內層的panel
midPan = wx.Panel(panel, -1)
midPan.SetBackgroundColour('#ededed')
#把內層的panel加入BoxSizer中
vbox.Add(midPan, 1, wx.EXPAND | wx.ALL, 5)
#設定外層panel的BoxSizer
panel.SetSizer(vbox)
self.Centre()
self.Show(True)
app = wx.App()
Border(None, -1, '')
app.MainLoop()
在這段程式裡面主要說明如何加入一個BoxSizer到panel裡面.
產生一個外層的panel, 他的用處在於放置我們最後產生的BoxSizer. 接下來在BoxSizer中加入兩個
panel, 最後再把BoxSizer設定到外層的panel.
BoxSizer裡面可以加入其他widget元件, 而且BoxSizer裡面也可以加入BoxSizer, 利用這樣的方式,
我們就可以組合出我們想要的UI介面.
參考資料 :
The wxPython tutorial
[+/-] |
Python的ODBC Library : pyodbc |
使用wxPython寫視窗程式以後, 想說利用wxWidgets的其他library來試試看, 結果發現到除了視窗程式的部分, wxPython並不是完全支援wxWidgets的所有library. wxODBC也是不支援的其中之一, 但是又很想在Python用 ODBC方式連到資料庫, 所以就去找到了pyodbc這個library.
當然好處就是除了最重要的免費以外, 他所支援的功能還算完全, 也有看到有人用它來寫odbc程式, 所以應該是能用, 等有機會再來試試看.
參考資料 :
pyodbc - A Python DB API module for ODBC
巫紘碩(GDX) - 學習日誌
[+/-] |
wxPython裡的元件 : Menubar |
基本上Menubar是由Menu組成的, 而Menu裡面則有很多的Menu item, 在wxPython裡面就是用這樣的方式來加入一個Menubar.
- 建立MenuBar
- 建立Menus
- 建立Menu items
- 將建立的Menu items加入Menu中
- 將建立好的Menus加入MenuBar
- 在視窗中(Frame)加入MenuBar
# -*- coding: cp950 -*-在建立Menuitem的function
import wx
#建立Frame
class SimpleMenu(wx.Frame):
def __init__(self, parent, id, title):
#呼叫wx.Frame的constructor
wx.Frame.__init__(self, parent, id, title, size=(250, 150))
#建立MenuBar
menu_bar = wx.MenuBar()
#建立Menu
menu = wx.Menu()
#建立Menu item
menu_item = wx.MenuItem(menu, 1, '&Quit\tCtrl+Q')
#把Menu item加入Menu裡面
menu.AppendItem(menu_item)
#把Menu item被按下對應到某個function
self.Bind(wx.EVT_MENU, self.OnQuit, id=1)
#把Menu加入MenuBar中
menu_bar.Append(menu, '&File')
#在視窗中加入MenuBar
self.SetMenuBar(menu_bar)
#讓視窗產生在螢幕中間
self.Centre()
#顯示視窗
self.Show(True)
#當按下Quit menu item時, 關閉視窗
def OnQuit(self,event):
self.Close()
#程式主要部分
if __name__ == "__main__": #確定這個程式是自己執行, 而非被呼叫
#建立wx.App
app = wx.App()
#建立並顯示視窗
SimpleMenu(None, -1, 'simple menu example')
#等待event
app.MainLoop()
menu_item = wx.MenuItem(menu, 1, '&Quit\tCtrl+Q')第二個參數是那個Menuitem的id,
用來對應到下面讓這個Menuitem被按下時執行某個function裡的id參數:
self.Bind(wx.EVT_MENU, self.OnQuit, id=1)其他功能 :
在menu加入submenu : (submenu也是wx.Menu)
menu.AppendMenu(-1, 'I&mport', submenu)menu separator :
menu.AppendSeparator()menu item check類型:
menu.Check(ID_STAT, True)
self.shst = menu.Append(ID_STAT, 'Show statubar', 'Show Statusbar', kind=wx.ITEM_CHECK)
#被檢查menu是否被checked
def ToggleStatusBar(self, event):
if self.shst.IsChecked():
self.statusbar.Show()
else:
self.statusbar.Hide()
參考資料 :
The wxPython tutorial
2008年5月15日 星期四
[+/-] |
wxPython裡面的API |
紀錄一下目前學到的wxPython API
wx.Frame(parent, id=-1, title='', pos = wx.DefaultPosition,
size = wx.DefaultSize, style = wx.DEFAULT_FRAME_STYLE,
name = "frame")
: 初始化window的function
parent : wx.Window
id : int
title : string
pos : wx.Point
size : wx.Size
style
name : string
Move(wx.Point point) | move a window to the given position |
MoveXY(int x, int y) | move a window to the given position |
SetPosition(wx.Point point) | set the position of a window |
SetDimensions(wx.Point point, wx.Size size) | set the position and the size of a window |
取得parent widget : parent.GetParent()參考資料 :
設定button按下event:
button1 = wx.Button(self, -1, '+', (10, 10))
self.Bind(wx.EVT_BUTTON, self.OnPlus, id=button1.GetId())
The wxPython tutorial
[+/-] |
簡單的wxPython程式 |
wxPython是wxWidgets(wxWindows)的Python版本.
wxWidgets是一個跨平台的視窗程式開發的library, 好處在於 :
- 開放原始碼
- 跨平台
- 大量的視窗元件可以使用
- 常用的功能也有函式庫支援
下面就以一個簡單的建立視窗的程式來講解一下wxPython建立視窗程式的運作.
# -*- coding: cp950 -*-程式第一行的
import wx
#建立視窗物件
class Frame(wx.Frame):
def __init__(self, parent, id, pos, title, size):
wx.Frame.__init__(self, parent, id, title, size)
#建立控制視窗的物件
class App(wx.App):
def OnInit(self):
self.frame = Frame(None, -1, wx.DefaultPosition,'視窗標題', (300,300))
self.frame.Show()
self.SetTopWindow(self.frame)
return True
#主程式
if __name__ == '__main__':
app = App()
app.MainLoop()
# -*- coding: cp950 -*-表示程式碼的編碼是用big5編碼, 如果沒有這一行註解裡面有中文會無法執行
程式用到的class
- Frame (繼承wx.Frame) : 建立一個視窗, __init__ 相當於C++裡面的constructor, 而self相當於傳入物件自己本身, 名字沒有限定要用self, 但是習慣上大家都是用self增加可讀性. __init__裡面呼叫Frame的parent的__init__ (constructor), 建立視窗.
- App : 利用Frame來產生視窗, 主要的控制都在App裡面完成, 包含設定視窗屬性, Event的控制等 等, 但是在這個簡單程式裡面沒有做Event的bind. 需要定義OnInit, OnInit會在產生App這個物件的Instance時被呼叫.
- 建立App的Instance
- 建立Instance時, App裡面的OnInit被呼叫. 在OnInit中建立並且Show出Frame (視窗).
- app.MainLoop() 等待事件產生
(注) tk也有人推薦, 但是功能好像比較沒有這麼完整(視窗元件), 好處是容易學習.
參考資料 :
wxPython In Action翻譯
The wxPython tutorial
Gary的Blog
[+/-] |
switch-case在Python裡面的做法 |
在Python裡面通常在判斷多種情況的時候都是用
if condition:
statements
elif condition:
statements
else condition:
statements
因為Python沒有switch-case語法. 然而其實沒有switch-case語法的原因在於利用Python內建的資料結構dictionary就可以達到這種效果.
一般我們的switch寫法會像下面這樣 :
而在Python裡面的做法是 :
switch(case)
{
case 1:
case_1();
break;
case 2:
case_2();
break;
...
}
把switch設定成dictionary, 然後每個case會對應到一個function, 這個function就是執行那個case的動作. 在呼叫的時候就指定switch裡面所對應到的function.
switch = {
1: case_1,
2: case_2
}
switch[case]()
在這個例子裡面是沒有帶參數的function, 實際上也可以帶參數. 例如 : switch[case](x).
參考內容 :
http://www.mustap.com/pythonzone_post_224_python-switch-statement
http://pythonly.blogspot.com/2008/04/python-switch-case.html
2008年5月14日 星期三
[+/-] |
PostgreSQL |
目前嘗試建立PostgreSQL然後利用ODBC連線看看,但是在建立table時就發生了問題...QQ.
問題是在於我建立table以後在select時會出現 :
ERROR: relation "financenews" does not exist
這樣的訊息
結果查了好久才發現原來是PostgreSQL裡面在下SQL指令時, 好像default會把沒有被"框起來的字串當作小寫, 所以我原本下的指令是 :
select * from FinanceNews 會被解釋成 select * from financenews
把指令改成select * from "FinanceNews"就沒有問題了.
心得 :
學習新的東西時, 雖然既有的概念可以幫助快速學習, 但是在產生錯誤時就要拋棄一些舊有的想法, 因為往往錯誤都是在一些小小不同處產生.
P.S. 好像說的往往都比較簡單, 看看下次是不是能有進步...XD
2008年5月13日 星期二
[+/-] |
利用ASSERT來檢查問題 |
前一陣子發生了一個問題, 我的程式平常很正常, 但是在某些時候程式會彈出一個視窗, 內容為 : 參數錯誤。. 我完全搞不清楚發生了什麼事, 而且即使發生這樣的問題程式依舊可以繼續執行, 但是這還是程式的bug, 所以一直在想辦法解決.
結果後來才發現到, 原來是我的程式是用VS 2005做為開發環境, 而我在跑的程式是用Release模式去compile的. 在release模式下ASSERT這個函式會被忽略, 恰巧我呼叫CString裡面的Tokenize這個function它會去檢查傳入的參數是否<0, 以確保分解字串不會產生錯誤. 在這樣的情況下ASSERT沒有被執行, 而在ASSERT之後就會產生參數錯誤這樣的視窗了.
所以ASSERT的功用在於檢查某個運算式, 如果為真則不會做什麼, 如果為判斷式為False會跳出ASSERT的訊息, 並且告訴你在哪個檔案的哪一行出現這個錯誤. 總體來說, 這個故事告訴我們 :
- 除非對自己的程式很有信心, 還是用debug模式去compile比較安全
- 雖然很多問題都可以靠google解決, 但是常常碰到的就是那種無法用google解決的bug, 所以做每個動作都要檢查清楚, 還有紀錄log以供參考
- windows上的程式可以用drwtsn32.exe來產生core file, 並且可以用VS 2005來還原案發現場
[+/-] |
Pygame : 小圖形的移動 |
下面這段code簡介了如何利用pygame來讓使用者操作一個小圖形讓它在某個範圍內移動, 利用這樣的概念我們就可以做出很簡單的小遊戲.
# -*- coding: utf8 -*-
import sys,pygame
from pygame.locals import *
'''
顯示畫面大小
'''
MAP_Y_AXIS = 30
MAP_X_AXIS = 30
IMAGE_X_LEN = 20
IMAGE_Y_LEN = 20
MAP_X_LEN = IMAGE_X_LEN * MAP_X_AXIS
MAP_Y_LEN = IMAGE_Y_LEN * MAP_Y_AXIS
'''
初始畫遊戲地圖
'''
game_map = []
for y in range(MAP_Y_AXIS):
row = []
for x in range(MAP_X_AXIS):
row.append(0)
game_map.append(row)
'''
畫出圖形
'''
def draw_image(pos):
square = pygame.Surface((IMAGE_X_LEN,IMAGE_Y_LEN)) #建立移動的圖形, 大小20x20的方塊
square.fill((255,255,255)) #圖形的顏色(r,g,b)
game_display.blit(square, (pos[0]*IMAGE_X_LEN,pos[1]*IMAGE_Y_LEN)) #把圖形畫在game_display上, 座標為(move[0],move[1])
'''
圖形移動判斷
'''
def move_image(pos, move):
if pos[0] + move[0] >= 0 and pos[0] + move[0] < MAP_X_AXIS:
pos[0] += move[0]
if pos[1] + move[1] >= 0 and pos[1] + move[1] < MAP_Y_AXIS:
pos[1] += move[1]
return pos
pygame.init() #初始化pygame
game_display = pygame.display.set_mode((MAP_X_LEN,MAP_Y_LEN)) #設定遊戲的顯示螢幕大小
clock = pygame.time.Clock()
move = [0,0]
pos = [0,0] #目前的座標
while 1:
move = [0,0] #每次的移動
clock.tick(10) #設定執行的頻率
'''
結束遊戲
'''
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
keys = pygame.key.get_pressed() #取得按鍵
'''
判斷按下的按鍵
'''
if keys[K_RIGHT]:
move[0] = 1
if keys[K_LEFT]:
move[0] = -1
if keys[K_DOWN]:
move[1] = 1
if keys[K_UP]:
move[1] = -1
'''
畫面更新
'''
pos = move_image(pos,move) #移動圖形到某個座標上
game_display.fill((0,0,0)) #背景黑色
draw_image(pos) #畫圖形
pygame.display.flip() #更新畫面
上面這段code大致上都有註解, 簡單的講解一下流程 :
- 宣告畫面大小的變數
- 初始化pygame, 畫面大小, 以及畫面顯示的clock
- 執行迴圈, 每次取得按下的按鍵
- 更新圖片的位址, 畫下圖形, 更新畫面
P.S. 這個程式裡面的game_map變數沒有用到, 利用這個程式延伸遊戲時, 可以紀錄目前某個座標所在的物件為何.
[+/-] |
連接資料庫的方式 - ODBC |
透過ODBC (Open Database Connectivity)的方式, 我們可以利用它提供的API來對我們想要連結的資料庫進行運作. 它的好處就是可以和使用的資料庫無關, 即使換一個資料庫也只要把相對應的ODBC Driver給換掉就好了, 程式碼的部分大致上可以保持不用更動.
如何利用ODBC連結不同資料庫系統提供了各種不同資料庫系統如何設定ODBC連結字串, 在這裡就可以找到你要連結的資料庫系統如何設定ODBC.
[+/-] |
Python裡面寫遊戲的module : pygame |
pygame是python裡面的一個寫遊戲的module, 它是建構在有名的SDL Library上. 利用它裡面的module, 你可以很容易的來寫一個遊戲.
pygame的主站 : http://www.pygame.org/wiki/about
裡面有介紹pygame的文件, 還有一些別人利用pygame所做出來的有趣遊戲在裡面. 我個人也是利用了這個module寫了一個俄羅斯方塊來玩玩, 順便熟悉python的語法. 從遊戲中學習可能是一個快速而且能增加動機的方式.
[+/-] |
編寫快速, 支援很多modules的語言 : Python |
最近迷上了這個相當容易編寫的語言, 有格式的空隔方式, 能和C/C++溝通, 以及支援大量的modules, 從各方面來看這個語言有一種我心目中理想模式, 那就是不用根據不同的應用來學新的語言. 不論在處理文字方面, 設計網頁, 網路程式, 資料庫等等 ... , 都能用Python搞定.
它使用直譯器來執行, 從某種方面執行速度也許會比較慢, 但是卻增加編寫程式的速度, 也增加了學習的速度.因為你可以直接下交互模式下(Interactive mode)下語法的指令. 對某個語法有疑問, 可以直接試試看. 例如 : 某個module是否存在系統中直接import看看就知道了. 這樣的方便, 更加強了像我這樣懶人學習的意願.
入門的介紹文件也很多以下提供有興趣的人參考 :
[+/-] |
我的第一篇blog~~ |
為了避免自己懶的做事情, 以及身為一個資訊人(?)應該要為這個環境貢獻一點資源,
所以決定開始來寫blog.
希望能督促自己完成一些事情, 也可以讓生活不要這麼單調, 更重要的是能把一些錯誤(bug)
的發生和解決寫出來供大家參考, 雖然有些真的是蠻蠢的錯誤, 但是常常你的蠢錯誤, 也會是
別人的蠢錯誤. 像我就常常在別人的blog裡面找到一些珍貴的資源, 雖然對他而言也許是沒有
什麼大不了的一句話, 但是對我而言卻可以節省很多時間.
所以我希望能透過我小小的分享, 來增加google大神解決問題的能力...XD
還有更重要的是希望能加強寫作能力, 能夠透過寫blog來增加自己表達能力, 讓別人能很簡單
的透過我的表達獲得些什麼, 而不是無意義的將自己的資料堆給別人, 希望能帶給別人的是知識.