wxPython和pycairo练习记录10
这次写地图类和简易的地图编辑器。
10.1 地图类
简单按二维平铺地图中的障碍,地图以 json 格式保存。
self.obstacles = []
data = """{
"cellNumX": 10,
"cellNumY": 2,
"cellWidth": 48,
"cellHeight": 48,
"data": [2, 1, 2, 1, 2, 3, 4, 5, 4, 4,
2, 1, 2, 7, 0, 3, 6, 5, 4, 4,
2, 1, 2, 1, 2, 3, 4, 5, 4, 4]
}"""
map = Map(data)
map.Show()
self.obstacles += map.GetObjects()
# -*- coding: utf-8 -*-
# map.py
import json
from obstacle import *
class Map:
def __init__(self, data):
# 障碍类子类列表 [1Brick, 2Grass, 3Water, 4Ice, 5Iron, 6Blockhouse, 7Mud, 8Transmission]
self._obstacles = [Obstacle] + Obstacle.__subclasses__() # 0为空
self._cellNumX = 0 # 横轴网格数
self._cellNumY = 0 # 纵轴网格数
self._cellWidth = 0 # 网格宽
self._cellHeight = 0 # 网格高
self._data = [] # 地图障碍编号数据列表
self._objects = [] # 障碍实例,用于碰撞检测
self.Load(data)
def Load(self, data):
# 导入 json 格式地图数据,包括地图网格格数,网格宽高,障碍编号
map = json.loads(data)
self._cellNumX = map["cellNumX"]
self._cellNumY = map["cellNumY"]
self._cellWidth = map["cellWidth"]
self._cellHeight = map["cellHeight"]
self._data = map["data"]
def Show(self):
row = 0
for column, x in enumerate(self._data):
column = column % self._cellNumX
# 实例化障碍, 0 不显示
if x != 0:
obstacle = self._obstacles[x](self._cellWidth * column, self._cellHeight * row)
self._objects.append(obstacle)
if column == self._cellNumX - 1:
row += 1
def GetObjects(self):
return self._objects
10.2 地图编辑器
地图编辑器,简单实现导入素材图片,设置网格,平铺砖块,导出地图 json 。
只保留必要的功能。
画出简单原型图。
组件分析。网格可以用 wx.grid.Grid (发现 Grid 控件用在这里功能有些累赘,其实需要的只是网格的边框线);右侧砖块列表中可以用图片按钮,右侧属性用wx.ListCtrl;网格和素材的设置,地图的导入导出,放在窗口的菜单部分。
地图网格组件,绘图主要是网格线和单元格图片,事件主要是鼠标拖动事件、点击事件,尺寸过大时还需要滚动条。
砖块素材管理组件,绘图主要是砖块列表、属性列表,砖块列表默认编号为0的空砖块,属性列表可以直接用自带控件。砖块列表可以用图片按钮控件。
wx.Image 和 wx.Bitmap 的区别
图片数据需要根据需求作出转换。
wx.Image只是一个RGB字节的缓冲区,还有一个可选的alpha字节的缓冲区。它是所有通用的、独立于平台和图像文件格式的代码。它包括用于缩放、调整大小、剪裁和其他图像数据操作的通用代码。相反,wx.Bitmap旨在成为一个包装器,用于包装最快速/最容易绘制到DC的本地图像格式,或成为在wx.MemoryDC上执行的绘制操作的目标。通过像这样在Image/wxBitmap之间划分责任,那么就更容易使用所有平台和图像类型共享的通用代码进行通用操作,而在需要性能或兼容性的地方使用平台特定代码。
出自:https://docs.wxpython.org/wx.Bitmap.html
翻译自:https://www.deepl.com/
滚动地图网格
一种方案是地图网格用不带滚动的面板实现,外面再加一层可滚动的容器,这样绘制时不用自己计算鼠标相对位置,没有特别要注意的地方;一种方案是网格继承 wx.ScrolledWindow 或 wx.lib.scrolledpanel.ScrolledPanel ,绘制的时候需要调用 self.PrepareDC(dc) ,不然滚动不会绘制,滚动后鼠标相对坐标获取需要调用 self.CalcUnscrolledPosition(event.GetPosition()) ,鼠标在窗口拖动时自动滚动滚动条可以参考 wxPython demo 中的 DragScroller 。最好滚动还是单独作为一个操作,测试的时候边滚动边绘制体验并不好。
参考:evt.GetPosition() in wx.ScrolledWindow https://groups.google.com/g/wxpython-users/c/0VlpIcBYs04
可编辑 ListCtrl
点击砖块素材显示砖块相关属性,可直接编辑 ID 和图片路径。这里没写。
参考:https://www.blog.pythonlibrary.org/2011/01/04/wxpython-wx-listctrl-tips-and-tricks/
鼠标光标
选中砖块素材后,鼠标光标变为砖块缩略图。创建光标实例 wx.Cursor(image:wx.Image) ,然后调用控件方法 SetCursor 。
wx.GridBagSizer
用 wx.GridBagSizer 设置布局,就像是在 Excel 画好格子,然后放置控件。因为是弹性布局,格子的尺寸直接受控件尺寸影响,比如静态文本字太多的话,和它同一列的控件就不好控制位置,就需要把静态文本所占的列划分成很多列。
最后
一开始考虑不全面,后面写的时候就只有一个心思,烂代码,完成就好。代码创建控件实例是个痛苦的过程,因为做的基本都是重复的事,能用循环的最好写进循环,熟悉控件后推荐用 wxFromBuilder 。EXE文件下载地址:https://download.csdn.net/download/SmileBasic/86507349
# -*- coding: utf-8 -*-
# https://blog.csdn.net/SmileBasic
import os
import sys
from collections import OrderedDict
import json
import wx
import wx.lib.wxcairo
import wx.lib.scrolledpanel as scrolled
import wx.lib.dragscroller
class cv:
BOARD_WIDTH = 1000
BOARD_HEIGHT = 750
MAP_GAP = 10 # 网格留白
MAP_TILE_SIZE = (48, 48) # 砖块素材列表砖块尺寸
CURSOR_TILE_SIZE = (32, 32) # 鼠标箭头砖块尺寸
tiles = OrderedDict() # 砖块对象字典,以 ID 为键,有序字典便于素材列表顺序排列
currentTileId = 0 # 当前砖块 ID
def get_resource_path(relative_path):
# https://www.bilibili.com/read/cv12744142
if hasattr(sys, '_MEIPASS'):
return os.path.join(sys._MEIPASS, relative_path)
return os.path.join(os.path.abspath("."), relative_path)
class MapGrid(scrolled.ScrolledPanel):
"""
地图网格控件,网格线、单元格砖块绘制及鼠标事件处理
"""
def __init__(self, parent, cellNumX=20, cellNumY=15, cellWidth=48, cellHeight=48):
self._width = cellNumX * cellWidth + cv.MAP_GAP * 2 # 网格总宽度
self._height = cellNumY * cellHeight + cv.MAP_GAP * 2 # 网格总高度
super(MapGrid, self).__init__(parent=parent, size=(self._width, self._height), style=wx.SIMPLE_BORDER)
self._cellNumX = cellNumX # 横轴网格数
self._cellNumY = cellNumY # 纵轴网格数
self._cellWidth = cellWidth # 网格宽
self._cellHeight = cellHeight # 网格高
self._cells = [] # 地图数据二维数组
self._cursorPosition = (0, 0) # 鼠标坐标,用于更新笔刷
self.InitUI()
self.BindEvents()
def InitUI(self):
self.SetDoubleBuffered(True) # 双缓冲,防闪烁
# TODO 诡异的地方,每次打开软件初始的滚动条位置都不对
self.SetScrollbars(1, 1, self._width, self._height, 0, 0) # 设置滚动条
# 初始化地图数据二维数组,全部填充0
self.ClearMap()
def BindEvents(self):
self.Bind(wx.EVT_PAINT, self.OnPaint)
self.Bind(wx.EVT_MOUSE_EVENTS, self.OnDrag)
self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
self.Bind(wx.EVT_RIGHT_DOWN, self.OnRightDown)
def OnPaint(self, event):
# TODO 鼠标拖动绘制有时会出现断线
dc = wx.PaintDC(window=self)
self.PrepareDC(dc)
ctx = wx.lib.wxcairo.ContextFromDC(dc)
self.DrawGrid(ctx)
self.DrawCell(ctx)
def DrawGrid(self, ctx):
# 绘制网格
ctx.set_source_rgb(0.8, 0.8, 0.8)
ctx.set_dash([2, 2]) # 虚线,2像素线,2像素空
# 绘制竖线
for i in range(self._cellNumX + 1):
x = i * self._cellWidth + cv.MAP_GAP
ctx.move_to(x , cv.MAP_GAP)
ctx.line_to(x, self._height - cv.MAP_GAP)
ctx.stroke()
# 绘制横线
for j in range(self._cellNumY + 1):
y = j * self._cellHeight + cv.MAP_GAP
ctx.move_to(cv.MAP_GAP, y)
ctx.line_to(self._width - cv.MAP_GAP, y)
ctx.stroke()
def DrawCell(self, ctx):
for i in range(self._cellNumY):
for j in range(self._cellNumX):
tile = cv.tiles.get(self._cells[i][j])
if tile and tile.id != 0:
surface = tile.Scale((self._cellWidth, self._cellHeight)) # 缩放到地图设置砖块尺寸
ctx.set_source_surface(surface, self._cellWidth * j + cv.MAP_GAP, self._cellHeight * i + cv.MAP_GAP)
ctx.paint()
def _SetCell(self, position):
x, y = position
if (x > cv.MAP_GAP and x < self._width - cv.MAP_GAP) and (y > cv.MAP_GAP and y < self._height - cv.MAP_GAP):
# 鼠标拖动时所在行和列
column = (x - cv.MAP_GAP) // self._cellWidth
row = (y - cv.MAP_GAP) // self._cellHeight
# 将鼠标拖动经过单元格砖块编号设为笔刷砖块编号
self._cells[row][column] = cv.currentTileId
self.Refresh() # 重绘
def OnDrag(self, event):
# 响应鼠标拖动事件
self._cursorPosition = self.CalcUnscrolledPosition(event.GetPosition())
if event.Dragging():
if event.LeftIsDown():
self._SetCell(self._cursorPosition)
elif event.RightIsDown():
# 右键拖动清除砖块
tmp = cv.currentTileId
cv.currentTileId = 0
self._SetCell(self._cursorPosition)
cv.currentTileId = tmp
self.Refresh()
def OnLeftDown(self, event):
position = self.CalcUnscrolledPosition(event.GetPosition())
self._SetCell(position)
def OnRightDown(self, event):
# 右键单击清除砖块
position = self.CalcUnscrolledPosition(event.GetPosition())
tmp = cv.currentTileId
cv.currentTileId = 0
self._SetCell(position)
cv.currentTileId = tmp
def ClearMap(self):
self._cells.clear()
for i in range(self._cellNumY):
row = []
for j in range(self._cellNumX):
row.append(0)
self._cells.append(row)
self.Refresh()
def CreateMap(self):
dlg = NewMapDialog(parent=self, title="新建地图")
dlg.CenterOnScreen() # 居中显示
val = dlg.ShowModal() # 模态窗口
if val == wx.ID_OK:
textCtrls = [x for x in dlg.Children if isinstance(x, wx.TextCtrl)]
self._cellNumX = int(textCtrls[0].GetValue().strip())
self._cellNumY = int(textCtrls[1].GetValue().strip())
self._cellWidth = int(textCtrls[2].GetValue().strip())
self._cellHeight = int(textCtrls[3].GetValue().strip())
self._width = self._cellNumX * self._cellWidth + cv.MAP_GAP * 2
self._height = self._cellNumY * self._cellHeight + cv.MAP_GAP * 2
self.SetScrollbars(1, 1, self._width, self._height, 0, 0)
self.ClearMap()
dlg.Destroy()
def ExprotData(self):
data = {}
data["cellNumX"] = self._cellNumX
data["cellNumY"] = self._cellNumY
data["cellWidth"] = self._cellWidth
data["cellHeight"] = self._cellHeight
data["data"] = [x for y in self._cells for x in y]
return data
def ImportData(self, data):
self._cellNumX = data["cellNumX"]
self._cellNumY = data["cellNumY"]
self._cellWidth = data["cellWidth"]
self._cellHeight = data["cellHeight"]
self._width = self._cellNumX * self._cellWidth + cv.MAP_GAP * 2
self._height = self._cellNumY * self._cellHeight + cv.MAP_GAP * 2
self.SetScrollbars(1, 1, self._width, self._height, 0, 0)
self._cells.clear()
for i in range(self._cellNumY):
row = []
for j in range(self._cellNumX):
row.append(data["data"][i * self._cellNumX + j])
self._cells.append(row)
self.Refresh()
class NewMapDialog(wx.Dialog):
def __init__(self, *args, **kwargs):
super(NewMapDialog, self).__init__(*args, **kwargs)
grid = wx.GridBagSizer(vgap=5, hgap=5)
lst = ["横轴块数", "纵轴块数", "砖块宽度", "砖块高度"]
for i, x in enumerate(lst):
grid.Add(wx.StaticText(parent=self, label=x + ":"),
pos=(i, 0),
span=(1, 2),
flag=wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT | wx.LEFT | wx.TOP,
border=10)
grid.Add(wx.TextCtrl(parent=self),
pos=(i, 2),
span=(1, 5),
flag=wx.EXPAND | wx.ALIGN_LEFT | wx.TOP | wx.RIGHT,
border=5)
grid.Add(wx.Button(self, wx.ID_OK),
pos=(4, 1),
span=(1, 2),
flag=wx.TOP | wx.BOTTOM | wx.RIGHT,
border=10)
grid.Add(wx.Button(self, wx.ID_CANCEL),
pos=(4, 3),
span=(1, 2),
flag=wx.TOP | wx.BOTTOM | wx.LEFT,
border=10)
self.SetSizer(grid)
grid.Fit(self)
class Tile:
"""
砖块对象,存储砖块ID、尺寸、图像
"""
def __init__(self, id, x, y, image):
self.id = id
self.x = x
self.y = y
self.image = image
self.bitmap = wx.Bitmap(image, wx.BITMAP_TYPE_PNG)
self.surface = wx.lib.wxcairo.ImageSurfaceFromBitmap(self.bitmap) # 原始尺寸 surface
self.width = self.surface.get_width()
self.height = self.surface.get_height()
self.surfaceCursor = self.Scale(cv.CURSOR_TILE_SIZE)
self.imageCursor = wx.lib.wxcairo.BitmapFromImageSurface(self.surfaceCursor).ConvertToImage()
self.surfaceMap = self.Scale(cv.MAP_TILE_SIZE)
def Scale(self, destSize=(16, 16)):
img = self.bitmap.ConvertToImage() # 转换成 wx.Image 对象
img = img.Scale(*destSize) # 缩放到目标尺寸
bitmap = wx.Bitmap(img, wx.BITMAP_SCREEN_DEPTH)
surface = wx.lib.wxcairo.ImageSurfaceFromBitmap(bitmap)
return surface
class MapTile(wx.Panel):
"""
地图砖块素材控件,管理和显示砖块素材
"""
def __init__(self, parent):
super(MapTile, self).__init__(parent=parent, style=wx.SIMPLE_BORDER)
self._tiles = {} # 砖块对象
self.InitUI()
self.BindEvents()
def InitUI(self):
self._lastPosition = (0, 0) # 最近一个 tile 坐标
self._lastId = 0 # 最近一个 tile id
self.topScrollPanel = scrolled.ScrolledPanel(parent=self, style=wx.BORDER_RAISED)
self.topScrollPanel.SetupScrolling(scroll_x=False, scroll_y=True)
self.topPanel = wx.Panel(parent=self.topScrollPanel)
vbox = wx.BoxSizer(orient=wx.VERTICAL)
vbox.Add(self.topPanel, proportion=1, flag=wx.EXPAND)
self.topScrollPanel.SetSizer(vbox)
# TODO 使用可编辑 ListCtrl
self.listCtrl = wx.ListCtrl(parent=self, style=wx.LC_REPORT | wx.LC_HRULES | wx.LC_VRULES)
self.listCtrl.InsertColumn(0, '参数', width=60)
self.listCtrl.InsertColumn(1, '值', width=120)
self.idIndex = self.listCtrl.InsertItem(0, "ID")
self.widthIndex = self.listCtrl.InsertItem(1, "宽度")
self.heightIndex = self.listCtrl.InsertItem(2, "高度")
self.imageIndex = self.listCtrl.InsertItem(3, "图片")
# 默认放置置空砖块
tile = Tile(0, 0, 0, get_resource_path("delete.png"))
cv.tiles[0] = tile
wx.BitmapButton(parent=self.topPanel, bitmap=tile.bitmap, style=wx.BORDER_NONE,
name="0", pos=(0, 0), size=cv.MAP_TILE_SIZE)
vbox = wx.BoxSizer(orient=wx.VERTICAL)
vbox.Add(self.topScrollPanel, proportion=3, flag=wx.EXPAND | wx.ALL, border=10)
vbox.Add(self.listCtrl, flag=wx.EXPAND | wx.ALL, border=10)
self.SetSizer(vbox)
self._SetCursor() # 设置鼠标样式
def BindEvents(self):
self.Bind(wx.EVT_BUTTON, self.OnButton)
def OnButton(self, event):
# 鼠标左击按按钮事件,设置当前砖块
cv.currentTileId = int(event.GetEventObject().GetName())
tile = cv.tiles.get(cv.currentTileId)
self._SetCursor()
self.listCtrl.SetItem(self.idIndex, 1, str(tile.id))
self.listCtrl.SetItem(self.widthIndex, 1, str(tile.width))
self.listCtrl.SetItem(self.heightIndex, 1, str(tile.height))
self.listCtrl.SetItem(self.imageIndex, 1, str(tile.image))
def OnRightDown(self, event):
btn = event.GetEventObject() # 获取右键点击对象
cv.tiles.pop(int(btn.GetName())) # 从砖块字典删除
btn.Destroy() # 清除按钮
# 将 topPanel 子控件按钮重新排序
x = 0
y = 0
for button in self.topPanel.Children:
button.SetPosition((x, y))
self._lastPosition = (x, y)
x += cv.MAP_TILE_SIZE[0] + 2
if x > self.topPanel.Size[0] - (cv.MAP_TILE_SIZE[0] + 2):
x = 0
y += cv.MAP_TILE_SIZE[1] + 2
self.Refresh()
self.Parent.mapGrid.Refresh() # 刷新网格,网格中相应已绘制砖块也清除
def _SetCursor(self):
tile = cv.tiles.get(cv.currentTileId)
cursor = wx.Cursor(tile.imageCursor)
# 设置网格和砖块素材面板的鼠标样式
self.Parent.mapGrid.SetCursor(cursor)
self.topPanel.SetCursor(cursor)
def AddTile(self, path):
self._lastId += 1
x, y = self._lastPosition
x += cv.MAP_TILE_SIZE[0] + 2
if x > self.topPanel.Size[0] - (cv.MAP_TILE_SIZE[0] + 2):
x = 0
y += cv.MAP_TILE_SIZE[1] + 2
self._lastPosition = (x, y)
tile = Tile(self._lastId, x, y, path)
cv.tiles[tile.id] = tile
# 将砖块实例化为图片按钮,并绑定右键事件
btn = wx.BitmapButton(parent=self.topPanel,
bitmap=tile.bitmap,
style=wx.BORDER_NONE,
name=str(tile.id),
pos=(tile.x, tile.y),
size=cv.MAP_TILE_SIZE)
btn.Bind(wx.EVT_RIGHT_DOWN, self.OnRightDown)
def ImportTile(self):
wildcard = "图片 (*.bmp;*.gif;*.jpg;*.jpeg;*.png)|*.bmp;*.gif;*.jpg;*.jpeg;*.png|" \
"All files (*.*)|*.*"
dlg = wx.FileDialog(
self, message="Choose a file",
defaultDir=os.getcwd(),
defaultFile="",
wildcard=wildcard,
style=wx.FD_OPEN | wx.FD_MULTIPLE |
wx.FD_CHANGE_DIR | wx.FD_FILE_MUST_EXIST |
wx.FD_PREVIEW
)
if dlg.ShowModal() == wx.ID_OK:
for path in dlg.GetPaths():
self.AddTile(path)
dlg.Destroy()
self.Refresh()
def ClearTile(self):
# 清除除空以外的所有砖块
for button in self.topPanel.Children:
if button.GetName() != "0":
cv.tiles.pop(int(button.GetName()))
button.Destroy()
self._lastPosition = (0, 0)
self._lastId = 0
self.Refresh()
self.Parent.mapGrid.Refresh()
def ExportData(self):
data = []
tiles = [cv.tiles.get(int(x.GetName())) for x in self.topPanel.Children][1:]
for tile in tiles:
tmp = {}
tmp["id"] = tile.id
tmp["width"] = tile.width
tmp["height"] = tile.height
tmp["image"] = tile.image
data.append(tmp)
return data
def ImportData(self, data):
self.ClearTile()
for tileData in data["tiles"]:
self._lastId += 1
x, y = self._lastPosition
x += cv.MAP_TILE_SIZE[0] + 2
if x > self.topPanel.Size[0] - (cv.MAP_TILE_SIZE[0] + 2):
x = 0
y += cv.MAP_TILE_SIZE[1] + 2
self._lastPosition = (x, y)
tile = Tile(self._lastId, x, y, tileData["image"])
cv.tiles[tile.id] = tile
btn = wx.BitmapButton(parent=self.topPanel,
bitmap=tile.bitmap,
style=wx.BORDER_NONE,
name=str(tile.id),
pos=(tile.x, tile.y),
size=cv.MAP_TILE_SIZE)
btn.Bind(wx.EVT_RIGHT_DOWN, self.OnRightDown)
class MapPanel(wx.Panel):
"""
主面板,布局管理
"""
def __init__(self, parent):
super(MapPanel, self).__init__(parent=parent)
self.InitUI()
def InitUI(self):
self.mapGrid = MapGrid(parent=self)
self.mapTile = MapTile(parent=self)
hbox = wx.BoxSizer(orient=wx.HORIZONTAL)
hbox.Add(self.mapGrid, proportion=1, flag=wx.EXPAND | wx.ALL, border=25)
hbox.Add(self.mapTile, flag=wx.EXPAND | wx.ALL ^ wx.LEFT, border=25)
self.SetSizer(hbox)
class MapFrame(wx.Frame):
def __init__(self, *args, **kwargs):
super(MapFrame, self).__init__(*args, **kwargs)
self.map = MapPanel(parent=self)
self.Center()
self.SetMenu()
self.Bind(wx.EVT_MENU, self.OnMenu)
def SetMenu(self):
# TODO 菜单整理
menuBar = wx.MenuBar()
# 1st menu from left
menu1 = wx.Menu()
menu1.Append(101, "&新建地图")
menu1.Append(102, "&导出地图")
menu1.Append(103, "&导入地图")
menuBar.Append(menu1, "&地图(M)")
menu2 = wx.Menu()
menu2.Append(201, "&导入素材")
menuBar.Append(menu2, "&砖块(T)")
menu3 = wx.Menu()
menu3.Append(301, "&清空网格")
menu3.Append(302, "&清空素材")
menuBar.Append(menu3, "&清空(C)")
self.SetMenuBar(menuBar)
self.grid = self.map.mapGrid
self.tile = self.map.mapTile
self.menus = {
101: self.grid.CreateMap,
102: self.ExportMap,
103: self.ImportMap,
201: self.tile.ImportTile,
301: self.grid.ClearMap,
302: self.tile.ClearTile
}
def OnMenu(self, event):
id = event.GetId()
self.menus[id]()
def ExportMap(self):
gridData = self.grid.ExprotData()
tileData = self.tile.ExportData()
gridData["tiles"] = tileData
result = json.dumps(gridData)
wildcard = "地图 (*.map)|*.map|" \
"All files (*.*)|*.*"
dlg = wx.FileDialog(
self, message="导出地图", defaultDir=os.getcwd(),
defaultFile="", wildcard=wildcard, style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT
)
if dlg.ShowModal() == wx.ID_OK:
with open(dlg.GetPath(), "w") as f:
f.write(result)
def ImportMap(self):
wildcard = "地图 (*.map)|*.map|" \
"All files (*.*)|*.*"
dlg = wx.FileDialog(
self, message="导入地图",
defaultDir=os.getcwd(),
defaultFile="",
wildcard=wildcard,
style=wx.FD_OPEN |
wx.FD_CHANGE_DIR | wx.FD_FILE_MUST_EXIST |
wx.FD_PREVIEW
)
if dlg.ShowModal() == wx.ID_OK:
with open(dlg.GetPath(), "r") as f:
data = json.loads(f.read())
self.grid.ImportData(data)
self.tile.ImportData(data)
dlg.Destroy()
def main():
app = wx.App()
frame = MapFrame(parent=None, title="地图编辑器", size=(cv.BOARD_WIDTH, cv.BOARD_HEIGHT))
frame.Show()
app.MainLoop()
if __name__ == "__main__":
main()