locked
Custom Entry Renderer: Changing Property does not effect UI with MVVM RRS feed

  • Question

  • User211159 posted

    Hello guys,

    I have a Custom Entry Renderer: ` using System; using Xamarin.Forms; using Xamarin.Forms.Xaml;

    [assembly: XamlCompilation(XamlCompilationOptions.Compile)] namespace Retailer.CustomControl { public class TextBox : Entry {

        public static readonly BindableProperty BorderVisibilityProperty = BindableProperty.Create("BorderVisibility", typeof(bool), typeof(TextBox), false, BindingMode.TwoWay);
    
        public bool BorderVisibility
        {
            get
            {
                return (bool)GetValue(BorderVisibilityProperty);
            }
            set
            {
                SetValue(BorderVisibilityProperty, value);
            }
        }
    
        public new event EventHandler Completed;
    
        public void SendComplete()
        {
            Completed?.Invoke(this, EventArgs.Empty);
        }
    }
    

    } `

    in a rg.Xamarin.Pupup Page.

    `

    <Grid VerticalOptions="Center" HorizontalOptions="Center">
        <ScrollView>
            <StackLayout VerticalOptions="Center" HorizontalOptions="Center" BackgroundColor="WhiteSmoke" Padding="16" WidthRequest="320">
                <StackLayout>
                    <Label Text="No store information found!" FontSize="Medium" HorizontalOptions="Center" VerticalOptions="Center"/>
                    <Label Text="Please insert this information about your store" FontSize="Body" HorizontalOptions="Center" VerticalOptions="Center"/>
                </StackLayout>
    
                <StackLayout>
                    <Label Text="{Binding CountryTextBlockText}"/>
                    <UserControl:TextBox Text="{Binding CountryTextBoxText}" />
                </StackLayout>
    
                <StackLayout>
                    <Label Text="{Binding StreetTextBlockText}"/>
                    <StackLayout Orientation="Horizontal">
                        <UserControl:TextBox Text="{Binding StreetTextBoxText}" />
                        <UserControl:TextBox Text="{Binding HouseNumberTextBoxText}"/>
                    </StackLayout>
                </StackLayout>
    
                <StackLayout>
                    <Label Text="{Binding PostalCodeTextBlock}"/>
                    <UserControl:TextBox Placeholder="{Binding PostalCodeTextBoxText}" />
                </StackLayout>
    
                <StackLayout Orientation="Horizontal">
                    <Label Text="{Binding MapVisibilityLabel}"/>
                    <Switch IsToggled="{Binding ShowMapToggleToggled}"/>
                </StackLayout>
    
                <StackLayout Orientation="Horizontal">
                    <Label Text="{Binding UseCurrentPositionButton}"/>
                    <Button Text="{Binding UseCurrentPositionButtonText}" Command="{Binding GetCurrentPositionButtonCommand}"/>
                </StackLayout>
    
                <map:Map HeightRequest="240" IsVisible="{Binding ShowMapToggleToggled}"/>
    
                <Button Text="Submit" Command="{Binding SubmitButtonCommand}"/>
            </StackLayout>
        </ScrollView>
    </Grid>
    

    `

    For UWP I created a Custom Renderer:

    ` using Retailer.UWP.Renderer; using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; using Windows.UI; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Media; using Xamarin.Forms; using Xamarin.Forms.Platform.UWP;

    [assembly: ExportRenderer(typeof(Retailer.CustomControl.TextBox), typeof(TextBoxRenderer))] namespace Retailer.UWP.Renderer { public class TextBoxRenderer : EntryRenderer { protected override void OnElementChanged(ElementChangedEventArgs e) { base.OnElementChanged(e);

            if (e.OldElement == null)
            {
                Control.BorderBrush = new SolidColorBrush();
            }
        }
    
        protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
    
            if (((Retailer.CustomControl.TextBox)sender).BorderVisibility == false)
            {
                Control.BorderBrush = new SolidColorBrush();
            }
    
            if(e.PropertyName == nameof(TextBox.TextProperty))
            {
                if(Control.Text != (sender as Retailer.CustomControl.TextBox).Text)
                    Control.Text = (sender as Retailer.CustomControl.TextBox).Text;
            }
    
        }
    
        private void FixFormsBackgroundColor(Xamarin.Forms.Frame frame)
    
        {
    
            var color = new Windows.UI.Color
    
            {
    
                A = Convert.ToByte(frame.BackgroundColor.A * 255),
    
                R = Convert.ToByte(frame.BackgroundColor.R * 255),
    
                G = Convert.ToByte(frame.BackgroundColor.G * 255),
    
                B = Convert.ToByte(frame.BackgroundColor.B * 255)
    
            };
    
    
    
            if (Control.Parent is Panel parent)
    
            {
    
                parent.Background = null;
    
            }
    
    
    
            Control.Background = new SolidColorBrush(color);
    
        }
    }
    

    } `

    For iOS: ` using Retailer.CustomControl; using Retailer.iOS.Renderer; using System.ComponentModel; using UIKit; using Xamarin.Forms; using Xamarin.Forms.Platform.iOS;

    [assembly: ExportRenderer(typeof(TextBox), typeof(TextFieldRenderer))] namespace Retailer.iOS.Renderer { public class TextFieldRenderer : EntryRenderer { protected override void OnElementChanged(ElementChangedEventArgs e) { base.OnElementChanged(e);

            if (Control == null)
            {
                Control.BorderStyle = UITextBorderStyle.None;
                Control.BackgroundColor = UIColor.White;
            }
    
    
        }
        protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            base.OnElementPropertyChanged(sender, e);
    
            if (Control != null && ((Retailer.CustomControl.TextBox)sender).BorderVisibility == false)
            {
                Control.BorderStyle = UITextBorderStyle.None;
            }
            else
            {
                Control.BorderStyle = UITextBorderStyle.Line;
            }
        }
    }
    

    } `

    And Android:

    ` using Android.Content; using Retailer.Droid.Renderer; using System.ComponentModel; using Xamarin.Forms; using Xamarin.Forms.Platform.Android;

    [assembly: ExportRenderer(typeof(Retailer.CustomControl.TextBox), typeof(CustomEntryRenderer))] namespace Retailer.Droid.Renderer { public class CustomEntryRenderer : EntryRenderer { public CustomEntryRenderer(Context context) : base(context) { } protected override void OnElementChanged(ElementChangedEventArgs e) { base.OnElementChanged(e);

            if (Control != null)
            {
                Control.SetBackground(new Android.Graphics.Drawables.ColorDrawable(Android.Graphics.Color.White));
            }
        }
    
        protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            base.OnElementPropertyChanged(sender, e);
    
            if (((Retailer.CustomControl.TextBox)sender).BorderVisibility == false)
            {
    
            }
        }
    }
    

    } `

    Changing the Properties in Constructure in ViewModel works to change the Value of myCustom Renderer TextBox. But I am trying to fill the TextBoxes with Lication Data from Geocoding.

    ` private string countryTextBoxText;

        public string CountryTextBoxText
        {
            get { return countryTextBoxText; }
            set { SetProperty(ref countryTextBoxText, value);
     }
    

    `

    ` private async Task setCurrentPlacemark() { var location = await Geolocation.GetLocationAsync();

            var placemarks = await Geocoding.GetPlacemarksAsync(location);
    
            currentPlacemark = placemarks?.FirstOrDefault();
    
            CountryTextBoxText = currentPlacemark.CountryName;
    
            if (placemarks != null)
            {
                PositionDataDownloaded();
            }
    
        }
    

    `

    But this does not work. Replacing TextBox with Entry works perfectly. I think my Custom renderer does sot support Binding the original Text Property. But Why?

    Hopefully someone can help.

    Tuesday, November 12, 2019 8:11 PM

