自定义Fyne自适应网格布局

huangapple go评论91阅读模式
英文:

Custom Fyne Adaptive Grid Layout

问题

我正在修改 fyne 库中的 container.NewAdaptiveGrid() 方法,以便根据传入的比例切片来渲染小部件的宽度。目前,container.NewAdaptiveGrid() 方法会在一行中渲染等宽的小部件,即(总行宽 / 小部件数量)。

你的代码如下:

package main

import (
	"fmt"
	"math"

	"fyne.io/fyne/v2"
	"fyne.io/fyne/v2/app"
	"fyne.io/fyne/v2/container"
	"fyne.io/fyne/v2/theme"
	"fyne.io/fyne/v2/widget"
)

func New(layout fyne.Layout, objects ...fyne.CanvasObject) *fyne.Container {
	return fyne.NewContainerWithLayout(layout, objects...)
}

func NewAdaptiveGridWithRatios(ratios []float32, objects ...fyne.CanvasObject) *fyne.Container {
	return New(NewAdaptiveGridLayoutWithRatios(ratios), objects...)
}

// 声明符合 Layout 接口
var _ fyne.Layout = (*adaptiveGridLayoutWithRatios)(nil)

type adaptiveGridLayoutWithRatios struct {
	ratios          []float32
	adapt, vertical bool
}

func NewAdaptiveGridLayoutWithRatios(ratios []float32) fyne.Layout {
	return &adaptiveGridLayoutWithRatios{ratios: ratios, adapt: true}
}

func (g *adaptiveGridLayoutWithRatios) horizontal() bool {
	if g.adapt {
		return fyne.IsHorizontal(fyne.CurrentDevice().Orientation())
	}

	return !g.vertical
}

func (g *adaptiveGridLayoutWithRatios) countRows(objects []fyne.CanvasObject) int {
	count := 0
	for _, child := range objects {
		if child.Visible() {
			count++
		}
	}

	return int(math.Ceil(float64(count) / float64(len(g.ratios))))
}

// 获取网格单元格的前导(顶部或左侧)边缘。
// size 是理想的单元格大小,offset 是所在的列或行。
func getLeading(size float64, offset int) float32 {
	ret := (size + float64(theme.Padding())) * float64(offset)

	return float32(ret)
}

// 获取网格单元格的尾随(底部或右侧)边缘。
// size 是理想的单元格大小,offset 是所在的列或行。
func getTrailing(size float64, offset int) float32 {
	return getLeading(size, offset+1) - theme.Padding()
}

// Layout 方法用于将所有子对象打包到指定的大小中。
// 对于 GridLayout,它将对象打包成一个表格格式,行数由我们的构造函数指定。
func (g *adaptiveGridLayoutWithRatios) Layout(objects []fyne.CanvasObject, size fyne.Size) {
	rows := g.countRows(objects)
	cols := len(g.ratios)
	if g.horizontal() {
		cols = rows
		rows = len(g.ratios)
	}

	padWidth := float32(cols-1) * theme.Padding()
	padHeight := float32(rows-1) * theme.Padding()
	var totalRatio float32
	for _, r := range g.ratios {
		totalRatio += r
	}

	cellWidth := (float64(size.Width) - float64(padWidth)) / float64(len(g.ratios))
	cellHeight := float64(size.Height-padHeight) / float64(rows)

	if !g.horizontal() {
		cellWidth, cellHeight = cellHeight, cellWidth
		cellWidth = float64(size.Width-padWidth) / float64(rows)
		cellHeight = float64(size.Height-padHeight) / float64(len(g.ratios))
	}

	row, col := 0, 0
	i := 0
	for _, child := range objects {
		if !child.Visible() {
			continue
		}

		//ratio := g.ratios[j%len(g.ratios)]
		cellSize := fyne.NewSize(float32(cellWidth)*g.ratios[i], float32(cellHeight))

		x1 := getLeading(float64(cellSize.Width), col)
		y1 := getLeading(float64(cellSize.Height), row)
		x2 := getTrailing(float64(cellSize.Width), col)
		y2 := getTrailing(float64(cellSize.Height), row)
		fmt.Println("1s :", x1, y1)
		fmt.Println("2s :", x2, y2)
		child.Move(fyne.NewPos(x1, y1))
		child.Resize(cellSize)

		if g.horizontal() {
			if (i+1)%cols == 0 {
				row++
				col = 0
			} else {
				col++
			}
		} else {
			if (i+1)%cols == 0 {
				col++
				row = 0
			} else {
				row++
			}
		}
		i++
	}
	fmt.Println("i :", i)
}

