【问题标题】:c# - ObservableCollection doesn't seem save added itemsc# - ObservableCollection 似乎没有保存添加的项目
【发布时间】:2016-04-17 12:57:14
【问题描述】:

说明

我正在尝试构建这个简单的 UWP MVVM 笔记应用程序。 该应用程序的目的是将文本从 Textbox 添加到 ListView,当单击 Add Button 或删除 @987654325 的项目时@ 通过按 Delete Button,分配给ListView 中的每个项目。

将项目添加到 ObservableCollection

ObservableCollection<Note> 添加项目似乎工作正常。项目显示在ListView 没有任何问题。

删除 ObservableCollection 中的项目

删除项目无法正常工作。

我的调试尝试

我尝试调用负责从构造函数和 Delete Button 中删除项目的方法。 当我从 Delete Button 调用 DoDeleteNote(Note itemToDelete) 时,什么也没有发生,但如果我从构造函数调用相同的方法,则该项目将被删除。

我在DoDeleteNote(Note itemToDelete) 方法中创建了一个断点,我可以在调试器中看到它在代码中运行,但没有从ObservableCollection<Note> 中删除任何内容。 但是,当我从构造函数调用 DoDeleteNote(Note itemToDelete) 方法时,该项目被删除。

奇怪的是,我从 NoteViewModel 构造函数创建并添加到 ObservableCollection<Note>Note 项是 ObservableCollection<Note> 中的唯一项.我使用 Add Button 添加的项目已消失,但仍显示在 ListView 中。

我在想INotifyPropertyChanged 或绑定可能有问题,但我不确定从哪里开始寻找,以及寻找什么,所以我可以寻求帮助。

我知道这里似乎有很多代码,但我觉得有必要不要遗漏任何东西来理解数据流。

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>        

注意

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

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

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

通知

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
    }
}

作为 ICommand 实现的 RelayCommand 类似乎与问题无关,所以我没有在此处包含它,但如果您好奇,可以在 GitHub 上看到它

【问题讨论】:

  • 您确定在您的Note itemToDelete = textBlockSender.DataContext as Note; 中获得itemToDelete 吗?如果你得到它,那么你确定它是Notes - this.Notes.Contains(note) 中的注释吗?
  • 我刚刚再次尝试调试,该项目确实被删除,从DoDeleteNote()方法中的ObservableCollection&lt;Note&gt;,但它似乎是创建的 2 个 Note 对象每次我通过DoAddNote()DoDeleteNote() 时都会重新创建构造函数中的内容,就像 ObservableCollection 已被重置一样。 Xaml 代码是否以某种方式多次实例化 NoteViewModel,所以 ObservableCollection&lt;Note&gt; 被重置?我认为可能与删除按钮中的 DataContext 有关,但我不太确定。
  • 我刚刚注意到 ` ` 这实际上是为每个实际创建 一个新的 NoteViewModel集合中的视图模型。而NoteViewModel 显然不在this.Notes 集合中。实际项目将自动传播到 DataTemplate 的 DataContext - 无需执行任何特殊操作。
  • 我将&lt;DataContext&gt;添加到&lt;Button&gt;,以便能够将&lt;Button Command &gt;绑定到DeleteNoteCommand,因为ListView ItemsSource指向注释 ,所以&lt;TextBox&gt; 可以使用Note 属性,而&lt;Button&gt; 只能绑定到与&lt;TextBox&gt; 相同的项目,因为它们在同一个&lt;ListView&gt; 内。我不确定如何解决这个问题,因为 MVVM 和 XAML 对我来说是新的,但似乎我们找到了罪魁祸首。

标签: c# xaml listview win-universal-app observablecollection


【解决方案1】:

正如@Eugene Podskal 指出的问题之一是这段代码

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

您的 Layout Grid 实例化了一个新的 NoteViewModel,上面的代码将执行相同的操作,从而使您在页面上保持 2 个 NoteViewModel 处于活动状态。

首先给 ListView 起个名字

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

接下来让我们修复 ListView MyListDataTemplate 上的绑定

<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>

这一行

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

表示我们现在正在绑定到 MyListDataContext,这是您在主 Grid 下定义的 NoteViewModel

CommandParamter 被简化为

CommandParameter="{Binding}"

正如我在下面解释的,最好绑定到 MyList 中的对象,在这种情况下是 Note 的对象

为了完成这项工作,我们需要稍微调整您的NoteViewModel 将您的私有删除字段和公共属性更改为

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

在构造函数中

DeleteNoteCommand = new RelayCommand<Note>(DoDeleteNote);

DoDeleteNote 方法被简化为

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

所以我们可以轻松地摆脱 TextBlock 的强制转换。您现在可以摆脱不再需要的 DeleteNote 方法。

最后,我们需要添加一个新的RelayCommand,它采用通用类型,以使我们的命令DeleteNoteCommand 正常工作。

中继命令

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
}

抱歉,这个答案很长,但我想指出每一步。我还建议在使用 xaml 时使用 mvvm 框架,因为它会让你的生活更轻松。我推荐mvvmlight,但还有很多其他的。希望有帮助

【讨论】:

  • 不要因为回答太长而道歉。我喜欢你解释中的详细程度。我实际上想知道如何从 访问其他属性,当 ItemsSource 已经设置为 Notes 时,正如我自己意识到的那样,通过使用 DataContext 2 次实际上会创建 2 个对象NoteViewModel,然后构造函数创建一个新的ObservableCollection,它重置前一个中的值。 {Binding DataContext.DeleteNoteCommand ElementName= MyList} 正是我想要的。我现在可以删除列表元素了。
  • 非常高兴它有帮助:)
  • 我也喜欢您通过删除 DeleteNote() 方法来清理我的代码的方式。我还没有实现最后一部分,因为我需要更多地研究你的代码才能完全理解它,因为我上周刚刚听说了 ICommand 和代表,所以这对我来说仍然是一个新概念,但它看起来从ListView 元素传递数据的更智能的方法。感谢您花时间在这个很棒的答案上。
  • 没问题,代表需要一段时间才能理解(我在几年后仍在尝试,哈哈)但是一旦你理解了上面的代码,转移到像 mvvm light 这样的框架会更容易,因为你已经确定概念。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-07
  • 2015-07-13
  • 1970-01-01
相关资源
最近更新 更多