8
votes

There are a few good examples already of how to create a "custom control" by -

I want to create a "compound custom control OR usercontrol" which contains multiple elements which are defined in XAML (in the shared code), and then customised with a renderer (to say tweak the styling per platform).

Does anyone have an example of doing this please? A simple example with a view that has a bindable label and an entry box should be enough to show the main principles.

Here is what I have so far -

Defined a ContentView to represent our usercontrols layout and contents.

<ContentView xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="News.Forms.VisualNewsContentView">
    <ContentView.Content>
        <StackLayout>
            <Label x:Name="MyLabel" Text="Label"></Label>
            <Entry x:Name="MyEntry" Text="Entry"></Entry>
        </StackLayout>
    </ContentView.Content>
</ContentView>

with codebehind -

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

    // Not sure if I need this to access Entry ...
    public Entry GetEntry ()
    {
        return MyEntry;
    }
}

Add an Android Custom Renderer for that ContentView, how do I access and customise natively parts / controls of the ContentView?

[assembly:ExportRenderer (typeof(VisualNewsContentView), typeof(VisualNewsRenderer))]

namespace News.Forms.Android
{
    public class VisualNewsRenderer: ViewRenderer
    {
        public VisualNewsRenderer () { }

        protected override void OnModelChanged (VisualElement oldModel, VisualElement newModel)
        {
            base.OnModelChanged (oldModel, newModel);

            if (newModel != null) {
                VisualNewsContentView newsContentView = newModel as VisualNewsContentView;

                // i.e. How could I get hold of EditText etc so I could natively customise its appearance? When you use a built in renderer like EntryRenderer you can use Control to access native control.
                Console.WriteLine (newsContentView.GetLabel ().Text);
                EditText ed = (EditText)newsContentView.GetEntry ().???
            }
        }
    }
}

Just can't quite get the pieces together to work, the ContentView seems to render fine on page but cannot work out how to access its Child Native controls in the viewrenderer.

Be nice to also show how you can use Binding for the Label and Entry Text values.

I do not want to have to define a custom renderer for each single label / entry etc of the usercontrol.

2

2 Answers

2
votes

Is this what you meant?

Some properties to access the Xamarin.Forms controls:

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

        public Label Label
        {
            get
            {
                return MyLabel;
            }
            set
            {
                MyLabel = value;
            }
        }

        public Entry Entry
        {
            get
            {
                return MyEntry;
            }
            set
            {
                MyEntry = value;
            }
        }
    }

Some magic inside the Renderer to customize the controls on the page:

[assembly:ExportRenderer (typeof(VisualNewsContentView), typeof(VisualNewsRenderer))]

namespace News.Forms.Android
{
    public class VisualNewsRenderer: ViewRenderer
    {
        public VisualNewsRenderer () { }

        protected override void OnModelChanged (VisualElement oldModel, VisualElement newModel)
        {
            base.OnModelChanged (oldModel, newModel);

            if (newModel != null) {
                VisualNewsContentView newsContentView = newModel as VisualNewsContentView;
                newsContentView.Label.Text = "It´s some kind of..";
                newsContentView.Entry.Text = "MAGIC!";
                newsContentView.Entry.BackgroundColor = Color.Blue;
                newsContentView.Entry.RotationX = 180;
                newsContentView.Entry.Focus();

            }
        }
    }
}

EDIT:

I don't know if it's possible to map your controls from the XAML-page to native controls. You could add the controls which you want to customize natively @ the renderer.

    [assembly:ExportRenderer (typeof(VisualNewsContentView), typeof(VisualNewsRenderer))]

namespace News.Forms.Android
{
    public class VisualNewsRenderer: NativeRenderer
    {
        public VisualNewsRenderer () { }

        protected override void OnModelChanged (VisualElement oldModel, VisualElement newModel)
        {
            base.OnModelChanged (oldModel, newModel);

            if (newModel != null) {
                LinearLayout layout = new LinearLayout (Application.Context);
                layout.Orientation = Orientation.Vertical;

                TextView tv = new TextView (Application.Context);
                tv.Ellipsize = TextUtils.TruncateAt.Middle;
                tv.Text = "It´s some kind of..";

                EditText et = new EditText (Application.Context);
                et.SetTextColor (Graphics.Color.Chocolate);
                et.Text = "MAGIC!";

                layout.AddView (tv);
                layout.AddView (et);

                SetNativeControl (layout);
            }
        }
    }
}

But like this you won't be using your ContentView.. I'm sorry, I have nothing better than this..

1
votes

My solution for customizing compound user control is make a custom control for each control used in compound user control. For example, which this control:

<ContentView xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="News.Forms.VisualNewsContentView">
    <ContentView.Content>
        <StackLayout>
            <Label x:Name="MyLabel" Text="Label"></Label>
            <Entry x:Name="MyEntry" Text="Entry"></Entry>
        </StackLayout>
    </ContentView.Content>
</ContentView>

I will do something like this:

<ContentView xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
xmlns:CustomControls="clr-namespace:App.CustomControls;assembly=App"  x:Class="News.Forms.VisualNewsContentView">
        <ContentView.Content>
            <CustomControls:StackLayout>
                <CustomControls:Label x:Name="MyLabel" Text="Label"></CustomControls:Label>
                <CustomControls:Entry x:Name="MyEntry" Text="Entry"></CustomControls:Entry>
            </CustomControls:StackLayout>
        </ContentView.Content>
    </ContentView>

Example class for CustomControls:StackLayout is:

(in StackLayout.cs)

using Xamarin.Forms;

namespace App.CustomControls
{
    public class StackLayout : Xamarin.Forms.StackLayout
    {
    }
}

(in StackLayoutRenderer.cs for android project)

[assembly: ExportRenderer(typeof(App.CustomControls.StackLayout), typeof(App.Droid.CustomRenderers.StackLayoutRenderer))]
namespace App.Droid.CustomRenderers.MapView
{
    public class StackLayoutRenderer : ViewRenderer<StackLayout, Android.Widget.LinearLayout>
    {
        protected override void OnElementChanged(ElementChangedEventArgs<StackLayout> e)
        {
            base.OnElementChanged(e);                               
        }           
    }
}