So I'm trying to make some different virtual keyboard designs with the goal of using them in combination with head tracking. I wanted to explore if maybe a circular keyboard would be easier to use with head tracking compared to a standard layout.
The keyboard would look like the one in the picture, but I'm not sure how to make this kind of a layout using tkinter (this is what I used for the other keyboard but I'm not limited to this lib in any way). Any tips on how to make non square/rectangular UI?
Thanks in advance.
You can use the Canvas widget to draw the menu as a collection of arc items. One of the features of the canvas is that you can give items on the canvas a tag, and you can bind that tag to an event.
This isn't a complete solution, but it illustrates how it's possible to draw a circular menu that responds to clicks, as well as enter and leave events. In the following code we draw a circle as a sequence of eight arcs. Each arc is given a generic tag of "item" and a specific tag of "item-". These tags can then be used to bind events, and to get information about the item that was clicked on.
class PieMenu(tk.Canvas):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.tag_bind("item", "<ButtonRelease-1>", self._click)
self.tag_bind("item", "<Enter>", self._enter)
self.tag_bind("item", "<Leave>", self._leave)
for index, start in enumerate(range(0, 360, 45)):
self.create_arc(
(2, 2, 200, 200),
outline="black", fill="white",
start=start, extent=45, tags=(f"item-{index}", "item")
)
def _click(self, event):
item = event.widget.find_withtag("current")[0]
tags = event.widget.itemcget("current", "tags")
print(f"item: {item} tags: {tags}")
def _enter(self,event):
event.widget.itemconfigure("current", fill="lightgray")
def _leave(self,event):
event.widget.itemconfigure("current", fill="white")
You would need to add code to include the text of each item, and code to know what function to call when a particular part of the menu is clicked. The point of the example is to show the technique for drawing one simple menu that can respond to mouse events.
Related
I'm trying to create a simple drag-and-drop interface in Tkinter using the canvas. However, I've hit a snag:
The event binds 'enter' and 'leave' seem to only work for the TOP-most widget under the cursor. E.g., I wrote this bit of code to try and recognize when the cursor is over the 'board' space or not:
self.canvas.create_rectangle(50, 50, 1250, 750, fill = 'grey', tag = 'Board')
self.canvas.tag_bind('Board', "<Enter>", self.on_board)
self.canvas.tag_bind('Board', "<Leave>", self.off_board)
self.mouse_on_board = False
def on_board(self, event):
self.mouse_on_board = True
def off_board(self, event):
self.mouse_on_board = False
But when the cursor enters a draggable item on the board (e.g. a canvas.create_image() image drawn on top of the 'board' rectangle) this is treated as the cursor leaving the board widget. Whereas for my purpose, I want the code to recognize it is still over the board (just over another widget which is itself drawn over the board).
Is there a simple way in Tkinter to know whether the cursor is on top of a widget REGARDLESS of whether there are any other widgets stacked above or beneath it?
Thanks in advance for any answers!
(this is a repost from https://groups.google.com/forum/?utm_medium=email&utm_source=footer#!msg/kivy-users/0B5GYWkTIwE/-OQYp_ykCQAJ)
Very occasionally our kivy-based UI glitches in a very odd way: the system remains responsive and the displays of buttons and text work but it seems that all "drawn" items disappear, i.e. items from kivy.graphics.
Here is the main control screen in it's proper state:
and when it goes wrong it changes to:
All the items rendered with the kivy.graphics objects have gone!
This is using 1.9.1-1build3 (in Ubuntu) and 1.9.1-dev (from kivypie on an RPi)
Does anyone have a clue as to what might be causing this or how to track it down? It seems to happen on touch events, but it is so rare it's been impossible so far to find any commonality and there is no error report output to stdout/stderr or the .kivy/log/
Here's a section of code showing how elements are being drawn:
class LineWidget(GUIWidget):
"""A line widget. A simple sequence of points"""
def __init__(self, points, width, **kwargs):
GUIWidget.__init__(self, **kwargs)
self.points = points
with self.canvas:
self.line_color = theme.system.line.Color()
self.line = Line(width=width)
self.bind(size=self.update_rect, pos=self.update_rect)
def update_rect(self, instance, value):
"""Update the point positions."""
self.pos = instance.pos
if self.points:
self.line.points = self.get_points(self.points)
def set_color(self, color):
"""Change the color"""
self.line_color.rgb = color
and the blue/grey background is rendered:
with self.canvas.before:
if background_color is None:
background_color = theme.primary.black
self._background_color = Color(*background_color)
self._background_rectangle = Rectangle(size=self.size, pos=self.pos)
In my post to the kivy group, it has been suggested that maybe some race condition has happened resulting in the native kivy objects being rendered on top of mine and to try using canvas.after, but unless I can find a way to provoke the problem I'll never know if this has worked.
Is it possible that the render order (Z-index) has changed?
Is it possible there is some race condition?
The problem happens so rarely it has been impossible so far to
provoke the problem (100s of random clicks on widgets is the only
way) - is there some programmatic method to try and force the issue?
what debug could I put in to try and find out what might be
happening?
Thanks,
I have a menu which when the user moves their mouse off I want to disappear. The menu consists of a Frame packed with several Label/Button widgets. I can detect the user moving their mouse off a Label/Button with simply binding to the <Enter>/<Leave events for those. But binding to the same events for the frame never triggers - I guess because the widgets on top cover up the frame so the mouse never enters it?
Is their a way to make the events propagate down to the containing Frame?
window=tkinter.Tk()
menu_frm = tkinter.Frame(window, name="menu_frm")
lbl_1 = tkinter.Label(menu_frm, text="Label", name="lbl_1")
lbl_1.pack()
lbl_2 = tkinter.Label(menu_frm, text="Label", name="lbl_2")
lbl_2.pack()
menu_frm.pack()
# These two (per label and the number of labels is likely to grow) events fire
lbl_1.bind("<Enter>", lambda _: stayopenfunction())
lbl_1.bind("<Leave>", lambda _: closefunction())
lbl_2.bind("<Enter>", lambda _: stayopenfunction())
lbl_2.bind("<Leave>", lambda _: closefunction())
# These two events do not fire
menu_frm.bind("<Enter>", lambda _: stayopenfunction())
menu_frm.bind("<Leave>", lambda _: closefunction())
I found an easy fix for what your trying to do, I was trying to do the same!
def function2(event):
-revert event-
Widget.bind("<Enter>", function1)
def function1(event):
-hover event-
Widget.bind("<Leave>", function2)
Widget.bind("<Enter>", function1)
Its a bit stupid, its not the best, but it just means that when you hover it does what its meant to and then listens for the event that reverts it, in this case ''.
If you're just trying to simplify maintenance of the callbacks on the widgets, then I don't think binding events to the containing frame is the best approach. (I guess it's possible to generate virtual events in the callbacks on the buttons and labels, and bind the virtual event to the frame, but it seems rather complicated. Maybe that's just because I have no experience with virtual events.)
Why not just subclass the label and button widgets? Look at the "Instance and Class Bindings" heading at http://effbot.org/tkinterbook/tkinter-events-and-bindings.htm Then you'd just have two class binding to maintain, one for the labels and one for the buttons. This seems cleaner to me; it's just vanilla OOP.
Hope this helps.
I have several GUIs which open progressively that are all experiencing the same issue. When I open them, the first object which is selected is a TextCtrl object. These GUIs are rather large and have scroll bars. Since a TextCtrl object is selected, scrolling with the mouse wheel does nothing and makes it appear as if the scroll bars are broken. To demonstrate this, I made the following code:
import wx
class Tester(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, "Window", size=(500, 500))
self.panel = wx.ScrolledWindow(self,wx.ID_ANY)
self.panel.SetScrollbars(30,30,600,400)
textBox = wx.TextCtrl(panel, -1, "", size=(200, 150), style=wx.TE_MULTILINE|wx.TE_LEFT)
textStuff = wx.StaticText(panel, -1, "A\nbunch\nof\nlines\nto\nmake\nthis\nlong\nenough\nto\nhave\nscroll\nbars\n\n\n\n\n\n\n\n\n\nIts lonely down here\n\n\n\n:(")
lonelyBtn = wx.Button(panel, -1, "So Lonely")
vbox = wx.BoxSizer(wx.VERTICAL)
vbox.Add(textBox, flag=wx.EXPAND|wx.LEFT|wx.RIGHT|wx.TOP, border=10)
vbox.Add(textStuff, flag=wx.LEFT|wx.RIGHT|wx.TOP, border=10)
vbox.Add(lonelyBtn, flag=wx.LEFT|wx.RIGHT|wx.TOP, border=10)
panel.SetSizer(vbox)
panel.Layout()
app = wx.PySimpleApp()
Tester().Show()
app.MainLoop()
When you run this and try to scroll down, you'll notice you cannot scroll with the mouse wheel.
So far, this all makes sense. Here's where it gets a little weird. If the selected object is a Button, using the mouse wheel engages the scroll bars. You can test this by pressing the button, then using the mouse wheel. Also, clicking on the panel, or even the scroll bar itself, doesn't allow the mouse wheel to work.
What I'm looking for is a way to make sure that if there are scroll bars, they can be used with the mouse wheel upon displaying the GUI (they are selected by default). I can accept that the mouse wheel will not function once a user clicks into the text control.
Additionally, if you have an explanation for why the mouse wheel works for buttons and not text controls, I'd love to hear it
EDIT: I know I can add a listener (thanks to Mr. Joran Beasley), but this means that the scroll bars within a multi-line text control can never be used with the mouse wheel. The ideal solution (which I'm not sure is possible), is to have clicking on anywhere outside the text control (panel or scroll bar) allows the mouse wheel to scroll the panel. Is this possible?
Additionally, I've switched over to using ScrolledWindow instead of ScrolledPanel
EDIT 2: The fix was to use the following:
self.panel.Bind(wx.EVT_MOTION, self.onMouseMove)
def onMouseMove(self, event):
self.panel.SetFocusIgnoringChildren()
EDIT 3: The actual fix was to do something a little tricky. Using the code below I bound only multiline text controls to EVT_ENTER_WINDOW and EVT_LEAVE_WINDOW as well as binding every item (and the panel itself) to EVT_MOUSEWHEEL. Then a logical self.inMLTxtCtrl tracks if the mouse if over any of the multiline text controls
self.panel.Bind(wx.EVT_MOUSEWHEEL, self.onWheel)
for sizerItem in self.panel.GetSizer().GetChildren():
try:
if sizerItem.GetWindow().IsMultiLine():
sizerItem.GetWindow().Bind(wx.EVT_ENTER_WINDOW, self.onMouseEnter)
sizerItem.GetWindow().Bind(wx.EVT_LEAVE_WINDOW, self.onMouseLeave)
sizerItem.GetWindow().Bind(wx.EVT_MOUSEWHEEL, self.onWheel)
except:
sizerItem.GetWindow().Bind(wx.EVT_MOUSEWHEEL, self.onWheel)
Then a logical self.inMLTxtCtrl tracks if the mouse if over any of the multiline text controls as shown below.
def onMouseEnter(self, event):
print "entering"
self.inMLTxtCtrl = True
def onMouseLeave(self, event):
print "leaving"
self.inMLTxtCtrl = False
Finally, the onWheel() function uses this logical flag to determine where to scroll. If the mouse is in a multiline text control when the scroll wheel is turned, it attempts to scroll in that text control. Othewise, the SetFocusIgnoringChildren() function is called and the panel is scrolled. Since the panel and text control use different scrolling methods, a try...except is needed.
def onWheel(self, event):
if self.inMLTxtCtrl:
print "in", event.GetWheelRotation()
else:
print "out", event.GetWheelRotation()
self.panel.SetFocusIgnoringChildren()
try:
currScroll = self.panel.FindFocus().GetViewStart()
newScroll = (currScroll[0],currScroll[1]- event.GetWheelRotation()/60)
self.panel.FindFocus().Scroll(*newScroll)
except:
self.panel.FindFocus().ScrollLines(event.GetWheelRotation()/-60)
class Tester(wx.Frame):
def OnTxtScroll(self,e):
currScroll = self.panel.GetViewStart()
newScroll = (currScroll[0],currScroll[1]- e.GetWheelRotation()/120)
self.panel.Scroll(*newScroll)
def __init__(self):
....
#your code
....
self.panel = panel
textBox.Bind(wx.EVT_MOUSEWHEEL,self.OnTxtScroll)
after your clarification ... I think that this would work (its a bit hacky and doesnt do exactly what you describe ... but it might work)
def OnTxtScroll(self,e):
print dir(e)
target = e.GetEventObject()
p1 = target.GetScrollPos(wx.VERTICAL)
e.Skip()
self.Update()
def updateScroll(p1,target,scroll_amt):
p2 = target.GetScrollPos(wx.VERTICAL)
if p1 ==p2:#scroll did not effect target object so lets scroll our main panel
currScroll = self.panel.GetViewStart()
newScroll = (currScroll[0],currScroll[1]- scroll_amt)
self.panel.Scroll(*newScroll)
wx.CallAfter(updateScroll,p1,target,e.GetWheelRotation()/120)
OK, so iv almost completed my program for my project but I cant get a BUTTON_EVT to work which if i am honest should be the easiest thing todo. I have the buttons on my program which represent the hardware and I have created a def function for them to appear on the OGL canvas.
Problem has been solved... The code associated with the problem is found in the answer below
Edited from your last comment. Use this (using your own images):
def OnClickRouter(self, event):
image=wx.Image('cat.jpg', wx.BITMAP_TYPE_JPEG)
self.frame = bucky(None, image)
self.frame.Show()
If you call bucky() this way you must also fix the class signature:
class bucky(wx.Frame):
# Creating the outer window/frame
def __init__(self, parent, image=None):
wx.Frame.__init__(self, parent, -1,'Karls Network Tool', size=(900,700))
my_image = image if image else wx.Image("myself.bmp", wx.BITMAP_TYPE_BMP)
''''''''''''''''''''''''''''''''
# Button images
buttonOneRouter = my_image.ConvertToBitmap()
self.buttonOneRouter = wx.BitmapButton(panel, -1, buttonOneRouter, pos=(20,340))
self.buttonOneRouter.Bind(wx.EVT_BUTTON, self.OnClickRouter)
''''''''''''''''''''''''''''''''
Then you can see that after clicking the buttonOnerouter what actually you are doing is opening a new frame. The left figure is what I get when I run the program, the right one is after I click and enter again my name (I simplified a bit your code. Thats why you only see one button at the bottom instead of 4):
If you want to put my cat in the canvas instead of in the button there is still some work to do. I recommend to you to give a look at the wxPython demo. In the miscellaneous group of examples you have one called OGL that shows how to do that.
Edit: You can download the wxPython docs and demos package from here
I don't know if this is right or not but I suggest you to take this approach and see if it works or not.
Modify your frame class as:
def __init(self,parent,id,img=None)
def onClickRouter(self,event):
image=wx.Image('router.jpg', wx.BITMAP_TYPE_JPEG)
temp = image.ConvertToBitmap()
self.bmp = wx.StaticBitmap(parent=self, bitmap=temp)
self.frame=bucky(self.bmp)
Please let know the outcome.