Remove created objects

I will try to explain this as clearly as possible. I am creating my own simple chart ( I don’t have $300.00 for the plugin for personal use ) for an application I have written for my own use. The premise is simple, I have a canvas set up to display rectangles sized and placed based on input values. ( see screen shot for idea of what I am doing ).

The Window is set up so I can select a year I want to look at and a topic ( topic in this case would be a bill ). It works great for the first go, but when I change the topic the first result remains. So how do I remove the rectangles I have created in the first go so the second set will build without displaying the first set too? I have been digging around for a while now and can’t seem to find an answer that works.

Below is the code I have in the Canvas.Paint area. This is called by the ComboBox.Expenses ( aka Topic )

// ----------- Variable Declaration Center ---------------
// A = Left
// B = Top
// C = Width
// D = Height
Var i, A, B, C, D As Integer = 0
Var strArryExpense( ) As String // Used to set the rectangle height...
Var dblArryExp( ) As Double // Stores the actual dollar value of the item...
Var strArryDate( ) As String // Stores the date of the values...
Var strArryRnum( ) As String // Store the rNum of the values...
Var rsData As RowSet = modSelect.mthSel_Expenses( "Sorted", "" )
Var strPaid, strDate As String
// ---------- Where the magic happens! ------------------
A = 20 ' Starting point...
B = 666 ' Bottom of the canvas...
C = 10 ' Width of the item...
D = 100 ' Height of the item...

g.ClearRect(0, 0, g.Width, g.Height)

Try
  If( rsData <> Nil ) Then // So long as there is a record return it then close the rowset...
    // Build array with bill names...
    strDate = rsData.Column( "transDate" ).StringValue
    strDate = strDate.Left( 4 )
    For Each row As DatabaseRow In rsData
      If( strDate = cboYear.Text ) Then
        If( rsData.Column( "billPmntSrc" ).StringValue = cboExpense.Text ) Then
          strPaid = modSystem.mthCash_UNFormat( rsData.Column( "withdraw" ).StringValue )
          
          B = B + strPaid.ToInteger ' Subtract the value from the base top to get the actual height...
          strArryExpense.Add B.ToString
          B = 666
          
          If( strPaid.Left( 1 ) = "-" ) Then
            Var iLen As Integer = strPaid.Length - 1
            strPaid = strPaid.Right( iLen )
            dblArryExp.Add strPaid.ToDouble
          Else
            dblArryExp.Add strPaid.ToDouble
          End If
          
          strArryRnum.Add rsData.Column( "rNum" ).StringValue
          strArryDate.Add rsData.Column( "transDate" ).StringValue
          
          i = i + 1
        End If
      End If
      'strCompare = rsExpenses.Column( "billPmntSrc" ).StringValue
      strDate = rsData.Column( "transDate" ).StringValue
      strDate = strDate.Left( 4 )
    Next
  End If
  
  Var iHigh, iLow, iAvg, iTest, iAdd As Double
  Var strHighRnum, strAvgRnum, strLowRnum, strHighDate, strAvgDate, strLowDate As String
  iLow = 1000
  
  For X As Integer = strArryExpense.FirstIndex To strArryExpense.LastIndex
    'Var nTxtFld1 As DesktopTextField = New txtSpent
    'Var nTxtFld2 As DesktopTextField = New txtPercentage
    'Var nLabel As DesktopLabel = New lblAcctName
    Var nRect As DesktopRectangle = New rctOne
    'Var curTotExp As Currency
    iTest = dblArryExp( X )
    
    ' Get the max value...
    If( iTest > iHigh ) Then
      iHigh = iTest
      strHighDate = strArryDate( X )
      strHighRnum = strArryRnum( X )
    End If
    
    ' Get the low value
    If( iTest < iLow ) Then
      iLow = iTest
      strLowDate = strArryDate( X )
      strLowRnum = strArryRnum( X )
    End If
    
    ' Average them all...
    iAdd = iAdd + iTest
    iAvg = iAdd / i
    nRect.Visible = True
    D = strArryExpense( X ).ToInteger
    nRect.Top = D
    nRect.Left = A
    nRect.Width = C
    nRect.Height = 666
    A = A + 10
  Next
  
  txtMax.Text = modSystem.mthCash_Format( iHigh.ToString )
  txtAvg.Text = modSystem.mthCash_Format( iAvg.ToString )
  txtMin.Text = modSystem.mthCash_Format( iLow.ToString )
  txtMaxDate.Text = strHighDate.ToText
  txtMinDate.Text = strLowDate.ToText
  txtMaxRnum.Text = strHighRnum.ToText
  txtMinRnum.Text = strLowRnum.ToText
  
  'MessageBox( "There were " + i.ToString + " grocery records processed for 2022" )
  'MessageBox( "The high value was $" + iHigh.ToString + ". The low value was $" + iLow.ToString + ". And the average was $" + iAvg.ToString + "." )
  
