How can I have recursive Data Template for my Custom Control - wpf

I have a Custom Control, which is basically a kind of a TreeView. Now the thing is I need to have any level of detail in my TreeView control so I came up with the following data template
I have the following Generic.xaml
<DataTemplate x:Key="treetemplate">
<StackPanel>
<TextBlock Text="{Binding Label}" ></TextBlock>
<ItemsControl ItemsSource="{Binding Children}" ItemTemplate="{DynamicResource treetemplate}" />
</StackPanel>
</DataTemplate>
<ControlTemplate TargetType="{x:Type local:CustomControl1}" x:Key="testkey">
<ControlTemplate.Resources>
<local:AnythingToListConverter x:Key="anyconv"></local:AnythingToListConverter>
</ControlTemplate.Resources>
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ItemsControl ItemsSource="{Binding Root, Converter={StaticResource anyconv}}" ItemTemplate="{StaticResource treetemplate}" />
</Border>
</ControlTemplate>
<Style TargetType="{x:Type local:CustomControl1}">
<Setter Property="Template" Value="{StaticResource testkey}" />
</Style>
Here is my custom control constructor
this.Root = new Node();
this.Root.Label = "Root";
this.Root.Children = new List<Node>();
this.Root.Children.Add(new Node(){Label="Child1"});
this.DataContext = this;
And this is how control looks like
Here is what I think the problem is
For the Recursive call of same template, I am using DynamicResource. Which never worked on my and the actual resource never got called. If I change that to StaticResource, it will not compile, because it won't see itself. How do I fix it ?
Full solution can be downloaded here.

What you need is HierarchicalDataTemplate. It's designed to pinpoint exactly this scenario.
Here is how you use it:
<HierarchicalDataTemplate ItemsSource="{Binding Children}"
DataType="{x:Type local:Node}">
<TextBlock Text="{Binding Label}" ></TextBlock>
</HierarchicalDataTemplate>
Note that using this template, we have to set its DataType to the Type of your data item (in this case I guess it's Node). Also I guess you don't need any custom control for this, just use the default TreeView with this template defined as some resource like this:
<TreeView ItemsSource="{Binding Root, Converter={StaticResource anyconv}}">
</TreeView>
If you still want to keep your code, try just replacing your ItemsControl with TreeView or some HeaderedItemsControl. The HierarchicalDataTemplate is used only for HeaderedItemsControl (TreeView is also just a kind of HeaderedItemsControl).

Related

Stretch WPF ListView Template layout [duplicate]

I want to have the ListItems to extend with their orange background the full width of the Listbox.
Currently they are only as wide as the FirstName + LastName.
I've set every element I can to: HorizontalAlignment="Stretch".
I want the background of the ListboxItems to expand as the user stretches the Listbox so I don't want to put in absolute values.
What do I have to do so that the background color of the ListBoxItems fill the width of the ListBox?
<Window x:Class="TestListBoxSelectedItemStyle.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestListBoxSelectedItemStyle"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<local:CustomerViewModel x:Key="TheDataProvider"/>
<DataTemplate x:Key="CustomerItemTemplate">
<Border CornerRadius="5" Background="Orange" HorizontalAlignment="Stretch" Padding="5" Margin="3">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Stretch" Width="Auto">
<TextBlock HorizontalAlignment="Stretch">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} {1}">
<Binding Path="FirstName"/>
<Binding Path="LastName"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</StackPanel>
</Border>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox ItemsSource="{Binding Path=GetAllCustomers, Source={StaticResource TheDataProvider}}"
ItemTemplate="{StaticResource CustomerItemTemplate}"/>
</Grid>
</Window>
I found another solution here, since I ran into both post...
This is from the Myles answer:
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter>
</Style>
</ListBox.ItemContainerStyle>
This worked for me.
I'm sure this is a duplicate, but I can't find a question with the same answer.
Add HorizontalContentAlignment="Stretch" to your ListBox. That should do the trick. Just be careful with auto-complete because it is so easy to get HorizontalAlignment by mistake.
If your items are wider than the ListBox, the other answers here won't help: the items in the ItemTemplate remain wider than the ListBox.
The fix that worked for me was to disable the horizontal scrollbar, which, apparently, also tells the container of all those items to remain only as wide as the list box.
Hence the combined fix to get ListBox items that are as wide as the list box, whether they are smaller and need stretching, or wider and need wrapping, is as follows:
<ListBox HorizontalContentAlignment="Stretch"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
(credits for the scroll bar idea)
Since the border is used just for visual appearance, you could put it into the ListBoxItem's ControlTemplate and modify the properties there. In the ItemTemplate, you could place only the StackPanel and the TextBlock. In this way, the code also remains clean, as in the appearance of the control will be controlled via the ControlTemplate and the data to be shown will be controlled via the DataTemplate.
The fix for me was to set property HorizontalAlignment="Stretch" on ItemsPresenter inside ScrollViewer..
Hope this helps someone...
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBox">
<ScrollViewer x:Name="ScrollViewer" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Foreground="{TemplateBinding Foreground}" Padding="{TemplateBinding Padding}" HorizontalAlignment="Stretch">
<ItemsPresenter Height="252" HorizontalAlignment="Stretch"/>
</ScrollViewer>
</ControlTemplate>
</Setter.Value>
</Setter>
I also had the same problem, as a quick workaround, I used blend to determine how much padding was being added. In my case it was 12, so I used a negative margin to get rid of it. Now everything can now be centered properly

