Al John
Al John

Reputation: 900

Binding a control in ContentView to properties in a ViewModel and reusing the ContentView for other pages

How do I bind a property in ViewModel to a ContentView within a CollectionView in such way that I can reuse the ContentView for other pages as well?

Referencing the CollectionViews' binding context using its name works normally. When using a ContentView as a template for the items to display in it, I cannot reference its binding context so this doesn't work:

<Label x:Name="HowToBindThis"
       Text="{Binding Source={x:Reference ItemCollection}, 
                      Path=BindingContext.ItemInParentViewModel}"/>

Works only for the specified ViewModel:

<Label x:Name="HowToBindThis"
       Text="{Binding Source={RelativeSource AncestorType={x:Type viewmodels:ParentViewModel}, 
                      Path=ItemInParentViewModel}"/>

ContentPage

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:viewmodels="clr-namespace:NewProject23.ViewModels"
             xmlns:models="clr-namespace:NewProject23.Models"
             xmlns:contentviews="clr-namespace:NewProject23.ContentViews"
             x:Class="NewProject23.Views.MainPage"
             Title="MainPage"/>
    <CollectionView ItemsSource="{Binding Items}"
                    x:Name="ItemCollection">
        <CollectionView.ItemTemplate>
            <DataTemplate x:DataType="models:Item">
                <VerticalStackLayout>
                    <contentviews:ItemContentView/>
                </VerticalStackLayout>
            </DataTemplate>
        </CollectionView.ItemTemplate>
    </CollectionView>
</ContentPage>

ContentView
How do I bind the text property of <Label x:Name="HowToBindThis"/> relatively, so I can re-use this ContentView on any other page with any other viewmodel as well?

<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:models="clr-namespace:NewProject23.Models"
             xmlns:viewmodels="clr-namespace:NewProject23.ViewModels"
             x:Class="NewProject23.ContentViews.ItemContentView"
             x:Name="ItemContentView"
             x:DataType="model:Item">
    <Frame Style="{StaticResource ItemFrame}">
        <VerticalStackLayout>
            <Label Text="{Binding ItemTitle}"/>
            <Label x:Name="HowToBindThis"
                   Text="{Binding Source={RelativeSource AncestorType={x:Type viewmodels:ParentViewModel}, 
                                  Path=ItemInParentViewModel}"/>
        </VerticalStackLayout>
    </Frame>
</ContentView>

ViewModel

using ...

namespace NewProject23.ViewModels;

public class ParentViewModel : BaseViewModel
{
    ...
    
    private string itemInParentViewModel = "Item in ParentViewModel";
    public string ItemInParentViewModel
    {
        get => itemInParentViewModel;
        set => SetProperty(ref itemInParentViewModel, value);
    }
    
    public ObservableRangeCollection<Item> Items { get; private set; } = new();
    
    ...
}

Upvotes: 0

Views: 275

Answers (1)

FreakyAli
FreakyAli

Reputation: 16409

I will try to add a basic example which you will obviously have to change based on your requirements but will help you do what you are looking for.

Let's create a TextViewCell and the XAML would look like this:

<?xml version="1.0" encoding="utf-8" ?>
<ContentView
    x:Class="Mobile.ViewCells.TextViewCell"
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    x:Name="this">
    <Grid
        Padding="10,15"
        BackgroundColor="{StaticResource White}"
        ColumnDefinitions="*,*">
        <VerticalStackLayout Grid.Column="0" VerticalOptions="Center">
            <Label
                FontSize="{Binding LeadingTitleLabelFontSize, Source={x:Reference this}}"
                HorizontalTextAlignment="Start"
                Text="{Binding LeadingTitleLabelText, Source={x:Reference this}}"
                TextColor="{Binding LeadingTitleTextColor, Source={x:Reference this}}" />
            <Label
                FontSize="{Binding LeadingSubTitleLabelFontSize, Source={x:Reference this}}"
                HorizontalTextAlignment="Start"
                IsVisible="{Binding LeadingSubTitleLabelText, Source={x:Reference this}, Converter={StaticResource StringIsNullToBoolConvertor}}"
                Text="{Binding LeadingSubTitleLabelText, Source={x:Reference this}}"
                TextColor="{Binding LeadingSubTitleTextColor, Source={x:Reference this}}" />
        </VerticalStackLayout>

        <VerticalStackLayout Grid.Column="1" VerticalOptions="Center">
            <Label
                FontSize="{Binding TrailingTitleLabelFontSize, Source={x:Reference this}}"
                HorizontalTextAlignment="Start"
                Text="{Binding TrailingTitleLabelText, Source={x:Reference this}}"
                TextColor="{Binding TrailingTitleTextColor, Source={x:Reference this}}" />
            <Label
                FontSize="{Binding TrailingSubTitleLabelFontSize, Source={x:Reference this}}"
                HorizontalTextAlignment="Start"
                IsVisible="{Binding TrailingSubTitleLabelText, Source={x:Reference this}, Converter={StaticResource StringIsNullToBoolConvertor}}"
                Text="{Binding TrailingSubTitleLabelText, Source={x:Reference this}}"
                TextColor="{Binding TrailingSubTitleTextColor, Source={x:Reference this}}" />
        </VerticalStackLayout>
    </Grid>
</ContentView>

Notice how I am using Source with a reference for the current control that gives me access to some data I need and the data does not come from another Control or ViewModel but from itself.

public partial class TextViewCell : ContentView
{
    public TextViewCell()
    {
        InitializeComponent();
    }

    #region Trailing

    public static readonly BindableProperty TrailingTitleLabelTextProperty =
       BindableProperty.Create(
           nameof(TrailingTitleLabelText),
           typeof(string),
           typeof(TextViewCell),
           Label.TextProperty.DefaultValue
           );

    public string TrailingTitleLabelText
    {
        get => (string)GetValue(TrailingTitleLabelTextProperty);
        set => SetValue(TrailingTitleLabelTextProperty, value);
    }

    public static readonly BindableProperty TrailingTitleTextColorProperty =
        BindableProperty.Create(nameof(TrailingTitleTextColor), typeof(Color), typeof(TextViewCell), defaultValue: Colors.Black);

    public Color TrailingTitleTextColor
    {
        get => (Color)GetValue(TrailingTitleTextColorProperty);
        set => SetValue(TrailingTitleTextColorProperty, value);
    }

    public static readonly BindableProperty TrailingTitleLabelFontSizeProperty =
        BindableProperty.Create(
            nameof(TrailingTitleLabelFontSize),
            typeof(double),
            typeof(TextViewCell),
            Label.FontSizeProperty.DefaultValue);

    [TypeConverter(typeof(FontSizeConverter))]
    public double TrailingTitleLabelFontSize
    {
        get => (double)GetValue(TrailingTitleLabelFontSizeProperty);
        set => SetValue(TrailingTitleLabelFontSizeProperty, value);
    }

    public static readonly BindableProperty TrailingSubTitleLabelTextProperty =
        BindableProperty.Create(
            nameof(TrailingSubTitleLabelText),
            typeof(string),
            typeof(TextViewCell),
            Label.TextProperty.DefaultValue
        );

    public string TrailingSubTitleLabelText
    {
        get => (string)GetValue(TrailingSubTitleLabelTextProperty);
        set => SetValue(TrailingSubTitleLabelTextProperty, value);
    }

    public static readonly BindableProperty TrailingSubTitleLabelFontSizeProperty =
        BindableProperty.Create(
            nameof(TrailingSubTitleLabelFontSize),
            typeof(double),
            typeof(TextViewCell),
            Label.FontSizeProperty.DefaultValue);

    [TypeConverter(typeof(FontSizeConverter))]
    public double TrailingSubTitleLabelFontSize
    {
        get => (double)GetValue(TrailingSubTitleLabelFontSizeProperty);
        set => SetValue(TrailingSubTitleLabelFontSizeProperty, value);
    }

    public static readonly BindableProperty TrailingSubTitleTextColorProperty =
        BindableProperty.Create(nameof(TrailingSubTitleTextColor), typeof(Color), typeof(TextViewCell), defaultValue: Colors.Black);

