5
votes

I have a XAML View and a View Model in my project. The project is a stopwatch and as a Lap button is pressed it adds an object to an ObservableCollection which is displayed in the view as a ListView.

In the ListView I have added a button to delete one of the Collection items. It has a Command with a Binding of "DeleteTimingCommand" and a CommandParameter with a Binding of "Position" which is the identifier I want to use in order to delete the object from the collection.

The DeleteTimingCommand is in the ViewModel constructor and set to fire a method called "DeleteTiming". This method then takes care of the actual deletion.

However, I can't get my code to hit my DeleteTiming() method at all! No matter where I put a breakpoint, nothing is firing it. I press the button and nothing happens.

Something isn't hooked up right. Can anyone point me in the right direction?

XAML

 <?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" 
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
    x:Class="TechsportiseApp.Views.Timer" Title="Timer">
    <StackLayout VerticalOptions="Start" HorizontalOptions="FillAndExpand">
        <StackLayout Orientation="Horizontal">
            <Label Text="{Binding ElapsedTime}" HorizontalOptions="FillAndExpand" HorizontalTextAlignment="Center" FontSize="40"/>
            <Button Command="{Binding LapCommand}" BackgroundColor="#eca400" IsVisible="{Binding EndVisibility}" Image="Scanning.png" WidthRequest="50" HeightRequest="50"/>
        </StackLayout>
        <Button x:Name="buttonStart" Text="Start Race" Command="{Binding StartTimerCommand}" IsVisible="{Binding StartVisibility}" />
        <Button x:Name="buttonEnd" Text="End Race" Command="{Binding EndTimerCommand}" IsVisible="{Binding EndVisibility}" />
        <Button x:Name="buttonSubmit" Text="Submit Results" Command="{Binding EndTimerCommand}" IsVisible="{Binding SubmitVisibility}" />
        <BoxView HeightRequest="1" Color="Silver" HorizontalOptions="FillAndExpand" />
        <Label Text="YOUR RESULTS" HorizontalOptions="FillAndExpand" HorizontalTextAlignment="Center" FontSize="Small" FontAttributes="Bold"/>
        <BoxView HeightRequest="1" Color="Silver" HorizontalOptions="FillAndExpand" />

        <ListView ItemsSource="{Binding Timings}"
        SeparatorVisibility="None"
        HasUnevenRows="true">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                        <Grid RowSpacing="3" ColumnSpacing="3">
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto" />
                                <RowDefinition Height="Auto" />
                            </Grid.RowDefinitions>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="50" />
                                <ColumnDefinition Width="*" />
                                <ColumnDefinition Width="50" />
                            </Grid.ColumnDefinitions>
                            <Label x:Name="Pos" Text="{Binding Position}" VerticalTextAlignment="Center" HorizontalTextAlignment="Center"  Grid.Row="0" Grid.Column="0" FontSize="Large" FontAttributes="Bold"/>
                            <Label Text="{Binding Elapsed}" VerticalTextAlignment="Center" Grid.Row="0" Grid.Column="1"  FontSize="Large" />
                            <Button Image="trash.png" BackgroundColor="Red" WidthRequest="50" HeightRequest="50" Command="{Binding DeleteTimingCommand}" CommandParameter="{Binding Position}" Grid.Row="0" Grid.Column="2" />
                            <BoxView HeightRequest="1" Color="Silver" HorizontalOptions="FillAndExpand" Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" />

                        </Grid>
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </StackLayout>
</ContentPage>

ViewModel

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Timers;
using System.Windows.Input;
using TechsportiseApp.Models;
using Xamarin.Forms;

namespace TechsportiseApp.ViewModels
{
    public class TimerViewModel : INotifyPropertyChanged
    {
        public Timer myTimer = new Timer();

        public ICommand StartTimerCommand { get; set; }
        public ICommand EndTimerCommand { get; set; }
        public ICommand LapCommand { get; set; }
        public ICommand DeleteTimingCommand { get; set; }

        public TimerViewModel()
        {
            StartTimerCommand = new Command(StartTimer);
            EndTimerCommand = new Command(EndTimer);
            LapCommand = new Command(LapButton);
            DeleteTimingCommand = new Command<int>(DeleteTiming);
            Timings = new ObservableCollection<Timing>();
            LapPosition = 1;
            StartVisibility = true;
            EndVisibility = false;
            SubmitVisibility = false;
        }


        //TimeSpan _elapsedTime;
        //public TimeSpan ElapsedTime
        //{
        //    get {              
        //        return _elapsedTime;
        //    }
        //    set
        //    {
        //        if (_elapsedTime == value)
        //            return;

        //        _elapsedTime = value;
        //        OnPropertyChanged("ElapsedTime");
        //    }
        //}

        string _elapsedTime;
        public String ElapsedTime
        {
            get
            {
                if (_elapsedTime != null)
                {
                    return _elapsedTime;
                }
                else
                {
                    _elapsedTime = "00:00:00.00";
                    return _elapsedTime;
                }

            }
            set
            {
                if (_elapsedTime == value)
                    return;

                _elapsedTime = value;
                OnPropertyChanged("ElapsedTime");
            }
        }

