I am trying to write a simple ContentView XAML control in .NET MAUI using inheritance and Binding. The control simply displays a name/value pair on a HorizontalStack - the base control displays the name, and the child control displays the value. Here's the base control, which is simply a grid with a label that displays the ControlName. It exposes the ControlName bindable string property, and a ContentView inside the grid that the child control TextNameValueControl can use to add the value.
XAML - Base Control
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Name="thisControlBase"
x:Class="MauiPlayground.Controls.NameValueControlBase">
<Grid BindingContext="{x:Reference thisControlBase}">
<Label Text="{Binding ControlName}"
VerticalOptions="Center"
HorizontalOptions="Start" />
<ContentView x:Name="ControlValueContentView" />
</Grid>
</ContentView>
Code-Behind - Base Control
public partial class NameValueControlBase : ContentView
{
public static readonly BindableProperty ControlNameProperty = BindableProperty.Create(
propertyName: nameof(ControlName),
returnType: typeof(string),
declaringType: typeof(NameValueControlBase),
defaultValue: string.Empty,
defaultBindingMode: BindingMode.OneWay);
public string ControlName
{
get => (string)GetValue(ControlNameProperty);
set => SetValue(ControlNameProperty, value);
}
public View ControlValueContent
{
get => this.ControlValueContentView.Content;
set => this.ControlValueContentView.Content = value;
}
public NameValueControlBase()
{
InitializeComponent();
}
}
The child TextNameValueControl inherits from the NameValueControlBase, and exposes a ControlValue bindable string property, which it displays in another Label in the base class' exposed ContentView:
XAML - Child Control
<ctrl:NameValueControlBase xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:ctrl="clr-namespace:MauiPlayground.Controls"
x:Name="thisNameValueControlBase"
x:Class="MauiPlayground.Controls.TextNameValueControl">
<ctrl:NameValueControlBase>
<ctrl:NameValueControlBase.ControlValueContent>
<HorizontalStackLayout VerticalOptions="Center"
HorizontalOptions="End"
BindingContext="{x:Reference thisNameValueControlBase}">
<Label BindingContext="{x:Reference thisNameValueControlBase}"
Text="{Binding ControlTextValue}" />
</HorizontalStackLayout>
</ctrl:NameValueControlBase.ControlValueContent>
</ctrl:NameValueControlBase>
</ctrl:NameValueControlBase>
Code-Behind - Child Control
public partial class TextNameValueControl : NameValueControlBase
{
public static readonly BindableProperty ControlTextValueProperty = BindableProperty.Create(
propertyName: nameof(ControlTextValue),
returnType: typeof(string),
declaringType: typeof(TextNameValueControl),
defaultValue: string.Empty,
defaultBindingMode: BindingMode.OneWay);
public string ControlTextValue
{
get => (string)GetValue(ControlTextValueProperty);
set => SetValue(ControlTextValueProperty, value);
}
public TextNameValueControl()
{
InitializeComponent();
}
}
Here's the main page, which simply displays the control and provides values for the name and value properties:
XAML - Main Page
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:MauiPlayground.Controls"
x:Class="MauiPlayground.MainPage">
<VerticalStackLayout>
<controls:TextNameValueControl ControlName="Employee Name"
ControlTextValue="John Doe" />
</VerticalStackLayout>
</ContentPage>
The problem I'm having is that the Value is displayed, but the Name from the base control is not: "Employee Name" should appear here
I tried changing the x:Name on both controls to "this", which didn't help. I even tried removing the Binding from the controls altogether and using the event handlers to set the label text:
XAML - Base Control (Binding removed)
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Name="thisControlBase"
x:Class="MauiPlayground.Controls.NameValueControlBase">
<Grid>
<Label x:Name="NameLabel"
VerticalOptions="Center"
HorizontalOptions="Start" />
<HorizontalStackLayout HorizontalOptions="End">
<ContentView x:Name="ControlValueContentView" />
</HorizontalStackLayout>
</Grid>
</ContentView>
Code-Behind - Base Control (Binding removed)
public partial class NameValueControlBase : ContentView
{
public static readonly BindableProperty ControlNameProperty = BindableProperty.Create(
propertyName: nameof(ControlName),
returnType: typeof(string),
declaringType: typeof(NameValueControlBase),
defaultValue: string.Empty,
defaultBindingMode: BindingMode.TwoWay,
propertyChanged: OnControlNamePropertyChanged);
private static void OnControlNamePropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
var control = (NameValueControlBase)bindable;
control.NameLabel.Text = (string)newValue;
}
public string ControlName
{
get => (string)GetValue(ControlNameProperty);
set => SetValue(ControlNameProperty, value);
}
public View ControlValueContent
{
get => this.ControlValueContentView.Content;
set => this.ControlValueContentView.Content = value;
}
public NameValueControlBase()
{
InitializeComponent();
}
}
This didn't work either.
I used this type of inheritance in Xamarin and it worked ok. In Xamarin, the control displayed both the ControlName from the base control and the ControlValue from the derived control.
Am I doing something wrong? Or is this a bug in .NET MAUI?
UPDATE
I found out what was causing the problem. I added a layer to the child control XAML definition, and I believe it was causing the base control to be overwritten:
<ctrl:NameValueControlBase xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:ctrl="clr-namespace:MauiPlayground.Controls"
x:Name="thisNameValueControlBase"
x:Class="MauiPlayground.Controls.TextNameValueControl">
// <ctrl:NameValueControlBase> <-- Remove this
<ctrl:NameValueControlBase.ControlValueContent>
<HorizontalStackLayout VerticalOptions="Center"
HorizontalOptions="End"
BindingContext="{x:Reference thisNameValueControlBase}">
<Label BindingContext="{x:Reference thisNameValueControlBase}"
Text="{Binding ControlTextValue}" />
</HorizontalStackLayout>
</ctrl:NameValueControlBase.ControlValueContent>
// </ctrl:NameValueControlBase> <-- Remove this
</ctrl:NameValueControlBase>
Once I removed this layer it worked.