Python:GUI 和后端
Python: GUI and back-end
我正在从事一个数据采集项目,使用 python 为不同的仪器(电压表、照相机等)构建多个 data-monitoring/controlling 程序。我正在使用 python3 和 tkinter(由于其开放许可证)作为我的 GUI。
现在每种乐器的基本结构是:
import packages
class all_GUI():
def __init__():
device = volt_device()
functions linking GUI elements to HW calls
mainloop()
class volt_device():
def __init__():
functions to access HW functionality
mainapp = all_GUI()
它更有效,但代码中有很多 GUI 和硬件之间的调用 类。如果我想重用代码的 GUI 部分并 link 它与另一个硬件板,我几乎必须重写整个东西。你可以想象这不是很吸引人:-)
我想 class volt_device
可以移动到一个单独的文件中并根据需要加载。但是因为 GUI 从 HW 部分调用许多功能,每个 HW 文件(例如支持不同的板)必须具有完全相同的命名约定。不可怕,但也不是我认为最好的。
我正在考虑尽可能将 GUI 和 HW 分开,但遇到了一些困难。我正在研究模型-视图-控制器模式,但无法使其工作。我的想法是拥有三个程序:
import GUI
import HW
objGUI =
objHW =
link functions to interface objects
mainloop()
class GUI():
def __init__():
build GUI here with all elements
(this is getting sticky since I need to define functions to be executed when GUI values change
or buttons are pushed)
有多个硬件文件支持不同的仪器。
class HW():
def __init__():
define hardware board, have functions to change/monitor values
理想情况下,我会有一个相对简单的 HW 文件(文件 3)。要拥有全新的虚拟设备,我必须加载 GUI 部分(文件 2;未修改)并编写一个简单的“控制器”(文件 1)linking GUI 元素到硬件功能。听起来很简单...
当我尝试 link GUI 和 HW 一起时,我卡住了。我不确定如何正确处理 GUI 元素并为它们分配适当的 HW call/function。也许整个想法是有缺陷的,GUI/HW 分离需要以不同的方式处理...
我确信这个问题在我找不到它之前就已经解决了……或者现在就解决了。如果您有任何建议 and/or 编码参考,我将不胜感激。
谢谢。
拉多万
...would have to have the exact same naming convention. Not terrible, but
not the best either I think.
相反,那可能是最好的方法。本质上,您将创建一个通用接口,并让每个“板”用它的具体细节或 subclass 实现接口。然后你为 tkinter
创建一个 class,它可以从 methods
和 arguments
.
构建一个接口
两个显示都是自动生成的,并且一切都以一种或另一种方式回到最基本的组件。
非常通用和简化的示例:
import tkinter as tk, abc
from typing import List, Tuple, Callable, Iterable, Dict
import inspect
#create formal interface
class IGenericBoard(metaclass=abc.ABCMeta):
@classmethod
def __subclasshook__(cls, subclass):
isinterface = hasattr(subclass, 'read_pin') and callable(subclass.read_pin)
isinterface &= hasattr(subclass, 'write_pin') and callable(subclass.write_pin)
return isinterface
@abc.abstractmethod
def generic_pin_read(self, pin:int) -> int:
raise NotImplementedError
@abc.abstractmethod
def generic_pin_write(self, pin:int, data:int):
raise NotImplementedError
#implement IGenericBoard
class GenericBoard(IGenericBoard):
@property
def model(self):
#the "model type" for this board instance
return type(self).__name__
@property
def prefix(self) -> List:
#the prefix(es) to use when finding functions
return self._prefix if isinstance(self._prefix , (List, Tuple)) else [self._prefix]
@property
def msgvar(self) -> tk.StringVar:
#the output message var
return self._msgvar
@property
def attributes(self) -> Dict:
#get everything in one shot ~ for **kwargs
return dict(
model =self.model ,
prefix=self.prefix,
msgvar=self.msgvar,
)
def __init__(self):
self._prefix = 'generic'
self._msgvar = tk.StringVar()
def generic_pin_read(self, pin:int) -> int:
self._msgvar.set(f'reading pin {pin}')
#... really do this
return 0
def generic_pin_write(self, pin:int, data:int):
self._msgvar.set(f'writing {data} on pin {pin}')
#... really do this
#"final" class
class LEDBoard(GenericBoard):
def __init__(self):
GenericBoard.__init__(self)
self._prefix = self.prefix + ['led']
def led_blink_write(self, pin:int=13):
self.generic_pin_write(pin, 1)
self._msgvar.set(f'blinking on pin {pin}')
#... really do this
''' tkBaseBoard
the baseclass for all "tk[Version]Board" classes
generates form interfaces for methods with the proper prefix(es)
'''
class tkBaseBoard(tk.Frame):
def __init__(self, master, model, msgvar, prefix, **kwargs):
tk.Frame.__init__(self, master, **{'bd':2, 'relief':'raised', **kwargs})
self.grid_columnconfigure(0, weight=1)
#board model label
tk.Label(self, text=model, font="Consolas 12 bold").grid(row=0, column=0, sticky='w')
#message output from board
self.output_ent = tk.Entry(self, width=30, textvariable=msgvar)
self.output_ent.grid(row=2, column=0, sticky='e')
#common feature label configuration
self.lbl_opts = dict(width=6, anchor='w', font='Consolas 10')
#annotation conversion
self.conversion = {
"<class 'int'>" :lambda: tk.IntVar(),
"<class 'str'>" :lambda: tk.StringVar(),
"<class 'bool'>" :lambda: tk.BooleanVar(),
"<class 'float'>":lambda: tk.DoubleVar(),
}
#build a feature for every "feat_" suffixed method
for feature in [func for func in dir(self) if callable(getattr(self, func)) and func.split('_')[0] in prefix]:
self._add_feature(feature)
#create a list of variable values
def __tovalue(self, vars) -> List[int]:
return [v.get() for v in vars]
#dynamically create the gui for a method
def _add_feature(self, feature):
main = tk.Frame(self)
main.grid(sticky='we')
#parse feature components
command = getattr(self, feature)
featcmp = feature.split('_')
if featcmp and len(featcmp) == 3:
_, label, action = featcmp
#create a list of Vars based on command argument types
args, vars = inspect.signature(command).parameters, []
for name in args:
try:
#convert annotations to the proper tk.[Type]Var
vars.append(self.conversion[str(args[name].annotation)]())
except KeyError:
#fallback to StringVar
vars.append(tk.StringVar())
#create label and button for this command
tk.Label(main, text=label, **self.lbl_opts).grid(row=0, column=0, sticky='e')
tk.Button(main, text=action, width=5, command=lambda v=vars: command(*self.__tovalue(v))).grid(row=0, column=1, sticky='w', padx=8)
#create an Entry for every argument in command
for i, v in enumerate(vars):
tk.Entry(main, width=2, textvariable=v).grid(row=0, column=i+2, sticky='w')
#give all the weight to the last row
main.grid_columnconfigure(i+2, weight=1)
else:
#feature name components did not pass expectations
raise ValueError('ValueError: feature component must consist of three underscore-seperated parts as: PREFIX_LABEL_ACTION')
##EXAMPLES OF THE ULTIMATE IMPLEMENTATION ALL OF THE ABOVE ALLOWS
#generate GenericBoard display
class tkGenericBoard(tkBaseBoard, GenericBoard):
def __init__(self, master, **kwargs):
GenericBoard.__init__(self)
tkBaseBoard.__init__(self, master, **self.attributes, **kwargs)
#generate LEDBoard display
class tkLEDBoard(tkBaseBoard, LEDBoard):
def __init__(self, master, **kwargs):
LEDBoard.__init__(self)
tkBaseBoard.__init__(self, master, **self.attributes, **kwargs)
##EXAMPLE BASE USAGE
if __name__ == '__main__':
root = tk.Tk()
root.title('Example')
root.configure(padx=2, pady=2)
tkGenericBoard(root).grid()
tkLEDBoard(root).grid()
root.mainloop()
我正在从事一个数据采集项目,使用 python 为不同的仪器(电压表、照相机等)构建多个 data-monitoring/controlling 程序。我正在使用 python3 和 tkinter(由于其开放许可证)作为我的 GUI。
现在每种乐器的基本结构是:
import packages
class all_GUI():
def __init__():
device = volt_device()
functions linking GUI elements to HW calls
mainloop()
class volt_device():
def __init__():
functions to access HW functionality
mainapp = all_GUI()
它更有效,但代码中有很多 GUI 和硬件之间的调用 类。如果我想重用代码的 GUI 部分并 link 它与另一个硬件板,我几乎必须重写整个东西。你可以想象这不是很吸引人:-)
我想 class volt_device
可以移动到一个单独的文件中并根据需要加载。但是因为 GUI 从 HW 部分调用许多功能,每个 HW 文件(例如支持不同的板)必须具有完全相同的命名约定。不可怕,但也不是我认为最好的。
我正在考虑尽可能将 GUI 和 HW 分开,但遇到了一些困难。我正在研究模型-视图-控制器模式,但无法使其工作。我的想法是拥有三个程序:
import GUI
import HW
objGUI =
objHW =
link functions to interface objects
mainloop()
class GUI():
def __init__():
build GUI here with all elements
(this is getting sticky since I need to define functions to be executed when GUI values change
or buttons are pushed)
有多个硬件文件支持不同的仪器。
class HW():
def __init__():
define hardware board, have functions to change/monitor values
理想情况下,我会有一个相对简单的 HW 文件(文件 3)。要拥有全新的虚拟设备,我必须加载 GUI 部分(文件 2;未修改)并编写一个简单的“控制器”(文件 1)linking GUI 元素到硬件功能。听起来很简单...
当我尝试 link GUI 和 HW 一起时,我卡住了。我不确定如何正确处理 GUI 元素并为它们分配适当的 HW call/function。也许整个想法是有缺陷的,GUI/HW 分离需要以不同的方式处理...
我确信这个问题在我找不到它之前就已经解决了……或者现在就解决了。如果您有任何建议 and/or 编码参考,我将不胜感激。
谢谢。
拉多万
...would have to have the exact same naming convention. Not terrible, but not the best either I think.
相反,那可能是最好的方法。本质上,您将创建一个通用接口,并让每个“板”用它的具体细节或 subclass 实现接口。然后你为 tkinter
创建一个 class,它可以从 methods
和 arguments
.
两个显示都是自动生成的,并且一切都以一种或另一种方式回到最基本的组件。
非常通用和简化的示例:
import tkinter as tk, abc
from typing import List, Tuple, Callable, Iterable, Dict
import inspect
#create formal interface
class IGenericBoard(metaclass=abc.ABCMeta):
@classmethod
def __subclasshook__(cls, subclass):
isinterface = hasattr(subclass, 'read_pin') and callable(subclass.read_pin)
isinterface &= hasattr(subclass, 'write_pin') and callable(subclass.write_pin)
return isinterface
@abc.abstractmethod
def generic_pin_read(self, pin:int) -> int:
raise NotImplementedError
@abc.abstractmethod
def generic_pin_write(self, pin:int, data:int):
raise NotImplementedError
#implement IGenericBoard
class GenericBoard(IGenericBoard):
@property
def model(self):
#the "model type" for this board instance
return type(self).__name__
@property
def prefix(self) -> List:
#the prefix(es) to use when finding functions
return self._prefix if isinstance(self._prefix , (List, Tuple)) else [self._prefix]
@property
def msgvar(self) -> tk.StringVar:
#the output message var
return self._msgvar
@property
def attributes(self) -> Dict:
#get everything in one shot ~ for **kwargs
return dict(
model =self.model ,
prefix=self.prefix,
msgvar=self.msgvar,
)
def __init__(self):
self._prefix = 'generic'
self._msgvar = tk.StringVar()
def generic_pin_read(self, pin:int) -> int:
self._msgvar.set(f'reading pin {pin}')
#... really do this
return 0
def generic_pin_write(self, pin:int, data:int):
self._msgvar.set(f'writing {data} on pin {pin}')
#... really do this
#"final" class
class LEDBoard(GenericBoard):
def __init__(self):
GenericBoard.__init__(self)
self._prefix = self.prefix + ['led']
def led_blink_write(self, pin:int=13):
self.generic_pin_write(pin, 1)
self._msgvar.set(f'blinking on pin {pin}')
#... really do this
''' tkBaseBoard
the baseclass for all "tk[Version]Board" classes
generates form interfaces for methods with the proper prefix(es)
'''
class tkBaseBoard(tk.Frame):
def __init__(self, master, model, msgvar, prefix, **kwargs):
tk.Frame.__init__(self, master, **{'bd':2, 'relief':'raised', **kwargs})
self.grid_columnconfigure(0, weight=1)
#board model label
tk.Label(self, text=model, font="Consolas 12 bold").grid(row=0, column=0, sticky='w')
#message output from board
self.output_ent = tk.Entry(self, width=30, textvariable=msgvar)
self.output_ent.grid(row=2, column=0, sticky='e')
#common feature label configuration
self.lbl_opts = dict(width=6, anchor='w', font='Consolas 10')
#annotation conversion
self.conversion = {
"<class 'int'>" :lambda: tk.IntVar(),
"<class 'str'>" :lambda: tk.StringVar(),
"<class 'bool'>" :lambda: tk.BooleanVar(),
"<class 'float'>":lambda: tk.DoubleVar(),
}
#build a feature for every "feat_" suffixed method
for feature in [func for func in dir(self) if callable(getattr(self, func)) and func.split('_')[0] in prefix]:
self._add_feature(feature)
#create a list of variable values
def __tovalue(self, vars) -> List[int]:
return [v.get() for v in vars]
#dynamically create the gui for a method
def _add_feature(self, feature):
main = tk.Frame(self)
main.grid(sticky='we')
#parse feature components
command = getattr(self, feature)
featcmp = feature.split('_')
if featcmp and len(featcmp) == 3:
_, label, action = featcmp
#create a list of Vars based on command argument types
args, vars = inspect.signature(command).parameters, []
for name in args:
try:
#convert annotations to the proper tk.[Type]Var
vars.append(self.conversion[str(args[name].annotation)]())
except KeyError:
#fallback to StringVar
vars.append(tk.StringVar())
#create label and button for this command
tk.Label(main, text=label, **self.lbl_opts).grid(row=0, column=0, sticky='e')
tk.Button(main, text=action, width=5, command=lambda v=vars: command(*self.__tovalue(v))).grid(row=0, column=1, sticky='w', padx=8)
#create an Entry for every argument in command
for i, v in enumerate(vars):
tk.Entry(main, width=2, textvariable=v).grid(row=0, column=i+2, sticky='w')
#give all the weight to the last row
main.grid_columnconfigure(i+2, weight=1)
else:
#feature name components did not pass expectations
raise ValueError('ValueError: feature component must consist of three underscore-seperated parts as: PREFIX_LABEL_ACTION')
##EXAMPLES OF THE ULTIMATE IMPLEMENTATION ALL OF THE ABOVE ALLOWS
#generate GenericBoard display
class tkGenericBoard(tkBaseBoard, GenericBoard):
def __init__(self, master, **kwargs):
GenericBoard.__init__(self)
tkBaseBoard.__init__(self, master, **self.attributes, **kwargs)
#generate LEDBoard display
class tkLEDBoard(tkBaseBoard, LEDBoard):
def __init__(self, master, **kwargs):
LEDBoard.__init__(self)
tkBaseBoard.__init__(self, master, **self.attributes, **kwargs)
##EXAMPLE BASE USAGE
if __name__ == '__main__':
root = tk.Tk()
root.title('Example')
root.configure(padx=2, pady=2)
tkGenericBoard(root).grid()
tkLEDBoard(root).grid()
root.mainloop()