简体   繁体   English

在C#后端,如何在分段控件中选择元素?

[英]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. 这很好用,但我想做的是能够从我的C#代码中选择其中一个按钮。

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 iOS渲染器

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 Android渲染器

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? 也有人知道Xamarin是否希望将其变成表单组件?

Why not just set margins in shared xaml? 为什么不在共享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>

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 - ie SelectedSegment - you need to implement this at renderer level, and have them translate this selection to native controls. 为了能够以编程方式选择一个段 - 即SelectedSegment - 您需要在渲染器级别实现它,并让它们将此选择转换为本机控件。 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. 此外,还建议重写OnElementPropertyChanged方法以订阅属性更改(例如SelectedSegment )并确保本机控件状态与共享表单元素保持同步,反之亦然。


EDIT - 1: Add code sample 编辑 - 1:添加代码示例

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 iOS渲染器

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 Android渲染器

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 样本用法

在此输入图像描述

<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 Github: 链接

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何在具有C#后端而不是XAML的模板上绑定XAML元素? - How can I bind a XAML element on a template with the C# back end instead of in the XAML? 在C#后端中将资源声明为Class时,如何访问资源? - How can I access a resource when it's declared as a Class in C# back end? 如何将DynamicResource的值发送到自定义控件C#后端? - How can I send the value of a DynamicResource to my custom controls C# back end? 如何在后端C#和渲染器之间共享字段的值? - How can I share the value of a field between back-end C# and a renderer? 如何在C#后端的模板中更改标签的颜色? - How can I change the color of a Label in a template in the C# back end? 我可以将XAML网格的高度绑定回我的C#后端代码吗? - Can I bind the Height of a XAML Grid back to my C# back end code? 如何通过直接调用后端C#的VM修改我的应用程序,以改用Xamarin MessagingCenter? - How can I modify my application from VM making direct calls to the back end C# to instead use Xamarin MessagingCenter? 为什么不能在ViewModel中声明的后端C#中填充数组? - Why can I not populate an array in my back-end C# that I declare in my ViewModel? 有什么办法可以在使用C#后端时加快向页面添加新元素的速度吗? - Is there any way that I can speed up the adding of new elements to a page when using the C# back end? 如果我在XAML中使用ContentPage.BindingContext定义绑定上下文,那么如何在C#后端访问它? - If I use ContentPage.BindingContext in XAML to define my binding context then how do I access that in the C# back end?
相关标签
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM