ui的设计,控件id的记录是一件比较繁琐的事情。
此外,赋值和读取数据也比较繁琐,非常不pythonic。
有没有神马办法优雅一点呢?life is short。
鉴于控件有name属性,通过dir(Entry_obj)得知,存放在一个_name的属性里面。于是就有了以下代码:
Entry(frame,name='your_id1').grid(row=x1,column=y1) Entry(frame,name='your_id2').grid(row=x2,column=y2) ... Entry(frame, name='your_idn').grid(row=xn,column=yn)
当然还有其他输入控件,此处略。
可以通过 frame.grid_slaves()得到其上面所有的控件。
然后可以通过 __class__来判断具体类型。可以在此基础上判断哪些控件是输入控件,如下:
for ctrl in frame.grid_slaves(): if ctrl.__class__.__name__ in ('Entry','Text',....): return True return False
此外,我们还需要一个映射表,来关联界面控件和我们的模型。
mapper={'model_attr1':ctrol_id1, 'model_attr2':ctrol_id2,...., 'model_attrn':ctrol_idn}
通过这个映射表,我们可以方便的进行表单数据的设置和读取,假设所有输入控件皆为Entry,于是就有了一下读取表单的代码:
def get_form_data(self): vals = {}.fromkeys(self.mapper.keys(),False) ctrls = dict([(x._name,x) for x in self.input_ctrls]) for k,v in self.mapper.items(): ctrl = ctrls.get(v,False) if ctrl and ctrl.get(): vals.update({k:ctrl.get()}) #print vals logging.debug('product form data:%s'%vals) return vals
关于input_ctrls的说明,因为控件的从属关系,本质上应该是一个树。
可以采用一个设计:
将控件放入到frame中,所有的frame放入到一个列表中frame_list。
frame中的控件用grid方式布局。于是就有了以下读取界面上所有输入控件的代码:
def _is_input(self, ctrl_obj): """ 是否为输入控件 :param ctrl_obj: :return: """ if ctrl_obj: name = ctrl_obj._name matches= ['txt_','cb_'] if (name.split('_')[0]+'_') in matches: return True return False def _get_input_ctrls(self, container): if container: cs = container.grid_slaves() cs.extend(container.pack_slaves()) for c in cs: if self._is_input(c): self.input_ctrls.append(c) else: self._get_input_ctrls(c) def get_input_ctrls(self): self.input_ctrls=[] for f in self.frames: self._get_input_ctrls(f) return self.input_ctrls
是否输入控件采用了根据_name属性进行的判断。此处需要有coding规范约束。
实际上可以改写_is_input逻辑,根据__class__来进行枚举判断。
相应的设置表单的值逻辑如下:
def set_data(self, data): if not isinstance(data,dict): raise TypeError if not self.mapper: raise "set_mapper method must be called before this method being called." ctrls = dict([(x._name,x) for x in self.get_input_ctrls()]) for k,v in self.mapper.items(): if True:#data.get(k,None) is not None: ctrl_obj = ctrls.get(v) if ctrl_obj: state = ctrl_obj['state'] ctrl_obj.configure(state='normal') if isinstance(ctrl_obj,NewCBtn): ctrl_obj.checked(data.get(k)) elif isinstance(ctrl_obj,NewCBox): vals = data.get(k) if isinstance(vals,(list,tuple)): val= vals[-1] if not ctrl_obj.dictionary: t={vals[-1]:vals[0]} ctrl_obj.set_dict(t) ctrl_obj.set(val) else: ctrl_obj.value(vals) elif isinstance(ctrl_obj,NewImage): ctrl_obj.Image(data.get(k)) elif isinstance(ctrl_obj,Text): ctrl_obj.delete("1.0",END) ctrl_obj.insert("1.0",data.get(k)) elif isinstance(ctrl_obj,Entry): ctrl_obj.delete(0,END) ctrl_obj.insert(0, str(data.get(k) or '')) else: logging.warning('unkown control type object:%s'%ctrl_obj.__class__.__name__) ctrl_obj.configure(state=state)
其中,类Newxxx为自己定义的类:
NewCBox
class NewCBox(ttk.Combobox): def __init__(self, master, dictionary={}, *args, **kw): if not kw:kw={'values':sorted(list(dictionary.keys()))} else:kw.update({'values':sorted(list(dictionary.keys()))}) ttk.Combobox.__init__(self, master, *args, **kw) self.dictionary = dictionary #self.bind('<<ComboboxSelected>>', self.selected) #purely for testing purposes self.bind('<Control-a>', self.select_all_input) def select_all_input(self,event): #选择输入文本,不起作用 #print 'Ctrl+A' text = self.get() vals = self.cget('values') for i in range(len(vals)): if vals[i]==text: self.current(i) def set_dict(self, dictionary): self.dictionary=dictionary self['values']= sorted(list(dictionary.keys())) def value(self,new_val=None): if new_val is None: key = self.get() d= self.dictionary k1=key.encode('UTF-8')#ASCII码就是UTF-8编码的最少字节的版本 k2=key.encode('GB2312') k3=key.encode('CP936') return d.get(key,d.get(k1,d.get(k2,d.get(k3,False))))else: for k,v in self.dictionary.items(): if str(new_val)==str(v): self.set(k) return k # def selected(self, event): #Just to test # print(self.value())
NewImage
import io class NewImage(Label): def __init__(self, master=None,image_path=r'nullImage.png', cnf={}, **kw): image = Image.open(image_path) self.default_image = ImageTk.PhotoImage(image) self.current_image = self.default_image Label.__init__(self, image=self.current_image, master=master, cnf=cnf,**kw) def Image(self,base64_source=None,encoding='base64'): if not base64_source is None: if base64_source: base64_source = resize_img(base64_source,size=(100,100)) image_stream = io.BytesIO(base64_source.decode(encoding)) image = Image.open(image_stream) self.current_image =ImageTk.PhotoImage(image)#PhotoImage(file='pro.gif')# PhotoImage(data=background_stream.getvalue()) self['image']=self.current_image else: self['image']=self.default_image return self['image']
NewCBtn
class NewCBtn(Checkbutton): def __init__(self, master,*args, **kw): self.value = IntVar() if not kw:kw={'variable':self.value} else:kw.update({'variable':self.value}) Checkbutton.__init__(self,master,*args,**kw) def checked(self,value=None): if value is not None: self.value.set(value and 1 or 0) return self.value.get()