简体   繁体   中英

Binding ObservableCollection<class> fields to ListBox DataTemplate

I am a student that just finished up a summer internship, and I brought home a project to work on briefly before school starts up. This project has a stopwatch in it, and I would rather use an ObservableCollection bound to my ListBox for my split times, rather that using the listbox.Items.Add(). When I add to the ObservableCollection, the ListBox UI does not update. Could anyone point me in the right direction on what I missed or what I did wrong?

I have my TimeSplits class:

public class TimeSplits : INotifyPropertyChanged
{

    private int _hours;
    private int _minutes;
    private int _seconds;

    public int hours
    {
        get
        {
            return _hours;
        }
        set
        {
            _hours = value;
            NotifyPropertyChanged(hours);
        }
    }
    public int minutes
    {
        get
        {
            return _minutes;
        }
        set
        {
            _minutes = value;
            NotifyPropertyChanged(minutes);
        }
    }
    public int seconds
    {
        get
        {
            return _seconds;
        }
        set
        {
            _seconds = value;
            NotifyPropertyChanged(seconds);
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void NotifyPropertyChanged(int propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(nameof(propertyName)));
        }
    }

    public override string ToString()
    {
        return hours.ToString() + ":" + minutes.ToString() + ":" + seconds.ToString();
    }
}

and my ObservableCollection in my Page:

public partial class StopwatchPage : Page , INotifyPropertyChanged
{
...
    public ObservableCollection<TimeSplits> splits = new ObservableCollection<TimeSplits>();
...
    public StopwatchPage()
    {
        DataContext = this;
        InitializeComponent();
        timer.Interval = TimeSpan.FromSeconds(1);
        timer.Tick += new EventHandler(stopwatchTimer);
    }
...
    private void splitButton_Click(object sender, RoutedEventArgs e)
    {
        TimeSplits split = new TimeSplits();
        split.hours = Hours;
        split.minutes = Minutes;
        split.seconds = Seconds;
        splits.Add(split);
    }
...
}

and my xaml:

<ListBox x:Name="newSplitListBox" HorizontalAlignment="Left" Margin="139,0,0,47" Width="185" Height="268" VerticalAlignment="Bottom" ItemsSource="{Binding splits}">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="{Binding hours}"/>
                    <TextBlock Text="{Binding minutes}"/>
                    <TextBlock Text="{Binding seconds}"/>
                </StackPanel>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>

I am sure it is something small that I have no clue about, as I just started learning data binding this summer. Any help is greatly appreciated! Thanks in advance.

It looks like you have nameof() in the wrong place. The way your current code reads, it will always send the value of "propertyName" as the name of the property that changed, regardless of what property actually changed.

Try this:

public int hours
{
    get
    {
        return _hours;
    }
    set
    {
        _hours = value;
        NotifyPropertyChanged();
    }
}

Then, in your NotifyPropertyChanged() , do this:

private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
    if (PropertyChanged != null)
    {
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName);
    }
}

Edit: Added fix for the following:

Also, the ObservableCollection needs to be a property. Change this code:

public ObservableCollection<TimeSplits> splits = new ObservableCollection<TimeSplits>();

To this:

public ObservableCollection<TimeSplits> Splits { get; set; } = new ObservableCollection<TimeSplits>();

I learned a trick from Xamarin's ViewModel template that helped me immensely. Here is the code that it generates that handles an observable View Model (much like the ObservableCollection).

    protected bool SetProperty<T>(ref T backingStore, T value,
        Action onChanged = null,
        [CallerMemberName]string propertyName = "")
    {
        if (EqualityComparer<T>.Default.Equals(backingStore, value))
            return false;

        backingStore = value;
        onChanged?.Invoke();
        OnPropertyChanged(propertyName);
        return true;
    }

    #region INotifyPropertyChanged
    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
    {
        var changed = PropertyChanged;
        if (changed == null)
            return;

        changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
    #endregion

Then, to use this, simply add this to your properties:

private string _title = string.Empty;
public string Title
{
    get => _title;
    set => SetProperty(ref _title, value);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM