Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UpdateHeader func of a table with header is being called multiple times whenever a cell in the table's body is refreshed #4915

Open
2 tasks done
Bl4cksunLtd opened this issue Jun 5, 2024 · 2 comments
Labels
unverified A bug that has been reported but not verified

Comments

@Bl4cksunLtd
Copy link

Bl4cksunLtd commented Jun 5, 2024

Checklist

  • I have searched the issue tracker for open issues that relate to the same problem, before opening a new one.
  • This issue only relates to a single bug. I will open new issues for any other problems.

Describe the bug

When a table is created with headers, the function that is called to update the header is called once for each header entry whenever any cell within the table's body is refreshed using table.RefreshItem(tablecellid).
With a limited number of tables that aren't changing often this is not noticeable, but as the number of cells changing increases especially with multiple tables it causes major issues.
The cpu usage increases, but not to 100% but there may be some sort of deadlocking going on, but this is purely as guess based on what ive seen. I had a progress bar that showed the latency of a websocket connection. As the number of tables increased, eventually the websocket disconnected. Without the custom headers, this wasn't an issue until significantly more tables had been added to the display.

How to reproduce

Create a table with headers.
Add a function for createheaders and updateheader.
In the updateheader function log the id.col and id.row.
Add a go routine to change cells within the table body's data, it doesnt matter if the cells are visible or not.
You will notice the updateheader function will log an entry for each header cell whenever a body cell changes.

Screenshots

No response

Example code

package main
import (
    "sort"
	"log"
    "strconv"
    "strings"
	"math/rand"
	"time"
    "fyne.io/fyne/v2"
    "fyne.io/fyne/v2/app"
    "fyne.io/fyne/v2/theme"
    "fyne.io/fyne/v2/widget"
    "github.com/go-loremipsum/loremipsum"
)
type row struct {
    id                int
    name, description string
}
var rows []row
func init() {
    l := loremipsum.New()
    for i := 0; i < 500; i++ {
        name := l.Word() + " " + l.Word()
        desc := l.Word() + " " + l.Word() + " " + l.Word() + " " + l.Word() + " " + l.Word()
        r := row{i, name, desc}
        rows = append(rows, r)
    }
}
type dir int
const (
    sortOff dir = iota
    sortAsc
    sortDesc
)
var sorts = [3]dir{}
func main() {
    a := app.New()
    w := a.NewWindow("Hello")
    t := widget.NewTableWithHeaders(func() (int, int) {
        return len(rows), 3
    },
        func() fyne.CanvasObject {
            l := widget.NewLabel("John Smith")
            return l
        },
        func(id widget.TableCellID, o fyne.CanvasObject) {
            l := o.(*widget.Label)
            l.Truncation = fyne.TextTruncateEllipsis
            switch id.Col {
            case 0:
                l.Truncation = fyne.TextTruncateOff
                l.SetText(strconv.Itoa(rows[id.Row].id))
            case 1:
                l.SetText(rows[id.Row].name)
            case 2:
                l.SetText(rows[id.Row].description)
            }
        })
    t.SetColumnWidth(0, 40)
    t.SetColumnWidth(1, 125)
    t.SetColumnWidth(2, 450)
    t.CreateHeader = func() fyne.CanvasObject {
        return widget.NewButton("000", func() {})
    }
	t.ShowHeaderColumn = false
    t.UpdateHeader = func(id widget.TableCellID, o fyne.CanvasObject) {
        b := o.(*widget.Button)
		log.Printf("%d,%d : ",id.Col,id.Row)
        if id.Col == -1 {
			 b.SetText(strconv.Itoa(id.Row))
            b.Importance = widget.LowImportance
            b.Disable()
        } else {
            switch id.Col {
            case 0:
                b.SetText("ID")
                switch sorts[0] {
                case sortAsc:
                    b.Icon = theme.MoveUpIcon()
                case sortDesc:
                    b.Icon = theme.MoveDownIcon()
                default:
                    b.Icon = nil
                }
            case 1:
                b.SetText("Name")
                switch sorts[1] {
                case sortAsc:
                    b.Icon = theme.MoveUpIcon()
                case sortDesc:
                    b.Icon = theme.MoveDownIcon()
                default:
                    b.Icon = nil
                }
            case 2:
                b.SetText("Description")
                switch sorts[2] {
                case sortAsc:
                    b.Icon = theme.MoveUpIcon()
                case sortDesc:
                    b.Icon = theme.MoveDownIcon()
                default:
                    b.Icon = nil
                }
            }
            b.Importance = widget.MediumImportance
            b.OnTapped = func() {
                applySort(id.Col, t)
            }
            b.Enable()
            b.Refresh()
        }
    } 
	go func()	{
		for {
			i:=rand.Intn(100)+5
			j:=rand.Intn(100)+5
			temp:=rows[i]
			rows[i]=rows[j]
			rows[j]=temp
			time.Sleep(200*time.Millisecond)
			t.RefreshItem(widget.TableCellID{i,1})
			t.RefreshItem(widget.TableCellID{i,2})
			t.RefreshItem(widget.TableCellID{j,1})
			t.RefreshItem(widget.TableCellID{j,2})
			
		}
	}()
			
    w.SetContent(t)
    w.ShowAndRun()
}
func applySort(col int, t *widget.Table) {
    order := sorts[col]
    order++
    if order > sortDesc {
        order = sortOff
    }
    // reset all and assign tapped sort
    for i := 0; i < 3; i++ {
        sorts[i] = sortOff
    }
    sorts[col] = order
    sort.Slice(rows, func(i, j int) bool {
        a := rows[i]
        b := rows[j]
        // re-sort with no sort selected
        if order == sortOff {
            return a.id < b.id
        }
        switch col {
        case 1:
            if order == sortAsc {
                return strings.Compare(a.name, b.name) < 0
            }
            return strings.Compare(a.name, b.name) > 0
        case 2:
            if order == sortAsc {
                return strings.Compare(a.description, b.description) < 0
            }
            return strings.Compare(a.description, b.description) > 0
        default:
            if order == sortDesc {
                return a.id > b.id
            }
            return a.id < b.id
        }
    })
    t.Refresh()
}

Fyne version

2.4.5

Go compiler version

1.19.2

Operating system and version

windows 10 /amd64

Additional Information

No response

@Bl4cksunLtd Bl4cksunLtd added the unverified A bug that has been reported but not verified label Jun 5, 2024
@Bl4cksunLtd
Copy link
Author

It may be linked to the following code in func (r *tableCellsRenderer) refreshForID(toDraw TableCellID) in the widget/table.go file (around line 1342:
for id, head := range headers { r.cells.t.updateHeader(id, head) }
This appears to be calling the updateHeader function for each header cell whenever the table renderer refreshes any part of the table.

@andydotxyz
Copy link
Member

That sounds possible, thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
unverified A bug that has been reported but not verified
Projects
None yet
Development

No branches or pull requests

2 participants