Adding Event Handlers to WPF DataGrid throws NullReferenceException - wpf

I'm not sure what's wrong with the DataGrid I've written. The contents show up correctly but when I try to add in a OnSelectionChanged event handler, sth odd happens.Please help me!
Firstly, no problems below:
<DataGrid ItemsSource="{Binding XPath=services/service}" AutoGenerateColumns="False" Padding="2">
<DataGrid.Columns>
<DataGridTextColumn Header=" Service Name " Binding="{Binding XPath=name}" Width="300"/>
<DataGridTextColumn Header=" Status " Binding="{Binding XPath=status}" />
</DataGrid.Columns>
</DataGrid>
Here services/service are from my external XML file. I used XmlDataProvider resource in the document. In that XML, some 'services' tag has many 'service' children elements; some 'services' tag does not have any children at all (wondering if this is the cause of problem).
So the resultant UI is some of the datagrids contain all the rows and columns. some of the datagrids only show the headers.
Now I add in this:
<DataGrid ItemsSource="{Binding XPath=services/service}" AutoGenerateColumns="False" Padding="2"
SelectionChanged="DataGrid_SelectionChanged">
And put in a empty method on the code:
private void DataGrid_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
}
Now I execute, and an exception is thrown
System.NullReferenceException was unhandled
Message=Object reference not set to an instance of an object.
Source=ForeFront Support Monitor 2
StackTrace:
at FSM.MainWindow.System.Windows.Markup.IStyleConnector.Connect(Int32 connectionId, Object target) in h:\Personal\Visual Studio 2010\Projects\ForeFront Support Monitor 2\ForeFront Support Monitor 2\MainWindow.xaml:line 42
at System.Windows.FrameworkTemplate.LoadTemplateXaml(XamlReader templateReader, XamlObjectWriter currentWriter)
at System.Windows.FrameworkTemplate.LoadTemplateXaml(XamlObjectWriter objectWriter)
at System.Windows.FrameworkTemplate.LoadOptimizedTemplateContent(DependencyObject container, IComponentConnector componentConnector, IStyleConnector styleConnector, List1 affectedChildren, UncommonField1 templatedNonFeChildrenField)
at System.Windows.FrameworkTemplate.LoadContent(DependencyObject container, List`1 affectedChildren)...

XML binding to WPF DataGrid can be torturous.
Try binding XML in a different way (like a object model)... How to bind xml to the WPF DataGrid correctly?
And then check if selection changed event fires correctly?

Related

How to create a IList Dep property in DataGridTextColumn custom control

I want to pass a list object to my Custom control of datagrid DataGridTextColumn.
for this I used this code
public class DataGridListBoxColumn : DataGridTextColumn
{
public IList<Student> ListItems
{
get { return (IList<Student>)GetValue(_ListItems); }
set { SetValue(_ListItems, value); }
}
public static readonly DependencyProperty _ListItems = DependencyProperty.Register("ListItems", typeof(IList<Student>), typeof(DataGridListBoxColumn));
}
I XAML
<local:DataGridListBoxColumn Binding="{Binding M_Name,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}" ListItems="{Binding Path= stud, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}}" Width="100"/>
OR
<local:DataGridListBoxColumn Binding="{Binding M_Name,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}" ListItems="{Binding RelativeSource={RelativeSource AncestorType=DataGridTextColumn}, Path=stud}" Width="100"/>
Both are not working, Is there any method to pass list to my Custom control
Thanks
The problem with DataGrid columns is, that they are not present in the visual or logical tree, therefore you can't use RelativeSource. The possibility, which also has restrictions, is to set Source for your Binding with x:Reference. Restriction is, that you can't set as source UIElement, which contains element with binding. So you can't set your main window or datagrid, which contains the column as binding's source otherwise you'll get cyclical dependency.
Now put next to your DataGrid a hidden control as access to the data, or use some existing:
<TextBlock x:Name="studAccess" Visibility="Collapsed"/>
<DataGrid>
<local:DataGridListBoxColumn .../>
</DataGrid>
and bind you dependency property of columns via this control(implied is, that DataContext of studAccess has a stud property):
<local:DataGridListBoxColumn Binding="{Binding M_Name,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}" ListItems="{Binding DataContext.stud, Source={x:Reference studAccess}}" Width="100"/>

DataGridColumnHeader of a DataGrid defined in a DataTemplate for a ViewModel

I have successfully used ProxyElement to pass Data Context of my Data Grid to DataGridColumnHeaders. However, I am trying out something new and I just can't figure out what I am doing wrong over here.
Here is what I am trying to do: I am creating a UserControl and associating it to my ViewModel in my Resources file (see Resources.xaml code snippet below).
Resources.xaml:
<ResourceDictionary
xmlns:myVm="clr-namespace:..."
xmlns:myUserControl="clr-namespace:...">
<DataTemplate DataType={x:Type myVm:DummyModel}">
<myUserControl:DummyUserControl />
</DataTemplate>
</ResourceDictionary>
Now in my UserControl, I have a DataGrid with a DataGridComboBoxColumn. I am trying to access my data context to set its item source and in the past I was able to do it using proxy element. This time however I am not able to do so. (See DummyUserControl.xaml code snippet below)
DummyUserControl.xaml:
<UserControl x:Class="Client.MyControl.DummyUserControl"
...>
<UserControl.Resources>
<FrameworkElement x:Key="ProxyElement" x:Name="ProxyElement"
DataContext="{Binding}" />
</UserControl.Resources>
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Products}">
<DataGridComboBoxColumn
Header="Company"
ItemsSource="{Binding Path=DataContext.ProductCompanies,
Source={StaticResource ProxyElement}}"
DisplayMemberPath="Name" SelectedValuePath="Id"
SelectedValueBinding="{Binding CompanyId}" />
</DataGrid>
</UserControl>
When I do this, my binding fails with the following message:
System.Windows.Data Error: 3 : Cannot find element that provides DataContext.
BindingExpression:(no path); DataItem=null; target element is 'FrameworkElement'
(Name='ProxyElement'); target property is 'DataContext' (type 'Object')
I have no idea what to do here. I remember reading that the datacontext for a datatemplate is automatically set, so I have no idea why the data context is null in this case. To prove it is null, I also tried setting the binding in the code-behind file and added a breakpoint to check its value (which was null).
Can anyone suggest what to do here?
Edit 1
I have also tried the following approaches:
Remove ProxyElement altogether and see if it can detect DataContext. To no surprise, this failed.
Tried binding to the templated parent. Fail.
Tried binding to the UserControl itself. Fail.
I also tried referencing the data context of the parent item where this view model is going to be displayed, which is in a TabItem of a TabControl.
All of the alternate bindings gave me same error as the error above.
Here is a (working but not preferred) solution to this problem. You will realize why it is not preferred by the end of it.
The key to this problem is understanding what and how data context of a data template works. Whenever you define a Data Template for a View Model, the data context for the view that follows, whether it is a user control or just xaml itself, is the View Model! This shouldn't be a surprise to anyone.
But this will surprise people: if you specify a User Control, the Data Context of the User Control is not set during construction of the User Control! In other words, in the constructor of User Control, Data Context is going to be null. Furthermore, any XAML code that relies on the Data Context at construction time, which in this case was my FrameworkElement resource called ProxyElement got its DataContext set to null because it gets constructed at construction time of the User Control!
So when does the DataContext get set? Simply after the User Control is created. In pseudo code, this following describes the logic behind drawing a ViewModel:
Draw ViewModel x;
DataTemplate in ResourceDictionary says ViewModel x can be drawn using UserControl abc
Let's create a new instance of UserControl abc
Let's now assign the DataContext of abc to the ViewModel itself.
Let's return the newly created instance of UserControl abc
So what do we do to solve the problem in this question?
UserControl.xaml:
<UserControl ...
DataContextChanged="DaCoHasChanged">
<UserControl.Resources>
<FrameworkElement x:Key="ProxyElement" /> <!--Remove DataContext="{Binding}"-->
</UserControl.Resources>
<DataGrid ...>
<DataGridComboBoxColumn
ItemsSource="{Binding Path=DataContext.ProductCompanies,
Source={StaticSource ProxyElement}}"
... />
</DataGrid>
</UserControl>
UserControl.xaml.cs:
private void DaCoHasChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var proxyElement = Resources["ProxyElement"] as FrameworkElement;
proxyElement.DataContext = e.NewValue; // instead of e.NewValue, you could
// also say this.DataContext
}
I am trying to figure out a way of getting rid of the code in the code-behind file. But till then, if someone else hits this problem, then they might be able to get inspired from this solution.
Credit to the concept behind this solution goes to: How to set the DataContext for a View created in DataTemplate from ViewModel
Try this
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Products}">
<DataGridTemplateColumn Header="Company">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox
ItemsSource="{Binding Path="{Binding DataContext.ProductCompanies,RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"
DisplayMemberPath="Name" SelectedValuePath="Id"
SelectedValueBinding="{Binding CompanyId}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Now as I got your problem I think the problem is in DataGridComboBoxColumn I dont know why it not Binding using RelativeResource . Try it with DataGridTemplateColumn and you would not require any ProxyElement I hope this will help.

How to create a WPF ListCollectionView to sort DataGrid CollectionViewSources

Here are my CollectionViewSources:
<CollectionViewSource x:Key="topLevelAssysViewSource" d:DesignSource="{d:DesignInstance my:TopLevelAssy, CreateList=True}" />
<CollectionViewSource x:Key="topLevelAssysRefPartNumsViewSource" Source="{Binding Path=RefPartNums, Source={StaticResource topLevelAssysViewSource}}" />
<CollectionViewSource x:Key="topLevelAssysRefPartNumsRefPartNumBomsViewSource" Source="{Binding Path=RefPartNumBoms, Source={StaticResource topLevelAssysRefPartNumsViewSource}}" />
I currently have the following controls feeding data to one another:
DataContext for my window is fed through a Grid housing all of my Controls:
<Grid DataContext="{StaticResource topLevelAssysViewSource}">
A ComboBox:
<ComboBox DisplayMemberPath="TopLevelAssyNum" Height="23" HorizontalAlignment="Left" ItemsSource="{Binding}" Margin="12,12,0,0" Name="topLevelAssysComboBox" SelectedValuePath="TopLevelAssyID" VerticalAlignment="Top" Width="120" />
a ListBox:
<ListBox DisplayMemberPath="RefPartNum1" Height="744" HorizontalAlignment="Left" ItemsSource="{Binding Source={StaticResource topLevelAssysRefPartNumsViewSource}}" Margin="12,41,0,0" Name="refPartNumsListBox" SelectedValuePath="RefPartNumID" VerticalAlignment="Top" Width="120" />
Finally, a DataGrid which I am trying to make Sort-able: (Just one Column for now):
<DataGrid CanUserSortColumns="true" AutoGenerateColumns="False" EnableRowVirtualization="True" HorizontalAlignment="Left" ItemsSource="{Binding Source={StaticResource topLevelAssysRefPartNumsRefPartNumBomsViewSource}}" Margin="6,6,0,1" Name="refPartNumBomsDataGrid" RowDetailsVisibilityMode="VisibleWhenSelected" Width="707">
<DataGrid.Columns >
<DataGridTextColumn x:Name="cageCodeColumn" Binding="{Binding Path=CageCode}" Header="CageCode" Width="45" />
<DataGridTextColumn x:Name="partNumColumn" Binding="{Binding Path=PartNum}" Header="PartNum" Width="165" SortDirection="Ascending" />
</DataGrid.Columns>
</DataGrid>
My Exact code thus far is:
public partial class MainWindow : Window
{
racr_dbEntities racr_dbEntities = new racr_dbEntities();
public MainWindow()
{
InitializeComponent();
}
private System.Data.Objects.ObjectQuery<TopLevelAssy> GetTopLevelAssysQuery(racr_dbEntities racr_dbEntities)
{
// Auto generated code
System.Data.Objects.ObjectQuery<racr_dbInterface.TopLevelAssy> topLevelAssysQuery = racr_dbEntities.TopLevelAssys;
// Update the query to include RefPartNums data in TopLevelAssys. You can modify this code as needed.
topLevelAssysQuery = topLevelAssysQuery.Include("RefPartNums");
// Update the query to include RefPartNumBoms data in TopLevelAssys. You can modify this code as needed.
topLevelAssysQuery = topLevelAssysQuery.Include("RefPartNums.RefPartNumBoms");
// Returns an ObjectQuery.
return topLevelAssysQuery;
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
// Load data into TopLevelAssys. You can modify this code as needed.
CollectionViewSource topLevelAssysViewSource = ((CollectionViewSource)(this.FindResource("topLevelAssysViewSource")));
ObjectQuery<racr_dbInterface.TopLevelAssy> topLevelAssysQuery = this.GetTopLevelAssysQuery(racr_dbEntities);
topLevelAssysViewSource.Source = topLevelAssysQuery.Execute(MergeOption.AppendOnly);
ListCollectionView topLevelAssyView = CollectionViewSource.GetDefaultView(CollectionViewSource.CollectionViewTypeProperty) as ListCollectionView;
topLevelAssyView.SortDescriptions.Add(new SortDescription("PartNum", ListSortDirection.Descending));
}
I have read and understand the importance of creating the ListCollectionViews in order to handle the sort properties included in the CollectionViewSource, which I got from blog Bea Stollnitz's blog.
However, I keep getting the error message Null Reference Exception Unhandled: "Object reference not set to an instance of the object."
How do I take care of this issue? Do I need to further define my ListCollectionView, or perhaps I need to establish an ICollectionView? My PartNum column contains part numbers that begin with numbers and sometimes letters. Will the standard sortdirections apply?
Please provide full stack trace for the exception, or at least number of line in your example which throws this exception.
From what you've provided so far, I think that the source of error is
ListCollectionView topLevelAssyView = CollectionViewSource.GetDefaultView(CollectionViewSource.CollectionViewTypeProperty) as ListCollectionView;
If you are using Entity Framework, the default View for ObjectQuery Results will not be ListCollectionView, hence NullReferenceException.
To use ObjectQuery/EntityCollection as the source for CollectionViewSource and sort with it you have to wrap it in some other container that supports sorting (and if want to perform CRUD, use that container everywhere instead of source EntityCollection).
For example, try something along those lines:
ObservableCollection<TopLevelAssy> observableCollection = new ObservableCollection(topLevelAssysQuery.Execute(MergeOption.AppendOnly));
((ISupportInitialize)topLevelAssysViewSource).BeginInit();
topLevelAssysViewSource.CollectionViewType = typeof(ListCollectionView);
topLevelAssysViewSource.Source = observableCollection;
topLevelAssysViewSource.SortDescriptions.Add(new SortDescription("CageCode", ListSortDirection.Ascending));
((ISupportInitialize)topLevelAssysViewSource).EndInit();
And change your binding to refer to CollectionViewSource.View property:
ItemsSource="{Binding Source={StaticResource topLevelAssysViewSource}, Path=View}"
Additional reading: http://blog.nicktown.info/2008/12/10/using-a-collectionviewsource-to-display-a-sorted-entitycollection.aspx

How to mix Silverlight DataGrid columns with and without binding?

In Silverlight 4 I have a DataGrid which is bound to a RIA DomainDataSource, all done in XAML. The AutoGenerateColumns property is set to false and each column is manually defined and bound. Basically this works fine.
Where I now run into problems is having an additional DataGridTextColumn which has no Binding. I want to manually populate the cells in this column in the code behind. When having this column without the Binding, at runtime the following exception pops up:
System.ArgumentNullException: Value cannot be null.
Parameter name: binding
at System.Windows.Data.BindingOperations.SetBinding(DependencyObject target, DependencyProperty dp, BindingBase binding)
at System.Windows.Controls.DataGridTextColumn.GenerateElement(DataGridCell cell, Object dataItem)
at System.Windows.Controls.DataGrid.PopulateCellContent(Boolean isCellEdited, DataGridColumn dataGridColumn, DataGridRow dataGridRow, DataGridCell dataGridCell)
at System.Windows.Controls.DataGrid.AddNewCellPrivate(DataGridRow row, DataGridColumn column)
at System.Windows.Controls.DataGrid.CompleteCellsCollection(DataGridRow dataGridRow)
at System.Windows.Controls.DataGrid.GenerateRow(Int32 rowIndex, Int32 slot, Object dataContext)
at System.Windows.Controls.DataGrid.AddSlots(Int32 totalSlots)
at System.Windows.Controls.DataGrid.RefreshRows(Boolean recycleRows, Boolean clearRows)
at System.Windows.Controls.DataGrid.RefreshRowsAndColumns(Boolean clearRows)
at System.Windows.Controls.DataGrid.InitializeElements(Boolean recycleRows)
at System.Windows.Controls.DataGridDataConnection.NotifyingDataSource_CollectionChanged(Object sender, NotifyCollectionChangedEventArgs e)
at System.Windows.Controls.DomainDataSourceView.OnCollectionChanged(NotifyCollectionChangedEventArgs e)
at System.Windows.Controls.DomainDataSourceView.OnCollectionViewCollectionChanged(Object sender, NotifyCollectionChangedEventArgs e)
at System.Windows.Controls.EntityCollectionView.OnCollectionChanged(NotifyCollectionChangedEventArgs args)
at System.Windows.Controls.PagedEntityCollectionView.OnCollectionChanged(NotifyCollectionChangedEventArgs args)
at System.Windows.Controls.PagedEntityCollectionView.RefreshView()
at System.Windows.Controls.PagedEntityCollectionView.SourceCollectionChanged(NotifyCollectionChangedEventArgs args)
at System.Windows.Controls.EntityCollectionView.HandleSourceCollectionChanged(Object sender, NotifyCollectionChangedEventArgs e)
at System.Windows.Controls.PagedEntityCollection.RaiseCollectionChanged(NotifyCollectionChangedAction action, Entity entity, Int32 index)
at System.Windows.Controls.PagedEntityCollection.CompleteLoad()
at System.Windows.Controls.DomainDataSource.ProcessLoadedEntities(LoadContext loadContext, IEnumerable`1 entities)
at System.Windows.Controls.DomainDataSource.DomainContext_Loaded(LoadedDataEventArgs e, LoadContext loadContext)
How can I avoid this exception and have my DataGrid with most columns bound and one column unbound?
Thanks in advance.
Edit:
My XAML looks as follows:
<sdk:DataGrid AutoGenerateColumns="False"
ItemsSource="{Binding Data, ElementName=MyDomainDataSource, Mode=OneWay}">
<sdk:DataGrid.Columns>
<sdk:DataGridTextColumn Binding="{Binding Name}" Header="Name" />
<sdk:DataGridTextColumn Binding="{Binding CreationDate}" Header="Creation Date" />
<sdk:DataGridTextColumn Header="Unbound Col" />
</sdk:DataGrid.Columns>
</sdk:DataGrid>
Edit #2:
One idea I found on the web was to bind the last column to some unique value (I have an ID in my data model) and use a custom IValueConverter which converts the ID to a dependent value. This would have been exactly what I wanted, but unfortunately I get the dependent value from a WCF service call, which is always asynchronous. As you cannot use async method calls in an IValueConverter this solution is not an option for me.
Have you considered using a DataGridTemplateColumn instead? It does not matter if you set the binding or not for this type of column.
For example, if you just want to set the unbound value once at the time of loading you could do something like this:
<sdk:DataGridTemplateColumn>
<sdk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox x:Name="MyText" Loaded="MyText_Loaded"></TextBox>
</DataTemplate>
</sdk:DataGridTemplateColumn.CellTemplate>
</sdk:DataGridTemplateColumn>
...and then set the value in the code behind like this:
private void MyText_Loaded(object sender, RoutedEventArgs e)
{
var textBox = sender as TextBox;
if (textBox != null) textBox.Text = "My one off text";
}
I'm not sure what your exact requirements are.

