Search code examples
c#.netxamlmaui

.Net MAUI Styles.xaml not detecting custom control's properties in Visual Studio (XFC0001)


As I was creating custom controls throughout my project, I realized that I needed to make it possible to style them through the Styles.xaml file. To do that, I apparently had to transform all the control's properties into BindableProperties. I did so with one of my custom controls, CameraView.xaml, and it worked just fine.

After that, I did the same with another of my controls, LoadingView.xaml, following the very same steps to turn all properties into bindable properties. Everything went fine, up to the point that I wanted to create a style for it.

Apparently, the Styles.xaml file is failing to recognize any of the control's custom properties. I truly to not know why, since I followed the exact same steps as with my first control. It's worth noticing that it's recognizing all of the ContentView's standard properties, but none of the properties I've made.

I've spent a great deal of time trying to understand why it's not working, all to no avail as of yet. I've also experimented by modifying some of the properties, rebuilding the entire solution, closing and opening VisualStudio, etc etc. All to no avail.

Therefore, I wish to ask: is there anything wrong with how I've written the properties? It's quite a lot of them, and I apologize in advance, but I am truly at a loss of what to do at this point.

LoadingView.xaml.cs:

(...)
  /// <summary>
  /// Gets or sets the title's style.
  /// </summary>
  public Style StyleTitle
  {
    set => SetValue(StyleTitleProperty, value);
    get => (Style)GetValue(StyleTitleProperty);
  }
  /// <summary>
  /// StyleTitle bindable property.
  /// </summary>
  public readonly BindableProperty StyleTitleProperty = BindableProperty.Create(nameof(StyleTitle), typeof(Style), typeof(LoadingView));

  /// <summary>
  /// Gets or sets the text's style.
  /// </summary>
  public Style StyleText
  {
    set => SetValue(StyleTextProperty, value);
    get => (Style)GetValue(StyleTextProperty);
  }
  /// <summary>
  /// StyleText bindable property.
  /// </summary>
  public readonly BindableProperty StyleTextProperty = BindableProperty.Create(nameof(StyleText), typeof(Style), typeof(LoadingView));

  /// <summary>
  /// Gets or sets the retry button's style.
  /// </summary>
  public Style StyleButton
  {
    set => SetValue(StyleButtonProperty, value);
    get => (Style)GetValue(StyleButtonProperty);
  }
  /// <summary>
  /// StyleButton bindable property.
  /// </summary>
  public readonly BindableProperty StyleButtonProperty = BindableProperty.Create(nameof(StyleButton), typeof(Style), typeof(LoadingView));

  /// <summary>
  /// Gets or sets the current lottie source.
  /// </summary>
  public SKLottieImageSource? LottieSource
  {
    set => SetValue(LottieSourceProperty, value);
    get => GetValue(LottieSourceProperty) as SKLottieImageSource;
  }
  /// <summary>
  /// LottieSource bindable property.
  /// </summary>
  public readonly BindableProperty LottieSourceProperty = BindableProperty.Create(nameof(LottieSource), typeof(SKLottieImageSource), typeof(LoadingView));

  /// <summary>
  /// Gets or sets the image source for when the view is loading.
  /// </summary>
  public SKLottieImageSource? LoadingLottieSource
  {
    set => SetValue(LoadingLottieSourceProperty, value);
    get => GetValue(LoadingLottieSourceProperty) as SKLottieImageSource;
  }
  /// <summary>
  /// LoadingLottieSource bindable property.
  /// </summary>
  public readonly BindableProperty LoadingLottieSourceProperty = BindableProperty.Create(nameof(LoadingLottieSource), typeof(SKLottieImageSource), typeof(LoadingView));

  /// <summary>
  /// Gets or sets the image source for when an exception is thrown while loading.
  /// </summary>
  public SKLottieImageSource? ErrorLottieSource
  {
    set => SetValue(ErrorLottieSourceProperty, value);
    get => GetValue(ErrorLottieSourceProperty) as SKLottieImageSource;
  }
  /// <summary>
  /// ErrorLottieSource bindable property.
  /// </summary>
  public readonly BindableProperty ErrorLottieSourceProperty = BindableProperty.Create(nameof(ErrorLottieSource), typeof(SKLottieImageSource), typeof(LoadingView));

  /// <summary>
  /// Gets or sets the view's title.
  /// </summary>
  public string? Title
  {
    set => SetValue(TitleProperty, value);
    get => GetValue(TitleProperty) as string;
  }
  /// <summary>
  /// Title bindable property.
  /// </summary>
  public readonly BindableProperty TitleProperty = BindableProperty.Create(nameof(Title), typeof(string), typeof(LoadingView));

  /// <summary>
  /// Gets or sets the view's text.
  /// </summary>
  public string? Text
  {
    set => SetValue(TextProperty, value);
    get => GetValue(TextProperty) as string;
  }
  /// <summary>
  /// Text bindable property.
  /// </summary>
  public readonly BindableProperty TextProperty = BindableProperty.Create(nameof(Text), typeof(string), typeof(LoadingView));

  /// <summary>
  /// Gets or sets the title for when the view is loading.
  /// </summary>
  public string? LoadingTitle
  {
    set => SetValue(LoadingTitleProperty, value);
    get => GetValue(LoadingTitleProperty) as string;
  }
  /// <summary>
  /// LoadingTitle bindable property.
  /// </summary>
  public readonly BindableProperty LoadingTitleProperty = BindableProperty.Create(nameof(LoadingTitle), typeof(string), typeof(LoadingView));

  /// <summary>
  /// Gets or sets the text for when the view is loading.
  /// </summary>
  public string? LoadingText
  {
    set => SetValue(LoadingTextProperty, value);
    get => GetValue(LoadingTextProperty) as string;
  }
  /// <summary>
  /// LoadingText bindable property.
  /// </summary>
  public readonly BindableProperty LoadingTextProperty = BindableProperty.Create(nameof(LoadingText), typeof(string), typeof(LoadingView));

  /// <summary>
  /// Gets or sets the title for when the view throws an exception.
  /// </summary>
  public string? ErrorTitle
  {
    set => SetValue(ErrorTitleProperty, value);
    get => GetValue(ErrorTitleProperty) as string;
  }
  /// <summary>
  /// ErrorTitle bindable property.
  /// </summary>
  public readonly BindableProperty ErrorTitleProperty = BindableProperty.Create(nameof(ErrorTitle), typeof(string), typeof(LoadingView));

  /// <summary>
  /// Gets or sets the text for when the view throws an exception. Can use string.Format with the following indexes:
  /// 0: The exception type;
  /// 1: The exception message.
  /// </summary>
  public string? ErrorText
  {
    set => SetValue(ErrorTextProperty, value);
    get => GetValue(ErrorTextProperty) as string;
  }
  /// <summary>
  /// ErrorText bindable property.
  /// </summary>
  public readonly BindableProperty ErrorTextProperty = BindableProperty.Create(nameof(ErrorText), typeof(string), typeof(LoadingView));

  /// <summary>
  /// Gets or sets the retry button's text.
  /// </summary>
  public string? ButtonText
  {
    set => SetValue(ButtonTextProperty, value);
    get => GetValue(ButtonTextProperty) as string;
  }
  /// <summary>
  /// ButtonText bindable property.
  /// </summary>
  public readonly BindableProperty ButtonTextProperty = BindableProperty.Create(nameof(ButtonText), typeof(string), typeof(LoadingView));

  /// <summary>
  /// Gets or sets the lottie's repeat count.
  /// </summary>
  public int LottieRepeat
  {
    set => SetValue(LottieRepeatProperty, value);
    get => (int)GetValue(LottieRepeatProperty);
  }
  /// <summary>
  /// LottieRepeat bindable property.
  /// </summary>
  public readonly BindableProperty LottieRepeatProperty = BindableProperty.Create(nameof(LottieRepeat), typeof(int), typeof(LoadingView));

  /// <summary>
  /// Number of times that the loading lottie has to repeat.
  /// </summary>
  public int LoadingLottieRepeat
  {
    set => SetValue(LoadingLottieRepeatProperty, value);
    get => (int)GetValue(LoadingLottieRepeatProperty);
  }
  /// <summary>
  /// LoadingLottieRepeat bindable property.
  /// </summary>
  public readonly BindableProperty LoadingLottieRepeatProperty = BindableProperty.Create(nameof(LoadingLottieRepeat), typeof(int), typeof(LoadingView));
(...)

LoadingView.xaml:

<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:skia="clr-namespace:SkiaSharp.Extended.UI.Controls;assembly=SkiaSharp.Extended.UI"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="Cephei.MAUI.Lottie.LoadingView">
  
  <VerticalStackLayout>
    <skia:SKLottieView x:Name="LottieView" WidthRequest="200" HeightRequest="200" Source="{Binding LottieSource}" RepeatCount="{Binding LottieRepeat}"/>
    <Label x:Name="LabelTitle" Style="{Binding StyleTitle}" Text="{Binding Title}"/>
    <Label x:Name="LabelDetail" Style="{Binding StyleText}" Text="{Binding Text}"/>
    <Button x:Name="ButtonRetry" Style="{Binding StyleButton}" Text="{Binding ButtonText}" IsVisible="False" Clicked="Button_Clicked"/>
  </VerticalStackLayout>
</ContentView>

Styles.xaml:

<ResourceDictionary 
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:lottie="clr-namespace:Cephei.MAUI.Lottie;assembly=Cephei.MAUI.Lottie"
    xmlns:camera="clr-namespace:Cephei.MAUI.Camera;assembly=Cephei.MAUI.Camera"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
(...)
  <Style TargetType="camera:CameraView">
    <!-- The CameraView's properties worked just fine -->
    <Setter Property="CameraWidth" Value="300"/>
    <Setter Property="CameraHeight" Value="300"/>
    <Setter Property="OverlayWidth" Value="275"/>
    <Setter Property="OverlayHeight" Value="275"/>
    <Setter Property="OverlayCornerWidth" Value="8"/>
    <Setter Property="OverlayCornerHeight" Value="8"/>
    <Setter Property="OverlayCornerThickness" Value="2"/>
  </Style>

  <Style TargetType="lottie:LoadingView">
    <!-- None of the LoadingView's custom properties are appearing as suggestions in Visual Studio -->
  </Style>
(...)

Any help which would shed a light to me in this issue would be appreciated. Thanks in advance!

EDIT: Even if I force a custom property onto the control's style, the compiler throws the following exception:

XFC0001: Cannot resolve property "LoadingTitle" on type "LoadingView (property missing or missing accessors)".

So it seems that the compiler is unable to detect any of the custom properties for whatever reason.


Solution

  • The Bindable Properties must be static, For Example,

      public static readonly BindableProperty StyleTitleProperty = BindableProperty.Create(nameof(StyleTitle), typeof(Style), typeof(LoadingView));