Search code examples
pythonpython-2.7wxpythonwxwidgets

wxPython custom progressBar problems


First of all I new to python so please do not judge me about mess in code :). I trying to make circular progress bar but I run into some problems and can't find what it causing. Problem is setRange(0, 100) or setMmaximum(100) not working if I setValue(100) I getting filled all circle. And second problem is my progress bar running backwards. Please can someone explain what I doing wrong?

enter image description here

Here what I have so far:

main.py file

import wx
from src.arc import TestArc


class bandom(wx.Frame):

    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(800, 600))

        self.gauge = TestArc(self)
        self.gauge.SetFocus()
        #self.gauge.setMinimun(0)
        #self.gauge.setMaximun(100)
        self.gauge.setRange(0, 100)
        self.gauge.setValue(10)

       # timer for testing progressbar
  #     self.timer = wx.Timer(self)
  #     self.Bind(wx.EVT_TIMER, self.OnTimer, self.timer)
  #     self.timer.Start(100)

  #     self.val = 0

 # def OnTimer(self, evt):
 #     self.val += 1
 #     self.gauge.setValue(self.val)
 #
 #     if self.val >= 100:
 #         self.val = 0
 #     #print(self.val)


class MyApp(wx.App):
    def OnInit(self):
       frame = bandom(None, -1, 'Window title here')
       frame.Show(True)
       self.SetTopWindow(frame)
       return True

    def main():
       app = MyApp(0)
       app.MainLoop()

if __name__ == '__main__':
    main()

and arc.py file

class TestArc(wx.Panel):

def __init__(self, *args, **kwargs):
    super(TestArc, self).__init__(*args, **kwargs)

    self.lineWidth = 0
    self.min = 0
    self.max = 100
    self._value = 0
    self.setText = '---'
    # self.font = someFont()
    self.position = wx.Rect()  # self.position.Set(x, y, width, height)
    self.startPoint = math.radians(0)
    self.endPoint = math.radians(0)

    self.Bind(wx.EVT_PAINT, self.OnPaint)

def setRange(self, min, max):
    self.min = min
    self.max = max

    if self.max < self.min:
        self.max, self.min = self.min, self.max

    if self._value < self.min:
        self._value = self.min
    elif self._value > self.max:
        self._value = self.max

    self.Refresh()

def setMinimun(self, min):
    self.setRange(min, self.max)

def setMaximun(self, max):
    self.setRange(self.min, max)

def setValue(self, val):
    if self._value != val:
        if val < self.min:
            self._value = self.min
        elif val > self.max:
            self._value = self.max
        else:
            self._value = val
        self.Refresh()


    self.Refresh()

def setLineWidth(self, lineWidth):
    self.lineWidth = lineWidth

def setPosition(self, x, y, width, height):
    self.position = wx.Rect(x, y, width, height)

def OnPaint(self, event=None):
    dc = wx.PaintDC(self)
    gc = self.MakeGC(dc)
    self.Draw(gc)

def MakeGC(self, dc):
    try:
        if False:
            gcr = wx.GraphicsRenderer.GetCairoRenderer
            gc = gcr() and gcr().CreateContext(dc)
            if gc is None:
                wx.MessageBox("Unable to create Cairo Context.", "Oops")
                gc = wx.GraphicsContext.Create(dc)
        else:
            gc = wx.GraphicsContext.Create(dc)

    except NotImplementedError:
        dc.DrawText("This build of wxPython does not support the wx.GraphicsContext "
                    "family of classes.",
                    25, 25)
        return None
    return gc

def Draw(self, gc):

    #middle progressbar line
    radStart = math.radians(90)
    radEnd = math.radians(0)
    path = gc.CreatePath()
    path.AddArc(80, 80, 50, radStart, radEnd, True)
    pen = wx.Pen('#000000', 4)
    pen.SetCap(wx.CAP_BUTT)
    gc.SetPen(pen)
    gc.SetBrush(wx.Brush('#000000', wx.TRANSPARENT))
    gc.DrawPath(path)

    #progress bar
    start = math.radians(90)
    #r = math.radians(270)
    arcStep = -270 / (self.max - self.min) * self._value
    end = math.radians(arcStep)
    path = gc.CreatePath()
    path.AddArc(80, 80, 50, start, end)
    pen = wx.Pen('#CC7F32', 15)
    pen.SetCap(wx.CAP_BUTT)
    gc.SetPen(pen)
    gc.SetBrush(wx.Brush('#000000', wx.TRANSPARENT))
    gc.DrawPath(path)

Solution

  • It's a bit gruesome because you have offset everything by 90º, so you have to account for that.
    Here goes nothing:

    import wx
    import math
    class TestArc(wx.Panel):
    
        def __init__(self, *args, **kwargs):
            super(TestArc, self).__init__(*args, **kwargs)
    
            self.lineWidth = 0
            self.min = -90
            self.max = 360
            self._value = 0
            self.setText = '---'
            # self.font = someFont()
            self.position = wx.Rect()  # self.position.Set(x, y, width, height)
            self.startPoint = math.radians(0)
            self.endPoint = math.radians(0)
            self.Bind(wx.EVT_PAINT, self.OnPaint)
    
        def setRange(self, min, max):
            self.min = min
            self.max = max
            if self._value < self.min:
                self._value = self.min
            elif self._value > self.max:
                self._value = self.max
            self.Refresh()
    
        def setMinimun(self, min):
            self.setRange(min, self.max)
    
        def setMaximun(self, max):
            self.setRange(self.min, max)
    
        def setValue(self, val):
            if self._value != val:
                if val < self.min:
                    self._value = self.min
                elif val > self.max:
                    self._value = self.max
                else:
                    self._value = val
            self.Refresh()
    
        def setLineWidth(self, lineWidth):
            self.lineWidth = lineWidth
    
        def setPosition(self, x, y, width, height):
            self.position = wx.Rect(x, y, width, height)
    
        def OnPaint(self, event=None):
            dc = wx.PaintDC(self)
            gc = self.MakeGC(dc)
            self.Draw(gc)
    
        def MakeGC(self, dc):
            try:
                if False:
                    gcr = wx.GraphicsRenderer.GetCairoRenderer
                    gc = gcr() and gcr().CreateContext(dc)
                    if gc is None:
                        wx.MessageBox("Unable to create Cairo Context.", "Oops")
                        gc = wx.GraphicsContext.Create(dc)
                else:
                    gc = wx.GraphicsContext.Create(dc)
            except NotImplementedError:
                dc.DrawText("This build of wxPython does not support the wx.GraphicsContext "
                            "family of classes.",
                            25, 25)
                return None
            return gc
    
        def Draw(self, gc):
            #middle progressbar line
            radStart = math.radians(90)
            radEnd = math.radians(0)
            path = gc.CreatePath()
            path.AddArc(80, 80, 50, radStart, radEnd, True)
            pen = wx.Pen('#000000', 4)
            pen.SetCap(wx.CAP_BUTT)
            gc.SetPen(pen)
            gc.SetBrush(wx.Brush('#000000', wx.TRANSPARENT))
            gc.DrawPath(path)
    
            #progress bar
            start = math.radians(90)
            #r = math.radians(270)
            arcStep = 270 / (self.max - self.min) * self._value
            end = math.radians(arcStep)
            path = gc.CreatePath()
            path.AddArc(80, 80, 50, start, end)
            pen = wx.Pen('#CC7F32', 15)
            pen.SetCap(wx.CAP_BUTT)
            gc.SetPen(pen)
            gc.SetBrush(wx.Brush('#000000', wx.TRANSPARENT))
            gc.DrawPath(path)
    
    class bandom(wx.Frame):
    
        def __init__(self, parent, id, title):
            wx.Frame.__init__(self, parent, id, title, size=(800, 600))
    
            self.gauge = TestArc(self)
            self.gauge.SetFocus()
            self.gauge.setMinimun(90)
            self.gauge.setMaximun(360)
            self.gauge.setValue(90)
    
           # timer for testing progressbar
            self.val = 90
            self.timer = wx.Timer(self)
            self.Bind(wx.EVT_TIMER, self.OnTimer, self.timer)
            self.timer.Start(100)
    
        def OnTimer(self, evt):
           self.val += 2.7
           self.gauge.setValue(self.val)
           if self.val >= 360:
               self.val = 90
    
    if __name__ == '__main__':
        app = wx.App()
        frame = bandom(None, -1, 'Window title here')
        frame.Show(True)
        app.MainLoop()
    

    I shoved it all into one file for my convenience.
    Edit: The reason for changing the maximum value to 360 and calculating the Step in the Arc with 270 is that you have chosen to use a 3/4 circle for your gauge and you are calculating the amount to increment the gauge using radians. You could have used degrees or radians but either way it's going to be calculated between 0 and 270 for degrees or 0 and 3π/2 for radians. Originally you said that you required a scale between 0 and 100, the easiest way to arrange that is to increment/decrement the gauge counter by 2.7 rather than 1 i.e. 270 points to travel / 100 (your required scale). It would be easier to start the gauge at point 0 rather than 90 but I assume that you are doing this either as an exercise or for aesthetic reasons.
    Your last comment regarding needing 240 increments, conflicts with the original question and other comments but can be achieved by incrementing the gauge counter by 1.125 (270/240) rather than either 1 or 2.7

    Edit 2:
    Running this code on my machine I get no "flicker". It could be down to the speed of your machine and may improve if you change the value that you pass to the wx.Timer, currently 100 milliseconds or 10 times a second. Remember that this code in essence pretends to show the progress of a task by using the timer, in reality you would be passing values into the gauge not based on a timer but on the progress of a actual process.