Input controls property: IsRequired

KenNickersonKenNickerson USMember ✭✭✭

Any chance of getting another property added to input controls? It would be useful to include an IsRequired property on entry and picker controls to reduce some validation logic. Created a custom bindable property per below:

        public class EntryCustomBinding : Entry
        {
            public static readonly BindableProperty IsRequiredProperty = BindableProperty.Create(
                nameof(IsRequired),
                typeof(bool),
                typeof(EntryCustomBinding),
                false);

            public bool IsRequired
            {
                get
                {
                    return IsRequired = (bool)GetValue(IsRequiredProperty);
                }
                set { SetValue(IsRequiredProperty, value); }
            }
        }
Tagged:

Best Answer

Answers

  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭
    edited August 2017

    Maybe its just me.. .But that looks... funny.

    Your class is called 'binding' but inherits from Entry so it isn't a binding... Its UI control. Right?

    An Entry isn't data. If you try to impose IsRequired in the UI instead of in the data then you have a huge hole for potentially not validating the data. You're only validating it at the user input level. And not when it comes in from local database... when the app is restore after restarting... data comes from a server...

    I would think that between interfaces, attached properties, extension methods, and behaviors... there has to be a better way to do that with an existing mechanism.

    Later thoughts... Other ways to enforce that on the UI level.
    1. Put a DataTrigger on the Entry so its background is red when the binded value is null.
    2. There's probably a {Submit} button on the form, right? It shouldn't be enabled until all the required fields are filled with valid data. You would have the Command.CanExecute binded to a method that validates the data. Again, the ViewModel does the work with the data like it should, and the UI updates automagically when the data is validated.

  • KenNickersonKenNickerson USMember ✭✭✭
    edited August 2017

    @ClintStLaurent

    You are right. I see that, however, my intent was to indicate the data is required, not the control itself. Probably a different property name such as TextIsRequired would have gotten the point across that the example provided. My need was only on the entry side but I will reword this, unless there is something out of the box.

    thanks

  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭

    Well... Like I said... I'd probably put a DataTrigger on the entry.
    If the binded value is null... make the background red and the border yellow... or make the size larger... or something like that to make the Entry stand out on the view.

    You could also just set the Placeholder text to something like "REQUIRED"

  • KenNickersonKenNickerson USMember ✭✭✭

    I get that. Indicating it is required isn't the issue. I am getting data from the server which is driving which fields are required for a given transaction. If it was a matter of a couple of fields that were always required, I would take that approach, but if the requirement of an entry changes, I can't rely on the hard coded validation or adjust the app easily, without going through the JSON on save or OnChanged.

    Thanks for your input

  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭

    but if the requirement of an entry changes,

    Ah... But an Entry isn't required. Data is. So if the data object has an IsRequired property on it, then your UI has a property to bind to and react to.

  • KenNickersonKenNickerson USMember ✭✭✭
    edited August 2017

    Correct. It is the data related to the control that may or may not be required. So I will try to make the custom Entry work with the IsRequired property.

    thanks

  • KenNickersonKenNickerson USMember ✭✭✭

    Clint,

    Thanks for your help although I feel I am still missing something, just not sure what.

  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭

    Been there. Its Friday. Probably missing caffeine.

    When you think of what it is jump back on this thread and we can pick up where we left off.

  • KenNickersonKenNickerson USMember ✭✭✭

    Do I still need to create an Custom control to include IsRequired property? Otherwise, how do you see the code snippet being used? I will step away and revisit.

    thanks again!

  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭

    No custom control.
    The data comes in and has a property of IsRequired.

    A regular Entry has a Trigger to change the background color if IsRequired is true.
    I'll work up an example over the weekend. Right now I'm still at the office.

  • KenNickersonKenNickerson USMember ✭✭✭

    Thanks Clint. Have a great weekend!

  • KenNickersonKenNickerson USMember ✭✭✭

    Clint

    Went back to the following and appears to be working for what I need:

            public class CustomEntry : Entry
            {
                public static readonly BindableProperty IsRequiredProperty = BindableProperty.Create(
                    nameof(IsRequired),
                    typeof(bool),
                    typeof(CustomEntry),
                    false);
    
                public bool IsRequired
                {
                    get
                    {
                        return IsRequired;
                    }
                    set { SetValue(IsRequiredProperty, value); }
                }
            }
    
  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭

    That's awesome. I got about 90% done with a sample that includes some XAML binding and data triggers - but doesn't require a custom Entry-derived control class.
    Then got side tracked with my wife's "honey-do list". and a temperamental 3d printer.
    I should have it finished in another day or two. Sorry for the delay

  • KenNickersonKenNickerson USMember ✭✭✭

    Clint. I appreciate your taking time to help me. I am anxious to see what you come up with, because I am sure it will be a learning experience for me. Take you time and when it's done, I will review and integrate.

    thanks again!

  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭
    edited August 2017

    Ok. Here goes nothing.

    Intent

    The property on the model is what is required. Not that an Entry is required. ViewModels don't even know if there is a UI associated with them. Or there could be 10 UI all using the same ViewModel as their BindingContext. Plus we may want to have our logic know if a property IsRequired or not so it can make choices. For example: Don't upload the object if a required property is still null.

    UI Goal

    So we want the Entry binded to the required property to highlight in red if there is no value.
    We'll highlight it in green when we have a good value.
    And as a perk, we'll highlight it in yellow when the Entry has focus (is the selected UI element)
    And we're going to do all of this UI work in the XAML.

    The Data

    This is all based on the idea that a property can be marked as IsRequired. Of course it also helps if the property has a name and a value otherwise it doesn't do us much good. So we are going to make an object. I called it EnforceableModel because its a model that we can enforce. I gave it the following properties.

    Name, Value and IsRequired we already talked about. HasValue is for binding convenience because I'm lazy.

        public class EnforceableModel : ModelBase
        {
            #region Name (string)
            private string _Name;
            public string Name
            {
                [DebuggerStepThrough]
                get
                {
                    //if (_Name == null) Name = new string();
                    return _Name;
                }
    
                [DebuggerStepThrough]
                set
                {
                    if (_Name == value) return;
                    OnPropertyChanging(() => Name);
                    _Name = value;
                    OnPropertyChanged(() => Name);
                }
            }
            #endregion Name  (string)
    
            #region Value (object)
            private object _Value = null;
            public object Value
            {
                [DebuggerStepThrough]
                get
                {
                    //if (_Value == null) Value = new object();
                    return _Value;
                }
    
                [DebuggerStepThrough]
                set
                {
                    if (_Value == value) return;
                    OnPropertyChanging(() => Value);
                    _Value = value;
                    OnPropertyChanged(() => Value);
                    OnPropertyChanged(() => HasValue);
                }
            }
            #endregion Value  (object)
    
            #region IsRequired (bool)
            private bool _IsRequired;
            public bool IsRequired
            {
                [DebuggerStepThrough]
                get
                {
                    //if (_IsRequired == null) IsRequired = new bool();
                    return _IsRequired;
                }
    
                [DebuggerStepThrough]
                set
                {
                    if (_IsRequired == value) return;
                    OnPropertyChanging(() => IsRequired);
                    _IsRequired = value;
                    OnPropertyChanged(() => IsRequired);
                    //UpdateDynamicSetting(()=> IsRequired, value);
                }
            }
            #endregion IsRequired  (bool)
    
            #region HasValue (bool)
            private bool _HasValue;
    
            /// <summary>Convenient for binding</summary>
            [XmlIgnore]
            public bool HasValue
            {
                get
                {
                    if (Value == null) return false;
                    if (Value is string && string.IsNullOrEmpty(Value.ToString())) return false;
                    return true;
                }
    
            }
            #endregion HasValue  (bool)
        }
    

    ViewModel

    So that's the model for any property we want to make enforceable as being required. Since the Value is an object you can use this for a string, an int, or a complex model like a Vehicle.

    Your view is binded to some ViewModel. I don't what, but you should be able to adapt this. For this example, I just added a property to the ViewModel backing my MainPage

            #region SomeRequiredProperty (EnforceableModel)
            private EnforceableModel _SomeRequiredProperty = new EnforceableModel()
            {
                Name = "Customer",
                IsRequired = true
            };
            public EnforceableModel SomeRequiredProperty
            {
                [DebuggerStepThrough]
                get
                {
                    //if (_SomeRequiredProperty == null) SomeRequiredProperty = new EnforceableModel();
                    return _SomeRequiredProperty;
                }
    
                [DebuggerStepThrough]
                set
                {
                    if (_SomeRequiredProperty == value) return;
                    OnPropertyChanging(() => SomeRequiredProperty);
                    _SomeRequiredProperty = value;
                    OnPropertyChanged(() => SomeRequiredProperty);
                    //UpdateDynamicSetting(()=> SomeRequiredProperty, value);
                }
            }
            #endregion SomeRequiredProperty  (EnforceableModel)
    

    The MainPage UI XAML

    Now that we have all the grunt work done, we can do the fun part - the UI.
    I tested this in a project that had some styles already defined.
    If you want to check that out its at http://RedPillXamarin.com
    But basically we're just showing a StackLayout with a Label and an Entry. A regular not custom, not derived Entry.
    We've then added a few Triggers to make it change its look when various properties change.

    So let's talk about it.
    Label on line 44 - Notice that we didn't hard code it to say "Customer". We binded that to the model-property as well since it had a name property on it. I'm lazy. We didn't have to do it; I just felt like it. So the Label automatically shows the name of the property. If the data object changes 3 months from now I don't have to scrub through all the markup in the project to update the UI - It fixes itself.

    Trigger on line 49 - When the Entry.IsFocused becomes true the background color is changed to yellow.
    Trigger on line 52 - When the binded property SomeRequiredProperty.HasValue is true the background is changed to green
    Trigger on line 57 - When the binded property SomeRequiredProperty.HasValue is false the background is changed to red
    Trigger on line 62 - When the binded property SomeRequiredProperty.IsRequired is true we set the Placeholder to "Required"

    And that's how we get the UI to do all those nifty changes based on properties. The UI takes care of the UI, the data knows only data. They don't try to do each other's jobs. And we don't have to make a custom control in this case.

    Now... If we don't want to have to do all this trigger stuff 20 times on a page... Put it all in a Style. Then assign the Style to the Entry. That way you have all the behavior rules in one place. Later when the boss says: "I hate that red. Make them all orange" you only have to change it in one place and compile. 10 minutes, take a long lunch. Tell the boss it was murder to do. If you're not familiar with doing styles, that's in article 203 of that Red Pill Xamarin blog I linked above.

  • KenNickersonKenNickerson USMember ✭✭✭

    Clint

    Very cool. I will study this to make sure I get it all, 100%.

    Thanks for the lesson!

  • KenNickersonKenNickerson USMember ✭✭✭

    The challenge I still see, is I don't know if the value is required when I created the control in XAML. It is determined on a transaction by transaction basis. I image you should be able to set the binding on the fly but I need to wrap my head around it first.

  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭

    That's fine. Better even. You must have already been planning on using your custom Entry on every Entry because you didn't know if it was needed or not. Right?

    Instead, you can use a standard Entry everyplace - use the Style that checks to see if the value IsRequire on every Entry. If its not required then nothing will look different.

  • KenNickersonKenNickerson USMember ✭✭✭

    I knew it was covered but still digesting. thanks again

Sign In or Register to comment.