I'm having some trouble using bindings in my column headers. After reading this post: http://www.telerik.com/community/forums/wpf/gridview/binding-the-header-property-of-a-gridviewcolumn.aspx it seems like you have to have the property you are binding to available when the column is initialised, otherwise it will not get updated.
I am wanting to bind to a property of the data context of a parent of the control that hosts the RadGridView. The DataContext hasn't been set by the time the column is initialized. Is there a way around this? I had a few ideas:
- Is there some event I can handle which is called before the columns are initialized, but after we have access to the UserControl's parent and hence its DataContext.
- Is there a way to reset the columns and hence the bindings?
I'm trying an MVVM approach, but I can't seem to fit in the ModelView approach with the column headers as I can't bind. The only way is to have the DataContext set in the control constructor, but that's fairly limited and I would rather avoid it.
Any suggestions / tips would be much appreciated!
16 Answers, 1 is accepted
<
Page
DataContext
=
"{StaticResource modelView}"
>
<
ControlWithGrid
DataContext
=
"{Binding SubModelView}"
/>
</
Page
>
I define a RadGridView in the ControlWithGrid and attempt to bind the column header to a property of SubModelView:
<
UserControl
>
<
telerik:RadGridView
ItemsSource
=
"{Binding Players}"
AutoGenerateColumns
=
"False"
>
<
telerik:RadGridView.Columns
>
<
telerik:GridViewColumn
Header
=
"{Binding Path=HeaderName}"
>
<
telerik:GridViewColumn.CellTemplate
>
<
DataTemplate
>
<
TextBlock
Text
=
"{Binding Name}"
/>
</
DataTemplate
>
</
telerik:GridViewColumn.CellTemplate
>
</
telerik:GridViewColumn
>
</
telerik:RadGridView.Columns
>
</
telerik:RadGridView
>
</
UserControl
>
However, the header binding is not set because the DataContext is set too late.
I've come up with a workaround where I've created sub-classes of GridViewColumn and added them to RadGridView.Columns after DataContext is set. This works fine, though it would be nice to have a better solution if possible.
Basically, the DataContext of the column's header is different that of the column itself. So, you may need to define your ViewModel as a Resource and then set the Source property in the Binding of the Header.
For example:
<
Window.Resources
>
<
local:Headers
x:Key
=
"MyViewModel"
/>
</
Window.Resources
>
<
telerik:GridViewDataColumn.Header
>
<
TextBlock
Text
=
"{Binding HeaderName, Source={StaticResource MyViewModel}}"
/>
</
telerik:GridViewDataColumn.Header
>
HeaderName is a property defined in your ViewModel for example.
Let me know if it meets your requirements.
Kind regards,
Maya
the Telerik team
I am sending you a sample project illustrating the solution proposed above. Please take a look at it and let me know in case of any misunderstandings according to your requirements. Feel free to change the application in the way you want and send it back if needed.
Maya
the Telerik team
I don't understand why the data context of the Header property is not inherited from it's parent object on the visual tree.
I think this must be a bug.
It is not a big deal to explicitly set the data context of the Header property in code if you only have one grid in your application. But if the grid is part of a user control that appears on, say, multiple tab items, then it is more of a big deal.
The header of the column is of type object and you can place whichever control you want inside. It was out intention to leave it that way and not have it bound to the data context of the grid.
Maya
the Telerik team
Explore the entire Telerik portfolio by downloading Telerik DevCraft Ultimate.
Perhaps there is a good reason for this. But I think it is standard for objects in the visual tree to inherit their parent's data context.
In my application, the RadGridView with bound column headers is part of a user control that appears on multiple RadTabItems. Each RadTabItem has it's own ViewModel object. It would be nice if the header objects could inherit their data context from the RadTabItem.
It is not a big deal for me to work around this.
p.s. Being able to bind column headers is a nice feature.
It was our intention to leave it that way so the user has the freedom to bind it to whichever context he requires. Similar is the case with the DataGrid.
Maya
the Telerik team
Explore the entire Telerik portfolio by downloading Telerik DevCraft Ultimate.
Here are 2 generic classes (DataContextProxy and DataContextProxyBehavior) that help to pass data context from one control to resource so that other visual/or not visual can bind to.
DataContextProxy: Just a simple VM that hold DataContext object.
DataContextProxyBehavior: Simple behavior that can be attached to any FrameworkElement. Once attached, it tracks that FrameworkElement's DataContext. Any change of DataContext, it will pass the change to DataContextProxy. The goal is to have DataContextProxy be in resource that other visual/non visual can bind to.
Usage: See an example of declaration and usage at the bottom.
Hope it helps someone :)
01.
public
class
DataContextProxy : SomeImplementationOfINotifyPropertyChanged
02.
{
03.
#region private data
04.
private
object
_dataContext;
05.
#endregion
06.
07.
#region public properties
08.
public
object
DataContext
09.
{
10.
get
{
return
_dataContext; }
11.
set
{ _dataContext = value; FirePropertyChanged(() => DataContext); }
12.
}
13.
#endregion
14.
15.
#region constructors
16.
public
DataContextProxy()
17.
{
18.
_dataContext =
null
;
19.
}
20.
#endregion
21.
}
01.
public
class
DataContextProxyBehavior : Behavior<FrameworkElement>
02.
{
03.
#region DataContextProxy Dependency
04.
public
DataContextProxy DataContextProxy
05.
{
06.
get
{
return
(DataContextProxy)GetValue(DataContextProxyProperty); }
07.
set
{ SetValue(DataContextProxyProperty, value); }
08.
}
09.
10.
public
static
readonly
DependencyProperty DataContextProxyProperty =
11.
DependencyProperty.Register(
"DataContextProxy"
,
typeof
(DataContextProxy),
typeof
(DataContextProxyBehavior));
12.
#endregion
13.
14.
#region overrides
15.
protected
override
void
OnAttached()
16.
{
17.
base
.OnAttached();
18.
19.
UpdateDataContext();
20.
AssociatedObject.DataContextChanged += AssociatedObjectOnDataContextChanged;
21.
}
22.
23.
protected
override
void
OnDetaching()
24.
{
25.
base
.OnDetaching();
26.
AssociatedObject.DataContextChanged -= AssociatedObjectOnDataContextChanged;
27.
}
28.
#endregion
29.
30.
#region private functions
31.
private
void
AssociatedObjectOnDataContextChanged(
object
sender, DependencyPropertyChangedEventArgs args)
32.
{
33.
UpdateDataContext();
34.
}
35.
36.
private
void
UpdateDataContext()
37.
{
38.
if
(AssociatedObject !=
null
&& DataContextProxy !=
null
)
39.
{
40.
DataContextProxy.DataContext = AssociatedObject.DataContext;
41.
}
42.
}
43.
#endregion
44.
}
Sample Usage of DataContextProxy and DataContextProxyBehavior classes:
Create an instance of DataContextProxy in resource dictionary
<
UserControl.Resources
>
<
DataContextProxy
x:Key
=
"DataContextProxy"
/>
</
UserControl.Resources
>
Use DataContextProxyBehavior to pass an element framework's DataContext to DataContextProxy
<
Grid
DataContext
=
"{Binding SomeViewModel}"
>
<
i:Interaction.Behaviors
>
<
DataContextProxyBehavior
DataContextProxy
=
"{StaticResource DataContextProxy}"
/>
</
i:Interaction.Behaviors
>
</
Grid
>
In a non-visual or visual element, bind to DataContextProxy using StaticResource.
<
telerik:GridViewDataColumn.Header
>
<
TextBlock
Text
=
"{Binding DataContext.SomeTextPropertyOnSomeViewModel, Source={StaticResource DataContextProxy}}"
/>
</
telerik:GridViewDataColumn.Header
>
That's great. Thank you a lot for supporting the community.
Regards,
Maya
Telerik
Hi,
What about if I want the data context of the Windows ?
If I understand the ItemsSource of RadGridView is related only for the grid item ? (that's work great for me).
But what is the current context of GridViewDataColumn.Header ?
In my case I have UserControl wich has a viewModel as DataContext.
This viewModel contains to properties: title and ObservableCollection<AnyClass> and I wnt that Title is bound to the header and the observableCollection (called collection) bound to rows.
All thinks work exepted the header....
Can you help me ?
Best Regards,
question: What about if I want the data context of the Windows?
answer: I am assumed that the RadGridView is hosted by a Window? If so, in general you can get the DataContext of the parent window by using
<Binding DataContext.YourPropertyInsideWindowDataContext, RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}
If using the DataContextProxy in previous post, then you would replace
<
Grid
DataContext
=
"..."
>
with
<
Grid
DataContext
=
"DataContext.YourPropertyInsideWindowDataContext, RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}"
>
By now, the Grid would bind to YourPropertyInsideWindowDataContext and the DataContextProxy would have YourPropertyInsideWindowDataContext. Then your GridViewDataColumn.Header would be able to access Title and ObservableCollection inside YourPropertyInsideWindowDataContext (follow sample described in previous post).
question: If I understand the ItemsSource of RadGridView is related only for the grid item ?
answer: generally yes.
question: But what is the current context of GridViewDataColumn.Header ?
answer: as I can remember, it's just empty typed object. It's definitely a different visual tree than the main one. That's the reason why the DataContextProxy was introduced so that the Header can get DataContext of main visual tree.
Hope the answers help you. The key to remember is to somehow to initialize the DataContextProxy the right VM. Once you have the right VM, you can do anything with it.
Thanks again for the absolutely accurate answers and the support to the other devs in the forum. I updated your Telerik points for the cooperation.
Regards,
Maya
Telerik
<
telerik:GridViewDataColumn
DataMemberBinding
=
"{Binding DataItemProperty}"
Width
=
"70"
>
<
telerik:GridViewDataColumn.Header
>
<
TextBlock
Text
=
"{Binding DataContext.HeaderTextProperty,RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}"
/>
</
telerik:GridViewDataColumn.Header
>
</
telerik:GridViewDataColumn
>
If the DataContext is not holded by the UserControl but another Parent, you can change the AncestorType accordingly...
Great, thanks a lot for providing another approach for handling the case.
Regards,
Maya
Telerik