wpf datagrid databind with nested objects (like master detail)

I have a simple problem binding an entity to datagrid in wpf.
I have an entity Called "User".... and each "User" have one "Workgroup" .. the relationship between the two is one to one.
now in EF every User entity has one workgroup entity inside.
when I want to bind Users Collection to datagrid I have no clue hot to say that you have to put forexample workgroup.Title inside a datagrid Column
I'm trying to bind in this way :
XAML:
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Users}" HorizontalAlignment="Stretch" Margin="5" Name="dgUserList" VerticalAlignment="Stretch" SelectionChanged="dgUserList_SelectionChanged">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding FirstName}" Header="FirstName" />
<DataGridTextColumn Binding="{Binding LastName}" Header="LastName" />
<DataGridTextColumn Binding="{Binding Username}" Header="UserName" />
<DataGridTextColumn Binding="{Binding WorkGroup}" Header="Workgroup" />
</DataGrid.Columns>
</DataGrid>
Code Behind :
Created a property like this :
public List<User> Users
{
get { return dal.GetUsers(); }
}
and do the binding :
private void BindGrid()
{
dgUserList.ItemsSource = Users;
}
this work file with direct properties of User Entity but it puts type of Workgroup entity inside the datagrid column and the reason is obvious. I want to put the Title of workgroup inside
how can i achive this ?
any help would be greatly appreciated
WPF Bindings support nested properties so to get to any of the sub-properties on a property of a bound object just use normal "." syntax:
<DataGridTextColumn Binding="{Binding WorkGroup.Title}" Header="Workgroup" />
You also don't need to set the ItemsSource twice. If you have the DataContext of the DataGrid set up as the Window (or UserControl, etc.) whose code-behind declared the Users property then the ItemsSource Binding in XAML is sufficient and you can remove the BindGrid method. If you haven't set the DataContext, the XAML ItemsSource Binding isn't doing anything (you can probably see an error message in your Debug Output) so you can remove that and just let the code-behind method take care of it.
You should also consider using an ObservableCollection to get automatic notification and UI updates when items are added or removed. Since you're already using EF you may also be able to just use the EntityCollection for User, which includes the same automatic INotifyCollectionChanged notification.

Resources