avoid creating empty row in datagrid - wpf

I'm having two classes Cricket, Football and List of Observable collections of type object. Based on certain condition I want to add the object of type Cricket/Football to Observable Collection. I'm not assigning any data i.e just creating and instance of class Cricket/Football and adding that instance to Observable Collections and binding to UI. My expectation is, as I'm not assigning any data to the instance of Cricket/Football, only header has to create in the datagrid. But what I found was a row with the default value of the variables defined under the respective class along with the row header as I'm creating the instance of that class. How shall I avoid creating void row where my datagrid header is unaffected.
<DataGrid SelectionMode="Single" VerticalAlignment="Stretch" ItemsSource="{Binding itemSource, UpdateSourceTrigger=PropertyChanged}" CanUserReorderColumns="False" CanUserAddRows="False" IsReadOnly="True" CanUserDeleteRows="False" CanUserResizeColumns="False" HorizontalGridLinesBrush="Black" VerticalGridLinesBrush="Black" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Disabled" />
Edit

I think your datagrid's CanUserAddRows property is set to true. Just set it to false to fix your issue.
CanUserAddRows="false"
IsReadOnly="True"
EDIT :
Sorry. I read your question properly right now. It’s because the ObservableCollection’s type is object. I will show you a small sample so that you understand how binding works in a datagrid. Your collection should have public properties, so datagrid could bind columns to it. If you use collection type of Object than you have no properies for binding, so the empty rows will be displayed.
XAML :.
<DataGrid AutoGenerateColumns="False" Height="253" HorizontalAlignment="Left" Margin="27,24,0,0" Name="dataGrid1" VerticalAlignment="Top" Width="448">
<DataGrid.Columns>
<DataGridTextColumn Header="First" Binding="{Binding Path=Field, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</DataGrid.Columns>
</DataGrid>
Code behind :
public partial class MainWindow : Window
{
public ObservableCollection dataSource;
public MainWindow()
{
InitializeComponent();
this.dataSource = new ObservableCollection<SomeDataSource>();
this.dataSource.Add(new SomeDataSource { Field = "123" });
this.dataSource.Add(new SomeDataSource { Field = "1234" });
this.dataSource.Add(new SomeDataSource { Field = "12345" });
this.dataGrid1.ItemsSource = this.dataSource;
}
}
public class SomeDataSource
{
public string Field {get;set;}
}

Related

Binding to a collection in WPF

I have a problem trying to bind a DataGrid to a List, or to a Dictionary.
If I set DataContext to an object, and set ItemSource to a List property of that object, I get DataGrid populated with List count, in case of a List, why? How can I bind properly to a List, and how to a Dictionary?
List<string> con = new List<string>();
con.Add("aaaddd");
con.Add("bbb");
this.DataContext = con;
<DataGrid AutoGenerateColumns="True" Height="104" HorizontalAlignment="Left" Margin="34,171,0,0" Name="dg" VerticalAlignment="Top" Width="421" ItemsSource="{Binding}"/>
And I get populated with
Length
6
3
Why? And how to bind to a Dictionary?
Problem is you have set AutoGenerateColumns to True on your dataGrid.
When AutoGenerateColumns is set to true than columns are auto generated based on the properties exposed by the underlying object which is in your case is string which only exposes single property i.e. Length.
In case you want to get the value of string, you need to set that property to false and provide your own columns collection. This will work -
<DataGrid ItemsSource="{Binding}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Value"
IsReadOnly="True"
Binding="{Binding}"/>
</DataGrid.Columns>
</DataGrid>
As said in the comment, I think you're refering to WPF's DataGrid Control.
If you're setting the list object to the DataContext then just ItemsSource="{Binding}" this will bind to the DataContext root which is your list object.

WPF DataGrid TwoWay Binding

