ใน chapter นี้ จะพูดถึง 4 ส่วนประกอบ นี้ เป็นหลัก คือ toolbox, drawing space, general options, and status bar ทั้ง 4 ส่วนประกอบนี้จะมีการโต้ตอบกัน ซึ่งเรามีความจำเป็นต้องเพิ่ม attributes ไปที่ class ของ project สามารถแสดงความสัมพันธ์ในไฟล์ comiccreator ได้ดังนี้
ภาพจากหนังสือ Kivy : Interactive Applications in Python
การสร้าง ids ด้วยตัวเอง ไม่ควรนำไปใช้ภายนอกภาษา Kivy ควรสร้าง attributes ไว้เรียกใช้ใน Python Code
จากโค้ด comiccreator.kv เรานำมาดัดแปลงดังนี้
# File name: comiccreator.kv
#:kivy 1.7.0
<ComicCreator>:
AnchorLayout:
anchor_x: 'left'
anchor_y: 'top'
ToolBox:
id: _tool_box
drawing_space: _drawing_space
comic_creator: root
size_hint: None,None
width: 100
AnchorLayout:
anchor_x: 'right'
anchor_y: 'top'
DrawingSpace:
id: _drawing_space
status_bar: _status_bar
general_options: _general_options
tool_box: _tool_box
size_hint: None,None
width: root.width - _tool_box.width
height: root.height - _general_options.height - _status_bar.height
AnchorLayout:
anchor_x: 'center'
anchor_y: 'bottom'
BoxLayout:
orientation: 'vertical'
GeneralOptions:
id: _general_options
drawing_space: _drawing_space
comic_creator: root
size_hint: 1,None
height: 48
StatusBar:
id: _status_bar
size_hint: 1,None
height: 24
IDs ที่ highlight ด้วยสีเหลือง คือการประกาศตัวแปร ส่วนที่ hightlight ด้วยสีฟ้า คือการใช้ ids สร้าง attributes มีความสัมพันธ์ดัง comiccreator diagram ก่อนหน้านี้
ชื่อของ attributes ไม่จำเป็นต้องเปลี่ยนแปลงมาก แค่เพิ่ม _ข้างหน้า ก็พอ เช่น _status_bar
Basic widget events – dragging the stickman
ตัวอย่าง basic widget events
mouse event , finger event , pen event
ตัวอย่าง event 3 แบบ
on_touch_down : เมื่อกดคลิกบนหน้าจอ หรือ ทัชบนหน้าจอโปรแกรม
on_touch_move : เมื่อกดคลิกแล้วย้าย เช่น ลากเมาส์
on_touch_up : เมื่อปล่อยเมาส์ หรือ เอานิ้วออกจากหน้าจอ
on_touch_down กับ on_touch_up จะไม่ค่อยมีปัญหาในการแสดงผล แต่ on_touch_move จะไม่แสดงผลที่ไม่มี dragging action วิธีการ add ความสามารถในการ drag สามารถทำได้โดย ดัดแปลงโค้ดดังนี้
โค้ด comicwidgets.kv (add drag capability)
# File name: comicwidgets.kv
#:kivy 1.7.0
#:import comicwidgets comicwidgets
<DraggableWidget>:
size_hint: None, None
<StickMan>:
size: 48,48
...
ส่วน comicwidgets.py ให้ดัดแปลงโค้ดดังนี้
# File name: comicwidgets.py
import kivy
kivy.require('1.7.0')
from kivy.uix.relativelayout import RelativeLayout
from kivy.graphics import Line
class DraggableWidget(RelativeLayout):
def __init__(self, **kwargs):
self.selected = None
self.touched = False
super(DraggableWidget, self).__init__(**kwargs)
ตรงส่วน __init__ เป็น constructor คลาสนี้จะมี 3 overload method on_touch_down , on_touch_move , on_touch_up
เริ่มที่ on_touch_down
def on_touch_down(self, touch):
if self.collide_point(touch.x, touch.y):
self.touched = True
self.select()
return True
return super(DraggableWidget, self).on_touch_down(touch)
ใช้ method colide_point เพื่อเช็คพิกัดของการ touch หรือ คลิก
event ต่อมา
def select(self):
if not self.selected:
self.ix = self.center_x
self.iy = self.center_y
with self.canvas:
self.selected = Line(rectangle=(0,0,self.width,self.height), dash_offset=2)
event นี้คือจะเช็คว่าถ้าคลิิกตรงกลางของ object ต่างๆ จะให้สร้าง กรอบสี่เหลี่มที่เป็นเส้นประล้อมรอบขึ้นมา
ภาพจากหนังสือ Kivy : Interactive Applications in Python |
ต่อด้วย on_touch_move
def on_touch_move(self, touch):
(x,y) = self.parent.to_parent(touch.x, touch.y)
if self.selected and self.touched and self.parent.collide_point(x - self.width/2, y -self.height/2):
go = self.parent.general_options
go.translation=(touch.x-self.ix,touch.y-self.iy)
return True
return super(DraggableWidget, self).on_touch_move(touch)
ตรงส่วนการ drag หรือ การลาก จะมีการเช็ค collide_point ไม่ให้ลาก object ออกไปนอก drawing space ถ้าเงื่อนไขเป็น True ก็จะสั่งให้เรียกใช้ translate method
def translate(self, x, y):
self.center_x = self.ix = self.ix + x
self.center_y = self.iy = self.iy + y
สุดท้าย on_touch_up
def on_touch_up(self, touch):
self.touched = False
if self.selected:
if not self.parent.general_options.group_mode:
self.unselect()
return super(DraggableWidget, self).on_touch_up(touch)
on_touch_up event เป็นการคืนค่าสถานะของ on_touch_down ถ้าเช็คแล้วมีการ selected อยู่ ก็จะเรียกใช้ method unselected()
def unselect(self):
if self.selected:
self.canvas.remove(self.selected)
self.selected = None
มีโค้ดอีกนิดหน่อยใน comicwidgets.py
class StickMan(DraggableWidget):
pass
Localizing coordinates – adding stickmen
หัวข้อนี้จะพูดถึงการทำปุ่มตรงส่วน toolbox ให้มี event
# File name: toolbox.py
import kivy
kivy.require('1.7.0')
import math
from kivy.uix.togglebutton import ToggleButton
from kivy.graphics import Line
from comicwidgets import StickMan, DraggableWidget
class ToolButton(ToggleButton):
def on_touch_down(self, touch):
ds = self.parent.drawing_space
if self.state == 'down' and ds.collide_point(touch.x, touch.y):
(x,y) = ds.to_widget(touch.x, touch.y)
self.draw(ds, x, y)
return True
return super(ToolButton, self).on_touch_down(touch)
def draw(self, ds, x, y):
pass
บรรทัดที่ highlight คือ ส่วนที่ใช้ในการเรียก method ในการวาด ToolStickMan , ToolCircle , และ ToolLine
ตัวอย่างเช่น ToolStickMan เป็น method ในการวาด stickman ขนาด 48x48 pixel บนหน้า drawingspace
class ToolStickman(ToolButton):
def draw(self, ds, x, y):
sm = StickMan(width=48, height=48)
sm.center = (x,y)
ds.add_widget(sm)
Binding and unbinding events – sizing limbs and heads
หัวข้อนี้จะพูดถึงการ ย่อขยายต่างๆ เช่น วงกลม กับ เส้น เพื่อสร้าง stickman ในขนาดที่แตกต่างออกไป
class toolfigure จะมีทั้งหมด 6 medthod
class ToolFigure(ToolButton):
def draw(self, ds, x, y):
(self.ix, self.iy) = (x,y)
with ds.canvas:
self.figure=self.create_figure(x,y,x+1,y+1)
ds.bind(on_touch_move=self.update_figure)
ds.bind(on_touch_up=self.end_figure)
def update_figure(self, ds, touch):
if ds.collide_point(touch.x, touch.y):
(x,y) = ds.to_widget(touch.x, touch.y)
ds.canvas.remove(self.figure)
with ds.canvas:
self.figure = self.create_figure(self.ix, self.iy,x,y)
def end_figure(self, ds, touch):
ds.unbind(on_touch_move=self.update_figure)
ds.unbind(on_touch_up=self.end_figure)
ds.canvas.remove(self.figure)
(fx,fy) = ds.to_widget(touch.x, touch.y)
self.widgetize(ds,self.ix,self.iy,fx,fy)
def widgetize(self,ds,ix,iy,fx,fy):
widget = self.create_widget(ix,iy,fx,fy)
(ix,iy) = widget.to_local(ix,iy,relative=True)
(fx,fy) = widget.to_local(fx,fy,relative=True)
widget.canvas.add(self.create_figure(ix,iy,fx,fy))
ds.add_widget(widget)
def create_figure(self,ix,iy,fx,fy):
pass
def create_widget(self,ix,iy,fx,fy):
pass
class toolfigure จะมีทั้งหมด 6 medthod
- draw: ให้เริ่มวาดจากจุดที่เราคลิกครั้งแรก เช่น เริ่มจากตรงกลางของวงกลม หรือ จุดปลายของเส้น
- update_figure : update จุดเริ่มต้นถึงจุดสุดท้ายของรูป เช่น รัศมีจากจุดกึ่งกลาง หรือ จากจุดหนึ่งไปยังอีกจุดหนึ่งของเส้น
- end_figure : จะบอกจุดสุดท้าย การทำงานคล้ายกับ update_figure
- widgetize : สร้างการ drag figure
- create_figure : เป็น method ที่เรียกใช้ ToolLine ,ToolCircle ในการวาดรูป
- create_widget : ทำงานเหมือน create_figure
ไม่ต้องการให้ on_touch_move กับ on_touch_up events ทำงานตลอดเวลา จึงใช้ Binding and unbinding events ในการแก้ปัญหา (ให้แค่ลากแล้วปล่อย)
โค้ดในส่วนของ class ToolLine ,ToolCircle
class ToolLine(ToolFigure):
def create_figure(self,ix,iy,fx,fy):
return Line(points=[ix, iy, fx, fy])
def create_widget(self,ix,iy,fx,fy):
pos = (min(ix, fx), min(iy, fy))
size = (abs(fx-ix), abs(fy-iy))
return DraggableWidget(pos = pos, size = size)
class ToolCircle(ToolFigure):
def create_figure(self,ix,iy,fx,fy):
return Line(circle=[ix,iy,math.hypot(ix-fx,iy-fy)])
def create_widget(self,ix,iy,fx,fy):
r = math.hypot(ix-fx, iy-fy)
pos = (ix-r, iy-r)
size = (2*r, 2*r)
return DraggableWidget(pos = pos, size = size)
ทั้ง 2 class จะใช้การคำนวณแบบธรรมดาในการกำหนดขนาด
โค้ดในส่วนของ toolbox.kv (มีการแก้ไข)
# File name: toolbox.kv
#:kivy 1.7.0
#:import toolbox toolbox
<ToolButton>:
size_hint: None,None
size: 48,48
group: 'tool'
canvas:
PushMatrix:
Translate:
xy: self.x,self.y
canvas.after:
PopMatrix:
<ToolBox@GridLayout>:
cols: 2
padding: 2
tool_circle: _tool_circle
tool_line: _tool_line
tool_stickman: _tool_stickman
ToolCircle:
id: _tool_circle
canvas:
Line:
circle: 24,24,14
ToolLine:
id: _tool_line
canvas:
Line:
points: 10,10,38,38
ToolStickman:
id: _tool_stickman
StickMan:
pos_hint: {'center_x':.5,'center_y':.5}
โค้ดนี้จะมีการเพิ่ม class ที่เราทำใหม่เข้ามา ToolCircle , ToolLine , ToolStickman และมีการเพิ่ม attributes ที่เอาไว้ใช้ในการสร้าง gestures ใน chapter ที่ 4
Binding events in the Kivy language
ในหัวข้อนี้จะพูดถึงการทำ event ในส่วนของ general option
โค้ด generaloption.kv
# File name: generaloptions.kv
#:kivy 1.7.0
#:import generaloptions generaloptions
<GeneralOptions>:
orientation: 'horizontal'
padding: 2
Button:
text: 'Clear'
on_press: root.clear(*args)
Button:
text: 'Remove'
on_release: root.remove(*args)
ToggleButton:
text: 'Group'
on_state: root.group(*args)
Button:
text: 'Color'
on_press: root.color(*args)
ToggleButton:
text: 'Gestures'
on_state: root.gestures(*args)
Button หรือ ปุ่มกด จะมี event หลักๆอยู่ 2 อย่างคือ on_press กับ on_release
on_press จะใช้กับ Clear Button กับ Color Button ก็คือกดครั้งเดียวพอ ทำงานเสร็จก็จบ
on_release จะใช้กับ Remove Button กดแล้วสามารถกดต่อได้อีก
on_state จะใช้ ToggleButton ก็คือ กดครั้งนึงไว้แล้วจะทำงานตลอดจนกว่าจะกดอีกครั้งเพื่อหยุด ใช้กับ GroupButton กับ GestureButton
โค้ดในส่วน generaloptions.py
# File name: generaloptions.py
import kivy
kivy.require('1.7.0')
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import NumericProperty, ListProperty
class GeneralOptions(BoxLayout):
group_mode = False
translation = ListProperty(None)
def clear(self, instance):
self.drawing_space.clear_widgets()
def remove(self, instance):
ds = self.drawing_space
if len(ds.children) > 0:
ds.remove_widget(ds.children[0])
def group(self, instance, value):
if value == 'down':
self.group_mode = True
else:
self.group_mode = False
self.unselect_all()
def color(self, instance):
pass
def gestures(self, instance, value):
pass
def unselect_all(self):
for child in self.drawing_space.children:
child.unselect()
def on_translation(self,instance,value):
for child in self.drawing_space.children:
if child.selected:
child.translate(*self.translation)
class GeneralOptions มีทั้งหมด 7 methods
# File name: generaloptions.py
import kivy
kivy.require('1.7.0')
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import NumericProperty, ListProperty
class GeneralOptions(BoxLayout):
group_mode = False
translation = ListProperty(None)
def clear(self, instance):
self.drawing_space.clear_widgets()
def remove(self, instance):
ds = self.drawing_space
if len(ds.children) > 0:
ds.remove_widget(ds.children[0])
def group(self, instance, value):
if value == 'down':
self.group_mode = True
else:
self.group_mode = False
self.unselect_all()
def color(self, instance):
pass
def gestures(self, instance, value):
pass
def unselect_all(self):
for child in self.drawing_space.children:
child.unselect()
def on_translation(self,instance,value):
for child in self.drawing_space.children:
if child.selected:
child.translate(*self.translation)
class GeneralOptions มีทั้งหมด 7 methods
- clear : ล้าง widget ที่เราวาดใน drawing space ทั้งหมด
- remove : ลบการวาดครั้งล่าสุด ถ้ากดอีกก็จะลบการวาดครั้งก่อนหน้านั้นไปเรื่อยๆ
- group : ถ้าคลิกเลือกจะมีการเปลี่ยนโหมด จาก True เป็น False
- color : ในที่นี้ยังไม่มี event
- gesture : ในที่นี้ยังไม่มี event
- unselect_all : ทำงานเมื่อไมได้คลิกรูปใดๆ
- on_translation :
Creating your own events – the magical properties
การสร้าง properties
ตัวอย่าง types of properties
NumericProperty , StringProperty , ListProperty , DictProperty , or ObjectProperty
ถ้าเป็น Kivy property จะให้ตั้งชื่อโดย ใส่ on_ หน้าชื่อของ properties เช่น state property ของ
ToogleButton ก็จะเป็น on_state
นอกจากนี้ยังกล่าวถึงการสร้าง event ของ group ซึ่งใช้ state propertty ของ ToogleButton ซึ่งเราต้องการให้ เมื่อเราอยู่ใน group mode เราสามารถเลือก object หลายๆตัว แล้วลากไปพร้อมๆกันได้
ภาพจากหนังสือ Kivy : Interactive Applications in Python |
แก้โค้ดในส่วนของ comicwidgets.py ดังนี้
# File name: comicwidgets.py
import kivy
kivy.require('1.7.0')
from kivy.uix.relativelayout import RelativeLayout
from kivy.graphics import Line
class DraggableWidget(RelativeLayout):
def __init__(self, **kwargs):
self.selected = None
self.touched = False
super(DraggableWidget, self).__init__(**kwargs)
def on_touch_down(self, touch):
if self.collide_point(touch.x, touch.y):
self.touched = True
self.select()
return True
return super(DraggableWidget, self).on_touch_down(touch)
def select(self):
if not self.selected:
self.ix = self.center_x
self.iy = self.center_y
with self.canvas:
self.selected = Line(rectangle=(0,0,self.width,self.height), dash_offset=2)
def on_touch_move(self, touch):
(x,y) = self.parent.to_parent(touch.x, touch.y)
if self.selected and self.touched and self.parent.collide_point(x - self.width/2, y -self.height/2):
go = self.parent.general_options
go.translation=(touch.x-self.ix,touch.y-self.iy)
return True
return super(DraggableWidget, self).on_touch_move(touch)
def translate(self, x, y):
self.center_x = self.ix = self.ix + x
self.center_y = self.iy = self.iy + y
def on_touch_up(self, touch):
self.touched = False
if self.selected:
if not self.parent.general_options.group_mode:
self.unselect()
return super(DraggableWidget, self).on_touch_up(touch)
def unselect(self):
if self.selected:
self.canvas.remove(self.selected)
self.selected = None
class StickMan(DraggableWidget):
pass
Kivy and properties
หัวข้อนี้ก็จะเป็นตัวอย่างของ property ใน Kivy
ตัวอย่างจากโค้ด statusbar.py
# File name: statusbar.py
import kivy
kivy.require('1.7.0')
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import NumericProperty, ObjectProperty
class StatusBar(BoxLayout):
counter = NumericProperty(0)
previous_counter = 0
def on_counter(self, instance, value):
if value == 0:
self.msg_label.text = "Drawing space cleared"
elif value - 1 == self.__class__.previous_counter:
self.msg_label.text = "Widget added"
elif value + 1 == StatusBar.previous_counter:
self.msg_label.text = "Widget removed"
self.__class__.previous_counter = value
มีการใช้ NumericProperty (on_counter) ในการนับ Figure ที่เราสร้าง
โค้ดในส่วน statusbar.kv
# File name: statusbar.kv
#:kivy 1.7.0
#:import statusbar statusbar
<StatusBar>:
msg_label: _msg_label
orientation: 'horizontal'
Label:
text: 'Total Figures: ' + str(root.counter)
Label:
id: _msg_label
text: "Kivy started"
ส่วนต่อมาเราต้องการที่ count หรือนับ figure ที่อยู่บน drawings space ให้ทำการแก้ไขโค้ดใน drawingspace.py ดังนี้
# File name: drawingspace.py
import kivy
kivy.require('1.7.0')
from kivy.uix.relativelayout import RelativeLayout
class DrawingSpace(RelativeLayout):
def on_children(self, instance, value):
self.status_bar.counter = len(self.children)
โค้ดในส่วนของ drawingspace.kv
# File name: drawingspace.kv
#:kivy 1.7.0
#:import drawingspace drawingspace
<DrawingSpace@RelativeLayout>:
หน้าต่างเริ่มต้นโปรแกรม |
ทดลองสร้าง Figure ต่างๆ |
เมื่อคลิกเมาส์ที่ figure จะมีการสร้าง กรอบสี่เหลี่ยมที่เป็นเส้นประล้อมรอบ |
ลองกด clear button จะเห็นได้ว่า figure นั้นถูกลบไปหมดแล้ว และมีข้อความแสดง Drawing space cleared |
ลองสร้าง Stickman แล้วใช้คำสั่ง Group |
สามารถเคลื่อนย้าย Stickman ได้ |
ทดลองสร้าง Stickman 2 ตัว |
ทดลองใช้คำสั่ง Remove |
จะเห็นได้ว่าตัวที่เพิ่มเข้ามาล่าสุดถูก Remove ออกไป |
ไม่มีความคิดเห็น:
แสดงความคิดเห็น