Catch error As DatabaseException
  MessageBox( "Error: " + error.Message )
End Try

There’s a free chart component. Check if works and fits your needs: ChartPart

Repaint your new chart from the scratch with the new data set?

g.Clearrect doesnt usually do what you think it should.

Try replacing it with

g.forecolor = &C000000  //or whatever
g.fillrect 0,0,g.width, g.height
1 Like

First of all, that is a very BAD idea. The Paint event should only have code to… PAINT the canvas.

All the calculations should be done outside the event. Keep the data in a variable, could be a variable in the window.

And where are you telling the canvas to repaint (invalidate) itself ???

From ComboBox.Expense – frmChart.cnvChartDisplay.Refresh

I can move the calculations outside easy enough, but please explain why it’s a bad idea to have the calculations inside the paint?

It’s not “Bad bad”, and no Xojo police will come knocking at the door, but good practice is to keep the maths separate from the display.
That way in theory, you could use the maths to display in another format or another screen without duplicating code, for example.

1 Like

As a thumb of rule: Calculations can be slow, but painting must be fast.

So prepare what you will show, them paint it. Separating the concerns you can separate tasks as data acquisition and calculations to a thread, and when it ends, it fires an event as “DataIsReady” and from there you fire a refresh of the chart that will paint everything at once when ready, all that with your app responsive all the time.

1 Like

A good reason to keep the calculations out of the paint event is that paint can happen a lot more frequently than the calculations need to change. For example if you cover the window and then move that covering window slowly to the side. You can cause a repaint a 100 times or more. The calculations may not have changed in that time and so all the time you spend recalculating for each individual pixel that is shown becomes a massive impact.

1 Like

If the app is for personal use, do t worry about the refresh issue. macOS is very intelligent about this and it’ll only repaint during a resize, something else is dragged in front of the window or if you call refresh. It shouldn’t be an issue.

1 Like

Okay, I understand the reason. Thanks guys! I use Linux ( have since 1999 ), anyhow I still am not sure how to “destroy” the first set of rectangles I created when I go to create a second set. I have this issue with another area that has no canvas but creates Labels and Text fields on the fly.

I haven’t had a chance to change the code yet, been dealing with downed trees and rain and wind… but I still don’t know how to clear/destroy what I created on the fly.

The problem is that you’re creating actual controls and not just drawing shapes and text. It would be best not to do that. However, you can iterate over the window’s Controls and delete all the rctOne controls.

Wouldn’t:

  mycontrol.Close ()

be enough?

1 Like

Yes, that’s what I was referring to.

Downloaded ChartPart. It is very old. Last updated when graphics could be used outside of the paint event.

I was curious, so I downloaded it too.

Took about 5 minutes to switch the me.graphics. references into g. and add
‘g as graphics’ to the method definitions.
Now it runs… still needs a bit of work to get back to 100% working, but don’t let the me.graphics bit stop you…

@Jeremie_L has open source ChartView and it’s awesome

1 Like