Mouse tracking over canvas not working - PyQT - python

I want to enable mouse tracking over a figure (plot), however the mouse tracking is only working for the area NOT INCLUDING the figure/canvas.
The minimum code to show the issue:
class PlotWorstRegion(QtWidgets.QWidget):
def __init__(self, parent = MatplotlibWidget):
QtWidgets.QWidget.__init__(self, parent)
self.initUI()
def initUI(self):
self.canvas = FigureCanvas(Figure())
self.canvas.axes = self.canvas.figure.add_subplot(111)
grid = QtWidgets.QGridLayout()
x = 0
y = 0
self.text = "x: {0}, y: {1}".format(x, y)
self.label = QtWidgets.QLabel(self.text, self)
grid.addWidget(self.label,0,0)
grid.addWidget(self.canvas)
self.setGeometry(10, 240, 200, 300)
self.setWindowTitle('Event object')
self.setLayout(grid)
self.show()
self.setMouseTracking(True)
def mouseMoveEvent(self, e):
x = e.x()
y = e.y()
text = "x: {0}, y: {1}".format(x, y)
self.label.setText(text)
When I comment out grid.addWidget(self.canvas), this provides tracking in the grid, except when hovering over/around the label widget.
When I add self.label.setMouseTracking(True) in InitUI, the tracking is enabled for everywhere in the grid.
HOWEVER, when I uncomment grid.add_widget(self.canvas) AND add self.canvas.setMouseTracking(True), the tracking is 'blocked' by the canvas and the tracking is only enabled just outside the borders of the canvas.
Any help getting the mouse tracking to apply to the canvas as well would be greatly appreciated!

Related

wxPython Manual Scroll of panel ScrolledPanel failing with wx.Grid

I've created a panel thats derived from wx.lib.scrolledpanel. I could scroll on it fine with my mousewheel until I put a grid into the panel. Now when the mouse cursor is on top of the grid, the scroll stopped working, and would start working again if i moved the cursor outside of the grid.
I figured the easiest solution after searching and searching was to just manually capture the mousewheel event and scroll the panel manually. I bound this handler to wx.EVT_MOUSEWHEEL inside my wx.App object
class Wx_app(wx.App):
def __init__(self):
super().__init__(clearSigInt=True)
self.frame = MyFrame(None, pos=(0,0), size=(1900, 1100))
self.Bind(wx.EVT_MOUSEWHEEL, self.on_mouse_wheel)
def on_mouse_wheel(self, e):
# get the current scroll pos, is tuple with x as first val, y as second val
pos = self.frame.panel.CalcUnscrolledPosition(0, 0)
y_pos = pos[1]
# detemrine if user is scrolling up or down
if e.GetWheelRotation() > 0:
# user is scrolling up
print("UP")
self.frame.panel.Scroll(0, y_pos + 10)
else:
# user is scrolling down
print("DOWN")
self.frame.panel.Scroll(0, y_pos - 10)
This code works when i try to scroll down, but when i try to scroll back up with the the mousewheel nothing happens, even though "UP" registers in my terminal. Also i would think up should be y_pos - 10 and not y_pos + 10, but then the wheel scrolls in the opposite direction you would expect. What am I doing wrong? Perhaps i'm not getting the correct existing position in the first place, but CalcUnscrolledPosition is the only thing I could find that could maybe do that. I'm new to Python please explain it like I'm a 5 year old. thanks
Your immediate issue is that Up would be Y - 10 and Down would be Y + 10.
You need to go further up (less y) or further down (more y).
You also may well be comparing Apples with Oranges, when relying on the position as returned from the mouse event and how that relates to the position within the scrolled window.
The crux of this is that both the scrolledpanel and the grid are scrollable widgets.
Your issue seems to be that your grid is not of sufficent size to show the grid's scrollbars, thus causing confusion.
You can force the scrollbars on the grid and size it so that it is obvious that there are two sets of scrollbars and hopefully your users will work it out.
Here is a sample grid in a scrolled panel to play with (ignore the bound functions, they were there because initially I misunderstood your issue, thinking that you wanted manual scrolling within the grid)
import wx
import wx.lib.scrolledpanel
import wx.grid
class MyPanel(wx.Panel):
def __init__(self, *args, **kwargs):
wx.Panel.__init__(self, *args, **kwargs)
self.sizer = wx.BoxSizer(wx.HORIZONTAL)
self.fileFormPanel = FileFormPanel(self)
self.sizer.Add(self.fileFormPanel, 1, wx.EXPAND)
self.SetSizer(self.sizer)
class FileFormPanel(wx.lib.scrolledpanel.ScrolledPanel):
def __init__(self, parent):
wx.lib.scrolledpanel.ScrolledPanel.__init__(self, parent)
sizer = wx.BoxSizer(wx.VERTICAL)
# Create a wxGrid object
self.grid = wx.grid.Grid(self, -1, size=(300,480))
self.grid.CreateGrid(5, 10)
self.grid.SetRowLabelSize(1)
self.grid.SetDefaultColSize(120)
self.grid.SetColSize(0, 50)
self.grid.ShowScrollbars(True, True)
for j in range(5):
for k in range(10):
self.grid.SetCellValue(j, k, str(k))
text = wx.TextCtrl(self, wx.ID_ANY, value="Some text")
sizer.Add(self.grid)
sizer.Add(text)
self.SetSizer(sizer)
self.SetupScrolling()
#self.grid.Bind(wx.EVT_MOUSEWHEEL, self.OnGrid)
#self.Bind(wx.EVT_MOUSEWHEEL, self.OnScroll)
#self.grid.Bind(wx.grid.EVT_GRID_SELECT_CELL, self.OnSelect)
# def OnGrid(self, event):
# obj = event.GetEventObject()
# if event.WheelRotation > 0:
# self.grid.MoveCursorUp(False)
# else:
# self.grid.MoveCursorDown(False)
#
# def OnSelect(self, event):
# obj = event.GetEventObject()
# r = event.GetRow()
# c = event.GetCol()
# self.grid.MakeCellVisible(r,c)
# event.Skip()
#
# def OnScroll(self, event):
# print("window scroll")
# event.Skip()
class DemoFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, parent = None, size = (400, 400))
MyPanel(self)
class App(wx.App):
def OnInit(self):
self.frame = DemoFrame()
self.frame.Show()
self.SetTopWindow(self.frame)
return True
app = App()
app.MainLoop()

QWidget does not appear in the QMainWindow using PyQt5

What I want is an interface which opens windows under a SINGLE window, that is, I do NOT want the interface to open extra windows.
I will guide you to my error. When I run my code I get a home page, from there I click on View/Edit --> View/Edit Processed Slices. At this point this is what you should get in the MAIN WINDOW:
PICTURE 1
What I want the interface to do is to see the window in picture 2 whenever I click the blue rectangle. I want it to open the new widget IN THE SAME MAIN WINDOW
PICTURE 2
However, when I click it a new window opens and the previous window remains open (PICTURE 3). This is what I want to avoid, I want only 1 window not 2.
PICTURE 3
Here is the code:
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import cv2
import numpy as np
"""
MAIN WINDOW
"""
class CancerSegWindow(QMainWindow):
def __init__(self):
super().__init__()
self.title = 'Cancer Segmentation GUI '
self.initUI()
def initUI(self):
self.central = HOME()
self.setCentralWidget(self.central)
##
# ACTIONS
##
##File
#Exit
exitAct = QAction(QIcon('E:\BEATSON_PROJECT\python\GUI\exit.png'), 'Exit', self) # QAction is an abstraction for actions performed with a menubar, toolbar, or with a custom keyboard shortcut
exitAct.setShortcut('Ctrl+Q')
exitAct.setStatusTip('Exit application') # Status tip at the bottom
exitAct.triggered.connect(self.close) # Triggered signal is emitted. The signal is connected to the close() method of the QMainWindow widget.
#Home
HomeAct = QAction(QIcon('E:\BEATSON_PROJECT\python\GUI\home.png'), 'Home', self)
HomeAct.setStatusTip('Go Home')
HomeAct.triggered.connect(self.Home)
## View and Edit
# Slices
# Processed Slices
ProcessedAct = QAction('View / Edit Processed Slices', self)
ProcessedAct.triggered.connect(self.Processed_Slices)
self.statusBar().showMessage('Home') # First call statusBar creates a status bar at the bottom
# Subsequent calls return the statusbar object
##
# main MENU bar
##
menubar = self.menuBar() # create a menubar
# File
fileMenu = menubar.addMenu('File') # File (menu)
fileMenu.addAction(exitAct) # Exit
# View and Edit
veMenu = menubar.addMenu('View / Edit') # View and Edit (menu)
vesMenu = QMenu('View / Edit Slices',self) # Slices
vesMenu.addAction(ProcessedAct) # Processed
veMenu.addMenu(vesMenu)
##
# ICONS
##
toolbar = self.addToolBar('Exit')
toolbar.addAction(exitAct)
toolbarHome = self.addToolBar('Home')
toolbarHome.addAction(HomeAct)
##
# WINDOW
##
self.setGeometry(0, 30, 1366, 697)
self.setWindowTitle(self.title)
self.setWindowIcon(QIcon('E:\BEATSON_PROJECT\python\GUI\medicine.png'))
self.show()
def Home (self):
self.central = HOME()
self.setCentralWidget(self.central)
def Processed_Slices (self):
self.statusBar().showMessage('Displaying Processed Slices. Click on one Slice to View and Edit it individually')
self.central = ProcessedSlices()
self.setCentralWidget(self.central)
self.setWindowTitle(self.title + self.central.title)
def Pre_Edit_Processed (self, SliceNo=1):
self.statusBar().showMessage('Displaying Automatic Processed Slice' + str(SliceNo) + ' You can redraw it manually or modify the existing contour')
self.central = PreEditProcessed(SliceNo)
self.setCentralWidget(self.central)
self.setWindowTitle(self.title + self.central.title)
"""
HOME WINDOW
"""
class HOME (QWidget):
def __init__(self):
super().__init__()
#self.central = QPixmap("E:\BEATSON_PROJECT\python\GUI\Home_.png")
self.lbl1 = QLabel(self)
#self.lbl1.setPixmap(self.central)
"""
PROCESSED SLICES WINDOW
"""
class ProcessedSlices(QWidget):
def __init__(self):
super().__init__()
self.title = ('- Processed Slices')
self.initUI()
def initUI(self):
##
#CHECKBOXES
##
# Slice 1
#CheckBox
self.cb1 = QCheckBox('Slice 1', self)
self.cb1.move(1270, 115)
self.cb1.toggle()
self.cb1.stateChanged.connect(self.OpenSlice1)
#Pixmap (Image) 1
pixmap1 = QPixmap(310, 330) # Contour
pixmap1.fill(Qt.blue)
#pixmap1 = QPixmap("E:\BEATSON_PROJECT\python\GUI\Processed_Slice_1.png")
self.lbl1 = QLabel(self)
self.lbl1.setGeometry(QRect(QPoint(10,0),QPoint(310,330))) #
self.lbl1.setPixmap(pixmap1)
##
# SET GRID to obtain the mouse position
##
grid = QGridLayout()
self.text = "x: {0}, y: {1}".format(0, 0) # display the x and y coordinates of a mouse pointer in a label widget
self.label = QLabel(self.text, self) # x and y coordinates are displayd in a QLabel widget
grid.addWidget(self.label, 0, 1270, Qt.AlignTop)
self.setLayout(grid)
##
#WINDOW
##
#self.setGeometry(0, 25, 1365, 700)
#self.setWindowTitle('Processed Slices')
self.show()
def OpenSlice1(self, state):
self.lbl1.setVisible(state == Qt.Checked)
def mousePressEvent(self, e): # The e is the event object. it contains data about the event that was triggered
x = e.x() # in our case, a mouse CLICK
y = e.y() # x() and y() methods we determine the x and y coordinates of the mouse pointer
text = "None selected x: {0}, y: {1}"
if ( x >= 10 and x <= 310 and y >= 0 and y <= 330 and self.cb1.isChecked()):
text = "Slice 1 x: {0}, y: {1}".format(x, y)
self.close()
self.CSW = CancerSegWindow()
self.CSW.Pre_Edit_Processed(1)
self.label.setText(text)
"""
PROCESSED SLICES CHECK WINDOW
"""
class PreEditProcessed(QWidget):
def __init__(self, SliceNo=1):
super().__init__()
self.title = ('- Check Processed Slices')
self._SliceNo = SliceNo
self.initUI()
def initUI(self):
#self.draw = Draw(self)
#self.draw._windows = 1
# Button to clear both image and drawing
self.button = QPushButton('Discard and segment MANUALLY ', self)
#self.button.clicked.connect(self.editManually)
# Button to modify contour
self.BmodContour = QPushButton('Modify existing contour ', self)
#self.BmodContour.clicked.connect(self.modContour)
# Button to finish and compare
self.BFinish = QPushButton('Finish ', self)
#self.BFinish.clicked.connect(self.Finish)
# Arrange Layout
self.layout = QVBoxLayout(self)
#self.layout.addWidget(self.draw) # Show Slice
self.layout.addWidget(self.button) # Manually
self.layout.addWidget(self.BmodContour) # Modify contour
self.layout.addWidget(self.BFinish) # Finish and compare
self.setGeometry(0, 25, 1365, 700)
self.setWindowTitle('Check Slices')
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = CancerSegWindow()
sys.exit(app.exec_())
Note that the relevant part of the code is inside class ProcessedSlices:
def mousePressEvent(self, e):
x = e.x()
y = e.y()
text = "None selected x: {0}, y: {1}"
if ( x >= 10 and x <= 310 and y >= 0 and y <= 330 and self.cb1.isChecked()):
text = "Slice 1 x: {0}, y: {1}".format(x, y)
self.close()
self.CSW = CancerSegWindow()
self.CSW.Pre_Edit_Processed(1)
Your problem is that you are creating another instance of the class CancerSegWindow() in the function mousePressEvent(self, e).
The best way is use pyqtSignal().
You have to declare the pyqtSignal(int) in ProcessedSlices class:
class ProcessedSlices(QWidget):
#here declare signal
signal = pyqtSignal(int)
def __init__(self):
# more code....
And emit the signal in your mousePressEvent(self, e):
def mousePressEvent(self, e): # The e is the event object. it contains data about the event that was triggered
x = e.x() # in our case, a mouse CLICK
y = e.y() # x() and y() methods we determine the x and y coordinates of the mouse pointer
text = "None selected x: {0}, y: {1}"
if ( x >= 10 and x <= 310 and y >= 0 and y <= 330 and self.cb1.isChecked()):
text = "Slice 1 x: {0}, y: {1}".format(x, y)
self.close()
self.signal.emit(1) # emit signal with SliceNo=1
self.label.setText(text)
Finally, capture it in your Processed_Slices():
def Processed_Slices (self):
self.statusBar().showMessage('Displaying Processed Slices. Click on one Slice to View and Edit it individually')
self.central = ProcessedSlices()
self.central.signal.connect(self.Pre_Edit_Processed) #connect signal
self.setCentralWidget(self.central)
self.setWindowTitle(self.title + self.central.title)

Using wx.EVT_PAINT and wx.PaintDC

You use wx.EVT_PAINT and wx.PaintDC to draw shapes, so that when window is resized (redrawn) shapes will not be lost. This works when the window is created. But, how will I preserve the shapes that I create after window is created?
Below, I present you a code, when the app first starts, a rectangle is drawn on the window. When user double clicks somewhere on the window, another rectangle is created. The initial rectangle is always preserved because it is bind to wx.EVT_PAINT event, so that it will be redrawn every time the window is redrawn.
But the second rectangle is not associated to the wx.EVT_PAINT, therefore it is lost when window is redrawn. How do I preserve the second rectangle as well?
import wx
class MyPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent, -1)
self.Bind(wx.EVT_PAINT, self.OnPaint)
self.Bind(wx.EVT_LEFT_DCLICK, self.on_left_double_click)
def OnPaint(self, evt):
dc = wx.PaintDC(self)
dc.DrawRectangle(50, 60, 90, 40)
def on_left_double_click(self, evt):
x = evt.GetX()
y = evt.GetY()
dc = wx.ClientDC(self)
dc.SetBrush(wx.Brush("yellow"))
dc.DrawRectangle(x, y, 90, 40)
class MyForm(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Test",style=wx.DEFAULT_FRAME_STYLE,size=wx.Size(400, 300))
self.main_panel = MyPanel(self)
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm()
frame.Show()
app.MainLoop()
There is no universal solution to this except handling every drawing operation in a wx.PaintDC. You would do something along the lines of the following:
def __init__(self, parent):
# ...
self.show_yellow_box = False
self.box_pos = None
def OnPaint(self, evt):
dc = wx.PaintDC(self)
dc.DrawRectangle(50, 60, 90, 40)
if self.show_yellow_box:
x, y = self.box_pos
dc.SetBrush(wx.Brush("yellow"))
dc.DrawRectangle(x, y, 90, 40)
def on_left_double_click(self, evt):
x = evt.GetX()
y = evt.GetY()
self.box_pos = (x, y)
self.show_yellow_box = True
self.Refresh() # important, to trigger EVT_PAINT on panel
If the operations in the paint event are more expensive, you probably will end up collecting the expensive drawing operations on the DC in a wx.MemoryDC and blit the bitmap content back onto the panel in the MyPanel.OnPaint.
There is a temporary DC (wx.Overlay/wx.OverlayDC), which is however only useful to apply temporary changes between paint events.

wxPython Paint Damaged, Clipped area

I have the following simple code (click the pink box and you can move it around with your mouse while holding down the left mouse button).
import wx
class AppPanel(wx.Panel):
def __init__(self, parent, id):
wx.Panel.__init__(self, parent, id)
p = MovablePanel(self, -1)
self.i = 0
self.Bind(wx.EVT_PAINT, self.OnPaint, self)
def OnPaint(self, event):
dc = wx.PaintDC(self)
self.i = self.i+10
c = self.i % 255
c = (0, 0, c)
dc.SetPen(wx.Pen(c))
dc.SetBrush(wx.Brush(c))
dc.DrawRectangle(0, 0, 10000,10000)
class MovablePanel(wx.Panel):
def __init__(self, parent, id):
wx.Panel.__init__(self, parent, id)
self.SetMinSize((500,500))
self.SetSize((500,500))
self.SetBackgroundColour("PINK")
self.LEFT_DOWN = False
self.Bind(wx.EVT_MOTION, self.OnMove, self)
self.Bind(wx.EVT_LEFT_DOWN,
self.OnClickDown,
self)
self.Bind(wx.EVT_LEFT_UP,
self.OnClickUp,
self)
def OnClickUp(self, event):
self.LEFT_DOWN = False
self.Refresh()
def OnClickDown(self, event):
self.LEFT_DOWN = True
self.Refresh()
def OnMove(self, event):
if self.LEFT_DOWN:
p = self.GetTopLevelParent().ScreenToClient(wx.GetMousePosition())
self.SetPosition(p)
if __name__ == "__main__":
app = wx.App(False)
f = wx.Frame(None, -1, size = (700, 700))
p = AppPanel(f, -1)
f.Show()
f.Maximize()
app.MainLoop()
and it is suppose to look like the following (simply resize the frame)
However after moving the pink box around you will see it really looks like this
I have tried the following
dc.Clear()
dc.DestroyClippingRegion()
wx.FULL_REPAINT_ON_RESIZE
wx.EVT_ERASE_BACKGROUND
I'm pretty sure it has to do with it being a panel, and therefore the PaintEvent only marking it partially damaged. This part is colored differently making the 'ghosting' or 'smearing' obvious. Perhaps I'm using the wrong words because I was unable to find a solution (and I this seems to be a non complex issue simply having to do with the 'damaged' region).
Ok I found the problem, but I'll try to post more details later.
Basically the goal of this code is to move a panel around and then update the parent panel. SetPosition calls Move which going through the wxWidget code calls DoMoveWindow, all of this leads to a change in position and a repaint call (not sure what calls the repaint yet). Great. However the repaint only marks a certain 'area' as it tries to be efficient. That is why some of the issue can be solved by having the panel go over the 'ghosted' area. What you have to do is after the SetPosition, call GetParent().Refresh(), which will send a 'full' paint without any excluded area.
Another thing to note is there are TWO terms for this 'damaged' or 'clipped' area. One is 'damage' however there is another, 'dirty'. Damage is used in the wx PaintDC information
Using wx.PaintDC within EVT_PAINT handlers is important because it
automatically sets the clipping area to the damaged area of the
window. Attempts to draw outside this area do not appear.
Trusting the documentation you will be mostly lost. However in one of the wxPython DoubleBuffer how to's the lingo changes (but it is the same thing as 'damage')
Now the OnPaint() method. It's called whenever ther is a pain event
sent by the system: i.e. whenever part of the window gets dirty.
Knowing this if you Google wx Window dirty you will get the following
Mark the specified rectangle (or the whole window) as "dirty" so it
will be repainted. Causes an EVT_PAINT event to be generated and sent
to the window.
Take the following three print cycles where an EVT_PAINT was fired after a SetPosition call (this is WITHOUT the GetParent().Refresh() call)
# first EVT_PAINT
Drawing
Panel Size (1440, 851)
Clipping Rect (0, 0, 1440, 851)
Client Update Rect (x=0, y=6, w=500, h=501) # the only place getting update is
# directly below the panel
# (that is (500, 500) )
# second
Drawing
Panel Size (1440, 851)
Clipping Rect (0, 0, 1440, 851)
Client Update Rect (x=0, y=6, w=910, h=845) # however this time the update area is
# bigger, this is also right before
# the move
# i believe what it is doing is
# drawing from (0,6) to (910, 851)
# why? because the panel is moving to
# (410, 390) and the bottom right
# corner of the panel (after moved)
# is (410+500, 390+461) = (910, 851)
# or about where the edge of the panel
# will be
# third
Drawing
Panel Size (1440, 851)
Clipping Rect (0, 0, 1440, 851)
Client Update Rect (x=410, y=390, w=500, h=461)
Here is the update code to play around with, hopefully this will help others.
import wx
instructions = """
How to use.
1) Hover your mouse over the pink panel.
2) Click down (left click)
3) While holding down drag mouse around
4) Release mouse button to stop.
"""
class AppPanel(wx.Panel):
def __init__(self, parent, id):
wx.Panel.__init__(self, parent, id)
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.settings_sizer = wx.BoxSizer(wx.HORIZONTAL)
p = MovablePanel(self, -1)
self.c = wx.CheckBox(self, -1, label = "Ghosting On?")
self.p = p
self.i = 0
self.settings_sizer.Add(self.c)
self.sizer.Add(self.settings_sizer)
self.sizer.Add(self.p)
self.SetSizer(self.sizer)
self.Layout()
self.Bind(wx.EVT_CHECKBOX, self.OnCheck, self.c)
self.Bind(wx.EVT_PAINT, self.OnPaint, self)
self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnErase, self)
def OnCheck(self, event):
print "CHECK\n\n\n\n\n"
v = self.c.GetValue()
self.p.r = v
print v
def OnErase(self, event):
pass
def OnPaint(self, event):
print "Drawing"
dc = wx.PaintDC(self)
print "Panel Rect, ", self.p.GetPosition(),
print self.p.GetSize()
print "Clipping Rect", dc.GetClippingBox()
print "Client Update Rect", self.GetUpdateClientRect()
print "----------------------------"
self.i = self.i+10
c = self.i % 255
c = (0, 0, c)
dc.SetPen(wx.Pen(c))
dc.SetBrush(wx.Brush(c))
dc.DrawRectangle(0, 0, 10000,10000)
self.SetBackgroundColour(c)
dc.SetPen(wx.Pen("WHITE"))
dc.SetBrush(wx.Brush("WHITE"))
dc.DrawRectangle(0, 0, self.GetSize()[0], self.c.GetSize()[1])
class MovablePanel(wx.Panel):
def __init__(self, parent, id):
wx.Panel.__init__(self, parent, id)
self.SetMinSize((300,300))
self.SetSize((300,300))
txt = wx.StaticText(self, -1, label = "CLICK AND DRAG ME!")
inst = wx.StaticText(self, -1, label = instructions)
font = wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD)
txt.SetFont(font)
inst.SetFont(font)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(txt, flag = wx.ALIGN_CENTRE_VERTICAL | wx.ALIGN_CENTRE_HORIZONTAL)
sizer.Add(inst, flag = wx.ALIGN_CENTRE_VERTICAL | wx.ALIGN_CENTRE_HORIZONTAL)
self.SetSizer(sizer)
self.SetBackgroundColour("PINK")
self.LEFT_DOWN = False
self.r = False
self.Bind(wx.EVT_MOTION, self.OnMove, self)
self.Bind(wx.EVT_LEFT_DOWN,
self.OnClickDown,
self)
self.Bind(wx.EVT_LEFT_UP,
self.OnClickUp,
self)
def OnClickUp(self, event):
self.LEFT_DOWN = False
self.Refresh()
def OnClickDown(self, event):
self.LEFT_DOWN = True
self.Refresh()
def OnMove(self, event):
if self.LEFT_DOWN:
p = self.GetTopLevelParent().ScreenToClient(wx.GetMousePosition())
self.SetPosition(p)
if not self.r:
self.GetParent().Refresh()
if __name__ == "__main__":
app = wx.App(False)
f = wx.Frame(None, -1, size = (700, 700))
p = AppPanel(f, -1)
f.Show()
app.MainLoop()

