79、Jython GUI开发:从基础到实践

Jython GUI开发:从基础到实践

在软件开发领域,图形用户界面(GUI)的开发至关重要,它直接影响着用户与软件的交互体验。Jython作为一种结合了Python语法和Java强大功能的语言,在GUI开发方面有着独特的优势。本文将深入探讨Jython在使用Java的Abstract Windowing Toolkit(AWT)和Swing类进行GUI开发时的相关特性和实践。

1. Jython与Java GUI对比

在Jython中编写GUI与在Java中编写有很多相似之处,大多数Java GUI可以轻松转换为Jython代码,并且能按预期工作。下面通过一个简单的例子来对比两者。

1.1 简单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); 
        //WindowListener needed for window close event 
        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); 
        // pack and show 
        top_frame.pack(); 
        Dimension winSz = Toolkit.getDefaultToolkit().getScreenSize(); 
        top_frame.setLocation(winSz.width/2 - top_frame.getWidth()/2, 
                              winSz.height/2 - top_frame.getHeight()/2); 
        top_frame.setVisible(true); 
    } 
    public static void main(String[] args) {
        SimpleJavaGUI s = new SimpleJavaGUI(); 
    } 
    public void actionPerformed(ActionEvent event) {
        System.exit(0); 
    } 
} 

这个Java示例展示了如何创建一个简单的GUI窗口,包含一个按钮和一个标签。使用了AWT的相关类,如 Frame Panel Button Label ,并设置了窗口关闭事件和按钮点击事件。

1.2 简单Jython GUI示例
# File: simpleJythonGUI.py 
import java 
from java import awt 
# Make an exit function 
def exit(e): 
    java.lang.System.exit(0) 
#Make root frame and the panel it will contain 
top_frame = awt.Frame(title="A Basic Jython GUI", 
                      background = awt.Color.yellow, 
                      windowClosing=exit) 
panel = awt.Panel() 
#Add stuff to look at inside frame 
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) 
# pack and show 
top_frame.pack() 
# set location 
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 

在Jython版本中,代码更加简洁。Java中的类型声明、访问修饰符、分号和大括号在Jython中都不需要。同时,Jython使用属性赋值的方式来处理窗口关闭和按钮点击事件,而不是像Java那样使用接口和匿名内部类。

1.3 语法和类型转换差异
  • 语法差异 :Java的类型声明、访问修饰符、分号和大括号在Jython中不存在。例如,Java中按钮的实例化 private Button mybutton = new Button("OK"); 在Jython中变为 mybutton = awt.Button("OK")
  • 类型转换 :Jython会自动进行Java类型和Python类型之间的转换。例如, setVisible 方法需要一个Java布尔参数,Jython会将 PyInteger 1 转换为 Boolean true 0 转换为 Boolean false 。而且,Jython可以直接将 1 赋值给 visible 属性,而不需要调用 setVisible 方法。
1.4 类的使用差异

在Jython中,类不是必需的。虽然使用类可以更准确地翻译Java代码,但Jython允许不使用类来实现GUI。不过,由于AWT和Swing类的特性,复杂的无类GUI实现起来会比较困难。

2. Bean属性和事件

在深入了解Jython的GUI开发之前,我们需要先了解一下Bean的概念。Bean是遵循特定方法、属性和事件约定的类。

2.1 Bean属性
  • 属性访问 :在Java中,访问Bean的属性需要使用 get set 方法。例如,对于一个名为 A 的Bean,获取 name 属性需要调用 bean.getName() 。而在Jython中,可以直接将属性作为类的属性来访问,如 b.name
import A 
bean = A() 
name = bean.name 
  • 属性赋值 :在Java中,设置 name 属性需要调用 bean.setName("new name") ,而在Jython中,只需进行属性赋值 bean.name = "new name"
import A 
bean = A() 
bean.name = "new name" 
  • 属性类型和方法签名 :Bean属性的 get set 方法签名需要相互匹配,否则在Jython中可能会出现问题。例如,如果 setName 方法的参数是 Object 类型,而 getName 方法返回 String 类型,在Jython中可能会导致属性被视为只读。
2.2 Bean属性与元组

Jython会自动将分配给Bean属性的元组解释为该属性类型的构造函数。例如,将一个三元组分配给 background 属性时,它会自动成为 java.awt.Color 类的构造函数值;将元组分配给 bounds 属性时,会自动成为 Rectangle 类型。

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 
2.3 Bean事件

在Java中,处理事件需要实现相应的事件监听器接口,并使用 addEvent Listener removeEvent Listener 方法来注册和移除事件监听器。而在Jython中,可以直接将可调用对象分配给事件的Bean名称,将事件视为类的属性。

def wclose(e): 
    print "Processing the windowClosing Event" 
    e.getSource().dispose() 

import java 
f = java.awt.Frame("Jython AWT Example") 
f.windowClosing = wclose 
f.visible = 1 
2.4 名称优先级

Jython支持实例方法、类静态字段、Bean属性和Bean事件属性,可能会出现命名冲突。Jython通过分配优先级来解决这些冲突,优先级顺序如下:
1. 实例方法
2. 类静态属性
3. 事件属性
4. Bean属性

3. pawt包

Jython的 pawt 包提供了一些方便的模块,用于在Jython中使用AWT和Swing。

3.1 GridBag类

pawt.GridBag 类是一个包装类,用于简化 java.awt.GridBagLayout java.awt.GridBagConstraints 的使用。它有两个方法: add addRow

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) 

add 方法用于添加指定组件,并使用关键字参数指定约束条件; addRow 方法除了添加组件外,还会完成当前行,后续组件将从下一行开始。

3.2 colors模块

colors 模块包含了许多预定义的颜色名称,方便开发者使用。可以通过以下方式查看所有定义的颜色:

import pawt 
dir(pawt.colors) 
3.3 test函数

test 函数允许对图形组件进行简单测试,无需使用 Frame Panel 。可以指定组件的大小,并且该函数会返回使用的根框架,方便后续交互。

import java 
import pawt 
b = java.awt.Button("help", background=(212,144,100)) 
pawt.test(b) 
3.4 pawt.swing模块

pawt.swing 模块有两个主要功能:选择适合JDK的Swing库,并提供一个专门用于Swing组件的测试函数。不过,该模块比较动态,在编译Jython时可能会出现问题,因此在子类化Swing组件或编译Jython应用程序时,建议直接使用 javax.swing

4. 示例

通过实际的示例可以更好地理解Jython在GUI开发中的应用。

4.1 简单AWT图形示例
# 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,  #approx. center 
                     self.height/2 + 14)              #same here 
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 

这个示例展示了如何在 java.awt.Canvas paint 方法中绘制一个Jython横幅。使用了Jython的自动Bean属性来设置画布的高度、宽度以及图形的字体和颜色属性,同时使用自动事件属性来处理窗口关闭事件。

4.2 添加事件示例
# 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() 

这个示例展示了如何处理鼠标事件。通过 PolarCanvas 类实现了一个极坐标图,当鼠标在画布上移动时,会显示鼠标位置的半径和角度。同时,鼠标释放时会记录一个点并重新绘制画布。

4.3 图像显示示例
# 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() # add peer to get insets 
        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 

该示例展示了如何在Jython中显示图像。使用 awt.MediaTracker 确保图像完全加载后再进行绘制,同时使用 addNotify 方法获取框架的插入信息,以确定框架的总尺寸。

4.4 菜单和菜单事件示例
# 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 

此示例展示了如何在Jython中创建菜单和处理菜单事件。通过填充 MenuBar 实例并将其分配给框架的 menuBar 属性,同时为每个菜单项的 actionPerformed 属性分配相应的处理方法。

4.5 拖放功能示例
# 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() 
        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): 
        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 

该示例展示了如何在Jython中实现拖放功能。需要实现 DragGestureListener DragSourceListener DropTargetListener 接口,分别建立拖放源和拖放目标。

4.6 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 

这个示例展示了如何使用Swing创建一个简单的树状显示。与AWT的 Frame 相比, JFrame 使用 contentPane 来添加组件。Jython的自动Bean属性和事件属性同样适用于Swing组件,提高了开发效率。

通过以上示例可以看出,Jython在GUI开发中具有很多优势,如简洁的语法、自动的属性和事件处理等。它结合了Python的灵活性和Java的强大功能,为开发者提供了一种高效的GUI开发方式。无论是简单的图形绘制、事件处理,还是复杂的拖放功能和Swing应用,Jython都能轻松应对。希望本文能帮助你更好地掌握Jython的GUI开发技巧。

Jython GUI开发:从基础到实践

5. 综合应用与总结

在前面的内容中,我们详细介绍了Jython在GUI开发中的各个方面,包括与Java GUI的对比、Bean属性和事件的处理、 pawt 包的使用以及多个实际示例。接下来,我们将对这些内容进行综合应用,并总结Jython在GUI开发中的优势和注意事项。

5.1 综合应用示例

假设我们要开发一个简单的图像查看器,它具有基本的菜单功能,如打开图像、关闭程序,并且可以显示图像。我们可以结合前面介绍的菜单和图像显示的知识来实现这个应用。

# file: ImageViewer.py
from java import awt
from java.awt import event
from java.lang import System
from java.io import File
from javax.swing import JFileChooser

class ImageViewer(awt.Frame):
    def __init__(self):
        bar = awt.MenuBar()
        file_menu = awt.Menu("File")
        open_item = awt.MenuItem("Open", actionPerformed=self.open_image)
        close_item = awt.MenuItem("Close", actionPerformed=lambda e: System.exit(0))
        file_menu.add(open_item)
        file_menu.add(close_item)
        bar.add(file_menu)
        self.menuBar = bar

        self.windowClosing = lambda e: System.exit(0)
        self.bounds = 100, 100, 400, 400
        self.im = None

    def open_image(self, e):
        chooser = JFileChooser()
        result = chooser.showOpenDialog(self)
        if result == JFileChooser.APPROVE_OPTION:
            file = chooser.getSelectedFile()
            self.im = awt.Toolkit.getDefaultToolkit().getImage(file.getAbsolutePath())
            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)
            self.repaint()

    def paint(self, g):
        if self.im:
            g.drawImage(self.im, self.insets.left, self.insets.top, self)

viewer = ImageViewer()
viewer.visible = 1

在这个示例中,我们创建了一个 ImageViewer 类,它继承自 awt.Frame 。在构造函数中,我们创建了一个菜单,包含“Open”和“Close”两个菜单项。当用户点击“Open”菜单项时,会弹出文件选择对话框,选择图像文件后,加载并显示该图像。

5.2 Jython GUI开发的优势总结
  • 简洁的语法 :Jython去除了Java中繁琐的类型声明、访问修饰符等,代码更加简洁易读。例如,在创建GUI组件时,Jython的代码行数明显少于Java。
  • 自动属性和事件处理 :Jython的自动Bean属性和事件属性使得属性访问和事件处理更加方便。开发者可以直接将属性作为类的属性进行访问和赋值,将事件作为属性进行处理,无需编写大量的接口实现和匿名内部类。
  • 强大的兼容性 :由于Jython基于Java,它可以直接使用Java的AWT和Swing类,同时也能利用Java的丰富类库。这使得开发者可以在Jython中使用Java的各种功能,为GUI开发提供了更多的可能性。
  • pawt 包的便利 pawt 包提供了一些方便的模块,如 GridBag 类简化了复杂布局的设置, colors 模块提供了预定义的颜色名称, test 函数方便了图形组件的测试, pawt.swing 模块帮助选择合适的Swing库。
5.3 注意事项
  • 类型转换 :虽然Jython会自动进行Java类型和Python类型之间的转换,但在某些情况下,仍需要注意类型的匹配。例如, setVisible 方法需要Java布尔类型的参数,Jython会将 1 转换为 true 0 转换为 false ,但在使用时要确保类型的正确性。
  • pawt.swing 模块的使用 pawt.swing 模块在编译Jython时可能会出现问题,因此在子类化Swing组件或编译Jython应用程序时,建议直接使用 javax.swing
  • 命名冲突 :Jython支持多种属性和方法,可能会出现命名冲突。要注意Jython的名称优先级规则,优先使用实例方法,避免因命名冲突导致的错误。
6. 总结与展望

通过本文的介绍,我们可以看到Jython在GUI开发中具有诸多优势,它结合了Python的简洁语法和Java的强大功能,为开发者提供了一种高效、便捷的GUI开发方式。无论是初学者还是有经验的开发者,都可以利用Jython快速开发出功能丰富的GUI应用。

在未来,随着技术的不断发展,Jython可能会在更多领域得到应用。例如,与机器学习、数据分析等领域结合,开发出具有可视化界面的应用程序。同时,Jython社区也可能会不断完善和扩展 pawt 包等工具,提供更多的便利和功能。

为了更好地理解Jython GUI开发的流程,下面是一个简单的mermaid流程图:

graph LR
    A[开始] --> B[选择开发工具包(AWT或Swing)]
    B --> C[创建GUI组件]
    C --> D[设置组件属性和事件]
    D --> E[布局管理]
    E --> F[添加组件到容器]
    F --> G[显示GUI]
    G --> H[处理用户交互]
    H --> I[结束]

同时,为了更清晰地对比Java和Jython在GUI开发中的差异,我们可以看下面的表格:
| 对比项 | Java | Jython |
| ---- | ---- | ---- |
| 语法 | 类型声明、访问修饰符、分号和大括号 | 简洁,无上述元素 |
| 属性访问 | 使用 get set 方法 | 直接作为类属性访问和赋值 |
| 事件处理 | 实现接口和使用匿名内部类 | 直接将可调用对象分配给事件属性 |
| 类的使用 | 必须使用类 | 类可选,但复杂无类GUI实现困难 |

总之,Jython为GUI开发带来了新的思路和方法,希望开发者能够充分利用其优势,开发出优秀的GUI应用程序。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值