I have property UserSet which contains from ObservableCollection<GridRow>.
GridRow - my class, which contains 4 properties like this:
public int Id
{
get { return id; }
set
{
id = value;
RaisePropertyChanged("Id");
}
}
I populate UserSet, then Grid binds to it. When I change id field works setter Id. It sets right values.
But, after all changes, when I click other button my UserSet has not modified values. So I can't get updated Grid.
This is my XAML:
<DataGrid ItemsSource="{Binding UsersSet, Mode=TwoWay}" AutoGenerateColumns="True">
</DataGrid>
Please help.
You could try and set the UpdateSourceTrigger:
<DataGrid ItemsSource="{Binding UsersSet,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"
AutoGenerateColumns="True">
</DataGrid>
Without knowing the rest of your code it is pretty hard to guess.

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 get the active Row of the WPF DataGrid inside the TemplateSelector

I have a WPF Datagrid with three columns. The first two columns DataGridComboBox Columns. The third column is the Template column with ContentControl inside. I want to use this Datagrid as Data entry form for an advanced search. The first column will be binded with the list of properties about a document in the databse (like DocumentName, OwnerName, DateCreated etc). The second column will be a list of Operators (<,<=,!=). These operators will be dynamically changed based on the property selected from the combobox in the
cell[0]. This is handled in the SelectionChanged event of the first comboBox.
Inside the content control(In cell[2]),I need to load a DataTempalte(each contains diffetent UI controls like autocompleteBox,DatePicker,etc) defined in the window's Resources based on the property selected in the comboBox in the first colum. I have added a custom template selector for this purpose. But I am unable to access the DataGrid ComboxBox and its content of the first cell inside the DataTempalte selector code.
Please note that I didn't want to bind existing data to the datagrid. The datagrid will be used as data entry form. So I always want to retain the values selected in the previous rows in edit mode only.
Please refer the sample code from my UI and code behind:
public class DocumentSearchProperty
{
public string PropertyName { get; set; }
public string Operator { get; set; }
public string PropertyValue { get; set; }
}
This is the class which I set as a itemsource for my Datasource.
<DataGrid AutoGenerateColumns="False" Width="Auto" Name="documentPropertyGrid"
ItemsSource="{}"
SelectionMode="Single" CanUserAddRows="True" CanUserDeleteRows="True" CanUserResizeColumns="True" CanUserSortColumns="True"
CanUserResizeRows="False" FrozenColumnCount="0" HorizontalContentAlignment="Stretch" HorizontalAlignment="Stretch" RowHeaderWidth="0"
AlternatingRowBackground="Gainsboro" AlternationCount="2" VerticalContentAlignment="Stretch" VerticalAlignment="Top" VerticalScrollBarVisibility="Auto">
<DataGrid.Columns>
<DataGridComboBoxColumn x:Name="columnProperty" ItemsSource="{}" SelectedItemBinding="{Binding Path=Id}"
Header="Property" Width="170"/>
<DataGridComboBoxColumn x:Name="columnOperator" ItemsSource="{}" SelectedItemBinding="{Binding Path=value}" Header="Operator" Width="170" />
<DataGridTemplateColumn x:Name="PropertyValue" Header="Value" Width="100">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ContentControl x:Name="CntControl">
<ContentControl.Content>1</ContentControl.Content>
</ContentControl>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
The below code is used to handle the selection changed event of the first comboBox. Inside this selection changed event I can get the ComboBox in the cell[1]. But I cannot get the ContentControl in cell[2]. Can anybody suggest a better way to achiveve this?
DataGridRow row = this.documentPropertyGrid.ItemContainerGenerator.ContainerFromIndex(this.documentPropertyGrid.SelectedIndex) as DataGridRow;
ComboBox ele = this.documentPropertyGrid.Columns[0].GetCellContent(row) as ComboBox;
ContentControl contentControl = this.documentPropertyGrid.Columns[2].GetCellContent(row) as ContentControl;
I have made following changes for getting the third column of the grid as content presenter:
ContentPresenter contentPresenter = this.documentPropertyGrid.Columns[2].GetCellContent(row) as ContentPresenter;
After the above change I can now assign a datatemplate dynamically to the contentPresenter as follows in the SelectionChanged event of the comboxBox Column(in the first column).
DataTemplate dt = new DataTemplate();
switch (propertyName)
{
case "DocumentName":
dt = this.FindResource("AutoCompleteBoxTemplate") as DataTemplate;
break;
case "DateCreated":
case "DateModified":
case "DateAccessed":
dt = this.FindResource("DatePickerTemplate") as DataTemplate;
break;
default:
dt = this.FindResource("AutoCompleteBoxTemplate") as DataTemplate;
break;
}
contentPresenter.ContentTemplate = dt;
But now in the second column the Operator values which I have set are replaced with the latest operator in all the rows. For e-g If the property selected in the first combo box is TemplateName there will be a list of two operators (Equal,NotEqual). But when the DateCreated is selected in the first comboBox then the operator column in the second row should display (<,<=,>,>=). But since I am assigning these operators by using the name of the comboxColumn(columnOperator), the operators in the first row also updated with operators for DateCreated. How to restore the values in previous rows?

