I'm converting an old tkinter program to wxPython. One of the things from tk that I used liberally was tk.IntVar() and the like. Is there anything in wx that provides similar functionality?
Specifically, I'd like to be able to define module-level variables such as myvar = tk.StringVar(). Then when those variables are updated, have one or more UI elements update based on the new variable value just like what would happen with:
self.score = tk.Entry(self, textvariable=myvar.get())
here is how you would normally organize your app .... globals tend to be a bad idea
class MyNestedPanel(wx.Panel):
def __init__(self,*a,**kw):
...
self.user = wx.TextCtrl(self,-1)
def SetUser(self,username):
self.user.SetValue(username)
class MyMainPanel(wx.Panel):
def __init__(self,*a,**kw):
...
self.userpanel = MyNestedPanel(self,...)
def SetUsername(self,username):
self.userpanel.SetUser(username)
class MainFrame(wx.Frame):
def __init__(self,*a,**kw):
...
self.mainpanel = MyMainPanel(self,...)
def SetUsername(self,username):
self.mainpanel.SetUsername(username)
a = wx.App()
f = MainFrame(...)
f.Show()
a.MainLoop()
although you can make helper functions
def set_widget_value(widget,value):
if hasattr(widget,"SetWidgetValue"):
return widget.SetWidgetValue(value)
if isinstance(widget,wx.Choice):
return widget.SetStringSelection(value)
if hasattr(widget,"SetValue"):
return widget.SetValue(value)
if hasattr(widget,"SetLabel"):
return widget.SetLabel(value)
else:
raise Exception("Unknown Widget Type : %r"%widget)
def get_widget_value(widget):
if hasattr(widget,"GetWidgetValue"):
return widget.GetWidgetValue()
if isinstance(widget,wx.Choice):
return widget.GetStringSelection()
if hasattr(widget,"GetValue"):
return widget.GetValue()
if hasattr(widget,"GetLabel"):
return widget.GetLabel()
else:
raise Exception("Unknown Widget Type : %r"%widget)
class WidgetManager(wx.Panel):
def __init__(self,parent):
self._parent = parent
wx.Panel.__init__(self,parent,-1)
self.CreateWidgets()
def CreateWidgets(self):
#create all your widgets here
self.widgets = {}
def SetWidgetValue(self,value):
if isinstance(value,dict):
for k,v in value.items():
set_widget_value(self.widgets.get(k),v)
else:
raise Exception("Expected a dictionary but got %r"%value)
def GetWidgetValue(self):
return dict([(k,get_widget_value(v))for k,v in self.widgets])
and then use them like this https://gist.github.com/joranbeasley/37becd81ff2285fcc933
Related
I'm trying to port this IronPython WPF example to CPython with python.NET.
I'm able to get the window running after fixing some known issues (adding __namespace__ to the ViewModel class, using Single Thread Apartment and updating the syntax of pyevent.py to python3), but bindings are ignored: I can write in the textbox, but no events gets triggered, and the button doesn't fire the OnClick method.
Here's the full code:
import clr
clr.AddReference(r'wpf\PresentationFramework')
clr.AddReference('PresentationCore')
from System.Windows.Markup import XamlReader
from System.Windows import Application, Window
from System.ComponentModel import INotifyPropertyChanged, PropertyChangedEventArgs
import pyevent
from System.Threading import Thread, ThreadStart, ApartmentState
XAML_str = """<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Sync Paramanager" Height="180" Width="400">
<StackPanel x:Name="DataPanel" Orientation="Horizontal">
<Label Content="Size"/>
<Label Content="{Binding size}"/>
<TextBox x:Name="tbSize" Text="{Binding size, UpdateSourceTrigger=PropertyChanged}" />
<Button x:Name="Button" Content="Set Initial Value"></Button>
</StackPanel>
</Window>"""
class notify_property(property):
def __init__(self, getter):
def newgetter(slf):
#return None when the property does not exist yet
try:
return getter(slf)
except AttributeError:
return None
super().__init__(newgetter)
def setter(self, setter):
def newsetter(slf, newvalue):
# do not change value if the new value is the same
# trigger PropertyChanged event when value changes
oldvalue = self.fget(slf)
if oldvalue != newvalue:
setter(slf, newvalue)
slf.OnPropertyChanged(setter.__name__)
return property(
fget=self.fget,
fset=newsetter,
fdel=self.fdel,
doc=self.__doc__)
class NotifyPropertyChangedBase(INotifyPropertyChanged):
__namespace__ = "NotifyPropertyChangedBase"
PropertyChanged = None
def __init__(self):
self.PropertyChanged, self._propertyChangedCaller = pyevent.make_event()
def add_PropertyChanged(self, value):
self.PropertyChanged += value
def remove_PropertyChanged(self, value):
self.PropertyChanged -= value
def OnPropertyChanged(self, propertyName):
if self.PropertyChanged is not None:
self._propertyChangedCaller(self, PropertyChangedEventArgs(propertyName))
class ViewModel(NotifyPropertyChangedBase):
__namespace__ = "WpfViewModel"
def __init__(self):
super(ViewModel, self).__init__()
# must be string to two-way binding work correctly
self.size = '10'
#notify_property
def size(self):
return self._size
#size.setter
def size(self, value):
self._size = value
print(f'Size changed to {self.size}')
class TestWPF(object):
def __init__(self):
self._vm = ViewModel()
self.root = XamlReader.Parse(XAML_str)
self.DataPanel.DataContext = self._vm
self.Button.Click += self.OnClick
def OnClick(self, sender, event):
# must be string to two-way binding work correctly
self._vm.size = '10'
def __getattr__(self, name):
# provides easy access to XAML elements (e.g. self.Button)
return self.root.FindName(name)
def main():
tw = TestWPF()
app = Application()
app.Run(tw.root)
if __name__ == '__main__':
thread = Thread(ThreadStart(main))
thread.SetApartmentState(ApartmentState.STA)
thread.Start()
thread.Join()
It seems that assigning the ModelView to DataPanel's DataContext doesn't trigger any binding registration, but I have no clue on how to fix that.
Am I missing something obvious?
Unfortunately pythonnet does not support defining binding in the xaml file.
Sorry for such a noobish question, but, now i am writing a code that has some features i didn't know how to write by myself, so i copied them from stack overflow. I was a class i didnt study, yet i understood it mostly. The question is, how do i acess any of values created in it. Ex
class SimpleApp(object):
def __init__(self, master, filename, **kwargs):
self.master = master
self.filename = filename
self.canvrt = tk.Canvas(master, width=200, height=200, bg="#FF5733")
self.canvrt.pack()
self.update = self.draw().__next__
master.after(100, self.update)
def draw(self):
image = Image.open(self.filename)
angle = 0
while True :
tkimage = ImageTk.PhotoImage(image.rotate(angle))
canvas_obj = self.canvrt.create_image(
100, 100, image=tkimage)
self.master.after_idle(self.update)
yield
self.canvrt.delete(canvas_obj)
angle += 1
angle %= 360
how can i access the canvrt from the code? I need to acces this canvrt outisde of the class, so i can input it for example in a fucntion
You create an instance of the class SinpleApp:
myapp = SimpleApp(master, filename)
And then you can access any of its variables like this:
myapp.canvrt
However, notice that it is confusing to call filename the argument of your function clean if it expects a tkinter widget...
Depends on where you want to access it.
class NewClass():
def __init__(self,msg):
self.msg ="Hi"
def print_msg(self):
print("Message is : ", self.msg)
Inside any of the class functions/methods, you should make use of self, i.e,
self.msg
Outside of the class, use an object to access it, i.e,
obj = NewClass("hi")
obj.msg
//will print Hi
I have a Toplevel widget which asks the User what widget he want to spawn and then asks for cnf. Whatfor and stuff is not important.
The cnf will be asked in an Scrollframe-Table-something (I don't really know how to describe it ^^'). Extra for that Scrollframe and its Scrollbar I made a Frame, so I can easily pack it left and right. But somehow the Scrollframe is taking the Tk window (root of my toplevel) as master.
Here is the code - I can't find my mistake:
from tkinter import _cnfmerge as cnfmerge
from tkinter import *
class Scrollframe(Frame):
def __init__(self,master=None,height=200,width=200,**kw):
if 'yscrollcommand' in kw:
self.ysc=kw['yscrollcommand']
del kw['yscrollcommand']
else: ysc=None
if 'pad' in kw:
self.pad=kw['pad']
del kw['pad']
else: self.pad=0
Frame.__init__(self,height=height,width=width)
self.scrollframe=Frame(self,**kw)
self.scrollframe.place(x=0,y=0,relwidth=1)
self.config(bg=self.scrollframe['bg'])
self.bind('<Configure>',self.adopt)
self.widgets,self.scrollable={},False
def adopt(self,event=None):
if self.scrollframe.winfo_height()>self.winfo_height():
self.scrollable=True
self.scrollframe.place(y=0)
self.ysc(0,0)
else:
self.scrollable=False
self.ysc(0,1)
def addItem(self,widget=None,cnf={},**kw):
if widget:
cnf=cnfmerge((cnf,kw))
if 'width' in cnf: del cnf['width']
obj=widget(self.scrollframe,cnf)
if len(self.widgets)==0 and self.pad!=0: obj.pack(fill=X)
else: obj.pack(fill=X,pady=(self.pad,0))
id_=str(id(obj))+widget.__name__
obj.bind('<Destroy>',lambda event: self.delItem(id_),'+')
self.widgets[id_]=obj
return id_
def getItem(self,id):
return self.widgets[id]
def delItem(self,id):
try: self.widgets[id].destroy()
except TclError: del self.widgets[id]
except KeyError: pass
def yview(self,*args):
try: delta=int(args[1])
except ValueError: delta=float(args[1])
maxnegscroll=self.winfo_height()-self.scrollframe.winfo_height()
if isinstance(delta,float):
if maxnegscroll<0: self.scrollframe.place(y=int(maxnegscroll*delta))
delta=abs(int(self.scrollframe.place_info()['y'])/maxnegscroll)
self.ysc(delta,delta)
else:
delta=-delta*3
if int(self.scrollframe.place_info()['y'])+delta<maxnegscroll: self.scrollframe.place(y=maxnegscroll)
elif int(self.scrollframe.place_info()['y'])+delta>0: self.scrollframe.place(y=0)
else: self.scrollframe.place(y=int(self.scrollframe.place_info()['y'])+delta)
delta=abs(int(self.scrollframe.place_info()['y'])/maxnegscroll)
self.ysc(delta,delta)
class CreateWindow(Toplevel):
def __init__(self,master=None):
Toplevel.__init__(self,master,height=458,width=400)
self.grab_set()
self.resizable(False,False)
self.title('Neues Item')
self.vars,self.cnf,self.cnfids={},{},{}
cnf=create_dict(bg='gainsboro',width=380)
Frame(self,cnf=cnf,height=39).place(x=10,y=10)
Frame(self,cnf=cnf,height=103).place(x=10,y=59)
Frame(self,cnf=cnf,height=220).place(x=10,y=172)
bottom=Frame(self,cnf=cnf,height=46)
bottom.pack_propagate(False)
bottom.place(x=10,y=402)
var,values,self.oldwidget=StringVar(value='Frame'),list(_tkinter_widgets.keys())[2:],'Frame'
for i in range(len(values)): values[i]=values[i].__name__
Spinbox(self,values=values,textvar=var,state=READONLY,cursor='arrow',command=self.refresh,buttonuprelief=FLAT,buttondownrelief=FLAT,wrap=True).place(x=20,y=20)
self.vars['widget']=var
Label(self,text='Höhe:',bg='gainsboro',anchor=W,bd=1).place(x=20,y=69)
var=StringVar()
Entry(self,textvar=var,justify=CENTER,width=40).place(x=136,y=69)
self.vars['height']=var
Label(self,text='Breite:',bg='gainsboro',anchor=W,bd=1).place(x=20,y=98)
var=StringVar()
Entry(self,textvar=var,justify=CENTER,width=40).place(x=136,y=98)
self.vars['width']=var
var=BooleanVar(value=True)
Checkbutton(self,onvalue=True,offvalue=False,text='Farbe übernehmen (falls vorhanden)',variable=var,cursor='hand2',bg='gainsboro',activebackground='gainsboro').place(x=20,y=127)
self.vars['takecolor']=var
cnfsframe=Frame(self,height=200,width=360)
cnfsframe.pack_propagate(0)
cnfsframe.place(x=20,y=182)
sb=Scrollbar(cnfsframe)
sb.pack(fill=Y,side=RIGHT)
self.cnfs=Scrollframe(master=cnfsframe,width=360-17,height=200,yscrollcommand=sb.set)
self.cnfs.pack(fill=Y,side=LEFT)
sb.config(command=self.cnfs.yview)
for arg in _tkinter_widgets[Frame]:
id=self.cnfs.addItem(Frame,height=19,width=360)
obj=self.cnfs.getItem(id)
var=StringVar()
Entry(obj,width=35,justify=CENTER,textvar=var).place(x=146,y=0)
Label(obj,text=arg,bd=1).place(x=0,y=0)
self.cnf[arg],self.cnfids[arg]=var,id
Button(bottom,text='Bestätigen',command=self.confirm,width=12,height=1).pack(side=LEFT,padx=10,pady=10)
Button(bottom,text='Abbrechen',command=self.destroy,width=12,height=1).pack(side=RIGHT,padx=(0,10),pady=10)
def refresh(self):
self.vars['height'].set(''),self.vars['width'].set(''),self.vars['takecolor'].set(True)
for arg in _tkinter_widgets[eval(self.oldwidget)]:
self.cnfs.delItem(self.cnfids[arg])
del self.cnfids[arg],self.cnf[arg]
for arg in _tkinter_widgets[eval(self.vars['widget'].get())]:
id=self.cnfs.addItem(Frame,height=19,width=360)
obj=self.cnfs.getItem(id)
obj.pack_propagate(False)
var=StringVar()
Entry(obj,width=35,justify=CENTER,textvar=var).pack(side=RIGHT)
Label(obj,text=arg,bd=1).pack(fill=X,side=LEFT)
self.cnf[arg],self.cnfids[arg]=var,id
self.oldwidget=self.vars['widget'].get()
self.focus()
def confirm(self):
raise NotImplementedError #first I'll have to fix that scrollframe issue xD
if __name__=='__main__':
t=Tk()
cw=CreateWindow(t)
Before someone asks what self.scrollable in Scrollframe is for: Its for the MouseWheel binding I'll implement later.
In this line, you are not passing the master to the super-class' __init__:
Frame.__init__(self,height=height,width=width)
Just change it to:
Frame.__init__(self,master=master, height=height,width=width)
That said, it is a general Python recommendation to use super() instead of hardcoding the superclass name:
super().__init__(master=master, height=height, width=width)
Let's say I want a widget composed of an IntText widget and a DropDown widget which value is a concatened string of those widgets values. How can I do?
Here is an attempt:
import re
import ipywidgets as ipw
from IPython.display import display
class IntMultipliedDropdown:
_VALUE_PATTERN = re.compile('(?P<num>\d+) (?P<option>\w+-?\w*)')
def __init__(self, options, option_value, int_value=1):
self.number = ipw.IntText(int_value)
self.options = ipw.Dropdown(options=options, value=option_value)
self.box = ipw.HBox([self.number, self.options])
self.number.observe(self._on_changes, names='value')
self.options.observe(self._on_changes, names='value')
self._handelers = []
def _on_changes(self, change):
for handeler in self._handelers:
handeler(self.value)
#property
def value(self):
return "{} {}".format(self.number.value, self.options.value)
#value.setter
def value(self, value):
match = re.search(self._VALUE_PATTERN, value)
groupdict = match.groupdict()
self.number.value = groupdict['num']
self.options.value = groupdict['option']
def _ipython_display_(self, **kwargs):
return self.box._ipython_display_(**kwargs)
def observe(self, handler):
if handler not in self._handelers:
self._handelers.append(handler)
mywidget = IntMultipliedDropdown(['apple', 'bed', 'cell'], 'cell')
mywidget.observe(print)
display(mywidget)
print('default value:', mywidget.value)
mywidget.value = '2 bed'
It works but has drawbacks. First, when I set mywidget.value the observed function is called two times: on number value change and on option value change.
Second and worst is that I cannot use this widget in a Box widget like:
ipw.HBox([ipw.Label('Mylabel'), mywidget])
Which raises:
ValueError: Can't clean for JSON: <__main__.IntMultipliedDropdown object at 0x7f7d604fff28>
Is there a better solution?
The class you created is not a widget, although you did mimick some of the behaviors (observe, display). This is probably why you couldn't get it to display in an HBox. If you want to create a new widget, inherit from ipyw.Widget or any other widget.
You have two underlying widgets that are being listened to, so it is normal that two functions get called when you change their values. If you want only one function to be called, listen directly to the value of your new widget.
This is how you could do it, by inheriting from HBox:
import re
import ipywidgets as ipw
from traitlets import Unicode
from IPython.display import display
class IntMultipliedDropdown(ipw.HBox):
_VALUE_PATTERN = re.compile('(?P<num>\d+) (?P<option>\w+-?\w*)')
value = Unicode()
def __init__(self, options, option_value, int_value=1, **kwargs):
self.number = ipw.IntText(int_value)
self.options = ipw.Dropdown(options=options, value=option_value)
self._update_value()
self.number.observe(self._update_value, names='value')
self.options.observe(self._update_value, names='value')
self.observe(self._update_children, names='value')
super().__init__(children=[self.number, self.options], **kwargs)
def _update_children(self, *args):
match = re.search(self._VALUE_PATTERN, self.value)
groupdict = match.groupdict()
self.number.value = groupdict['num']
self.options.value = groupdict['option']
def _update_value(self, *args):
self.value = "{} {}".format(self.number.value, self.options.value)
mywidget = IntMultipliedDropdown(['apple', 'bed', 'cell'], 'cell')
display(mywidget)
There likely is a reason why you went to all the trouble of creating a new widget, but why not use the interactive function?
Something like:
import ipywidgets as ipw
from ipywidgets import *
w_number = ipw.IntText(value = 1)
w_options = ipw.Dropdown(options = ['apple', 'bed', 'cell'], value ='cell')
mywidget_value = ''
def display_value(number, options):
mywidget_value = str(number)+' '+options
#print(mywidget_value)
return mywidget_value
w_box = interactive(display_value, number=w_number, options=w_options)
display(w_box)
Then you have aBox, and you can adapt its layout. You can also access the keyword arguments with w_box.kwargs or the return value of the function with w_box.result, which is the concatenated string of the 2 widgets that you were looking for...
for some reason when I try to add an object to a dictionary in a class, where the dictionary belongs to another class and objects are added/removed by class functions it always seems to fail adding.
Heres the datahandler :
class datastore():
def __init__(self, dict=None):
self.objectStore = {}
self.stringStore = {}
if dict is not None:
self.objectStore = dict
def __addobj__(self,obj,name):
print("adddedval")
self.objectStore[name] = obj
def __getobject__(self,name):
_data = self.objectStore.get(name)
return _data
def __ripobj__(self,name,append):
if isinstance(append, object):
self.objectStore[name] = append
def __returnstore__(self):
return self.objectStore
def __lst__(self):
return self.objectStore.items()
and heres the trigger code to try to add the item :
if self.cmd=="addtkinstance-dev":
print("Adding a tk.Tk() instance to dataStore")
#$$ below broken $$#
_get = datastore.__dict__["__returnstore__"](self.dat)
_get["test-obj"] = tk.Tk()
datastore.__init__(self.dat, dict=_get)
#--------------------------------------------#
tool(tk.Tk(), "test-obj", datastore())
and also heres the init for the class that trys to add the object
class cmdproc(tk.Tk, datastore):
def __init__(self,lst,variable_mem,restable):
self.pinst = stutils(lst,restable,variable_mem)
self.vinst = varutils(variable_mem,lst,restable)
self.tki = tkhandler()
self.dat = datastore(dict=None)
datastore.__init__(self, dict=datastore.__returnstore__(self.dat))
tk.Tk.__init__(self)
self.lst = lst
self.vdat = variable_mem
self.restable = restable
please help this is seriously baffling me
(note that tkhandler dosn't have to do with anything)