I’m trying to make a custom slider and I’m unsure which class to subclass.
Should I subclass wx.Control, wx.Panel or wx.Slider?
In the wxpython documentation under wx.Control it says:
This is the base class for a control or “widget”.
A control is generally a small window which processes user input and/or displays one or more item of data.
So from this it seems that my custom slider class should subclass from wx.Control. I think that wx.Control is subclassing wx.Window because in the file core.pyi is a line
class Control(Window):
(Is the Control class implementation written in C because I’m not able to find the source code for it? so are all classes listed in .pyi files implemented in C?)
In the wxpython documentation under wx.Panel it says
A panel is a window on which controls are placed.
It is usually placed within a frame. Its main feature over its parent class wx.Window is code for handling child windows and TAB traversal, which is implemented natively if possible (e.g. in wxGTK) or by wxWidgets itself otherwise.
In the code for my slider I subclass wx.Panel. From the documentation mentioned above I get the impression that the intention is that I should subclass wx.Control instead. I don’t understand the implications of this. What behavior or functionality is changed when subclassing wx.Panel instead of wx.Control. Since TAB traversal is explicitly mentioned under wx.Panel I assume that there is going to be some difference there.
There is also the option of subclassing wx.Slider…
So my question is really what is the difference between subclassing wx.Panel, wx.Control or wx.Slider?
In what cases should I subclass from wx.Panel instead of wx.Control and vice verse?
The code for my custom slider is
import wx
RW = 300 # slider width
RM = 20 # slider margin
RH = 25 # slider height
CR = 5 # circle radius
BG_COLOR = '#212121'
FG_COLOR = '#838383'
class TickSlider(wx.Panel):
def __init__(self, parent, minvalue, maxvalue, minortickstep = 0, majortickstep = 0):
wx.Panel.__init__(self, parent, size=(RW + 2*RM, RH))
self.font = wx.Font(7, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, 'Courier 10 Pitch')
self.minvalue = minvalue
self.maxvalue = maxvalue
self.minortickstep = minortickstep
self.majortickstep = majortickstep
self.value = self.minvalue
self.Bind(wx.EVT_PAINT, self.OnPaint)
self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
self.Bind(wx.EVT_MOTION, self.OnMotion)
# helper
self.baseheight = RH-CR-2
self.selected = False
self.mouseOver = False
self.dragging = False
def OnPaint(self, e):
# double buffering
buffer = wx.Bitmap(RW + 2*RM, RH)
dc = wx.BufferedPaintDC(self, buffer)
dc.Clear()
brush = wx.Brush(BG_COLOR)
dc.SetBrush(brush)
dc.SetPen(wx.Pen(BG_COLOR, 1, style=wx.TRANSPARENT))
dc.DrawRectangle(0, 0, RW+2*RM , RH)
dc.SetFont(self.font)
dc.SetPen(wx.Pen('#535353', 2))
dc.DrawLine(RM, self.baseheight, RW+RM, self.baseheight)
dc.SetPen(wx.Pen(FG_COLOR, 1))
dc.SetTextForeground(FG_COLOR)
# draw major ticks
for tick in range(self.minvalue, self.maxvalue+1, self.majortickstep):
hpos = RW*(tick - self.minvalue)/(self.maxvalue - self.minvalue) + RM
dc.DrawLine(hpos, self.baseheight, hpos, self.baseheight-6)
w, h = dc.GetTextExtent(str(tick))
dc.DrawText(str(tick), hpos-w/2, self.baseheight-17)
for tick in range(self.minvalue, self.maxvalue+1, self.minortickstep):
hpos = RW*(tick - self.minvalue)/(self.maxvalue - self.minvalue) + RM
dc.DrawLine(hpos, self.baseheight, hpos, self.baseheight-4)
if self.selected:
brush = wx.Brush('#21c151')
dc.SetBrush(brush)
dc.SetPen(wx.Pen('#21c151', 2))
else:
if self.mouseOver:
dc.SetPen(wx.Pen('#B3B3B3', 2))
else:
dc.SetPen(wx.Pen('#838383', 2))
hpos = RW*(self.value - self.minvalue)/(self.maxvalue - self.minvalue) + RM
dc.DrawCircle(hpos, self.baseheight, CR)
def Clip(self, val):
if(val < self.minvalue):
val = self.minvalue
if(val > self.maxvalue):
val = self.maxvalue
return val
def PosToValue(self, pos):
val = ((self.maxvalue - self.minvalue)*(pos - RM)/RW) + self.minvalue
return val
def ValueToPos(self, val):
pos = RW*(val - self.minvalue)/(self.maxvalue - self.minvalue) + RM
return pos
def OnLeftDown(self, e):
xpos, ypos = e.GetPosition()
xslider = self.ValueToPos(self.value)
yslider = self.baseheight
d = ((xpos - xslider)**2 + (ypos - yslider)**2)**0.5
if d <= CR:
self.dragging = True
self.selected = True
else:
# check click on slider line
if (ypos <= self.baseheight + CR + 2) and (ypos >= self.baseheight - CR - 2):
self.value = self.PosToValue(xpos)
self.value = self.Clip(self.value)
self.dragging = True
self.selected = True
else:
self.selected = False
self.dragging = False
self.Refresh()
def OnMotion(self, e):
if self.dragging and e.LeftIsDown():
xpos, ypos = e.GetPosition()
val = ((self.maxvalue - self.minvalue)*(xpos - RM)/RW) + self.minvalue
self.value = self.Clip(val)
self.Refresh(eraseBackground=False)
else:
xpos, ypos = e.GetPosition()
xslider = RW*(self.value - self.minvalue)/(self.maxvalue - self.minvalue) + RM
yslider = self.baseheight
d = ((xpos - xslider)**2 + (ypos - yslider)**2)**0.5
if d <= CR:
mouseState = True
else:
mouseState = False
if self.mouseOver != mouseState:
self.mouseOver = mouseState
self.Refresh()
def OnLeftUp(self, e):
if self.HasCapture():
self.ReleaseMouse()
if self.dragging:
self.dragging = False
self.Refresh
class MyFrame(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, 'MyFrame', size=(1000, 600))
self.p = wx.Panel(self, wx.ID_ANY)
# create a sizer
boxSizer = wx.BoxSizer(wx.VERTICAL)
minvalue = 0
maxvalue = 500
minortickstep = 20
majortickstep = 100
# create custom slider
self.slider = TickSlider(self.p, minvalue, maxvalue, minortickstep, majortickstep)
boxSizer.Add(self.slider)
self.p.SetSizer(boxSizer)
self.Layout()
def main():
app = wx.App()
ex = MyFrame()
ex.Show()
app.MainLoop()
if __name__ == '__main__':
main()