Storing and retrieving object in ray.io - actor

I have a ray cluster running on a machine as below:
ray start --head --redis-port=6379
I have two files that need to run on the cluster.
Producer p_ray.py:
import ray
ray.init(address='auto', redis_password='5241590000000000')
#ray.remote
class Counter(object):
def __init__(self):
self.n = 0
def increment(self):
self.n += 1
def read(self):
return self.n
counters = [Counter.remote() for i in range(4)]
[c.increment.remote() for c in counters]
futures = [c.read.remote() for c in counters]
print(futures, type(futures[0]))
obj_id = ray.put(ray.get(futures))
print(obj_id)
print(ray.get(obj_id))
while True:
pass
Consumer c_ray.py:
import ray
ray.init(address='auto', redis_password='5241590000000000')
[objs] = ray.objects()
print('OBJ-ID:', objs, 'TYPE:', type(objs))
print(ray.get([objs]))
My intention is to store the futures objects from producer and retrieve it in the consumer. I can retrieve the Object ID in the consumer. However the get in the consumer never returns.
What am I doing wrong?
How do I resolve my requirement?

This particular case can be a bug (I am not 100% sure). I created an issue at Ray Github.
But, this is not a good way to get object created by the p_ray.py. If you have many objects, it will be extremely complicated to manage. You can implement a similar thing using a detached actor. https://ray.readthedocs.io/en/latest/advanced.html#detached-actors.
The idea is to create a detached actor that can be retrieved by any driver/worker running in the same cluster.
p_ray.py
import ray
ray.init(address='auto', redis_password='5241590000000000')
#ray.remote
class DetachedQueue:
def __init__(self):
self.dict = {}
def put(key, value):
self.dict[key] = value
def get(self):
return self.dict
#ray.remote
class Counter(object):
def __init__(self):
self.n = 0
def increment(self):
self.n += 1
def read(self):
return self.n
queue = DetachedQueue.remote(name="queue_1", detached=True)
counters = [Counter.remote() for i in range(4)]
[c.increment.remote() for c in counters]
futures = [c.read.remote() for c in counters]
print(futures, type(futures[0]))
queue.put.remote("key", ray.get(futures)))
while True:
pass
c_ray.py:
import ray
ray.init(address='auto', redis_password='5241590000000000')
queue = ray.util.get_actor("queue_1")
print(ray.get(queue.get.remote()))

Related

Pool objects, keep reference to pool, make illegal states irrepresentable

I have a number of objects and I'd like to "pool" them, i.e., put them into lists or sets such that
every object appears in at most one list, and
every object knows which list it's in.
In Python, I could do
# create objects
pool1 = [obj1, obj5, obj6]
pool2 = [obj3]
pool3 = [obj8, obj7]
obj1.pool = pool1
obj2.pool = None
obj3.pool = pool2
obj4.pool = None
obj5.pool = pool1
obj6.pool = pool1
obj7.pool = pool3
obj8.pool = pool3
This works, but has the disadvantage that the data structure can represent illegal states, e.g.,
pool1 = [obj1]
pool2 = []
obj1.pool = pool2
or
pool1 = [obj1]
pool2 = [obj1]
obj1.pool = pool1
Is there a more fitting data structure for this?
I don't think there is a more fitting data structure for this, as you need the association to work in two directions (from object to list, from list to object).
The best is probably to encapsulate this logic in a class and require that the caller uses only the provided methods to manipulate the data structure.
In Python that could look like this:
class Node:
def __init__(self, name):
self.name = name
self.pool = None
def __repr__(self):
return self.name
class Pools():
def __init__(self):
self._pools = {}
def assign(self, poolid, obj):
if poolid not in self._pools:
self._pools[poolid] = set()
if obj.pool is not None:
self._pools[obj.pool].discard(obj)
if poolid is not None:
self._pools[poolid].add(obj)
obj.pool = poolid
def unassign(self, obj):
self.assign(None, obj)
def content(self, poolid):
return list(self._pools[poolid])
# demo
a = Node("a")
b = Node("b")
c = Node("c")
pools = Pools()
pools.assign(0, a)
pools.assign(0, b)
pools.assign(5, c)
pools.assign(3, a)
pools.assign(3, c)
pools.unassign(b)
print(pools.content(0)) # []
print(pools.content(3)) # ['a', 'c']
print(pools.content(5)) # []
print(a.pool) # 3
print(b.pool) # None
print(c.pool) # 3
You could improve on this and make Pools a subclass of dict, but you get the idea.

Urwid and Multiprocessing

i try to sequence some actions in urwid
I made a timer which run in background and communicate with the mainprocess
like this:
from multiprocessing import Process, Pipe
import time
import urwid
def show_or_exit(key):
if key in ('q', 'Q'):
raise urwid.ExitMainLoop()
class midiloop(urwid.Frame):
def __init__(self):
self.message = urwid.Text('Press Space', align='center')
self.filler = urwid.Filler(self.message, "middle")
super().__init__(urwid.Frame(self.filler))
def keypress(self, size, key):
if key == " ":
self.seq()
else:
return key
def timer(self,conn):
x = 0
while True:
if (conn.poll() == False):
pass
else:
z = conn.recv()
if (z == "kill"):
return()
conn.send(x)
x+=1
time.sleep(0.05)
def seq(self):
self.parent_conn, self.child_conn = Pipe()
self.p = Process(target=self.timer, args=(self.child_conn,))
self.p.start()
while True:
if (self.parent_conn.poll(None)):
self.y = self.parent_conn.recv()
self.message.set_text(str(self.y))
loop.draw_screen()
if ( self.y > 100 ):
self.parent_conn.send("kill")
self.message.set_text("Press Space")
return()
if __name__ == '__main__':
midiloop = midiloop()
loop = urwid.MainLoop(midiloop, unhandled_input=show_or_exit, handle_mouse=True)
loop.run()
The problem is i'm blocking urwid mainloop with while True:
So anyone can give me a solution to listen for key Q to quit the program before it reachs the end of the loop for example and more generally to interact with urwid and communicate with the subprocess
It seems to be rather complicated to combine multiprocessing and urwid.
Since you're using a timer and your class is called midiloop, I'm going to guess that maybe you want to implement a mini sequencer.
One possible way of implementing that is using an asyncio loop instead of urwid's MainLoop, and schedule events with the loop.call_later() function. I've implemented a simple drum machine with that approach in the past, using urwid for drawing the sequencer, asyncio for scheduling the play events and simpleaudio to play. You can see the code for that here: https://github.com/eliasdorneles/kickit
If you still want to implement communication with multiprocessing, I think your best bet is to use urwid.AsyncioEventLoop and the aiopipe helper for duplex communication.
It's not very minimal I'm afraid. However I did spend a day writing this Urwid frontend that starts, stops and communicates with a subprocess.
import os
import sys
from multiprocessing import Process, Pipe, Event
from collections import deque
import urwid
class suppress_stdout_stderr(object):
"""
Supresses the stdout and stderr by piping them to dev null...
The same place I send bad faith replies to my tweets
"""
def __enter__(self):
self.outnull_file = open(os.devnull, 'w')
self.errnull_file = open(os.devnull, 'w')
self.old_stdout_fileno_undup = sys.stdout.fileno()
self.old_stderr_fileno_undup = sys.stderr.fileno()
self.old_stdout_fileno = os.dup(sys.stdout.fileno())
self.old_stderr_fileno = os.dup(sys.stderr.fileno())
self.old_stdout = sys.stdout
self.old_stderr = sys.stderr
os.dup2(self.outnull_file.fileno(), self.old_stdout_fileno_undup)
os.dup2(self.errnull_file.fileno(), self.old_stderr_fileno_undup)
sys.stdout = self.outnull_file
sys.stderr = self.errnull_file
return self
def __exit__(self, *_):
sys.stdout = self.old_stdout
sys.stderr = self.old_stderr
os.dup2(self.old_stdout_fileno, self.old_stdout_fileno_undup)
os.dup2(self.old_stderr_fileno, self.old_stderr_fileno_undup)
os.close(self.old_stdout_fileno)
os.close(self.old_stderr_fileno)
self.outnull_file.close()
self.errnull_file.close()
def subprocess_main(transmit, stop_process):
with suppress_stdout_stderr():
import time
yup = ['yuuuup', 'yuuuuup', 'yeaup', 'yeoop']
nope = ['noooooooe', 'noooope', 'nope', 'nope']
mesg = 0
i = 0
while True:
i = i % len(yup)
if transmit.poll():
mesg = transmit.recv()
if mesg == 'Yup':
transmit.send(yup[i])
if mesg == 'Nope':
transmit.send(nope[i])
if stop_process.wait(0):
break
i += 1
time.sleep(2)
class SubProcess:
def __init__(self, main):
"""
Handles forking, stopping and communication with a subprocess
:param main: subprocess method to run method signature is
def main(transmit, stop_process):
transmit: is a multiprocess Pipe to send data to parent process
stop_process: is multiprocess Event to set when you want the process to exit
"""
self.main = main
self.recv, self.transmit = None, None
self.stop_process = None
self.proc = None
def fork(self):
"""
Forks and starts the subprocess
"""
self.recv, self.transmit = Pipe(duplex=True)
self.stop_process = Event()
self.proc = Process(target=self.main, args=(self.transmit, self.stop_process))
self.proc.start()
def write_pipe(self, item):
self.recv.send(item)
def read_pipe(self):
"""
Reads data sent by the process into a list and returns it
:return:
"""
item = []
if self.recv is not None:
try:
while self.recv.poll():
item += [self.recv.recv()]
except:
pass
return item
def stop(self):
"""
Sets the event to tell the process to exit.
note: this is co-operative multi-tasking, the process must respect the flag or this won't work!
"""
self.stop_process.set()
self.proc.join()
class UrwidFrontend:
def __init__(self, subprocess_main):
"""
Urwid frontend to control the subprocess and display it's output
"""
self.title = 'Urwid Frontend Demo'
self.choices = 'Start Subprocess|Quit'.split('|')
self.response = None
self.item = deque(maxlen=10)
self.event_loop = urwid.SelectEventLoop()
# start the heartbeat
self.event_loop.alarm(0, self.heartbeat)
self.main = urwid.Padding(self.main_menu(), left=2, right=2)
self.top = urwid.Overlay(self.main, urwid.SolidFill(u'\N{MEDIUM SHADE}'),
align='center', width=('relative', 60),
valign='middle', height=('relative', 60),
min_width=20, min_height=9)
self.loop = urwid.MainLoop(self.top, palette=[('reversed', 'standout', ''), ], event_loop=self.event_loop)
self.subprocess = SubProcess(subprocess_main)
def exit_program(self, button):
raise urwid.ExitMainLoop()
def main_menu(self):
body = [urwid.Text(self.title), urwid.Divider()]
for c in self.choices:
button = urwid.Button(c)
urwid.connect_signal(button, 'click', self.handle_button, c)
body.append(urwid.AttrMap(button, None, focus_map='reversed'))
return urwid.ListBox(urwid.SimpleFocusListWalker(body))
def subproc_menu(self):
self.response = urwid.Text('Waiting ...')
body = [self.response, urwid.Divider()]
choices = ['Yup', 'Nope', 'Stop Subprocess']
for c in choices:
button = urwid.Button(c)
urwid.connect_signal(button, 'click', self.handle_button, c)
body.append(urwid.AttrMap(button, None, focus_map='reversed'))
listbox = urwid.ListBox(urwid.SimpleFocusListWalker(body))
return listbox
def update_subproc_menu(self, text):
self.response.set_text(text)
def handle_button(self, button, choice):
if choice == 'Start Subprocess':
self.main.original_widget = self.subproc_menu()
self.subprocess.fork()
self.item = deque(maxlen=10)
if choice == 'Stop Subprocess':
self.subprocess.stop()
self.main.original_widget = self.main_menu()
if choice == 'Quit':
self.exit_program(button)
if choice == 'Yup':
self.subprocess.write_pipe('Yup')
if choice == 'Nope':
self.subprocess.write_pipe('Nope')
def heartbeat(self):
"""
heartbeat that runs 24 times per second
"""
# read from the process
self.item.append(self.subprocess.read_pipe())
# display it
if self.response is not None:
self.update_subproc_menu(['Subprocess started\n', f'{self.item}\n', ])
self.loop.draw_screen()
# set the next beat
self.event_loop.alarm(1 / 24, self.heartbeat)
def run(self):
self.loop.run()
if __name__ == "__main__":
app = UrwidFrontend(subprocess_main)
app.run()

