Exposing Switch.IsToggled property for binding in XAML from custom control

azatrainazatrain ✭✭USMember ✭✭

I feel like I'm close on this, but can't quite get over the hump so I figured I'd ask the experts. I've got a custom ViewCell control that is used in a list box to allow multiple selections from that list box. I've got an image, a label, and a switch in that control. I've got the bindings for the image and label working like a charm, but can't seem to figure out how to expose the IsToggled property of the switch so that I can bind to it in my XAML. I want to be able to react to the user toggling that switch as it happens. Any help is very much appreciated.

Here is the code for the ViewCell control:

    public class OperationalLayerListItem : ViewCell
    {
        public static readonly BindableProperty NameProperty =
            BindableProperty.Create("Name", typeof(string), typeof(OperationalLayerListItem));

        public static readonly BindableProperty ImageSourceProperty =
            BindableProperty.Create("ImageSource", typeof(string), typeof(OperationalLayerListItem));

        public static readonly BindableProperty ItemSelectedProperty =
            BindableProperty.Create("ItemSelected", typeof(bool), typeof(OperationalLayerListItem), false);

        public string Name
        {
            get { return (string)GetValue(NameProperty); }
            set { SetValue(NameProperty, value); }
        }

        public string ImageSource
        {
            get { return (string)GetValue(ImageSourceProperty); }
            set { SetValue(ImageSourceProperty, value); }
        }

        public bool ItemSelected
        {
            get { return (bool)GetValue(ItemSelectedProperty); }
            set { SetValue(ItemSelectedProperty, value); }
        }


        Label lbName;
        Image imgThumbnail;

        public OperationalLayerListItem()
        {
            lbName = new Label { HorizontalOptions = LayoutOptions.StartAndExpand };
            imgThumbnail = new Image { VerticalOptions = LayoutOptions.Center };

            Grid infoLayout = new Grid
            {
                ColumnDefinitions =
                {
                    new ColumnDefinition { Width = new GridLength(1,GridUnitType.Star) },
                    new ColumnDefinition { Width = new GridLength(3,GridUnitType.Star) }
                },
                HorizontalOptions = LayoutOptions.FillAndExpand
            };

            infoLayout.Children.Add(imgThumbnail, 0, 0);
            infoLayout.Children.Add(lbName, 1, 0);

            var cellWrapper = new Grid
            {
                Padding = 10,
                ColumnDefinitions =
                {
                    new ColumnDefinition { Width = new GridLength(1,GridUnitType.Auto) },
                    new ColumnDefinition { Width = new GridLength(1,GridUnitType.Star) },
                }
            };

            var sw = new Switch();
            sw.SetBinding(Switch.IsToggledProperty, "IsSelected");
            sw.SetBinding(Switch.IsToggledProperty, "ItemSelected", BindingMode.TwoWay);

            cellWrapper.Children.Add(infoLayout, 0, 0);
            cellWrapper.Children.Add(sw, 1, 0);

            View = cellWrapper;
        }

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

            if (BindingContext != null)
            {
                lbName.Text = Name;
                imgThumbnail.Source = ImageSource;
            }
        }
    }

Here is the pertinent code from the ViewModel:

        private ObservableCollection<SelectableItemWrapper<MapService>> _OperationalLayers;
        public ObservableCollection<SelectableItemWrapper<MapService>> OperationalLayers
        {
            get
            {
                return _OperationalLayers;
            }

            set
            {
                if (_OperationalLayers != value)
                {
                    _OperationalLayers = value;
                    OnPropertyChanged("OperationalLayers");
                }
            }
        }

    private string _ItemSelected;
        public string ItemSelected
        {
            get
            {
                return _ItemSelected;
            }

            set
            {
                if (_ItemSelected != value)
                {
                    _ItemSelected = value;
                    OnPropertyChanged("ItemSelected");
                }
            }
        }

Here is the XAML for my UI:

<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:viewModels="clr-namespace:EFCloud_POC.ViewModels"
             xmlns:components="clr-namespace:EFCloud_POC.Components"
             x:Class="EFCloud_POC.Views.AddDataTool" x:Name="AddDataTool">

  <ContentView.BindingContext>
    <viewModels:AddDataToolViewModel />
  </ContentView.BindingContext>

  <ListView ItemsSource="{Binding OperationalLayers, Mode=TwoWay}<del></del>">
    <ListView.ItemTemplate>
      <DataTemplate>
        <components:OperationalLayerListItem Name="{Binding Item.Label}" ImageSource="{Binding Item.Thumbnail}"
                                             ItemSelected="{Binding Source={x:Reference Name=AddDataTool}, Path=BindingContext.ItemSelected}"/>
      </DataTemplate>
    </ListView.ItemTemplate>
  </ListView>
</ContentView>

Thanks ...

Answers

  • JulienRosenJulienRosen ✭✭✭✭ CAMember ✭✭✭✭
    edited August 2016

    there is so much code here, when all you are trying to do is monitor a switch.

    <Switch IsToggled="{Binding IsToggled}" />
    
    public bool IsToggled {get;set;}
    

    Not sure why you have ItemSelected as a string, or IsSelected whatsoever. Switches dont get selected, they get toggled.

  • azatrainazatrain ✭✭ USMember ✭✭

    Thanks for your response Julien. Yeh that would be the way to go if I wasn't doing a custom, reusable control for my list view items. I'm having trouble exposing that Switch.IsToggled property through that custom control so I can bind to it in XAML.

  • JulienRosenJulienRosen ✭✭✭✭ CAMember ✭✭✭✭
    edited September 2016

    Are you having trouble passing in the initial state of the switch? I'm not really understanding the problem here. The fact that the switch is inside a custom control doesn't change anything, unless you are having issues with the proper BindingContext

  • JoeMankeJoeManke ✭✭✭✭✭ USMember ✭✭✭✭✭

    Set the BindingContext of the Switch (and your other child Views, probably) to this (the ViewCell).

  • azatrainazatrain ✭✭ USMember ✭✭

    Thanks for the suggestions Julien and Joe. With enough time I may have been able to get this custom control working, but I had to abandon ship and just get her done. My end game was to get the actual list item when a user clicked the switch in addition to the true/false toggle value for the switch. For each item I ended up binding the IsToggled property of the switch to a boolean property on the object used to generate the list item. I then subscribed to the property changed event exposed by the object's model to determine which item that switch belonged to. So I don't have my custom control or the elegance I was after, but I do have it working like I wanted.

    Here is the xaml for the View Cell ...

        <ListView.ItemTemplate>
          <DataTemplate>
            <ViewCell>
              <StackLayout Orientation="Horizontal" VerticalOptions="CenterAndExpand" HorizontalOptions="FillAndExpand" 
                           Padding="10" Spacing="10">
                <Image Source="{Binding Thumbnail}" HorizontalOptions="Start" />
                <Label Text="{Binding Label}" HorizontalOptions="FillAndExpand" />
                <Switch IsToggled="{Binding Visible, Mode=TwoWay}" HorizontalOptions="End" />
              </StackLayout>
            </ViewCell>
    
          </DataTemplate>
        </ListView.ItemTemplate>
    

    Here's the population of the observable collection used to populate the list view and the subscription to the property changed event.

                    OperationalLayers = new ObservableCollection<MapService>((cm.OperationalLayers).ToList());
    
                    foreach (MapService ms in OperationalLayers)
                    {
                        ms.PropertyChanged += MapService_PropertyChanged;
                    }
    

    And here's the code for that event where I retrieve the object associated with the clicked switch.

    private void MapService_PropertyChanged(object sender, PropertyChangedEventArgs e)
            {
                //  This method is run when a property on a MapService changes
    
                switch (e.PropertyName.ToUpper())
                {
                    case "VISIBLE":
                        //  The visible property has changed, toggle map layer visibility
    
                        ToggleLayerVisibility((MapService)sender);
                        break;
                }
            }
    
  • JulienRosenJulienRosen ✭✭✭✭ CAMember ✭✭✭✭
    edited September 2016

    You have this property Visible which hopefully exists in your view model MapService. When the switch is toggled, the set for the Visible property will be called. At this point, you are inside the BindingContext of the ListViewItem that was selected.

Sign In or Register to comment.