This is a migrated thread and some comments may be shown as answers.

Autogenerating column data context

26 Answers 576 Views
GridView
This is a migrated thread and some comments may be shown as answers.
AdaDog
Top achievements
Rank 1
AdaDog asked on 11 Feb 2014, 09:30 PM
When NOT autogenerating columns, it makes sense why the DataContext for a CellTemplate would be the entire row/object.  In this case when AutoGenerateColumns=False, the XAML developer is required to manually set up the data bindings; the column/property names are known to the developer.  This feature allows for multiple cell values to be combined into a single grid cell for some interesting features.

However, it makes templating cells very difficult when AutoGeneratingColumns=True because often the reason why the columns are generated automatically is that the specific names of the columns are not known ahead of time; the columns may be dynamic.  And, it's extremely difficult to get the value of the grid cell when automatically generating columns.  The bindings could be reset for every cell, but I've seen where this is not recommended, and I completely agree.

One technique I've seen is to have the DataContext of an element within the DataTemplate to scan the VisualTree hierarchy for the GridViewCell:

DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type gridView:GridViewCell}}"

This works unless you attempt to use this DataContext for a property, like a ToolTip, that doesn't live in the same VisualTree.  Attempting to bind to the parent DataContext will yield a "Cannot find element that provides DataContext." binding error during runtime.

Of course, instead of a DataRow, each row could be a collection of MVVM objects instead of a DataTable, and I could put metadata in the objects to be used to style the cells.  Unfortunately, this seems extremely inefficient and clunky.

A CellTemplateSelector with a well-crafted attached property seems to be the right way to go.  The SelectTemplate item parameter has the GridViewColumn but the value within the DataTemplate is still a DataRow.

I could convert each grid cell value with an IValueConverter into an object that pairs the cell data value with some metadata that could be used for selecting a template, but the specific column is not sent into the Convert method, so I wouldn't know what to convert it to.

I've seen many versions of this problem on your forum, but there are either no answers, or their touched on so vaguely that there not useful.  Do you know of a comprehensive and elegant solution to this common problem?


26 Answers, 1 is accepted

Sort by
0
Dimitrina
Telerik team
answered on 14 Feb 2014, 04:28 PM
Hello,

Working with the visual elements and RelativeSource binding would not be a recommended approach as the Name scope is not reliable. Since the RadGridView supports UI Virtualization, its rows/cells are reused and that is why we cannot rely on the visual elements. You can check our online documentation for a further reference.

The approach we can suggest is working with the bound data items. This is how RadGridView is designed to work. 

If you have a specific problem you are working on, may I ask you to open a support thread and attach a solution demonstrating your specific implementation that cannot work with the suggested approach? That way I can check it locally and advise further.

Regards,
Didie
Telerik

Check out the new Telerik Platform - the only modular platform that combines a rich set of UI tools with powerful cloud services to develop web, hybrid and native apps. Register for the free online keynote and webinar to learn more about the Platform on Wednesday, February 12, 2014 at 11:00 a.m. ET (8:00 a.m. PT).

0
AdaDog
Top achievements
Rank 1
answered on 18 Feb 2014, 01:50 AM
Thanks Didie.  I submitted a support ticket with a sample solution.
0
Dimitrina
Telerik team
answered on 19 Feb 2014, 02:02 PM
Hello,

Generally I would recommend using a DataTable.DefaultView as an ItemsSource, because DataView is INotifyCollectionChanged and DataRowView is INotifyPropertyChanged and you will get changes automatically. 

As to the Binding expression error on the TextBlock in TextBlock.ToolTip, the DataContext of the ToolTip will be the parent GridViewCell.

I have tested it with the following template, defining the ToolTip like so: 

<DataTemplate>
    <TextBlock DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type telerik:GridViewCell}}}" Text="Test Column, Point for ToolTip">
         <TextBlock.ToolTip>
             <TextBlock Text="{Binding Value, StringFormat='The tooltip text is \{0:N3\}'}" />
         </TextBlock.ToolTip>
     </TextBlock>
 </DataTemplate>
That way the ToolTip will be shown correctly.

Does the approach using CellTemplateSelector to return a proper template with right values based on the column works fine for you instead of using RelativeSource Binding?

Regards,
Didie
Telerik
0
AdaDog
Top achievements
Rank 1
answered on 19 Feb 2014, 02:34 PM
Thanks for the code, but the code you provided is no different than what I sent to you. 

In answer to your questions "Does the approach using CellTemplateSelector to return a proper template based on the column works fine for you instead of using RelativeSource Binding?"... Even if you use a CellTemplateSelector, which I am (see sample code), the selected template still requires bindings, and the context within the DataTemplate that the CellTemplateSelector chooses is always a DataRow or a DataRowView. 

Per your statement "instead of using RelativeSource Binding," are you saying that the DataContext within the DataTemplate can be changed by the CellTemplateSelector?

Is it possible to set the GridViewDataColumn.DataMemberBinding property within the RadGridView.AutoGeneratingColumn event so that the column's DataContext is the value within the cell instead of the entire DataRowView?  Otherwise, I don't see how you access the cell's value within a CellTemplateSelector-selected DataTemplate that contains bindings without using RelativeSource.  And RelativeSource is not recommended by Telerik or others because some objects within the DataTemplate may not belong in the VisualTree, like tool tips.  And, even using RelativeSource within a tooltip results in the following error:

 "System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='Telerik.Windows.Controls.GridView.GridViewCell', AncestorLevel='1''. BindingExpression:Path=; DataItem=null; target element is 'TextBlock' (Name=''); target property is 'DataContext' (type 'Object')."
 
Also, have you looked at the code that I sent?  It gives clear insight into the challenge and is using a CellTemplateSelector, an and an attached property for setting the selector on each column along with several different ways of binding within the selected DataTemplate, all of which work and clearly show the binding errors.  Changing the ItemsSource from DataTable to DataTable.DefaultView makes no difference in the case of DataTemplate DataContexts.
0
AdaDog
Top achievements
Rank 1
answered on 19 Feb 2014, 02:56 PM
Here's the complete code that I sent.

MainWindow.xaml
​
<Window x:Class="DynamicColumns.MainWindow"
        Title="Dynamic Columns" Height="350" Width="525">
    <Grid>
        <ContentPresenter Content="{Binding}"/>
    </Grid>
</Window>


MainWindow.xaml.cs
namespace DynamicColumns
{
    using DynamicColumns.ViewModels;
 
    public partial class MainWindow
    {
        public MainWindow()
        {
            InitializeComponent();
 
            this.DataContext = new MainViewModel();
        }
    }
}


App.xaml
<Application x:Class="DynamicColumns.App"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             StartupUri="MainWindow.xaml"
             >
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="DataTemplates.xaml"/>
                <ResourceDictionary Source="Converters.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>



DataTemplates.xaml
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:ViewModels="clr-namespace:DynamicColumns.ViewModels"
                    xmlns:Views="clr-namespace:DynamicColumns.Views">
 
    <DataTemplate DataType="{x:Type ViewModels:MainViewModel}">
        <Views:MainView/>
    </DataTemplate>
     
    <DataTemplate DataType="{x:Type ViewModels:DataViewModel}">
        <Views:DataView/>
    </DataTemplate>
</ResourceDictionary>

Converters.xaml
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:Converters="clr-namespace:DynamicColumns.Converters">
     
    <Converters:DataRowToValueConverter x:Key="DataRowToValueConverter"/>
     
</ResourceDictionary>



ViewModelBase.cs
namespace DynamicColumns.ViewModels
{
    using System;
    using System.Collections.Specialized;
    using System.ComponentModel;
    using System.Linq.Expressions;
 
    public class ViewModelBase : INotifyPropertyChanged, INotifyPropertyChanging
    {
        #region Events
 
        #region INotifyPropertyChanged Members
 
        public event PropertyChangedEventHandler PropertyChanged;
 
        public event PropertyChangingEventHandler PropertyChanging;
 
        #endregion INotifyPropertyChanged Members
 
        #endregion Events
 
        #region Methods
 
        #endregion Methods
 
        #region Helper Methods
 
        protected void RaisePropertyChanged(string propertyName)
        {
            this.OnPropertyChanged(propertyName);
        }
 
        private void OnPropertyChanged<T>(Expression<Func<T>> expression)
        {
            MemberExpression memberExpression = (MemberExpression)expression.Body;
            this.OnPropertyChanged(memberExpression.Member.Name);
        }
 
        protected void RaisePropertyChanged<T>(Expression<Func<T>> expression)
        {
            this.OnPropertyChanged(expression);
        }
 
 
        protected virtual void OnPropertyChanged(string propertyName)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
 
        protected void RaisePropertyChanging(string propertyName)
        {
            this.OnPropertyChanging(propertyName);
            if (this.PropertyChanging != null)
            {
                this.PropertyChanging(this, new PropertyChangingEventArgs(propertyName));
            }
        }
 
        protected void RaisePropertyChanging<T>(Expression<Func<T>> expression)
        {
            MemberExpression memberExpression = (MemberExpression)expression.Body;
            this.OnPropertyChanging(memberExpression.Member.Name);
        }
 
        protected virtual void OnPropertyChanging(string propertyName)
        {
            if (this.PropertyChanging != null)
            {
                this.PropertyChanging(this, new PropertyChangingEventArgs(propertyName));
            }
        }
 
 
        protected void Register<V>(V viewModel, PropertyChangedEventHandler handler)
            where V : class, INotifyPropertyChanged
        {
            if (viewModel != null)
            {
                viewModel.PropertyChanged += handler;
            }
        }
 
        protected void Unregister<V>(V viewModel, PropertyChangedEventHandler handler)
            where V : class, INotifyPropertyChanged
        {
            if (viewModel != null)
            {
                viewModel.PropertyChanged -= handler;
            }
        }
 
        protected void Register<V>(NotifyCollectionChangedEventArgs e, PropertyChangedEventHandler handler)
            where V : class, INotifyPropertyChanged
        {
            if (e.NewItems != null && e.NewItems.Count > 0)
            {
                foreach (object item in e.NewItems)
                {
                    if (item is V)
                    {
                        V viewModel = (V)item;
                        this.Register(viewModel, handler);
                    }
                }
            }
 
            if (e.OldItems != null && e.OldItems.Count > 0)
            {
                foreach (object item in e.OldItems)
                {
                    if (item is V)
                    {
                        V viewModel = (V)item;
                        this.Unregister(viewModel, handler);
                    }
                }
            }
        }
 
        #endregion Helper Methods
    }
}



MainViewModel.cs
namespace DynamicColumns.ViewModels
{
    public class MainViewModel : ViewModelBase
    {
        private DataViewModel data;
 
        public MainViewModel()
        {
            this.Data = new DataViewModel();
        }
 
        public DataViewModel Data
        {
            get
            {
                return this.data;
            }
 
            set
            {
                this.data = value;
                this.RaisePropertyChanged(() => this.Data);
            }
        }
    }
}



DataViewModel.cs
namespace DynamicColumns.ViewModels
{
    using System.Data;
    using System.Windows.Input;
 
    using DynamicColumns.Data;
 
    using Microsoft.Practices.Prism.Commands;
 
    public class DataViewModel : ViewModelBase
    {
        private ICommand loadCommand;
 
        private DataTable dataTable;
 
        public DataTable DataTable
        {
            get
            {
                return this.dataTable;
            }
 
            set
            {
                this.dataTable = value;
                this.RaisePropertyChanged(() => this.DataTable);
            }
        }
 
        public ICommand LoadCommand
        {
            get
            {
                return this.loadCommand ?? (this.loadCommand = new DelegateCommand(this.Load));
            }
        }
 
        private void Load()
        {
            this.DataTable = DataCreator.Create();
        }
    }
}



DataCreator.cs
namespace DynamicColumns.Data
{
    using System;
    using System.Collections.Generic;
    using System.Data;
 
    public static class DataCreator
    {
        static Random columnNumberRandom = new Random();
 
        static Random columnPrecisionRandom = new Random();
 
        static Random rowCountRandom = new Random();
 
        static Random doubleRandom = new Random();
 
        public static List<ColumnMetadata> ColumnMetadatas
        {
            get; private set;
        }
  
        public static DataTable Create()
        {
            DataTable dataTable = new DataTable();
 
            AddColumns(dataTable);
 
            AddData(dataTable);
 
            return dataTable;
        }
 
        private static void AddColumns(DataTable dataTable)
        {
            int columnCount = columnNumberRandom.Next(10, 30);
 
            ColumnMetadatas = new List<ColumnMetadata>();
 
            for (int i = 0; i < columnCount; i++)
            {
                string columnName = string.Format("Column{0}", i);
 
                int columnPrecision = columnPrecisionRandom.Next(5);
 
                string columnAlias = string.Format("Column #{0}, Precision {1}", i, columnPrecision);
 
                ColumnMetadatas.Add(new ColumnMetadata() { Alias = columnAlias, Name = columnName, Precision = columnPrecision});
 
                DataColumn dataColumn = new DataColumn(columnName, typeof(double));
 
                dataTable.Columns.Add(dataColumn);
            }
        }
 
        private static void AddData(DataTable dataTable)
        {
            int rowCount = rowCountRandom.Next(200);
 
            for (int i = 0; i < rowCount; i++)
            {
                List<object> list = new List<object>();
 
                for (int j = 0; j < dataTable.Columns.Count; j++)
                {
                    double value = doubleRandom.NextDouble() * 100;
                    list.Add(value);
                }
 
                dataTable.Rows.Add(list.ToArray());
            }
        }
    }
}


ColumnMetadata.cs
namespace DynamicColumns.Data
{
    public class ColumnMetadata
    {
        public string Name
        {
            get; set;
        }
 
        public string Alias
        {
            get; set;
        }
 
        public int Precision
        {
            get; set;
        }
    }
}


MainView.xaml
<UserControl x:Class="DynamicColumns.Views.MainView"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             mc:Ignorable="d"
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid Background="Red">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
             
            <ContentPresenter Grid.Row="1" Content="{Binding Data}"/>
             
        </Grid>
    </Grid>
</UserControl>


DataView.xaml
<UserControl x:Class="DynamicColumns.Views.DataView"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"
             xmlns:Attached="clr-namespace:DynamicColumns.Attached" mc:Ignorable="d"
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid>
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="*"/>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
         
            <telerik:RadGridView Grid.Row="0" ItemsSource="{Binding DataTable.DefaultView}">
                <Attached:RadGridViewDataHelper.Helper>
                    <Attached:RadGridViewDataHelper>
                        <Attached:RadGridViewDataHelper.TemplateSelector>
                            <Attached:TemplateSelector>
                                <Attached:TemplateSelector.ColumnDataTemplates>
                                    <Attached:ColumnDataTemplate ColumnPrecision="0">
                                        <Attached:ColumnDataTemplate.DataTemplate>
                                            <DataTemplate>
                                                <TextBlock DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type telerik:GridViewCell}}}" Text="{Binding Value, StringFormat={}{0:N0}}"/>
                                            </DataTemplate>
                                        </Attached:ColumnDataTemplate.DataTemplate>
                                    </Attached:ColumnDataTemplate>
                                    <Attached:ColumnDataTemplate ColumnPrecision="1">
                                        <Attached:ColumnDataTemplate.DataTemplate>
                                            <DataTemplate>
                                                <TextBlock DataContext="{Binding Path=Value, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type telerik:GridViewCell}}}" Text="{Binding StringFormat={}{0:N1}}"/>
                                            </DataTemplate>
                                        </Attached:ColumnDataTemplate.DataTemplate>
                                    </Attached:ColumnDataTemplate>
                                    <Attached:ColumnDataTemplate ColumnPrecision="2">
                                        <Attached:ColumnDataTemplate.DataTemplate>
                                            <DataTemplate>
                                                <TextBlock Text="{Binding Converter={StaticResource DataRowToValueConverter}, StringFormat={}{0:N2}}"/>
                                            </DataTemplate>
                                        </Attached:ColumnDataTemplate.DataTemplate>
                                    </Attached:ColumnDataTemplate>
                                    <Attached:ColumnDataTemplate ColumnPrecision="3">
                                        <Attached:ColumnDataTemplate.DataTemplate>
                                            <DataTemplate>
                                                <!--Causes binding warning => System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='Telerik.Windows.Controls.GridView.GridViewCell', AncestorLevel='1''. BindingExpression:Path=Value; DataItem=null; target element is 'TextBlock' (Name=''); target property is 'DataContext' (type 'Object')-->
                                                <TextBlock DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type telerik:GridViewCell}}}" Text="{Binding Value, StringFormat={}{0:N3}}">
                                                    <TextBlock.ToolTip>
                                                        <TextBlock Text="{Binding Value, StringFormat='The tooltip text is \{0:N3\}'}" />
                                                    </TextBlock.ToolTip>
                                                </TextBlock>
                                            </DataTemplate>
                                        </Attached:ColumnDataTemplate.DataTemplate>
                                    </Attached:ColumnDataTemplate>
                                    <Attached:ColumnDataTemplate ColumnPrecision="4">
                                        <Attached:ColumnDataTemplate.DataTemplate>
                                            <DataTemplate>
                                                <!--Causes binding warning => System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='Telerik.Windows.Controls.GridView.GridViewCell', AncestorLevel='1''. BindingExpression:Path=Value; DataItem=null; target element is 'TextBlock' (Name=''); target property is 'DataContext' (type 'Object')-->
                                                <TextBlock DataContext="{Binding Path=Value, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type telerik:GridViewCell}}}" Text="{Binding StringFormat={}{0:N4}}">
                                                    <TextBlock.ToolTip>
                                                        <TextBlock Text="{Binding StringFormat='The tooltip text is \{0:N4\}'}" />
                                                    </TextBlock.ToolTip>
                                                </TextBlock>
                                            </DataTemplate>
                                        </Attached:ColumnDataTemplate.DataTemplate>
                                    </Attached:ColumnDataTemplate>
                                    <Attached:ColumnDataTemplate ColumnPrecision="5">
                                        <Attached:ColumnDataTemplate.DataTemplate>
                                            <DataTemplate>
                                                <TextBlock Text="{Binding StringFormat={}{0:N5}}"/>
                                            </DataTemplate>
                                        </Attached:ColumnDataTemplate.DataTemplate>
                                    </Attached:ColumnDataTemplate>
                                </Attached:TemplateSelector.ColumnDataTemplates>
                            </Attached:TemplateSelector>
                        </Attached:RadGridViewDataHelper.TemplateSelector>
                    </Attached:RadGridViewDataHelper>
                </Attached:RadGridViewDataHelper.Helper>
            </telerik:RadGridView>
         
            <Button Grid.Row="1" Content="Load" Command="{Binding LoadCommand}"/>
        </Grid>
    </Grid>
</UserControl>



DataRowToValueConverter.cs
namespace DynamicColumns.Converters
{
    using System;
    using System.Globalization;
    using System.Windows.Data;
 
    public class DataRowToValueConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return value;
        }
 
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}



ColumnDataTemplate.cs
namespace DynamicColumns.Attached
{
    using System.Windows;
 
    public class ColumnDataTemplate
    {
        public DataTemplate DataTemplate
        {
            get;
            set;
        }
 
        public int ColumnPrecision
        {
            get;
            set;
        }
    }
}


RadGridViewHelper.cs
namespace DynamicColumns.Attached
{
using System.Linq;
    using System.Windows;
    using DynamicColumns.Data;
    using Telerik.Windows.Controls;
 
    public class RadGridViewDataHelper : DependencyObject
    {
        #region Fields
 
        private RadGridView radGridView;
 
        #endregion Fields
 
        #region Dependency Properties
 
 
        public static readonly DependencyProperty TemplateSelectorProperty = DependencyProperty.Register("TemplateSelector", typeof(TemplateSelector), typeof(RadGridViewDataHelper), new PropertyMetadata(TemplateSelectorChanged));
 
        private static void TemplateSelectorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
        }
 
        public static readonly DependencyProperty HelperProperty = DependencyProperty.RegisterAttached("Helper", typeof(RadGridViewDataHelper), typeof(RadGridView), new PropertyMetadata(HelperChanged));
 
        #endregion Dependency Properties
 
        #region Dependency Property Methods
 
        public static void SetHelper(RadGridView radGridView, RadGridViewDataHelper value)
        {
            radGridView.SetValue(HelperProperty, value);
        }
 
        public static RadGridViewDataHelper GetHelper(RadGridView itemsControl)
        {
            return (RadGridViewDataHelper)itemsControl.GetValue(HelperProperty);
        }
 
        public TemplateSelector TemplateSelector
        {
            get
            {
                return this.GetValue(TemplateSelectorProperty) as TemplateSelector;
            }
 
            set
            {
                this.SetValue(TemplateSelectorProperty, value);
            }
        }
 
        #endregion Dependency Property Methods
 
        #region Dependency Property Handler
 
        private static void HelperChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (d is RadGridView && e.NewValue is RadGridViewDataHelper)
            {
                RadGridViewDataHelper radGridViewHelper = ((RadGridViewDataHelper)e.NewValue);
 
                radGridViewHelper.Initialize((RadGridView)d);
            }
        }
 
        #endregion Dependency Property Handler
 
        private void RadGridView_AutoGeneratingColumn(object sender, GridViewAutoGeneratingColumnEventArgs e)
        {
            RadGridView gridView = sender as RadGridView;
 
            if (gridView != null)
            {
                    this.InitializeColumnHeader(e.Column);
 
                    this.InitializeCellTemplate(e.Column);
            }
        }
 
        private void InitializeColumnHeader(GridViewColumn gridViewColumn)
        {
            ColumnMetadata columnMetadata = DataCreator.ColumnMetadatas.FirstOrDefault(c => c.Name == gridViewColumn.UniqueName);
 
            if (columnMetadata != null)
            {
                gridViewColumn.Header = columnMetadata.Alias;
            }
        }
 
        private void InitializeCellTemplate(GridViewColumn gridViewColumn)
        {
            gridViewColumn.CellTemplateSelector = this.TemplateSelector;
 
            //GridViewDataColumn gridViewDataColumn = (GridViewDataColumn)gridViewColumn;
            //Binding binding = new Binding(gridViewDataColumn.UniqueName);
            //binding.Mode = BindingMode.TwoWay;
 
            //gridViewDataColumn.DataMemberBinding = binding;
        }
 
        private void Initialize(RadGridView radGridView)
        {
            this.radGridView = radGridView;
 
            this.radGridView.AutoGeneratingColumn += RadGridView_AutoGeneratingColumn;
        }
    }
}



TemplateSelector.cs
namespace DynamicColumns.Attached
{
    using System.Collections.Generic;
    using System.Linq;
    using System.Windows;
    using System.Windows.Controls;
    using DynamicColumns.Data;
    using Telerik.Windows.Controls.GridView;
 
    public class TemplateSelector : DataTemplateSelector
    {
        private List<ColumnDataTemplate> columnDataTemplates = new List<ColumnDataTemplate>();
 
        public List<ColumnDataTemplate> ColumnDataTemplates
        {
            get
            {
                return this.columnDataTemplates;
            }
        }
 
        public override DataTemplate SelectTemplate(object item, DependencyObject container)
        {
            DataTemplate dataTemplate = base.SelectTemplate(item, container);
 
            GridViewCell gridViewCell = container as GridViewCell;
 
            if (gridViewCell != null && gridViewCell.Column != null)
            {
                ColumnMetadata columnMetadata = DataCreator.ColumnMetadatas.FirstOrDefault(c => c.Name == gridViewCell.Column.UniqueName);
 
                if (columnMetadata != null)
                {
                    ColumnDataTemplate columnDataTemplate = this.ColumnDataTemplates.FirstOrDefault(c => c.ColumnPrecision == columnMetadata.Precision);
 
                    if (columnDataTemplate != null)
                    {
                        dataTemplate = columnDataTemplate.DataTemplate;
                    }
                }
            }
 
            return dataTemplate;
        }
    }
}

0
Dimitrina
Telerik team
answered on 19 Feb 2014, 03:45 PM
Hello,

The alternative option I can suggest would be setting the DataContext of the TextBlock (defined in XAML) in code. That way you will avoid the RelativeSource Binding.
For example:

<DataTemplate>
    <TextBlock Loaded="TextBlock_Loaded_1" Text="DataContext set in code">
        <TextBlock.ToolTip>
            <TextBlock Text="{Binding Value, StringFormat='The tooltip text is \{0:N3\}'}" />
        </TextBlock.ToolTip>
    </TextBlock>
</DataTemplate>
private void TextBlock_Loaded_1(object sender, RoutedEventArgs e)
{
    var textBlock = (sender as TextBlock);
    textBlock.DataContext = textBlock.Parent as GridViewCell;
}

How does this approach works for you?

Regards,
Didie
Telerik
0
AdaDog
Top achievements
Rank 1
answered on 19 Feb 2014, 04:16 PM
It's okay.  I'd rather not assume the content of a DataTemplate will be a TextBlock, and I'm not a fan of code-behind.  Also, setting the DataContext for every TextBlock seems very expensive.

I can solve the TextBlock issue by using the FrameworkElement lowest common denominator as in the following.

​
private void FrameworkElement_OnLoaded(object sender, RoutedEventArgs e)
{
    FrameworkElement frameworkElement = (FrameworkElement)sender;
 
    GridViewCell gridViewCell = frameworkElement.Parent as GridViewCell;
    frameworkElement.DataContext = gridViewCell;
}


I would prefer do do the work in the TemplateSelector as in
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
    DataTemplate dataTemplate = base.SelectTemplate(item, container);
 
    GridViewCell gridViewCell = container as GridViewCell;
 
    if (gridViewCell != null && gridViewCell.Column != null)
    {
        ColumnMetadata columnMetadata = DataCreator.ColumnMetadatas.FirstOrDefault(c => c.Name == gridViewCell.Column.UniqueName);
 
        if (columnMetadata != null)
        {
            ColumnDataTemplate columnDataTemplate = this.ColumnDataTemplates.FirstOrDefault(c => c.ColumnPrecision == columnMetadata.Precision);
 
            if (columnDataTemplate != null)
            {
                dataTemplate = columnDataTemplate.DataTemplate;
                DependencyObject dependencyObject = dataTemplate.LoadContent();
 
                FrameworkElement frameworkElement = (FrameworkElement)dependencyObject;
                if (frameworkElement != null && frameworkElement.Parent is GridViewCell)
                {
                    frameworkElement.DataContext = frameworkElement.Parent;
                }
                else
                {
                    frameworkElement.Loaded += frameworkElement_Loaded;
                }
            }
        }
    }
 
    return dataTemplate;
}
 
private void frameworkElement_Loaded(object sender, RoutedEventArgs e)
{
    FrameworkElement frameworkElement = (FrameworkElement)sender;
 
    GridViewCell gridViewCell = frameworkElement.Parent as GridViewCell;
    frameworkElement.DataContext = gridViewCell;
}


but at this point the Parent property is null.

It's not the most elegant solution, but, if it's not too expensive, then I guess I can live with it.

Thanks, Didie.
0
AdaDog
Top achievements
Rank 1
answered on 19 Feb 2014, 04:32 PM
Even with the code-behind setting the DataContext of the FrameworkElement within the DataTemplate


<DataTemplate>
    <TextBlock Loaded="FrameworkElement_OnLoaded" Text="{Binding Value, StringFormat={}{0:N2}}">
        <TextBlock.ToolTip>
            <TextBlock Text="{Binding Value, StringFormat='The tooltip text is \{0:N2\}'}" />
        </TextBlock.ToolTip>
    </TextBlock>
</DataTemplate>


still causes the following binding error:

System.Windows.Data Error: 40 : BindingExpression path error: 'Value' property not found on 'object' ''DataRowView' (HashCode=51955039)'. BindingExpression:Path=Value; DataItem='DataRowView' (HashCode=51955039); target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')

... but the tooltip shows correctly.  How do I get rid of the binding error?





0
AdaDog
Top achievements
Rank 1
answered on 19 Feb 2014, 04:36 PM
Even this (without the tooltip) causes binding errors.

<DataTemplate>
    <TextBlock Loaded="FrameworkElement_OnLoaded" Text="{Binding Value, StringFormat={}{0:N2}}">
    </TextBlock>
</DataTemplate>

0
Dimitrina
Telerik team
answered on 19 Feb 2014, 04:37 PM
Hello,

Please make sure you have replaced all the previous code you had with the suggested one. It seems that you still have some templates where the Binding is set otherwise.


Regards,
Didie
Telerik
0
AdaDog
Top achievements
Rank 1
answered on 19 Feb 2014, 04:44 PM
That's what I thought.  Here's the entire new DataView.xaml.

<UserControl x:Class="DynamicColumns.Views.DataView"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"
             xmlns:Attached="clr-namespace:DynamicColumns.Attached" mc:Ignorable="d"
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid>
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="*"/>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
         
            <telerik:RadGridView Grid.Row="0" ItemsSource="{Binding DataTable.DefaultView}">
                <Attached:RadGridViewDataHelper.Helper>
                    <Attached:RadGridViewDataHelper>
                        <Attached:RadGridViewDataHelper.TemplateSelector>
                            <Attached:TemplateSelector>
                                <Attached:TemplateSelector.ColumnDataTemplates>
                                    <Attached:ColumnDataTemplate ColumnPrecision="2">
                                        <Attached:ColumnDataTemplate.DataTemplate>
                                            <DataTemplate>
                                                <TextBlock Loaded="FrameworkElement_OnLoaded" Text="{Binding Value, StringFormat={}{0:N2}}">
                                                </TextBlock>
                                            </DataTemplate>
                                        </Attached:ColumnDataTemplate.DataTemplate>
                                    </Attached:ColumnDataTemplate>
                                </Attached:TemplateSelector.ColumnDataTemplates>
                            </Attached:TemplateSelector>
                        </Attached:RadGridViewDataHelper.TemplateSelector>
                    </Attached:RadGridViewDataHelper>
                </Attached:RadGridViewDataHelper.Helper>
            </telerik:RadGridView>
         
            <Button Grid.Row="1" Content="Load" Command="{Binding LoadCommand}"/>
        </Grid>
    </Grid>
</UserControl>

The binding error still occurs.  Can you see it in your Debug window as well when you run it?
0
AdaDog
Top achievements
Rank 1
answered on 19 Feb 2014, 05:08 PM
I added a converter to see what was going on

DataView.xaml
<UserControl x:Class="DynamicColumns.Views.DataView"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"
             xmlns:Attached="clr-namespace:DynamicColumns.Attached" mc:Ignorable="d"
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid>
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="*"/>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
         
            <telerik:RadGridView Grid.Row="0" ItemsSource="{Binding DataTable.DefaultView}">
                <Attached:RadGridViewDataHelper.Helper>
                    <Attached:RadGridViewDataHelper>
                        <Attached:RadGridViewDataHelper.TemplateSelector>
                            <Attached:TemplateSelector>
                                <Attached:TemplateSelector.ColumnDataTemplates>
                                    <Attached:ColumnDataTemplate ColumnPrecision="2">
                                        <Attached:ColumnDataTemplate.DataTemplate>
                                            <DataTemplate>
                                                <TextBlock Loaded="FrameworkElement_OnLoaded" Text="{Binding Converter={StaticResource DataRowToValueConverter}, StringFormat={}{0:N2}}">
                                                </TextBlock>
                                            </DataTemplate>
                                        </Attached:ColumnDataTemplate.DataTemplate>
                                    </Attached:ColumnDataTemplate>
                                </Attached:TemplateSelector.ColumnDataTemplates>
                            </Attached:TemplateSelector>
                        </Attached:RadGridViewDataHelper.TemplateSelector>
                    </Attached:RadGridViewDataHelper>
                </Attached:RadGridViewDataHelper.Helper>
            </telerik:RadGridView>
         
            <Button Grid.Row="1" Content="Load" Command="{Binding LoadCommand}"/>
        </Grid>
    </Grid>
</UserControl>


DataRowToValueConverter.cs
namespace DynamicColumns.Converters
{
    using System;
    using System.Diagnostics;
    using System.Globalization;
    using System.Windows.Data;
 
    using Telerik.Windows.Controls.GridView;
 
    public class DataRowToValueConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value is GridViewCell)
            {
                GridViewCell gridViewCell = (GridViewCell)value;
                return gridViewCell.Value;
            }
            else if (value == null)
            {
                Debug.WriteLine("Value is null");
            }
            else
            {
                Debug.WriteLine(string.Format("Not a GridViewCell.  It's a {0}", value.GetType().Name));
            }
 
            return value;
        }
 
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}


I get the following debug output:
Not a GridViewCell. It's a DataRowView
Not a GridViewCell. It's a DataRowView
Not a GridViewCell. It's a DataRowView
Not a GridViewCell. It's a DataRowView
Not a GridViewCell. It's a DataRowView
Not a GridViewCell. It's a DataRowView
Not a GridViewCell. It's a DataRowView
Not a GridViewCell. It's a DataRowView
Not a GridViewCell. It's a DataRowView
Not a GridViewCell. It's a DataRowView
Not a GridViewCell. It's a DataRowView
Not a GridViewCell. It's a DataRowView
Not a GridViewCell. It's a DataRowView
Not a GridViewCell. It's a DataRowView
Not a GridViewCell. It's a DataRowView
Not a GridViewCell. It's a DataRowView
Not a GridViewCell. It's a DataRowView
Not a GridViewCell. It's a DataRowView
Not a GridViewCell. It's a DataRowView
Not a GridViewCell. It's a DataRowView
Value is null
Value is null
Value is null

... in the attached image of the application.

"Not a GridViewCell. It's a DataRowView" seems to occur for each visible cell, but I don't understand why "Value is null" would appear 3 times.
0
AdaDog
Top achievements
Rank 1
answered on 19 Feb 2014, 05:11 PM
I added a converter to see what was going on

View
<UserControl x:Class="DynamicColumns.Views.DataView"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"
             xmlns:Attached="clr-namespace:DynamicColumns.Attached" mc:Ignorable="d"
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid>
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="*"/>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
         
            <telerik:RadGridView Grid.Row="0" ItemsSource="{Binding DataTable.DefaultView}">
                <Attached:RadGridViewDataHelper.Helper>
                    <Attached:RadGridViewDataHelper>
                        <Attached:RadGridViewDataHelper.TemplateSelector>
                            <Attached:TemplateSelector>
                                <Attached:TemplateSelector.ColumnDataTemplates>
                                    <Attached:ColumnDataTemplate ColumnPrecision="2">
                                        <Attached:ColumnDataTemplate.DataTemplate>
                                            <DataTemplate>
                                                <TextBlock Loaded="FrameworkElement_OnLoaded" Text="{Binding Converter={StaticResource DataRowToValueConverter}, StringFormat={}{0:N2}}">
                                                </TextBlock>
                                            </DataTemplate>
                                        </Attached:ColumnDataTemplate.DataTemplate>
                                    </Attached:ColumnDataTemplate>
                                </Attached:TemplateSelector.ColumnDataTemplates>
                            </Attached:TemplateSelector>
                        </Attached:RadGridViewDataHelper.TemplateSelector>
                    </Attached:RadGridViewDataHelper>
                </Attached:RadGridViewDataHelper.Helper>
            </telerik:RadGridView>
         
            <Button Grid.Row="1" Content="Load" Command="{Binding LoadCommand}"/>
        </Grid>
    </Grid>
</UserControl>


Converter
namespace DynamicColumns.Converters
{
    using System;
    using System.Diagnostics;
    using System.Globalization;
    using System.Windows.Data;
 
    using Telerik.Windows.Controls.GridView;
 
    public class DataRowToValueConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value is GridViewCell)
            {
                GridViewCell gridViewCell = (GridViewCell)value;
                return gridViewCell.Value;
            }
            else if (value == null)
            {
                Debug.WriteLine("Value is null");
            }
            else
            {
                Debug.WriteLine(string.Format("Not a GridViewCell.  It's a {0}", value.GetType().Name));
            }
 
            return value;
        }
 
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}


I got the following debug output
Not a GridViewCell. It's a DataRowView
Not a GridViewCell. It's a DataRowView
Not a GridViewCell. It's a DataRowView
Not a GridViewCell. It's a DataRowView
Not a GridViewCell. It's a DataRowView
Not a GridViewCell. It's a DataRowView
Not a GridViewCell. It's a DataRowView
Not a GridViewCell. It's a DataRowView
Not a GridViewCell. It's a DataRowView
Not a GridViewCell. It's a DataRowView
Not a GridViewCell. It's a DataRowView
Not a GridViewCell. It's a DataRowView
Not a GridViewCell. It's a DataRowView
Not a GridViewCell. It's a DataRowView
Not a GridViewCell. It's a DataRowView
Not a GridViewCell. It's a DataRowView
Not a GridViewCell. It's a DataRowView
Not a GridViewCell. It's a DataRowView
Not a GridViewCell. It's a DataRowView
Not a GridViewCell. It's a DataRowView
Value is null
Value is null
Value is null

...from the attached picture of the application.

"Not a GridViewCell. It's a DataRowView" seems to occur for each cell.  I'm not sure what is going on with "Value is null."
0
Dimitrina
Telerik team
answered on 19 Feb 2014, 05:20 PM
Hello,

I am not able to get the exception in the Output Window. Please refer to the attached solution after my modifications.

Regards,
Didie
Telerik
0
AdaDog
Top achievements
Rank 1
answered on 19 Feb 2014, 05:26 PM
You'll get it if you set the TextBlock.Text property to "{Binding Value}" instead of "DataContext set in code."
0
Dimitrina
Telerik team
answered on 19 Feb 2014, 05:32 PM
Hi,

It is because the Binding tries to evaluate before the new DataContext has been set on Loaded of the TextBlock. You will have to also assign the Text property in code.
For example:
private void TextBlock_Loaded_1(object sender, RoutedEventArgs e)
{
    var textBlock = (sender as TextBlock);
    var cell = textBlock.Parent as GridViewCell;
    textBlock.DataContext = textBlock.Parent as GridViewCell;
    textBlock.Text = cell.Value.ToString();
}

Please make sure you remove setting the Text in XAML.

Regards,
Didie
Telerik
0
AdaDog
Top achievements
Rank 1
answered on 19 Feb 2014, 10:28 PM
This is not a very elegant solution.  And, what's the point of using a DataTemplate.

The real production application has DataTemplate requirements much more complex than simply setting a Text property in a TextBlock, so this is an unworkable solution.

The bindings work, albeit with errors.  Do you know what is passing DataRowView objects into the converter?  Do you know what is passing null values into the Converter?  Is there a way I can intercept these seemingly redundant calls to the converter?
0
Dimitrina
Telerik team
answered on 20 Feb 2014, 07:56 AM
Hello,

Indeed, I understand that this solution is not very good. I have consulted the case with the development team and I am afraid we cannot suggest you a better solution based on your requirements. The best option would be to work with the bound data items and their properties. This is by design and this is what we recommend.

Regards,
Didie
Telerik
0
Dimitrina
Telerik team
answered on 20 Feb 2014, 08:35 AM
Hello,

I would like to remind you about the other option I have suggested you in the support thread you opened on the same issue. You could build the DataTemplate you return through the CellTemplateSelector in code based on the bound item and the column the parent cell is under, instead of just returning the one you have defined in XAML (having the RelativeSource Binding).
This is where this is currently done in your code:
GridViewCell gridViewCell = container as GridViewCell;
if (gridViewCell != null && gridViewCell.Column != null)
{
    ColumnMetadata columnMetadata = DataCreator.ColumnMetadatas.FirstOrDefault(c => c.Name == gridViewCell.Column.UniqueName);
    if (columnMetadata != null)
    {
        ColumnDataTemplate columnDataTemplate = this.ColumnDataTemplates.FirstOrDefault(c => c.ColumnPrecision == columnMetadata.Precision);
 
        if (columnDataTemplate != null)
        {
                 // Build the template now relying on the item
            dataTemplate = columnDataTemplate.DataTemplate;
        }
    }
}

Copy Code

Regards,
Didie
Telerik
0
AdaDog
Top achievements
Rank 1
answered on 20 Feb 2014, 02:11 PM
You posted code that I already posted from my sample solution.  See above for all of the code for the solution.

The DataTemplate is already built.  It doesn't need to be done in code.  It can be done in XAML.  The DataContext within the XAML is the same.  In this case, with an attached property.  The CellTemplateSelector must be set on each column, and since the columns are autogenerated, the selector for each column is set during the autogenerating event.  In my case, I chose to use an attached property rather than code-behind, but the effects are the same.

I was already using a CellTemplateSelector in my sample solution.  You can set the DataContext for the loaded FrameworkElement in the Loaded event in code-behind to remove the need for a RelativeSource binding.  The cell template selection is not the issue.  The fact that the binding works is not the issue.  The issue is the multiple bindings performed by the RadGridView with different DataContexts having unknown sources, the null values, and the binding errors.

Do you know what is passing DataRowView objects into the converter?  What is sending the DataRowView DataContext into the DataTemplate?  Why is the DataTemplate used once for each GridCell in the column AND each row in view.  Do you know what is passing null values into the Converter?  Is there a way I can intercept these seemingly redundant calls on the DataTemplate?
0
AdaDog
Top achievements
Rank 1
answered on 20 Feb 2014, 02:19 PM
I should also say that I AM working with bound data items.  The solution has been using bound data items from the beginning: DataTable and DataTable.DefaultView.  Are you suggesting to use POC's instead of a DataView?  If so, how would you handle an unknown collection shapes?  Reflection?

If I knew what was sending a DataRowView and null values into the DataTemplates, then I could probably figure out a workaround.
0
Dimitrina
Telerik team
answered on 21 Feb 2014, 10:13 AM
Hi,

This is exactly my point. You should work with the bound business objects - DataRowView - and its properties and not assigning the DataContext of the TextBlock to be the parent GridViewCell through RelativeSource Binding.

I have posted the code in your TemplateSelector as it is, so that you know where you need to change the current implementation. Instead of returning the DataTemplate defined in XAML, relying on RelativeSource Binding, you should build it in code, setting the Text property of the TextBlock inside to a Property of the DataRowView coming as input parameter to the TemplateSelector.

I am afraid we cannot suggest another solution than those additional code in code behind.

Regards,
Didie
Telerik
0
AdaDog
Top achievements
Rank 1
answered on 21 Feb 2014, 10:35 AM
Again, I am not using a RelativeSource binding.  I am setting the DataContext of the FrameworkElement/TextBlock in code-behind.  This doesn't change the fact that something of type DataRowView, GridViewCell, and null are attempting to use the DataTemplate.

The DataTemplate is already built in XAML.  Why build it in code?  Even if it is built in code, it will still have the same DataContext as the one in XAML, and the DataContext of the DataTemplate is the issue.  A TemplateSelector selects a type of DataTemplate, not of TextBlock.

I'm confused from your last post.  Are you saying that DataRowView is a bound business object?

Maybe I'll set a target type on the DataTemplate to see if the Telerik code still attempts to use it.  If it breaks, then maybe I can figure out where Telerik is attempting to apply the template for GridViewCell, DataRowView, and null.

I'm sorry you couldn't improve on my code, but I understand.  Setting the DataContext of an element inside of a DataTemplate misses the point of DataTemplates.  Creating UI objects in code-behind misses the point of a whole bunch of concepts.
0
Dimitrina
Telerik team
answered on 21 Feb 2014, 12:27 PM
Hi,

If you have set the ItemsSource of RadGridView to be DataTable.DefaultView, then the DataContext of each row and its cells will be DataRowView.

Regards,
Didie
Telerik
0
AdaDog
Top achievements
Rank 1
answered on 21 Feb 2014, 12:48 PM
It makes sense why each row and cell is attempting to use the DataTemplate.  It's too bad that the property name for a column is CellTemplateSelector and not RowAndCellAndUnknownSourceOfNullValueTemplateSelector.
0
Dimitrina
Telerik team
answered on 21 Feb 2014, 04:03 PM
Hi,

I apologize for the inconvenience.

Regards,
Didie
Telerik
Tags
GridView
Asked by
AdaDog
Top achievements
Rank 1
Answers by
Dimitrina
Telerik team
AdaDog
Top achievements
Rank 1
Share this question
or