简体   繁体   English

C#-ObservableCollection似乎没有保存添加的项目

[英]c# - ObservableCollection doesn't seem save added items

Description 描述

I'm trying to build this simple UWP MVVM notetaking app. 我正在尝试构建这个简单的UWP MVVM笔记应用程序。 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 . 该应用程序的目的是在单击“ 添加” Button时将文本从“ Textbox添加到ListView ,或者通过按Delete Button (指定给ListView每个项目) 删除 ListView的项目。

Adding item to the ObservableCollection 将项目添加到ObservableCollection

Adding items to the ObservableCollection<Note> seems to work fine. 将项目添加到ObservableCollection<Note>似乎可以正常工作。 The items are shown in the ListView without any problems. 这些项目显示在ListView没有任何问题。

Deleting items in the ObservableCollection 删除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 . 我试图调用负责从构造函数和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. 当我从Delete Button调用DoDeleteNote(Note itemToDelete) ,什么也没有发生,但是如果我从构造函数中调用相同的方法,则该项目将被删除。

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> . 我在DoDeleteNote(Note itemToDelete)方法中创建了一个断点,并且在调试器中可以看到它通过代码运行,但是没有从ObservableCollection<Note>删除任何内容。 However when i invoke the DoDeleteNote(Note itemToDelete) method from the constructor the item is deleted. 但是,当我从构造函数调用DoDeleteNote(Note itemToDelete)方法时,该项目将被删除。

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> . 这也是奇怪,我创建并注意项添加到ObservableCollection<Note>NoteViewModel构造是被在唯一项目ObservableCollection<Note> The items I add using the Add Button , are gone, but still shown in the ListView . 我使用“ 添加 Button添加的项目已消失,但仍显示在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. 我在想INotifyPropertyChanged或绑定可能有问题,但是我不确定从哪里开始寻找什么以及寻找什么,所以我可以使用一些帮助。

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 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 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 ICommand的实现的RelayCommand类似乎与该问题无关,因此我在此处未包括它,但是如果您很好奇,可以在GitHub上看到它

As @Eugene Podskal points out one of the problems is with this code 正如@Eugene Podskal指出的问题之一是此代码

<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. 您的布局网格实例化一个新的NoteViewModel ,上面的代码将执行相同的操作,使您在页面上处于活动状态的2个NoteViewModels。

Firstly give the ListView a name 首先给ListView一个名字

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

Next let's fix the Bindings on your DataTemplate of ListView MyList 接下来,让我们修复ListView MyList DataTemplate上的绑定

<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 意味着我们现在绑定到DataContextMyList这是您的NoteViewModel主要下定义Grid

The CommandParamter is simplified to CommandParamter简化为

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 正如我在下面解释的那样,最好将其绑定到MyList的对象,在这种情况下,该对象是Note的对象。

To make this work we need to adjust your NoteViewModel slightly Change your private delete field and public property to 为此,我们需要稍微调整您的NoteViewModel将您的私人删除字段和公共属性更改为

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 DoDeleteNote方法简化为

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

so we handily get rid of casting from a TextBlock . 因此,我们可以轻松摆脱TextBlock的强制转换。 You can now get rid of the DeleteNote method as it's no longer needed. 您现在可以摆脱DeleteNote方法,因为它不再需要。

Finally we need to add a new RelayCommand that takes a Generic Type to make our Command DeleteNoteCommand work correctly. 最后,我们需要添加一个采用通用类型的新RelayCommand ,以使我们的Command DeleteNoteCommand正常工作。

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. 我还建议在使用xaml时使用mvvm框架,因为它会使您的生活更加轻松。 I recommend mvvmlight but there are plenty of others. 我建议使用mvvmlight,但还有很多其他功能。 Hope that helps 希望能有所帮助

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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