[英]c# - ObservableCollection doesn't seem save added items
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 items to the ObservableCollection<Note>
seems to work fine. 将项目添加到
ObservableCollection<Note>
似乎可以正常工作。 The items are shown in the ListView
without any problems. 这些项目显示在
ListView
没有任何问题。
Deleting item doesn't work as it should. 删除项目无法正常工作。
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. 我知道这里似乎有很多代码,但是我觉得有必要不遗漏任何东西来理解数据流。
<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));
}
}
}
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
意味着我们现在绑定到
DataContext
的MyList
这是您的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.