How to upload the payload for a post request randomly in JMeter

Have the following scenario, in my JSON response there is a value called visit which can have either 1 or more than 1 element. So, if the visit have 1 element then the default payload should be sent next POST request or if the visit have more than 1 element then I'm fetching a random value and want to update the payload and sent to the next POST request.
Let me elaborate,
Condition 1 [Have 1 element in the visit section]
Condition 2 [Have more than 1 element in the visit section]
JSON Response for Condition 1
{"studyDTO":{"studyId":191,"studyCode":"test_ispptest2"},"sites":[{"studyId":191,"siteRecid":201,"siteId":"20000"}],"subjects":[{"studyId":191,"siteRecid":201,"subjectRecid":245,"subjectNumber":"20002"}],"states":null,"allVisits":true,"modalities":null,"examDates":null,"series":null,"transferType":null,"sftpLocations":[],"dicomLocations":[],"fileSystemLocations":[],"rawFileSystemLocations":[],"customFolder":null,"folderStructure":null,"customFile":null,"fileStructure":null,"includePS":null}
JSON Response for Condition 2
{"studyDTO":{"studyId":191,"studyCode":"test_ispptest2"},"sites":[{"studyId":191,"siteRecid":16521,"siteId":"11001"}],"subjects":[],"visits":[{"studyId":191,"visitSubmitName":"baseline","visitDisplayName":"Baseline","orderOfDisplay":10},{"studyId":191,"visitSubmitName":"cycle_1","visitDisplayName":"Cycle 1","orderOfDisplay":20}],"sftpLocations":[],"dicomLocations":[],"fileSystemLocations":[],"rawFileSystemLocations":[],"states":null,"modalities":null,"examDates":null,"series":null,"transferType":null,"customFolder":false,"customFile":false,"folderStructure":null,"fileStructure":null,"allSites":false,"allSubjects":true,"allVisits":false,"allStates":false,"allExamDates":false,"allModalities":false,"allSeries":false,"softEditOverride":false,"includePS":false,"includeSR":false,"includeRTStruct":false,"dicomTemplate":null,"errorMessage":null,"successMessage":null}
When I am in Condition 2, fetching random visitSubmitName,visitDisplayName,orderOfDisplay and updating in the payload.
Payload for condition 1:
{"studyDTO":{"studyId":191,"studyCode":"test_ispptest2"},"sites":[{"studyId":191,"siteRecid":201,"siteId":"20000"}],"subjects":[{"studyId":191,"siteRecid":201,"subjectRecid":245,"subjectNumber":"20002"}],"states":null,"allVisits":true,"modalities":null,"examDates":null,"series":null,"transferType":null,"sftpLocations":[],"dicomLocations":[],"fileSystemLocations":[],"rawFileSystemLocations":[],"customFolder":null,"folderStructure":null,"customFile":null,"fileStructure":null,"includePS":null}
Payload for condition 2:
{"studyDTO":{"studyId":191,"studyCode":"test_ispptest2"},"sites":[{"studyId":191,"siteRecid":16521,"siteId":"11001"}],"allSubjects":true,"states":null,"visits":[{"studyId":191,"visitSubmitName":"baseline","visitDisplayName":"Baseline","orderOfDisplay":10}],"modalities":null,"examDates":null,"series":null,"transferType":null,"sftpLocations":[],"dicomLocations":[],"fileSystemLocations":[],"rawFileSystemLocations":[],"customFolder":null,"folderStructure":null,"customFile":null,"fileStructure":null,"includePS":null}
Only change in the payload from condition 1 is,
Solution I have tried so far is created a JSR223 post processer with the following code:
import groovy.json.JsonSlurper
Random rnd = new Random();
def jsonString = prev.getResponseDataAsString();
def jsonConvert = new JsonSlurper();
def object = jsonConvert.parseText(jsonString);
def studyCode = object.studyDTO.studyCode;
def visitS = object.visits.size().toString();
def visitSize = visitS?.isInteger() ? visitS.toInteger() : null
def visitNUM = rnd.nextInt(visitSize);
//def defaultPayload = "{"studyDTO":{"studyId":${studyID},"studyCode":"${study_name}"},"sites":[{"studyId":${studyID},"siteRecid":${siteRecID},"siteId":"${siteID}"}],"subjects":[{"studyId":${studyID},"siteRecid":${siteRecID},"subjectRecid":${subjectRecID},"subjectNumber":"${subjectNumber}"}],"states":null,"allVisits":true,"modalities":null,"examDates":null,"series":null,"transferType":null,"sftpLocations":[],"dicomLocations":[],"fileSystemLocations":[],"rawFileSystemLocations":[],"customFolder":null,"folderStructure":null,"customFile":null,"fileStructure":null,"includePS":null}"
if (visitSize>1) {
def visitSubmitName = object.visits[visitNUM].visitSubmitName
def visitDisplayName = object.visits[visitNUM].visitDisplayName
def orderOfDisplay= object.visits[visitNUM].orderOfDisplay.toString();
//Change the payload with the three value
vars.put('payload',updatedPayload )
}
else {
vars.put('payload',defaultPayload )
}
And the next post sampler,
Error details:
Getting error with the defaultPayload declaration and need the logic for updating the payload.
Is the post sampler declared correctly?
Thanks, in advance
You either need to escape every quotation mark with a backslash
def defaultPayload = "def defaultPayload = '{\"studyDTO\":{\"studyId\":191,\"studyCode\":\"test_ispptest2\"},\"sites\":[{\"studyId\":191,\"siteRecid\":201,\"siteId\":\"20000\"}],\"subjects\":[{\"studyId\":191,\"siteRecid\":201,\"subjectRecid\":245,\"subjectNumber\":\"20002\"}],\"states\":null,\"allVisits\":true,\"modalities\":null,\"examDates\":null,\"series\":null,\"transferType\":null,\"sftpLocations\":[],\"dicomLocations\":[],\"fileSystemLocations\":[],\"rawFileSystemLocations\":[],\"customFolder\":null,\"folderStructure\":null,\"customFile\":null,\"fileStructure\":null,\"includePS\":null}'"
or use single quotation marks instead:
def defaultPayload = '{"studyDTO":{"studyId":191,"studyCode":"test_ispptest2"},"sites":[{"studyId":191,"siteRecid":201,"siteId":"20000"}],"subjects":[{"studyId":191,"siteRecid":201,"subjectRecid":245,"subjectNumber":"20002"}],"states":null,"allVisits":true,"modalities":null,"examDates":null,"series":null,"transferType":null,"sftpLocations":[],"dicomLocations":[],"fileSystemLocations":[],"rawFileSystemLocations":[],"customFolder":null,"folderStructure":null,"customFile":null,"fileStructure":null,"includePS":null}'
No, you either need to use just ${payload} or if you prefer coding go for __groovy() function like ${__groovy(vars.get('payload'),)}
More information:
Apache Groovy - Parsing and producing JSON
Apache Groovy: What Is Groovy Used For?
Guess Dmitri has solved your issue, now coming to the logic. Before that, if I checked your payload for the post request are different. Please try the below code in your JSR223 post-processer.
The json update could be done through json.builder
import groovy.json.JsonSlurper
import groovy.json.JsonBuilder
Random rnd = new Random();
def jsonString = prev.getResponseDataAsString();
def jsonConvert = new JsonSlurper();
def object = jsonConvert.parseText(jsonString);
def studyCode = object.studyDTO.studyCode;
def visitS = object.visits.size().toString();
def visitSize = visitS?.isInteger() ? visitS.toInteger() : null
def visitNUM = rnd.nextInt(visitSize);
def defaultPayload = '{"studyDTO":{"studyId":${studyID},"studyCode":"${study_name}"},"sites":[{"studyId":${studyID},"siteRecid":${siteRecID},"siteId":"${siteID}"}],"subjects":[{"studyId":${studyID},"siteRecid":${siteRecID},"subjectRecid":${subjectRecID},"subjectNumber":"${subjectNumber}"}],"states":null,"allVisits":true,"modalities":null,"examDates":null,"series":null,"transferType":null,"sftpLocations":[],"dicomLocations":[],"fileSystemLocations":[],"rawFileSystemLocations":[],"customFolder":null,"folderStructure":null,"customFile":null,"fileStructure":null,"includePS":null}';
def updatedPayLoad = "";
if (visitSize>1) {
def visitSubmitNameR = object.visits[visitNUM].visitSubmitName
def visitDisplayNameR = object.visits[visitNUM].visitDisplayName
def orderOfDisplayR = object.visits[visitNUM].orderOfDisplay.toString();
def newORDEROFDIS = orderOfDisplayR?.isInteger() ? orderOfDisplayR.toInteger() : null
updatedPayLoad = '{"studyDTO":{"studyId":${studyID},"studyCode":"${study_name}"},"sites":[{"studyId":${studyID},"siteRecid":${siteRecID},"siteId":"${siteID}"}],"allSubjects":true,"states":null,"visits":[{"studyId":${studyID},"visitSubmitName":"","visitDisplayName":"","orderOfDisplay":00}],"modalities":null,"examDates":null,"series":null,"transferType":null,"sftpLocations":[],"dicomLocations":[],"fileSystemLocations":[],"rawFileSystemLocations":[],"customFolder":null,"folderStructure":null,"customFile":null,"fileStructure":null,"includePS":null}'
def newUpdatedPayLoad = jsonConvert.parseText(updatedPayLoad);
def jsonBuilder = new JsonBuilder(newUpdatedPayLoad)
jsonBuilder.content.visits[0].visitSubmitName = visitSubmitNameR
jsonBuilder.content.visits[0].visitDisplayName = visitDisplayNameR
jsonBuilder.content.visits[0].orderOfDisplay = newORDEROFDIS
vars.put('finalPayLoad',jsonBuilder.toPrettyString())
}
else {
vars.put('finalPayLoad',defaultPayload)
}

Tkinter auto scrolling frame almost working. Apreciate input

I am trying to develop a scrollable frame in tkinter that can be used in the same way a normal frame can.
Thanks to many hint in this forum i develloped some code, that does exactly what it is supposed to, if i pack the scrollframe in the root window.
Unfortunately it fails if i use place or grid.
Here the code for the Frame
import tkinter as tk
class ScrollFrame(tk.Frame): #this frame will be placed on a canvas that is in a frame that goes on the parent window
class AutoScrollbar(tk.Scrollbar):
def set(self, *args):
if float(args[0])==0 and float(args[1])==1: self.grid_forget()
else:
if self.cget('orient')=="vertical": self.grid(row=0,column=1,sticky="ns")
else: self.grid(row=1,column=0,sticky="ew")
tk.Scrollbar.set(self, *args)
def __init__(self, root,*args,**args2):
self.outer_frame=tk.Frame(root,*args,**args2) #this is the frame that will be packed in th parent window
self.outer_frame.grid_columnconfigure(0, weight=1)
self.outer_frame.grid_rowconfigure(0, weight=2)
self.canvas = tk.Canvas(self.outer_frame, borderwidth=0, background="#ffffff")
tk.Frame.__init__(self, self.canvas,*args,**args2)
self.vscroll = ScrollFrame.AutoScrollbar(self.outer_frame, orient="vertical", command=self.canvas.yview)
self.hscroll = ScrollFrame.AutoScrollbar(self.outer_frame, orient="horizontal", command=self.canvas.xview)
self.canvas.configure(yscrollcommand=self.vscroll.set, xscrollcommand=self.hscroll.set)
self.canvas.create_window((0,0), window=self, anchor="nw")
self.canvas.grid(row=0,column=0,sticky="news")
self.hscroll.grid(row=1,column=0,sticky="ew")
self.vscroll.grid(row=0,column=1,sticky="ns")
self.bind("<Configure>", self.onFrameConfigure)
def onFrameConfigure(self, event): #Adapt the scroll region #does the resizing
self.canvas.config(scrollregion=self.canvas.bbox("all"))
self.canvas.config(width=event.width, height=event.height)
#convenience functions so the ScrollFrame can be treated like a normal frame
def destr_org(self):tk.Frame.destroy(self)
def destroy(self):
self.destroy=self.destr_org
self.outer_frame.destroy()
def pack(self,*args,**arg2):
self.outer_frame.pack(*args,**arg2)
def place(self,*args,**arg2):
self.outer_frame.place(*args,**arg2)
def grid(self,*args,**arg2):
self.outer_frame.grid(*args,**arg2)
def pack_forget(self,*args,**arg2):
self.outer_frame.pack_forget(*args,**arg2)
def place_forget(self,*args,**arg2):
self.outer_frame.place_forget(*args,**arg2)
def grid_forget(self,*args,**arg2):
self.outer_frame.grid_forget(*args,**arg2)
def config(self,*args,**arg2):
self.outer_frame.config(*args,**arg2)
tk.Frame.config(self,*args,**arg2)
def configure(self,*args,**arg2):
self.outer_frame.config(*args,**arg2)
tk.Frame.config(self,*args,**arg2)
here the code i used to test it. Just uncomment the f.place and f.grid lines to try them.
win=tk.Tk()
f=ScrollFrame(win)
for n in range(10):
o=tk.Button(f,text="-----------------------"+str(n)+"------------------------")
o.pack()
f.pack(expand=True, fill=tk.BOTH)
#f.place(x=0, y=0)
#f.grid(column=0,row=0)
Since i get no errors i am somewat lost and would be grateful for hints why it doesnt work.
I know there are packages with scrollable frames, but i really would like to get a frame without additional imports.
Its also tru that it is a little more complicated than necessary, but that is because I tried to design it in a way that it can be filled and placed exactly like a tk.Frame
Thanks a lot
Ok, I figured it out.
This works quite well for pack() and place() (didnt try to get grid working) (on Linux)
The key for place() is the binding to the resizing of the parent window, whereas for pack() the resizing of the inner frame (=self) seems to be important.
Even though the code of Novel (see comments above) is quite more elegant than mine, it sometimes gave me errors were this one worked :-)
class ScrollFrame(tk.Frame):
class AutoScrollbar(tk.Scrollbar):
def set(self, *args):
if float(args[0])==0 and float(args[1])==1: self.grid_forget()
else:
if self.cget('orient')=="vertical": self.grid(row=0,column=1,sticky="ns")
else: self.grid(row=1,column=0,sticky="ew")
tk.Scrollbar.set(self, *args)
def __init__(self, root,*args,**args2):
self.rootwin=root
self.dir=tk.BOTH
if "dir" in args2:
self.dir=args2["dir"]
del(args2["dir"])
self.outer_frame=tk.Frame(root,*args,**args2)
self.outer_frame.grid_columnconfigure(0, weight=1)
self.outer_frame.grid_rowconfigure(0, weight=2)
self.canvas = tk.Canvas(self.outer_frame, borderwidth=0, background="#ffffff")
tk.Frame.__init__(self, self.canvas,*args,**args2)
if self.dir==tk.Y or self.dir==tk.BOTH :
self.vscroll = ScrollFrame.AutoScrollbar(self.outer_frame, orient="vertical", command=self.canvas.yview)
self.canvas.configure(yscrollcommand=self.vscroll.set)
self.vscroll.grid(row=0,column=1,sticky="ns")
if self.dir==tk.X or self.dir==tk.BOTH :
self.hscroll = ScrollFrame.AutoScrollbar(self.outer_frame, orient="horizontal", command=self.canvas.xview)
self.canvas.configure(xscrollcommand=self.hscroll.set)
self.hscroll.grid(row=1,column=0,sticky="ew")
self.canvas.create_window((0,0), window=self, anchor="nw")
self.canvas.grid(row=0,column=0,sticky="news")
self.canvas.bind("<Enter>", self._bind_mouse)
self.canvas.bind("<Leave>", self._unbind_mouse)
def onFrameConfigure(self, event): #Adapt the scroll region
bb=self.canvas.bbox("all")
self.canvas.config(scrollregion=bb)
self.canvas.config(height=event.height,width=event.width)
def onRootConf(self, event):
bb=self.canvas.bbox("all")
self.canvas.config(scrollregion=bb)
w=bb[2]-bb[0]
h=bb[3]-bb[1]
rw=self.rootwin.winfo_width()-self.outer_frame.winfo_x()-20*int(self.vscroll.winfo_ismapped())
rh=self.rootwin.winfo_height()-self.outer_frame.winfo_y()-20*int(self.hscroll.winfo_ismapped())
if rh<h and (self.dir==tk.Y or self.dir==tk.BOTH):
h=rh
if rw<w and (self.dir==tk.X or self.dir==tk.BOTH):
w=rw
self.canvas.config(height=h,width=w)
def destr_org(self):tk.Frame.destroy(self)
def destroy(self):
self.destroy=self.destr_org
self.outer_frame.destroy()
def pack(self,*args,**arg2):
self.bind("<Configure>", self.onFrameConfigure)
self.outer_frame.pack(*args,**arg2)
def place(self,*args,**arg2):
self.outer_frame.place(*args,**arg2)
self.bind("<Configure>",self.onRootConf)
self.rootwin.bind("<Configure>",self.onRootConf)
def grid(self,*args,**arg2):
self.outer_frame.grid(*args,**arg2)
def pack_forget(self,*args,**arg2):
self.outer_frame.pack_forget(*args,**arg2)
def place_forget(self,*args,**arg2):
self.outer_frame.place_forget(*args,**arg2)
def grid_forget(self,*args,**arg2):
self.outer_frame.grid_forget(*args,**arg2)
def config(self,*args,**arg2):
self.outer_frame.config(*args,**arg2)
tk.Frame.config(self,*args,**arg2)
def configure(self,*args,**arg2):
self.outer_frame.config(*args,**arg2)
tk.Frame.config(self,*args,**arg2)
def winfo_ismapped(self):
return self.outer_frame.winfo_ismapped()
def _bind_mouse(self, event=None):
self.canvas.bind_all("<4>", self._on_mousewheel)
self.canvas.bind_all("<5>", self._on_mousewheel)
self.canvas.bind_all("<MouseWheel>", self._on_mousewheel)
def _unbind_mouse(self, event=None):
self.canvas.unbind_all("<4>")
self.canvas.unbind_all("<5>")
self.canvas.unbind_all("<MouseWheel>")
def _on_mousewheel(self, event):
if event.num == 4 or event.delta == 120: self.canvas.yview_scroll(-1, "units" )
elif event.num == 5 or event.delta == -120: self.canvas.yview_scroll(1, "units" )

Arrow key event handling in PyObjC

I am trying to make an app using PyObjC and am struggling to find how to record arrow key (left and right). I would like to be able to record every time the user presses the left and right arrow keys. I am using another example found online. Instead of the buttons used in previous example for increment and detriment, I would like to use the arrow keys on the key board. Been looking a while and thought I could get some help here. Thanks!
from Cocoa import *
from Foundation import NSObject
class TAC_UI_Controller(NSWindowController):
counterTextField = objc.IBOutlet()
def windowDidLoad(self):
NSWindowController.windowDidLoad(self)
# Start the counter
self.count = 0
#objc.IBAction
def increment_(self, sender):
self.count += 1
self.updateDisplay()
#objc.IBAction
def decrement_(self, sender):
self.count -= 1
self.updateDisplay()
def updateDisplay(self):
self.counterTextField.setStringValue_(self.count)
if __name__ == "__main__":
app = NSApplication.sharedApplication()
# Initiate the contrller with a XIB
viewController = test.alloc().initWithWindowNibName_("test")
# Show the window
viewController.showWindow_(viewController)
# Bring app to top
NSApp.activateIgnoringOtherApps_(True)
from PyObjCTools import AppHelper
AppHelper.runEventLoop()
Your NSView-derived class should implement a keyDown_ and / or keyUp_. You also need to have acceptsFirstResponder return True:
from AppKit import NSView
class MyView(NSView)
def keyDown_(self, event):
pass
def keyUp_(self, event):
pass
def acceptsFirstResponder(self):
return True
Here'a an example implementation from the PyObjC documentation you can use: https://pythonhosted.org/pyobjc/examples/Cocoa/AppKit/DragItemAround/index.html

Resources