Last week we looked at a simple example of customizing a control and creating a custom 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.
The only problem I have with Azure Mobile Services is the ilinibaty to ask for custom permission/scopes on Login with Facebook/Twitter. Sadly this became a deal breaker on my latest project and sent me back to native SDKs