cannot find Name of DataGridColumn programmatically

I found the Columns collection in my datagrid, and was hoping to iterate through it to find a certain column Name. However, I can't figure out how to address the x:Name attribute of the column. This xaml illustrates my problem with a DataGridTextColumn and a DataGridTemplateColumn:
<t:DataGrid x:Name="dgEmployees" ItemsSource="{Binding Employees}"
AutoGenerateColumns="false" Height="300" >
<t:DataGrid.Columns>
<t:DataGridTextColumn x:Name="FirstName" Header="FirstName"
Binding="{Binding FirstName}" />
<t:DataGridTemplateColumn x:Name="LastName" Header="LastName" >
<t:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding LastName}" />
</DataTemplate>
</t:DataGridTemplateColumn.CellTemplate>
</t:DataGridTemplateColumn>
</t:DataGrid.Columns>
</t:DataGrid>
And here is my code:
DataGrid dg = this.dgEmployees;
foreach (var column in dg.Columns)
{
System.Console.WriteLine("name: " + (string)column.GetValue(NameProperty));
}
At runtime, no value is present; column.GetValue doesn't return anything. Using Snoop, I confirmed that there is no Name property on either DataGridTextColumn or DataGridTemplateColumn.
What am I missing?
WPF has two different, yet similar concepts, x:Name, which is used to create a field which references an element defined in XAML, i.e. connecting your code-behind to your XAML, and FrameworkElement.Name, which uniquely names an element within a namescope.
If an element has a FrameworkElement.Name property, x:Name will set this property to the value given in XAML. However, there are instances where it is useful to link non FrameworkElement elements to fields in code-behind, such as in your example.
See this related question:
In WPF, what are the differences between the x:Name and Name attributes?
As an alternative, you could define your own attached property which can be used to name the columns. The attached property is defined as follows:
public class DataGridUtil
{
public static string GetName(DependencyObject obj)
{
return (string)obj.GetValue(NameProperty);
}
public static void SetName(DependencyObject obj, string value)
{
obj.SetValue(NameProperty, value);
}
public static readonly DependencyProperty NameProperty =
DependencyProperty.RegisterAttached("Name", typeof(string), typeof(DataGridUtil), new UIPropertyMetadata(""));
}
You can then assign a name to each column ...
xmlns:util="clr-namespace:WPFDataGridExamples"
<t:DataGrid x:Name="dgEmployees" ItemsSource="{Binding Employees}"
AutoGenerateColumns="false" Height="300" >
<t:DataGrid.Columns>
<t:DataGridTextColumn util:DataGridUtil.Name="FirstName" Header="FirstName"
Binding="{Binding FirstName}" />
<t:DataGridTemplateColumn util:DataGridUtil.Name="LastName" Header="LastName" >
<t:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding LastName}" />
</DataTemplate>
</t:DataGridTemplateColumn.CellTemplate>
</t:DataGridTemplateColumn>
</t:DataGrid.Columns>
</t:DataGrid>
Then access this name in code as follows:
DataGrid dg = this.dgEmployees;
foreach (var column in dg.Columns)
{
System.Console.WriteLine("name: " + DataGridUtil.GetName(column));
}
Hope that helps
You can use linq query to find name of the datagrid column Headers
dgvReports.Columns.Select(a=>a.Header.ToString()).ToList()
where dgvReports is name of the datagrid.

Resources