简体   繁体   English

使用 Select Widget 时我是如何设法恐慌的

[英]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. Go 和 Fyne 的新手,并且在试图从 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.我们将此附加信息称为 About 结构。

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.对于像 Entry 这样的小部件,我知道这种设计会让我不得不为粘贴、复制和 Entry 通常在右键单击时提供的其他操作编写自己的菜单选项,但我对此没有意见。

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.我已经完成了所有这些工作,但是在此过程中我破坏了 Select(并且其他小部件可能会损坏。)并且我不知道如何修复它。

Problem: trying to send the Select widget causes a panic: interface conversion: fyne.Canvas is nil, not *glfw.glCanvas问题:尝试发送 Select 小部件会导致恐慌:界面转换:fyne.Canvas 为零,而不是 *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:现在我必须捕获所有“事件”,以便 GenericWidget 会看到它们:

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.这并不复杂:当驱动程序调用 Tapped 时,将调用 GenericWidget.Tapped。 If this widget has a function pointer for “OnLeftClickp” set, call it.如果此小部件设置了“OnLeftClickp”的函数指针,则调用它。 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.至关重要的是,当发生这种情况时,我们会传递一个指向小部件本身的指针,因为我编写的所有事件处理程序都需要访问 *About 以及我添加到 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当需要创建标签时,我会这样做并将其折叠成一个 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.所有这些 GenericWidgets 都可以正常工作——我将它们添加到适当的 Box 中,并按照我的预期绘制窗口。 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.我可以右键单击一个标签,如果设置了 OnRightClickp,通常是这样,代码被调用并被授予访问 *GenericWidget 的权限,这会导致 *About,这意味着被放置的菜单可以提供所有正确的内容这个标签里有什么。

Of course, Labels don't normally care about clicking, so the fact that I've stolen all the calls in Tappable doesn't matter.当然,标签通常不关心点击,所以我窃取了 Tappable 中所有调用的事实并不重要。

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.但是 Select 小部件确实关心左键单击,因此 GenericWidget 拦截对 Tapped() 的调用这一事实意味着我永远不会看到下拉列表出现。

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:当我创建 Select 小部件和周围的 GenericWidget 时,我将指定我想自己调用 Select 的 Tapped:

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;我的 GenericWidget 只是一个小部件; 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.我认为这就是组合所做的,并且我创建的所有 GenericWidgets 都放在一个 Box 中,该 Box 位于一个 Box 中,该 Box 是窗口中的 SetContent。 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.但是错误提示我不知何故这个 Select 对象没有正确设置,当它去绘制选项时,缺少一些东西。 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).我的整个方法可能是错误的(我做了很多 C++ 和 Python,并且我对事物采取了面向对象的观点)。 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.如果您正在扩展内置小部件,您还需要调用 ExtendBaseWidget 以便驱动程序可以查找您的小部件而不是默认小部件。 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.也就是说,可以扩展任何其他类型的小部件类型的单个小部件与 Fyne API 的强类型设计背道而驰,您可能会遇到麻烦。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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