How to set ItemsSource of a ComboBox, if it was present in an ItemsCotrol

ComboBox items were not displaying, if we try to keep that combobox in an ItemsCotrol. Please click here for understanding my requirement
My requirement is to keep a combobox in an ItemsControl, so that the ItemsControl qill be having 5 Comboboxes in it and each combox will be having a collection of items which we can select. So for that i tried with the below code and able to get the comboboxes in the ItemsControl, but the comboboxes collection is getting filled, any suggestions or workaround please..
<xamDataPresenter:Field Label="Reqs" BindingType="Unbound" Row="0" Column="4">
<xamDataPresenter:Field.CellValuePresenterStyle>
<Style TargetType="{x:Type xamDataPresenter:CellValuePresenter}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type xamDataPresenter:CellValuePresenter}">
<ItemsControl Name="I" ItemsSource="{Binding Path=DataItem.CollectionCount}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Path=DataItem.Collection}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</xamDataPresenter:Field.CellValuePresenterStyle>
</xamDataPresenter:Field>
Ok, I've written up the most basic of ItemsControl to try and explain how these things work, which you can hopefully adapt for whatever your using for your dataitems.
So in your window's resources i've created a datatemplate. This respresents a repeating step and will be based on a DataItem. In this case my DataItem has 2 properties: DataItemProperty(string) and SelectedItem. The SelectedItem will have the same DataType of whatever it is your planning on showing in the combobox.
<DataTemplate x:Key="StepTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<TextBlock Text="{Binding Path=DataItemProperty}" Grid.Column="0"/>
<ComboBox Grid.Column="1" ItemsSource="{Binding Path=DataContext.ItemsToSelectFrom, Mode=OneWay, RelativeSource={RelativeSource AncestorType=Window}}"
SelectedItem="{Binding Path=SelectedItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</DataTemplate>
The combobox in this example will be getting a list of available options from the code behind/viewmodel and not the DataItem, but when you select something it updates the SelectedItem property on the DataItem.
Then to show your items:
<ItemsControl
Focusable="False"
ItemTemplate="{StaticResource StepTemplate}"
ItemsSource="{Binding Path=Steps, Mode=OneWay}" />
So Steps is a property in my codebehind/viewmodel that will determine how many 'rows' get displayed.
The itemsControl allows you to add repeating sets of data easily, without having to write the same xaml multiple times.
Hope that helps?

Showing or hiding a control in WPF template based on bindings

