Reputation: 807
I need to change the CellTemplate of a RadGridView. All of the examples I can find online defines the Columns statically in the Xaml, and then in that Column tag, they define the CellTemplate:
<telerik:RadGridView AutoGenerateColumns="False" ItemsSource="{Binding}" RowStyleSelector="{StaticResource styleSelector}">
<telerik:RadGridView.Columns>
<telerik:GridViewDataColumn DataMemberBinding="{Binding ID}" CellTemplateSelector="{StaticResource templateSelector}" />
</telerik:RadGridView.Columns>
</telerik:RadGridView>
The above example loads the CellTemplate only for the column with the heading "ID", and does that for all of the cells in that column.
I have managed to load the CellTemplates in the code behind but this required me to pass an instance of the whole grid to the ViewModel, and also had to add a dependency Property to the GridView to bind the Columns of the grid to a GridViewColumnCollection in the ViewModel.
This is a very messy workaround, one that I'm sure will get me fired if seen by the wrong eyes. What I need to do is something similar to this:
<telerik:RadGridView.CellTemplateSelector>
<local:MyTemplateSelector>
<local:MyTemplateSelector.NormalTemplate>
<DataTemplate>
...Some [Normal] template...
</DataTemplate>
</local:MyTemplateSelector.NormalTemplate
<local:MyTemplateSelector.DropdownTemplate>
<DataTemplate>
...Some [ComboBox] template...
</DataTemplate>
</local:MyTemplateSelector.DropdownTemplate>
</local:MyTemplateSelector>
</telerik:RadGridView.CellTemplateSelector>
I honestly have no idea why this RadGridView makes it so difficult to change the CellTemplate, because this prevents you from changing a common property such as the Foreground Color in the Label found in the ContentTemplate in the Cell itself.. Any ideas as to what I can do?
Upvotes: 1
Views: 3789
Reputation: 1403
So in advance, Im not sure about the "proper" way to do this, as WPF seems so highly contextual that it really seems to be a case of what suits your particular needs best.
My own approach to this probably issnt the most recommended, but i had the need to centralise all styleselector and cellselector variants and use logic written in code, not in xaml.
What i went with is something similar to telerik.StyleRule approach, but much simpler, as just in code-behind atm. Rather than perhaps defining in in shared xaml resource library or defining in each view, i use classes in a single folder and simply call it using CLR namespace and ect in the xaml (though could be set in code-behind ofcourse)
It uses the column name and very easy to define conditions using:
Expression<Func<BaseModelForAllTableObjectClasses,bool>>
The actual resources (datatemplate and styles) that it uses are defined in shared xaml resource library, but those can be quite generic if needed.
Its a little rough, and ill leave in comments in case of interest.
MyProject.CellStyleSelector
/// <summary>
/// Used in CellStyleSelector and its inheriting classes.
///
/// Wouldnt be too hard to modify to allow declarative if highly desired.
///
/// NOTE - tried extending telerik.StyleRule, issues with setting condition value
/// </summary>
public class CellStyleRule
{
/*
object _Value;
public object Value
{
get
{
return this._Value;
}
set
{
this._Value = value;
}
}
*/
// NOTE - if needing to widen use case, use <T>, but ONLY if needed
Expression<Func<BaseDataModel, bool>> _Condition;
public Expression<Func<BaseDataModel, bool>> Condition
{
get
{
return this._Condition;
}
set
{
this._Condition = value;
}
}
Style _Style;
public Style Style
{
get
{
return this._Style;
}
set
{
this._Style = value;
}
}
public CellStyleRule(Expression<Func<BaseDataModel, bool>> c, string s)
{
var d = App.Current.FindResource(s);
if (d != null)
this.Style = d as Style;
else
throw new Exception("No such style resource as '" + s + "'");
// Value = v;
Condition = c;
}
/*
public CellStyleRule(string c, string s)
{
var d = App.Current.FindResource(s);
if (d != null)
Style = d as Style;
else
throw new Exception("No such style resource as '" + s + "'");
// TODO - test - guessing at how to do this based on telerik classes
// Value = v;
Telerik.Windows.Data.ExpressionTypeConverter converter = new Telerik.Windows.Data.ExpressionTypeConverter();
Condition = (System.Linq.Expressions.Expression) converter.ConvertFromString(c);
c.ToString();
}
*/
}
// NOTE IMPORTANT - use of xaml defined ConditionalStyleSelectors is not a bad alternative approach, though diferent selector
// for each column it could all be defined in same resource dictionary
// - problem is that issues encountered with Enums, so decided on this approach instead
/// <summary>
/// A variation of StyleSelecter not unlike telerik:ConditionalStyleSelector.
///
/// However, rules are defined using a Dictionary<string,CellStyle> to distinct columns and use same
/// selector, with rules (currently) defined not declarativly (xaml), but in inheriting classes,
/// typically one for each entity type requiring a RadGridView and conditional styling.
/// </summary>
public abstract class CellStyleSelector : StyleSelector
{
// public Dictionary<String, Dictionary<string, Style>> conditions { get; set; }
// Use a Dictionary mapping X columns to individual Lists of rules to check
Dictionary<string, List<CellStyleRule>> _Rules;
public Dictionary<string, List<CellStyleRule>> Rules
{
get
{
if (this._Rules == null)
{
this._Rules = new Dictionary<string, List<CellStyleRule>>();
}
return this._Rules;
}
set { this._Rules = value; }
}
public override Style SelectStyle(object item, DependencyObject container)
{
if (item is BaseDataModel)
{
GridViewCell cell = container as GridViewCell;
var currentColumn = cell.Column as GridViewDataColumn;
string key = currentColumn.DataMemberBinding.Path.Path;
if (Rules.ContainsKey(key))
{
foreach (CellStyleRule rule in Rules[key])
{
// string debug = DebugHelper.Debug(rule.Condition);
// REVERTED NOTE - if just Func<>
// if (rule.Condition((BaseDataModel)item))
// NOTE - if Expression<Func<>>, first compile then pass in param
if (rule.Condition.Compile()((BaseDataModel)item))
{
return rule.Style;
}
}
}
}
return null;
}
}
/// <summary>
/// Used in CellCellTemplateRuleSelector and its inheriting classes.
///
/// Wouldnt be too hard to modify to allow declarative if highly desired.
/// </summary>
public class CellTemplateRule
{
/*
object _Value;
public object Value
{
get
{
return this._Value;
}
set
{
this._Value = value;
}
}
*/
// NOTE - if needing to widen use case, use <T>, but ONLY if needed
Expression<Func<BaseDataModel, bool>> _Condition;
public Expression<Func<BaseDataModel, bool>> Condition
{
get
{
return this._Condition;
}
set
{
this._Condition = value;
}
}
DataTemplate _Template;
public DataTemplate Template
{
get
{
return this._Template;
}
set
{
this._Template = value;
}
}
public CellTemplateRule(Expression<Func<BaseDataModel, bool>> c, string s)
{
var d = App.Current.FindResource(s);
if (d != null)
this.Template = d as DataTemplate;
else
throw new Exception("No such template resource as '" + s + "'");
Condition = c;
}
}
// Implementing class, used for any licence table
public class LicenceTableCellStyleSelector : CellStyleSelector
{
public LicenceTableCellStyleSelector()
: base()
{
this.Rules = new Dictionary<string, List<CellStyleRule>>()
{
{ "LicenceStatus" , new List<CellStyleRule>()
{
// Always most specific rules at top
// Error catcher, leave in as visual cue just in case
{ new CellStyleRule(x => ((Licence)x).LicenceExpires < DateTime.Now && ((Licence)x).LicenceStatus != LicenceStatus.Expired, "CellStyle_Licence_ExpiryMismatch") } ,
{ new CellStyleRule(x => ((Licence)x).LicenceStatus == LicenceStatus.Active, "CellStyle_Status_Active") } ,
// Same as != Active, as only those would through this far
{ new CellStyleRule(x => x != null, "CellStyle_Status_Inactive") } ,
}
},
{ "LicenceType" , new List<CellStyleRule>()
{
{ new CellStyleRule(x => ((Licence)x).LicenceType == LicenceType.Full, "CellStyle_Licence_Full") } ,
{ new CellStyleRule(x => ((Licence)x).LicenceType == LicenceType.Upgrade, "CellStyle_Licence_Upgrade") } ,
// Timed, uses fallthrough so no need to actually check unless wanting to distinct types of timed
{ new CellStyleRule(x => x != null, "CellStyle_Licence_Timed") } ,
}
},
{ "LicenceExpired" , new List<CellStyleRule>()
{
{ new CellStyleRule(x => ((Licence)x).LicenceExpires < DateTime.Now && ((Licence)x).LicenceStatus != LicenceStatus.Expired, "CellStyle_Licence_ExpiryMismatch") },
{ new CellStyleRule(x => ((Licence)x).LicenceExpires < ((Licence)x).LicenceIssued, "CellStyle_Licence_ExpiryMismatch") } ,
}
},
{ "LicenceIssued" , new List<CellStyleRule>()
{
{ new CellStyleRule(x => ((Licence)x).LicenceExpires < ((Licence)x).LicenceIssued, "CellStyle_Licence_ExpiryMismatch") } ,
}
}
};
}
}
CellTemplateSelector
/// <summary>
/// Used in CellCellTemplateRuleSelector and its inheriting classes.
///
/// Wouldnt be too hard to modify to allow declarative if highly desired.
/// </summary>
public class CellTemplateRule
{
/*
object _Value;
public object Value
{
get
{
return this._Value;
}
set
{
this._Value = value;
}
}
*/
// NOTE - if needing to widen use case, use <T>, but ONLY if needed
Expression<Func<BaseDataModel, bool>> _Condition;
public Expression<Func<BaseDataModel, bool>> Condition
{
get
{
return this._Condition;
}
set
{
this._Condition = value;
}
}
DataTemplate _Template;
public DataTemplate Template
{
get
{
return this._Template;
}
set
{
this._Template = value;
}
}
public CellTemplateRule(Expression<Func<BaseDataModel, bool>> c, string s)
{
var d = App.Current.FindResource(s);
if (d != null)
this.Template = d as DataTemplate;
else
throw new Exception("No such template resource as '" + s + "'");
Condition = c;
}
}
// NOTE IMPORTANT - use of xaml defined ConditionalTemplateSelectors is not a bad alternative approach, though diferent selector
// for each column it could all be defined in same resource dictionary
// - problem is that issues encountered with Enums, so decided on this approach instead
/// <summary>
/// See CellStyleSelector, this is a variant used with the CellTemplateSelector property
/// </summary>
public abstract class CellTemplateSelector : DataTemplateSelector
{
// public Dictionary<String, Dictionary<string, Template>> conditions { get; set; }
// Use a Dictionary mapping X columns to individual Lists of rules to check
Dictionary<string, List<CellTemplateRule>> _Rules;
public Dictionary<string, List<CellTemplateRule>> Rules
{
get
{
if (this._Rules == null)
{
this._Rules = new Dictionary<string, List<CellTemplateRule>>();
}
return this._Rules;
}
set { this._Rules = value; }
}
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item is BaseDataModel)
{
GridViewCell cell = container as GridViewCell;
var currentColumn = cell.Column as GridViewDataColumn;
string key = currentColumn.DataMemberBinding.Path.Path;
if (Rules.ContainsKey(key))
{
foreach (CellTemplateRule rule in Rules[key])
{
// string debug = DebugHelper.Debug(rule.Condition);
// REVERTED NOTE - if just Func<>
// if (rule.Condition((BaseDataModel)item))
// NOTE - if Expression<Func<>>, first compile then pass in param
if (rule.Condition.Compile()((BaseDataModel)item))
{
return rule.Template;
}
}
}
}
return null;
}
}
// Implementing class for a different table, though RadGridView can use both CellStyleSelector and CellTemplateSelector at same time, i just didnt need yet
public class OrderDongleWrapTableCellTemplateSelector : CellTemplateSelector
{
public OrderDongleWrapTableCellTemplateSelector()
: base()
{
this.Rules = new Dictionary<string, List<CellTemplateRule>>()
{
{ "ReplacedDongle.DongleID" , new List<CellTemplateRule>()
{
// Always most specific rules at top
{ new CellTemplateRule(x => ((OrderDongleWrap)x).IsReplacementDongle == false , "CellTemplate_OrderDongleWrap_NotReplacementDongler_NAField") },
}
},
{ "ReplacedDongleStatus" , new List<CellTemplateRule>()
{
// Always most specific rules at top
{ new CellTemplateRule(x => ((OrderDongleWrap)x).IsReplacementDongle == false , "CellTemplate_OrderDongleWrap_NotReplacementDongler_NAField") },
}
},
/*
* // REVERTED - better to just set to UNKNOWN CUSTOMER explicitly before shown in table, rather than overwrite datatemplate
{ "Customer.CustomerName" , new List<CellTemplateRule>()
{
// Always most specific rules at top
{ new CellTemplateRule(x => ((OrderDongleWrap)x).Customer == null || ((OrderDongleWrap)x).Customer.CustomerID == 1 , "CellTemplate_OrderDongleWrap_UnknownCustomerField") },
}
},
*/
};
}
}
xaml resources
<!--
The following are styles applied to cells or rows in RadGridViews based on StyleSelectors
First is generic colours, which are inherited by semi-generic flag styles, which optionally
can be further inherited by table-specific styles (which can include tooltips and other properties)
As a general rule, no colour style is applied directly, instead using semi-generic or table-specific
styles so as to make changes easier to manage. Which style actually matches which condition is
entirely the responsibility of the StyleSelector.
https://en.wikipedia.org/wiki/Web_colors
http://www.flounder.com/csharp_color_table.htm
<Style x:Key="CellStyle_" TargetType="telerik:GridViewCell" BasedOn="{StaticResource GridViewCellStyle}">
<Setter Property="Foreground" Value=""/>
</Style>
-->
<!--
NOTE: to get cell text underlining to work, see http://www.telerik.com/forums/underline-cell-contents
-->
<Style x:Key="CellStyle_Green" TargetType="telerik:GridViewCell" BasedOn="{StaticResource GridViewCellStyle}">
<Setter Property="Foreground" Value="Green"/>
</Style>
<Style x:Key="CellStyle_DarkGreen" TargetType="telerik:GridViewCell" BasedOn="{StaticResource GridViewCellStyle}">
<Setter Property="Foreground" Value="DarkGreen"/>
</Style>
<Style x:Key="CellStyle_ExtraDarkGreen" TargetType="telerik:GridViewCell" BasedOn="{StaticResource GridViewCellStyle}">
<Setter Property="Foreground" Value="#004000"/>
</Style>
<Style x:Key="CellStyle_MediumBlue" TargetType="telerik:GridViewCell" BasedOn="{StaticResource GridViewCellStyle}">
<Setter Property="Foreground" Value="MediumBlue"/>
</Style>
<Style x:Key="CellStyle_DarkBlue" TargetType="telerik:GridViewCell" BasedOn="{StaticResource GridViewCellStyle}">
<Setter Property="Foreground" Value="DarkBlue"/>
</Style>
<Style x:Key="CellStyle_Purple" TargetType="telerik:GridViewCell" BasedOn="{StaticResource GridViewCellStyle}">
<Setter Property="Foreground" Value="Purple"/>
</Style>
<Style x:Key="CellStyle_Red" TargetType="telerik:GridViewCell" BasedOn="{StaticResource GridViewCellStyle}">
<Setter Property="Foreground" Value="Red"/>
</Style>
<Style x:Key="CellStyle_Crimson" TargetType="telerik:GridViewCell" BasedOn="{StaticResource GridViewCellStyle}">
<Setter Property="Foreground" Value="Crimson"/>
</Style>
<Style x:Key="CellStyle_DarkOrange" TargetType="telerik:GridViewCell" BasedOn="{StaticResource GridViewCellStyle}">
<Setter Property="Foreground" Value="DarkOrange"/>
</Style>
<Style x:Key="CellStyle_Silver" TargetType="telerik:GridViewCell" BasedOn="{StaticResource GridViewCellStyle}">
<Setter Property="Foreground" Value="Silver"/>
</Style>
<Style x:Key="CellStyle_DarkGray" TargetType="telerik:GridViewCell" BasedOn="{StaticResource GridViewCellStyle}">
<Setter Property="Foreground" Value="DarkGray"/>
</Style>
<Style x:Key="CellStyle_Gray" TargetType="telerik:GridViewCell" BasedOn="{StaticResource GridViewCellStyle}">
<Setter Property="Foreground" Value="Gray"/>
</Style>
<Style x:Key="CellStyle_RedBG_WhiteFG" TargetType="telerik:GridViewCell" BasedOn="{StaticResource GridViewCellStyle}">
<Setter Property="Background" Value="Red"/>
<Setter Property="Foreground" Value="White"/>
</Style>
<Style x:Key="CellStyle_Muted" TargetType="telerik:GridViewCell" BasedOn="{StaticResource CellStyle_Gray}" />
<Style x:Key="CellStyle_Status_Active" TargetType="telerik:GridViewCell" BasedOn="{StaticResource CellStyle_ExtraDarkGreen}" />
<Style x:Key="CellStyle_Status_Inactive" TargetType="telerik:GridViewCell" BasedOn="{StaticResource CellStyle_Gray}" />
<Style x:Key="CellStyle_Status_Warning" TargetType="telerik:GridViewCell" BasedOn="{StaticResource CellStyle_Crimson}" />
<Style x:Key="CellStyle_Status_Error" TargetType="telerik:GridViewCell" BasedOn="{StaticResource CellStyle_Red}" />
<Style x:Key="CellStyle_Status_ErrorBG" TargetType="telerik:GridViewCell" BasedOn="{StaticResource CellStyle_RedBG_WhiteFG}" />
<Style x:Key="CellStyle_Dongle_InactiveWithActiveLicences" TargetType="telerik:GridViewCell" BasedOn="{StaticResource CellStyle_Status_Error}">
<Setter Property="ToolTip" Value="This Dongle is not Active, but has Licences which are"/>
</Style>
<Style x:Key="CellStyle_Licence_Full" TargetType="telerik:GridViewCell" BasedOn="{StaticResource GridViewCellStyle}" />
<Style x:Key="CellStyle_Licence_Upgrade" TargetType="telerik:GridViewCell" BasedOn="{StaticResource CellStyle_DarkBlue}" />
<Style x:Key="CellStyle_Licence_Timed" TargetType="telerik:GridViewCell" BasedOn="{StaticResource CellStyle_Purple}" />
<Style x:Key="CellStyle_Licence_ExpiryMismatch" TargetType="telerik:GridViewCell" BasedOn="{StaticResource CellStyle_Status_ErrorBG}">
<Setter Property="ToolTip" Value="Expiry doesnt match Status or Issued Date"/>
</Style>
<Style x:Key="CellStyle_OrderDongleWrap_NotReplacementDongler_NAField" TargetType="telerik:GridViewCell" BasedOn="{StaticResource CellStyle_Gray}">
<Setter Property="Content" Value="N/A"/>
</Style>
<Style x:Key="CellStyle_OrderDongleWrap_UnknownCustomerField" TargetType="telerik:GridViewCell" BasedOn="{StaticResource CellStyle_Gray}">
<Setter Property="Content" Value="N/A"/>
</Style>
<DataTemplate x:Key="CellTemplate_OrderDongleWrap_NotReplacementDongler_NAField">
<TextBlock>N/A</TextBlock>
</DataTemplate>
implementing xaml for CellStyleSelector
<telerik:RadGridView.Resources>
<gridviewstyleselectors:LicenceTableCellStyleSelector x:Key="LicenceTableCellStyleSelector" />
</telerik:RadGridView.Resources>
<telerik:RadGridView.Columns>
<telerik:GridViewDataColumn DataMemberBinding="{Binding DongleID}" Header="DongleID" IsReadOnly="True" />
<telerik:GridViewDataColumn DataMemberBinding="{Binding LicenceSerial}" Header="Serial" IsReadOnly="True" />
<telerik:GridViewDataColumn DataMemberBinding="{Binding LicenceStatus}" Header="Status" IsReadOnly="True"
CellStyleSelector="{StaticResource LicenceTableCellStyleSelector}"/>
<telerik:GridViewDataColumn DataMemberBinding="{Binding Product.ProductName}" Header="Product" IsReadOnly="True" />
<telerik:GridViewDataColumn DataMemberBinding="{Binding LicenceType}" Header="Licence Type"
CellStyleSelector="{StaticResource LicenceTableCellStyleSelector}" IsReadOnly="True" />
<telerik:GridViewDataColumn DataMemberBinding="{Binding LicenceIssued}" Header="Issued"
CellStyleSelector="{StaticResource LicenceTableCellStyleSelector}" IsReadOnly="True" />
<telerik:GridViewDataColumn DataMemberBinding="{Binding LicenceExpires}" Header="Expiry"
CellStyleSelector="{StaticResource LicenceTableCellStyleSelector}" IsReadOnly="True" />
</telerik:RadGridView.Columns>
Ive only had one case for CellTemplateSelector so far, and here it is:
implementing xaml for CellTemplateSelector
<telerik:RadGridView.Columns>
<telerik:GridViewDataColumn DataMemberBinding="{Binding Dongle.DongleID}" Header="DongleID">
<telerik:GridViewDataColumn.AggregateFunctions>
<telerik:CountFunction ResultFormatString="{}Dongles: {0}" />
</telerik:GridViewDataColumn.AggregateFunctions>
</telerik:GridViewDataColumn>
<telerik:GridViewDataColumn DataMemberBinding="{Binding OrderRows.Count}" Header="Licences"
CellStyleSelector="{StaticResource OrderDongleWrapTableCellStyleSelector}"
CellTemplateSelector="{StaticResource OrderDongleWrapTableCellTemplateSelector}" />
<telerik:GridViewDataColumn DataMemberBinding="{Binding Customer.CustomerName}" Header="Customer"
CellStyleSelector="{StaticResource OrderDongleWrapTableCellStyleSelector}"
CellTemplateSelector="{StaticResource OrderDongleWrapTableCellTemplateSelector}" />
<telerik:GridViewDataColumn DataMemberBinding="{Binding IsReplacementDongle}" Header="Replacement?"
IsVisible="False"
CellStyleSelector="{StaticResource OrderDongleWrapTableCellStyleSelector}"
CellTemplateSelector="{StaticResource OrderDongleWrapTableCellTemplateSelector}" />
<telerik:GridViewDataColumn DataMemberBinding="{Binding IsEducationalDongle}" Header="Educational?"
CellStyleSelector="{StaticResource OrderDongleWrapTableCellStyleSelector}"
CellTemplateSelector="{StaticResource OrderDongleWrapTableCellTemplateSelector}" />
<telerik:GridViewDataColumn DataMemberBinding="{Binding ReplacedDongle.DongleID}" Header="Replaced"
CellStyleSelector="{StaticResource OrderDongleWrapTableCellStyleSelector}"
CellTemplateSelector="{StaticResource OrderDongleWrapTableCellTemplateSelector}" />
<telerik:GridViewDataColumn DataMemberBinding="{Binding ReplacedDongleStatus}" Header="Replacement Status"
CellStyleSelector="{StaticResource OrderDongleWrapTableCellStyleSelector}"
CellTemplateSelector="{StaticResource OrderDongleWrapTableCellTemplateSelector}"/>
<telerik:GridViewDataColumn DataMemberBinding="{Binding OrderRows[0].OrderRowCosts[0].CostValue }" Header="Sub-Total (AUD)">
</telerik:GridViewDataColumn>
</telerik:RadGridView.Columns>
So this is my own approach, but it centralises the conditional logic and the styles into just 1 cs class per target class (GridView ItemsSource object) and just 1 resource sheet for all GridView.
You can refactor fairly easily into being declarative if you wanted, I saw it was possible but didnt need or want to do that so I didnt.
You can also refactor so the rules cascade off single cs class for all tables, or even to declare styles on the fly..? (by new Style and adding attributes?)
In any case, I hope this helps anyone else with this issue
Upvotes: 2