Binding in Templateselector does not work

Oliver 21 Reputation points
2023-12-26T15:48:53.1466667+00:00

Dear Community!

I have created a DateTemplate Selector to display a map instead for the first element of the RouteLocations from the Code behind. I set up the map in the code behind and also Set Bindings there. However, the RoutePinsProperty of the CustomMap remains null at all times even though the RouteLocations in the ViewModel has at least one element in it and when i fill in the RoutePins manually the ClickedRoutePinProperty does not take its changes to the ViewModel as i would expect it from the binding. Why does the binding not work here? Is this a problem with the DataTemplateSelector?

The CustomMapView:

 public static readonly BindableProperty RoutePinsProperty = BindableProperty.Create(
            propertyName: nameof(RoutePins),
            returnType: typeof(ObservableCollection<RouteLocationDisplayViewModel>),
            declaringType: typeof(CustomMapView),
            defaultBindingMode: BindingMode.OneWay,
            propertyChanged: RoutePinsPropertyChanged
            );

public ObservableCollection<RouteLocationDisplayViewModel> RoutePins
        {
            get => (ObservableCollection<RouteLocationDisplayViewModel>)GetValue(RoutePinsProperty);
            set => SetValue(RoutePinsProperty, value);
        }

private static void RoutePinsPropertyChanged(BindableObject bindable, object oldValue, object newValue)
        {
            if(bindable is not CustomMapView instance)
                return;
            if(newValue is ObservableCollection<RouteLocationDisplayViewModel> routePins)
            {
                instance.DrawMap(routePins);
                routePins.CollectionChanged += (sender, args) =>
                {
                    if (sender is not ObservableCollection<RouteLocationDisplayViewModel> customMapView)
                        return;
                    instance.DrawMap(args);
                };

            }
            else if(newValue == null)
            {
                instance.Pins.Clear();
                instance.MapElements.Clear();
            }
        }

        private void DrawMap(NotifyCollectionChangedEventArgs args)
        {
            if(args.NewItems == null)
                return;
            List<RouteLocationDisplayViewModel> routeLocationDisplayViewModels =
                new List<RouteLocationDisplayViewModel>();
            foreach (RouteLocationDisplayViewModel routeLocationDisplayViewModel in args.NewItems)
            {
                routeLocationDisplayViewModels.Add(routeLocationDisplayViewModel);
            }
            DrawMap(routeLocationDisplayViewModels);
        }
        
        private void DrawMap(IList<RouteLocationDisplayViewModel>? args)
        {
            if (args == null)
            {
                Pins.Clear();
                return;
            }

            foreach (RouteLocationDisplayViewModel p in args)
            {
                if (!Pins.Any(f => f.Location.Latitude == p.Latitude && f.Location.Longitude == p.Longitude))
                    Pins.Add(new CustomPin() {Label="Route", Location = new Location(p.Latitude, p.Longitude),
                        PinImageText = p.IconText,
                        PinImageTextColor = p.IconTextColor,
                        PinImageTextSize = p.IconTextSize,
                        PinIndex = p.Index,
                    });
            }
            if (Pins.Count > 1)
            {
                List<Pin> pins = Pins.Where(t => t.Label == "Route").ToList();
                MapElements.Add(new Polyline()
                {
                    StrokeColor = Colors.Green,
                    StrokeWidth = 6,
                    Geopath =
                    {
                        pins[pins.Count - 2].Location,
                        pins.Last().Location,
                    }
                });
            }                    
            Pins.ForEach(t => t.MarkerClicked += (sender, e) => OnRoutePinClicked(sender, e ,this));
        }
        
        private static void OnRoutePinClicked(object sender, PinClickedEventArgs e, CustomMapView instance)
        {
            if(sender is not CustomPin customPin)
                return;
            instance.ClickedRoutePin = instance.RoutePins.FirstOrDefault(t => t.Index == customPin.PinIndex);
        }


The View Code behind:

public AddRouteView(AddRouteViewModel addRouteViewModel)
    {
        InitializeComponent();
        BindingContext = addRouteViewModel;

        ShimmerView.IsLoading = true;
        
        SetMapTemplate(addRouteViewModel);
    }

    // == private methods ==
    private async Task SetMapTemplate(AddRouteViewModel addRouteViewModel)
    {
        CustomMapView map = new CustomMapView
        {
            IsScrollEnabled = false,
            IsZoomEnabled = false,
            IsShowingUser = false
        };
        
        map.SetBinding(CustomMapView.RoutePinsProperty, nameof(addRouteViewModel.RouteLocations));
        map.SetBinding(CustomMapView.ClickedRoutePinProperty, nameof(addRouteViewModel.ClickedRoutePin));
        
        DataTemplate MapTemplate = new DataTemplate(() => map);

        DataTemplate ImageTemplate = new DataTemplate(() =>
        {
            Image image = new Image();
            ByteArrayToImageSourceConverter converter = new ByteArrayToImageSourceConverter();
            
            image.SetBinding(Image.SourceProperty, nameof(ImageDisplayViewModel.Image), converter: converter);
            image.Aspect = Aspect.Fill;

            return image;
        });

        DataTemplateSelector selector = new RouteImageTemplateSelector()
        {
            ImageDateTemplate = ImageTemplate,
            MapTemplate = MapTemplate
        };

        CarouselView.ItemTemplate = selector;
        
        
        await Task.Delay(500);
        map.MoveToRegion(addRouteViewModel.DisplayMapSpan);
        await Task.Delay(2000);
        rect.IsVisible = false;
        ShimmerView.IsLoading = false;
    }

The ViewModel:

[ObservableProperty] 
    private ObservableCollection<RouteLocationDisplayViewModel> routeLocations;
 [ObservableProperty]
    private RouteLocationDisplayViewModel? clickedRoutePin;

 public AddRouteViewModel(INavigationService navigationService, SaveRouteTransferObject transferObject,
        PermissionState permissionState, RouteState2 routeState2, CropImageTransferObject cropImageTransferObject) : base(navigationService, permissionState)
    {
        _saveRouteTransferObject = transferObject;
        _routeState = routeState2;
        _cropImageTransferObject = cropImageTransferObject;
        RouteImages = new ObservableCollection<ImageDisplayViewModel>();
            
        RouteLocations = new ObservableCollection<RouteLocationDisplayViewModel>(transferObject.RouteLocations);
        SetRouteProperties(RouteLocations);

        if(RouteLocations != null)
            RouteLocations.CollectionChanged += (sender, args) => OnRouteLocationsChanged(null);
        
        RouteImages.Add(new ImageDisplayViewModel("Map",0, Array.Empty<byte>()));
    }

 partial void OnClickedRoutePinChanged(RouteLocationDisplayViewModel oldValue, RouteLocationDisplayViewModel newValue)
    {
        if(!RouteLocations.Any(t => t == newValue))
            return;

        Test(newValue);
    }

private async Task Test(RouteLocationDisplayViewModel newValue)
    {
        bool delete = await NavigationService.DisplayAlert("Delete route point", "Do you really want to delete this route point?",
            "Yes", "No");

        if (delete)
            RouteLocations.Remove(newValue);
        ClickedRoutePin = null;
    }
.NET MAUI
.NET MAUI
A Microsoft open-source framework for building native device applications spanning mobile, tablet, and desktop.
4,079 questions
{count} votes

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.