I have 3 different layouts (similar to I guess what you would call Skins but the layouts are hugely different, not just changes to colors and fonts) which I have developed for my application. The layouts are used for displaying the same data, but in a completely different format. Each of these layouts have been constructed within their own Grid.
I want my application to decide which layout to display dynamically based on a string value available at runtime.
What's the best way to get a parent Grid to display a Child Grid dynamically?
I'm trying to find some sort of magical DataTemplate / DataBinding / Templating method but just can't seem to find the best way. Alternatively, should I be looking at a different method of displaying these different layouts? Like an ItemsControl or similar?
Ben
I usually use a ContentControl and DataTrigger to determine what ContentTemplate to use.
For example,
<ContentControl Content="{Binding MyViewModel}">
<ContentControl.Resources>
<DataTemplate x:Key="DefaultTemplate">
<TextBlock Text="DefaultTemplate" />
</DataTemplate>
<DataTemplate x:Key="TemplateA">
<TextBlock Text="Template A" />
</DataTemplate>
<DataTemplate x:Key="TemplateB">
<TextBlock Text="Template B" />
</DataTemplate>
</ContentControl.Resources>
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource DefaultTemplate}" />
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedView}" Value="ViewA">
<Setter Property="ContentTemplate" Value="{StaticResource TemplateA}" />
</DataTrigger>
<DataTrigger Binding="{Binding SelectedView}" Value="ViewB">
<Setter Property="ContentTemplate" Value="{StaticResource TemplateB}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
Related
In my window I have several generic DataTemplates which use DataTriggers to determine data sources and such. This cuts down heavily on xaml duplication in a window with a lot of similar pages. There are TabControls for navigation, with each TabItem housing a ContentControl using one of said templates.
The exception I'm getting is happening while two TabItems on the same TabControl have the same content. I've isolated the problem at the style for a DevExpress GridControl inside the template. The style has DataTriggers which use the tag of the currently selected TabItem to determine both the ItemsSource and ColumnsSource.
The exception: Name:ChangeLogicalParent MSG:Specified element is already the logical child of another element. Disconnect it first. STACK: at System.Windows.FrameworkContentElement.ChangeLogicalParent(DependencyObject newParent)
Below is an abbreviated version of the xaml:
<DataTemplate x:Key="GridControlTemplate">
...
<dxg:GridControl>
<dxg:GridControl.Style>
<Style TargetType="{x:Type dxg:GridControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type TabControl}}, Path=SelectedItem.Tag}"
Value="{StaticResource FirstTag}">
<Setter Property="ColumnsSource" Value="{StaticResource FirstColumns}" />
<Setter Property="ItemsSource" Value="{Binding FirstDataSource}" />
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type TabControl}}, Path=SelectedItem.Tag}"
Value="{StaticResource SecondTag}">
<Setter Property="ColumnsSource" Value="{StaticResource SecondColumns}" />
<Setter Property="ItemsSource" Value="{Binding SecondDataSource}" />
</DataTrigger>
...
<TabControl>
<TabItem Header="header 1" Tag="{StaticResource FirstTag}">
<ContentControl ContentTemplate="{StaticResource GridControlTemplate}" Content="{Binding}" />
</TabItem>
<TabItem Header="header 2" Tag="{StaticResource SecondTag}">
<ContentControl ContentTemplate="{StaticResource GridControlTemplate}" Content="{Binding}" />
</TabItem>
</TabControl>
Mind you, this works fine for other groups of TabItems sharing identical ContentTemplates. This TabControl scheme works fine when the GridControl's style doesn't have these DataTriggers. With that in mind, might this be a DevExpress-specific issue?
Your GridControl is part of a style held as a static resource. By default resources are shared, that is, the same instance is used by anything that uses the resource. Since both your tabs are using it as their control template, the same GridControl is trying to be made a child of each tab.
You can tell WPF to provide different instances via the x:Shared attribute:
<DataTemplate x:Key="GridControlTemplate" x:Shared="false">
So I have 3 levels of nodes in my TreeView:
A single Root Node (should display Image1.png)
Some second level nodes (should display Image2.png)
Each second level node has some third-level nodes (should display Image3.png)
I'm trying to use a DataTemplate to dynamically assign the display image to each node, depending upon its level. Since level is not as easily available in WPF as it is in WinForms, I simply resorted to using Tag property of TreeViewItems to store their level. Then I wrote this following Style for assigning display images:
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Name="img" Width="20" Height="20" Stretch="Fill">
<Image.Style>
<Style TargetType="{x:Type Image}">
<Style.Triggers>
<DataTrigger Binding="{Binding Tag}" Value="0">
<Setter Property="Source" Value="Icons\Image1.png"/>
</DataTrigger>
<DataTrigger Binding="{Binding Tag}" Value="1">
<Setter Property="Source" Value="Icons\Image2.png"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
<TextBlock VerticalAlignment="Center" Text="{Binding}" Margin="5,0" />
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
Yes, you guessed it; it doesn't work. Can someone please identify where the problem lies? Or am I doing it just the wrong way?
Right now, your {Binding Tag} will try to find the Tag property of the TreeViewItem's DataContext, not the DependencyProperty. And since, i'm guessing, there is no Tag property in the DataContext, it won't work. If you look at your output Windows in VS, you should see binding errors all over the place.
What you need to do is add the relative source to your binding so it looks at the TreeViewItem instead of the DataContext. Here is an example :
<DataTrigger Binding="{Binding Path=Tag, RelativeSource={RelativeSource AncestorType={x:Type TreeViewItem}}}" Value="0">
here i want to give alternate color white and grey to grid row . i hv done many try but i can not do styling of grid .the code is here
<Style TargetType="{x:Type wpftoolkit:DataGrid}">
<Setter Property="Margin" Value="0" />
<Setter Property="BorderBrush" Value="#A6A6A6" />
<Setter Property="BorderThickness" Value="0,1,0,0"/>
<Setter Property="Background" Value="{StaticResource GridBgBrush}" />
<Setter Property="RowBackground" Value="White" />
<Setter Property="AlternatingRowBackground" Value="#FFF3F6FA" />
<Setter Property="GridLinesVisibility" Value="Horizontal" />
<Setter Property="HorizontalGridLinesBrush" Value="Transparent" />
<Setter Property="RowHeaderWidth" Value="0" />
</Style>
here StaticResource GridBgBrush define earlier on this file as`
plz give proper solution .thanks in advance.
Make sure that your style is either defined within the resources section of your XAML file (after your GridBgBrush, so that it can reference it), or in a ResourceDictionary in your App somewhere making it accessible from anywhere. Without seeing more, I can't tell you where your problem is coming from. That is the correct way to define your style and I have several examples of this working as expected if you're interested in seeing them.
Another thing to note in case you didn't know, is that DataGrid (along with DatePicker) was introduced into WPF v4.0. This makes the WPF Toolkit (at least for the purposes of the DataGrid) unnecessary if you can target that version. After saying that, I suppose there's the slight chance that if you weren't aware you were using one and then styling the other, your style wouldn't work.
<XmlDataProvider x:Key="myData" Source="Data.xml" IsAsynchronous="True" />
<Style TargetType="{x:Type DataGrid}" x:Key="myStyle">
<Setter Property="AlternatingRowBackground" Value="Red"/>
</Style>
<Grid>
<DataGrid ItemsSource="{Binding Source={StaticResource myData}, XPath=persons/person}" AutoGenerateColumns="False" Style="{StaticResource myStyle}">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding XPath=firstname}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding XPath=lastname}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
You need to set AlternationCount property too.
I am trying to switch the content of a contentpresenter based on a datatrigger.I want to display a usercontrol in the contentpresenter.content, if i have a value set or else i need to display an error message.But the binding on my datatrigger fails stating that the property is not found.I cant get the datacontext to inherit for the datatrigger checking.I can make it work by using the commented out code.But i am confused why it doesn't work the normal way.
<ContentPresenter.Style>
<Style TargetType="{x:Type ContentPresenter}">
<Setter Property="Content" Value="{Binding UC}"/>
<Style.Triggers>
<!--<DataTrigger Binding="{Binding DataContext.HasValue,RelativeSource={RelativeSource AncestorType={x:Type ContentPresenter}}}" Value="false">
<Setter Property="Content" Value="No preview"/>
</DataTrigger>-->
<DataTrigger Binding="{Binding HasValue}" Value="false">
<Setter Property="Content" Value="No value"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentPresenter.Style>
</ContentPresenter>
If you want to use triggers to display UserControl, you should use ContentControl not ContentPresenter.
I prefer to use ContentPresenter for CustomControls and When I am using the UserControl for views of Custom Data Types in my system and Allow to give dynamic behavior.
Example: To switch templates for ContentPresenter you need to set ContentTemplateSelector like this
<ContentPresenter Content="{Binding MyContent}"
ContentTemplate="{Binding MyContentTemplate}"
ContentTemplateSelector="{Binding MyContentTemplateSelector}"/>
MyContent, MyContentTemplate & MyContentTemplateSelector are Dependency Properties and can be binded wherever you are using its instance.
READ :
Usage of ContentPresenter
What is the difference between ContentControl and ContentPresenter
The binding mentioned in the question won't work as
ContentPresenter’s DataContext is automatically set to the value of
its Content property, while ContentControl’s DataContext is not.
Bindings are resolved relatively to the value of the DataContext property. If you declare a binding on the ContentPresenter, the moment its content is set, the binding would be re-evaluated.
ContentControl.Content Property can be changed on any trigger based on your requirement. If you want to use it to change on PropertyChanged Event of a property of ViewModel, DataTrigger can be used by binding it with a DataTemplate with UserControl instance in it or using static resource of that UserControl.
<ContentControl>
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Value="{StaticResource UnSelectedDataTemplate}" Property="ContentTemplate" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsSelected}" Value="True">
<Setter Value="{StaticResource SelectedDataTemplate}" Property="ContentTemplate" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentContro.Style>
</ContentControl>
READ How to use triggers for content template, more details here
Difference in DataTemplate and StaticResource scope is DataTemplate creates a new instance of the template every time its applied.
Whereas, StaticResource is using the same instance of UserControl again (Static Instance).
You can also use EventTriggers to change content base don Control Events like MouseOver etc.
Alternate approach
Very similar to the above with slight difference. Defining as a data template in resources. Triggering for the content change is essentially identical.
...in <x.Resources /> tag:
<DataTemplate x:Key="DesignerTemplate" DataType="{x:Type vm:SolutionViewModel}">
<vw:SolutionDesignerView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:SolutionViewModel}">
<ContentControl Content="{Binding }">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsLoaded}" Value="True">
<Setter Property="ContentTemplate" Value="{StaticResource DesignerTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
...then:
<ContentControl Content="{Binding Solution}" />
I usually use trigger Like this...
<UserControl>
<UserControl.Resources>
<DataTemplate x:Key="normalTemplate" >
<!-Fav UserControl->
</DataTemplate >
<DataTemplate x:Key="overWriteTempalte">
<!-Fav UserControl-> </DataTemplate>
</UserControl.Resources>
<ContentPresenter x:Name="ContentField"
Content="{Binding}"
ContentTemplate="{StaticResource ResourceKey=normalTemplate}" />
<UserControl.Triggers>
<DataTrigger Binding="{Binding Path=MyProperty}" Value="True">
<Setter TargetName="ContentField" Property="ContentTemplate" Value="{StaticResource ResourceKey=overWriteTempalte}" />
</DataTrigger>
</UserControl.Triggers>
</UserControl>
If Bindings are a problem Use Snoop to Detect binding errors
let's say we have simple data class:
public class Ex {
public string Prop1 {...} // notify property
public string Prop2 {...} // notify property
}
and an ObservableCollection of objects of this class. I want to have this collection displayed in a ListView with seperated DataTemplated which is distinguished by Ex.Prop2 (if it is null or empty then template01 is used, otherwise template02). This property can be changed in runtime so simple "trick" with ListView.ItemTemplateSelector does not work :(
How to achieve this functionality? Is it possible to achieve it any other way than listening to NotifyPropertyChanged on each object of the collection and than changing manually the template?
Thanks for your help.
Below piece of code which I already have:
<ListView x:Name="lstTerms"
ItemsSource="{Binding Game.Words}"
HorizontalContentAlignment="Stretch"
Grid.IsSharedSizeScope="True">
<ListView.ItemContainerStyle>
<Style>
<Setter Property="Control.Padding" Value="0" />
</Style>
</ListView.ItemContainerStyle>
<!-- checks if element is null or its Prop2 is null or empty. If so, uses NullTemplate -->
<ListView.ItemTemplateSelector>
<local:MySelectTemplate
NormalTemplate="{StaticResource NormalItemTemplate}"
NullTemplate="{StaticResource NullItemTemplate}" />
</ListView.ItemTemplateSelector>
</ListView>
Instead of using a TemplateSelector, you can have a single DataTemplate containing two Grids, which switch visibility dependent on the property values.
Here is an example:
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid Background="LightBlue" Name="normalGrid">
<Grid.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Prop1}" Value="{x:Null}">
<Setter Property="Grid.Visibility" Value="Hidden"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<TextBlock Text="{Binding Prop1}"></TextBlock>
</Grid>
<Grid Background="Green" Name="nullGrid">
<Grid.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=normalGrid, Path=Visibility}" Value="Visible">
<Setter Property="Grid.Visibility" Value="Hidden"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<TextBlock Text="{Binding Prop2}"></TextBlock>
</Grid>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
Obviously you could replace the TextBlock elements with UserControls representing your two DataTemplates.
If you want, you can also remove the need for the bulky Styles by binding Grid.Visibility to a property (named, for example, IsVisible) on your ViewModel and using a VisibilityConverter.
I usually just use a ContentControl which changes its ContentTemplate based on a DataTrigger. DataTriggers respond to the value getting changed, while DataTemplateSelectors do not
<Style x:Key="SomeStyleKey" TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource DefaultTemplate}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Prop2}" Value="{x:Null}">
<Setter Property="ContentTemplate" Value="{StaticResource NullTemplate}" />
</DataTrigger>
<DataTrigger Binding="{Binding Prop2}" Value="">
<Setter Property="ContentTemplate" Value="{StaticResource NullTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
...
<ListView.ItemTemplate>
<DataTemplate>
<ContentControl Style="{StaticResource SomeStyleKey}" />
</DataTemplate>
</ListView.ItemTemplate>
You could also use a Converter that returns String.IsNullOrEmpty(value) if you wanted a single DataTrigger