cannot find Name of DataGridColumn programmatically - wpf

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.

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

avoid creating empty row in datagrid

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;}
}

reusable datatemplate binding to celltemplate

I have the following datagrid
<DataGrid x:Name="dataGrid1"
AutoGenerateColumns="False"
ItemsSource="{Binding}">
<DataGrid.Resources>
<DataTemplate DataType="{x:Type DataTypes:Foo}" x:Key="dTemp">
<TextBox Background="{Binding BgColor}" Text="{Binding Path=RowId}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type DataTypes:Foo}" x:Key="dTemp2">
<TextBox Background="{Binding BgColor}" Text="{Binding Path=Alias}"/>
</DataTemplate>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTemplateColumn CellTemplate="{StaticResource dTemp}"/>
<DataGridTemplateColumn CellTemplate="{StaticResource dTemp2}"/>
</DataGrid.Columns>
</DataGrid>
and in the codebehind I have:
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Media;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public ObservableCollection<Foo[]> ObservableCollection;
public MainWindow()
{
InitializeComponent();
this.ObservableCollection = new ObservableCollection<Foo[]>();
Foo[] ocoll = new Foo[3];
ocoll[0] = (new Foo(1, "FIRST ARRAY FIRST ROW FIRST COLUMN", Brushes.Aqua));
ocoll[1] = (new Foo(2, "FIRST ARRAY FIRST ROW SECOND COLUMN", Brushes.Red));
ocoll[2] = (new Foo(3, "FIRST ARRAY FIRST ROW THIRD COLUMN", Brushes.Green));
Foo[] ocoll2 = new Foo[3];
ocoll2[0] = (new Foo(4, "SECOND ARRAY SECOND ROW FIRST COLUMN", Brushes.Aqua));
ocoll2[1] = (new Foo(5, "SECOND ARRAY SECOND ROW SECOND COLUMN", Brushes.Red));
ocoll2[2] = (new Foo(6, "SECOND ARRAY SECOND ROW THIRD COLUMN", Brushes.Green));
this.ObservableCollection.Add(ocoll);
this.ObservableCollection.Add(ocoll2);
dataGrid1.DataContext = ObservableCollection;
}
}
public class Foo
{
public int RowId { get; set; }
public string Alias { get; set; }
public Brush BgColor { get; set; }
public Foo(int rowId, string #alias, Brush bgColor)
{
this.RowId = rowId;
this.Alias = alias;
this.BgColor = bgColor;
}
}
}
The Object Foo has more than 30 properties (I only wrote 2 in here to make it easier to follow)..
the question is:
do I really have to define 30 different datatemplates (like dTemp, dTemp2, ... see above) to bind the CellTemplate of each DataGridTemplateColumn to it ?
You can create just one implicit DataTemplate for your data type if you do not declare an x:Key value for it:
<DataTemplate DataType="{x:Type DataTypes:YourDataType}">
<TextBox Text="{Binding TextDescription}" Background="{Binding Color}" />
</DataTemplate>
This will automatically be applied to all of your instances of your data type that are within the scope of this DataTemplate, eg. put it in your control Resources or Application.Resources section.
UPDATE >>>
Unfortunately, you still don't seem to have it right. If you re-read my original answer, you should see that I specified that you do not declare an x:Key value for the DataTemplate.
Furthermore, you previously said that all of the DataTemplates would be the same, but now you have added two different ones, so this method will no longer work.
UPDATE 2 >>>
MSDN states that 'A DataTemplate describes the visual structure of a data object'. It might be clearer if it said that a DataTemplate can show the properties of one type of data object. However, only one DataTemplate is required to be defined per object type if you do not declare an x:Key value for it. So if you define one for a particular type, all instances of that type will be automatically rendered according to that DataTemplate.
You asked me 'how to set the DataTemplate to display more than one element in it'... if by that you mean 'how to apply the DataTemplate to more than one instance of the defined data type', the answer is simply to not declare an x:Key value for it and do not set the DataGridTemplateColumn.CellTemplate property. Using this method would render each item with the same UI controls, but with the different values from each instance.
However, if you mean 'how to display different instances of the defined data type differently' as is currently shown in your question, then the answer is shown in your question... you would have to explicitly declare each one with a x:Key value and set the DataGridTemplateColumn.CellTemplate property to the named DataTemplate.

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