func (g *adaptiveGridLayoutWithRatios) MinSize(objects []fyne.CanvasObject) fyne.Size {
	minSize := fyne.NewSize(0, 0)
	return minSize
}

func main() {
	myApp := app.New()
	myWindow := myApp.NewWindow("My Windows")
	myWindow.Resize(fyne.NewSize(600, 200))

	button1 := widget.NewButton("Button 1", func() {
		// 处理按钮 1 的点击事件
	})

	button2 := widget.NewButton("Button 2", func() {
		// 处理按钮 2 的点击事件
	})
	button1.Importance = widget.WarningImportance
	button2.Importance = widget.DangerImportance
	title := widget.NewLabelWithStyle("Custom", fyne.TextAlignCenter, fyne.TextStyle{Bold: true})

	myWindow.SetContent(container.NewVBox(title,
		NewAdaptiveGridWithRatios([]float32{0.3, 0.7}, button1, button2)))

	myWindow.ShowAndRun()
}

我预期按钮应该并排显示,其中按钮的相对宽度比例为 3:7。但是我得到了两条水平线,一条在另一条下面。
我正在修改的文件是:https://github.com/fyne-io/fyne/blob/8c2509518b2df442a6b748d9b07754739592e6d7/layout/gridlayout.go
以创建我的自定义布局。

自定义Fyne自适应网格布局

英文:

I'm modifying container.NewAdaptiveGrid() of fyne library in a way that we render widgets's width according the ratio slice we pass. As of now, container.NewAdaptiveGrid() renders widgets of equal width in a row. basically (total row size / now of widgets).

My code:

package main
import (
"fmt"
"math"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
)
func New(layout fyne.Layout, objects ...fyne.CanvasObject) *fyne.Container {
return fyne.NewContainerWithLayout(layout, objects...)
}
func NewAdaptiveGridWithRatios(ratios []float32, objects ...fyne.CanvasObject) *fyne.Container {
return New(NewAdaptiveGridLayoutWithRatios(ratios), objects...)
}
// Declare conformity with Layout interface
var _ fyne.Layout = (*adaptiveGridLayoutWithRatios)(nil)
type adaptiveGridLayoutWithRatios struct {
ratios          []float32
adapt, vertical bool
}
func NewAdaptiveGridLayoutWithRatios(ratios []float32) fyne.Layout {
return &adaptiveGridLayoutWithRatios{ratios: ratios, adapt: true}
}
func (g *adaptiveGridLayoutWithRatios) horizontal() bool {
if g.adapt {
return fyne.IsHorizontal(fyne.CurrentDevice().Orientation())
}
return !g.vertical
}
func (g *adaptiveGridLayoutWithRatios) countRows(objects []fyne.CanvasObject) int {
count := 0
for _, child := range objects {
if child.Visible() {
count++
}
}
return int(math.Ceil(float64(count) / float64(len(g.ratios))))
}
// Get the leading (top or left) edge of a grid cell.
// size is the ideal cell size and the offset is which col or row its on.
func getLeading(size float64, offset int) float32 {
ret := (size + float64(theme.Padding())) * float64(offset)
return float32(ret)
}
// Get the trailing (bottom or right) edge of a grid cell.
// size is the ideal cell size and the offset is which col or row its on.
func getTrailing(size float64, offset int) float32 {
return getLeading(size, offset+1) - theme.Padding()
}
// Layout is called to pack all child objects into a specified size.
// For a GridLayout this will pack objects into a table format with the number
// of columns specified in our constructor.
func (g *adaptiveGridLayoutWithRatios) Layout(objects []fyne.CanvasObject, size fyne.Size) {
rows := g.countRows(objects)
cols := len(g.ratios)
if g.horizontal() {
cols = rows
rows = len(g.ratios)
}
padWidth := float32(cols-1) * theme.Padding()
padHeight := float32(rows-1) * theme.Padding()
var totalRatio float32
for _, r := range g.ratios {
totalRatio += r
}
cellWidth := (float64(size.Width) - float64(padWidth)) / float64(len(g.ratios))
cellHeight := float64(size.Height-padHeight) / float64(rows)
if !g.horizontal() {
cellWidth, cellHeight = cellHeight, cellWidth
cellWidth = float64(size.Width-padWidth) / float64(rows)
cellHeight = float64(size.Height-padHeight) / float64(len(g.ratios))
}
row, col := 0, 0
i := 0
for _, child := range objects {
if !child.Visible() {
continue
}
//ratio := g.ratios[j%len(g.ratios)]
cellSize := fyne.NewSize(float32(cellWidth)*g.ratios[i], float32(cellHeight))
x1 := getLeading(float64(cellSize.Width), col)
y1 := getLeading(float64(cellSize.Height), row)
x2 := getTrailing(float64(cellSize.Width), col)
y2 := getTrailing(float64(cellSize.Height), row)
fmt.Println("1s :", x1, y1)
fmt.Println("2s :", x2, y2)
child.Move(fyne.NewPos(x1, y1))
child.Resize(cellSize)
if g.horizontal() {
if (i+1)%cols == 0 {
row++
col = 0
} else {
col++
}
} else {
if (i+1)%cols == 0 {
col++
row = 0
} else {
row++
}
}
i++
}
fmt.Println("i :", i)
}
func (g *adaptiveGridLayoutWithRatios) MinSize(objects []fyne.CanvasObject) fyne.Size {
minSize := fyne.NewSize(0, 0)
return minSize
}
func main() {
myApp := app.New()
myWindow := myApp.NewWindow("My Windows")
myWindow.Resize(fyne.NewSize(600, 200))
button1 := widget.NewButton("Button 1", func() {
// Handle button click for button 1
})
button2 := widget.NewButton("Button 2", func() {
// Handle button click for button 2
})
button1.Importance = widget.WarningImportance
button2.Importance = widget.DangerImportance
title := widget.NewLabelWithStyle("Custom", fyne.TextAlignCenter, fyne.TextStyle{Bold: true})
myWindow.SetContent(container.NewVBox(title,
NewAdaptiveGridWithRatios([]float32{0.3, 0.7}, button1, button2)))
myWindow.ShowAndRun()
}

