wxPython 笔记

安装

Win7 / Win10

直接通过 pip install wxpython 安装

Ubuntu18.04 / Ubuntu 20.04

在Linux下的安装会稍微麻烦, 可以参考官网上的说明 https://wxpython.org/pages/downloads/

因为存在不同的环境, 以及Gtk2, Gtk3, 所以没有针对各个发行版直接可用的pip whl文件, 通过 pip install wxpython的话, 实际上会去下载源码到本地编译安装, 参考 https://wxpython.org/blog/2017-08-17-builds-for-linux-with-pip/index.html  其中What You Need部分, 在Ubuntu下需要安装这些包

# 安装依赖
sudo apt install make gcc libgtk-3-dev ibgstreamer-gl1.0-0 freeglut3 freeglut3-dev python3-gst-1.0 libglib2.0-dev ubuntu-restricted-extras libgstreamer-plugins-base1.0-dev

# 从国内源下载, 之后会自动启动编译
pip3 install -i https://pypi.mirrors.ustc.edu.cn/simple/ wxpython

但是这个编译时间非常长, 在i5 2520M 下编译需要将近一个小时.

还好官方有提供Ubuntu的whl文件, 可以直接安装, 从 https://extras.wxpython.org/wxPython4/extras/linux/gtk3/ubuntu-20.04/ 直接下载20.04的whl文件, 在本地通过pip安装. 对于Ubuntu18.04存在不同的python版本, 选择不同的cp36, cp37, cp38

pip3 install -U -f wxPython-4.1.0-cp38-cp38-linux_x86_64.whl wxPython

在安装的过程中, 会检查numpy并下载安装

在后续运行代码时, 如果遇到 libSDL2-2.0.so.0: cannot open shared object file: No such file or directory 错误, 则需要安装 libsdl2-2.0-0

sudo apt install libsdl2-2.0-0

 

打包生成可执行文件

在Ubuntu下安装pyinstaller, 然后在项目目录下执行命令

$ pip3 install pyinstaller
$ pyinstaller v2subform.py
36 INFO: PyInstaller: 3.6
36 INFO: Python: 3.6.9
36 INFO: Platform: Linux-4.15.0-99-generic-x86_64-with-Ubuntu-18.04-bionic
37 INFO: wrote /home/milton/PycharmProjects/python-tools/v2sub/v2subform.spec
38 INFO: UPX is not available.
40 INFO: Extending PYTHONPATH with paths
['/home/milton/PycharmProjects/python-tools/v2sub',
 '/home/milton/PycharmProjects/python-tools/v2sub']
40 INFO: checking Analysis
...
8115 INFO: Building COLLECT COLLECT-00.toc
8803 INFO: Building COLLECT COLLECT-00.toc completed successfully.

最后生成的结果在dist目录下, 如果运行时不希望出现命令行, 可以加上 --noconsole 参数

在用下面的代码测试时, 发现生成的结果文件非常大, 有263M, 其中wx目录就占了200多M, 

$ ll -h wx
total 199M
-rwxr-xr-x 1 milton milton  18M May  7 11:16 _adv.cpython-36m-x86_64-linux-gnu.so*
-rwxr-xr-x 1 milton milton 156M May  7 11:16 _core.cpython-36m-x86_64-linux-gnu.so*
-rwxr-xr-x 1 milton milton  14M May  7 11:16 _grid.cpython-36m-x86_64-linux-gnu.so*
-rwxr-xr-x 1 milton milton  12M May  7 11:16 _html.cpython-36m-x86_64-linux-gnu.so*
-rwxr-xr-x 1 milton milton 795K May  7 11:16 siplib.cpython-36m-x86_64-linux-gnu.so*

 这么大的发布文件, 在日常和商业上都是非常不适用的, 这就仅作为一个尝试吧.

代码示例

#!/usr/bin/python3
# -*- coding: UTF-8 -*-

import base64
import json
import os
import subprocess
import requests
import wx
import wx.grid as grid

class MainFrame(wx.Frame):
    def __init__(self, parent, title):
        super().__init__(parent=parent, title=title, size=(665, 450))
        self.panel = MainPanel(self)
        self.Show(True)


