Wednesday, December 17, 2014

Xamarin Forms Non-Native CheckBox

Having experience developing iOS applications, I know that there is no "check box" per say. When I searched for a Xamarin Forms built in check box and was left with only an native implementation, this would have been acceptable, except that they were causing some significant performance issues/lag when a view containing the controls would be added to the navigation stack.

In my last couple posts, I dove into creating non-native controls in Xamarin Forms. I can build on that knowledge to build a Xamarin Forms checkbox hopefully improve performance. This definitely renders a lot faster.
<localcontrol:CheckBoxView Checked="{Binding SomeBooleanProperty}" DefaultText="Check Box Text" ReadOnly="true" HorizontalOptions="FillAndExpand" TextColor="#000000" FontSize="12" />
The source code...
<?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="IntPonApp.Controls.CheckBoxView">

 <StackLayout x:Name="CheckBoxStack" Orientation="Horizontal">
  <StackLayout.GestureRecognizers>
   <TapGestureRecognizer 
      Command="{Binding CheckCommand}"
      CommandParameter="#" />
  </StackLayout.GestureRecognizers>
  <Image x:Name="boxImage" 
      Source="{Binding BoxImageSource}" />
  <Label x:Name="textLabel" 
      Text="{Binding Text}" 
      LineBreakMode="WordWrap" 
      XAlign="Center" 
      HorizontalOptions="StartAndExpand" 
      VerticalOptions="Center" 
      TextColor="{Binding TextColor}" 
      Font="{Binding Font}" />
 </StackLayout>
 
</ContentView>

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Labs;
using CustomControls;
using System.Diagnostics;
using System.Windows.Input;

namespace Check123Mobile.Controls
{
    public partial class CheckBoxView
    {
        #region Properties

        /// <summary>
        /// The width request in inches property.
        /// </summary>
        public static readonly BindableProperty CheckedProperty =
            BindableProperty.Create<CheckBoxView, bool>(
                p => p.Checked, false
                , propertyChanged: new BindableProperty.BindingPropertyChangedDelegate<bool>(
                    (BindableObject obj, bool oldPlaceHolderValue, bool newPlaceHolderValue) =>
                    {
                        var cbv = (CheckBoxView)obj;
                        cbv.BoxImageSource = cbv.GetCheckBoxImageSource();
                    })
                );

        protected static readonly BindableProperty BoxImageSourceProperty =
            BindableProperty.Create<CheckBoxView, ImageSource>(
                p => p.BoxImageSource, null);

        /// <summary>
        /// The read only property.
        /// </summary>
        public static readonly BindableProperty ReadOnlyProperty =
            BindableProperty.Create<CheckBoxView, bool>(
                p => p.ReadOnly, false);

        /// <summary>
        /// The checked text property.
        /// </summary>
        public static readonly BindableProperty CheckedTextProperty =
            BindableProperty.Create<CheckBoxView, string>(
                p => p.CheckedText, string.Empty);

        /// <summary>
        /// The unchecked text property.
        /// </summary>
        public static readonly BindableProperty UncheckedTextProperty =
            BindableProperty.Create<CheckBoxView, string>(
                p => p.UncheckedText, string.Empty);

        /// <summary>
        /// The checked image property.
        /// </summary>
        public static readonly BindableProperty CheckedImageProperty =
            BindableProperty.Create<CheckBoxView, string>(
                p => p.CheckedImage, string.Empty);

        /// <summary>
        /// The unchecked image property.
        /// </summary>
        public static readonly BindableProperty UncheckedImageProperty =
            BindableProperty.Create<CheckBoxView, string>(
                p => p.UncheckedImage, string.Empty);

        /// <summary>
        /// The default text property.
        /// </summary>
        public static readonly BindableProperty DefaultTextProperty =
            BindableProperty.Create<CheckBoxView, string>(
                p => p.Text, string.Empty);

        /// <summary>
        /// Identifies the TextColor bindable property.
        /// </summary>
        /// 
        /// <remarks/>
        public static readonly BindableProperty TextColorProperty =
            BindableProperty.Create<CheckBoxView, Color>(
                p => p.TextColor, Color.Black);

        /// <summary>
        /// The font size property
        /// </summary>
        public static readonly BindableProperty FontSizeProperty =
            BindableProperty.Create<CheckBoxView, double>(
                p => p.FontSize, -1);

        /// <summary>
        /// The font name property.
        /// </summary>
        public static readonly BindableProperty FontNameProperty =
            BindableProperty.Create<CheckBoxView, string>(
                p => p.FontName, string.Empty);

        public static ImageSource CheckedImageSource { get; protected set; }

        public static ImageSource UncheckedImageSource { get; protected set; }

        /// <summary>
        /// The checked changed event.
        /// </summary>
        public EventHandler<EventArgs<bool>> CheckedChanged;

        /// <summary>
        /// Gets or sets a value indicating whether the control is checked.
        /// </summary>
        /// <value>The checked state.</value>
        public bool Checked
        {
            get
            {
                return this.GetValue<bool>(CheckedProperty);
            }

            set
            {
                this.SetValue(CheckedProperty, value);
                var eventHandler = this.CheckedChanged;
                if (eventHandler != null)
                {
                    eventHandler.Invoke(this, value);
                }
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the control is checked.
        /// </summary>
        /// <value>The checked state.</value>
        public bool ReadOnly
        {
            get
            {
                return this.GetValue<bool>(ReadOnlyProperty);
            }

            set
            {
                this.SetValue(ReadOnlyProperty, value);
            }
        }

        /// <summary>
        /// Gets or sets a value indicating the checked text.
        /// </summary>
        /// <value>The checked state.</value>
        /// <remarks>
        /// Overwrites the default text property if set when checkbox is checked.
        /// </remarks>
        public string CheckedText
        {
            get
            {
                return this.GetValue<string>(CheckedTextProperty);
            }

            set
            {
                this.SetValue(CheckedTextProperty, value);
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the control is checked.
        /// </summary>
        /// <value>The checked state.</value>
        /// <remarks>
        /// Overwrites the default text property if set when checkbox is checked.
        /// </remarks>
        public string UncheckedText
        {
            get
            {
                return this.GetValue<string>(UncheckedTextProperty);
            }

            set
            {
                this.SetValue(UncheckedTextProperty, value);
            }
        }

        public ImageSource BoxImageSource
        {
            get
            {

                return this.GetValue<ImageSource>(BoxImageSourceProperty) ?? GetCheckBoxImageSource();
            }

            set
            {
                this.SetValue(BoxImageSourceProperty, value);
            }
        }

        /// <summary>
        /// Gets or sets a value indicating the checked text.
        /// </summary>
        /// <value>The checked state.</value>
        /// <remarks>
        /// Overwrites the default text property if set when checkbox is checked.
        /// </remarks>
        public string CheckedImage
        {
            get
            {
                return this.GetValue<string>(CheckedImageProperty);
            }

            set
            {
                this.SetValue(CheckedImageProperty, value);
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the control is checked.
        /// </summary>
        /// <value>The checked state.</value>
        /// <remarks>
        /// Overwrites the default text property if set when checkbox is checked.
        /// </remarks>
        public string UncheckedImage
        {
            get
            {
                return this.GetValue<string>(UncheckedImageProperty);
            }

            set
            {
                this.SetValue(UncheckedImageProperty, value);
            }
        }

        /// <summary>
        /// Gets or sets the text.
        /// </summary>
        public string DefaultText
        {
            get
            {
                return this.GetValue<string>(DefaultTextProperty);
            }

            set
            {
                this.SetValue(DefaultTextProperty, value);
            }
        }

        public Color TextColor
        {
            get
            {
                return this.GetValue<Color>(TextColorProperty);
            }

            set
            {
                this.SetValue(TextColorProperty, value);
            }
        }

        /// <summary>
        /// Gets or sets the size of the font.
        /// </summary>
        /// <value>The size of the font.</value>
        public double FontSize
        {
            get
            {
                return (double)GetValue(FontSizeProperty);
            }
            set
            {
                SetValue(FontSizeProperty, value);
            }
        }

        /// <summary>
        /// Gets or sets the name of the font.
        /// </summary>
        /// <value>The name of the font.</value>
        public string FontName
        {
            get
            {
                return (string)GetValue(FontNameProperty);
            }
            set
            {
                SetValue(FontNameProperty, value);
            }
        }

        public Font Font
        {
            get
            {
                return Font.SystemFontOfSize(FontSize);
            }
        }

        public string Text
        {
            get
            {
                return this.Checked
                    ? (string.IsNullOrEmpty(this.CheckedText) ? this.DefaultText : this.CheckedText)
                        : (string.IsNullOrEmpty(this.UncheckedText) ? this.DefaultText : this.UncheckedText);
            }
        }

        public ICommand CheckCommand { get; protected set; }

        #endregion Properties

        #region Constructor

        public CheckBoxView()
        {
            CheckCommand = new Command((object s) =>
                {
                    if (!ReadOnly)
                    {
                        Checked = !Checked;
                    }
                });
            InitializeComponent();
            LoadImages();
            CheckBoxStack.BindingContext = this;
            boxImage.BindingContext = this;
            textLabel.BindingContext = this;
        }

        #endregion Constructor
         
        #region Image Functions

        protected void LoadImages()
        {
            if (CheckedImageSource == null)
            {
                CheckedImageSource = ImageSource.FromResource(GetCheckedImage());
            }
            if (UncheckedImageSource == null)
            {
                UncheckedImageSource = ImageSource.FromResource(GetUncheckedImage());
            }
        }

        private ImageSource GetCheckBoxImageSource()
        {
            return this.Checked ? CheckedImageSource : UncheckedImageSource;
        }

        private string GetCheckBoxImage()
        {
            return this.Checked
                        ? GetCheckedImage()
                        : GetUncheckedImage();
        }

        private string GetCheckedImage()
        {
            return (string.IsNullOrEmpty(this.CheckedImage) ?
                            "Check123Mobile.Resources.checked_checkbox.png" :
                            this.CheckedImage);
        }

        private string GetUncheckedImage()
        {
            return (string.IsNullOrEmpty(this.UncheckedImage) ?
                            "Check123Mobile.Resources.unchecked_checkbox.png" :
                            this.UncheckedImage);
        }

        #endregion Image Functions
    }
}

No comments: