Skip to content

Commit

Permalink
Begin replacing previous data binding system with a new one based on …
Browse files Browse the repository at this point in the history
…properties
  • Loading branch information
lxn committed Oct 30, 2012
1 parent 1bab8dc commit c0c6a73
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 60 deletions.
9 changes: 6 additions & 3 deletions container.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ func (cb *ContainerBase) SetDataBinder(db *DataBinder) {
cb.dataBinder = db

if db != nil {
var boundWidgets []DataBindable
var boundWidgets []Widget

walkDescendants(cb.widget, func(w Widget) bool {
if w.BaseWidget().Handle() == cb.hWnd {
Expand All @@ -139,8 +139,11 @@ func (cb *ContainerBase) SetDataBinder(db *DataBinder) {
return false
}

if bindable, ok := w.(DataBindable); ok && bindable.BindingMember() != "" {
boundWidgets = append(boundWidgets, bindable)
for _, prop := range w.BaseWidget().name2Property {
if _, ok := prop.Source().(string); ok {
boundWidgets = append(boundWidgets, w)
break
}
}

return true
Expand Down
121 changes: 64 additions & 57 deletions databinding.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,11 @@ type ErrorPresenter interface {

type DataBinder struct {
dataSource interface{}
boundWidgets []DataBindable
widget2ChangedHandle map[DataBindable]int
invalidValueWidgets map[DataBindable]bool
boundWidgets []Widget
properties []*Property
property2Widget map[*Property]Widget
property2ChangedHandle map[*Property]int
widget2Property2Error map[Widget]map[*Property]error
errorPresenter ErrorPresenter
canSubmitChangedPublisher EventPublisher
}
Expand All @@ -53,74 +55,79 @@ func (db *DataBinder) SetDataSource(dataSource interface{}) {
db.dataSource = dataSource
}

func (db *DataBinder) BoundWidgets() []DataBindable {
func (db *DataBinder) BoundWidgets() []Widget {
return db.boundWidgets
}

func (db *DataBinder) SetBoundWidgets(boundWidgets []DataBindable) {
for widget, handle := range db.widget2ChangedHandle {
widget.BindingValueChanged().Detach(handle)
func (db *DataBinder) SetBoundWidgets(boundWidgets []Widget) {
for prop, handle := range db.property2ChangedHandle {
prop.Changed().Detach(handle)
}

db.boundWidgets = boundWidgets

db.widget2ChangedHandle = make(map[DataBindable]int)
db.invalidValueWidgets = make(map[DataBindable]bool)
db.property2Widget = make(map[*Property]Widget)
db.property2ChangedHandle = make(map[*Property]int)
db.widget2Property2Error = make(map[Widget]map[*Property]error)

for _, widget := range boundWidgets {
widget := widget

changedEvent := widget.BindingValueChanged()
if changedEvent == nil {
continue
}
for _, prop := range widget.BaseWidget().name2Property {
prop := prop
if _, ok := prop.Source().(string); !ok {
continue
}

db.widget2ChangedHandle[widget] = changedEvent.Attach(func() {
db.validateWidget(widget)
})
}
}
db.properties = append(db.properties, prop)
db.property2Widget[prop] = widget

func (db *DataBinder) validateWidget(widget DataBindable) {
validatable, ok := widget.(Validatable)
if !ok {
return
db.property2ChangedHandle[prop] = prop.Changed().Attach(func() {
db.validateProperty(prop, widget)
})
}
}
}

validator := validatable.Validator()
func (db *DataBinder) validateProperty(prop *Property, widget Widget) {
validator := prop.Validator()
if validator == nil {
return
}

if err := validator.Validate(widget.BindingValue()); err != nil {
if db.invalidValueWidgets[widget] {
return
}

db.invalidValueWidgets[widget] = true
var changed bool
prop2Err := db.widget2Property2Error[widget]

if len(db.invalidValueWidgets) == 1 {
db.canSubmitChangedPublisher.Publish()
}
err := validator.Validate(prop.Get())
if err != nil {
changed = len(db.widget2Property2Error) == 0

if db.errorPresenter != nil {
db.errorPresenter.PresentError(err, widget)
if prop2Err == nil {
prop2Err = make(map[*Property]error)
db.widget2Property2Error[widget] = prop2Err
}
prop2Err[prop] = err
} else {
if !db.invalidValueWidgets[widget] {
if prop2Err == nil {
return
}

delete(db.invalidValueWidgets, widget)
delete(prop2Err, prop)

if len(db.invalidValueWidgets) == 0 {
db.canSubmitChangedPublisher.Publish()
}
if len(prop2Err) == 0 {
delete(db.widget2Property2Error, widget)

if db.errorPresenter != nil {
db.errorPresenter.PresentError(nil, widget)
changed = len(db.widget2Property2Error) == 0
}
}

if db.errorPresenter != nil {
db.errorPresenter.PresentError(err, widget)
}

if changed {
db.canSubmitChangedPublisher.Publish()
}
}

func (db *DataBinder) ErrorPresenter() ErrorPresenter {
Expand All @@ -132,16 +139,16 @@ func (db *DataBinder) SetErrorPresenter(ep ErrorPresenter) {
}

func (db *DataBinder) CanSubmit() bool {
return len(db.invalidValueWidgets) == 0
return len(db.widget2Property2Error) == 0
}

func (db *DataBinder) CanSubmitChanged() *Event {
return db.canSubmitChangedPublisher.Event()
}

func (db *DataBinder) Reset() error {
return db.forEach(func(widget DataBindable, field reflect.Value) error {
if f64, ok := widget.BindingValue().(float64); ok {
return db.forEach(func(prop *Property, field reflect.Value) error {
if f64, ok := prop.Get().(float64); ok {
switch v := field.Interface().(type) {
case float32:
f64 = float64(v)
Expand Down Expand Up @@ -183,19 +190,19 @@ func (db *DataBinder) Reset() error {
f64 = float64(v)

default:
return newError(fmt.Sprintf("Field '%s': Can't convert %s to float64.", widget.BindingMember(), field.Type().Name()))
return newError(fmt.Sprintf("Field '%s': Can't convert %s to float64.", prop.Source().(string), field.Type().Name()))
}

if err := widget.SetBindingValue(f64); err != nil {
if err := prop.Set(f64); err != nil {
return err
}
} else {
if err := widget.SetBindingValue(field.Interface()); err != nil {
if err := prop.Set(field.Interface()); err != nil {
return err
}
}

db.validateWidget(widget)
db.validateProperty(prop, db.property2Widget[prop])
return nil
})
}
Expand All @@ -205,8 +212,8 @@ func (db *DataBinder) Submit() error {
return errValidationFailed
}

return db.forEach(func(widget DataBindable, field reflect.Value) error {
value := widget.BindingValue()
return db.forEach(func(prop *Property, field reflect.Value) error {
value := prop.Get()
if value == nil {
// This happens e.g. if CurrentIndex() of a ComboBox returns -1.
// FIXME: Should we handle this differently?
Expand All @@ -225,19 +232,19 @@ func (db *DataBinder) Submit() error {
field.SetUint(uint64(f64))

default:
return newError(fmt.Sprintf("Field '%s': Can't convert float64 to %s.", widget.BindingMember(), field.Type().Name()))
return newError(fmt.Sprintf("Field '%s': Can't convert float64 to %s.", prop.Source().(string), field.Type().Name()))
}

return nil
}

field.Set(reflect.ValueOf(widget.BindingValue()))
field.Set(reflect.ValueOf(value))

return nil
})
}

func (db *DataBinder) forEach(f func(widget DataBindable, field reflect.Value) error) error {
func (db *DataBinder) forEach(f func(prop *Property, field reflect.Value) error) error {
p := reflect.ValueOf(db.dataSource)
if p.Type().Kind() != reflect.Ptr {
return newError("DataSource must be a pointer to a struct.")
Expand All @@ -252,13 +259,13 @@ func (db *DataBinder) forEach(f func(widget DataBindable, field reflect.Value) e
return newError("DataSource must be a pointer to a struct.")
}

for _, widget := range db.boundWidgets {
if field := s.FieldByName(widget.BindingMember()); field.IsValid() {
if err := f(widget, field); err != nil {
for _, prop := range db.properties {
if field := s.FieldByName(prop.Source().(string)); field.IsValid() {
if err := f(prop, field); err != nil {
return err
}
} else {
return newError(fmt.Sprintf("Field '%s' not found in struct '%s'.", widget.BindingMember(), s.Type().Name()))
return newError(fmt.Sprintf("Struct '%s' has no field '%s'.", s.Type().Name(), prop.Source().(string)))
}
}

Expand Down
62 changes: 62 additions & 0 deletions widget.go
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,10 @@ type WidgetBase struct {
suspended bool
visible bool
enabled bool
name2Property map[string]*Property
enabledProperty *Property
visibleProperty *Property
toolTipTextProperty *Property
}

var widgetWndProcPtr uintptr = syscall.NewCallback(widgetWndProc)
Expand Down Expand Up @@ -374,6 +378,8 @@ func InitWidget(widget, parent Widget, className string, style, exStyle uint32)
wb.enabled = true
wb.visible = true

wb.name2Property = make(map[string]*Property)

var hwndParent HWND
if parent != nil {
hwndParent = parent.BaseWidget().hWnd
Expand Down Expand Up @@ -425,6 +431,41 @@ func InitWidget(widget, parent Widget, className string, style, exStyle uint32)
}
}

wb.enabledProperty = NewProperty(
"Enabled",
func() interface{} {
return wb.widget.Enabled()
},
func(v interface{}) error {
wb.widget.SetEnabled(v.(bool))
return nil
},
nil)

wb.visibleProperty = NewProperty(
"Visible",
func() interface{} {
return wb.widget.Visible()
},
func(v interface{}) error {
wb.widget.SetVisible(v.(bool))
return nil
},
nil)

wb.toolTipTextProperty = NewProperty(
"ToolTipText",
func() interface{} {
return wb.widget.ToolTipText()
},
func(v interface{}) error {
wb.widget.SetToolTipText(v.(string))
return nil
},
nil)

wb.MustRegisterProperties(wb.enabledProperty, wb.visibleProperty, wb.toolTipTextProperty)

succeeded = true

return nil
Expand Down Expand Up @@ -491,6 +532,23 @@ func rootWidget(w Widget) RootWidget {
return rw
}

func (wb *WidgetBase) MustRegisterProperties(properties ...*Property) {
for _, prop := range properties {
if prop == nil {
panic("property must not be nil")
}
if wb.name2Property[prop.name] != nil {
panic("property already registered")
}

wb.name2Property[prop.name] = prop
}
}

func (wb *WidgetBase) Property(name string) *Property {
return wb.name2Property[name]
}

func (wb *WidgetBase) hasStyleBits(bits uint) bool {
style := uint(GetWindowLong(wb.hWnd, GWL_STYLE))

Expand Down Expand Up @@ -651,6 +709,8 @@ func (wb *WidgetBase) SetEnabled(value bool) {
wb.enabled = value

EnableWindow(wb.hWnd, wb.widget.Enabled())

wb.enabledProperty.changedEventPublisher.Publish()
}

// Font returns the *Font of the *WidgetBase.
Expand Down Expand Up @@ -823,6 +883,8 @@ func (wb *WidgetBase) SetVisible(visible bool) {
wb.visible = visible

wb.updateParentLayout()

wb.visibleProperty.changedEventPublisher.Publish()
}

// BringToTop moves the *WidgetBase to the top of the keyboard focus order.
Expand Down

0 comments on commit c0c6a73

Please sign in to comment.