【发布时间】:2019-06-27 17:23:57
【问题描述】:
我正在尝试使用 WPF C# 学习 MVVM 模式。在将信息保存到 sqlite 数据库后尝试关闭打开的窗口时,我遇到了错误。当发出保存新联系人的命令时,我在 HasAddedContact(this, new EventArgs()); 上收到错误消息;
错误:System.NullReferenceException:'对象引用未设置为对象的实例。'
我的视图模型:
public class NewContactViewModel : BaseViewModel
{
private ContactViewModel _contact;
public ContactViewModel Contact
{
get { return _contact; }
set { SetValue(ref _contact, value); }
}
public SaveNewContactCommand SaveNewContactCommand { get; set; }
public event EventHandler HasAddedContact;
public NewContactViewModel()
{
SaveNewContactCommand = new SaveNewContactCommand(this);
_contact = new ContactViewModel();
}
public void SaveNewContact()
{
var newContact = new Contact()
{
Name = Contact.Name,
Email = Contact.Email,
Phone = Contact.Phone
};
DatabaseConnection.Insert(newContact);
HasAddedContact(this, new EventArgs());
}
}
SaveNewContactCommand:
public class SaveNewContactCommand : ICommand
{
public NewContactViewModel VM { get; set; }
public SaveNewContactCommand(NewContactViewModel vm)
{
VM = vm;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
VM.SaveNewContact();
}
}
NewContactWindow.Xaml.Cs 后面的代码:
public partial class NewContactWindow : Window
{
NewContactViewModel _viewModel;
public NewContactWindow()
{
InitializeComponent();
_viewModel = new NewContactViewModel();
DataContext = _viewModel;
_viewModel.HasAddedContact += Vm_ContactAdded;
}
private void Vm_ContactAdded(object sender, EventArgs e)
{
this.Close();
}
}
在我调用新窗口的地方添加额外的代码:
public class ContactsViewModel
{
public ObservableCollection<IContact> Contacts { get; set; } = new ObservableCollection<IContact>();
public NewContactCommand NewContactCommand { get; set; }
public ContactsViewModel()
{
NewContactCommand = new NewContactCommand(this);
GetContacts();
}
public void GetContacts()
{
using(var conn = new SQLite.SQLiteConnection(DatabaseConnection.dbFile))
{
conn.CreateTable<Contact>();
var contacts = conn.Table<Contact>().ToList();
Contacts.Clear();
foreach (var contact in contacts)
{
Contacts.Add(contact);
}
}
}
public void CreateNewContact()
{
var newContactWindow = new NewContactWindow();
newContactWindow.ShowDialog();
GetContacts();
}
}
ContactsWindow.Xaml
<Window x:Class="Contacts_App.View.ContactsWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Contacts_App.View"
xmlns:vm="clr-namespace:Contacts_App.ViewModel"
mc:Ignorable="d"
Title="Contacts Window" Height="320" Width="400">
<Window.Resources>
<vm:ContactsViewModel x:Key="vm"/>
</Window.Resources>
<StackPanel Margin="10">
<Button
Content="New Contact"
Command="{Binding NewContactCommand}"/>
<TextBox Margin="0,5,0,5"/>
<ListView
Height="200"
Margin="0,5,0,0"
ItemsSource="{Binding Contacts}">
<ListView.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Name}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</Window>
NewContactWindow.Xaml
<Window x:Class="Contacts_App.View.NewContactWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Contacts_App.View"
xmlns:vm="clr-namespace:Contacts_App.ViewModel"
mc:Ignorable="d"
Title="New Contact Window" Height="250" Width="350">
<Window.Resources>
<vm:NewContactViewModel x:Key="vm"/>
</Window.Resources>
<Grid>
<StackPanel
Margin="10">
<Label Content="Name" />
<TextBox
Text="{Binding Source={StaticResource vm}, Path=Contact.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Margin="0,0,0,5"/>
<Label Content="Email" />
<TextBox
Text="{Binding Source={StaticResource vm}, Path=Contact.Email, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Margin="0,0,0,5"/>
<Label Content="Phone Number" />
<TextBox
Text="{Binding Source={StaticResource vm}, Path=Contact.Phone, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Margin="0,0,0,5"/>
<Button
Content="Save"
Command="{Binding Source={StaticResource vm}, Path=SaveNewContactCommand}"/>
</StackPanel>
</Grid>
</Window>
【问题讨论】:
-
在引发事件之前,请始终检查
HasAddedContact等事件处理程序是否为空。如果没有处理程序,它将为 null。您还可以使用空条件运算符调用Invoke():HasAddedContact?.Invoke(this, new EventArgs());。这将检查HasAddedContact是否为空,并且仅在它不为空时调用该方法。 但是为什么没有处理程序? 很可能,您在某处创建了一个额外的视图模型实例,并且您在错误的实例上调用了该命令。是这样吗?你能在你创建和显示窗口的地方显示代码吗? -
如果
HasAddedContact在引发时没有处理程序,您必须在某处创建NewContactViewModel的另一个实例,并以某种方式使其成为创建联系人对话框的DataContext。在引发事件的行上放置一个断点。那里是空的吗?在NewContactViewModel构造函数中放置一个断点。它被调用了两次吗?该错误不在您向我展示的代码中。或者DatabaseConnection可能为空。 -
请将窗口的 XAML 添加到您的问题中。你有类似
<Window.DataContext><local:NewContactViewModel /></Window.DataContext>的东西吗?这是新 WPF 程序员创建不需要的额外视图模型的经典方法。 -
我编辑了上面的条目以包含两个 XAML 文件。 NewContactWindow.xaml 中的代码行是
<Window.Resources><vm:NewContactViewModel x:Key="vm"/></Window.Resources>。 -
绝对是一次学习经历,哈哈。我删除了
Source={StaticResource vm}和<Window.Resources><vm:NewContactViewModel x:Key="vm"/></Window.Resources>。这似乎现在奏效了!