I am new to WPF binding/templating. I have some basic questions about a templated TabControl I have as below :
<TabControl x:Name="tcTabs" ItemsSource="{Binding Rooms, UpdateSourceTrigger=PropertyChanged}" Grid.Row="1" Margin="5" BorderThickness="1" IsSynchronizedWithCurrentItem="True">
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding Name}" />
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="130"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="22"/>
</Grid.RowDefinitions>
<ListBox Grid.Row="0" Grid.Column="0" BorderThickness="0" ItemsSource="{Binding Messages}" DisplayMemberPath="Raw" />
<ListBox Grid.Row="0" Grid.Column="1" BorderThickness="1,0,0,0" BorderBrush="#FFBBBBBB" ItemsSource="{Binding Users}" DisplayMemberPath="Nick" />
<TextBox Grid.Row="1" Grid.ColumnSpan="2" BorderThickness="0,1,0,0" BorderBrush="#FFBBBBBB" Height="22" />
</Grid>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
The TabControl contains in each tab 2 list boxes and a textbox. One of the listboxes contains user names is is not necessary all the time.
There are 3 kinds of tabs, Server tabs, room tabs and private tabs. In private and server tabs the user list should not exist or be hidden.
I have an enum on the bound room object :
public enum IRCRoomType
{
Server,
Channel,
Private
}
How do I automatically hide the user list based on the enum, I have seen samples of 2 approaches, the binding on visibility with a converter or a trigger. Which is the better approach and are there any more?
When there are no tabs, and the first tab is created it is not automatically selected, how do I select it?
Is there a way of impacting the item styles inside the listboxes depending on tab type? How would I acheive this?
I am just looking for links/hints and not for actual solutions, but if you can give code then that would be a bonus!
It depends on how complicated code. If it's simple I rather use Trigger (you have everything which belows to UI in XAML), but if code is much more complicated consider using Converters (It's actually simpler to use it)
Bind to SelectedIndex of List and set it to 0?
Yes, of course, you can use ContentControl with DataTemplate (Or just DataTemplate in some cases) Some code where I use it:
<ListBox>
<ListBox.Resources>
<DataTemplate DataType="{x:Type your_namespace:your_type}">
... your code ...
</DataTemplate>
<DataTemplate DataType="{x:Type system:String}">
... your code ...
</DataTemplate>
</ListBox.Resources>
</ListBox>
Code you posted is actually a new Template, but you've changed the Style. Please consider override some Template.
Best regards

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>

WPF MVVM - What's the most correct way to fire events within TreeViewItem

I'm currently using this extension to set specific events which handle data in the ViewModel... Example:
<swi:Interaction.Triggers>
<swi:EventTrigger EventName="Click">
<esi:CallDataMethod Method="SaveRevision_Clicked"/>
</swi:EventTrigger>
</swi:Interaction.Triggers>
Where esi and swi are:
xmlns:esi="clr-namespace:Expression.Samples.Interactivity;assembly=Expression.Samples.Interactivity"
xmlns:swi="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
But what I want to set an event for a TreeViewItem? I don't have direct access to them, do I?
EDIT: I'm actually using ItemContainerStyle,
<Style x:Key="FolderView" TargetType="{x:Type TreeViewItem}">
<Setter Property="HeaderTemplate">
<Setter.Value>
<HierarchicalDataTemplate ItemsSource="{Binding Children}" >
<StackPanel Orientation="Horizontal">
<Image Name="img"
Width="20"
Height="20"
Stretch="UniformToFill"
Source="Images/hdicon.png"/>
<TextBlock Text="{Binding FolderName}" Margin="5,0" />
</StackPanel>
</HierarchicalDataTemplate>
</Setter.Value>
</Setter>
</Style>
But I can't place my event in there. Where should I put these lines?
<swi:Interaction.Triggers>
<swi:EventTrigger EventName="Expanded">
<esi:CallDataMethod Method="Expand"/>
</swi:EventTrigger>
</swi:Interaction.Triggers>
Thanks in advance!
You need to use TreeView.ItemContainerStyle to achive this, here are couple of implementations which may help:
Strange Behaviour WPF TreeView ItemContainerStyle and ItemTemplate
WPF Double Click TreeviewItem Child Node

Resources