Python之点到为止: 给你的GUI换个feel
上篇文章讲了GUI的故事, nodejs有强大的electron, python有没有类似的? 能不能用前端代码写Python GUI程序?
是否需要
还是那句话, 看你是否真正需要. 因为每一个新模块都代表着一段新知识. 学习是需要成本的, 不要感觉不错用这个库然后没几天又发现别的也不错换别的. 这样浪费时间不说最后也有可能连个Hello World都没写出来. 可能这个库更适合适合Python全栈? ?
相关库
- cefpython3 —— 开源项目CEF与Python结合
这东西就是个浏览器, 所以浏览器能干的东西他都行. 浏览器不行的就对接Python. 你甚至可以用它来做爬虫, 但是没必要headless就够用. 至于爬虫方面等以后慢慢讲.
实操
本文会提供一个完全可以移植Demo,开箱即用, 但是写文章的时候Python最新版本为3.8.2, cefpython3只支持Python3.7版本及以前, 最近一次更新在2018年8月21日基于Chromium 66.0.3359.181编译的. 所以就目前来看不是特别推荐学习, 至少我现在是在用electron, nodejs学习成本对于后端全栈来说不是太高的. 前端全栈更别说了,nodejs这玩意必须会的东西.
先看看成品什么样, 演示视频中前端代码放在服务器上, 所以加载会有延迟. 实际编写推荐放在本地.
提醒看代码别劝退, 因为很好理解. 实在不理解的话也不用去理解, 因为只用一次. 窗口创建部分都来源自官方Demo: https://github.com/cztomczak/cefpython/blob/master/examples/pywin32.py
from cefpython3 import cefpython as cef import math import os import sys import win32api import win32con import win32gui DEFAUTL_URL = 'https://api.virace.cc/jgah/cef/' DEFAULT_USERNAME = 'root' DEFAULT_PASSWORD = 'root' DEFAULT_WINDOW_TITLE = '处女座之最 - 演示程序' # Globals WindowUtils = cef.WindowUtils() # 全局窗口句柄 g_windows_handle = None # 多线程 g_multi_threaded = False class BindFunction: @staticmethod def get_title(callback): callback.Call(DEFAULT_WINDOW_TITLE) @staticmethod def login(data, callback): if 'username' not in data or 'password' not in data: callback.Call(False, '提交格式不正确') elif data['username'] == '' or data['password'] == '': callback.Call(False, '用户名密码不能为空') elif data['username'] == DEFAULT_USERNAME and data['password'] == DEFAULT_PASSWORD: callback.Call(True) else: callback.Call(False, '用户名或密码错误.') @staticmethod def min(): win32gui.PostMessage(g_windows_handle, win32con.WM_SYSCOMMAND, win32con.SC_MINIMIZE) @staticmethod def max(): # 判断窗口状态 if win32gui.GetWindowPlacement(g_windows_handle)[1] == win32con.SW_SHOWMAXIMIZED: win32gui.PostMessage(g_windows_handle, win32con.WM_SYSCOMMAND, win32con.SC_RESTORE) else: win32gui.PostMessage(g_windows_handle, win32con.WM_SYSCOMMAND, win32con.SC_MAXIMIZE) @staticmethod def close(): win32gui.PostMessage(g_windows_handle, win32con.WM_CLOSE) @staticmethod def move(): # 捕获鼠标 win32gui.ReleaseCapture() # 移动 win32gui.SendMessage(g_windows_handle, win32con.WM_SYSCOMMAND, win32con.SC_MOVE + win32con.HTCAPTION, 0) pass def main(): sys.excepthook = cef.ExceptHook # To shutdown all CEF processes on error settings = { "multi_threaded_message_loop": g_multi_threaded, } cef.Initialize(settings=settings) window_proc = { win32con.WM_CLOSE: close_window, win32con.WM_DESTROY: exit_app, win32con.WM_SIZE: WindowUtils.OnSize, win32con.WM_SETFOCUS: WindowUtils.OnSetFocus, win32con.WM_ERASEBKGND: WindowUtils.OnEraseBackground } global g_windows_handle g_windows_handle = create_window(title=DEFAULT_WINDOW_TITLE, class_name=DEFAULT_WINDOW_TITLE, width=1100, height=730, window_proc=window_proc, icon="resources/chromium.ico") window_info = cef.WindowInfo() window_info.SetAsChild(g_windows_handle) if g_multi_threaded: # When using multi-threaded message loop, CEF's UI thread # is no more application's main thread. In such case browser # must be created using cef.PostTask function and CEF message # loop must not be run explicitilly. cef.PostTask(cef.TID_UI, create_browser, window_info, {}, DEFAUTL_URL) win32gui.PumpMessages() else: create_browser(window_info=window_info, settings={}, url=DEFAUTL_URL) cef.MessageLoop() cef.Shutdown() def create_browser(window_info, settings, url): assert (cef.IsThread(cef.TID_UI)) bind_js(cef.CreateBrowserSync(window_info=window_info, settings=settings, url=url)) def bind_js(browser): """ 绑定js事件, 也可以用LoadHandler调用 :param browser: :return: """ bindings = cef.JavascriptBindings() bindings.SetFunction("py_title", BindFunction.get_title) bindings.SetFunction("py_login", BindFunction.login) bindings.SetFunction("py_move", BindFunction.move) bindings.SetFunction("py_windows_min", BindFunction.min) bindings.SetFunction("py_windows_max", BindFunction.max) bindings.SetFunction("py_windows_close", BindFunction.close) browser.SetJavascriptBindings(bindings) def create_window(title, class_name, width, height, window_proc, icon): # Register window class wndclass = win32gui.WNDCLASS() wndclass.hInstance = win32api.GetModuleHandle(None) wndclass.lpszClassName = class_name wndclass.style = win32con.CS_VREDRAW | win32con.CS_HREDRAW wndclass.hbrBackground = win32con.COLOR_WINDOW wndclass.hCursor = win32gui.LoadCursor(0, win32con.IDC_ARROW) wndclass.lpfnWndProc = window_proc atom_class = win32gui.RegisterClass(wndclass) assert (atom_class != 0) # Center window on screen. screenx = win32api.GetSystemMetrics(win32con.SM_CXSCREEN) screeny = win32api.GetSystemMetrics(win32con.SM_CYSCREEN) xpos = int(math.floor((screenx - width) / 2)) ypos = int(math.floor((screeny - height) / 2)) if xpos < 0: xpos = 0 if ypos < 0: ypos = 0 # Create window window_style = (win32con.WS_POPUP | win32con.WS_CLIPCHILDREN | win32con.WS_VISIBLE) window_handle = win32gui.CreateWindow(class_name, title, window_style, xpos, ypos, width, height, 0, 0, wndclass.hInstance, None) assert (window_handle != 0) # Window icon icon = os.path.abspath(icon) if not os.path.isfile(icon): icon = None if icon: # Load small and big icon. # WNDCLASSEX (along with hIconSm) is not supported by pywin32, # we need to use WM_SETICON message after window creation. # Ref: # 1. http://stackoverflow.com/questions/2234988 # 2. http://blog.barthe.ph/2009/07/17/wmseticon/ bigx = win32api.GetSystemMetrics(win32con.SM_CXICON) bigy = win32api.GetSystemMetrics(win32con.SM_CYICON) big_icon = win32gui.LoadImage(0, icon, win32con.IMAGE_ICON, bigx, bigy, win32con.LR_LOADFROMFILE) smallx = win32api.GetSystemMetrics(win32con.SM_CXSMICON) smally = win32api.GetSystemMetrics(win32con.SM_CYSMICON) small_icon = win32gui.LoadImage(0, icon, win32con.IMAGE_ICON, smallx, smally, win32con.LR_LOADFROMFILE) win32api.SendMessage(window_handle, win32con.WM_SETICON, win32con.ICON_BIG, big_icon) win32api.SendMessage(window_handle, win32con.WM_SETICON, win32con.ICON_SMALL, small_icon) return window_handle def close_window(window_handle, message, wparam, lparam): browser = cef.GetBrowserByWindowHandle(window_handle) browser.CloseBrowser(True) # OFF: win32gui.DestroyWindow(window_handle) return win32gui.DefWindowProc(window_handle, message, wparam, lparam) def exit_app(*_): win32gui.PostQuitMessage(0) return 0 if __name__ == '__main__': main()
先说说这个代码的特点:
- pywin32, 纯Windows API创建窗口
- 无边框窗口, 无标题栏.
- 解决无边框窗口拖动问题
- 最大最小化关闭功能完善
这段代码看起来很多, 其实大部分都是创建窗口. 然而cefpython可以配合绝大部分GUI模块一起配合使用 https://github.com/cztomczak/cefpython/blob/master/examples/README-examples.md#gui-frameworks QT、tkinter、wxpython这几个常用跨平台的都可以的哦, 如果需要多窗口的时候这些, 如果但窗口就够用上面代码复制拿走直接加上自己的功能就行了.
其实整个代码只有BindFunction类和绑定js代码是后打的. 剩下什么创建窗口都是官方例子里的, 拿过来就用能看懂就看懂看不懂咱会用不就完了. 但是也没用几个API 哈哈??
撸了一个前端界面, 就是一个普普通通的登录界面, 登录成功后跳转的页面就改了个标题而已. https://api.virace.cc/jgah/cef/
上面的代码是经过绑定JS的方式交互, 说实话如果方法少的话还是挺爽的. 还有一种前后端分离写法, 懂后端的应该已经懂了. 就是前端只干前端显示的活, Python在本地启动一个http服务器, 比如Flask、django(有点大). 再配合上Vue, 嗬~~ 所有操作都通过服务端设置的API操作, 这样后续维护比较方便. 所以说这个框架适合Python全栈来用.
然后上面提供的登录页面, 大部分库用的都是cdn, 源码都在Github对应库分享了. 只是写个例子, 因为网页在本地访问就可以放心大胆的加各种特效, 也不会因为网络问题导致打开速度慢等问题.
总结
虽然听长时间不更新了, 但是你说用Python写这个东西还是挺爽的. 我最早一次用也两年以前了, 为了码文章又看文档跑了一遍. 现在的程序如果你们打开的网页想放在服务器中, 就会引文网络问题刚打开会有白屏的问题. 给你们个思路, 可以将窗口设置透明, 然后利用Animate.css上几个动画. 就会缓和好多. 但还是推荐在本地打开.
那么我就点到为止了
Just give a hint.
相关资料: 登录界面: https://api.virace.cc/jgah/cef/ cefpython3: https://github.com/cztomczak/cefpython/ 本期文件: https://github.com/Virace/python-jgah/tree/master/Main/2154 ps: 官方文档例子超多
另外多说一句: 转载请注明出处, 点到为止系列为博主原创文章.
文章评论