wxPython gives different results on Windows and Ubuntu - windows

I have developed a small wxPython program that gives radically different output in Windows and Ubuntu (I am more than happy to be told I've programmed it incorrectly and I would regard that as a result- provided we can get it to work)
The program displays four shapes the right hand side. Double clicking on the right hand images moves them to the left hand side and vice versa.
Issues on Windows (see screen shots taken after the same actions in Windows and Ubuntu): The buttons don't render correctly; when I click on the cross, cicle or square they move correctly to the left of the screen, but multiple images of the triangle remain; an image appears in the top left corner
None of these issues appear in Ubuntu
import wx
class ImageSizer(wx.Frame):
def __init__(self, parent, title):
super(ImageSizer, self).__init__(parent, title=title,
size=(250, 200))
self.ShapeTypes=['available','selected']
self.MainSizer=wx.GridBagSizer()
self.SetSizer(self.MainSizer)
cmdNew=wx.Button(self, label='New')
cmdNew.Bind(wx.EVT_BUTTON, self.onNewClick)
cmdCancel=wx.Button(self, label='Cancel')
cmdCancel.Bind(wx.EVT_BUTTON, self.CancelClick)
self.MainSizer.Add((500,0), pos=(0,0), span=(1,2)) #dummy to position Available
self.MainSizer.Add((0,200), pos=(1,0), span=(1,1)) #dummy to position Buttons
self.MainSizer.Add(cmdNew, pos=(2,2), flag=wx.LEFT|wx.TOP, border=10)
self.MainSizer.Add(cmdCancel, pos=(2,3), flag=wx.RIGHT|wx.BOTTOM|wx.TOP|wx.ALIGN_RIGHT, border=10)
self.SetBackgroundColour((246, 244, 242))
self.Initialise()
self.Center()
self.Fit()
self.Show()
def DisplayImages(self):
availableSizer=ShapeSizer(self, self.AvailableShapes, self.ShapeTypes.index('available'))
self.RefreshSizerCell(self.MainSizer, availableSizer, (1,2), (1,2))
selectedSizer=ShapeSizer(self, self.SelectedShapes, self.ShapeTypes.index('selected'))
self.RefreshSizerCell(self.MainSizer, selectedSizer, (1,1), (1,1))
def Initialise(self):
self.AvailableShapes=['square','circle','triangle','cross']
self.SelectedShapes=[]
self.DisplayImages()
def RefreshSizerCell(self, sizer, item, pos, span, flag=wx.ALL, border=10):
self.Freeze()
oldItem=sizer.FindItemAtPosition(pos)
if (oldItem !=None) and oldItem.IsWindow():
oldItem.GetWindow().Destroy()
sizer.Add(item, pos=pos, span=span, flag=flag, border=border)
self.Layout()
self.Thaw()
def GetShapeName(self, event):
imgCtrl=event.GetEventObject()
return imgCtrl.GetName()
def onAvailableShapeDClick(self, event):
shape=self.GetShapeName(event)
self.AvailableShapes.remove(shape)
self.SelectedShapes.append(shape)
self.DisplayImages()
def onSelectedShapeDClick(self, event):
shape=self.GetShapeName(event)
self.SelectedShapes.remove(shape)
self.AvailableShapes.append(shape)
self.DisplayImages()
def onNewClick(self, event):
self.Initialise()
def CancelClick(self, event):
self.Destroy()
class ShapeSizer(wx.Panel):
def __init__(self, frame, shapes, shapeType):
wx.Panel.__init__(self, frame, id=wx.ID_ANY)
if shapeType==frame.ShapeTypes.index('available'):
size=40
action=frame.onAvailableShapeDClick
elif shapeType==frame.ShapeTypes.index('selected'):
size=80
action=frame.onSelectedShapeDClick
shapeSizer=wx.GridBagSizer()
shapes.sort()
for ii in range(0, len(shapes)):
bitmap=wx.Bitmap(shapes[ii]+'.png',wx.BITMAP_TYPE_PNG)
bitmap=self.ScaleBitmap(bitmap, size, size)
img=wx.StaticBitmap(self, wx.ID_ANY, bitmap, name=shapes[ii])
img.Bind(wx.EVT_LEFT_DCLICK, action)
shapeSizer.Add(img, pos=(0,ii), flag=wx.RIGHT, border=10)
self.SetSizer(shapeSizer)
def ScaleBitmap(self, bitmap, width, height):
image = wx.ImageFromBitmap(bitmap)
image = image.Scale(width, height, wx.IMAGE_QUALITY_HIGH)
result = wx.BitmapFromImage(image)
return result
if __name__ == '__main__':
app = wx.App()
ImageSizer(None, title='Image Sizer')
app.MainLoop()

The problem here is the lines
self.Freeze()
and
self.Thaw()
in
RefreshSizerCell()
Don't seem to be of any use in Windows :(
The line
Self.Layout()
also seems to be redundant

Related

How to get the size of the wxpython panel

I'm developing a calendar application
The top level window is a frame containing a panel that displays the calendar grid and a panel that contains a "Close" button.
I'm unable to obtain the size of the calendar grid panel.
When I add code to get the panel size, the result is (20,20), which cannot be correct
The screen size is (1920,1080) so I'm expecting something like (1920, 1000)
When I add the wx.lib.inspection module, I see the correct size being displayed. It is (1920, 968)
Can anyone shed some light how to get the correct size of the panel?
This is the code I have so far
import wx
class DrawFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, parent=None, title='Agenda', style= wx.CAPTION | wx.CLOSE_BOX)
self.drawpanel = DrawPanel(self)
self.buttonpanel = ButtonPanel(self)
self.framesizer = wx.BoxSizer(wx.VERTICAL)
self.framesizer.Add(self.drawpanel,1, flag=wx.EXPAND)
# Add an empty space 10 pixels high above and below the button panel
self.framesizer.Add((0,10),0)
self.framesizer.Add(self.buttonpanel,0, flag=wx.EXPAND)
self.framesizer.Add((0,10),0)
self.SetSizer(self.framesizer)
self.SetInitialSize()
self.Maximize()
self.Show()
def GetPanelSize(self):
return self.drawpanel.GetSize()
def OnClose(self, event):
self.Close()
class DrawPanel(wx.Panel):
# This panel's parent is DrawFrame. DrawFrame is the top level window.
def __init__(self, parent):
wx.Panel.__init__(self, parent=parent)
self.parent = parent
self.Bind(wx.EVT_PAINT, self.OnPaint)
self.x1, self.y1, self.x2, self.y2 = wx.GetClientDisplayRect()
b = self.x1, self.y1, self.x2, self.y2
print b
self.width, self.height = wx.GetDisplaySize()
c = self.width, self.height
print c
def OnPaint(self, event=None):
dc = wx.PaintDC(self)
dc.Clear()
dc.SetPen(wx.Pen(wx.BLACK, 2))
dc.SetBrush(wx.Brush('WHITE'))
"""
DrawRectangle (self, x, y, width, height)
Draw a rectangle with the given corner coordinate and size.
x and y specify the top left corner coordinates and both width and height are positive.
"""
dc.DrawRectangle(self.x1 + 5, self.y1, self.x2 - 10, self.y2 - 60)
dc.DrawLine(40, 100, 600, 100)
class ButtonPanel(wx.Panel):
# This panel's parent is DrawFrame. DrawFrame is the top level window.
def __init__(self, parent):
wx.Panel.__init__(self, parent=parent)
self.parent=parent
self.buttonpanelsizer = wx.BoxSizer(wx.HORIZONTAL)
self.closebutton = wx.Button(self, label = 'Close')
self.Bind(wx.EVT_BUTTON, self.OnClose, self.closebutton)
self.buttonpanelsizer.AddStretchSpacer(prop=1)
self.buttonpanelsizer.Add(self.closebutton, 0, wx.ALIGN_CENTER)
self.SetSizer(self.buttonpanelsizer)
def OnClose(self, event):
self.parent.OnClose(event)
app = wx.App(False)
frame = DrawFrame()
print frame.GetPanelSize()
app.MainLoop()
Much appreciated,
Thanks
You are calling the GetPanelSize too early. Keep in mind that wxPython (and pretty much any GUI framework) is event based. That means that for it to work it must keep processing events, which in case of wxPython means that app.MainLoop() must run. So do not call GetPanelSize before calling app.MainLoop(). Instead, call it when you need it. Do you need it when you paint something? Just use dc.GetSize(). Do you need it elsewhere? Process the wx.EVT_SIZE event and store the current size. Possibly you will have to trigger some action in the EVT_SIZE handler.

Multilpe screen in wxpython

all.
I'd like to be able to switch between multiple screens. Meaning, the first one is the main, then when with a button or an external switch is activated I can see the page #2, in that one I may have an other button to return to the first one, or going to #3, etc. Cause I have a main screen for a big RPM meter, but I may want to see instead all three meter on the same page, or view the raw data in an other page, or go to the set-up page or elsewhere in the future development. I'm using the full screen space for my graphic. Maybe something like "hide" or "show" a page with an event of some kind. I have a single class script for every pages so far, but unable to group them in a single one. Thanks for your help
I wrote about this concept several years ago here. I went ahead an reproduced the example from that article:
import wx
import wx.grid as gridlib
class PanelOne(wx.Panel):
""""""
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent=parent)
txt = wx.TextCtrl(self)
class PanelTwo(wx.Panel):
""""""
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent=parent)
grid = gridlib.Grid(self)
grid.CreateGrid(25,12)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(grid, 0, wx.EXPAND)
self.SetSizer(sizer)
class MyForm(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY,
"Panel Switcher Tutorial")
self.panel_one = PanelOne(self)
self.panel_two = PanelTwo(self)
self.panel_two.Hide()
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.sizer.Add(self.panel_one, 1, wx.EXPAND)
self.sizer.Add(self.panel_two, 1, wx.EXPAND)
self.SetSizer(self.sizer)
menubar = wx.MenuBar()
fileMenu = wx.Menu()
switch_panels_menu_item = fileMenu.Append(wx.ID_ANY,
"Switch Panels",
"Some text")
self.Bind(wx.EVT_MENU, self.onSwitchPanels,
switch_panels_menu_item)
menubar.Append(fileMenu, '&File')
self.SetMenuBar(menubar)
def onSwitchPanels(self, event):
""""""
if self.panel_one.IsShown():
self.SetTitle("Panel Two Showing")
self.panel_one.Hide()
self.panel_two.Show()
else:
self.SetTitle("Panel One Showing")
self.panel_one.Show()
self.panel_two.Hide()
self.Layout()
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm()
frame.Show()
app.MainLoop()
The basic idea here is to Hide() one panel and Show() another. You might also want to look at the Notebook controls that wxPython provides as they have a similar functionality.

How to set the gravity on a GTK3+ window in python

I run python 2.7.13 on windows 7.
I am creating a window with Gtk (from pygobject 3.18.2).
I am running windows 7 with a custom shell and I am trying to make a toolbar at the bottom of the screen.
I use a grid to divide the window in a top and a bottom part.
The bottom part is always visible.
The top part must show above the bottom part on mouse enter and hide on mouse leave without moving the bottom part.
The default positioning of a window uses the top-left corner of the window, but this will cause the bottom part to shift up to the position of the top part when the top part is hidden.
I think I understand that I have to use
set_gravity(Gdk.Gravity.SOUTH_WEST)
to change this behaviour
I do not get errors, but it seems this setting is ignored. The placement of the window is not affected at all.
What am I missing?
Anything wrong in the way I call set_gravity()?
Is set_gravity the right way to achieve this?
I read Set window gravity in PyGObject?, but this question is still not answered
Here is the code I try to get working
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk
class MyWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="Test")
self.set_decorated(0)
self.screen = Gdk.Screen.get_default()
self.connect("destroy", self.destroy)
self.connect("enter-notify-event", self.mouseenter)
self.connect("leave-notify-event", self.mouseleave)
self.label1 = Gtk.Label("Label1\n line1\n line2")
self.label2 = Gtk.Label("Label2")
self.label1.set_hexpand(True)
self.label2.set_hexpand(True)
self.maingrid = Gtk.Grid()
self.add(self.maingrid)
self.maingrid.attach(self.label1, 0, 0, 1, 1)
self.maingrid.attach(self.label2, 0, 1, 1, 1)
self.set_gravity(Gdk.Gravity.SOUTH_WEST) # looks like this is ignored
print self.get_gravity()
def mouseleave(self, widget, data=None):
print "mouse leave"
self.label1.hide()
label2_height = self.label2.get_allocation().height
self.resize(self.screen.width(), label2_height)
def mouseenter(self, widget, data=None):
print "mouse enter"
label1_height = self.label1.get_allocation().height
label2_height = self.label2.get_allocation().height
self.resize(self.screen.width(), label1_height + label2_height)
self.label1.show()
# Here I expect label2 to stay where it is at the bottom of the screen and label1 to be drawn above label2.
# But label2 is pushed down to make space for label1
# (normal behaviour if Gdk.Gravity.SOUTH_WEST is not set)
def destroy(self, widget, data=None):
print "destroy signal occurred"
Gtk.main_quit()
win = MyWindow()
win.show_all()
win.label1.hide()
height = win.label2.get_allocation().height
win.resize(win.screen.width(), height)
#win.move(0, win.screen.height()) # I expect this to place the window at the bottom of the screen
# if Gdk.Gravity.SOUTH_WEST is set, but it is placed offscreen
# (normal behaviour if Gdk.Gravity.SOUTH_WEST is not set)
win.move(0, win.screen.height() - 200) # shift it up 200 pixels to see what is happening
Gtk.main()
Here is a working version where I move the window to it's proper position after resizing. Moving the window makes the window flicker and it also generates the leave-notify-event and the enter-notify-event.
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk
class MyWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="Test")
self.set_decorated(0)
self.screen = Gdk.Screen.get_default()
# self.set_gravity(Gdk.Gravity.SOUTH_WEST)
self.connect("destroy", self.destroy)
self.connect("enter-notify-event", self.mouseenter)
self.connect("leave-notify-event", self.mouseleave)
self.label1 = Gtk.Label("Label1\n line1\n line2")
self.label2 = Gtk.Label("Label2")
self.label1.set_hexpand(True)
self.label2.set_hexpand(True)
self.maingrid = Gtk.Grid()
self.add(self.maingrid)
self.maingrid.attach(self.label1, 0, 0, 1, 1)
self.maingrid.attach(self.label2, 0, 1, 1, 1)
self.ismoving = 0
def mouseleave(self, widget, data=None):
print "mouse leave"
if self.ismoving:
print "window is moving"
else:
self.label1.hide()
label2_height = self.label2.get_allocation().height
self.resize(self.screen.width(), label2_height)
self.move(0, self.screen.height() - label2_height)
def mouseenter(self, widget, data=None):
print "mouse enter"
if self.ismoving: # moving the window generates a leave-notify-event and a enter-notify-event
self.ismoving = 0 # ignore these events when moving the window
else:
self.ismoving = 1
label1_height = self.label1.get_allocation().height
label2_height = self.label2.get_allocation().height
self.resize(self.screen.width(), label1_height + label2_height)
self.move(0, self.screen.height()-label1_height - label2_height)
self.label1.show()
def destroy(self, widget, data=None):
print "destroy signal occurred"
Gtk.main_quit()
win = MyWindow()
win.show_all()
win.label1.hide()
height = win.label2.get_allocation().height
win.resize(win.screen.width(), height)
win.move(0, win.screen.height() - height)
Gtk.main()
Based on AlexB's comment i assume my code is correct, but it is not working for me. I don't see any reason why it will not run under python 2. Maybe there is an issue with the window manager. I'll investigate
Did anyone succesfully use set_gravity() on windows?
Documentation indicates it may or may not work, depending on Window Manager. It doesn't for me on Xubuntu 18.04

Function to set image of buttons causes image to jump between buttons

I'm trying to teach myself the tkinter module by programming minesweeper. I have created a grid with buttons and a method to set an image flag to cells. It works, in that when you press the right mouse button the image of the button changes as desired, but when you right click on the next button the image just moves to the next button, rather than creating a second flag. I want to be able to place a new flag image on each cell that I right click, rather than just shuffle the image around. Here's my code:
import tkinter as Tk
def main():
root = Tk.Tk()
root.geometry('{}x{}'.format(700, 700))
instance = Minesweeper(root, 10, 10)
root.mainloop()
class Minesweeper:
def __init__(self, parent, height, width):
self.top_frame = Tk.Frame(parent)
self.top_frame.place(anchor=Tk.CENTER, relx=0.5, rely=0.5)
self.frames = []
self.buttons = []
index = 0
for x in range(height):
for y in range(width):
self.frames.append(Tk.Frame(self.top_frame, height=50, width=50))
self.buttons.append(Tk.Button(self.frames[index], bg="white"))
self.frames[index].grid_propagate(False)
self.frames[index].columnconfigure(0, weight=1)
self.frames[index].rowconfigure(0, weight=1)
self.frames[index].grid(row=x, column=y)
self.buttons[index].grid(sticky="wens")
self.buttons[index].bind('<Button-3>', self.flag)
index += 1
def flag(self, event):
self.flag = Tk.PhotoImage(file="flag.png")
event.widget.configure(image=self.flag)
if __name__ == "__main__":
main()
Seems that the below fixed it:
def flag(self, event):
self.flag = Tk.PhotoImage(file="flag.png")
event.widget.image = self.flag # <---- this seemed to fix it
event.widget.configure(image=self.flag)

