lxn / walk

A Windows GUI toolkit for the Go Programming Language
Other
6.82k stars 886 forks source link

NumberEdit changes value involuntary when placed in ScrollView #467

Closed StephanVerbeeck closed 5 years ago

StephanVerbeeck commented 5 years ago

In a dialog poup to edit the type of booking of an accounting program the following error occurs. When there are more transfers detail boxes than fit on screen and the scrollview is moved up and down with the scrollwheel of the mouse then the value of all the numeric fields change at random.

It took me a while to figure out that NumberEdit uses the mouse scroll wheel ALSO so while scrolling the scrollview some of it goes to the number edit and some goes to the scrollview (depending on where the mouse cursor is at any given moment).

The result is that the user can not control the value that he/she is entering (they always change when this is NOT the intention and cause severe errors in the accounting system!!!

The code of the dialog is added below and I tried to switch the scrollwheel usage of the NumberEdit off by setting the Increment value to 0.0 but that does not work.

location of bug -> github.com\lxn\walk\declarative\numberedit.go#78

schermafdruk 2019-03-08 14 56 01

package main

import (
    "fmt"

    "github.com/lxn/walk"
    . "github.com/lxn/walk/declarative"
)

func RunBookingTypeDialog(bookingType *BookingType) (int, error) {

    var dataBinder *walk.DataBinder
    var dlg *walk.Dialog
    var acceptPB *walk.PushButton
    var cancelPB *walk.PushButton

    transfers := bookingType.Transfers[:]                 // used for local editing without direct storing into booking type
    transfers = append(transfers, Transfer{Percent: 100}) // add empty transfer to be able to add an extra transfer

    return Dialog{
        AssignTo:      &dlg,
        Title:         "Booking Type",
        Name:          "BookingTypeDialog",
        Icon:          appIcon,
        DefaultButton: &acceptPB,
        CancelButton:  &cancelPB,
        Layout:        VBox{SpacingZero: true, MarginsZero: true},
        MinSize:       Size{500, 500},
        Children: []Widget{
            ScrollView{
                Layout: VBox{},
                Children: []Widget{
                    GroupBox{
                        Title:   "Type",
                        MinSize: Size{300, 100},
                        MaxSize: Size{1000, 100},
                        Layout:  Grid{Columns: 2},
                        DataBinder: DataBinder{
                            AssignTo:       &dataBinder,
                            DataSource:     bookingType,
                            ErrorPresenter: ToolTipErrorPresenter{},
                        },
                        Children: []Widget{
                            Label{Text: "ID"},
                            LineEdit{Text: Bind("ID")},

                            Label{Text: "Name"},
                            LineEdit{Text: Bind("Title")},

                            Label{Text: "Contact"},
                            LineEdit{Text: Bind("ContactID")},
                        },
                    },
                    Composite{
                        Layout:   VBox{SpacingZero: true, MarginsZero: true},
                        Children: transferGroupBoxes(transfers),
                    },
                    Composite{
                        Layout: HBox{},
                        Children: []Widget{
                            HSpacer{},
                            PushButton{
                                AssignTo: &acceptPB,
                                Text:     "OK",
                                MinSize:  Size{100, 40},
                                MaxSize:  Size{100, 40},
                                OnClicked: func() {
                                    if transferValidate(bookingType, transfers) {
                                        dataBinder.Submit()
                                        accounting.isChanged = true
                                        dlg.Accept()
                                    }
                                },
                            },
                            PushButton{
                                AssignTo: &cancelPB,
                                Text:     "Cancel",
                                MinSize:  Size{100, 40},
                                MaxSize:  Size{100, 40},
                                OnClicked: func() {
                                    dlg.Cancel()
                                },
                            },
                        },
                    },
                },
            },
        },
    }.Run(mainWindow)
}

func transferGroupBoxes(transfers []Transfer) []Widget {
    boxes := make([]Widget, len(transfers))

    for t := range transfers {
        boxes[t] = GroupBox{
            Title:   fmt.Sprintf("Transfer#%d", t+1),
            MinSize: Size{300, 100},
            MaxSize: Size{1000, 100},
            Layout:  Grid{Columns: 2},
            DataBinder: DataBinder{
                DataSource:     &transfers[t],
                AutoSubmit:     true,
                ErrorPresenter: ToolTipErrorPresenter{},
            },
            Children: []Widget{
                Label{Text: "Debet"},
                LineEdit{Text: Bind("DebetPostID")},

                Label{Text: "Credit"},
                LineEdit{Text: Bind("CreditPostID")},

                Label{Text: "Percent"},
                NumberEdit{Value: Bind("Percent", Range{0.00, 200.00}), Suffix: "%", Decimals: 2, Increment: 0},
            },
        }
    }
    return boxes
}

// return true if validation of all transfers did not reveal an error (popup error message otherwise)
func transferValidate(bookingType *BookingType, transfers []Transfer) bool {
    // find out how many of the transfer boxes were filled with data
    used := 0
    for _, transfer := range transfers {
        if transfer.DebetPostID == "" && transfer.CreditPostID == "" {
            break // this box is not used
        }
        used++ //this box is used
    }
    bookingType.Transfers = transfers[:used] // truncate the list of boxes to exclude the empty at the end
    return true
}
StephanVerbeeck commented 5 years ago

I tried this, but it still has the problem that the NumberEdit "eats" the mouse scroll wheel messages (scrollview is blocked from scrolling when mouse cursor gets above a NumberEdit).

NumberEdit{Value: Bind("Percent", Range{0.00, 200.00}), Suffix: "%", Decimals: 2, Increment: -1},

github.com\lxn\walk\declarative\numberedit.go
    inc := ne.Increment
    if inc == 0 {
        inc = 1
    }

github.com\lxn\walk\numberedit.go#819
    case win.WM_MOUSEWHEEL:
        if nle.ReadOnly() || nle.increment <= 0 {
            break
        }
lxn commented 5 years ago

I implemented your proposed solution 2 and it works for me on Windows 10 and Server 2008 R2, not eating WM_MOUSEWHEEL, even when a NumberEdit is focused.

StephanVerbeeck commented 5 years ago

Thanks! Problem solved.