Skip to content
This repository has been archived by the owner on May 1, 2024. It is now read-only.

RadioButton #5349

Closed
wants to merge 2 commits into from
Closed

RadioButton #5349

wants to merge 2 commits into from

Conversation

andreinitescu
Copy link
Contributor

@andreinitescu andreinitescu commented Feb 22, 2019

Description of Change

RadioButton implementation.

Issues Resolved

API Changes

Added:

public class RadioButton : IToggleButtonElement, ToggleButtonElement, ...
{
      public event EventHandler Checked;
      public event EventHandler Unchecked;

      public bool IsChecked {get; set; }  //BP
      public string GroupName { get; set; } //BP
}
interface IToggleButtonElement
{
      event EventHandler Checked;
      event EventHandler Unchecked;
}
static class ToggleButtonElement
{
     public static bool IsChecked { get; set; }  // BP
}

Platforms Affected

  • Core/XAML (all platforms)
  • iOS
  • Android
  • UWP
  • MacOS

Behavioral/Visual Changes

GroupName property

The "RadioButton group Gallery - Legacy" has three tabs which show group behavior.
Radio-buttons with group name null are grouped by their parent.
Radio-buttons with same group name, no matter the hierarchy, are grouped at page level.

Video: https://www.youtube.com/watch?v=fwYSmvjCfbw

Before/After Screenshots

image

Testing Procedure

PR Checklist

  • Has automated tests
  • Rebased on top of the target branch at time of PR
  • Changes adhere to coding standard

@andreinitescu
Copy link
Contributor Author

andreinitescu commented Feb 22, 2019

Some thoughts:

  1. ToggleButtonElement can be used as a base class for all kind of toggle buttons, RadioButton, CheckBox, or any custom drawn toggle buttons, etc.

  2. On Android, I think RadioButtonRenderer could derive from ButtonFastRenderer because android.widget.RadioButton indirectly derives from android.widget.Button. Right now there's a lot of code duplication across the RadioButtonRenderer and ButtonFastRenderer.
    Also, I'd like to use ButtonLayoutManager for padding and text, but it's meant to deal with Button's ContentLayout. I'd have to refactor ButtonLayoutManager, maybe a BaseTextButtonLayoutManager which deals with padding and text?

  3. On iOS, it's a similar situation, much of the code in RadioButtonRenderer and ButtonRenderer is duplicated. I think it makes sense here to have a BaseButtonRenderer too.

I could have done all that already by I'm waiting for your input in order to plan my effort efficiently, thanks.

@rookiejava
Copy link
Collaborator

I'll add the Tizen implementation through the subsequent PR. 😉

@andreinitescu andreinitescu force-pushed the RadioButton branch 2 times, most recently from 3e9f3d3 to 9497861 Compare March 8, 2019 06:45
@samhouts samhouts self-requested a review April 8, 2019 16:40
@samhouts samhouts added the API-change Heads-up to reviewers that this PR may contain an API change label Apr 10, 2019
@samhouts
Copy link
Member

We also have #4369, and as you mentioned, there can be some shared code there. I wonder how we do that without causing a tangled mess.

Copy link
Member

@StephaneDelcroix StephaneDelcroix left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reviewed the Core part only.

Thanks for this, I'm quite happy with what I'm seeing. Could you please review and discuss naming and code sharing strategies (with Switch), and also review the groupName scoping ? Thanks

Xamarin.Forms.Core/ITextButtonElement.cs Outdated Show resolved Hide resolved
Xamarin.Forms.Core/ITextElement.cs Outdated Show resolved Hide resolved
Xamarin.Forms.Core/RadioButton.cs Outdated Show resolved Hide resolved
Xamarin.Forms.Core/RadioButton.cs Outdated Show resolved Hide resolved
Xamarin.Forms.Core/RadioButton.cs Outdated Show resolved Hide resolved
Xamarin.Forms.Core/ToggleButton.cs Outdated Show resolved Hide resolved
Xamarin.Forms.Core/ToggleButton.cs Outdated Show resolved Hide resolved
Xamarin.Forms.Core/ToggleButton.cs Outdated Show resolved Hide resolved
Xamarin.Forms.Core/ToggleButton.cs Outdated Show resolved Hide resolved
@samhouts samhouts added p/iOS 🍎 p/Android p/UWP p/macOS DO-NOT-MERGE-!!! 🛑 This is in progress and needs to be updated before it can be merged. labels Apr 24, 2019
@andreinitescu
Copy link
Contributor Author

andreinitescu commented May 21, 2019

This is very different than how the CheckBox works. I wish there was some feedback.

I put a considerable amount of time on this PR, I'm not sure how to continue with this now.

@PureWeen
Copy link
Contributor

PureWeen commented May 23, 2019

This is very different than how the CheckBox works.

Can you elaborate on this a bit? Are you saying that your approach of an implementation is different than how we did CheckBox? Or that fundamentally the two don't align so nothing should be shared betewen them?

ToggleButtonElement can be used as a base class for all kind of toggle buttons, RadioButton, CheckBox, or any custom drawn toggle buttons, etc.

I feel like the mileage here of a common base class wouldn't give us too much. I've found that just using interfaces to illustrate shared property is enough and then just reusing the bindable properties via the classes like ButtonElement

On Android, I think RadioButtonRenderer could derive from ButtonFastRenderer because android.widget.RadioButton indirectly derives from android.widget.Button. Right now there's a lot of code duplication across the RadioButtonRenderer and ButtonFastRenderer.
Also, I'd like to use ButtonLayoutManager for padding and text, but it's meant to deal with Button's ContentLayout. I'd have to refactor ButtonLayoutManager, maybe a BaseTextButtonLayoutManager which deals with padding and text?

Don't we want to add something like (or just exactly) ContentLayout to Checkbox and Radiobutton as well so you can position the text to the right or left of the checkbox and also effect the spacing? I hadn't dove into it yet but my plan for checkbox was to just pass the checkbox into buttonlayoutmanager, initialize it the same way it's initialized on material, and then just hope it all works :-)

Checkbox, RadioButton, and Button with an Image Source set
Are all basically the same thing right? The only difference being that CB and RB have state so the Drawable gets toggled based on that state. Otherwise they all just implement button on the native platforms and it's all just the same code we already use for Button + ImageSource + Text

On iOS, it's a similar situation, much of the code in RadioButtonRenderer and ButtonRenderer is duplicated. I think it makes sense here to have a BaseButtonRenderer too.

The implementation of ButtonRenderer on ios is super thin. It's more just passing things to Managers. I think just following the same strategy with the checkbox renderer of passing to the managers first is a good idea. Then once it's done and working we can look at refactoring to a base class. Either way I feel like this is a minor detail as most of the core shared code here was already refactored to managers

@andreinitescu
Copy link
Contributor Author

andreinitescu commented May 25, 2019

Can you elaborate on this a bit? Are you saying that your approach of an implementation is different than how we did CheckBox?

My RadioButton impl. is very different than the CheckBox impl. I wanted the RadioButton to be a button , with text, pressed states. After all on native platforms which have a CheckBox (and all have it except iOS), the native check-box control is actually a button.

Or that fundamentally the two don't align so nothing should be shared betewen them?

They should be aligned I think, they are both toggle buttons and share similar properties (same IsChecked property, same CheckChanged event, and same visual states, same border properties), just with a different UI, and the RadioButton has some extra logic for grouping.

I feel like the mileage here of a common base class wouldn't give us too much. I've found that just using interfaces to illustrate shared property is enough and then just reusing the bindable properties via the classes like ButtonElement

A toggle button base class would have important advantages in my opinion:

  1. Makes it very clear the responsibility of derived classes.
    The implementation for CheckBox and RadioButton would become very short and concise, which is much easier to understand and maintain than having a lot of code duplicated.
    I'm aware adding a base class must always be planned carefully because of single inheritance in C#, but for this specific purpose of a toggle button I think it should be OK.

  2. Makes it extensible for custom controls.
    If I want to create a custom drawn toggle button, I shouldn't have to create a class from scratch (IsChecked property, CheckedChanged event, border properties, visual state logic, etc.).

However, if you still think it doesn't make sense to have a common class, that's OK for me.

If you'd like me to still work on this control, please let me know feedback on the existing PR.
@StephaneDelcroix already placed feedback, but I'm confused. He is suggesting to have Switch as a base class. But the CheckBox doesn't have Switch as base class... Shouldn't CheckBox and RadioButton be aligned? Also, I'm not sure about having Switch as base class would be OK.
With little planning and guidance, I think I can work on refactoring the RadioButton to align with how you want it.

Thanks!

@PureWeen
Copy link
Contributor

PureWeen commented Jun 2, 2019

My RadioButton impl. is very different than the CheckBox impl. I wanted the RadioButton to be a button , with text, pressed states. After all on native platforms which have a CheckBox (and all have it except iOS), the native check-box control is actually a button.

I feel like very different is a bit of an overstatement :-) Currently I see CheckBox as a subset to your vision. CheckBox and RadioButton are effectively the same controls just with different images and different state behavior when you click on them. So I see whatever you are doing for RB as something that's just going to be symmetrically applied to CB by whatever means of sharing we come up with

The sharing at the cross platform level

Still not sure. Switch has IsToggled and CheckBox has IsChecked. Checkbox hasn't been released so should we call it IsToggled instead and create common base class? In a perfect world I'd say we go the android route and just create a CompoundButton that inherits from Button then just make switch, radio, checkbox all inherit from that because even at the platform level they are all just buttons with states so literally all the code we have that operates on buttons should just be applied to cb, rb, switch. Let me bounce this off a few people then get back to you.

Copy link
Contributor

@PureWeen PureWeen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Compilation error

Xamarin.Forms.Platform.iOS\Renderers\RadioButtonRenderer.cs(82,34): Error CS0311: The type 'Xamarin.Forms.RadioButton' cannot be used as type parameter 'T' in the generic type or method 'VisualElementExtensions.UseLegacyColorManagement<T>(T)'. There is no implicit reference conversion from 'Xamarin.Forms.RadioButton' to 'Xamarin.Forms.IElementConfiguration<Xamarin.Forms.RadioButton>'.
Xamarin.Forms.Platform.UAP\RadioButtonRenderer.cs(69,8): Error CS0311: The type 'Xamarin.Forms.RadioButton' cannot be used as type parameter 'T' in the generic type or method 'VisualElementExtensions.UseFormsVsm<T>(T)'. There is no implicit reference conversion from 'Xamarin.Forms.RadioButton' to 'Xamarin.Forms.IElementConfiguration<Xamarin.Forms.RadioButton>'.
Process 'msbuild.exe' exited with code '1'.
  • On iOS I can "uncheck" the RadioButton if I click it twice.
  • UWP isn't using the ContentLayout property
  • On iOS can we also refactor that to use the ButtonLayoutManager?

Tag = this;
}

void UpdateFont()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we just move all of this code into ButtonLayoutManager and anything else that's the same among the buttons? How much of the identical code can we just move into ButtonLayoutManager?

Copy link
Member

@StephaneDelcroix StephaneDelcroix left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

only re-reviewed the Core, we're not there yet (but making progress)

set { SetValue(IsCheckedProperty, value); }
}

public string GroupName
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you have

StackLayout
  Frame
    RadioButton
  Frame
    RadioButton

so the 2 radio buttons aren't in the same group ?

public static readonly BindableProperty ButtonSourceProperty = BindableProperty.Create(
nameof(ButtonSource), typeof(ImageSource), typeof(RadioButton), null);

public event EventHandler<CheckedChangedEventArgs> CheckedChanged;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see how this is resolved

[RenderWith(typeof(_RadioButtonRenderer))]
public partial class RadioButton : IToggleButtonElement, IToggleButtonController, ITextButtonElement
{
static Dictionary<string, List<WeakReference<RadioButton>>> _groupNameToElements;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the problem with this approach is the List keeps getting populated, and so the performance degrades over time

set { SetValue(IsCheckedProperty, value); }
}

public string GroupName
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also, you say the scope is the parent, but looking at GetVisualRoot, the scope is the page. what if I have this

Page
  StackLayout
    YesNoMaybeControl
    YesNoMaybeControl
    YesNoMaybeControl

}
}

static Element GetVisualRoot(Element element)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👎

[RenderWith(typeof(_RadioButtonRenderer))]
public partial class RadioButton : IToggleButtonElement, IToggleButtonController, ITextButtonElement
{
static Dictionary<string, List<WeakReference<RadioButton>>> _groupNameToElements;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've seen PurgeDeads, but we probably can do better


public event EventHandler<CheckedChangedEventArgs> CheckedChanged;

public bool IsChecked
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checked is probably a better name. We try to avoid property names containing multiple verbs. The IsXxxx construct is valid when Xxxx is an adjective. We can debate that in this case Checked is used as an adjective or not, I still think the name is overloaded

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not a big deal, I know we have IsEnabled already, and might want to reserve the Checked name for the event. so it's fine to leave it as is, and rename the event

namespace Xamarin.Forms
{
[RenderWith(typeof(_RadioButtonRenderer))]
public partial class RadioButton : Button
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

drop the partial. where is the IToggleElement interface you use to have ?


public const string IsCheckedVisualState = "IsChecked";

public static readonly BindableProperty IsCheckedProperty = BindableProperty.Create(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this isn't 2-Way, but should

public static readonly BindableProperty GroupNameProperty = BindableProperty.Create(
nameof(GroupName), typeof(string), typeof(RadioButton), null, propertyChanged: (b, o, n) => ((RadioButton)b).OnGroupNamePropertyChanged((string)o, (string)n));

public static readonly BindableProperty ButtonSourceProperty = BindableProperty.Create(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how does this works ? is this a spriteMap ? don't we need a source for both on and off ?

@jsuarezruiz
Copy link
Contributor

@andreinitescu There is some feedback (and also conflicts). Let us know if we can help you with something ;)

@samhouts samhouts removed the retarget-branch-required PR or associated issues target a milestone. Please target this PR to the matching branch. label Dec 3, 2019
@jfversluis jfversluis mentioned this pull request Dec 16, 2019
4 tasks
@samhouts samhouts closed this Jan 17, 2020
rmarinho added a commit that referenced this pull request Feb 18, 2020
* RadioButton

* Removed unused files

* Rebase and make it run

* First round of feedback

* Revert AppCompatButton -> AButton

* Cleaned minor usings

* Fix unselecting radiobutton on iOS

* Fixed Mac OS grouping

* [Android] Fix API29 usages

Co-authored-by: Andrei Nitescu <nitescua@yahoo.com>
Co-authored-by: Rui Marinho <me@ruimarinho.net>
@samhouts samhouts added this to the 4.6.0 milestone Feb 28, 2020
@SagarPanwala
Copy link

How end user can differentiate between RadioButton and Checkbox in iOS ?
Current implementation of checkbox in iOS is in Round Shape, which identically says user that if there are group of checkboxes, they can select only 1.
Can we make Square shape for checkbox in iOS as well or give a property for shape?

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[F100] RadioButton
8 participants