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: 官方文档例子超多
另外多说一句: 转载请注明出处, 点到为止系列为博主原创文章.
文章评论