无法从数据网格“对象引用未设置为对象实例"中选择项目. [英] Unable to select Item from Data-grid “Object reference not set to an instance of an object.”

查看:102
本文介绍了无法从数据网格“对象引用未设置为对象实例"中选择项目.的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的应用程序有问题,没有选择要使用EF更新的项目.

我正在创建一个应用程序,供用户输入其组织详细信息.似乎很简单.使用选项卡控件输入数据后,我将拥有一个包含一系列数据网格的视图,该视图使用户能够更新或插入另一条数据(如果需要)或具有删除功能.

在我的视图模型中,我有一系列不同的东西(这可能不是实现MVVM的最佳方法,但最终会尝试达到这一点).

我有实现INOTIFYPROPERTYCHANGE的属性.保存,更新和删除的命令.还有我的实体方法(使用Entity Framework进行CRUD操作-稍后将在我进一步了解MVVM等时使用存储库系统).

到目前为止,我必须使用视图背后的代码来加载可以正常工作的数据(显然,我不应该这样做,因此向我展示的任何建议将不胜感激),就像这样;

I have an issue in regards to my application, and not selecting an item for it to get updated using EF.

I''m creating an application for a user to enter in their organisation details. Seems simple enough. After data has been entered using a tab control, I have a view that contains a series of data grids, giving the user the ability to update, or insert another piece of data if required or have the ability to delete.

within my view-model I have a series of different things (which may not be the best way of implementing MVVM but trying to get there eventually).

I have my properties implementing INOTIFYPROPERTYCHANGE. Commands to save, update and delete. And my entity methods (CRUD opererations using Entity Framework - Will use a Repository system later on when I learn more about MVVM etc).

So far, I''m having to use the code-behind of the view to load my data which works perfectly (obviously I shouldn''t be doing this so any advice to show me otherwise would be much appreciated) like so;

private void Page_Loaded(object sender, RoutedEventArgs e)
    {
        DBEntities context = new DBEntities();

        var orgTypeDetails = (from o in context.OrganisationTypeDetails
                              select o).ToList();

        dgOrgTypeDetail.ItemsSource = orgTypeDetails;
    }


我还有一个事件处理程序,它仅允许您选择一行,然后更新该特定行;


I also have a event-handler which only allows you to select a row and then to update that specific row;

private void btnUpdateOrgTypeDetail_Click(object sender, RoutedEventArgs e)
    {
        OrganisationTypeDetailViewModel org = new OrganisationTypeDetailViewModel();

        OrganisationTypeDetail selected = dgOrgTypeDetail.SelectedItem as OrganisationTypeDetail;

        if (selected == null)
            MessageBox.Show("You must select a 'Type' before updating.");
        else
        {
            OrganisationTypeDetailUpdateView update = new OrganisationTypeDetailUpdateView();

            update.ShowDialog();
    org.UpdateOrganisationTypeDetail(selected);
            Page_Loaded(null, null);
        }
    }


最后,我的方法是使用View-Model中的EF更新表;


and finally, my method in which updates the table using EF from View-Model;

public void UpdateOrganisationTypeDetail(OrganisationTypeDetail orgTypeDetail)
    {
        using (DBEntities context = new DBEntities())
        {
            //var orgTD = context.OrganisationTypeDetails.Where(otd => otd.OrganisationTypeDetailID == OrganisationTypeDetailID).FirstOrDefault();

            var orgTD = (from a in context.OrganisationTypeDetails
                         where a.OrganisationTypeDetailID == OrganisationTypeDetailID
                         select a).FirstOrDefault();

            orgTD.OrganisationTypeDetailID = OrganisationTypeDetailID;
            orgTD.OrganisationTypeID = OrganisationTypeID;
            orgTD.Title = Title;
            orgTD.FirstName = FirstName;
            orgTD.Surname = Surname;
            orgTD.Position = Position;
            orgTD.DateOfBirth = DateOfBirth;
            orgTD.Address = Address;
            orgTD.Country = Country;
            orgTD.Postcode = Postcode;
            orgTD.PhoneNumber = PhoneNumber;
            orgTD.MobileNumber = MobileNumber;
            orgTD.FaxNumber = FaxNumber;
            orgTD.Email = Email;
            orgTD.NINumber = NINumber;

            context.OrganisationTypeDetails.ApplyCurrentValues(orgTD);
            context.SaveChanges();

            MessageBox.Show("Updated Organisation Type Details");
        }


执行此应用程序时,它会产生Null引用异常.

在我的属性中,为了确保它不会崩溃,我不得不以编程方式设置ID. public int _OrganisationTypeDetailID = 17;但是我希望能够选择一个行,而不是只选择从17行中获取行.

我尝试过同时使用Linq和Lamba表达式,但似乎都不起作用.

抱歉,阅读本书有很多帮助,并愿意在需要时添加更多代码或解释:).


-------编辑-------

香港专业教育学院改变了代码加载数据的方式
视图模型;


When executing this application, it produces a Null Reference exception.

In my properties, to ensure that it doesn''t crash, I have had to set programmatically the ID''s; public int _OrganisationTypeDetailID=17; But I want to be able to select a row at my choice, rather then only having the option to get the row from 17.

I''ve tried using both Linq and Lamba expressions but neither seem to work.

Sorry if there''s a lot to take in from reading this and would happy add more code or explanation if required :).


------- EDIT-------

Ive changed the way the code loads the data
View-Model;

public List<OrganisationTypeDetail> GetOrganisationTypeDetail
        {
            get
            {
                using (DBEntities context = new DBEntities())
                {
                    var query = from e in context.OrganisationTypeDetails
                                select e;
                    return query.ToList<OrganisationTypeDetail>();
                }
            }
        }



后台代码:



Code-behind:

private void Page_Loaded(object sender, RoutedEventArgs e)
        {
dgOrgTypeDetail.DataContext = new OrganisationTypeDetailViewModel();
}

推荐答案

好的,让我们从头开始:

为了简要了解MVVM及其主要角色,我认为本文 [ ^ ]是一个好的开始.
我还建议本文为 INotifyPropertyChanged
[ ^ ]
如果您已经(熟悉)DataContext,Binding和Commands的基本概念,将向您展示如何为您的解决方案实现这些概念(使用SelectedItem方法).
有关完整的代码示例,请参见此解决方案的底部,其中还介绍了ICollectionView方法.

假设您在"MainV.xaml"中定义了以下DataGrid.

ok, so let''s start from the beginning:

To get a brief overview of MVVM and it´s keyplayers, i think this article [^]is a good start.
I also recommend this article for a solid base-implementation for INotifyPropertyChanged
[^]
if you are (already) familiar with the basic concepts of DataContext, Binding and Commands, i''ll show you how you could implement thos concepts for your solution (using the SelectedItem-approach).
see the bottom of this solution for a complete code-sample, which also covers the ICollectionView-approach.

let´s say you have the following DataGrid defined in "MainV.xaml"

<datagrid>
    ItemsSource="{Binding Data}"
    SelectedItem="{Binding SelectedItem}" />

</datagrid>



WPF将尝试解析存储在DataGrid的DataContextProperty中的对象上的Data-Property和SelectedItem-Property(并通过INotifyPropertyChanged观察值的变化).

让我们看一下MainV的构造函数:



WPF would try to resolve a Data-Property and a SelectedItem-Property on the object that is stored in the DataGrid´s DataContextProperty (and also observes for value-changes through INotifyPropertyChanged).

Let''s take a look at the constructor of MainV:

public MainV() {
    InitializeComponent();

    var vm = new MainVM();
    DataContext = vm;

    vm.Load(); 
}



以及MainVM的定义:



and the definition of MainVM:

    public class MainVM : INotifyPropertyChanged {

	private IEnumerable<dataitem> _Data = null;
	public IEnumerable<dataitem> Data {
	    get {
	        return _Data;
	    }
	    private set {
	        _Data = value;
	        NotifyPropertyChanged("Data");
	    }
	}

	internal void Load() {
	//load dummy data synchronously; 
	//in a real worldapplication this should be done asynchronously 
	//e.g. using the new .net 4.5 async/await keywords
	Data = (from i in Enumerable.Range(1, 100)
		select new DataItem() {
		    Id = i,
		    Name = "Name " + i.ToString()
		}).ToArray();
	}

	private DataItem _SelectedItem;
        public DataItem SelectedItem {
            get {
                return _SelectedItem;
            }
            set {
                _SelectedItem = value;
                NotifyPropertyChanged("SelectedItem");
            }
        }

}



加载完成并将查询的数据分配给数据属性后,将引发PropertyChanged-Event,这将导致对其的绑定(ItemsSource ="{Binding Data}")进行重新评估.
如果用户随后单击记录,则DataGrid.SelectedItem会更改.因为我们将此属性绑定到我们的ViewModels SelectedItem-Property,所​​以此更改将自动传播到我们的ViewModel中.

为了消除Click事件处理程序,您可能会使用某种DelegateCommand(网络上有很多与此主题相关的文章)...

MainV.xaml:



Once Load has finished and the queried data is assigned to the Data-Property, the PropertyChanged-Event is raised, which causes the Binding to it (ItemsSource="{Binding Data}") to re-evaluate.
if a user then clicks on a record, the DataGrid.SelectedItem gets changed. Cause we are binding this property to our ViewModels SelectedItem-Property, this change will automatically be propagated into our ViewModel.

In order to eliminate the Click-eventhandler you would probably use some kind of DelegateCommand (there are lot´s of posts on this topic on the web)...

MainV.xaml:

<button>
    Content="Show and switch with slected item"
    Command="{Binding ShowAndSwitchWithSelectedItemCommand}" />
</button>



MainVM:



MainVM:

private readonly DelegateCommand _ShowAndSwitchWithSelectedItemCommand;
public ICommand ShowAndSwitchWithSelectedItemCommand {
    get {
        return _ShowAndSwitchWithSelectedItemCommand;
    }
}

public MainVM() {
	_ShowAndSwitchWithSelectedItemCommand = new DelegateCommand(
		p => {
			MessageBox.Show(SelectedItem.Name);
			var newItem = (from item in Data
					where item.Id == 17
					select item).FirstOrDefault();
			SelectedItem = newItem;
		}, 
		p => {
			return SelectedItem != null;
		}
	);
}



当然,该示例具有很大的优化潜力,但我希望它可以帮助您更好地进入MVVM.


完整的代码样本:

MainV.xaml:



Sure, this sample has much optimization potential, but i hope it helps you to find your way into MVVM a little bit better.


Complete code-sample:

MainV.xaml:

<window>
    x:Class="Earloc.MVVMSample.MainV"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Main View"
    Height="350"
    Width="525">
    <stackpanel>
        <stackpanel>
            Orientation="Horizontal">
            <stackpanel>
                <stackpanel>
                    Orientation="Horizontal">
                    <textblock>
                        Text="selected id:" />
                    <textblock>
                        Text="{Binding SelectedItem.Id}" />
                
                <combobox>
                    ItemsSource="{Binding Data}"
                    SelectedItem="{Binding SelectedItem}"
                    DisplayMemberPath="Name" />
                <button>
                    Content="Show and switch with slected item"
                    Command="{Binding ShowAndSwitchWithSelectedItemCommand}" />
                <datagrid>
                    ItemsSource="{Binding Data}"
                    SelectedItem="{Binding SelectedItem}" />
            
            <stackpanel>
                <stackpanel>
                    Orientation="Horizontal">
                    <textblock>
                        Text="selected id:" />
                    <textblock>
                        Text="{Binding Data/Id}" />
                    <!-- Slash (/) in binding does CollectionViewSource.GetDefaultView(Data).CurrentItem-->
                
                <combobox>
                    IsSynchronizedWithCurrentItem="True"
                    ItemsSource="{Binding Data}"
                    DisplayMemberPath="Name" />
                <button>
                    Content="Show and switch with default collection view"
                    Command="{Binding ShowAndSwitchWithCollectionView}"></button>
                <datagrid>
                    IsSynchronizedWithCurrentItem="True"
                    ItemsSource="{Binding Data}"
                     />
            
        </stackpanel>
    </stackpanel>
</window>



MainV.cs:



MainV.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace Earloc.MVVMSample {
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainV : Window {
        public MainV() {
            InitializeComponent();

            var vm = new MainVM();
            DataContext = vm;

            vm.Load();
        }
    }
}



MainVM.cs(由于这里有多个类的延迟):



MainVM.cs (due to lazyness multiple classes in here):

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Data;
using System.Windows.Input;

namespace Earloc.MVVMSample {

    public class DataItem {
        public string Name { get; set; }
        public int Id { get; set; }
    }

    public class MainVM : INotifyPropertyChanged {

        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged(string AIPropertyName) {
            var handler = PropertyChanged;
            if (handler != null)
                handler(this, new PropertyChangedEventArgs(AIPropertyName));
        }

        private IEnumerable<dataitem> _Data = null;
        public IEnumerable<dataitem> Data {
            get {
                return _Data;
            }
            private set {
                _Data = value;
                NotifyPropertyChanged("Data");
            }
        }

        private ICollectionView _collectionView;

        internal void Load() {
            //load dummy data synchronously; 
            //in a real worldapplication this should be done asynchronously 
            //e.g. using the new .net 4.5 async/await keywords
            Data = (from i in Enumerable.Range(1, 100)
                    select new DataItem() {
                        Id = i,
                        Name = "Name " + i.ToString()
                    }).ToArray();

            _collectionView = CollectionViewSource.GetDefaultView(Data);
            _collectionView.CurrentChanged += OnCurrentChanged; //should be detached when VM is discarded
        }

        private void OnCurrentChanged(object sender, EventArgs e) {
            _ShowAndSwitchWithCollectionView.NotifyCanExecuteChanged();
        }

        private DataItem _SelectedItem;
        public DataItem SelectedItem {
            get {
                return _SelectedItem;
            }
            set {
                _SelectedItem = value;
                NotifyPropertyChanged("SelectedItem");
                _ShowAndSwitchWithSelectedItemCommand.NotifyCanExecuteChanged();
            }
        }

        private readonly DelegateCommand _ShowAndSwitchWithSelectedItemCommand;
        public ICommand ShowAndSwitchWithSelectedItemCommand {
            get {
                return _ShowAndSwitchWithSelectedItemCommand;
            }
        }

        private readonly DelegateCommand _ShowAndSwitchWithCollectionView;
        public ICommand ShowAndSwitchWithCollectionView {
            get {
                return _ShowAndSwitchWithCollectionView;
            }
        }

        public MainVM() {
            _ShowAndSwitchWithSelectedItemCommand = new DelegateCommand(
                p => {
                    MessageBox.Show(SelectedItem.Name);
                    var newItem = (from item in Data
                                   where item.Id == 17
                                   select item).FirstOrDefault();
                    SelectedItem = newItem;
                }, 
                p => {
                    return SelectedItem != null;
                }
            );

            _ShowAndSwitchWithCollectionView = new DelegateCommand(
                p => {
                    var selectedItem = _collectionView.CurrentItem as DataItem;
                    if (selectedItem == null)
                        return;
                    MessageBox.Show(selectedItem.Name);

                    var newItem = (from item in Data
                                   where item.Id == 17
                                   select item).FirstOrDefault();

                    _collectionView.MoveCurrentTo(newItem);
                },
                p => {
                    return _collectionView.CurrentItem != null;
                }
            );
        }
    }