I expected buttons to be side by side where relative width of the buttons are in ration 3:7. But I'm getting 2 horizontal line one below another.
I'm modifying : https://github.com/fyne-io/fyne/blob/8c2509518b2df442a6b748d9b07754739592e6d7/layout/gridlayout.go
to make my custom one.

自定义Fyne自适应网格布局

答案1

得分: 2

这是一个工作的代码示例,其中包含了多个按钮和其他小部件,它们以不同的比例排列。

package main

import (
	"fmt"
	"math"

	"fyne.io/fyne/v2"
	"fyne.io/fyne/v2/app"
	"fyne.io/fyne/v2/container"
	"fyne.io/fyne/v2/theme"
	"fyne.io/fyne/v2/widget"
)

func New(layout fyne.Layout, objects ...fyne.CanvasObject) *fyne.Container {
	return fyne.NewContainerWithLayout(layout, objects...)
}

func NewAdaptiveGridWithRatios(ratios []float32, objects ...fyne.CanvasObject) *fyne.Container {
	return New(NewAdaptiveGridLayoutWithRatios(ratios), objects...)
}

// 声明符合 Layout 接口
var _ fyne.Layout = (*adaptiveGridLayoutWithRatios)(nil)

type adaptiveGridLayoutWithRatios struct {
	ratios          []float32
	adapt, vertical bool
}

func NewAdaptiveGridLayoutWithRatios(ratios []float32) fyne.Layout {
	return &adaptiveGridLayoutWithRatios{ratios: ratios, adapt: true}
}

func (g *adaptiveGridLayoutWithRatios) horizontal() bool {
	if g.adapt {
		return fyne.IsHorizontal(fyne.CurrentDevice().Orientation())
	}

	return !g.vertical
}

func (g *adaptiveGridLayoutWithRatios) countRows(objects []fyne.CanvasObject) int {
	count := 0
	for _, child := range objects {
		if child.Visible() {
			count++
		}
	}

	return int(math.Ceil(float64(count) / float64(len(g.ratios))))
}