How to smooth redrawing objects on the form (wxPython)

I'm writing simple GUI using wxPyhon and faced some problems.
My application does simple things: it draws triangle on the form and rotates it when user clicks arrow buttons or drags a mouse cursor over the form.
THe problems I see now are following:
1. Then I drag a mouse sursor fast the triangle rotates with keeping old image visible for a short time. When keeping moving a cursor fast for a while the drawing on the form is looking like 2 or 3 triangles.
2. If I expand the form to entire size of the screen the triangle moves unsmoothly, with small jumps from old appearance to a new one. I looked at coordinates of a mouse cursor during that rotating and noticed that they are tracked with gaps. Friend of mine said me that it is because I redraw the entire window of the application every time I wand to rotate the triangle a little bit. And that's why it works slowly and it slow down the tracking of a mouse cursor.
To refresh the view I'm using wx.Panel.Refresh() method. As drawing context I'm using wx.BufferedDC()
Please tell me how to draw CORRECTLY dynamicaly changing pictures/drawings on the wxPython forms, especially the way I make in that application.
I could place my code here, but it's too long. So if I must tell something more about my case - ask me please, I will answer.
Thanks !
class SimpleGraphics(wx.Panel):
def __init__(self, parent, size=(50, 50)):
super(SimpleGraphics, self).__init__(parent,
size=size,
style=wx.NO_BORDER)
self.color = "Black"
self.thickness = 2
self.pen = wx.Pen(self.color, self.thickness, wx.SOLID)
self.MARGIN = 1 #px
self.points = [[0.0, 0.5], [0.5, 0.0], [-0.5, -0.5]]
self.pos = (0, 0)
self.cur_vector = Vector2D(1, 1)
self.InitBuffer()
self.Bind(wx.EVT_SIZE, self.OnSize)
self.Bind(wx.EVT_IDLE, self.OnIdle)
self.Bind(wx.EVT_KEY_DOWN, self.OnKeyArrow)
# MOUSE TRACKING
self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
self.Bind(wx.EVT_MOTION, self.OnMotion)
self.Bind(wx.EVT_PAINT, self.OnPaint)
def InitBuffer(self):
self.client_size = self.GetClientSize()
self.buffer = wx.EmptyBitmap(self.client_size.width, self.client_size.height)
dc = wx.BufferedDC(None, self.buffer)
dc.SetBackground(wx.Brush(self.GetBackgroundColour()))
dc.Clear()
self.DrawImage(dc)
self.reInitBuffer = False
def OnSize(self, event):
self.reInitBuffer = True
def repaint_the_view(self):
self.InitBuffer()
self.Refresh()
def OnIdle(self, event):
if self.reInitBuffer:
self.repaint_the_view()
def OnKeyArrow(self, event):
key_code = event.GetKeyCode()
if key_code == wx.WXK_LEFT:
self.rotate_points(degrees_to_rad(5))
elif key_code == wx.WXK_RIGHT:
self.rotate_points(degrees_to_rad(-5))
self.repaint_the_view()
event.Skip()
def OnLeftDown(self, event):
# get the mouse position and capture the mouse
self.pos = event.GetPositionTuple()
self.cur_vector = create_vector2d(self.pos[0], self.pos[1],
self.client_size.width / 2,
self.client_size.height / 2)
self.CaptureMouse()
def OnLeftUp(self, event):
#release the mouse
if self.HasCapture():
self.ReleaseMouse()
def OnMotion(self, event):
if event.Dragging() and event.LeftIsDown():
newPos = event.GetPositionTuple()
new_vector = create_vector2d(newPos[0], newPos[1],
self.client_size.width / 2,
self.client_size.height / 2)
if new_vector.lenth() > 0.00001:
c = cos_a(self.cur_vector, new_vector)
s = sin_a(self.cur_vector, new_vector)
rot_matr = rotation_matrix(s, c)
self.rotate_points(rot_matr=rot_matr)
dc = wx.BufferedDC(wx.ClientDC(self), self.buffer) # this line I've added after posting the question
self.repaint_the_view()
self.cur_vector = new_vector
event.Skip()
def OnPaint(self, event):
wx.BufferedPaintDC(self, self.buffer)
def DrawImage(self, dc):
dc.SetPen(self.pen)
new_points = self.convetr_points_to_virtual()
dc.DrawPolygon([wx.Point(x, y) for (x, y) in new_points])
def to_x(self, X_Log):
X_Window = self.MARGIN + (1.0 / 2) * (X_Log + 1) * (self.client_size.width - 2 * self.MARGIN)
return int(X_Window)
def to_y(self, Y_Log):
Y_Window = self.MARGIN + (-1.0 / 2) * (Y_Log - 1) * (self.client_size.height - 2 * self.MARGIN)
return int(Y_Window)
def convetr_points_to_virtual(self):
return [(self.to_x(x), self.to_y(y)) for (x, y) in self.points]
def rotate_points(self, angle_in_degrees=None, rot_matr=None):
if angle_in_degrees is None:
self.points = [rotate_point(x, y , rotator_matrix=rot_matr) for (x, y) in self.points]
else:
self.points = [rotate_point(x, y , angle_in_degrees) for (x, y) in self.points]
class SimpleGraphicsFrame(wx.Frame):
def __init__(self, parent, *args, **kwargs):
wx.Frame.__init__(self, parent, *args, **kwargs)
# Attributes
self.panel = SimpleGraphics(self)
# Layout
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.panel, 1, wx.EXPAND)
self.SetSizer(sizer)
class SimpleGraphApp(wx.App):
def OnInit(self):
self.frame = SimpleGraphicsFrame(None,
title="Drawing Shapes",
size=(300, 400))
self.frame.Show()
return True
if __name__ == '__main__':
app = SimpleGraphApp(False)
app.MainLoop()
You call self.Refresh() from your OnKeyArrow and OnMotion events. Update your scene data in those methods and set some flag e.g. self.repaint_needed = True. Then in OnIdle repaint the scene if self.repaint_needed is True.
Now you try to repaint the window every time the event is received. Which may be a lot.
What you want to do is to update the scene information every time, but repaint the window only when wx indicates it has some "free time".

Resources