Programmatically creating and binding Android Spinners in MvvmCross
Asked Answered
P

1

12

I am building a cross-platform application that requires dynamically generating and binding Spinner controls. I was able to do that on iOS and Windows platforms, but have issues with Android. If I understand it correctly, I have to pass some arguments to the constructor of MvxSpinner - context and attrs, but I am not able to figure out how can I do that and what should be passed there. Also, I don't know how to then bind the ItemsSource and SelectedItem. I suppose a new binding set has to be created (similarly to the iOS version), but I am not able to figure it out on Android. Can you please give me some directions?

Here is my source code from Windows version:

private void InputColourAtlasChangedMessageHandler( InputColourAtlasChangedMessage message )
{
    ColourAtlas selected = message.SelectedColourAtlas;
    var vm = ViewModel as ColorMatchViewModel;

    List<ComboBox> newComboboxes = new List<ComboBox>();
    var currentCount = ColourPickersContainer.Children.Count;
    for ( int i = currentCount; i < message.ColourCodePartCount; i++ )
    {
        ComboBox cb = new ComboBox { Margin = new Thickness( 0, 0, 10, 0 ), PlaceholderText = "choose" };
        Binding itemsSourceBinding = new Binding();
        itemsSourceBinding.Path = new PropertyPath( "ColourPartLists[" + i + "]" );
        Binding selectedItemBinding = new Binding();
        selectedItemBinding.Path = new PropertyPath( "SelectedColourCodeParts[" + i + "]" );
        selectedItemBinding.Mode = BindingMode.TwoWay;
        cb.Tag = i;
        ColourPickersContainer.Children.Add( cb );
        cb.SetBinding( ComboBox.ItemsSourceProperty, itemsSourceBinding );
        cb.SetBinding( ComboBox.SelectedItemProperty, selectedItemBinding );
        cb.SelectionChanged += cb_SelectionChanged;                                
        BindingOperations.SetBinding( cb, ComboBox.SelectedItemProperty, selectedItemBinding );
        newComboboxes.Add( cb );
    }
    while ( ColourPickersContainer.Children.Count > message.ColourCodePartCount )
    {
        ColourPickersContainer.Children.RemoveAt( ColourPickersContainer.Children.Count - 1 );
    }
    _comboboxes = newComboboxes;            
}

void cb_SelectionChanged( object sender, SelectionChangedEventArgs e )
{
    var cb = sender as ComboBox;
    int changedIndex = ( int )cb.Tag;
    if ( e.AddedItems.Count > 0 )
    {
        ( DataContext as ColorMatchViewModel ).ColourCodePartChangedCommand.Execute( changedIndex );
    }
}

And here is the iOS version (doing more or less the same thing - although it just clears the existing spinners instead of reusing them):

private void InputColourAtlasChangedMessageHandler( InputColourAtlasChangedMessage message )
{
    ColourAtlas selected = message.SelectedColourAtlas;
    ClearPickers();
    var currentSet = this.CreateBindingSet<ColorMatchView, ColorMatchViewModel>();
    for ( int i = 0; i < message.ColourCodePartCount; i++ )
    {
        var j = i;
        UIPickerView picker = new UIPickerView();
        var pickerViewModel = new MvxPickerViewModel( picker );
        picker.Model = pickerViewModel;
        picker.ShowSelectionIndicator = true;
        pickerViewModel.SelectedItemChanged += vm_SelectedItemChanged;
        var textView = new PaddedUITextField( new RectangleF( 10, 50 + i * 40, 300, 30 ) );
        Add( textView );
        textView.InputView = picker;
        _pickers.Add( picker );
        _textViews.Add( textView );
        currentSet.Bind( textView ).For( t => t.Text ).To( "SelectedColourCodeParts[" + i + "]" );
        currentSet.Bind( pickerViewModel ).For( p => p.ItemsSource ).To( "ColourPartLists[" + i + "]" );
        currentSet.Bind( pickerViewModel ).For( p => p.SelectedItem ).To( "SelectedColourCodeParts[" + i + "]" );
        currentSet.Bind( pickerViewModel ).For( p => p.SelectedChangedCommand ).To( vm => vm.ColourCodePartChangedCommand ).CommandParameter( j );
    }
    currentSet.Apply();
    UpdateLayout( View.Frame.Size );
}

private void ClearPickers()
{
    foreach ( var picker in _pickers )
    {
        var vm = picker.Model as MvxPickerViewModel;
        vm.SelectedItemChanged -= vm_SelectedItemChanged;
        picker.RemoveFromSuperview();
    }
    foreach ( var textView in _textViews )
    {
        textView.RemoveFromSuperview();
    }
    _pickers.Clear();
    _textViews.Clear();
}  

Partial (and not functional) outline for the Android version I have now is as follows:

private void InputColourAtlasChangedMessageHandler( InputColourAtlasChangedMessage message )
        {
            ColourAtlas selected = message.SelectedColourAtlas;
            var layout = FindViewById<LinearLayout>( Resource.Id.spinnerList );
            ClearPickers();

            for ( int i = 0; i < message.ColourCodePartCount; i++ )
            {
                MvxSpinner spinner = new MvxSpinner( Context??, Attrs??);
                MvxAdapter adapter = new MvxAdapter( this );

                spinner.ItemSelected += spinner_ItemSelected;
                layout.AddView( spinner );
                _spinners.Add( spinner );
            }
        }

        void spinner_ItemSelected( object sender, AdapterView.ItemSelectedEventArgs e )
        {
            var changedIndex = _spinners.IndexOf( sender as MvxSpinner );
            ( DataContext as ColorMatchViewModel ).ColourCodePartChangedCommand.Execute( changedIndex );
        }

        private void ClearPickers()
        {
            var layout = FindViewById<LinearLayout>( Resource.Id.spinnerList );
            foreach ( var spinner in _spinners )
            {
                spinner.ItemSelected -= spinner_ItemSelected;
            }
            layout.RemoveAllViews();
            _spinners.Clear();
        }
Parados answered 23/4, 2015 at 11:3 Comment(2)
github.com/MvvmCross/MvvmCross-Tutorials/blob/master/… and github.com/MvvmCross/MvvmCross-Tutorials/blob/master/… - both use data-binding rather than code behind.Seedbed
Thank you Stuart, that gives me the solution to the data binding part of the question, but I still now have problem with progrmmatically creating the MvxSpinner in code. How can that be done? What should be passed to it's constructor?Parados
M
5

Here is an example on how to create an mvxspinner by code in Activity.OnCreate:

_bindingContext = new MvxAndroidBindingContext(this, new LayoutInflaterProvider(LayoutInflater), _viewModel);
var view = (LinearLayout)_bindingContext.BindingInflate(Resource.Layout.Main, null);
SetContentView(view);
var spinner = new MvxSpinner(this, null, new MvxAdapter(this, _bindingContext));
view.AddView(spinner);

Then if you wanted your LayoutInflaterProvider might look something like this:

public class LayoutInflaterProvider
    : IMvxLayoutInflater
{
    public LayoutInflaterProvider(LayoutInflater layoutInflater)
    {
        LayoutInflater = layoutInflater;
    }

    public LayoutInflater LayoutInflater { get; private set; }
}

I was initially looking at this tutorial.

Molder answered 25/4, 2015 at 15:48 Comment(2)
Thank you for the response, only thing I am missing is the LayoutInflaterProvider type, how to access it?Parados
Oops sorry that is actually it's own custom class. You can find it here: github.com/MvvmCross/MvvmCross-Tutorials/blob/…. I'll update my answer as well.Molder

© 2022 - 2024 — McMap. All rights reserved.