        DateTime _currentTime;
        public DateTime CurrentTime
        {
            get { return _currentTime; }
            set
            {
                if (_currentTime == value)
                    return;

                _currentTime = value;
                OnPropertyChanged("CurrentTime");
            }
        }

        bool _startVisibility;
        public bool StartVisibility
        {
            get { return _startVisibility; }
            set
            {
                if (_startVisibility == value)
                    return;

                _startVisibility = value;
                OnPropertyChanged("StartVisibility");
            }
        }

        bool _endVisibility;
        public bool EndVisibility
        {
            get { return _endVisibility; }
            set
            {
                if (_endVisibility == value)
                    return;

                _endVisibility = value;
                OnPropertyChanged("EndVisibility");
            }
        }

        bool _submitVisibility;
        public bool SubmitVisibility
        {
            get { return _submitVisibility; }
            set
            {
                if (_submitVisibility == value)
                    return;

                _submitVisibility = value;
                OnPropertyChanged("SubmitVisibility");
            }
        }

        DateTime _raceStartTime;
        public DateTime RaceStartTime
        {
            get { return _raceStartTime; }
            set
            {
                if (_raceStartTime == value)
                    return;

                _raceStartTime = value;
                OnPropertyChanged("RaceStartTime");
            }
        }

        DateTime _lapTime;
        public DateTime LapTime
        {
            get { return _lapTime; }
            set
            {
                if (_lapTime == value)
                    return;

                _lapTime = value;
                OnPropertyChanged("LapTime");
            }
        }

        ObservableCollection<Timing> _timings;
        public ObservableCollection<Timing> Timings
        {
            get
            {
                //var sortedtimings = new ObservableCollection<Timing>(_timings.OrderBy(c => c.Position));
                var sortedtimings = new ObservableCollection<Timing>(_timings.OrderByDescending(c => c.Position));
                _timings = sortedtimings;
                return _timings;
            }
            set
            {
                if (_timings != value)
                {
                    _timings = value;
                    OnPropertyChanged("Timings");
                }
            }
        }

        int _lapPosition;
        public int LapPosition
        {
            get { return _lapPosition; }
            set
            {
                if (_lapPosition == value)
                    return;

                _lapPosition = value;
                OnPropertyChanged("LapPosition");
            }
        }



        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged(string propertyName)
        {
            var changed = PropertyChanged;
            if (changed != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        void StartTimer()
        {
            RaceStartTime = DateTime.Now;
            myTimer.Elapsed += new ElapsedEventHandler(DisplayTimeEvent);
            myTimer.Interval = 10; // 1000 ms is one second
            myTimer.Start();
            StartVisibility = false;
            EndVisibility = true;
            //ElapsedTime = myTimer.Elapsed.Milliseconds() += new ElapsedEventHandler(DisplayTimeEvent);
        }

        void EndTimer()
        {
            myTimer.Stop();
            EndVisibility = false;
            SubmitVisibility = true;
            //ElapsedTime = myTimer.Elapsed.Milliseconds() += new ElapsedEventHandler(DisplayTimeEvent);
        }

        void DisplayTimeEvent(object source, ElapsedEventArgs e)
        {
            TimeSpan _elapsed;
            _elapsed = e.SignalTime - RaceStartTime;

            string hours = _elapsed.Hours.ToString().PadLeft(2, '0');
            string minutes = _elapsed.Minutes.ToString().PadLeft(2, '0');
            string seconds = _elapsed.Seconds.ToString().PadLeft(2, '0');
            int _hundredths = _elapsed.Milliseconds / 10;

            string hundredths = _hundredths.ToString().PadLeft(2, '0');
            _elapsedTime = hours + ":" + minutes + ":" + seconds + "." + hundredths;
            ElapsedTime = _elapsedTime;
            OnPropertyChanged("ElapsedTime");
        }

        void LapButton()
        {
            var lap = new Timing
            {
                Id = 0,
                RaceId = 0,
                StartTime = RaceStartTime,
                EndTime = DateTime.Now,
                Elapsed = ElapsedTime,
                Position = LapPosition,
                Status = 0
            };
            Timings.Add(lap);

            OnPropertyChanged("Timings");
            LapPosition = LapPosition + 1;
            var listcheck = Timings.Count;

        }

        void DeleteTiming(int position)
        {
            foreach (var timing in Timings)
            {
                if (timing.Position == position)
                {
                    Timings.Remove(timing);                   
                    break;
                }
            }

            var NewPosition = Timings.Count();
            foreach (var timing in Timings)
            {
                timing.Position = NewPosition;
                NewPosition = NewPosition - 1;
            }

            OnPropertyChanged("Timings");
        }
    }
}
1
each element in your list is bound to a Timing object, and you have your command defined on your TimerViewModel classJason

1 Answers

17
votes

As per current code, the forms framework will try to look for DeleteTimingCommand in the corresponding Timing object. In order to get around that you will have to specify the parent view-model as source in the binding.

Add name reference attribute to the parent element:

<ContentPage x:Name="ParentView" xmlns="http://xamarin.com/sch ..

And, change your binding to:

<Button Image="trash.png".. Command="{Binding BindingContext.DeleteTimingCommand, Source={x:Reference ParentView}}" CommandParameter="{Binding Position}" .. />

This way the forms framework will know to look for the command in the parent-view's view model i.e. TimerViewModel.