I have a listbox that contains a control chooser I'm trying to develop. Basically, the datasource is XML and I want to read the current context to decide which element control to display.
For this purpose I want to grab the XmlDataProvider with the current item's context, and evaluate that XML. Within XAML I would write {Binding Path=#label} to retrieve a label attribute from the curretn XML element. From code behind, I cant even figure where to get this XML, as it's passed to the class by the list control to this control, but not as an accessible property so far as I can find.
Anyway getting #label isn't sufficient; I want the XmlElement object in class ControlChooser, instantiated below.
<ListBox
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding XPath=*[not(self::units)]}"
>
<ListBox.ItemTemplate>
<DataTemplate>
<W3V:ControlChooser/>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemContainerStyle>
<!-- Force the items to fill all available space. -->
<Style TargetType="ListBoxItem">
<Setter
Property="VerticalContentAlignment"
Value="Stretch"
/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
Or, if you can suggest another way to get the job (switching what control is displayed) done....
You can use multiple DataTemplates to style the different element types. If you are familiar with XSLT, DataTemplates are the functional equivalent of xsl:template.
Example to style:
<Window x:Class="ZoomingScrollViewer.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<XmlDataProvider x:Key="testData" XPath="/Contacts/*">
<x:XData>
<Contacts xmlns="">
<Person Name="John" />
<Person Name="Robby" />
<Business>
<ContactName>Jemma</ContactName>
<BusinessName>Ars</BusinessName>
</Business>
<Business>
<BusinessName>The other one</BusinessName>
</Business>
</Contacts>
</x:XData>
</XmlDataProvider>
</Window.Resources>
<Grid>
<ListBox ItemsSource="{Binding Source={StaticResource testData}}">
<ListBox.Resources>
<DataTemplate DataType="Person">
<TextBlock Text="{Binding XPath=#PersonName}" />
</DataTemplate>
<DataTemplate DataType="Business">
<StackPanel>
<TextBlock Text="{Binding XPath=ContactName}" />
<TextBlock Text="{Binding XPath=BusinessName}" />
</StackPanel>
</DataTemplate>
</ListBox.Resources>
</ListBox>
</Grid>
</Window>
If you wanted to select the DataTemplate based off of an attribute, rather than an element, you can use a DataTemplateSelector to run arbitrary code as explained in this question.
I found "Different views / data template based on member variable" as the closest to an answer. With the idea there, I could make a standalone control that made sense to me. This is the key bit:
<DataTrigger Binding="{Binding XPath=#kind}" Value="Number">
<Setter Property="ContentTemplate" Value="{StaticResource SmallInt}" />
</DataTrigger>
Here's the whole control:
<ContentControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:W3V="clr-namespace:W3.Views"
x:Class="W3.Views.ControlChooser">
<ContentControl.Resources>
<DataTemplate x:Key="StringChoice" >
<W3V:ComboView />
</DataTemplate>
<DataTemplate x:Key="SmallInt" >
<W3V:SpinView />
</DataTemplate>
</ContentControl.Resources>
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource StringChoice}" />
<Style.Triggers>
<DataTrigger Binding="{Binding XPath=#kind}" Value="Number">
<Setter Property="ContentTemplate" Value="{StaticResource SmallInt}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
Related
I have the following piece of XAML that creates a treeview and populates it with TreeViewItems.
The Converter simply takes the name and spits back a StackPanel with 2 different coloured strings.
The only way I've found that I can actually use that stackpanel visually is by setting it in the header of a TreeViewItem, this however doesn't work optimally as a TreeViewItem is created programatically already.
The result is that I can't click the label(header) but it looks fancy, however I discovered that there's a big space in front of the label that I can click, which must then be from the generated TreeViewItem.
I really need the label to contain a stackpanel of 2 textblocks, is there a solution for this?
<UserControl.Resources>
<XmlDataProvider x:Key="MyXmlProvider" Source="LogansTest.xml" XPath="/Items"/>
<customScripts:NameGeneration x:Key="NameGeneration"/>
<HierarchicalDataTemplate x:Key="NodeTemplate" ItemsSource="{Binding XPath=./*}">
<TreeViewItem x:Name="nodetext"/>
<HierarchicalDataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=NodeType}" Value="Element">
<Setter TargetName="nodetext" Property="Header" Value="{Binding Converter={StaticResource NameGeneration}}"/>
</DataTrigger>
</HierarchicalDataTemplate.Triggers>
</HierarchicalDataTemplate>
</UserControl.Resources>
EDIT
Following mm8's suggestion I've edited my code, I've uploaded my XAML in its entirety, it is no longer generating the tree, I'm pretty new to XAML so I can't see what I'm doing wrong, heh.
Ran a few tests, the converter isn't being called and the treeview is just empty where before it had all the nodes of the XML file
<UserControl x:Class="XmlOutline.OutlineWindowControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:vsshell="clr-namespace:Microsoft.VisualStudio.Shell;assembly=Microsoft.VisualStudio.Shell.15.0"
xmlns:customScripts="clr-namespace:XmlOutline.CustomScripts"
Background="{DynamicResource {x:Static vsshell:VsBrushes.WindowKey}}"
Foreground="{DynamicResource {x:Static vsshell:VsBrushes.WindowTextKey}}"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
Name="MyToolWindow">
<UserControl.Resources>
<XmlDataProvider x:Key="MyXmlProvider" Source="LogansTest.xml" XPath="/Items"/>
<customScripts:NameGeneration x:Key="NameGeneration"/>
</UserControl.Resources>
<Grid x:Name="TreeGrid" DataContext="MyXmlProvider">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TreeView Name="TreeItems" Visibility="Hidden"
ItemsSource="{Binding Source={StaticResource MyXmlProvider}}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
VirtualizingStackPanel.IsVirtualizing="False"
VirtualizingStackPanel.VirtualizationMode="Standard"
Background="#252525"
SelectedItemChanged="TreeView_OnSelectedItemChanged">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding XPath=./*}"/>
</TreeView.ItemTemplate>
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=NodeType}" Value="Element">
<Setter Property="Header" Value="{Binding Converter={StaticResource NameGeneration}}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
</Grid>
</UserControl>
EDIT
This is the code that sets the new datasource whenever a new XML document is opened.
var provider = new XmlDataProvider()
{
Source = new Uri(gotFocus.Document.Path + gotFocus.Document.Name),
XPath = "./*"
};
OutlineWindowInstance.TreeItems.DataContext = provider;
By the way, the entire GIT repo can be found here:
https://github.com/LoganLabster/VsXmlOutline
You are not supposed to create another TreeViewItem container in an HierarchicalDataTemplate. Try to define an ItemContainerStyle that sets the Header property:
<TreeView ItemsSource="{Binding Source={StaticResource MyXmlProvider}}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding XPath=./*}" />
</TreeView.ItemTemplate>
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=NodeType}" Value="Element">
<Setter Property="Header" Value="{Binding Converter={StaticResource NameGeneration}}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
Thanks to #mm8 I managed to find the answer, it was really quite simple once I figured it out (which took way too long, doh).
Rather than set the StackPanel to the treeviewitem header, I built the StackPanel in XAML and set the values there one at a time, and voila, it worked.
<UserControl x:Class="XmlOutline.OutlineWindowControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:vsshell="clr-namespace:Microsoft.VisualStudio.Shell;assembly=Microsoft.VisualStudio.Shell.15.0"
xmlns:customScripts="clr-namespace:XmlOutline.CustomScripts"
Background="{DynamicResource {x:Static vsshell:VsBrushes.WindowKey}}"
Foreground="{DynamicResource {x:Static vsshell:VsBrushes.WindowTextKey}}"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
Name="MyToolWindow">
<UserControl.Resources>
<XmlDataProvider x:Key="MyXmlProvider" Source="LogansTest.xml" XPath="/Items"/>
<customScripts:NameGeneration x:Key="NameGeneration"/>
</UserControl.Resources>
<Grid x:Name="TreeGrid" DataContext="MyXmlProvider">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TreeView Name="TreeItems" Visibility="Hidden"
ItemsSource="{Binding}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
VirtualizingStackPanel.IsVirtualizing="False"
VirtualizingStackPanel.VirtualizationMode="Standard"
Background="#252525"
SelectedItemChanged="TreeView_OnSelectedItemChanged"
TreeViewItem.Expanded="TreeViewItem_Expanded"
TreeViewItem.Collapsed="TreeViewItem_Collapsed">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate x:Name="myTest" ItemsSource="{Binding XPath=./*}">
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="Title" Foreground="LightSkyBlue" FontSize="14"/>
<TextBlock x:Name="SubTitle" Foreground="YellowGreen" FontSize="13"/>
</StackPanel>
<HierarchicalDataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=NodeType}" Value="Element">
<Setter TargetName="Title" Property="Text" Value="{Binding Path=Name}"/>
<Setter TargetName="SubTitle" Property="Text" Value="{Binding Converter={StaticResource NameGeneration}}" />
</DataTrigger>
</HierarchicalDataTemplate.Triggers>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
</UserControl>
at the moment in order to fix a bug from telerik, my ItemsSource must be pointing to the viewmodel I'm currently working with.
Relationship.xaml
<UserControl.Resources>
<Client:PersonViewModel x:Key="MyViewModel"/>
</UserControl.Resources>
Where it's used.
<Telerik:GridViewComboBoxColumn Header="Relationship"
ItemsSource="{Binding GridRelationshipTypes, Mode=TwoWay, Source={StaticResource MyViewModel}}"
DataMemberBinding="{Binding RelationshipType}"
SelectedValueMemberPath="Id"
DisplayMemberPath="Name"
IsReadOnly="False"/>
I have four other view models this logic needs to be applied to. I don't want to create 5 different UserControls for such a small thing. I'm wondering if I can create a method such that it'll check what the current viewmodel type is and will use the corresponding viewmodel.
PseudoCode - ViewModelTypes is an enum.
public void StaticResourcToUse(ViewModelTypes viewModelType)
{
if (viewModelType == ViewModelTypes.PersonViewModel)
use personviewmodel resources
if (viewModelType == ViewModelTypes.BusinessViewModel)
use businessViewModel resources
}
If I understand correctly what you want is switch your view based on view model.
Use a ContentControl to display the data, and swap out the ContentTemplate in a trigger based on the property that changes.
Here's an example in Rachel Lim's blog that swaps a template based on a bound property:
<DataTemplate x:Key="CarTemplate" TargetType="{x:Type local:YourViewModel}">
<TextBlock Text="I'm a Car" />
</DataTemplate>
<DataTemplate x:Key="TrackTemplate" TargetType="{x:Type local:YourViewModel}">
<TextBlock Text="I'm a Track" />
</DataTemplate>
<DataTemplate DataType="{x:Type local:YourViewModel}">
<ContentControl Content="{Binding }">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource CarTemplate}" />
<Style.Triggers>
<DataTrigger Binding="{Binding YourType}" Value="Track">
<Setter Property="ContentTemplate" Value="{StaticResource TrackTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
I have a problem with the user control I created. This control consists of a search textbox and a treeview. The treeview shows different data templates for different node types. So I created the usercontrol with a dependency property of type datatemplate which can be bound when using my control. Inside the control, the treeview binds to the dependency property. But sadly the treeviewtemplate selector doesn't get called.
<UserControl x:Class="yyy.yyy.yyy.UI.UserControls.SearchableTreeView.SearchableTreeView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:behaviours="clr-namespace:yyy.yyy.yyy.UI.Behaviours;assembly=yyy"
mc:Ignorable="d"
x:Name="parent"
d:DesignHeight="300" d:DesignWidth="300">
<DockPanel DataContext="{Binding ElementName=parent}">
<TextBox DockPanel.Dock="Top" Margin="5" Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}"/>
<TreeView DockPanel.Dock="Top" Margin="5" ItemsSource="{Binding TreeViewItems}" ItemTemplateSelector="{Binding TreeViewTemplateSelector}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="behaviours:TreeViewItemBehaviour.IsBroughtIntoViewWhenSelected" Value="true"/>
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="Visibility" Value="Visible"/>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
<DataTrigger Binding="{Binding Path=IsVisible}" Value="false">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
</DockPanel>
The code behind with the dependency property looks like that:
public partial class SearchableTreeView : UserControl
{
public SearchableTreeView()
{
InitializeComponent();
}
public static readonly DependencyProperty TreeViewTemplateSelectorProperty = DependencyProperty.Register(
"TreeViewTemplateSelector", typeof (DataTemplateSelector), typeof (SearchableTreeView), new PropertyMetadata(default(DataTemplateSelector)));
public DataTemplateSelector TreeViewTemplateSelector
{
get { return (DataTemplateSelector) GetValue(TreeViewTemplateSelectorProperty); }
set { SetValue(TreeViewTemplateSelectorProperty, value); }
}
}
And the usercontrol is used in a xaml like that:
<searchableTreeView:SearchableTreeView TreeViewTemplateSelector="{StaticResource TreeViewFieldTemplateSelector}"/>
Where the TreeViewFieldTemplateSelector is a class of type datatemplateselector, which allready worked before i startet to create a usercontrol out of the searchable treeview.
Does anybody know what I'm doing wrong? Or is it not possible to bind a datatemplateselector directly to a treeview?
Thanks
Manuel
You are complicating your system by using a DataTemplateSelector. While it is true that these objects were created for this purpose, there is a much easier way to achieve your requirements. Basically, if you declare a DataTemplate for each data type without specifying the x:Key values, then they will be applied implicitly to all objects of the correct type:
<DataTemplate DataType="{x:Type YourPrefix:YourDataType">
...
</DataTemplate>
<DataTemplate DataType="{x:Type YourPrefix:YourOtherDataType">
...
</DataTemplate>
<DataTemplate DataType="{x:Type YourPrefix:SomeOtherDataType">
...
</DataTemplate>
Now, if you put items of these data types into a collection and data bind that to a collection control, then you'll see your various items rendered as expected, but without the complications of the DataTemplateSelector.
UPDATE >>>
Ok, then try this instead... first remove the DataContext="{Binding ElementName=parent}" setting and then add a RelativeSource Binding for the TreeViewTemplateSelector property:
<DockPanel>
<TextBox DockPanel.Dock="Top" Margin="5" Text="{Binding SearchText,
UpdateSourceTrigger=PropertyChanged}"/>
<TreeView DockPanel.Dock="Top" Margin="5" ItemsSource="{Binding TreeViewItems}"
ItemTemplateSelector="{Binding TreeViewTemplateSelector, RelativeSource={
RelativeSource AncestorType={x:Type YourPrefix:SearchableTreeView}}}">
<TreeView.ItemContainerStyle>
...
</TreeView.ItemContainerStyle>
</TreeView>
</DockPanel>
I have the following resource:
<Window.Resources>
<Style x:Key="TopKey" TargetType="local:CustomType">
<Style.Resources>
<DataTemplate x:Key="NestedKey">
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
</Style.Resources>
</Style>
</Window.Resources>
Then I have the following declaration:
<local:CustomType ItemTemplate="{StaticResource TopKey.NestedKey}"/>
Of course the above line doesn't compile and I don't know how to resolve this...
Putting a Resource in a ResourceDictionary of a FrameworkElement means that you don't want the Resource to be accessible outside this FrameworkElement (though you could get around it in code behind).
In your case NestedKey is in the wrong ResourceDictionary. Try something like this:
<Window.Resources>
<DataTemplate x:Key="NestedKey">
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
<Style x:Key="TopKey" TargetType="local:CustomType">
<!-- here I can use {StaticResource NestedKey} -->
</Style>
</Window.Resources>
<!-- in the same window I can use: -->
<local:CustomType ItemTemplate="{StaticResource NestedKey}"/>
You could also define a new Style that is based on the TopKey Resource, thus gaining access to it's ResourceDictionary (but that is a workaround to something you can do better)
<local:CustomType>
<local:CustomType.Style>
<Style BasedOn={StaticResource TopKey} TargetType="local:CustomType">
<!-- here I can use {StaticResource NestedKey} -->
</Style>
</local:CustomType.Style>
</local:CustomType>
simply do this
<Window.Resources>
<DataTemplate x:Key="NestedKey">
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
<Style x:Key="TopKey" TargetType="local:CustomType">
<Setter Property="ItemTemplate" Value="{StaticResource NestedKey}" />
</Style>
</Window.Resources>
<local:CustomType Style="{StaticResource TopKey}" />
hope that helps
Can you please tell me how to change content template when, in WPF, when it is clicked on different Menu Item headers. I've defined user control that I can put it as a template.
For example: Menu Items are: Home, Players, Team . when i click on Home I want that specific template in my Content Control to pop up, tehen when I click on Players I want another template (list of players) to pop in Content Control as template.
How to do that with triggers in XAML?
Thank you very much :)
You can use a ContentControl to host whatever your content will be, and set the ContentControl.ContentTemplate based on how you want to draw your content.
As a very basic example,
<ContentControl x:Name="MyContentControl">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding }" Value="Home">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<local:MyHomeUsercontrol />
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding }" Value="Players">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<local:MyPlayersUsercontrol />
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding }" Value="Team">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<local:MyTeamUsercontrol />
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style>
</ContentControl.Style>
</ContentControl>
And on MenuItem.Click
MyContentControl.Content = "Home"; // or "Players" or "Team"
In this example I'm using a string for the ContentControl.Content, however if you were to use a class object such as a HomeViewModel or PlayersViewModel, your XAML could be simplified to use implicit data templates, which are templates that automatically get used whenever WPF tries to draw a specific class
<Window.Resources>
<DataTemplate DataType="{x:Type HomeViewModel}">
<local:MyHomeUserControl />
</DataTemplate>
<DataTemplate DataType="{x:Type PlayersViewModel}">
<local:MyPlayersUserControl />
</DataTemplate>
<DataTemplate DataType="{x:Type TeamViewmModel}">
<local:MyTeamUserControl />
</DataTemplate>
</Window.Resources>
<ContentControl x:Name="MyContentControl" />
and
MyContentControl.Content = new HomeViewModel();
How about using a tab control with tab placement at the top? it would be much cleaner that way..
Edit; Example:
<TabControl>
<TabItem>
<TabItem.Header>
<TextBlock Text="Football header!"/>
</TabItem.Header>
<TabItem.Content>
<Button Content="Push for football!"/>
</TabItem.Content>
</TabItem>
</TabControl>
this might help also; http://www.switchonthecode.com/tutorials/the-wpf-tab-control-inside-and-out