52 Weeks of Xamarin: Week 12 – Advanced Customized Controls

Last week we looked at a simple example of customizing a control and creating a SpecialBoxViewcustom renderer.  This week we go a bit deeper.  Our goal is to give a BoxView a border, and to adjust that border dynamically.

[ This content is taken from my forthcoming Pluralsight course ]

As noted last week, we start with an Element and a Renderer.  In our case, the element will be a SpecialBoxView.  Unlike last week, however, this custom class will have contents; specifically the properties we want to be able to bind to.

Bindable Properties

public class SpecialBoxView : BoxView {     public static readonly BindableProperty BorderColorProperty =          BindableProperty.Create<SpecialBoxView, Color>(             p => p.BorderColor, default(Color));     public Color BorderColor {         get { return (Color)GetValue(BorderColorProperty); }         set { SetValue(BorderColorProperty, value); }     }     public static readonly BindableProperty BorderThicknessProperty =          BindableProperty.Create<SpecialBoxView, double>(             p => p.BorderThickness, default(double));     public double BorderThickness {         get { return (double)GetValue(BorderThicknessProperty); }         set { SetValue(BorderThicknessProperty, value); }     } }

Notice that these come in pairs: a bindable property and the public property that we’ll be using to get and set that value.  Thus, we can bind to BorderColor and/or BorderThickness.

Custom Renderer

With that done, we are ready to implement the custom renderer.  To do so, this week we’ll turn to iOS; next week we’ll follow up by creating the Android renderer.

The first step is to derive from BoxRenderer,

 public class SpecialBoxViewRenderer : BoxRenderer {

Within that class, we’ll override two methods: Draw and OnElementPropertyChanged.  In our override of Draw, we will first get the element itself,

 public override void Draw(CoreGraphics.CGRect rect) {
     SpecialBoxView specialBoxView = (SpecialBoxView)Element;
 

We will then get a context to work with to set our fill and stroke color as well as the width of the stroke.

 using (var context = UIGraphics.GetCurrentContext()) {
 
     context.SetFillColor(specialBoxView.Color.ToCGColor());
     context.SetStrokeColor(specialBoxView.BorderColor.ToCGColor());
     context.SetLineWidth((float)specialBoxView.BorderThickness);

We can then get the inset to draw the border.

    var rectangle = this.Bounds.Inset((int)specialBoxView.BorderThickness, (int)specialBoxView.BorderThickness);

    var path = CGPath.FromRect(rectangle);
    context.AddPath(path);
    context.DrawPath(CGPathDrawingMode.FillStroke);

All that is left is to override OnElementPropertyChanged.  We’ll make sure that it is the BorderThicknessProperty that has changed, and if so we’ll call SetNeedsDisplay which will cause Draw to be called and the border to be redrawn,

 

protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) {
    if (e.PropertyName == SpecialBoxView.BorderThicknessProperty.PropertyName) {
        SetNeedsDisplay();
    }
}

Don’t forget!

If we use this class as is it will build and it will run, but there will be no border, because we haven’t established a link between the renderer and the custom control.  As we did last week, we need to add an attribute outside of any namespace.

 [assembly: ExportRendererAttribute(typeof(SpecialBoxView), 
    typeof(SpecialBoxViewRenderer))]

You will need to add using statements to resolve the SpecialBoxView and the SpecialBoxViewRenderer.

XAML

In the XAML we’ll need a name space for the SpecialBoxView,

   xmlns:local="clr-namespace:CustomBoxView;assembly=CustomBoxView"
 

We can then instantiate a SpecialBoxView.  We’ll want to adjust the width of the border with a slider, so we’ll set the slider to be the BindingContext and the BorderThickness property will bind to the slider’s Value.

 <ContentPage.Content>
   <StackLayout
     Padding="20">
     <Label Text="Customized Control" TextColor="Red" FontSize="24"/> 
     <local:SpecialBoxView
       WidthRequest="200"
       HeightRequest="200"
       Color="Blue"
       BorderColor="Red"
       BindingContext="{x:Reference Name=ThicknessSlider}"
       BorderThickness="{Binding Path=Value}" />
     <Label
       BindingContext="{x:Reference Name=ThicknessSlider}"
       Text="{Binding Path=Value}" />
     <Slider
       x:Name="ThicknessSlider"
       Minimum="0"
       Maximum="100" />
   </StackLayout>
 </ContentPage.Content>
 

The result is shown in the image at the top of this posting.

 

About Jesse Liberty

Jesse Liberty has three decades of experience writing and delivering software projects and is the author of 2 dozen books and a couple dozen online courses. His latest book, Building APIs with .NET will be released early in 2025. Liberty is a Senior SW Engineer for CNH and he was a Senior Technical Evangelist for Microsoft, a Distinguished Software Engineer for AT&T, a VP for Information Services for Citibank and a Software Architect for PBS. He is a Microsoft MVP.
This entry was posted in Xamarin. Bookmark the permalink.

One Response to 52 Weeks of Xamarin: Week 12 – Advanced Customized Controls

Comments are closed.