Databinding sucks

Posts   
 
    
Posts: 93
Joined: 13-Feb-2008
# Posted on: 28-Feb-2008 20:51:28   

I have a form with 2 text boxes which are bound to 2 distinct string properties of an entity. In my validator class I return false and set an error on the the property if the string is null or empty.

So if you type a valid value into the first text box, then tab to the next one and do the same, finally return to the first textbox and delete the value. The validator is called, the error is set and voila my little blinky error shows up. Thats great, I'm feeling warm and fuzzy. So now tab out of the text box with the error to the next textbox. As soon as you type anything into the second text box the old value is inserted into the first textbox. I'm not so warm and fuzzy anymore. If the error would have gone away I could accept this behavior, but it doesn't. The binding source has decided that its a grand idea to rebind all the controls instead of just the one with the focus.

I understand where the old value is coming from but to be explicit it is coming from the fact that i didn't allow the property to be set to null or empty so the "blank" value never made it to the entity property. I agree 100% with this behavior. And I understand that since as far as the entity is concerned nothing has changed and no event is fired when the binding source calls the getter anyway and thus blinky error sticks around, but why does the binding source have to refresh every control? I've looked high and low for way to intercept this and only allow the binding to refresh the control that has focus..no luck.

After scouring ~10 pages of posts on this forum, I get the feeling that databinding is a POS. I don't want to force the user to make the text valid before they can move on to the other controls on the form. I want unobtrusive field validation because I can always keep the save button disabled until the entire entity is valid.

Ok, so this is more a rant than a post, but if anyone has anything to add I'd love to hear it. Misery loves company.

Walaa avatar
Walaa
Support Team
Posts: 14950
Joined: 21-Aug-2005
# Posted on: 29-Feb-2008 08:44:05   

One workaround you may try is to return true from the ValidateEntityField if the field is invalid(accept the invalid value) but use the SetEntityError and SetEntityFieldError, to report for errors, and then check them out in the button click event to disable submitting.

Another thing you can do is to postpone the validation till the button is pressed. i.e not to use FieldValidation and rather use EntityValidation. So you can call the Implement ValidateEntity and call myEntity.ValidateEntity() on the button click before submitting. Or just implement ValidateEntityBeforeSave if you are saving to the database.

Posts: 93
Joined: 13-Feb-2008
# Posted on: 29-Feb-2008 15:02:38   

If you set a field error and return true from the validate field method, the error is cleared out by EntityBase2. I tried that already and tracked it to this snippet.


                        if(valueIsSet)
            {
                // audit set
                OnAuditEntityFieldSet(fieldIndex, originalValue);
                // signal change
                OnFieldValueChanged(originalValue, fieldToSet);
                if(fieldToSet.IsForeignKey && performDesyncForFKFields)
                {
                    PerformDesyncSetupFKFieldChange(fieldIndex);
                }
                PostFieldValueSetAction(valueIsSet, fieldToSet.Name);
                // clear an error message if applicable
                SetEntityFieldError(fieldToSet.Name, string.Empty, false);
            }

I like the validation model because if the value is not valid the entity will never have the value. Unfortunately, in order to get the UI behavior that is being requested I may have to circumvent this model and use field validation as a advice instead of the law, and push the law onto validate entity. I would have to recompile the runtime libraries


SetEntityFieldError(fieldToSet.Name, string.Empty, false);

to get this. A side effect of this is that if the value is actually valid i will have to clear out the error myself in the validator class.

Another question: I need to alter the templates to insert the DI info into the validator classes. I think the master validator template could add the DI attribute by defaullt. I can't think of any reason that would cause trouble, since unless you set up the DI in the app.config the attribute will be ignored.

I'll play around with commenting out that line and see if I can get the behavior I'm aiming for. Thanks for the comment, I felt bad about submitting a "rantish" post...Windows Forms just irritates me. simple_smile

Walaa avatar
Walaa
Support Team
Posts: 14950
Joined: 21-Aug-2005
# Posted on: 29-Feb-2008 15:19:06   

In fact we liked your post and its topic, most of us find databinding as bad as you did, whether it's windows databinding or ASP.NET databinding. It might be a time saver sometimes but a lot of time you find yourself cornered by strange design issues.

Posts: 93
Joined: 13-Feb-2008
# Posted on: 29-Feb-2008 16:39:47   

If you comment out the clearing behavior in EntityBase2 (see previous post) and set up the validator class as shown below you can achieve the result that I was whining about in the original post. With this you can leave a text box that has a validation error and avoid the counter-intuitive UI behavior where the text box magically simple_smile retrieves the old value on the next automatic binding refresh but the blinky error remains visible. Just remember that your entity now has the ability to contain "invalid" values and you must remember to wire up your save button to the ValidateEntity method AND override onbeforesave to call ValidateEntity as well. I think I'll also include an IsValid property on the EntityBase2 like below so I can SaveButton.Enabled = Entity.IsValid in the BindingComplete event of the BindingSource.


public bool IsValid
        {
            get
            {
                try
                {
                    if (Validator != null)
                    {
                        Validator.ValidateEntity(this);                     
                    }
                    else
                    {
                        ValidateEntity();
                    }

                    return true;
                }
                catch
                {
                    return false;
                }
            }
        }


//The Warning
public override bool ValidateFieldValue(IEntityCore involvedEntity, int fieldIndex, object value)
        {
            string message = null;

            switch ((EntityFieldIndex)fieldIndex)
            {
                case EntityFieldIndex.Field:
                    if (!ValidateField(value))
                    {
                        message = "Invalid Field";
                    }
                    break;

                 //other field cases
                
                default:
                    break;
            }

            //set the error every time, because we commented out the clearing behavior
            // in EntityBase2
            involvedEntity.SetEntityFieldError(((IEntity2)involvedEntity).Fields[fieldIndex].Name,
                message, false);

            [b]return true;[/b]
        }

//The Error
public override void ValidateEntity(IEntityCore involvedEntity)
        {
            bool isValid = true;

            //spin through the fields setting all errors
            foreach (IEntityField2 field in ((IEntity2)involvedEntity).Fields)
            {
                switch ((EntityFieldIndex)field.FieldIndex)
                {
                    case EntityFieldIndex.Field:
                        if (!ValidateField(field.CurrentValue))
                        {
                            isValid = false;
                        }
                        break;

                       //other field cases

                    default:break;
                }
            }

            //if any field was not valid, throw the exception
            if (!isValid)
            {
                throw new ORMEntityValidationException("Entity is not valid", involvedEntity);
            }
        }

GizmoTN76
User
Posts: 36
Joined: 22-Oct-2007
# Posted on: 01-Mar-2008 18:16:37   

I've been through most of the same issues... One thing I'm curious about though is why you're getting a resetbindings event between controls.

The behavior I ran into was that if I was on a given bound entity (through a bindingsource) as long as I stayed on the current record I could get it to put the error provider next to the field upon an OnValidate event even though the underlying entity was still in a good state. Where I ran into issues was that upon current change of the bindingsource it would reread in the bindings and I would have the original values back with the error providers popped.

To work around that I ended up wiring up a similar call to yours on current change that would go back through and revalidate the current state of all the fields, it's still sort of weird behavior because the original values will return if you move to a different record and return but at least it didn't have incorrect errors popped for valid values.

I did however have to do all sorts of interesting hacks in a partialled out CommonEntityBase to handle null values etc because the validation logic wouldn't even get hit if the value passed in was null so there was no place to set the errors. I ended up converting all passed in null sets of string fields to string.empty and that was able to get around that issue.

Now you have me wondering if there's some edge case I didn't consider where the reset bindings can occur within a given bound entity and I can run into the same scenario you did simple_smile . Out of curiousity are your bindings based off OnValidate or OnPropertyChange?

I think SD needs to come out with a nice winforms gui framework built on top of llblgen where all these interesting issues have already been resolved for us smile . Auto form generation really isn't practical as it takes too much control away from the devs but it's totally possible to create base editor forms that know how to wire up all the validation save, undo, redo etc. and to generate a nice proxy remoting framework that mates with that gui to encapsulate all the calls off to some type of app server which isolates your db connections. Seems like every product I work on involves the mandatory "Ok we need to spend the first couple of months writing a framework before we can touch the business logic."

I know that csla.net was aimed at that purpose (if you really liked typing) and jcl.net tried to bridge the gap between csla and llblgen covering alot of those areas as well but it would be nice to get a supported and documented gui platform from the brilliant minds that brought us llblgen so we could get back to writing business logic instead of playing with databinding for long hours simple_smile .

Posts: 93
Joined: 13-Feb-2008
# Posted on: 01-Mar-2008 19:40:40   

Bindings are set to update OnPropertyChange. I have no idea why the binding source refreshes every control bound to the entity when one of the controls is updated. If anyone can shed some light on that I'd like to know. The solution I've outlined here works well for my situation. It's not counter-intuitive to have to take full control of the error on each field in the validator. It would be nice to set a property or configuration that determines whether the entity clears the error for you or not...then I wouldn't have to maintain a custom build of the ORM library simple_smile

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 03-Mar-2008 10:27:25   

I fully understand the sentiments, if you search this forum for databinding you'll likely find some of my posts here where I ran into weird issues I couldn't explain.

We won't release a UI framework, because we believe that we can better become a great data-access framework which can be used with any UI framework/setup than a 'good' data-access framework which is actually only usable with its own ui framework. I personally also want to stay away from UI nightmares, especially because there are so many ways to create a UI and if MS releases a new UI framework next year, everything falls apart and that new framework has to be supported as well.

Frans Bouma | Lead developer LLBLGen Pro
Rosacek
User
Posts: 155
Joined: 18-Mar-2012
# Posted on: 28-Apr-2014 18:43:45   

I was catched by the same trap disappointed ValidateEntityField returned False and GUI control was not reflecting rejecting new value... Really databinding sucks rage

I would be nice if you could mention in DOC "Entity and Field Validation" that ValidateEntityField has some issues and better to avoid using ValidateEntityField in most cases, while better is use GUI validation and ValidateEntityBeforeSave as the last chance to verify data.

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 29-Apr-2014 06:03:00   

The problem is in the control. You can overcome that with some tricks. We have an usable databinding example in the Examples section on the main site.

If you need further help, you can open a new thread with the specifics.

David Elizondo | LLBLGen Support Team
Rosacek
User
Posts: 155
Joined: 18-Mar-2012
# Posted on: 01-May-2014 11:10:10   

I checked the examples, especially Validation Example SelfServicing There is OrderValidator

1/ Why do you clean error info in ValidateFieldValue? I think this was fixed in 2008 and we do not need to clean it anymore, right? If I check the latest DOC, also there is not cleaning code, so I expect it was really fixed. Also see http://www.llblgen.com/tinyforum/Messages.aspx?ThreadID=14970

2/ I went thru CustomerEntity partial class There you have overriden OnSetValue as a workaround for IDataError info flaw. Ok.

I think this this is not solution to my problem.

For example: I have textboxes txtSurname and txtName databound to entity fields .Surname and .Name I validate them by PersonValidator

Case PersonFieldIndex.PersonSurname
     If String.IsNullOrWhiteSpace(CStr(value)) Then
          involvedEntity.SetEntityFieldError(involvedEntity.Fields(fieldIndex).Name, "Surname cannot be empty", False)
          toReturn = False
End If

Now I delete text from txtSurname and move to txtName. At that time your workaround: OnSetValue is fired

Protected Overrides Sub OnSetValue(fieldIndex As Integer, valueToSet As Object, ByRef cancel As Boolean)
          If Fields(fieldIndex).CurrentValue IsNot Nothing Then
               If Fields(fieldIndex).CurrentValue.Equals(valueToSet) AndAlso Not String.IsNullOrEmpty(DirectCast(Me, System.ComponentModel.IDataErrorInfo)(Fields(fieldIndex).Name)) Then
                    SetEntityFieldError(Fields(fieldIndex).Name, String.Empty, False)
               End If
          End If

          MyBase.OnSetValue(fieldIndex, valueToSet, cancel)
End Sub

As there is no IDateError yet, it just went thru skipping SetEntityFieldError... line

After that ValidateFieldValue is fired, seting SetEntityFieldError and returning FALSE

Case PersonFieldIndex.PersonSurname
     If String.IsNullOrWhiteSpace(CStr(value)) Then
          involvedEntity.SetEntityFieldError(involvedEntity.Fields(fieldIndex).Name, "Surname cannot be empty", False)
          toReturn = False
End If

And then I can see in GUI empty txtSurname with error icon. This is strange, because entity.Surname has still original value, because validator did not allow to change it. So GUI displays bad information. Entity data are different for what I see on screen.

Well next I move to txtName and change it to anything else. After that some kind of binding reset is done behind the scene and txtSurname shows correct value from entity.Surname again, while error icon is still there.

You see your workaround did not solve this. In fact I think you solve something what was solved in 2008, see 1/

I think the only workaround is to check all validated fields everytime when any field value is changed, either in OnSetValue or in PersonValidator. This would set properly fields IDateError.

On the other hand there is still the problem that GUI show empty txtSurname even validator did not allow to change it to String.Empty and entity.Surname equal to original surname value. Maybe after ValidateFieldValue should be fired reset binding, which will set GUI txtSurname automatically to original value if ValidateFieldValue return FALSE

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 05-May-2014 07:49:16   

Hi Rosacek,

Do you have a working example that reproduce what you said? Are you using ASP.Net, MVC, Winforms, etc? What I recall from that example is that some controls don't set the value on time. I will check whether the validation of the example code is still valid. However, from what you say, I think the problem is that the control is not in sync with the data inside the entity.

David Elizondo | LLBLGen Support Team
Rosacek
User
Posts: 155
Joined: 18-Mar-2012
# Posted on: 05-May-2014 10:38:30   

Hi, I build WinForms.

Do you mean that if ValidateFieldValue returns FALSE then entity is not updated (of course) **and also control is not updated **(is reset to original value)?

It seems to me, that control is updated to the new value even ValidateFieldValue returns FALSE. Then the control is not in sync with bound entity field until I call bindingsource.ResetCurrentItem or .ResetBindings or I change data in any other bound control. Then the control shows original entity field value.

If this is not how it should behave, then I could try to prepare some sample app.

EDIT: I think I found the explanation in post from jeroen1980 http://www.llblgen.com/TinyForum/Messages.aspx?ThreadID=12938

It matters if formattingEnabled in databinding constructor is TRUE or FALSE VS designer set formattingEnabled to TRUE, then even ValidateFieldValue return FALSE, control accept such invalid value. If I change formattingEnabled = FALSE, then control resets to original value if ValidateFieldValue return FALSE

Me.TextBox2.DataBindings.Add(New System.Windows.Forms.Binding("Text", Me.ManagedEntityBindingSource, "PersonName", False)) vs Me.TextBox2.DataBindings.Add(New System.Windows.Forms.Binding("Text", Me.ManagedEntityBindingSource, "PersonName", True))

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 05-May-2014 15:09:23   

correct. You have to do that manual labor to make it work, unfortunately disappointed

Frans Bouma | Lead developer LLBLGen Pro
hemang
User
Posts: 4
Joined: 13-Nov-2014
# Posted on: 25-Nov-2014 17:40:16   

Hello,

Facing same problem you faced. As you given the solution to set the property formattingEnabled = false.

But this property is not available for textbox (Windows control)

Rosacek wrote:

Hi, I build WinForms.

Do you mean that if ValidateFieldValue returns FALSE then entity is not updated (of course) **and also control is not updated **(is reset to original value)?

It seems to me, that control is updated to the new value even ValidateFieldValue returns FALSE. Then the control is not in sync with bound entity field until I call bindingsource.ResetCurrentItem or .ResetBindings or I change data in any other bound control. Then the control shows original entity field value.

If this is not how it should behave, then I could try to prepare some sample app.

EDIT: I think I found the explanation in post from jeroen1980 http://www.llblgen.com/TinyForum/Messages.aspx?ThreadID=12938

It matters if formattingEnabled in databinding constructor is TRUE or FALSE VS designer set formattingEnabled to TRUE, then even ValidateFieldValue return FALSE, control accept such invalid value. If I change formattingEnabled = FALSE, then control resets to original value if ValidateFieldValue return FALSE

Me.TextBox2.DataBindings.Add(New System.Windows.Forms.Binding("Text", Me.ManagedEntityBindingSource, "PersonName", False)) vs Me.TextBox2.DataBindings.Add(New System.Windows.Forms.Binding("Text", Me.ManagedEntityBindingSource, "PersonName", True))

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 26-Nov-2014 07:42:11   

hemang wrote:

Hello,

Facing same problem you faced. As you given the solution to set the property formattingEnabled = false.

But this property is not available for textbox (Windows control)

I think that it's not a textbox's property, but a property of the databinding information set on that textbox.

myTextbox.DataBindings.Add(new System.Windows.Forms.Binding("Text", myBindingSource, "PersonName", false));
David Elizondo | LLBLGen Support Team
hemang
User
Posts: 4
Joined: 13-Nov-2014
# Posted on: 26-Nov-2014 11:06:54   

Can we do in .xaml?

 <TextBox x:Name="textbox_VesselName"
                 Grid.Row="0"
                 Grid.Column="1"
                 Margin="5"
                 Width="250"
                 Text="{Binding VesselName, Mode=TwoWay}"></TextBox>
daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 27-Nov-2014 08:55:37   

This thread didn't address any WPF scenario. So please give us the full information we need to reproduce your situation and give you the best advice we can ( http://www.llblgen.com/TinyForum/Messages.aspx?ThreadID=7720 ). If possible, open a new thread, to start a fresh conversation.

David Elizondo | LLBLGen Support Team