Need help working with self.MemoryDC in wxPython

I'm trying to make a custom text widget that is double buffered (In order to avoid flicker).
However, I'd like to be able to do a few things. Yet, I'm unsure of the exact methods I should use.
The first two are easy I simply want to change the background and foreground color.
So more or less I want to be able to change the text color for self.Text in self.Draw().
Snippet:
self.Text = mdc.DrawText(self.TextString, 10, 0)
As sell as the Background (fill) color for self.MemoryDC.
Next, does anyone know how I could center self.Text? Finally, how do I configure self.Text after it has been created?
The widget thus far:
class DynamicText (wx.Panel):
def __init__(self, par):
self.Par = par
wx.Panel.__init__(self, self.Par)
self.Time = Time(self, func=self.SetTime)
self.Dim = self.Par.GetClientSize()
self.SetSize(self.Dim)
self.Bind(wx.EVT_SIZE, self.Resize)
self.Bind(wx.EVT_ERASE_BACKGROUND, self.Erase)
self.Bind(wx.EVT_PAINT, self.Paint)
def Set (self, text) :
self.TextString = text
def SetTime (self, time) :
self.Set(str(time))
self.Resize(None)
def Resize(self, event):
self.Width, self.Height = self.GetSize()
bitmap = wx.EmptyBitmap(self.Width, self.Height)
self.MemoryDC = wx.MemoryDC(bitmap)
''' Redraws **self.MemoryDC** '''
mdc = self.MemoryDC
''' Deletes everything from widget. '''
mdc.Clear()
fs = 11
font = wx.Font( fs, wx.DEFAULT, wx.NORMAL, wx.NORMAL)
mdc.SetFont(font)
self.Draw()
self.Refresh()
def Draw (self) :
mdc = self.MemoryDC
self.Text = mdc.DrawText(self.TextString, 10, 0)
def Erase(self, event):
''' Does nothing, as to avoid flicker. '''
pass
def Paint(self, event):
pdc = wx.PaintDC(self)
w, h = self.MemoryDC.GetSize()
pdc.Blit(0, 0, w, h, self.MemoryDC, 0, 0)
I don't understand what you mean by configuring self.Text after it was created. If you want to change the text after you've drawn it - you can't. Once you've drawn it to the DC it's there, and the only way to change it would be to clear the DC and repaint it. In your case, it seems all you need to do when the text is updated is to call Resize() again, forcing a redraw. Note that DrawText() retruns nothing, so the value of your self.Text would be None. You definitely can't use that to refer to the drawn text. :D
As for the rest, here's an example of a Draw() method that centers the text and paints it blue:
def Draw(self) :
mdc = self.MemoryDC
dc_width, dc_height = mdc.GetSizeTuple()
text_width, text_height, descent, externalLeading = mdc.GetFullTextExtent(self.TextString)
x = (dc_width - text_width) / 2
y = (dc_height - text_height) / 2
mdc.SetTextForeground('Blue')
mdc.DrawText(self.TextString, x, y)

Categories