Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In the C# back end, how can I select an element in a Segmented Control?

I'm using a custom control to create joined buttons that display like this:

********************************************
*            *            *                *
*    One     *    Two     *     Three      *
*            *            *                *
********************************************

When I click the buttons then an event is fired:

void OnValueChanged(object sender, EventArgs e)
{
     switch (segControl.SelectedValue)
     {

This is working well but what I would like to do is to be able to select one of those buttons from within my C# code.

When I look at the source code for the control I cannot see how to do this.

Would appreciate if anyone has any suggestions on what I should do to for example select the 2nd button in the same way as if I had clicked on it.

Here is the code I am using:

<local:SegmentedControl ValueChanged="OnValueChanged" SelectedSegment="{Binding CustomPointsSwitch}" x:Name="segControl" HorizontalOptions="End">
   <local:SegmentedControl.Children>
       <local:SegmentedControlOption Text="Two" />
       <local:SegmentedControlOption Text="Four" />
   </local:SegmentedControl.Children>
</local:SegmentedControl>

Shared Code

public class SegmentedControl : View, IViewContainer<SegmentedControlOption>
{
    public IList<SegmentedControlOption> Children { get; set; }

    public SegmentedControl()
    {
        Children = new List<SegmentedControlOption>();
    }

    public event ValueChangedEventHandler ValueChanged;

    public delegate void ValueChangedEventHandler(object sender, EventArgs e);

    private string selectedValue;

    public string SelectedValue
    {
        get { return selectedValue; }
        set
        {
            selectedValue = value;
            if (ValueChanged != null)
                ValueChanged(this, EventArgs.Empty);
        }
    }
    public static readonly BindableProperty SelectedSegmentProperty = BindableProperty.Create("SelectedSegment", typeof(int), typeof(SegmentedControl), 0);
    public int SelectedSegment
    {
        get
        {
            return (int)GetValue(SelectedSegmentProperty);
        }
        set
        {
            SetValue(SelectedSegmentProperty, value);
        }
    }
}

public class SegmentedControlOption : View
{
    public static readonly BindableProperty TextProperty = BindableProperty.Create("Text", typeof(string), typeof(SegmentedControlOption), string.Empty);

    public string Text
    {
        get { return (string)GetValue(TextProperty); }
        set { SetValue(TextProperty, value); }
    }
}

iOS Renderer

public class SegmentedControlRenderer : ViewRenderer<SegmentedControl, UISegmentedControl>
{
    public SegmentedControlRenderer()
    {
    }

    protected override void OnElementChanged(ElementChangedEventArgs<SegmentedControl> e)
    {
        base.OnElementChanged(e);

        var segmentedControl = new UISegmentedControl();
        for (var i = 0; i < e.NewElement.Children.Count; i++)
        {
            segmentedControl.InsertSegment(e.NewElement.Children[i].Text, i, false);
        }

        segmentedControl.ValueChanged += (sender, eventArgs) => {
            e.NewElement.SelectedSegment = (int)segmentedControl.SelectedSegment;
            e.NewElement.SelectedValue = segmentedControl.TitleAt(segmentedControl.SelectedSegment);
        };

        SetNativeControl(segmentedControl);
    }
}

Android Renderer

public class SegmentedControlRenderer : ViewRenderer<SegmentedControl, RadioGroup>
{

    protected override void OnElementChanged(ElementChangedEventArgs<SegmentedControl> e)
    {
        base.OnElementChanged(e);

        var layoutInflater = (LayoutInflater)Context.GetSystemService(Context.LayoutInflaterService);

        var g = new RadioGroup(Context);
        g.Orientation = Orientation.Horizontal;
        g.CheckedChange += (sender, eventArgs) => {
            var rg = (RadioGroup)sender;
            if (rg.CheckedRadioButtonId != -1)
            {
                var id = rg.CheckedRadioButtonId;
                var radioButton = rg.FindViewById(id);
                var radioId = rg.IndexOfChild(radioButton);
                var btn = (RadioButton)rg.GetChildAt(radioId);
                var selection = (String)btn.Text;
                e.NewElement.SelectedValue = selection;
            }
        };

        for (var i = 0; i < e.NewElement.Children.Count; i++)
        {
            var o = e.NewElement.Children[i];
            var v = (SegmentedControlButton)layoutInflater.Inflate(Resource.Layout.SegmentedControl, null);
            v.Text = o.Text;
            if (i == 0)
                v.SetBackgroundResource(Resource.Drawable.segmented_control_first_background);
            else if (i == e.NewElement.Children.Count - 1)
                v.SetBackgroundResource(Resource.Drawable.segmented_control_last_background);
            g.AddView(v);
        }

        SetNativeControl(g);
    }
}

public class SegmentedControlButton : RadioButton
{
    private int lineHeightSelected;
    private int lineHeightUnselected;
    private Paint linePaint;

    public SegmentedControlButton(Context context, IAttributeSet attributes) : this(context, attributes, Resource.Attribute.segmentedControlOptionStyle)
    {
    }

    public SegmentedControlButton(Context context, IAttributeSet attributes, int defStyle) : base(context, attributes, defStyle)
    {
        Initialize(attributes, defStyle);
    }

    private void Initialize(IAttributeSet attributes, int defStyle)
    {
        var a = this.Context.ObtainStyledAttributes(attributes, Resource.Styleable.SegmentedControlOption, defStyle, Resource.Style.SegmentedControlOption);

        var lineColor = a.GetColor(Resource.Styleable.SegmentedControlOption_lineColor, 0);
        linePaint = new Paint();
        linePaint.Color = lineColor;

        lineHeightUnselected = a.GetDimensionPixelSize(Resource.Styleable.SegmentedControlOption_lineHeightUnselected, 0);
        lineHeightSelected = a.GetDimensionPixelSize(Resource.Styleable.SegmentedControlOption_lineHeightSelected, 0);

        a.Recycle();
    }

    protected override void OnDraw(Canvas canvas)
    {
        base.OnDraw(canvas);

        if (linePaint.Color != 0 && (lineHeightSelected > 0 || lineHeightUnselected > 0))
        {
            var lineHeight = Checked ? lineHeightSelected : lineHeightUnselected;

            if (lineHeight > 0)
            {
                var rect = new Rect(0, Height - lineHeight, Width, Height);
                canvas.DrawRect(rect, linePaint);
            }
        }
    }
}

I would appreciate any suggestions on how I can make the change. Also does anyone have an insights as to if Xamarin are looking to make this into a forms component?

like image 252
Samantha J T Star Avatar asked Nov 06 '17 07:11

Samantha J T Star


2 Answers

Why not just set margins in shared xaml?

<local:SegmentedControl ValueChanged="OnValueChanged" SelectedSegment="{Binding CustomPointsSwitch}" x:Name="segControl" HorizontalOptions="End">
   <local:SegmentedControl.Children>
       <local:SegmentedControlOption Text="Two" Margin="8,0,8,0" />
       <local:SegmentedControlOption Text="Four" Margin="8,0,8,0" />
   </local:SegmentedControl.Children>
</local:SegmentedControl>
like image 93
Nick Kovalsky Avatar answered Oct 13 '22 09:10

Nick Kovalsky


Have you had a chance to look at this plugin - it seems perfectly suited for what you are trying to achieve.

For being able to programmatically select a segment - i.e. SelectedSegment - you need to implement this at renderer level, and have them translate this selection to native controls. Furthermore, it is also recommended to override OnElementPropertyChanged method to subscribe to property changes (such as SelectedSegment) and ensure that the native control state stays in sync with the shared forms element, and vice-versa.


EDIT - 1: Add code sample

Shared code

public class SegmentedControl : View, IViewContainer<SegmentedControlOption>
{
    public IList<SegmentedControlOption> Children { get; set; }

    public SegmentedControl()
    {
        Children = new List<SegmentedControlOption>();
    }

    public event EventHandler ValueChanged;
    public static readonly BindableProperty SelectedValueProperty =
        BindableProperty.Create(
            "SelectedValue", typeof(string), typeof(SegmentedControl),
            defaultBindingMode: BindingMode.TwoWay,
            defaultValue: default(string), propertyChanged: OnSelectedValueChanged);

    public string SelectedValue
    {
        get { return (string)GetValue(SelectedValueProperty); }
        set { SetValue(SelectedValueProperty, value); }
    }

    static void OnSelectedValueChanged(BindableObject bindable, object oldValue, object newValue)
    {
        ((SegmentedControl)bindable).OnSelectedValueChangedImpl((string)oldValue, (string)newValue);
    }

    protected virtual void OnSelectedValueChangedImpl(string oldValue, string newValue)
    {
        ValueChanged?.Invoke(this, EventArgs.Empty);
        SelectedSegment = GetSelectedIndex(SelectedValue);
    }

    public static readonly BindableProperty SelectedSegmentProperty =
        BindableProperty.Create(
            "SelectedSegment", typeof(int), typeof(SegmentedControl),
            defaultBindingMode: BindingMode.TwoWay,
            defaultValue: default(int), propertyChanged: OnSelectedSegmentChanged);

    public int SelectedSegment
    {
        get { return (int)GetValue(SelectedSegmentProperty); }
        set { SetValue(SelectedSegmentProperty, value); }
    }

    private static void OnSelectedSegmentChanged(BindableObject bindable, object oldValue, object newValue)
    {
        ((SegmentedControl)bindable).OnSelectedSegmentChangedImpl((int)oldValue, (int)newValue);
    }

    protected virtual void OnSelectedSegmentChangedImpl(int oldValue, int newValue)
    {
        SelectedValue = GetSelectedValue(SelectedSegment);
    }

    int GetSelectedIndex(object selectedItem)
    {
        if (selectedItem == null)
            return -1;

        if (selectedItem is string optionText)
            return Children.IndexOf(Children.FirstOrDefault(x => Equals(x.Text, optionText)));

        return -1;
    }

    string GetSelectedValue(int index)
    {
        if (index >= 0 && index < Children.Count)
            return Children[index].Text;

        return null;
    }
}

//Keep SegmentedControlOption as same (No changes needed)
public class SegmentedControlOption : View
{
    ...
}

iOS renderer

public class SegmentedControlRenderer : ViewRenderer<SegmentedControl, UISegmentedControl>
{
    protected override void OnElementChanged(ElementChangedEventArgs<SegmentedControl> e)
    {
        base.OnElementChanged(e);

        UISegmentedControl segmentedControl = null;
        if (Control == null)
        {
            segmentedControl = new UISegmentedControl();

            for (var i = 0; i < e.NewElement.Children.Count; i++)
            {
                segmentedControl.InsertSegment(Element.Children[i].Text, i, false);
            }

            SetNativeControl(segmentedControl);
            SetSelectedSegment();
        }

        if (e.OldElement != null)
        {
            // Unsubscribe from event handlers and cleanup any resources
            if (segmentedControl != null)
                segmentedControl.ValueChanged -= NativeValueChanged;
        }

        if (e.NewElement != null)
        {
            // Configure the control and subscribe to event handlers
            segmentedControl.ValueChanged += NativeValueChanged;
        }
    }

    protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
    {
        base.OnElementPropertyChanged(sender, e);

        if (e.PropertyName == nameof(SegmentedControl.SelectedSegment))
            SetSelectedSegment();
    }

    void NativeValueChanged(object sender, EventArgs e)
    {
        if (Element is SegmentedControl formsElement)
        {
            formsElement.SelectedSegment = (int)Control.SelectedSegment;
        };
    }

    void SetSelectedSegment()
    {
        if (Element is SegmentedControl formsElement)
        {
            if (formsElement.SelectedSegment >= 0 && formsElement.SelectedSegment < Control.NumberOfSegments)
                Control.SelectedSegment = formsElement.SelectedSegment;
        }
    }
}

Android renderer

public class SegmentedControlRenderer : ViewRenderer<SegmentedControl, RadioGroup>
{
    protected override void OnElementChanged(ElementChangedEventArgs<SegmentedControl> e)
    {
        base.OnElementChanged(e);

        RadioGroup nativeControl = null;
        if (Control == null)
        {
            // Instantiate the native control and assign it to the Control property with the SetNativeControl method
            var layoutInflater = (LayoutInflater)Context.GetSystemService(Context.LayoutInflaterService);
            nativeControl = new RadioGroup(Context)
            {
                Orientation = Orientation.Horizontal
            };

            for (var i = 0; i < e.NewElement.Children.Count; i++)
            {
                var o = e.NewElement.Children[i];
                var v = (SegmentedControlButton)layoutInflater.Inflate(Resource.Layout.SegmentedControl, null);
                v.Text = o.Text;
                if (i == 0)
                    v.SetBackgroundResource(Resource.Drawable.segmented_control_first_background);
                else if (i == e.NewElement.Children.Count - 1)
                    v.SetBackgroundResource(Resource.Drawable.segmented_control_last_background);
                nativeControl.AddView(v);
            }

            SetNativeControl(nativeControl);
            SetSelectedSegment();
        }

        if (e.OldElement != null)
        {
            // Unsubscribe from event handlers and cleanup any resources
            if (nativeControl != null)
                nativeControl.CheckedChange -= NativeCheckedChanged;
        }

        if (e.NewElement != null)
        {
            nativeControl.CheckedChange += NativeCheckedChanged;
        }
    }

    protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
    {
        base.OnElementPropertyChanged(sender, e);

        if (e.PropertyName == nameof(SegmentedControl.SelectedSegment))
            SetSelectedSegment();
    }

    void NativeCheckedChanged(object sender, EventArgs e)
    {
        if (Element is SegmentedControl formsElement)
        {
            var rg = (RadioGroup)sender;
            if (rg.CheckedRadioButtonId != -1)
            {
                var id = rg.CheckedRadioButtonId;
                var radioButton = rg.FindViewById(id);
                var radioIndex = rg.IndexOfChild(radioButton);
                formsElement.SelectedSegment = radioIndex;
            }
        };
    }

    void SetSelectedSegment()
    {
        if (Element is SegmentedControl formsElement)
        {
            if (formsElement.SelectedSegment >= 0 && formsElement.SelectedSegment < Control.ChildCount)
            {
                var radioBtn = (RadioButton)Control.GetChildAt(formsElement.SelectedSegment);
                radioBtn.Checked = true;
            }
        }
    }
}

//Keep SegmentedControlOption as same (No changes needed)
public class SegmentedControlButton : RadioButton
{ 
    ...
}

Sample Usage

enter image description here

<StackLayout Margin="20">
    <local:SegmentedControl ValueChanged="OnValueChanged" SelectedSegment="{Binding CustomPointsSwitch}" x:Name="segControl" HorizontalOptions="End">
        <local:SegmentedControl.Children>
            <local:SegmentedControlOption Text="One" />
            <local:SegmentedControlOption Text="Two" />
            <local:SegmentedControlOption Text="Three" />
        </local:SegmentedControl.Children>
    </local:SegmentedControl>

    <Label Text="{Binding Path=SelectedSegment, StringFormat='Selected Segment: {0}', Source={x:Reference segControl}}" />
    <Label Text="{Binding Path=SelectedValue, StringFormat='Selected Value: {0}', Source={x:Reference segControl}}" />

    <StackLayout Orientation="Horizontal">
        <Button Text="Select One" Command="{Binding SelectCommand}" CommandParameter="0" HorizontalOptions="Start" />
        <Button Text="Select Two" Command="{Binding SelectCommand}" CommandParameter="1" HorizontalOptions="CenterAndExpand" />
        <Button Text="Select Three" Command="{Binding SelectCommand}" CommandParameter="2" HorizontalOptions="End" />
    </StackLayout>

    <Button Text="Set selection using SelectedValue" Clicked="Handle_Clicked" />
</StackLayout>

Code behind

public partial class SegmentedSamplePage : ContentPage
{
    public SegmentedSamplePage()
    {
        InitializeComponent();

        this.BindingContext = new PageViewModel
        {
            CustomPointsSwitch = 1
        };
    }

    void OnValueChanged(object sender, EventArgs e)
    {
        System.Diagnostics.Debug.WriteLine(this.segControl.SelectedValue);
    }

    void Handle_Clicked(object sender, System.EventArgs e)
    {
        this.segControl.SelectedValue = "Two";
    }
}

public class PageViewModel : ObservableObject
{
    int _customPointsSwitch;
    public int CustomPointsSwitch
    {
        get { return _customPointsSwitch; }
        set
        {
            SetProperty(ref _customPointsSwitch, value, nameof(CustomPointsSwitch));
        }
    }

    Command _selectCommand;
    public Command SelectCommand => _selectCommand ?? (_selectCommand = new Command((object param) =>
             {
                 if (Int32.TryParse(param?.ToString(), out int index))
                     CustomPointsSwitch = index;
             }));
}

Github: link

like image 41
Sharada Gururaj Avatar answered Oct 13 '22 09:10

Sharada Gururaj