Bbinding combobox within dataform to view model property outside dataform's context

I have two properties in my view model:
//Relationship has property ReasonForEndingId
private Relationship editRelationship;
public Relationship EditRelationship
{
get
{
return editRelationship;
}
set
{
if (editRelationship != value)
{
editRelationship = value;
RaisePropertyChanged(EditRelationshipChangedEventArgs);
}
}
}
//ReasonForLeaving has properties Reason & Id
private IList<ReasonForLeaving> reasonsComboList { get; set; }
public IList<ReasonForLeaving> ReasonsComboList
{
get
{
return reasonsComboList;
}
private set
{
if (reasonsComboList != value)
{
reasonsComboList = value;
RaisePropertyChanged(ReasonsComboListChangedEventArgs);
}
}
}
In my xaml I have the following: (specifically note the binding on the dataform and combobox)
<toolkit:DataForm x:Name="EditForm" CurrentItem="{Binding EditRelationship, Mode=TwoWay}">
<toolkit:DataForm.EditTemplate>
<DataTemplate>
<StackPanel>
<toolkit:DataField>
<ComboBox x:Name="EndReasonCombo" ItemsSource="{Binding ReasonsComboList}" DisplayMemberPath="Reason" SelectedValuePath="Id" SelectedValue="{Binding ReasonForEndingId, Mode=TwoWay}"/>
</toolkit:DataField>
So, I'm trying to bind to a list that exists in my viewmodel (the datacontext for the page). However, the DataForm's datacontext is EditRelationship. ReasonsComboList does not exist within EditRelationship.
How can I bind the combobox so that it will display the list of items available in ReasonsComboList?
Thanks for your help!
Here's what I did (tested and works):
Within a DataForm this won't work (because its a DataTemplate) :
<ComboBox MinWidth="150" DisplayMemberPath="Name" Name="cbCompanies"
SelectedItem="{Binding TODOCompany,Mode=TwoWay}"
ItemsSource="{Binding ElementName=control, Path=ParentModel.Companies}" />
But you can do this instead:
<ComboBox MinWidth="150" DisplayMemberPath="Name" Name="cbCompanies"
SelectedItem="{Binding TODOCompany,Mode=TwoWay}"
Loaded="cbCompanies_Loaded"/>
Code behind:
private void cbCompanies_Loaded(object sender, RoutedEventArgs e)
{
// set combobox items source from wherever you want
(sender as ComboBox).ItemsSource = ParentModel.Companies;
}
if you set your datacontext using locator in this way
DataContext="{Binding FormName, Source={StaticResource Locator}}"
<ComboBox ItemsSource="{Binding FormName.ReasonsComboList, Source={StaticResource Locator}, Mode=OneWay}"
DisplayMemberPath="Reason" SelectedValuePath="Id"
SelectedValue="{Binding ReasonForEndingId, Mode=TwoWay}"/>
tested and working
I haven't tested this with your exact scenario, but you should be able to reference the DataContext of some parent element when binding the ItemsSource of the ComboBox. Basically using Silverlight's element-to-element binding to actually bind to some property on the parent container's DataContext instead of the current element's DataContext.
For example, if your main ViewModel was the DataContext of the LayoutRoot element you should be able to do something like this:
<ComboBox x:Name="EndReasonCombo" ItemsSource="{Binding DataContext.ReasonsComboList, ElementName=LayoutRoot}" DisplayMemberPath="Reason" SelectedValuePath="Id" SelectedValue="{Binding ReasonForEndingId, Mode=TwoWay}"/>
Creating a Silverlight DataContext Proxy to Simplify Data Binding in Nested Controls
Disclaimer: This may not actually work for the DataForm, but is suitable for the same problem when using a DataGrid. but I'm putting it here as an answer because it was an interesting read and helped me understand some things when I experienced the same problem.

Resources