3

I created hierarchy of controls for my project: abstract BaseSettingsElement and inherited EntrySettingsElement, PickerSettingsElement, SwitchSettingsElement etc. The base class provide the properties to change text, color, font of header/subheader. Example of property declaration:

public static readonly BindableProperty HeadTextColorProperty =
            BindableProperty.Create("HeadTextColor", typeof(Color), typeof(BaseSettingsElement), Color.FromHex("#ffffff"),
                propertyChanged: (bindable, oldValue, newValue) =>
                {
                    (bindable as BaseSettingsElement).headText.TextColor = (Color)newValue;
                });

//...
public Color HeadTextColor
        {
            get { return (Color)GetValue(HeadTextColorProperty); }
            set { SetValue(HeadTextColorProperty, value); }
        }

There is no problem when I'm creating this controls in xaml and apply some properties to them:

        <custom:EntrySettingsElement Grid.Row="16"
                                     InputType="Number"
                                     DividersVisibility="None"
                                     IsEnabled="False"
                                     HeadTextColor="Red"
                                     Text="0" />

But when I'm trying to apply a global style in app.xaml to some of my controls I have a NullRefferenceException here:

public static readonly BindableProperty HeadTextColorProperty =
        BindableProperty.Create("HeadTextColor", typeof(Color), typeof(BaseSettingsElement), Color.FromHex("#ffffff"),
            propertyChanged: (bindable, oldValue, newValue) =>
            {
                (bindable as BaseSettingsElement).headText.TextColor = (Color)newValue;
                //Here is a NullReferenceException: bindable object is not null but the field 'headText' is null...
            });

Just in case the xaml of the base control:

<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms" 
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="RemoteControlApp.CustomViews.BaseSettingsElement"
             xmlns:custom="clr-namespace:RemoteControlApp.CustomViews;assembly=RemoteControlApp">
  <ContentView.Content>
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="Auto" />
            </Grid.ColumnDefinitions>

            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="*" />
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>

            <custom:Divider x:Name="topDivider"
                            Grid.Row="0"
                            Grid.ColumnSpan="2"
                            BackgroundColor="{StaticResource grayColor}" />

            <StackLayout x:Name="container"
                         Orientation="Vertical"
                         Spacing="0"
                         Grid.Column="0"
                         Margin="15,12,0,12"
                         Grid.Row="1"
                         VerticalOptions="Center">
                <Label x:Name="headText" />
                <Label x:Name="bodyText" />
            </StackLayout>

            <custom:Divider x:Name="bottomDivider"
                            Grid.Row="2"
                            Grid.ColumnSpan="2"
                            BackgroundColor="{StaticResource grayColor}" />

            <ContentView x:Name="asideContainer"
                         Margin="0,0,15,0"
                         Grid.Column="1"
                         Grid.Row="1" />
        </Grid>
    </ContentView.Content>
</ContentView>
Alexander Hryk
  • 585
  • 2
  • 4
  • 16
  • Possible duplicate of [What is a NullReferenceException, and how do I fix it?](https://stackoverflow.com/questions/4660142/what-is-a-nullreferenceexception-and-how-do-i-fix-it) – Dmitry Sep 10 '17 at 15:16
  • I know what is 'NullReferenceException' I don't understand WHY in this stage private fields of the base control are not initialized. – Alexander Hryk Sep 10 '17 at 15:22

1 Answers1

4

A quick look at compiler generated files (.xaml.g.cs) - you will notice that these private fields are assigned in InitializeComponent method:

[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Forms.Build.Tasks.XamlG", "0.0.0.0")]
private void InitializeComponent()
{
    global::Xamarin.Forms.Xaml.Extensions.LoadFromXaml(this, typeof(BaseSettingsElement));
    headText = global::Xamarin.Forms.NameScopeExtensions.FindByName<global::Xamarin.Forms.Label>(this, "headText");
}

Therefore, this seems to be a problem caused due to the order in which InitializeComponent and PropertyChangedHandler are called.

In first case, when property is explicitly set, this is the order the methods are called.

InitializeComponent -Start
InitializeComponent -End
HeadTextColor -PropertyChanged

While, in second case, while using global style to set properties, the order is:

HeadTextColor -PropertyChanged
InitializeComponent -Start
InitializeComponent -End

Looks like the property is set somewhere in a base class constructor. So the NullReferenceException is expected.

There are two ways to get around it.

Solution-1 (recommended)

Instead of using property-changed handler, use Binding to set properties on inner-child controls, with parent node as Source. For e.g:

<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms" 
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="RemoteControlApp.CustomViews.BaseSettingsElement"
             xmlns:custom="clr-namespace:RemoteControlApp.CustomViews;assembly=RemoteControlApp"
             x:Name="_parent">
  <ContentView.Content>
        <!-- .... -->
        <Label TextColor="{Binding HeadTextColor, Source={x:Reference _parent}}" />

Solution-2

Or, set inner control's properties after InitializeComponent.

public MyView()
{
    Debug.WriteLine("InitializeComponent -Start");
    InitializeComponent();
    Debug.WriteLine("InitializeComponent -End");

    //InitializeControl();
    this.headText.TextColor = HeadTextColor;
}

Note: In second solution, you will still need the property-changed handler. Otherwise, explicitly setting properties in XAML will not propagate the change to inner controls. I guess a null check is in order for that.

Sharada Gururaj
  • 13,471
  • 1
  • 22
  • 50
  • Actually, I had a very similar problem by only introducing a new custom control and used it in a listview item. The custom control was just a frame with a label that displayed some text. When using the "name the label and set value in protected override OnPropertyChanged" I got the same result as when using your "x:Reference _parent" approach => a NullReferenceException. The only way I could solve this issue was be disabling Xaml compilation for this new custom control... very annoying – Alexander Marek Oct 06 '18 at 14:42