【问题标题】:Closing an Open Window Using MVVM Pattern, Produces System.NullReferenceException error使用 MVVM 模式关闭打开的窗口,产生 System.NullReferenceException 错误
【发布时间】: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 添加到您的问题中。你有类似&lt;Window.DataContext&gt;&lt;local:NewContactViewModel /&gt;&lt;/Window.DataContext&gt; 的东西吗?这是新 WPF 程序员创建不需要的额外视图模型的经典方法。
  • 我编辑了上面的条目以包含两个 XAML 文件。 NewContactWindow.xaml 中的代码行是&lt;Window.Resources&gt;&lt;vm:NewContactViewModel x:Key="vm"/&gt;&lt;/Window.Resources&gt;
  • 绝对是一次学习经历,哈哈。我删除了Source={StaticResource vm}&lt;Window.Resources&gt;&lt;vm:NewContactViewModel x:Key="vm"/&gt;&lt;/Window.Resources&gt;。这似乎现在奏效了!

标签: c# wpf mvvm


【解决方案1】:

您正在构造函数中创建 NewContactWindow 的视图模型,将其正确分配给 DataContext,并将处理程序正确添加到该事件。不幸的是,您还在资源中创建了相同视图模型的第二个实例,并且您手动将所有绑定的 Source 属性设置为使用资源中没有事件处理程序的绑定。

Window.DataContext(在构造函数中设置)是 Window XAML 中任何绑定的默认 Source。让它做它的事。我还从 Bindings 到 TextBox.Text 中删除了所有多余的 Mode=TwoWay 东西,因为定义了该属性,因此默认情况下它上的所有绑定都是 TwoWay。我认为UpdateSourceTrigger=PropertyChanged 也没有做任何必要或有用的事情:这会导致 Binding 在每次按下键时更新您的 viewmodel 属性,而不是仅在 TextBox 失去焦点时更新。但我不认为你对重要的属性做任何事情。没有验证或任何东西。但是 TextBox.Text 实际使用的极少数地方之一,所以我把它留在了。

您应该删除另一个窗口中的类似视图模型资源。它没有造成任何伤害,但充其量是无用的。在最坏的情况下,这是一个有吸引力的麻烦。用火将它杀死,并将灰烬掩埋在午夜孤独的十字路口。

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

    <Grid>
        <StackPanel 
            Margin="10">
            <Label Content="Name" />
            <TextBox 
                Text="{Binding Contact.Name, UpdateSourceTrigger=PropertyChanged}"
                Margin="0,0,0,5"/>
            <Label Content="Email" />
            <TextBox 
                Text="{Binding Contact.Email, UpdateSourceTrigger=PropertyChanged}"
                Margin="0,0,0,5"/>
            <Label Content="Phone Number" />
            <TextBox 
                Text="{Binding Contact.Phone, UpdateSourceTrigger=PropertyChanged}"
                Margin="0,0,0,5"/>
            <Button 
                Content="Save"
                Command="{Binding SaveNewContactCommand}"/>
        </StackPanel>
    </Grid>
</Window>

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-06-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-12-28
    • 1970-01-01
    • 2010-11-22
    • 1970-01-01
    相关资源
    最近更新 更多