简体   繁体   中英

How did I manage to panic when using the Select Widget

New to Go and Fyne, and stumbling trying to get what I need out of Fyne. Sorry, this will be long.

My problem is this. I'm writing a application that gets a list of commands from a server, telling it to create a series of widgets and display them. They are of various types – Label, Button, Entry, Select, etc.

But these aren't standard widgets; I need to extend their behavior a bit. For one thing, when operated by the user, they each need access to some per-widget information. Button click, for example, has to reference some data specific to that button so it knows what to do. We'll call this additional information the About struct.

Secondly, every widget needs to be able to take a right click and drop down a widget specific menu. Yes, even buttons and labels need to be able to provide drop down menus. In the case of a widget like Entry, I know this design is going to condemn me to having to write my own menu choices for Paste, Copy and the other operations Entry normally offers on a right click, but I'm ok with that.

I have all this working, but in the process I broke Select (and probably there will be breakage for other widgets.) and I can't see how to fix it.

Problem: trying to send the Select widget causes a panic: interface conversion: fyne.Canvas is nil, not *glfw.glCanvas

Approach:

type GenericWidget struct {
    fyne.Widget //I’m some kind of widget
    about *About //here’s my personal “About” data

    //function pointers
    OnRightClickp func(gw *GenericWidget, pe *fyne.PointEvent)
    OnLeftClickp func(gw *GenericWidget, pe *fyne.PointEvent)
    …other “function pointers” for OnRunep and so on…
}

And now I have to catch all “events” so GenericWidget will see them:

func (gw *GenericWidget) TappedSecondary(pe *fyne.PointEvent) {
    if (gw.OnRightClickp != nil) {gw.OnRightClickp(gw, pe)}
}
func (gw *GenericWidget) Tapped(pe *fyne.PointEvent) {
    if (gw.OnLeftClickp != nil){gw.OnLeftClickp(gw, pe)}
}
//type Focusable interface
//etc….

This should represent any single widget, regardless of type. It's not complicated: When Tapped is invoked by the driver, GenericWidget.Tapped gets called. If this widget has a function pointer for “OnLeftClickp” set, call it. Crucially, we pass a pointer to the widget itself when that happens, because all the event handlers I write will need access to the *About and maybe anything else I add to GenericWidget.

Creation is simple enough:

func NewGenericWidget(w fyne.Widget, about *About) *GenericWidget {
    gw := GenericWidget{w, about, nil, nil, nil, nil, nil, nil, nil, nil}
    return &gw
}

And when it's time to create a Label, I do that and fold it into a GenericWidget

func NewExtendedLabelWithStyle( //Label with catachable left and right click
 text string, 
 about *About, 
 alignment fyne.TextAlign, 
 style fyne.TextStyle, 
 tappedLeft func(*GenericWidget, *fyne.PointEvent), 
 tappedRight func(*GenericWidget, *fyne.PointEvent)) *GenericWidget {
    e := NewGenericWidget(widget.NewLabelWithStyle(text, alignment, style), about)
    e.OnLeftClickp = tappedLeft
    e.OnRightClickp = tappedRight
   return e
}

All these GenericWidgets work fine – I add them to the appropriate Box and the window paints as I'd expect. I can right click on a label and if OnRightClickp is set, which is generally is, code gets called and is given access to *GenericWidget, which leads to *About, which means the menu that gets put up can offer all the right stuff for what's in this label.

Of course, Labels don't normally care about clicking, so the fact that I've stolen all the calls in Tappable doesn't matter.

But a Select widget does care about left clicks, so the fact that GenericWidget is intercepting the call to Tapped() means I'd never see the dropdown appear.

No problem, I thought. When I create the Select widget and the surrounding GenericWidget, I'll just specify I want to call Select's Tapped myself:

func NewExtendedSelect(about *About, sel func(*GenericWidget, string)) *GenericWidget {
    //build the options. NewSelect demands a slice of strings, so...
    st := make([]string, 64)
    for e := about.theList.Front(); e != nil; e = e.Next() {
        st = append(st, e.Value.(string))
    }
    s := widget.NewSelect(st, func(c string){})
    //make sure it selects the text it should, initially
    s.SetSelected(about.value)
    //wrap it
    e := NewGenericWidget(s, about)
    //e.OnChangedp = sel //not set up yet (and don’t know how)

    //HERE BE DRAGONS --------------

    //But we don't want to break left click. So when we intercept it, 
    // pass it down to the actual Selection widget:
    e.OnLeftClickp = func(me* GenericWidget, pe *fyne.PointEvent) {
        fmt.Println("select tapped")
        //call the Tapped that Select knows about:
        s.Tapped(pe) //PANIC!
    }
    // -----------------------------

    //Handle right click with our usual menu magic
    e.OnRightClickp =func(me* GenericWidget, pe *fyne.PointEvent) {
        //defer used because not sure what to do with the return value, so make it Go's problem
        defer widget.NewPopUpMenuAtPosition(me.GetRightClickMenu(), 
            me.about.sheet.window.Canvas(), pe.AbsolutePosition)
    }
    return e //here's your GenericWidget, ready to drop into a Fyne container.
}

Compiles fine, Selects get displayed, but when I left click it prints the expected “select tapped” and then immediately panics:

select tapped
panic: interface conversion: fyne.Canvas is nil, not *glfw.glCanvas

I'm lost. My GenericWidget is just a widget; I thought that's what composition did, and all the GenericWidgets I create are put in a Box which is in a Box which is SetContent into the window. But the error is suggesting to me that somehow this Select object wasn't set up right and when it goes to draw the options, something is missing. What did I do wrong?

It's possible my whole approach is wrong (I do a lot of C++ and Python and I take an OO view of things). In that case, how do I do all this?

事实证明这是一个 Fyne 错误,此后已修复。

It does not look like the select is being composed - your extended select is just creating a new one, tapping it later.

The crash is coming from it trying to display a pop up next to a select that has not been found on the canvas.

If you are extending builtin widgets you also need to call ExtendBaseWidget so that the driver can lookup your widget instead of the default one. That said a single widget that can extend any other type of widget kind of goes against the strongly types design of Fyne APIs and you may run in to trouble.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM