Setting datagrid cell template on runtime - wpf

I've written a DataTemplate:
<DataTemplate x:Key="ellipseTemplate">
<Grid Margin="4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Ellipse Height="20" Width="20" StrokeThickness="5" Stroke="Black" Fill="Gold"/>
</Grid>
</DataTemplate>
And I want to set it to a DateGridCell.Template property in run time.
I can access the cell and change its properties.
I'm using MVVM so I don't want to access it from code-behind
Can I access this template from view model code and set it to the cell Template property?
Or maybe I can build it in the viewmodel in code instead of XAML?

What you seem to be looking for is called CellTemplateSelector.
Take a look at this example:
This shall be placed inside Window.Resources.
<DataTemplate x:Key="DefaultTemplate">
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=Name}" Value="Andre">
<Setter Property="DataGrid.Foreground" Value="Yellow"></Setter>
</DataTrigger>
</DataTemplate.Triggers>
<TextBlock>
<TextBlock.Text>
<Binding Path="Name"/>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
And this shall be placed inside the DataGridTemplateColumn definition. Any DataGridColumn allows setting CellTemplateSelector.
<DataGridTemplateColumn.CellTemplateSelector>
<local:TemplateSelector
DefaultTemplate="{StaticResource DefaultTemplate}"
</local:TemplateSelector>
<DataGridTemplateColumn.CellTemplateSelector>
In the example I gave you the guy had a custom TemplateSelector which he defined inside CellTemplateSelector like this: <local:TemplateSelector...
To read more about CellTemplateSelector or generally about ContentTemplateSelectors I suggest you to check following links out:
http://msdn.microsoft.com/en-us/library/system.windows.controls.datagridtemplatecolumn.celltemplateselector%28v=vs.110%29.aspx
http://msdn.microsoft.com/en-us/library/system.windows.controls.contentcontrol.contenttemplateselector%28v=vs.110%29.aspx
The last link one has an MSDN example which you should check out too.

Related

Hide/Show DataGrid based on Selected ListBoxItem in WPF

I have a ListBox:
<ListBox Name="ListB" SelectedIndex="0" ItemsSource="{Binding Account}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock FontSize="16" Grid.Column="0" Grid.RowSpan="3">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} {1}" >
<Binding Path="AccountNumber" />
<Binding Path="Name" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Based on the AccountNumber I want to Show/Hide DataGrid's that are bound to the ListBoxItems:
<!--DataGrid 1-->
<DataGrid ItemsSource="{Binding ElementName=ListB, Path=SelectedItem}">
..................
</DataGrid>
<!--DataGrid 2-->
<DataGrid ItemsSource="{Binding ElementName=ListB, Path=SelectedItem}">
..................
</DataGrid>
Is there any if/else in WPF? For example
if SelectedItem in ListBox has an AccountNumber 100
than show DataGrid 1 and hide DataGrid 2
else hide DataGrid 1 and show DataGrid 2.
Thank you in advance for the tips.
Unfortunately WPF does not come with if/then/else structures. You have to build up a work-arround or use frameworks, which can solve your issue. One possible solution is to use blend sdk's interaction trigger framework:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
The behavior you mention would be implemented as following:
<i:Interaction.Triggers>
<!-- One Trigger for equal 100 -->
<ei:DataTrigger Binding="{Binding Path=SelectedItem.AccountNumber,
ElementName=ListB}"
Value="100">
<ei:ChangePropertyAction TargetName="DataGrid1"
PropertyName="Visibility"
Value="Collapsed" />
<ei:ChangePropertyAction TargetName="DataGrid2"
PropertyName="Visibility"
Value="Visible" />
</ei:DataTrigger>
<!-- One Trigger for not equal 100 -->
<ei:DataTrigger Binding="{Binding Path=SelectedItem.AccountNumber,
ElementName=ListB}"
Comparison="NotEqual"
Value="100">
<ei:ChangePropertyAction TargetName="DataGrid1"
PropertyName="Visibility"
Value="Visible" />
<ei:ChangePropertyAction TargetName="DataGrid2"
PropertyName="Visibility"
Value="Collapsed" />
</ei:DataTrigger>
</i:Interaction.Triggers>
You will need to include System.Windows.Interactivity.dll into your project references.
Also required would be Microsoft.Expression.Interactions.dll
Another solution would be to bind the visibility of your DataGrids directly to SelectedItem.AccountNumber and attach and IValueConverter, which extracts the Visibility according to logic.
Using the SelectionChanged Event of the ListBox would also work, but becomes pretty much unreadable and may be redundant. If you follow the MVVM approach, your code behind should be almost empty.
This would be straightforward if you were using a MVVM framework.
Add a BooleanToVisibility converter to your View as in this example, with boolean properties to control the visibility of each grid in the ViewModel which are updated whenever the ListBox.SelectedItem is changed.

Partially templated ListBox.ItemTemplate

I'm creating a custom control and I'm trying to create partially specified template for list box items. The template has some predefined parts and there should be another part that can be templated when using the control.
For this I have created a dependency property named SuggestionItemTemplate like so:
public static readonly DependencyProperty SuggestionItemTemplateProperty =
DependencyProperty.Register("SuggestionItemTemplate",
typeof(DataTemplate),
typeof(AutoSuggestTextBox),
new PropertyMetadata(null));
In my custom controls' generic.xaml I have:
<Style TargetType="local:AutoSuggestTextBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:AutoSuggestTextBox">
<Grid>
<ListBox x:Name="ItemsControl">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ContentPresenter Grid.Column="0"
ContentTemplate="{TemplateBinding SuggestionItemTemplate}"
Content="{Binding}" />
<ToggleButton Grid.Column="1"
x:Name="DetailsHover"
ClickMode="Hover"
Style="{StaticResource DetailsToggleButtonStyle}" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Unfortunatelly, this does not work as it's not possible to use TemplateBinding from inside ContentPresenter nested into DataTemplate. (The member "SuggestionItemTemplate" is not recognized or is not accessible.)
I also tried to use ancestor binding (available in Silverlight 5) like:
<ContentPresenter Grid.Column="0"
ContentTemplate="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=local:AutoSuggestTextBox}, Path=SuggestionItemTemplate}"
Content="{Binding}" />
But this results in binding error:
Error: System.Exception: BindingExpression_CannotFindAncestor
I suppose this happens because I'm inside ControlTemplate of my custom control and "local:AutoSuggestTextBox" is not defined anywhere in the style.
The third option that I tried was to apply ContentTemplate in OnApplyTemplate override but this also doesn't work:
var cp = itemsControlElement.ItemTemplate.LoadContent() as ContentPresenter;
cp.ContentTemplate = SuggestionItemTemplate;
In all cases, I get my grid with two columns, toggle button is visible but content presenter simple prints out view model's type name. (I believe this is the default behavior if the ContentTemplate is null).
Is this even possible to do? Are there any other ways to specify a partial template and then only add customized template part when necessary?
As a workaround for now, I can specify
ItemTemplate="{TemplateBinding SuggestionItemTemplate}"
for the list box and then copy/paste the generic template everywhere I use this control. But this is the behavior I'm hoping to avoid in the first place.
Thanks!
edit: I used the code tags for all blocks of code, but they're not highlighted for some reason. :/
It is possible to walk through Visual Ancestors in the OnApplyTemplate method, find your ContentPresenter(s) and set the ItemTemplate on that. To my mind, this is fine for a single item, but not so much in an ItemsControl scenario.
You could achieve what you are after using your own custom Control. Just give it a Content dependency property of type Object, and a Template DP of type DataTemplate (and multiples of the two if you fancy), and you can set up the root visual style and templates in the default style for your Control.
In this specific case, I would suggest that the best approach is to put your ToggleButton in the ListBoxItem template instead by customising the ListBox.ItemContainerStyle. It is easy to modify the default Control Template using Expression Blend, and the DataContext of the ToggleButton will not change, so the changes to your own logic should be minimal.
Edit: If you mean to use a number of different data templates, perhaps Implicit Data Templates will be more suitable.
I managed to solve this using a different approach. I used ancestor binding but instead of trying to reach the root control (my AutoSuggestTextBox) from the DataTemplate, I ask for a reference to my ListBox (here named ItemsControl).
However, since the ListBox doesn't have the SuggestionItemTemplate property, I sub-classed it to my own CustomListBox where I implemented that property. It all comes down to this code snippet:
<Style TargetType="local:AutoSuggestTextBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:AutoSuggestTextBox">
<Grid>
<local:CustomizableListBox x:Name="ItemsControl"
SuggestionItemTemplate="{TemplateBinding SuggestionItemTemplate}">
<local:CustomizableListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ContentPresenter Grid.Column="0"
ContentTemplate="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=local:CustomizableListBox}, Path=SuggestionItemTemplate}"
Content="{Binding}" />
<ToggleButton Grid.Column="1"
x:Name="DetailsHover"
ClickMode="Hover"
Style="{StaticResource DetailsToggleButtonStyle}" />
</Grid>
</DataTemplate>
</local:CustomizableListBox.ItemTemplate>
</local:CustomizableListBox>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

DataGrid column header text wrapping in silverlight

I am using a DataGrid in my silverlight project and I want one column to wrap it's header text. I know using a style for the header might be the answer but I want to know if there is a wrap property for a datagrid column header?
Here is my code:
<data:DataGrid x:Name="gridViewResources"
AutoGenerateColumns="False"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
ItemsSource="{Binding OpportunityResourceDetailList, Mode=TwoWay}" IsReadOnly="True">
<data:DataGrid.Columns>
<data:DataGridTemplateColumn Header="#" Width="Auto">
<data:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding PositionLevel.FullPositionAndLevelName}" />
</DataTemplate>
</data:DataGridTemplateColumn.CellTemplate>
</data:DataGridTemplateColumn>
Thanks!
I haven't found a way to get Silverlight to automatically wrap the header (in much the same way as the TextWrapping property of a TextBlock does). I suspect that it's not possible due to a limitation of the DataGridColumn.Header property:
Use discretion when using objects as header content; not all Silverlight objects are suitable for use within the limited presentation surface that appears for headers.
However, you can 'manually' wrap header text. If you put a newline in the header text, the header text will be broken over two lines at that point. (In XAML, you use the character entity
.) For example, the following header text appears split over three lines:
<sdk:DataGridTextColumn Header="ABCD
EFGH
IJKL" />
There is no specific property on a DataGridColumn to support this but once you have created the right style it is just as easy - simply set the HeaderStyle property for the specific column.
Create a Style resource with a TargetType of DataGridColumnHeader and set the ContentTemplate property to be a DataTemplate containing a TextBlock with the TextWrapping property set to Wrap. I've enclosed the TextBlock within a Grid panel to keep this closer to the default DataTemplate used by the ContentPresenter. Apply this style to a specific column using the HeaderStyle property or to the whole DataGrid using the ColumnHeaderStyle property.
Note that you will need to constrain the width of the Column to something less than the header text for the wrapping to take affect unless you specifically restrict the width of the TextBlock in the DataTemplate.
<Style x:Key="CustomDataGridColumnHeaderStyle" TargetType="sdk:DataGridColumnHeader" BasedOn="{StaticResource DefaultDataGridColumnHeaderStyle}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding}" TextWrapping="Wrap" />
</Grid>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
...
<sdk:DataGrid >
<sdk:DataGrid.Columns>
<sdk:DataGridTextColumn Header="Header Name" Binding="{Binding Xxx}" Width="80" HeaderStyle="{StaticResource CustomDataGridColumnHeaderStyle}"/>
</sdk:DataGrid.Columns>
</sdk:DataGrid>
here is my code
<DataGrid.ColumnHeaderStyle>
<Style TargetType="ContentControl">
<Setter Property="HorizontalContentAlignment" Value="Center"></Setter>
<Setter Property="ContentTemplate" >
<Setter.Value >
<DataTemplate>
<TextBlock Text="{Binding}" TextWrapping="Wrap" FontWeight="Bold" TextAlignment="Center" LineHeight="20"></TextBlock></DataTemplate>
</Setter.Value>
</Setter>
</Style>
`enter code here`</DataGrid.ColumnHeaderStyle>

How can I trigger this Error Template?

Below is a template that works from a binding perspective, but the error template doesn't show, and without an AdornedElementPlaceholder the result looks a bit garish.
My view models implement IDataErrorInfo, and normally I would trigger the error template by having ValidatesOnError=True as part of my binding. This particular view model is display only, so the IDataErrorInfo indexer is never invoked. I do have a number of useful properties related to validation though, including a boolean IsValid property as well as IDataErrorInfo.Error, both of which properly respond to the view model being invalid.
Should I translate the error to a ValidationResult and trigger it that way? Or is there something easier?
Cheers,
Berryl
current template
<!-- FooterViewModel DataTemplate -->
<DataTemplate DataType="{x:Type model:FooterViewModel}">
<Label x:Name="lblTotalTime"
Style="{StaticResource FooterStyle}"
Content="{Binding TotalTime, Converter={StaticResource TotalAmountConv}}" >
<Label.ToolTip>
<TextBlock Text="{Binding FeedbackMessage}" ></TextBlock>
</Label.ToolTip>
<Validation.ErrorTemplate>
<ControlTemplate>
<DockPanel LastChildFill="True">
<TextBlock DockPanel.Dock="Right" Text=" *"
Foreground="Red"
FontWeight="Bold" FontSize="16"
/>
<Border BorderBrush="Red" BorderThickness="1">
<AdornedElementPlaceholder Name="placeholder"></AdornedElementPlaceholder>
</Border>
</DockPanel>
</ControlTemplate>
</Validation.ErrorTemplate>
</Label>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsValid}" Value="False">
<Setter TargetName="lblTotalTime" Property="Control.BorderBrush" Value="Red"/>
<Setter TargetName="lblTotalTime" Property="Control.BorderThickness" Value="1"/>
<Setter TargetName="lblTotalTime" Property="Control.Background" Value="LightYellow"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
UPDATE
Ok, I am getting IDataErrorInfo to kick in just by changing my binding to include ValidatesOnErrors, BUT the error template still does not show up.
Here is the binding
<ItemsControl
ItemsSource="{Binding Path=FooterViewModels, Mode=OneWay, ValidatesOnDataErrors=True}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
By default, the validation is only run when the Source of the binding is updated. In your ItemsControl.ItemsSource binding the Sources is your FooterViewsModels, which obviously will never be updated (because you have Mode=OneWay).
You can use the DataErrorValidationRule.ValidatesOnTargetUpdated to run the validation when the target is updated as well. The link gives an example.
Keep in mind that the Binding.ValidatesOnDataErrors property is is just a short cut for adding an instance of DataErrorValidationRule to the Binding.ValidationRules collection.
Finally, the control that the binding is defined one will have the Validation.Errors. In your case, that is the ItemsControl, not the items inside it. So, I believe you need to add the DataErrorValidationRule to your Label.Content binding. Or you need to define your ErrorTemplate on the ItemsControl, depending on what you are going for.

How to create a standard DataTemplate for DataGridTemplateColumn?

I've got a DataTemplate for a DataGridTemplateColum wich looks like this:
<toolkit:DataGridTemplateColumn x:Name="DataGridTextColumnIstVorvorjahr" IsReadOnly="True" SortMemberPath="SummeIstVorvorjahr">
<toolkit:DataGridTemplateColumn.CellTemplate >
<DataTemplate>
<Grid HorizontalAlignment="Stretch" Background="Transparent" Margin="0,-5">
<DockPanel VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<TextBlock Panel.ZIndex="100" Style="{DynamicResource CellText}" Text="{Binding Path=SummeIstVorvorjahrGerundet, Converter={StaticResource numberFormatter}, ConverterParameter='#,0.0 T€'}" DockPanel.Dock="Right"/>
<Image Panel.ZIndex="90" DockPanel.Dock="Left" MouseLeftButtonUp="FilterDataGridAnalyse_MouseDoubleClick" HorizontalAlignment="Left" Margin="5,0,0,0" Width="20" Height="20" Visibility="Hidden" Name="ImageNormal" Source="pack://application:,,,/Cis.Common.Presentation;component/Resources/Images/Lupe.png" />
</DockPanel>
</Grid>
<DataTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter TargetName="ImageNormal" Property="Visibility" Value="Visible" />
</Trigger>
</DataTemplate.Triggers>
</DataTemplate>
</toolkit:DataGridTemplateColumn.CellTemplate>
<toolkit:DataGridTemplateColumn.HeaderTemplate>
<DataTemplate >
<DockPanel VerticalAlignment="Stretch" HorizontalAlignment="Stretch" LastChildFill="False">
<TextBlock x:Name="TextBlockHeaderZeile1" Text="Ist" DockPanel.Dock="Top" />
<WrapPanel DockPanel.Dock="Top">
<TextBlock x:Name="TextBlockHeaderZeile2" Text=""/>
<ContentPresenter x:Name="contentPresenter">
<ContentPresenter.Content>
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Content" />
</ContentPresenter.Content>
</ContentPresenter>
</WrapPanel>
<Border Style="{DynamicResource borderline}">
<TextBlock VerticalAlignment="Stretch" x:Name="TextBlockSumme" Text="{Binding Path=KumulierteSummeIstVorvorjahr, Converter={StaticResource numberFormatter}, ConverterParameter='#,0.0 T€', RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type cis:ChildWindow}}}"
/>
</Border>
</DockPanel>
</DataTemplate>
</toolkit:DataGridTemplateColumn.HeaderTemplate>
</toolkit:DataGridTemplateColumn>
Now I want to make a StandartTemplate for this Type because I've got many Colums like this, with only differ in the bindings of the texts in the colums as well as in their headers.
As far I've tried to make a Style for this, but this won't work, I tried to create an usercontrol (but I think it's like taking a sledgehammer to crack a nut).
So any help or hint how to solve this problem would be appreciated.
I don't see why you've rejected the UserControl approach. UserControls are pretty lightweight. They add very little overhead at runtime. They are an extra feature in your project of course, but I usually find that to be an improvement - WPF projects with a small number of large Xaml files are typically hard to maintain.
Far from being a 'sledgehammer', they seem like exactly the right solution here to me.
Add the DataTemplate into the Resources and then access it via a StaticResource
<Window>
<Window.Resources>
<DataTemplate x:Key="MyColumnTemplate">
...
</DataTemplate>
<DataTemplate x:Key="MyColumnTemplateHeader">
...
</DataTemplate>
</Window.Resources>
...
<toolkit:DataGridTemplateColumn x:Name="DataGridTextColumnIstVorvorjahr" IsReadOnly="True" SortMemberPath="SummeIstVorvorjahr"
CellTemplate={StaticResource MyColumnTemplate}
HeaderTemplate={StaticResource MyColumnTemplateHeader}
...
</Window>
If I understand you, you try to bind the same column template with different data and to have different header's content relative with column data. So, you may use "dynamic XAML" (XAML used in C# code - that is dynamic) which allows you to use one template for different data.
Here a simple example.
In C# code we create DataGridTemplateColumn object:
DataGridTemplateColumn tc = new DataGridTemplateColumn();
Then we set the CellTemplate property with template which is created dynamically in special function:
tc.CellTemplate = (DataTemplate)XamlReader.Parse(GetTextCellDataTemplate(someText));
Here is a special function which creates our template:
public static string GetTextCellDataTemplate(string bindingPath)
{
return #"
<DataTemplate
xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""
xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml"" >
<ScrollViewer MaxHeight=""200"" MaxWidth=""250"" VerticalScrollBarVisibility=""Auto"">
<TextBlock Text=""{Binding Path=" + bindingPath + #"}""
TextWrapping=""Wrap"" />
</ScrollViewer>
</DataTemplate>";
}
Now you may send various information in this function as text and get the same template. You may choose the template from the information you want to put in the cell. For this you must write various function which will return various templates.
The same approach can be applied to header template.

Resources