    public Color TrailingSubTitleTextColor
    {
        get => (Color)GetValue(TrailingSubTitleTextColorProperty);
        set => SetValue(TrailingSubTitleTextColorProperty, value);
    }

    #endregion Trailing

    #region Leading

    public static readonly BindableProperty LeadingTitleLabelTextProperty =
       BindableProperty.Create(
           nameof(LeadingTitleLabelText),
           typeof(string),
           typeof(TextViewCell),
           Label.TextProperty.DefaultValue
           );

    public string LeadingTitleLabelText
    {
        get => (string)GetValue(LeadingTitleLabelTextProperty);
        set => SetValue(LeadingTitleLabelTextProperty, value);
    }

    public static readonly BindableProperty LeadingTitleTextColorProperty =
        BindableProperty.Create(nameof(LeadingTitleTextColor), typeof(Color), typeof(TextViewCell), defaultValue: Colors.Black);

    public Color LeadingTitleTextColor
    {
        get => (Color)GetValue(LeadingTitleTextColorProperty);
        set => SetValue(LeadingTitleTextColorProperty, value);
    }

    public static readonly BindableProperty LeadingTitleLabelFontSizeProperty =
        BindableProperty.Create(
            nameof(LeadingTitleLabelFontSize),
            typeof(double),
            typeof(TextViewCell),
            Label.FontSizeProperty.DefaultValue);

    [TypeConverter(typeof(FontSizeConverter))]
    public double LeadingTitleLabelFontSize
    {
        get => (double)GetValue(LeadingTitleLabelFontSizeProperty);
        set => SetValue(LeadingTitleLabelFontSizeProperty, value);
    }

    public static readonly BindableProperty LeadingSubTitleLabelTextProperty =
        BindableProperty.Create(
            nameof(LeadingSubTitleLabelText),
            typeof(string),
            typeof(TextViewCell),
            Label.TextProperty.DefaultValue
        );

    public string LeadingSubTitleLabelText
    {
        get => (string)GetValue(LeadingSubTitleLabelTextProperty);
        set => SetValue(LeadingSubTitleLabelTextProperty, value);
    }

    public static readonly BindableProperty LeadingSubTitleLabelFontSizeProperty =
        BindableProperty.Create(
            nameof(LeadingSubTitleLabelFontSize),
            typeof(double),
            typeof(TextViewCell),
            Label.FontSizeProperty.DefaultValue);

    [TypeConverter(typeof(FontSizeConverter))]
    public double LeadingSubTitleLabelFontSize
    {
        get => (double)GetValue(LeadingSubTitleLabelFontSizeProperty);
        set => SetValue(LeadingSubTitleLabelFontSizeProperty, value);
    }

    public static readonly BindableProperty LeadingSubTitleTextColorProperty =
        BindableProperty.Create(nameof(LeadingSubTitleTextColor), typeof(Color), typeof(TextViewCell), defaultValue: Colors.Black);

    public Color LeadingSubTitleTextColor
    {
        get => (Color)GetValue(LeadingSubTitleTextColorProperty);
        set => SetValue(LeadingSubTitleTextColorProperty, value);
    }

    #endregion Leading
}

And now all you need to do is whenever you need to use this you pass in the data that you expect and things and this will work as needed

Now you can use it either within a DataTemplate or on its own:

   <CollectionView.ItemTemplate>
            <DataTemplate x:DataType="models:WhateverModel">
                <viewCells:TextViewCell
                    Padding="0,0,25,0"
                    LeadingTitleLabelFontSize="20"
                    LeadingTitleLabelText="{Binding Name, Mode=OneTime}"
                    LeadingTitleTextColor="{StaticResource Black}" />
            </DataTemplate>
        </CollectionView.ItemTemplate>

or

   <viewCells:TextViewCell
                    Padding="0,0,25,0"
                    LeadingTitleLabelFontSize="20"
                    LeadingTitleLabelText="{Binding Name, Mode=OneTime}"
                    LeadingTitleTextColor="{StaticResource Black}" />

Upvotes: 2

Related Questions