简体   繁体   中英

c# - ObservableCollection doesn't seem save added items

Description

I'm trying to build this simple UWP MVVM notetaking app. The purpose of the app is adding text from a Textbox to a ListView , when a Add Button is clicked, or delete an item of the ListView by pressing a Delete Button , which is assigned to each item in the ListView .

Adding item to the ObservableCollection

Adding items to the ObservableCollection<Note> seems to work fine. The items are shown in the ListView without any problems.

Deleting items in the ObservableCollection

Deleting item doesn't work as it should.

My debug attempt

I tried to invoke the method responsible for deleting the items from both the constructor and the Delete Button . When I invoke DoDeleteNote(Note itemToDelete) from the Delete Button , nothing happens, but if i invoke the same method from the constructor then the item is deleted.

I created a breakpoint in the DoDeleteNote(Note itemToDelete) method, and I can see in the debugger that it runs through the code, but nothing is deleted from the ObservableCollection<Note> . However when i invoke the DoDeleteNote(Note itemToDelete) method from the constructor the item is deleted.

It is also strange, that the Note items I create and add to the ObservableCollection<Note> from the NoteViewModel constructor are the only items that are in the ObservableCollection<Note> . The items I add using the Add Button , are gone, but still shown in the ListView .

I'm thinking maybe something is wrong with either INotifyPropertyChanged or the bindings, but i'm not sure where to start looking, and what to look for, so I could use some help.

I know there seems to be a lot of code here, but I felt it was necessary not to omit anything to understand the data flow.

XAML

<Page
    x:Class="ListView2.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:ListView2"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:viewModel="using:ListView2.ViewModel"
    mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

    <Grid.DataContext>
        <viewModel:NoteViewModel/>
    </Grid.DataContext>

    <ListView  Header="Notes"
               HorizontalAlignment="Left" 
               Height="341"
               Width="228"
               VerticalAlignment="Top"
               Margin="163,208,0,0"
               ItemsSource="{Binding Notes, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">

        <ListView.ItemTemplate>
            <DataTemplate x:Name="MyDataTemplate">
                <StackPanel Orientation="Horizontal">

                    <TextBlock x:Name="TbxblListItem" Text="{Binding NoteText}"/>

                    <Button Command="{Binding DeleteNoteCommand}" 
                            CommandParameter="{Binding ElementName=TbxblListItem}">
                        <Button.DataContext>
                            <viewModel:NoteViewModel/>
                        </Button.DataContext>
                        <Button.Content>
                            <SymbolIcon Symbol="Delete" 
                                        ToolTipService.ToolTip="Delete Note" 
                                        HorizontalAlignment="Center" 
                                        VerticalAlignment="Center"/>
                        </Button.Content>
                    </Button>

                </StackPanel>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>

    <TextBox x:Name="TbxNoteContent" HorizontalAlignment="Left"
             Margin="571,147,0,0"
             TextWrapping="Wrap"
             VerticalAlignment="Top"
             Width="376"/>

    <Button Content="Add"
            HorizontalAlignment="Left"
            Margin="597,249,0,0"
            VerticalAlignment="Top"
            Command="{Binding AddNoteCommand}"
            CommandParameter="{Binding Text, ElementName=TbxNoteContent}"/>
</Grid>
</page>        

Note

namespace ListView2.Model
{
    class Note
    {
        private string _noteText;

        public Note(string noteText)
        {
            NoteText = noteText;
        }

        public string NoteText { get { return _noteText; } set { _noteText = value; } }
    }
}

Notification

using System.ComponentModel;

namespace ListView2.Model
{
    class Notification : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

NoteViewModel

using System.Collections.ObjectModel;
using Windows.UI.Xaml.Controls;
using ListView2.Model;

namespace ListView2.ViewModel
{
    class NoteViewModel : Notification
    {
        #region Instance Fields
        private ObservableCollection<Note> _notes;
        private RelayCommand _addNoteCommand;
        private RelayCommand _deleteNoteCommand;
        //private string _noteText;
        #endregion

        #region Constructors
        public NoteViewModel()
        {
            //adds sample data to Notes property (ObservableCollection<Note>)
            Notes = new ObservableCollection<Note>() { new Note("Sample text 1"), new Note("Sample text 2") };

            //Used for testing the deletion of items from ObservableCollection-------------------------------------
            Notes.RemoveAt(1);
            Notes.Add(new Note("Sample text 3"));
            //foreach (var item in Notes)
            //{
            //    if (item.NoteText == "Sample text 3")
            //    {
            //        DoDeleteNote(item);
            //        break;
            //    }
            //}
            //------------------------------------------------------

            //Button command methods are added to delegates
            AddNoteCommand = new RelayCommand(DoAddNote);
            DeleteNoteCommand = new RelayCommand(DoDeleteNote);
        }
        #endregion

        #region Properties
        public ObservableCollection<Note> Notes { get { return _notes; } set { _notes = value; OnPropertyChanged("Notes"); } }

        //public string NoteText { get { return _noteText; } set { _noteText = value; OnPropertyChanged("NoteText"); } }

        public RelayCommand AddNoteCommand { get { return _addNoteCommand; } set { _addNoteCommand = value; } }