class MainPanel(wx.Panel):
    def __init__(self, parent):
        super().__init__(parent=parent)
        self.configs = self.loadConfig()

        sizer1 = wx.BoxSizer(wx.VERTICAL)
        self.text_ctrl = wx.TextCtrl(self, -1)
        width, height = self.text_ctrl.GetSize().Get()
        # Make it shows the URL completely, weird bug of TextCtrl
        self.text_ctrl.SetSize((500, height))
        sizer1.Add(self.text_ctrl, 0, wx.ALL | wx.EXPAND, 5)
        self.main_grid = grid.Grid(self)
        self.main_grid.CreateGrid(0, 6)
        # Forbid row height changes
        self.main_grid.EnableDragRowSize(False)
        # Global forbid cell editing
        self.main_grid.EnableEditing(False)
        # Selection of the entire rows only
        self.main_grid.SetSelectionMode(grid.Grid.GridSelectRows)
        # Hide row header
        self.main_grid.HideRowLabels()
        # Set column headers
        self.main_grid.SetColLabelSize(grid.GRID_AUTOSIZE)
        self.main_grid.SetLabelFont(wx.Font(10, wx.FONTFAMILY_ROMAN, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL))

        self.main_grid.SetColLabelValue(0, 'ID')
        self.main_grid.SetColSize(0, 30)
        self.main_grid.SetColMinimalWidth(0, 30)

        self.main_grid.SetColLabelValue(1, 'Name')
        self.main_grid.SetColSize(1, 180)

        self.main_grid.SetColLabelValue(2, 'Address')
        self.main_grid.SetColSize(2, 180)
        self.main_grid.SetColLabelValue(3, 'Port')
        self.main_grid.SetColSize(3, 40)
        self.main_grid.SetColLabelValue(4, 'Network')
        self.main_grid.SetColSize(4, 60)
        self.main_grid.SetColLabelValue(5, 'Test')
        self.main_grid.SetColSize(5, 40)
        self.main_grid.Bind(grid.EVT_GRID_SELECT_CELL, self.onSingleSelect)
        sizer1.Add(self.main_grid, 1, wx.ALL | wx.EXPAND, 5)

        sizer2 = wx.BoxSizer(wx.VERTICAL)
        btn_import = wx.Button(self, label='Import')
        btn_import.Bind(wx.EVT_BUTTON, self.onImport)
        sizer2.Add(btn_import, 0, wx.ALL | wx.EXPAND, 5)

        btn_ping = wx.Button(self, label='Ping')
        btn_ping.Bind(wx.EVT_BUTTON, self.onPing)
        sizer2.Add(btn_ping, 0, wx.ALL | wx.EXPAND, 5)

        btn_select = wx.Button(self, label='Select')
        sizer2.Add(btn_select, 0, wx.ALL | wx.EXPAND, 5)

        sizer = wx.BoxSizer(wx.HORIZONTAL)
        sizer.Add(sizer1, 1, wx.EXPAND, 0)
        sizer.Add(sizer2, 0, wx.EXPAND, 0)
        self.SetSizer(sizer)

        # load subscribe url
        self.text_ctrl.SetValue(self.configs['subscribe_url'])
        # load nodes into grid
        self.loadGrid()

    def loadGrid(self):
        rows = self.main_grid.GetNumberRows()
        if rows > 0:
            self.main_grid.DeleteRows(0, rows)
        for k in self.configs['nodes']:
            node = self.configs['nodes'][k]
            self.main_grid.AppendRows(1)
            pos = self.main_grid.GetNumberRows() - 1
            self.main_grid.SetCellValue(pos, 0, k)
            self.main_grid.SetCellValue(pos, 1, node['ps'])
            self.main_grid.SetCellValue(pos, 2, node['add'])
            self.main_grid.SetCellValue(pos, 3, str(node['port']))
            self.main_grid.SetCellValue(pos, 4, node['net'])

        self.main_grid.SetFocus()

    def onSingleSelect(self, event):
        print('You selected row:{}, col:{}'.format(event.GetRow(), event.GetCol()))
        event.Skip()

    def loadConfig(self):
        this_path = os.path.dirname(__file__)
        conf_path = os.path.join(this_path, 'v2subform.conf')
        if not os.path.exists(conf_path):
            open(conf_path, 'w+')

        with open(conf_path, 'r') as conf_file:
            try:
                configs = json.load(conf_file)
                print('Loaded configs from file: {}'.format(conf_path))
            except json.decoder.JSONDecodeError:
                print('File {} is not a valid config file'.format(conf_path))
                configs = None

        if configs is None or len(configs) == 0:
            print('The configuration is empty, please input the necessary information')
            configs = {
                'subscribe_url': '',
                'local_port': 1080,
                'nodes' : []
            }
            with open(conf_path, 'w') as conf_file:
                json.dump(configs, conf_file, indent=2)

        return configs

    def onImport(self, event):
        subscribe_url = self.text_ctrl.GetValue()
        if not subscribe_url:
            dialog = wx.MessageDialog(self, 'You didn\'t input subscribe URL', 'Error', wx.OK)
            dialog.ShowModal()
            dialog.Destroy()
            return

        rows = self.main_grid.GetNumberRows()
        if rows > 0:
            self.main_grid.DeleteRows(0, rows)

        self.configs['subscribe_url'] = subscribe_url
        print('Subscribed URL: {}\nLocal port:{}\n'.format(self.configs['subscribe_url'], self.configs['local_port']))
        print('Reading server nodes... ', end='')
        node_strings = base64.b64decode(requests.get(self.configs['subscribe_url']).content).splitlines()
        print('Done')
        nodes = {}
        for i in range(len(node_strings)):
            node = json.loads(base64.b64decode(bytes.decode(node_strings[i]).replace('vmess://', '')))
            print('[{:>3}] {:25} {:30}:{}'.format(i, node['ps'], node['add'], node['port']))
            nodes[str(i)] = node
        self.configs['nodes'] = nodes

        this_path = os.path.dirname(__file__)
        conf_path = os.path.join(this_path, 'v2subform.conf')
        with open(conf_path, 'w') as conf_file:
            json.dump(self.configs, conf_file, indent=2)
        self.loadGrid()

    def onPing(self, event):
        rows = self.main_grid.GetSelectedRows()
        if rows is None:
            dialog = wx.MessageDialog(self, 'You didn\'t select any row', 'Error', wx.OK)
            dialog.ShowModal()
            dialog.Destroy()
            return
        id = self.main_grid.GetCellValue(rows[0], 0)
        node = self.configs['nodes'][id]
        result = subprocess.run(['ping', node['add']], capture_output=True)
        if not result is None:
            dialog = wx.MessageDialog(self, result.stdout, 'Result', wx.OK)
            dialog.ShowModal()
            dialog.Destroy()
            return


if __name__ == '__main__':
    app = wx.App(False)
    frame = MainFrame(None, 'Small Editor')
    app.MainLoop()

  

posted on 2020-05-07 12:15  Milton  阅读(833)  评论(0编辑  收藏  举报

导航