Reputation: 3270
I'm creating a custom control by deriving from ContentView
. My goal is to have a user consume the class as you would expect to consume any control with a single piece of content:
<local:NewControl>
<Image source="..." />
</local:NewControl>
However, I would like the control to contain a grid and then have the content placed in the grid. Conceptually (i.e. ignoring the fact that ContentPresenter
cannot be used in this way), I'd like NewControl.xaml
to look like this:
<ContentView ...>
<Grid x:Name="HiddenGrid">
<ContentPresenter />
</Grid>
</ContentView>
Doing what I detailed above results in HiddenGrid
being replaced by the Image
, rather than the image being inserted in the visual tree as a child of the ContentPresenter
.
At the moment, I'm achieving my goal by deriving from Grid
instead of ContentView
, but I'd like to hide this implementation detail so consumers cannot interfere with the Grid
and have a control to which they can assign only a single piece of content.
This seems like it should be straightforward (and widely applicable to all sorts of scenarios where content needs to be embellished in some way), but I can't see a way of doing it. Am I missing something obvious, is it not possible, or is it just tricky to do and I need to read up on something (like control templates)?
Upvotes: 3
Views: 1312
Reputation: 7455
One way i can think of is overriding the ContentProperty
of your NewControl
: by doing this you can control exactly what happens with the "Content" of the NewControl
.
To do this follow the next steps:
<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="HiddenGridTest.NewControl">
<ContentView.Content>
<Grid x:Name="HiddenGrid" Padding="20">
<Grid.RowDefinitions>
<RowDefinition Height="2*" />
<RowDefinition Height="*" />
<RowDefinition Height="100" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
</Grid>
</ContentView.Content>
</ContentView>
Very important here is that you put your Grid
inside the Content
property explicitly!
<ContentView.Content>
</ContentView.Content>
ContentProperty
of your control and link it to the Children
of the HiddenGrid
using System.Collections.Generic;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace HiddenGridTest
{
[ContentProperty("Contents")]
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class NewControl : ContentView
{
public IList<View> Contents { get => HiddenGrid.Children; }
public NewControl()
{
InitializeComponent();
}
}
}
NewControl
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:HiddenGridTest"
x:Class="HiddenGridTest.MainPage">
<local:NewControl>
<Label Text="Inside grid"
BackgroundColor="Red"/>
</local:NewControl>
</ContentPage>
The result is shown in the image below. Note that the image is set as content of the Grid
(occupying the first row and first column!):
Interestingly enough, i also answered a couple of questions this week where also overriding the ContentProperty
was useful. Since they could complete the information posted in this answer, i leave the links here:
How to override/modify the Content property of Frame to accept multiple Views in Xamarin.Forms?
How can I combine XAML and a template into one C# template with Rg.Plugins.Popup?
Upvotes: 4
Reputation: 1404
We can use ControlTemplate. Xamarin.Forms control templates enable to define the visual structure of ContentView derived custom controls, and ContentPage derived pages.
<ContentView.ControlTemplate>
<ControlTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Label Text="Test" Grid.Row="0"></Label>
<ContentPresenter Grid.Row="1"></ContentPresenter>
</Grid>
</ControlTemplate>
</ContentView.ControlTemplate>
Xaml
<local:NewControl>
<Label Text= "Test"/>
</local:NewControl>
Upvotes: 5