        public RelayCommand DeleteNoteCommand { get { return _deleteNoteCommand; } set { _deleteNoteCommand = value; } }

        #endregion

        #region methods

        private void DoAddNote(object obj)
        {
            var newItem = obj as string;
            if (!string.IsNullOrEmpty(newItem))
            {
                AddNote(newItem);
            }
        }

        //Work in progress
        private void DoDeleteNote(object obj)
        {
            //Used when the XAML Delete Button invokes this method
            TextBlock textBlockSender = obj as TextBlock;
            //string myString = textBlockSender.Text;
            Note itemToDelete = textBlockSender.DataContext as Note;

            //Used when the constuctor invokes this method, for testing purposes------------
            //Note itemToDelete = obj as Note;
            //--------------------------------------------------------

            foreach (Note note in this.Notes)
            {
                if (note.NoteText == itemToDelete.NoteText)
                {
                    //int noteIndex = Notes.IndexOf(note);
                    //Notes.RemoveAt(noteIndex);
                    DeleteNote(note);
                    break;
                }
            }
            //if (Notes.Contains(itemToDelete))
            //{
            //    Notes.Remove(itemToDelete);
            //}
        }

        public void AddNote(string noteText)
        {
            this.Notes.Add(new Note(noteText));
        }

        public void DeleteNote(Note itemToDelete)
        {
            this.Notes.Remove(itemToDelete);
        }
        #endregion
    }
}

RelayCommand class which is the implementation for ICommand does not seem relevant for the question, so I haven't included it here, but if you are curious it can be seen on GitHub

As @Eugene Podskal points out one of the problems is with this code

<Button.DataContext>
    <viewModel:NoteViewModel/>
</Button.DataContext>

Your Layout Grid instantiates a new NoteViewModel and the above code will do the same leaving you with 2 NoteViewModels active on the page.

Firstly give the ListView a name

<ListView x:Name="MyList" Header="Notes"

Next let's fix the Bindings on your DataTemplate of ListView MyList

<ListView.ItemTemplate>
   <DataTemplate x:Name="MyDataTemplate">
      <StackPanel Orientation="Horizontal">
         <TextBlock x:Name="TbxblListItem" Text="{Binding NoteText}"/>
         <Button Command="{Binding DataContext.DeleteNoteCommand, ElementName=MyList}" 
            CommandParameter="{Binding}">                            
         <SymbolIcon Symbol="Delete" 
            ToolTipService.ToolTip="Delete Note" 
            HorizontalAlignment="Center" 
            VerticalAlignment="Center"/>
        </Button>
     </StackPanel>
  </DataTemplate>
</ListView.ItemTemplate>

this line

Command="{Binding DataContext.DeleteNoteCommand, ElementName=MyList}"

means we are now Binding to the DataContext of MyList which is your NoteViewModel as defined under the main Grid

The CommandParamter is simplified to

CommandParameter="{Binding}"

as I explain below it is better practice to Bind to the object in your MyList which in this case is an object of Note

To make this work we need to adjust your NoteViewModel slightly Change your private delete field and public property to

private RelayCommand<Note> _deleteNoteCommand;
public RelayCommand<Note> DeleteNoteCommand { get { return _deleteNoteCommand; } set { _deleteNoteCommand = value; } }

and in the constructor

DeleteNoteCommand = new RelayCommand<Note>(DoDeleteNote);

DoDeleteNote method is simplified to

private void DoDeleteNote(Note note)
{
    this.Notes.Remove(note);    
}

so we handily get rid of casting from a TextBlock . You can now get rid of the DeleteNote method as it's no longer needed.

Finally we need to add a new RelayCommand that takes a Generic Type to make our Command DeleteNoteCommand work correctly.

RelayCommand

public class RelayCommand<T> : ICommand
{
    #region Fields

    private readonly Action<T> _execute = null;
    private readonly Predicate<T> _canExecute = null;

    #endregion

    #region Constructors

    /// <summary>
    /// Creates a new command that can always execute.
    /// </summary>
    /// <param name="execute">The execution logic.</param>
    public RelayCommand(Action<T> execute)
            : this(execute, null)
    {
    }

    /// <summary>
    /// Creates a new command with conditional execution.
    /// </summary>
    /// <param name="execute">The execution logic.</param>
    /// <param name="canExecute">The execution status logic.</param>
    public RelayCommand(Action<T> execute, Predicate<T> canExecute)
    {
        if (execute == null)
            throw new ArgumentNullException("execute");

        _execute = execute;
        _canExecute = canExecute;
    }

    #endregion

    #region ICommand Members

    public bool CanExecute(object parameter)
    {
        return _canExecute == null ? true : _canExecute((T)parameter);
    }

    public event EventHandler CanExecuteChanged;

    public void RaiseCanExecuteChanged()
    {
        var handler = CanExecuteChanged;
        if (handler != null)
            CanExecuteChanged(this, new EventArgs());
    }


    public void Execute(object parameter)
    {
        _execute((T)parameter);
    }

    #endregion
}

Apologies this answer is long but I wanted to point out every step. I also suggest using a mvvm framework when working with xaml as it will make your life alot easier. I recommend mvvmlight but there are plenty of others. Hope that helps

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