Answers

  • User369979 posted

    This issue caused by the code below in your custom renderer:

    protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        base.OnElementPropertyChanged(sender, e);
    
        if (((TextBox)sender).BorderVisibility == false)
        {
            Control.BorderBrush = new SolidColorBrush();
        }
    }
    

    You forgot the code: base.OnElementPropertyChanged(sender, e);. When the text property changed it won't notify the parent class to change the appearance if you omitted it. And now it works on UWP:

    • Marked as answer by Anonymous Thursday, June 3, 2021 12:00 AM
    Thursday, November 14, 2019 5:42 AM

All replies

  • User369979 posted

    Have you implemented the INotifyPropertyChanged for your CountryTextBoxText of the view model? And I think this code is unnecessary:

    if(e.PropertyName == nameof(TextBox.TextProperty))
    {
        if(Control.Text != (sender as Retailer.CustomControl.TextBox).Text)
            Control.Text = (sender as Retailer.CustomControl.TextBox).Text;
    }
    

    If you only want to display the text property any subclass inheriting from the Entry can make it. And we don't need to add extra code in the renderer for the displaying.

    Wednesday, November 13, 2019 7:40 AM
  • User211159 posted

    Thanks this was just a Text. I forgot to delete this. Yes I Implemented INotifyPropertyChanged. It works when I set the poroperty in constructure of my ViewModel ut later when I get Locattion and want to fill it with Grocoding (Country Name, StreetName...) this does not work anymoer. I am Using XF 4.3

    Wednesday, November 13, 2019 9:06 AM
  • User369979 posted

    What platform did it occur on? If you comment out the custom renderer will it work? I created a blank project to test it, however, the text could be displayed correctly. We need your sample to help me reproduce this issue.

    Wednesday, November 13, 2019 9:22 AM
  • User211159 posted

    @LandLu said: What platform did it occur on? If you comment out the custom renderer will it work? I created a blank project to test it, however, the text could be displayed correctly. We need your sample to help me reproduce this issue.

    On All Platforms.

    It will Work if I use the official Entry. What do you mean with comment the renderer out? The Whole classes? I will create a project to test.

    Wednesday, November 13, 2019 4:23 PM
  • User389719 posted

    @Agredo
    This is interesting, but its too hard to follow so much code posted on a Forum Please send a support ticket and one of the Xamarin Support guys will be more then happy to help you out

    Microsoft Support link. Free support for all Xamarin Developers https://support.microsoft.com/en-us/supportforbusiness/productselection?sapId=211dd84f-3474-c3c5-79bf-66db630c92a6!

    Wednesday, November 13, 2019 8:26 PM
  • User211159 posted

    @LandLu said: What platform did it occur on? If you comment out the custom renderer will it work? I created a blank project to test it, however, the text could be displayed correctly. We need your sample to help me reproduce this issue.

    @LandLu @JoeHarvey_MSFT I uploaded a sample App. Currently this only works (I tested) on UWP because Xamarin.Essentials need some Information for iOS and Android.

    The Button to show the popup can be found on the ItemsPage.xaml.

    The UWP App just need a Key, I deleted because I dont want to make it public here,

    Thank you :)

    Wednesday, November 13, 2019 11:28 PM
  • User369979 posted

    This issue caused by the code below in your custom renderer:

    protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        base.OnElementPropertyChanged(sender, e);
    
        if (((TextBox)sender).BorderVisibility == false)
        {
            Control.BorderBrush = new SolidColorBrush();
        }
    }
    

    You forgot the code: base.OnElementPropertyChanged(sender, e);. When the text property changed it won't notify the parent class to change the appearance if you omitted it. And now it works on UWP:

    • Marked as answer by Anonymous Thursday, June 3, 2021 12:00 AM
    Thursday, November 14, 2019 5:42 AM
  • User211159 posted

    @JoeHarvey_MSFT @LandLu Thank you for helping me! I forgot base.OnElementPropertyChanged(sender, e);

    Thursday, November 14, 2019 1:01 PM