Search code examples
c#xamlcustom-controlswinui-3winui

WinUI 3 Custom Control Property Binding doesnt work


I created a Custom control that is supossed to be used as a button in an item View, however, the binding to change its text doesnt work.

CustomControls(Winui3 class library project)/CustomTableItem.cs

namespace CustomControls
{
    public sealed class CustomTableItem : Control
    {

        DependencyProperty TableNameProperty = DependencyProperty.Register(
            nameof(TableName),
            typeof(string),
            typeof(CustomTableItem),
            new PropertyMetadata(null));

        public string TableName
        {
            get => (string)GetValue(TableNameProperty);
            set => SetValue(TableNameProperty, value);
        }

        public CustomTableItem()
        {
            this.DefaultStyleKey = typeof(CustomTableItem);
        }

        public CustomTableItem(string TableName)
        {
            this.DefaultStyleKey = typeof(CustomTableItem);
            this.TableName = TableName;
        }
    }
}

CustomControls/Themes/Generic.xaml

//one other style above [...]

<Style TargetType="local:CustomTableItem">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="local:CustomTableItem">
                <Grid Height="150" Width="150" Background="White">
                    <TextBlock Text="..." Height="15" Width="20" HorizontalAlignment="Right" VerticalAlignment="Top"/>
                    <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center"
                        TextWrapping="Wrap"
                        Text="{Binding TableName, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay, FallbackValue='Error Binding'}"
                        Style="{StaticResource BodyStrongTextBlockStyle}" 
                        FontSize="20"
                        >
                    </TextBlock>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

View(packaged winui3 project)/CustomTablePage.xaml

<Page
    x:Class="View.CustomTablePage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:View"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:customControls ="using:CustomControls"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">


    <customControls:CustomTableItem TableName="This doesnt show in the label"/>

The CustomTablePage is shown using the ContentFrame.Navigate() function

End result:

The label is not updated with the text

When I place the exact same control in a page that is instantiated once and reused(ContentFrame.Content = PageObject) The Property works:

Here the text is shown

Other page location: View/PageName.xaml. Code:

<CustomControls:CustomTableItem TableName="Custom table name"></CustomControls:CustomTableItem>

I've tried changing the binding in the template text property but it still didnt work. I've seen some answers talking about something called DataContex, but I didnt understand how it works, and I noticed most of them were using user controls instead of custom controls or WPF.

Edit:

After debbuging the bind (following @Andrew KeepCoding answer), and adding an TargetNullValue to the binding line,

Text="{Binding TableName, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay, FallbackValue='Error Binding', TargetNullValue='Source is null!'}"

I've narrowed down the problem to the source being null.

Edit 2:

After reviewing in more detail my CustomTablePage constructor code:

namespace View
{
    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class CustomTablePage : Page
    {
        public CustomTablePage()
        {

            this.InitializeComponent();
            var item = new CustomTableItem();
            item.TableName = "Test";

            /*itemsViewTables.ItemsSource = new ObservableCollection<CustomTableItem>
            {
                item

            };*/
        }
    }
}

And commenting the lines:

var item = new CustomTableItem();
item.TableName = "Test";

The button text started showing up, but if I add more than one button, only the last text shows, all the others are null. It looks like there can only be one control of that type by page, no matter if the others are being displayed or not.

Added to CustomTablePage.xaml:

<Grid>
    <StackPanel>
        <customControls:CustomTableItem TableName="hello"/>
        <customControls:CustomTableItem TableName="hello2"/>
        <customControls:CustomTableItem TableName="hello3"/>
        <customControls:CustomTableItem TableName="hello4"/>
    </StackPanel>
</Grid>

What is happening now:

Only the last added control has the value bound

Fixed the issue by adding public static readonly to the dependency property declaration:

public static readonly DependencyProperty TableNameProperty = DependencyProperty.Register(
     nameof(TableName),
     typeof(string),
     typeof(CustomTableItem),
     new PropertyMetadata(null));

Solution

  • You should use the Live Visual Tree to debug this type of issues. You can see the properties for each control

    Live Visual Tree Button

    I guess you can't see it because the Foreground is White. Right-click the TextBlock and select Show Properties.

    Show Properties Menu

    Try using TemplateBinding:

    <TextBlock Text={TemplateBinding TableName} .../>
    

    I also found that public static readonly doc is missing on your dependency property:

    public static readonly DependencyProperty TableNameProperty =
        DependencyProperty.Register(
            nameof(TableName),
            typeof(string),
            typeof(CustomTableItem),
            new PropertyMetadata(null));