    public class DelegateCommand : ICommand {

        private readonly Action<object>     _Execute;
        private readonly Func<object,> _CanExecute;

        public DelegateCommand(Action<object> AIExecute, Func<object,> AICanExecute) {
            if (AIExecute == null)
                throw new ArgumentNullException("AIExecute");

            _Execute    = AIExecute;
            _CanExecute = AICanExecute;
        }

        public DelegateCommand(Action<object> AIExecute) 
            :this (AIExecute, null) {
        }

        public bool CanExecute(object AIParameter) {
            if (_CanExecute == null)
                return true;

            return _CanExecute(AIParameter);
        }

        public event EventHandler CanExecuteChanged;

        internal void NotifyCanExecuteChanged() {
            var handler = CanExecuteChanged;
            if (handler != null)
                handler(this, EventArgs.Empty);
        }

        public void Execute(object AIParameter) {
            _Execute(AIParameter);
        }
    }

}


我猜
var orgTD = (from a in context.OrganisationTypeDetails
                         where a.OrganisationTypeDetailID == OrganisationTypeDetailID
                         select a).FirstOrDefault();



返回null-在下面的行中导致NullReferenceException.

OrganisationTypeDetailID从何处获取其值?您说过,必须通过调试器将其设置为特定值,以防止崩溃...因此,必须将其设置为所选记录的ID.

尝试设置 IsSynchronizedWithCurrentItem [ ICollectionView [



returns null - resulting in the NullReferenceException at the following line.

where does OrganisationTypeDetailID gets its value from? you said, you had to set it to a specific value through the debugger in order to prevent the crash...so you have to set it to the id of the selected record.

try setting the IsSynchronizedWithCurrentItem[^]-Property of your DataGrid to true. Then, you can obtain an ICollectionView[^] and obtain the selected record from it using the call

var collectionView = CollectionViewSource.GetDefaultView(dgOrgTypeDetail.ItemsSource);
var selected = collectionView.CurrentItem;



(我使用dgOrgTypeDetail.ItemsSource作为源,因为您的代码未表明您将数据源存储在ViewModel中的其他位置)

请记住,使用ICollectionView不支持多选,因为它仅跟踪当前"项-即使选择了多个记录,也只会跟踪最后选择的一个.

顺便说一句:如果您想提供胶合代码以了解何时加载数据,请不要在视图中加载数据...将ViewModel设置为DataContext并在ViewModel中调用加载将加载数据,将其存储在ViewModel的属性中.视图然后绑定到此属性.
例如:



(i''m using dgOrgTypeDetail.ItemsSource as the source cause your code does not indicate that your are storing your datasource anywhere else within your ViewModel)

keep in mind, that using ICollectionView does not support multi-selection, as it only keeps track of the "current" item - even when multiple records are selected, only the last selected one is tracked.

btw: if you hav to provide glue code in order to get to know when to load your data, don´t load the data in your view...Set your ViewModel as DataContext and call a load in your ViewModel which will load data and store it in a property of your ViewModel. The View then binds to this property.
e.G:

private void Page_Loaded(object sender, RoutedEventArgs e) {
 var vm = new OrganisationTypeViewModel (); //new class responsible for loading data, etc.
  vm.Load(); //may be asynchronous.
 DataContext = vm;
}





<datagrid issynchronizedwithcurrentitem="true" itemssource="{Binding Data}" />



或者,您可以将DataGrid的SelectedItem-Property绑定到ViewModel属性,并以这种方式获取所选记录,但是这些天我经常使用ICollectionView,并且认为它非常方便.
通过这种方法,您可以在视图中隐藏极简代码!



alternatively you could bind the SelectedItem-Property of the DataGrid to a ViewModel Property and obtain the selected record this way, but i used ICollectionView a lot these days and think it´s quite handy.

With this approach you have minimalistic codebehind in your view!


这篇关于无法从数据网格“对象引用未设置为对象实例"中选择项目.的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