c# - 当我尝试将全局样式应用于 Xamarin Forms 中的自定义 ContentView 时出现 NullReferenceException

标签 c# xaml xamarin.forms

我为我的项目创建了控件层次结构:抽象 BaseSettingsElement 并继承了 EntrySettingsElementPickerSettingsElementSwitchSettingsElement 等. 基类提供更改标题/副标题的文本、颜色、字体的属性。属性声明示例:

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); }
        }

当我在 xaml 中创建这个控件并向它们应用一些属性时没有问题:

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

但是当我尝试将 app.xaml 中的全局样式应用于我的某些控件时,我在此处遇到了 NullRefferenceException:

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...
            });

以防万一基本控件的xaml:

<?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>

最佳答案

快速查看编译器生成的文件 (.xaml.g.cs) - 您会注意到这些私有(private)字段是在 InitializeComponent 方法中分配的:

[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");
}

因此,这似乎是由于InitializeComponentPropertyChangedHandler的调用顺序导致的问题。

在第一种情况下,当显式设置属性时,这是调用方法的顺序。

InitializeComponent -Start
InitializeComponent -End
HeadTextColor -PropertyChanged

而第二种情况,使用全局样式设置属性时,顺序是:

HeadTextColor -PropertyChanged
InitializeComponent -Start
InitializeComponent -End

看起来该属性是在基类构造函数中的某处设置的。所以 NullReferenceException 是预期的。

有两种方法可以解决这个问题。

方案一(推荐)

不使用属性更改处理程序,而是使用 Binding 来设置内部子控件的属性,父节点作为 Source。例如:

<?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}}" />

方案二

或者,在InitializeComponent 之后设置内部控件的属性。

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

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

注意:在第二种解决方案中,您仍然需要属性更改处理程序。否则,在 XAML 中显式设置属性不会将更改传播到内部控件。我想为此需要进行空检查。

关于c# - 当我尝试将全局样式应用于 Xamarin Forms 中的自定义 ContentView 时出现 NullReferenceException,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46142251/

相关文章:

c# - Xamarin 中的 SignaturePadView 在 ios 和 android 上形成不同

android - 将 sqlite 数据库从 android studio 应用程序迁移到 Xamarin.Forms 应用程序

c# - 创建子类的实例是否创建父类的实例?

c# - XML 中元素的子节点

xaml - 如何解决 Metro 风格应用程序中 XAML 中 webview 控件的 z-index 问题?

c# - 是否可以使用 C#7 绑定(bind)到 WPF 中的 ValueTuple 字段

c# - 在 Xamarin.Forms 中访问相机

c# - 如何将程序集绑定(bind)重定向到当前版本或更高版本?

c# - Expression.Call - 调用 linq 扩展 : FirstOrDefault, 其中

c# - Entity Framework 不将数据保存到 WPF 中的数据库