// Layout 被调用以将所有子对象打包到指定的大小中。
// 对于 GridLayout,这将使用构造函数中指定的列数将对象打包成表格格式。
func (g *adaptiveGridLayoutWithRatios) Layout(objects []fyne.CanvasObject, size fyne.Size) {

	rows := g.countRows(objects)
	cols := len(g.ratios)

	padWidth := float32(cols-1) * theme.Padding()
	padHeight := float32(rows-1) * theme.Padding()
	tGap := float64(padWidth)
	tcellWidth := float64(size.Width) - tGap
	cellHeight := float64(size.Height-padHeight) / float64(rows)

	fmt.Println(cols, rows)
	fmt.Println(cellHeight, tcellWidth+tGap, tGap)
	fmt.Println("tcellWidth, cellHeight", tcellWidth, cellHeight)
	if !g.horizontal() {
		padWidth, padHeight = padHeight, padWidth
		tcellWidth = float64(size.Width-padWidth) - tGap
		cellHeight = float64(size.Height-padHeight) / float64(cols)
	}

	row, col := 0, 0
	i := 0
	var x1, x2, y1, y2 float32 = 0.0, 0.0, 0.0, 0.0
	fmt.Println("padWidth, padHeight, tcellWidth, cellHeight, float32(theme.Padding()):", padWidth, padHeight, tcellWidth, cellHeight, float32(theme.Padding()))
	for _, child := range objects {
		if !child.Visible() {
			continue
		}

		if i == 0 {
			x1 = 0
			y1 = 0
		} else {
			x1 = x2 + float32(theme.Padding())*float32(1)
			y1 = y2 - float32(cellHeight)
		} // float32(tGap/float64(col))
		//  (size + float64(theme.Padding())) * float64(offset)  float32(theme.Padding())*float32(1)
		x2 = x1 + float32(tcellWidth*float64(g.ratios[i]))
		y2 = float32(cellHeight)

		fmt.Println("x1,y1 :", x1, y1)
		fmt.Println("x2, y2 :", x2, y2)
		fmt.Println("eff width", tcellWidth*float64(g.ratios[i]))

		fmt.Println("------")
		child.Move(fyne.NewPos(x1, y1))
		child.Resize(fyne.NewSize((x2 - x1), y2-y1))

		if g.horizontal() {
			if (i+1)%cols == 0 {
				row++
				col = 0
			} else {
				col++
			}
		} else {
			if (i+1)%cols == 0 {
				col++
				row = 0
			} else {
				row++
			}
		}
		i++
	}
	fmt.Println("i :", i)
}

func (g *adaptiveGridLayoutWithRatios) MinSize(objects []fyne.CanvasObject) fyne.Size {
	rows := g.countRows(objects)
	minSize := fyne.NewSize(0, 0)
	for _, child := range objects {
		if !child.Visible() {
			continue
		}

		minSize = minSize.Max(child.MinSize())
	}

	if g.horizontal() {
		minContentSize := fyne.NewSize(minSize.Width*float32(len(g.ratios)), minSize.Height*float32(rows))
		return minContentSize.Add(fyne.NewSize(theme.Padding()*fyne.Max(float32(len(g.ratios)-1), 0), theme.Padding()*fyne.Max(float32(rows-1), 0)))
	}

	minContentSize := fyne.NewSize(minSize.Width*float32(rows), minSize.Height*float32(len(g.ratios)))
	return minContentSize.Add(fyne.NewSize(theme.Padding()*fyne.Max(float32(rows-1), 0), theme.Padding()*fyne.Max(float32(len(g.ratios)-1), 0)))
}

func main() {
	myApp := app.New()
	myWindow := myApp.NewWindow("My Windows Custom UI")
	myWindow.Resize(fyne.NewSize(600, 200))

	var buttons [16]*widget.Button

	for i := 0; i < 16; i++ {
		button := widget.NewButton(fmt.Sprintf("Btn %d", i+1), func() {
			// 处理此按钮的点击事件
		})

		// 根据按钮索引设置按钮的重要性
		if i%2 == 0 {
			button.Importance = widget.WarningImportance
		} else {
			button.Importance = widget.DangerImportance
		}

		buttons[i] = button
	}

	pgBar := widget.NewLabelWithStyle("Progress :", fyne.TextAlignCenter, fyne.TextStyle{Italic: true})
	progressBar := widget.NewProgressBar()
	progressBar.SetValue(0.95)

	myWindow.SetContent(container.NewVBox(
		NewAdaptiveGridWithRatios([]float32{0.1, 0.4, 0.4, 0.1}, buttons[0], buttons[1], buttons[2], buttons[3]),
		NewAdaptiveGridWithRatios([]float32{0.2, 0.3, 0.1, 0.4}, buttons[4], buttons[5], buttons[6], buttons[7]),
		NewAdaptiveGridWithRatios([]float32{0.6, 0.1, 0.2, 0.1}, buttons[8], buttons[9], buttons[10], buttons[11]),
		NewAdaptiveGridWithRatios([]float32{0.1, 0.4, 0.4, 0.1}, buttons[12], buttons[13], buttons[14], buttons[15]),
		NewAdaptiveGridWithRatios([]float32{0.1, 0.9}, pgBar, progressBar),
	))

	myWindow.ShowAndRun()
}

这段代码创建了一个自定义的 UI 窗口,其中包含多个按钮和其他小部件,它们以不同的比例排列。

英文:

This works :

package main
import (
&quot;fmt&quot;
&quot;math&quot;
&quot;fyne.io/fyne/v2&quot;
&quot;fyne.io/fyne/v2/app&quot;
&quot;fyne.io/fyne/v2/container&quot;
&quot;fyne.io/fyne/v2/theme&quot;
&quot;fyne.io/fyne/v2/widget&quot;
)
func New(layout fyne.Layout, objects ...fyne.CanvasObject) *fyne.Container {
return fyne.NewContainerWithLayout(layout, objects...)
}
func NewAdaptiveGridWithRatios(ratios []float32, objects ...fyne.CanvasObject) *fyne.Container {
return New(NewAdaptiveGridLayoutWithRatios(ratios), objects...)
}
// Declare conformity with Layout interface
var _ fyne.Layout = (*adaptiveGridLayoutWithRatios)(nil)
type adaptiveGridLayoutWithRatios struct {
ratios          []float32
adapt, vertical bool
}
func NewAdaptiveGridLayoutWithRatios(ratios []float32) fyne.Layout {
return &amp;adaptiveGridLayoutWithRatios{ratios: ratios, adapt: true}
}
func (g *adaptiveGridLayoutWithRatios) horizontal() bool {
if g.adapt {
return fyne.IsHorizontal(fyne.CurrentDevice().Orientation())
}
return !g.vertical
}
func (g *adaptiveGridLayoutWithRatios) countRows(objects []fyne.CanvasObject) int {
count := 0
for _, child := range objects {
if child.Visible() {
count++
}
}
return int(math.Ceil(float64(count) / float64(len(g.ratios))))
}
// Layout is called to pack all child objects into a specified size.
// For a GridLayout this will pack objects into a table format with the number
// of columns specified in our constructor.
func (g *adaptiveGridLayoutWithRatios) Layout(objects []fyne.CanvasObject, size fyne.Size) {
rows := g.countRows(objects)
cols := len(g.ratios)
padWidth := float32(cols-1) * theme.Padding()
padHeight := float32(rows-1) * theme.Padding()
tGap := float64(padWidth)
tcellWidth := float64(size.Width) - tGap
cellHeight := float64(size.Height-padHeight) / float64(rows)
fmt.Println(cols, rows)
fmt.Println(cellHeight, tcellWidth+tGap, tGap)
fmt.Println(&quot;tcellWidth, cellHeight&quot;, tcellWidth, cellHeight)
if !g.horizontal() {
padWidth, padHeight = padHeight, padWidth
tcellWidth = float64(size.Width-padWidth) - tGap
cellHeight = float64(size.Height-padHeight) / float64(cols)
}
row, col := 0, 0
i := 0
var x1, x2, y1, y2 float32 = 0.0, 0.0, 0.0, 0.0
fmt.Println(&quot;padWidth, padHeight, tcellWidth, cellHeight, float32(theme.Padding()):&quot;, padWidth, padHeight, tcellWidth, cellHeight, float32(theme.Padding()))
for _, child := range objects {
if !child.Visible() {
continue
}
if i == 0 {
x1 = 0
y1 = 0
} else {
x1 = x2 + float32(theme.Padding())*float32(1)
y1 = y2 - float32(cellHeight)
} // float32(tGap/float64(col))
//  (size + float64(theme.Padding())) * float64(offset)  float32(theme.Padding())*float32(1)
x2 = x1 + float32(tcellWidth*float64(g.ratios[i]))
y2 = float32(cellHeight)
fmt.Println(&quot;x1,y1 :&quot;, x1, y1)
fmt.Println(&quot;x2, y2 :&quot;, x2, y2)
fmt.Println(&quot;eff width&quot;, tcellWidth*float64(g.ratios[i]))
fmt.Println(&quot;------&quot;)
child.Move(fyne.NewPos(x1, y1))
child.Resize(fyne.NewSize((x2 - x1), y2-y1))
if g.horizontal() {
if (i+1)%cols == 0 {
row++
col = 0
} else {
col++
}
} else {
if (i+1)%cols == 0 {
col++
row = 0
} else {
row++
}
}
i++
}
fmt.Println(&quot;i :&quot;, i)
}
func (g *adaptiveGridLayoutWithRatios) MinSize(objects []fyne.CanvasObject) fyne.Size {
rows := g.countRows(objects)
minSize := fyne.NewSize(0, 0)
for _, child := range objects {
if !child.Visible() {
continue
}
minSize = minSize.Max(child.MinSize())
}
if g.horizontal() {
minContentSize := fyne.NewSize(minSize.Width*float32(len(g.ratios)), minSize.Height*float32(rows))
return minContentSize.Add(fyne.NewSize(theme.Padding()*fyne.Max(float32(len(g.ratios)-1), 0), theme.Padding()*fyne.Max(float32(rows-1), 0)))
}
minContentSize := fyne.NewSize(minSize.Width*float32(rows), minSize.Height*float32(len(g.ratios)))
return minContentSize.Add(fyne.NewSize(theme.Padding()*fyne.Max(float32(rows-1), 0), theme.Padding()*fyne.Max(float32(len(g.ratios)-1), 0)))
}
func main() {
myApp := app.New()
myWindow := myApp.NewWindow(&quot;My Windows Custom UI&quot;)
myWindow.Resize(fyne.NewSize(600, 200))
var buttons [16]*widget.Button
for i := 0; i &lt; 16; i++ {
button := widget.NewButton(fmt.Sprintf(&quot;Btn %d&quot;, i+1), func() {
// Handle button click for this button
})
// Set the button importance based on the button index
if i%2 == 0 {
button.Importance = widget.WarningImportance
} else {
button.Importance = widget.DangerImportance
}
buttons[i] = button
}
pgBar := widget.NewLabelWithStyle(&quot;Progress :&quot;, fyne.TextAlignCenter, fyne.TextStyle{Italic: true})
progressBar := widget.NewProgressBar()
progressBar.SetValue(0.95)
myWindow.SetContent(container.NewVBox(
NewAdaptiveGridWithRatios([]float32{0.1, 0.4, 0.4, 0.1}, buttons[0], buttons[1], buttons[2], buttons[3]),
NewAdaptiveGridWithRatios([]float32{0.2, 0.3, 0.1, 0.4}, buttons[4], buttons[5], buttons[6], buttons[7]),
NewAdaptiveGridWithRatios([]float32{0.6, 0.1, 0.2, 0.1}, buttons[8], buttons[9], buttons[10], buttons[11]),
NewAdaptiveGridWithRatios([]float32{0.1, 0.4, 0.4, 0.1}, buttons[12], buttons[13], buttons[14], buttons[15]),
NewAdaptiveGridWithRatios([]float32{0.1, 0.9}, pgBar, progressBar),
))
myWindow.ShowAndRun()
}

I've added multiple buttons and other widgets in different ratios.
自定义Fyne自适应网格布局

答案2

得分: 0

当调试自己的小部件或布局时,最好将窗口中的所有其他项目都排除在外。在这种情况下,VBox可能会在垂直方向上压缩内容,因此请将其移除,并继续修改代码,直到达到预期效果。

从表面上看,我认为countRows并没有按照你的预期工作。

英文:

When debugging your own widget or layout it is easier to eliminate all other items from the window. In this case the VBox may be compressing things vertically so remove that and continue to work on your code until it does what you expect.

On the face of it I expect that countRows is not doing what you expect.

huangapple
  • 本文由 发表于 2023年3月16日 14:16:17
  • 转载请务必保留本文链接:https://go.coder-hub.com/75752696.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定