SweetTomato
SweetTomato

Reputation: 569

How to convert listener from Java to C#

I need to display radio options as a grid and found this gist offering a RadioGridGroup class I can use instead of the RadioGroup provided in Android. (https://gist.github.com/saiaspire/a73135cfee1110a64cb0ab3451b6ca33)

I have converted everything into the code below and just wanted to ask some questions.

(1) Is it okay that I just deleted all the "final" keywords? ie int result = sNextGeneratedId.Get();

(2) Is converting parent == RadioGridGroup.this && child instanceof AppCompatRadioButton to parent is RadioGridGroup && child is AppCompatRadioButton an accurate conversion?

(3) In the CheckedStateTracker which extends/implements a Listener.. It has access to the RadioGridGroup fields like mProtectFromCheckedChange in the Java example, but in the C# example, those variables are inaccessable there. How do I solve this?

(4) For the listeners I had them implement Java.Lang.Object as well so I don't need to implement the Dispose side CheckedStateTracker implements CompoundButton.OnCheckedChangeListener became CheckedStateTracker : Java.Lang.Object, CompoundButton.IOnCheckedChangeListener

using System;
using Android.Content;
using Android.Support.V7;
using Android.Support.V7.Widget;
using Android.Text;
using Android.Util;
using Android.Views;
using Android.Views.Accessibility;
using Android.Widget;

using Java.Util.Concurrent.Atomic;

/**
 * https://stackoverflow.com/questions/60764344/how-to-convert-listener-from-java-to-c-sharp
 * 
 * <p>This class is used to create a multiple-exclusion scope for a set of radio
 * buttons. Checking one radio button that belongs to a radio group unchecks
 * any previously checked radio button within the same group.</p>
 * <p/>
 * <p>Intially, all of the radio buttons are unchecked. While it is not possible
 * to uncheck a particular radio button, the radio group can be cleared to
 * remove the checked state.</p>
 * <p/>
 * <p>The selection is identified by the unique id of the radio button as defined
 * in the XML layout file.</p>
 * <p/>
 * <p>See
 * {@link android.widget.GridLayout.LayoutParams GridLayout.LayoutParams}
 * for layout attributes.</p>
 *
 * @see AppCompatRadioButton
 */
public class RadioGridGroup: Android.Support.V7.Widget.GridLayout
{
    private static AtomicInteger sNextGeneratedId = new AtomicInteger(1);
    private int mCheckedId = -1;
    private CompoundButton.IOnCheckedChangeListener mChildOnCheckedChangeListener;
    private bool mProtectFromCheckedChange = false;
    private OnCheckedChangeListener mOnCheckedChangeListener;
    private PassThroughHierarchyChangeListener mPassThroughListener;

    public RadioGridGroup(Context context) : base(context)
    {
        init();

    }

    public RadioGridGroup(Context context, IAttributeSet attrs): base(context, attrs)
    {
        init();
    }

    private void init()
    {
        mChildOnCheckedChangeListener = new CheckedStateTracker();
        mPassThroughListener = new PassThroughHierarchyChangeListener();
        base.SetOnHierarchyChangeListener(mPassThroughListener);
    }

    public override void SetOnHierarchyChangeListener(IOnHierarchyChangeListener listener)
    {
        mPassThroughListener.mOnHierarchyChangeListener = listener;
    }

    protected override void OnFinishInflate()
    {
        base.OnFinishInflate();

        if (mCheckedId != -1)
        {
            mProtectFromCheckedChange = true;
            SetCheckedStateForView(mCheckedId, true);
            mProtectFromCheckedChange = false;
            setCheckedId(mCheckedId);
        }
    }

    public override void AddView(View child, int index, ViewGroup.LayoutParams prs)
    {
        if (child is AppCompatRadioButton) {
            AppCompatRadioButton button = (AppCompatRadioButton)child;
            if (button.Checked)
            {
                mProtectFromCheckedChange = true;
                if (mCheckedId != -1)
                {
                    SetCheckedStateForView(mCheckedId, false);
                }
                mProtectFromCheckedChange = false;
                setCheckedId(button.Id);
            }
        }

        base.AddView(child, index, prs);
    }

    public void check(int id)
    {
        if (id != -1 && (id == mCheckedId))
        {
            return;
        }

        if (mCheckedId != -1)
        {
            SetCheckedStateForView(mCheckedId, false);
        }

        if (id != -1)
        {
            SetCheckedStateForView(id, true);
        }

        setCheckedId(id);
    }

    private void setCheckedId(int id)
    {
        mCheckedId = id;
        if (mOnCheckedChangeListener != null)
        {
            mOnCheckedChangeListener.onCheckedChanged(this, mCheckedId);
        }
    }

    private void SetCheckedStateForView(int viewId, bool chkd)
    {
        View checkedView = FindViewById(viewId);
        if (checkedView != null && checkedView is AppCompatRadioButton) {
            ((AppCompatRadioButton)checkedView).Checked = (chkd);
        }
    }

    public int GetCheckedCheckableImageButtonId()
    {
        return mCheckedId;
    }

    public void clearCheck()
    {
        check(-1);
    }

    public void SetOnCheckedChangeListener(OnCheckedChangeListener listener)
    {
        mOnCheckedChangeListener = listener;
    }

    public override void OnInitializeAccessibilityEvent(AccessibilityEvent ev)
    {
        base.OnInitializeAccessibilityEvent(ev);
        ev.ClassName = (this.GetType().Name);
    }

    public override void OnInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)
    {
        base.OnInitializeAccessibilityNodeInfo(info);
        info.ClassName = (this.GetType().Name);
    }

    public interface OnCheckedChangeListener
    {
        void onCheckedChanged(RadioGridGroup group, int checkedId);
    }

    internal class CheckedStateTracker : Java.Lang.Object, CompoundButton.IOnCheckedChangeListener
    {
        public void OnCheckedChanged(CompoundButton buttonView, bool isChecked)
        {
            if (mProtectFromCheckedChange)
            {
                return;
            }

            mProtectFromCheckedChange = true;
            if (mCheckedId != -1)
            {
                SetCheckedStateForView(mCheckedId, false);
            }
            mProtectFromCheckedChange = false;

            int id = buttonView.Id;
            SetCheckId(id);
        }
    }

    internal class PassThroughHierarchyChangeListener : Java.Lang.Object
            ViewGroup.IOnHierarchyChangeListener
    {
        internal ViewGroup.IOnHierarchyChangeListener mOnHierarchyChangeListener;


        public void OnChildViewAdded(View parent, View child)
        {
            if (parent is RadioGridGroup && child is AppCompatRadioButton) {
                int id = child.Id;
                // generates an id if it's missing
                if (id == View.NoId)
                {
                    id = View.GenerateViewId();
                    child.Id = (id);
                }
                ((AppCompatRadioButton)child).SetOnCheckedChangeListener(
                        mChildOnCheckedChangeListener);
            }

            if (mOnHierarchyChangeListener != null)
            {
                mOnHierarchyChangeListener.OnChildViewAdded(parent, child);
            }
        }

        public void OnChildViewRemoved(View parent, View child)
        {
            if (parent is RadioGridGroup && child is AppCompatRadioButton) {
                ((AppCompatRadioButton)child).SetOnCheckedChangeListener(null);
            }

            if (mOnHierarchyChangeListener != null)
            {
                mOnHierarchyChangeListener.OnChildViewRemoved(parent, child);
            }
        }
    }

    public static int GenerateViewId()
    {
        for (; ; )
        {
            int result = sNextGeneratedId.Get();

            // aapt-generated IDs have the high byte nonzero; clamp to the range under that.
            int newValue = result + 1;
            if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0.

            if (sNextGeneratedId.CompareAndSet(result, newValue))
            {
                return result;
            }
        }
    }
}

UPDATE:

Below is my code which includes passing the enclosing parent to the listeners, and using those to compare the parent as well as to access the parent's fields. I've also added readonly to just the one location where it didn't error out to do so.

using System;
using Android.Content;
using Android.Support.V7;
using Android.Support.V7.Widget;
using Android.Text;
using Android.Util;
using Android.Views;
using Android.Views.Accessibility;
using Android.Widget;

using Java.Util.Concurrent.Atomic;

/**
 * https://stackoverflow.com/questions/60764344/how-to-convert-listener-from-java-to-c-sharp
 * 
 * <p>This class is used to create a multiple-exclusion scope for a set of radio
 * buttons. Checking one radio button that belongs to a radio group unchecks
 * any previously checked radio button within the same group.</p>
 * <p/>
 * <p>Intially, all of the radio buttons are unchecked. While it is not possible
 * to uncheck a particular radio button, the radio group can be cleared to
 * remove the checked state.</p>
 * <p/>
 * <p>The selection is identified by the unique id of the radio button as defined
 * in the XML layout file.</p>
 * <p/>
 * <p>See
 * {@link android.widget.GridLayout.LayoutParams GridLayout.LayoutParams}
 * for layout attributes.</p>
 *
 * @see AppCompatRadioButton
 */
public class RadioGridGroup : Android.Support.V7.Widget.GridLayout
{
    private static readonly AtomicInteger sNextGeneratedId = new AtomicInteger(1);
    private int mCheckedId = -1;
    private CompoundButton.IOnCheckedChangeListener mChildOnCheckedChangeListener;
    private bool mProtectFromCheckedChange = false;
    private OnCheckedChangeListener mOnCheckedChangeListener;
    private PassThroughHierarchyChangeListener mPassThroughListener;

    public RadioGridGroup(Context context) : base(context)
    {
        Init();
    }

    public RadioGridGroup(Context context, IAttributeSet attrs): base(context, attrs)
    {
        Init();
    }

    private void Init()
    {
        mChildOnCheckedChangeListener = new CheckedStateTracker(this);
        mPassThroughListener = new PassThroughHierarchyChangeListener(this);
        base.SetOnHierarchyChangeListener(mPassThroughListener);
    }

    public override void SetOnHierarchyChangeListener(IOnHierarchyChangeListener listener)
    {
        mPassThroughListener.mOnHierarchyChangeListener = listener;
    }

    protected override void OnFinishInflate()
    {
        base.OnFinishInflate();

        if (mCheckedId != -1)
        {
            mProtectFromCheckedChange = true;
            SetCheckedStateForView(mCheckedId, true);
            mProtectFromCheckedChange = false;
            SetCheckedId(mCheckedId);
        }
    }

    public override void AddView(View child, int index, ViewGroup.LayoutParams prs)
    {
        if (child is AppCompatRadioButton) {
            AppCompatRadioButton button = (AppCompatRadioButton)child;
            if (button.Checked)
            {
                mProtectFromCheckedChange = true;
                if (mCheckedId != -1)
                {
                    SetCheckedStateForView(mCheckedId, false);
                }
                mProtectFromCheckedChange = false;
                SetCheckedId(button.Id);
            }
        }

        base.AddView(child, index, prs);
    }

    public void check(int id)
    {
        if (id != -1 && (id == mCheckedId))
        {
            return;
        }

        if (mCheckedId != -1)
        {
            SetCheckedStateForView(mCheckedId, false);
        }

        if (id != -1)
        {
            SetCheckedStateForView(id, true);
        }

        SetCheckedId(id);
    }

    private void SetCheckedId(int id)
    {
        mCheckedId = id;
        if (mOnCheckedChangeListener != null)
        {
            mOnCheckedChangeListener.onCheckedChanged(this, mCheckedId);
        }
    }

    private void SetCheckedStateForView(int viewId, bool chkd)
    {
        View checkedView = FindViewById(viewId);
        if (checkedView != null && checkedView is AppCompatRadioButton) {
            ((AppCompatRadioButton)checkedView).Checked = (chkd);
        }
    }

    public int GetCheckedCheckableImageButtonId()
    {
        return mCheckedId;
    }

    public void clearCheck()
    {
        check(-1);
    }

    public void SetOnCheckedChangeListener(OnCheckedChangeListener listener)
    {
        mOnCheckedChangeListener = listener;
    }

    public override void OnInitializeAccessibilityEvent(AccessibilityEvent ev)
    {
        base.OnInitializeAccessibilityEvent(ev);
        ev.ClassName = (this.GetType().Name);
    }

    public override void OnInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)
    {
        base.OnInitializeAccessibilityNodeInfo(info);
        info.ClassName = (this.GetType().Name);
    }

    public interface OnCheckedChangeListener
    {
        void onCheckedChanged(RadioGridGroup group, int checkedId);
    }

    private class CheckedStateTracker : Java.Lang.Object, CompoundButton.IOnCheckedChangeListener
    {
        readonly RadioGridGroup enclosingClass;

        public CheckedStateTracker(RadioGridGroup enclosing)
        {
            enclosingClass = enclosing;
        }

        public void OnCheckedChanged(CompoundButton buttonView, bool isChecked)
        {
            if (enclosingClass.mProtectFromCheckedChange)
            {
                return;
            }

            enclosingClass.mProtectFromCheckedChange = true;
            if (enclosingClass.mCheckedId != -1)
            {
                enclosingClass.SetCheckedStateForView(enclosingClass.mCheckedId, false);
            }
            enclosingClass.mProtectFromCheckedChange = false;

            int id = buttonView.Id;
            enclosingClass.SetCheckedId(id);
        }
    }

    private class PassThroughHierarchyChangeListener : Java.Lang.Object,
            ViewGroup.IOnHierarchyChangeListener
    {
        internal ViewGroup.IOnHierarchyChangeListener mOnHierarchyChangeListener;
        readonly RadioGridGroup enclosingClass;

        public PassThroughHierarchyChangeListener(RadioGridGroup enclosing)
        {
            enclosingClass = enclosing;
        }

        public void OnChildViewAdded(View parent, View child)
        {
            if (parent == enclosingClass && child is AppCompatRadioButton) {
                int id = child.Id;
                // generates an id if it's missing
                if (id == View.NoId)
                {
                    id = View.GenerateViewId();
                    child.Id = (id);
                }
                ((AppCompatRadioButton)child).SetOnCheckedChangeListener(
                        enclosingClass.mChildOnCheckedChangeListener);
            }

            if (mOnHierarchyChangeListener != null)
            {
                mOnHierarchyChangeListener.OnChildViewAdded(parent, child);
            }
        }

        public void OnChildViewRemoved(View parent, View child)
        {
            if (parent == enclosingClass && child is AppCompatRadioButton) {
                ((AppCompatRadioButton)child).SetOnCheckedChangeListener(null);
            }

            if (mOnHierarchyChangeListener != null)
            {
                mOnHierarchyChangeListener.OnChildViewRemoved(parent, child);
            }
        }
    }

    public static int GenerateViewId()
    {


    for (; ; )
    {
        int result = sNextGeneratedId.Get();

        // aapt-generated IDs have the high byte nonzero; clamp to the range under that.
        int newValue = result + 1;
        if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0.

        if (sNextGeneratedId.CompareAndSet(result, newValue))
        {
            return result;
        }
    }
}

}

Upvotes: 1

Views: 187

Answers (1)

Rajan Prasad
Rajan Prasad

Reputation: 1679

To answer your multiple questions:

  1. It is ok in this scenario. You need to first understand what final is used for in Java. Here, you can replace final with readonly for all the three fields/variables.
  2. The conversion isn't accurate. In the first case, you are doing an instance comparison of the parent instance whereas in the second case you are doing the type comparison of the parent instance.
  3. As per this SO post , in C#, the nested class do not hold reference of the enclosing class. So it is basically not possible to do something like RadioGridGroup.this in C#, because even though RadioGridGroup encloses the class where RadiGridGroup.this field is being accessed, the reference to that is not available in the enclosed class. Hence, you cannot refer to any private member of the this instance of the RadioGridGroup class. The solution to this is simple. Just treat RadioGridGroup as any other class inside PassThroughHierarchyChangeListener and pass a reference to the this instance of the RadioGridGroup to PassThroughHierarchyChangeListener constructor. So your code would change to something like

    internal class CheckedStateTracker : CompoundButton.IOnCheckedChangeListener {
    
        //...
        readonly RadioGridGroup enclosingClass;
    
        //Constructor of CheckedStateTracker
        //With this, access the members of RadioGridGroup class with enclosingClass scope
        //So mProtectFromCheckedChange becomes enclosingClass.mProtectFromCheckedChange
    
        CheckedStateTracker ( RadioGridGroup enclosing){
            enclosingClass = enclosing;
        }
    }
    

Finally, you instantiate your mChildOnCheckedChangeListener as the following

      //Line 51
      mChildOnCheckedChangeListener = new CheckedStateTracker(this);

Upvotes: 1

Related Questions