Tobias
Tobias

Reputation: 2985

Multiple ContentPresenters in one ControlTemplate

Is it possible to have multiple ContentPresenters in a ControlTemplate?

I created a CustomControl with two BindableProperties of type View: ReadonlyContent and WritableContent. The ControlTemplate Wraps two ContentPresenters where the Content is bound to either ReadonlyContent or WritableContent.

Misteriously it only shows the content of one ContentPresenter in that case always ReadonlyContent uneffected by order of ContentPresenters or whatever.

So the question again: Is it possible to have two or more ContentPresenters in a ControlTemplate?

The ControlTemplate looks like this:

<ContentView.ControlTemplate>
    <ControlTemplate>
        <Grid HorizontalOptions="Fill" VerticalOptions="Fill" RowSpacing="0" Margin="0" Padding="0">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="40" />
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="40" />
            </Grid.ColumnDefinitions>

            <Button
                Grid.Column="2"
                ImageSource="{TemplateBinding IsReadonly, Converter={StaticResource BooleanToImageSourceConverter}}"
                BackgroundColor="Transparent"
                WidthRequest="40"
                HeightRequest="25"
                Padding="0"
                Clicked="OnToggleIsReadonly"
                x:Name="btnToggleEditMode"
                Margin="0" />
            <StackLayout Grid.Column="1" Orientation="Vertical">
                <ContentPresenter Content="{TemplateBinding ReadonlyContent, Mode=OneWay}" />
                <ContentPresenter Content="{TemplateBinding WriteableContent, Mode=OneWay}" />
            </StackLayout>
        </Grid>
    </ControlTemplate>
</ContentView.ControlTemplate>

while the code behind of the control looks like this:

[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class ActivatableContent : ContentView
{
    public static readonly BindableProperty IsReadonlyProperty = BindableProperty.Create(
        "IsReadonly",
        typeof(bool),
        typeof(ActivatableContent),
        true,
        BindingMode.TwoWay,
        propertyChanged: OnIsReadonlyChanged);

    public static readonly BindableProperty ReadonlyContentProperty = BindableProperty.Create(nameof(ReadonlyContent), typeof(View), typeof(ActivatableContent), propertyChanged: OnReadonlyContentChanged);

    public static readonly BindableProperty WritableContentProperty = BindableProperty.Create(nameof(WritableContent), typeof(View), typeof(ActivatableContent), propertyChanged: OnWritableContentChanged);

    public bool IsReadonly
    {
        get { return (bool)GetValue(IsReadonlyProperty); }
        set
        {
            SetValue(IsReadonlyProperty, value);
        }
    }

    public View ReadonlyContent
    {
        get { return (View)GetValue(ReadonlyContentProperty); }
        set { SetValue(ReadonlyContentProperty, value); }
    }

    public View WritableContent
    {
        get  { return (View)GetValue(WritableContentProperty); }
        set { SetValue(WritableContentProperty, value); }
    }

    public ActivatableContent()
    {
        InitializeComponent();
    }

    private static void OnIsReadonlyChanged(BindableObject bindable, object oldvalue, object newvalue)
    {
        ((ActivatableContent)bindable).IsReadonly = (bool)newvalue;
    }

    private static void OnReadonlyContentChanged(BindableObject bindable, object oldvalue, object newvalue)
    {
        var readonlyContent = (View)newvalue;
        ((ActivatableContent)bindable).ReadonlyContent = readonlyContent;
        SetInheritedBindingContext(readonlyContent, bindable.BindingContext);
    }

    private static void OnWritableContentChanged(BindableObject bindable, object oldvalue, object newvalue)
    {
        var writableContent = (View)newvalue;
        ((ActivatableContent)bindable).WritableContent = writableContent;
        SetInheritedBindingContext(writableContent, bindable.BindingContext);
    }

    private void OnToggleIsReadonly(object sender, EventArgs e)
    {
        IsReadonly = !IsReadonly;
    }

    /// <summary>Method that is called when the binding context changes.</summary>
    protected override void OnBindingContextChanged()
    {
        base.OnBindingContextChanged();

        var controlTemplate = ControlTemplate;
        if (ReadonlyContent != null && controlTemplate != null)
        {
            SetInheritedBindingContext(ReadonlyContent, BindingContext);
        }

        if (WritableContent != null && controlTemplate != null)
        {
            SetInheritedBindingContext(WritableContent, BindingContext);
        }
    }
}

Upvotes: 5

Views: 1680

Answers (1)

Wendy Zang - MSFT
Wendy Zang - MSFT

Reputation: 10948

ContentPresenter always points to the control's content by default you can't define two different content.

However, we could do this in custom control. You could download folder of ContentPresenterDemo from GitHub for reference. https://github.com/WendyZang/Test.git

First, define two different bindable properties in your custom control

public static readonly BindableProperty ReadonlyContentProperty = BindableProperty.Create(nameof(ReadonlyContent), typeof(View), typeof(CustomContentView));
    public View ReadonlyContent
    {
        get { return (View)GetValue(ReadonlyContentProperty); }
        set { SetValue(ReadonlyContentProperty, value); }
    }

    public static readonly BindableProperty WritableContentProperty = BindableProperty.Create(nameof(WritableContent), typeof(View), typeof(CustomContentView));
    public View WritableContent
    {
        get { return (View)GetValue(WritableContentProperty); }
        set { SetValue(WritableContentProperty, value); }
    }

Please note, do not forget to change ContentPage to ContentView in xaml.

And then define two views with template in Application.Resources.

<Application.Resources>
    <ControlTemplate x:Key="MyTemplate">
        <StackLayout>
            <ContentView Content="{TemplateBinding WritableContent}"/>
            <ContentView Content="{TemplateBinding ReadonlyContent}"/>
        </StackLayout>
    </ControlTemplate>

    <ContentView x:Key="MyContentView">
        <StackLayout>
            <Label Text="MyContentView" BackgroundColor="Red"></Label>
            <!--code here...-->
        </StackLayout>
    </ContentView>

    <ContentView x:Key="MyContentView2">
        <StackLayout>
            <Label Text="MyContentView2" BackgroundColor="Green"></Label>
            <!--code here...-->
        </StackLayout>
    </ContentView>

And then use it in page.

 <StackLayout>
        <local:CustomContentView ReadonlyContent="{StaticResource MyContentView}" 
                        WritableContent="{StaticResource MyContentView2}" 
                        ControlTemplate="{StaticResource MyTemplate}" />
    </StackLayout>

enter image description here

Or you could use Picker to do Multiple ContentPresenters.

Define a Picker with multiple ContentPresenters.

<Picker x:Name="picker" Title="Select a template" SelectedIndexChanged="SelectedIndexChanged">
      <Picker.ItemsSource>
        <x:Array Type="{x:Type x:String}">
          <x:String>Template 1</x:String>
          <x:String>Template 2</x:String>
          <x:String>Template 3</x:String>
          <x:String>Template 4</x:String>
        </x:Array>
      </Picker.ItemsSource>
    </Picker>

You could download from the GitHub. https://github.com/CrossGeeks/ControlTemplateSample

enter image description here

Upvotes: 3

Related Questions