Jython GUI开发全解析
1. Jython GUI开发概述
在Jython中开发图形用户界面(GUI),主要的工具包是Java的抽象窗口工具包(AWT)和Swing类。这是因为Jython是用Java编写的,能很好地在Python语法中使用Java类。不过,AWT和Swing并非实现GUI的唯一选择,越来越多与其他工具包的绑定也允许Java和Jython使用这些工具包。
1.1 Java与Jython GUI对比
编写Jython GUI与Java非常相似,大多数Java GUI能轻松转换为Jython代码并按预期工作。以下是一个简单的Java GUI示例:
// file SimpleJavaGUI.java
import java.awt.*;
import java.awt.event.*;
class SimpleJavaGUI implements ActionListener {
private Button mybutton = new Button("OK");
private Label mylabel = new Label("A Java GUI", Label.CENTER);
public SimpleJavaGUI() {
Frame top_frame = new Frame();
Panel panel = new Panel();
top_frame.setTitle("A Basic Jython GUI");
top_frame.setBackground(Color.yellow);
top_frame.addWindowListener(
new WindowAdapter() {
public void windowClosing(windowEvent e) {
System.exit(0);
}
});
mybutton.addActionListener(this);
panel.add(mylabel);
panel.add(mybutton);
top_frame.add(panel);
top_frame.pack();
Dimension winSz = Toolkit.getDefaultToolkit().getScreenSize();
top_frame.setLocation(winSz.width/2 - top__frame.getWidth()/2,
winSz.height/2 - top__frame.height()/2);
top_frame.setVisible(true);
}
public static void main(String[] args)) {
SimpleJavaGUI s = new SimpleJavaGUI();
}
public void actionPerformed(ActionEvent event) {
System.exit(0);
}
}
将相同的GUI在Jython中实现,语法上有明显变化。Java的类型声明、访问修饰符、分号和大括号在Jython中不存在。例如,Java中按钮组件的实例化:
import java.awt.*;
private Button mybutton = new Button("OK");
在Jython中变为:
from java import awt
mybutton = awt.Button("OK")
Jython会自动进行Java类型和Python类型之间的转换。例如,
setVisible
方法需要Java布尔参数,Jython会将
PyInteger
的1转换为布尔值
true
,0转换为布尔值
false
。而且,Jython不需要使用
setVisible
方法,可直接将1赋值给
visible
属性。
以下是对应的Jython代码:
# File: simpleJythonGUI.py
import java
from java import awt
def exit(e):
java.lang.System.exit(0)
top_frame = awt.Frame(title="A Basic Jython GUI",
background = awt.Color.yellow,
windowClosing=exit)
panel = awt.Panel()
mylabel = awt.Label("A Jython GUI", awt.Label.CENTER)
mybutton = awt.Button("OK", actionPerformed=exit)
panel.add(mylabel)
panel.add(mybutton)
top_frame.add(panel)
top_frame.pack()
toolkit = awt.Toolkit.getDefaultToolkit()
winSz = toolkit.screenSize
top_frame.setLocation(winSz.width/2 - top__frame.size.width/2,
winSz.height/2 - top__frame.height/2)
top_frame.visible = 1
1.2 Bean属性和事件
在介绍Jython的自动Bean属性之前,先回顾一下Bean的概念。Bean是遵循方法、属性和事件相关约定的类。Bean的方法是类中所有被指定为公共的方法,属性是通过
get
和
set
方法操作的数据对象,事件通过
add
和
remove
方法指定监听器对象来定义。
例如,以下Java类可看作“具有只读属性
name
的Bean A”:
public class A {
private String name;
public String getName() {
return name;
}
}
在Java中,会直接使用
getName()
方法来获取属性值:
import A;
bean = new A();
String name = bean.getName();
而在Jython中,可以将
name
属性当作实例属性来访问:
>>> import A
>>> b = A()
>>> name = b.name
对于
set
方法也是如此。Java中使用
setName
方法:
import A;
A bean = new A();
bean.setName("new name");
在Jython中,只需进行属性赋值:
import A
bean = A()
bean.name = "new name"
这种方式同样适用于事件。以下是Jython使用关键字参数在构造函数中设置组件属性的示例:
top_frame = awt.Frame(title="A Basic Jython GUI",
background = awt.Color.yellow,
windowClosing=exit)
mybutton = awt.Button("OK", actionPerformed=exit)
1.3 Bean属性的详细分析
Bean属性大多遵循一定的命名约定。例如,名为
extent
的Bean属性,其对应的访问器和修改器方法为
getExtent
和
setExtent
。如果Java类中有一个名为
setAMultiWordName
的方法,Jython会自动添加一个名为
aMultiWordName
的Bean属性。
Bean属性可以是只读的(仅定义
get
方法)、只写的(仅定义
set
方法)或读写的(同时定义
get
和
set
方法)。
get
和
set
方法的签名需要相互匹配,否则会出现问题。例如,以下Java Bean的
setName
方法参数设置错误:
public class A {
private String name = "";
public String getName() {
System.out.println("Getting name");
return name;
}
public void setName(Object s) {
name = (String)s;
System.out.println("Setting name");
}
}
在Jython中使用该Bean时会出现错误:
>>> import A
>>> b = A()
>>> b.name
Getting name
''
>>> b.name = "this"
Traceback (innermost last):
File "<console>", line 1, in ?
AttributeError: read-only attr: name
如果将类重写为具有正确匹配的签名:
public class A {
private String name = "';
public String getName() {
System.out.println("Getting name");
return name;
}
public void setName(String s) {
name = s;
System.out.println("Setting name");
}
}
在Jython中就能正常进行读写操作:
>>> import A
>>> b = A()
>>> b.name = "New name"
Setting name
>>> b.name
Getting name
'New name'
Bean属性也可以是序列。要实现对序列的完整读写访问,需要四个访问器方法:
-
setProperty([])
:设置整个序列
-
setProperty(int, Object)
:设置特定索引的值
-
getProperty()
:获取整个序列
-
getProperty(int)
:获取特定索引的值
但Jython的自动Bean属性只使用
getProperty()
和
setProperty([])
方法。以下是一个具有索引属性
items
的Java Bean:
// file: ItemBean.java
public class ItemBean {
private String[] data = {"A", "B", "C"};
public ItemBean() { ; }
public String[] getItems(() {
return data;
}
public String getItems(int i) {
return data[i];
}
public void setItems(String[] items)) {
data = items;
}
public void setItems(int index, String item) {
data[index] = item;
}
}
在Jython中与该属性的交互如下:
>>> import ItemBean
>>> ib = ItemBean()
>>> ib.items
array(['A', 'B', 'C'], java.lang.String)
>>> ib.items = ["1", "2", "3"]
1.4 Bean属性和元组
Jython会自动将分配给Bean属性的元组解释为要设置的属性类型的构造函数。例如,分配给
background
属性的三元组会自动成为
Java.awt.Color
类的构造函数值,因为该Bean属性类型是
java.awt.Color
;分配给
bounds
属性的元组会自动成为
Rectangle
类型。
以下是Java和Jython代码的对比:
// In Java (pseudo-code)
import java.awt.*;
Frame f = new Frame();
Label L = new Label("This is a Label");
L.setBackground( new Color(50, 160, 40));
L.setForeground( new Color(50, 255, 50));
L.setFont(new Font("Helvetica Bold", 18, 24));
f.add(L);
f.setVisible(true);
# In Jython
from java import awt
f = awt.Frame()
L = awt.Label("This is a Label", background=(50, 160, 40),
foreground=(50, 255, 50), font=("Helvetica Bold", 18, 24))
f.add(L)
f.visible = 1
1.5 Bean事件
Bean事件是通过
addEventListener
和
removeEventListener
注册的事件。例如,
java.awt.Window
类通过
addWindowListener
方法注册窗口事件,该方法需要一个实现
WindowListener
接口的类作为参数。
在Java中处理事件,需要添加一个实现相应事件监听器接口的类。例如,处理
windowClosing
事件,Java应用程序必须使用
addWindowListener
方法添加一个实现
WindowListener
接口的类或扩展一个这样的类,并实现
windowClosing
方法。Java也允许使用匿名内部类来满足接口要求。
而在Jython中,使用自动事件属性作为属性。可以将任何可调用的Jython对象分配给事件的Bean名称。例如,实现
windowClosing
事件,只需将一个可调用对象分配给容器的
windowClosing
属性:
>>> def wclose(e):
... print "Processing the windowClosing Event"
... e.getSource().dispose()
...
>>> def wopen(e):
... print "Processing the windowOpened Event"
...
>>> def wact(e):
... print "Processing the windowActivated event"
...
>>> import java
>>> f = java.awt.Frame("Jython AWT Example")
>>> f.windowClosing = wclose
>>> f.windowOpened = wopen
>>> f.windowActivated = wact
>>> f.visible = 1
所有事件属性分配也可以作为关键字参数包含在构造函数中:
f = java.awt.Frame("Jython AWT Example", windowOpened=wopen,
windowClosing=wclose, windowActivated=wact)
1.6 名称优先级
Jython支持实例方法、类静态字段、Bean属性和Bean事件属性,这可能会导致潜在的命名冲突。Jython通过分配优先级来解决名称冲突,优先级顺序如下:
1. 方法
2. 类静态属性
3. 事件属性
4. Bean属性
1.7 pawt包
Jython的
pawt
包为在Jython中使用AWT和Swing提供了一些便利模块,包括
GridBag
、
colors
、
test
和
swing
。
1.7.1 GridBag
pawt.GridBag
类是一个包装类,用于帮助处理
java.awt.GridBagLayout
和
java.awt.GridBagConstraints
。该类有两个方法:
add
和
addRow
。
以下是使用
pawt.GridBag
类简化布局设置的示例:
from java import awt
import pawt
top_frame = awt.Frame("GridBag frame")
bag = pawt.GridBag(top_frame)
pawt.GridBag
类的实例化可以选择接受关键字参数作为默认约束:
bag = pawt.GridBag(top_frame, fill="VERTICAL")
使用
pawt.GridBag
类添加组件的示例:
from java import awt
from pawt import GridBag
pane = awt.Panel()
bag = GridBag(pane, fill='HORIZONTAL')
nameLabel = awt.Label("Name: ")
nameField = awt.TextField(25)
bag.add(nameLabel, anchor="WEST", fill="NONE", gridwidth=1)
bag.addRow(nameField, anchor="WEST", fill="HORIZONTAL", gridwidth=3)
1.7.2 colors
pawt.colors
模块定义了许多命名颜色,方便人们使用。例如,
papayawhip
颜色比其RGB值
(255, 239, 213)
更容易记忆。要获取
pawt.colors
中定义的所有颜色列表,可以执行以下操作:
>>> import pawt
>>> dir(pawt.colors)
1.7.3 test
pawt
包中的
test
函数允许对图形组件进行简单测试。例如,测试按钮颜色:
>>> import java
>>> import pawt
>>> b = java.awt.Button("help", background=(212,144,100))
>>> pawt.test(b)
test
函数还可以接受一个大小参数:
>>> from java.awt import Label
>>> import pawt
>>> L = Label("Test", background=(255,10,10), foreground=(10, 10, 100))
>>> pawt.test(L, (200,140))
1.7.4 pawt.swing
pawt.swing
模块有两个功能:选择适合JDK的Swing库,并提供一个专门用于Swing组件的测试函数。
以下是使用
pawt.swing
模块测试Swing组件的示例:
>>> import pawt
>>> cb = pawt.swing.JCheckBox("Test Check Box")
>>> myframe = pawt.swing.test(cb)
1.8 开发流程总结
以下是使用Jython开发GUI的一般流程:
1.
选择工具包
:根据需求选择AWT或Swing工具包。
2.
创建组件
:使用相应工具包的类创建各种组件,如按钮、标签等。
3.
设置属性和事件
:利用Jython的自动Bean属性和事件属性,通过简单的属性赋值和函数分配来设置组件的属性和处理事件。
4.
布局管理
:使用合适的布局管理器来安排组件的位置,对于复杂的
GridBagLayout
,可以使用
pawt.GridBag
类简化操作。
5.
测试和调试
:使用
pawt.test
函数对组件进行简单测试,确保其功能正常。
1.9 工具和资源
- 开发环境 :可以使用任何支持Python和Jython的集成开发环境(IDE),如PyCharm等。
- 文档和教程 :Jython官方文档提供了详细的使用说明和示例,同时可以参考Java的AWT和Swing相关文档,加深对工具包的理解。
1.10 注意事项和其他补充说明
- 类型转换 :虽然Jython会自动进行Java类型和Python类型之间的转换,但在处理一些特殊类型时,仍需要注意类型的匹配。
- 命名冲突 :在使用Jython的自动Bean属性和事件属性时,要注意名称优先级,避免出现命名冲突导致的意外结果。
-
pawt.swing模块的局限性 :该模块是一个动态模块,在编译Jython应用程序时可能会出现问题,因此在子类化Swing组件或编译应用程序时,建议直接使用javax.swing。
2. Jython GUI开发示例
2.1 简单AWT图形
以下示例展示了如何在
java.awt.Canvas
的
paint
方法中绘制一个简单的横幅。该示例使用了Jython的自动Bean属性来设置画布的高度和宽度属性,以及图形的字体和颜色属性,还使用自动事件属性在
Frame
的构造函数中设置
windowClosing
事件。
# file: jythonBanner.py
from java import awt
import java
class Banner(awt.Canvas):
def paint(self, g):
g.color = 204, 204, 204
g.fillRect(15, 15, self.width - 30, self.height - 30)
g.color = 102, 102, 153
g.font = "Helvetica Bold", awt.Font.BOLD, 28
message = ">>> Jython"
g.drawString(message, self.width // 2 - len(message) * 10, self.height // 2 + 14)
top_frame = awt.Frame("Jython Banner", size=(350, 150), windowClosing=lambda e: java.lang.System.exit(0))
top_frame.add(Banner())
top_frame.visible = 1
2.2 添加事件
下面的示例实现了一个极坐标图,当鼠标在图形画布上移动时,显示鼠标位置的半径(r)和角度(theta)坐标。该示例展示了如何使用
java.awt.event.MouseListener
和
java.awt.event.MouseMotionListener
接口定义的鼠标事件,通过Jython的自动事件属性来处理这些事件。
# file: PolarGraph.py
import java
from java.lang import System
from java import awt
from java.lang import Math
class Main(awt.Frame):
def __init__(self):
self.background = awt.Color.gray
self.bounds = 50, 50, 400, 400
self.windowClosing = lambda e: System.exit(0)
self.r = awt.Label("r: ")
self.theta = awt.Label("theta: ")
infoPanel = awt.Panel()
infoPanel.add(self.r)
infoPanel.add(self.theta)
self.graph = PolarCanvas()
self.add("North", infoPanel)
self.add("Center", self.graph)
self.visible = 1
self.graph.mouseMoved = self.updateCoords
def updateCoords(self, e):
limit = self.graph.limit
width = self.graph.width
height = self.graph.height
x = (2 * e.x * limit) // width - limit
y = limit - (2 * e.y * limit) // height
r = Math.sqrt(x ** 2 + y ** 2)
if x == 0:
theta = 0
else:
theta = Math.atan(Math.abs(y) / Math.abs(x))
if x < 0 and y > 0:
theta = Math.PI - theta
elif x < 0 and y <= 0:
theta = theta + Math.PI
elif x > 0 and y < 0:
theta = 2 * Math.PI - theta
self.r.text = "r: %0.2f" % r
self.theta.text = "theta: %0.2f" % theta
class PolarCanvas(awt.Canvas):
def __init__(self, interval=100, limit=400):
self.background = awt.Color.white
self.mouseReleased = self.onClick
self.interval = interval
self.limit = limit
self.points = []
def onClick(self, e):
x = (2 * e.x * self.limit) // self.width - self.limit
y = self.limit - (2 * e.y * self.limit) // self.height
self.points.append(awt.Point(x, y))
self.repaint()
def paint(self, g):
rings = self.limit // self.interval
step = (self.width // (rings * 2), self.height // (rings * 2))
# Draw rings
for x in range(1, rings + 1):
r = awt.Rectangle(x * step[0], x * step[1], step[0] * (rings - x) * 2, step[1] * (rings - x) * 2)
g.color = (max(140 - x * 20, 10), max(200 - x * 20, 10), max(240 - x * 20, 10))
g.fillOval(r.x, r.y, r.width, r.height)
g.color = awt.Color.black
g.drawOval(r.x, r.y, r.width, r.height)
g.drawString(str((rings * self.interval) - (x * self.interval)), r.x - 8, self.height // 2 + 12)
# draw center dot
g.fillOval(self.width // 2 - 2, self.height // 2 - 2, 4, 4)
# draw points
g.color = awt.Color.red
for p in self.points:
x = (p.x * self.width) // (2 * self.limit) + self.width // 2
y = self.height // 2 - (p.y * self.height) // (2 * self.limit)
g.fillOval(x, y, 4, 4)
if __name__ == '__main__':
app = Main()
2.3 图像显示
以下示例展示了如何在AWT框架中显示图像。该示例使用
awt.Toolkit.getDefaultToolkit().getImage
方法加载图像,并使用
awt.MediaTracker
确保图像完全加载后再进行绘制。
# file jyimage.py
from java.lang import System
from java import awt
class jyimage(awt.Frame):
def __init__(self, im):
self.im = awt.Toolkit.getDefaultToolkit().getImage(im)
self.bounds = 100, 100, 200, 200
self.title = "Jython Image Display"
self.windowClosing = lambda e: System.exit(0)
mt = awt.MediaTracker(self)
mt.addImage(self.im, 0)
mt.waitForID(0)
self.addNotify()
i = self.insets
self.resize(self.im.width + i.left + i.right, self.im.height + i.top + i.bottom)
def paint(self, g):
g.drawImage(self.im, self.insets.left, self.insets.top, self)
if __name__ == '__main__':
f = jyimage("jython-new-small.gif")
f.visible = 1
2.4 菜单和菜单事件
在Jython中添加菜单和处理菜单事件非常简单。以下示例展示了如何创建菜单并为菜单项分配操作。
# file: jythonmenus.py
from java import awt
from java.awt import event
from java.lang import System
menus = [
('File', ['New', 'Open', 'Save', 'Saveas', 'Close']),
('Edit', ['Copy', 'Cut', 'Paste'])
]
class MenuTest(awt.Frame):
def __init__(self):
bar = awt.MenuBar()
for menu, menuitems in menus:
menu = awt.Menu(menu)
for menuitem in menuitems:
method = getattr(self, 'on%s' % menuitem)
item = awt.MenuItem(menuitem, actionPerformed=method)
menu.add(item)
bar.add(menu)
self.menuBar = bar
self.windowClosing = lambda e: System.exit(0)
self.eventLabel = awt.Label("Event: ")
self.bounds = 100, 100, 200, 100
self.add(self.eventLabel)
def onNew(self, e):
self.eventLabel.text = "Event: onNew"
def onOpen(self, e):
self.eventLabel.text = "Event: onOpen"
def onSave(self, e):
self.eventLabel.text = "Event: onSave"
def onSaveas(self, e):
self.eventLabel.text = "Event: onSaveas"
def onClose(self, e):
self.eventLabel.text = "Event: onClose"
System.exit(0)
def onCopy(self, e):
self.eventLabel.text = "Event: onCopy"
def onCut(self, e):
self.eventLabel.text = "Event: onCut"
def onPaste(self, e):
self.eventLabel.text = "Event: onPaste"
f = MenuTest()
f.visible = 1
2.5 拖放功能
在Jython中实现拖放(DnD)功能与Java类似,需要实现
DragGestureListener
、
DragSourceListener
和
DropTargetListener
接口。以下示例展示了如何实现两个列表之间的拖放功能。
# file: ListDnD.py
from java import awt
from java.awt import dnd
from java.lang import System
from java.awt import datatransfer
class JythonDnD(awt.Frame, dnd.DragSourceListener, dnd.DragGestureListener):
def __init__(self):
self.title = "Jython Drag -n- Drop Implementation"
self.bounds = 100, 100, 300, 200
self.windowClosing = lambda e: System.exit(0)
self.draglist = awt.List()
list(map(self.draglist.add, ["1", "2", "3", "4", "5"]))
self.droplist = droplist([])
self.dropTarget = dnd.DropTarget(self, dnd.DnDConstants.ACTION_COPY_OR_MOVE, self.droplist)
self.layout = awt.GridLayout(1, 2, 2, 2)
self.add(self.draglist)
self.add(self.droplist)
self.dragSource = dnd.DragSource()
self.recognize = self.dragSource.createDefaultDragGestureRecognizer(
self.draglist,
dnd.DnDConstants.ACTION_COPY_OR_MOVE,
self
)
def dragGestureRecognized(self, e):
item = self.draglist.getSelectedItem()
e.startDrag(self.dragSource.DefaultCopyDrop, datatransfer.StringSelection(item), self)
def dragEnter(self, e):
pass
def dragOver(self, e):
pass
def dragExit(self, e):
pass
def dragDropEnd(self, e):
pass
def dropActionChanged(self, e):
pass
class droplist(awt.List, dnd.DropTargetListener):
def __init__(self, datalist):
list(map(self.add, datalist))
self.dropTarget = dnd.DropTarget(self, 3, self)
def drop(self, e):
transfer = e.getTransferable()
data = transfer.getTransferData(datatransfer.DataFlavor.stringFlavor)
self.add(data)
e.dropComplete(1)
def dragEnter(self, e):
e.acceptDrag(dnd.DnDConstants.ACTION_COPY_OR_MOVE)
def dragExit(self, e):
pass
def dragOver(self, e):
pass
def dropActionChanged(self, e):
pass
win = JythonDnD()
win.visible = 1
2.6 Swing应用
使用Swing类开发GUI与使用AWT类似,Jython的自动Bean属性和事件属性同样适用于Swing组件。以下示例展示了如何创建一个简单的Swing树显示。
# file: SwingTree.py
from javax import swing
from javax.swing import tree
import sys
top_frame = swing.JFrame("A Simple Tree in Jython and Swing",
windowClosing=lambda e: sys.exit(0),
background=(180, 180, 200))
data = tree.DefaultMutableTreeNode("Root")
data.add(tree.DefaultMutableTreeNode("a leaf"))
childNode = tree.DefaultMutableTreeNode("a node")
childNode.add(tree.DefaultMutableTreeNode("another leaf"))
data.add(childNode)
t = swing.JTree(data)
t.putClientProperty("JTree.lineStyle", "Angled")
top_frame.contentPane.add(t)
top_frame.bounds = 100, 100, 200, 200
top_frame.visible = 1
2.7 总结
通过以上示例可以看出,Jython在GUI开发中具有很多优势:
-
语法简洁
:Jython的语法比Java更简洁,减少了代码量,提高了开发效率。
-
自动属性和事件处理
:利用自动Bean属性和事件属性,使属性设置和事件处理更加简单直观。
-
与Java集成
:由于Jython基于Java,能够很好地与Java的AWT和Swing工具包集成,充分利用Java的强大功能。
2.8 常见问题及解决方案
| 问题 | 解决方案 |
|---|---|
| 类型转换问题 | 虽然Jython会自动进行类型转换,但在处理特殊类型时,需要手动确保类型匹配。 |
| 命名冲突 | 注意Jython的名称优先级,避免因命名冲突导致意外结果。 |
pawt.swing
模块问题
|
在子类化Swing组件或编译Jython应用程序时,直接使用
javax.swing
。
|
2.9 未来趋势和发展方向
随着Python和Java技术的不断发展,Jython在GUI开发领域有望继续发挥重要作用。未来可能会有更多的工具和库支持Jython,进一步简化开发流程,提高开发效率。同时,随着跨平台开发需求的增加,Jython的跨平台特性将使其在多平台GUI开发中更具优势。
2.10 流程图:Jython GUI开发流程
graph LR
A[选择工具包] --> B[创建组件]
B --> C[设置属性和事件]
C --> D[布局管理]
D --> E[测试和调试]
2.11 总结表格
| 开发内容 | 关键点 |
|---|---|
| 简单AWT图形 |
使用
Canvas
的
paint
方法,利用自动Bean属性设置图形属性。
|
| 添加事件 |
利用自动事件属性处理鼠标事件,如
mouseMoved
和
mouseReleased
。
|
| 图像显示 |
使用
Toolkit
加载图像,
MediaTracker
确保图像加载完成。
|
| 菜单和菜单事件 |
通过
MenuBar
和
MenuItem
创建菜单,使用
getattr
方法绑定事件处理函数。
|
| 拖放功能 |
实现
DragGestureListener
、
DragSourceListener
和
DropTargetListener
接口。
|
| Swing应用 | 与AWT开发类似,利用自动Bean属性和事件属性简化开发。 |
Jython GUI开发全面解析与示例
超级会员免费看
2378

被折叠的 条评论
为什么被折叠?



