Search code examples
xamlxamarin.formsbindingpicker

Why is my binding to my picker not working with xaml/xamarin?


XAML Xamarin 5 - MacOS

XAML:

<Picker x:Name="StylesPicker" Title="Select stylesheet" HorizontalOptions="FillAndExpand" SelectedIndexChanged="StylesPicker_SelectedIndexChanged" SelectedItem="{Binding Path=vrrData.Style, Mode=TwoWay}"/>

Code behind:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using AppKit;
using VisitsRota.ViewModels;
using Xamarin.Essentials;
using Xamarin.Forms;

namespace VisitsRota.Views
{
    public partial class ElderlyInfirmPage : ContentPage
    {
        public ObservableCollection<string> StylesList { get; set; }

        public ElderlyInfirmPage()
        {
            InitializeComponent();
            BindingContext = new ElderlyInfirmViewModel();

            string[] stylesArray = Directory.GetFiles("/Users/Shared/VisitsRota.MacOS/Styles", "ElderlyInfirm-Schedule-*.xsl")
                                      .Select(file => Path.GetFileName(file)).ToArray<string>();
            StylesList = new ObservableCollection<string>(stylesArray);
            StylesPicker.ItemsSource = StylesList;
     
        }    

        async private void InstallStyleButton_Clicked(object sender, EventArgs e)
        {
            var customFileType =
                new FilePickerFileType(new Dictionary<DevicePlatform, IEnumerable<string>>
                {
                    {DevicePlatform.macOS, new[] {"xsl"} }
                });

            var pickResult = await FilePicker.PickAsync(new PickOptions
            {
                FileTypes = customFileType,
                PickerTitle = "Select template to install"

            });

            if(pickResult != null)
            {
                StylesList.Add(pickResult.FileName);
            }
        }

        void StylesPicker_SelectedIndexChanged(System.Object sender, System.EventArgs e)
        {
            var picker = (Picker)sender;
            int selectedIndex = picker.SelectedIndex;

            if(selectedIndex != -1)
            {
                string x =   (string)picker.ItemsSource[selectedIndex];
            }
        }
    }
}

View Model:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Xml.Serialization;

namespace VisitsRota.ViewModels
{
    public class ElderlyInfirmViewModel : BaseViewModel
    {
        public List<string> StylesList { get; set; }
        public List<string> ListMonths { get; }
        public VisitsRotaData vrrData { get; set; }

        public ElderlyInfirmViewModel()
        {
            vrrData = DeserializeFromXml<VisitsRotaData>("/Users/Shared/VisitsRota.MacOS/VisitsRotaData.xml");

            // Should this list be private?
            ListMonths = DateTimeFormatInfo.CurrentInfo.MonthNames.TakeWhile(m => m != String.Empty).ToList();

            StylesList = Directory.GetFiles("/Users/Shared/VisitsRota.MacOS/Styles", "ElderlyInfirm-Schedule-*.xsl")
                                      .Select(file => Path.GetFileName(file)).ToList<string>();

                
        }

        public T DeserializeFromXml<T>(string filePath)
        {
            try
            {
                if (!System.IO.File.Exists(filePath))
                    throw new ArgumentNullException(filePath + " not Exists");
                using (System.IO.StreamReader reader = new System.IO.StreamReader(filePath))
                {
                    System.Xml.Serialization.XmlSerializer xs = new System.Xml.Serialization.XmlSerializer(typeof(T));
                    T ret = (T)xs.Deserialize(reader);
                    return ret;
                }
            }
            catch (Exception ex)
            {
                return default(T);
            }
        }

    }
   
    [XmlType(TypeName="VisitsRotaData")]
    public class VisitsRotaData : BaseViewModel
    {

        private ObservableCollection<string> _listelders;
        private ObservableCollection<string> _listpublishers;
        private string _style;

        [XmlArray("Elders")]
        [XmlArrayItem("Elder")]
        public ObservableCollection<string> ListElders
        {
            get { return _listelders; }
            set {
                _listelders = value;
                OnPropertyChanged("ListElders");
            }
        }

        [XmlArray("Publishers")]
        [XmlArrayItem("Publisher")]
        public ObservableCollection<string> ListPublishers
        {
            get { return _listpublishers; }
            set
            {
                _listpublishers = value;
                OnPropertyChanged("ListPublishers");
            }
        }

        [XmlElement]
        public string Style
        {
            get { return _style; }
            set
            {
                _style = value;
                OnPropertyChanged("Style");
            }
        }
    }
}

XML Data:

<?xml version="1.0" encoding="UTF-8" ?>
<VisitsRotaData>
    <Elders>
        <Elder>Elder 1</Elder>
        <Elder>Elder 2</Elder>
        <Elder>Elder 3</Elder>
        <Elder>Elder 4</Elder>
        <Elder>Elder 5</Elder>
        <Elder>Elder 6</Elder>
        <Elder>Elder 7</Elder>
        <Elder>Elder 8</Elder>
    </Elders>
    <Publishers>
       <Publisher>Publisher 1</Publisher>
       <Publisher>Publisher 2</Publisher>
       <Publisher>Publisher 3</Publisher>
       <Publisher>Publisher 4</Publisher>
       <Publisher>Publisher 5</Publisher>
       <Publisher>Publisher 6</Publisher>
       <Publisher>Publisher 7</Publisher>
       <Publisher>Publisher 8</Publisher>
       <Publisher>Publisher 9</Publisher>
       <Publisher>Publisher 10</Publisher>
    </Publishers>
    <Style>ElderlyInfirm-Schedule-v2.xsl</Style>
</VisitsRotaData>

The Picker populates with with the files from the folder. That is fine. But when the window displays it has the first item from the picker selected and not the second (which is the value of Styles.

Expected behaviour:

Bind the value of the Picker to the Styles property. Picker defaults to the existing value.


Solution

  • It looks like I have worked it out. 😊 This answer here pointed me in the right direction.

    XAML

    I changed the Binding in the XAML like this for the Picker:

    <Picker x:Name="StylesPicker" Title="Select stylesheet" 
            HorizontalOptions="FillAndExpand" 
            ItemsSource="{Binding StylesList}" 
            SelectedItem="{Binding Path=vrrData.Style, Mode=TwoWay}"/>
    

    As you can see, I have also removed the StylesPicker_SelectedIndexChanged handler.

    Code Behind

    I removed the public ObservableCollection<string> StylesList { get; set; } from this class.

    View Model

    I pasted in the aforementioned StylesList property and populated it in the ElderlyInfirmViewModel constructor.

    The Style property remained unchanged in the VisitsRotaData class.


    Sample

    Now, when my window displays, the Picker defaults to the value from my XML file: enter image description here

    I think I was complicating it by having the ItemSource in the code behind and the SelectedItem in the view model. By keeping it all in the one place (the BindingContext of the Page) it works.