Templating ItemControl items without using ItemsSource - silverlight

I am trying to build a custom Silverlight ItemsControl. I want the users of this control to add items using XAML. The items will be other UI elements. I would like to add a margin around all added items and therefore I want to add an ItemTemplate.
I am trying to do this using the ItemsControl.ItemTemplate, but that does not seem to be used when binding to elements in XAML, i.e using the ItemsControl.Items property.
However, if I use the ItemsControl.ItemsSource property, the ItemTemplate is used.
Is there anyway to use the ItemTemplate even though I am not assigning ItemsSource?
This is my code so far
<ItemsControl x:Class="MyControl">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate >
<toolkit:WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Margin="20" Background="Red">
<TextBlock Text="Test text"/>
<ContentPresenter Content="{Binding}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.Template>
<ControlTemplate>
<Border>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ItemsPresenter x:Name="ItemsPresenter"/>
<Button Command="{Binding SearchCommand}"/>
</Grid>
</Border>
</ControlTemplate>
</ItemsControl.Template>
</ItemsControl>
And when I use my control
<MyControl>
<Button Content="Button"/>
<Button Content="Button"/>
</MyControl>
This got me a display of the items, with a wrap panel layout but no applied data template.
Then I found this post that mentioned two methods to override.
Son in my code-behind of the class I have now
protected override bool IsItemItsOwnContainerOverride(object item)
{
return false;
}
protected override void PrepareContainerForItemOverride(DependencyObject element,
object item)
{
base.PrepareContainerForItemOverride(element, item);
((ContentPresenter)element).ContentTemplate = ItemTemplate;
}
BUT - this gets me two items, with the style (I.E a red textblock) but no actual content. The buttons in the list are not added. It feels like I am doing something wrong - any pointers on what?
Thanks!

If all you want to do is add some margin then you can just set the ItemContainerStyle instead of specifying a template:
<ItemsControl>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="FrameworkElement.Margin" Value="10" /> <!-- or whatever margin you want -->
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
This will allow you to set any property of the container control (which in the case of an ItemsControl will be a ContentControl) through the style.

Related

Zero-height of ScrollViewer-templated item inside ItemsControl?

I have an ItemsControl with arbitrary items. Some of the items are wrapped inside a ScrollViewer. The code-behind for these scrollable items makes use of the ViewportWidth (almost equivalent to ActualWidth) and ViewportHeight (almost equivalent to ActualHeight) properties to arrange/size its visual children. This works as long as I don't put the item inside an ItemsControl. When the item appears in an ItemsControl the value of ViewportHeight equals 0 - effectively making my item invisible. Note that I want to arrange the items vertically, giving all items equal height! No fancy stuff, just a regular StackPanel.
The templates are applied automatically using DataType:
<MyControl.Resources>
<DataTemplate DataType="{x:Type MyScrollableItem}">
<MyControlWrappedInScrollViewer Text="{Binding Text}" />
</DataTemplate>
<DataTemplate DataType="{x:Type MyItem}">
<TextBlock Text="{Binding Text}"/>
</DataTemplate>
</MyControl.Resources>
<ItemsControl ItemsSource="{Binding MyCollection}" />
The structure of MyControlWrappedInScrollViewer looks something like this:
<UserControl>
<Grid>
<ScrollViewer CanContentScroll="True">
<Canvas />
</ScrollViewer>
</Grid>
</UserControl>
Why does my ScrollViewer get the height of 0? How can I tell my ItemsControl to size the item appropriately? E.g. One item yields a height of the ItemsControl height. Two items yield half of it, and so on.
This did the trick:
<Style TargetType="{x:Type ItemsControl}">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<UniformGrid Columns="1" IsItemsHost="True"/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>

Style Nth item in ItemsControl

I have an ItemsControl with an associated DataTemplate as below. My question is how can I style the Nth item in the ItemsControl differently? I am trying to place borders on certain items only.
<DataTemplate x:Key="CTemplate">
<Grid HorizontalAlignment="Left" Width="200" Height="Auto" Margin="0,0,30,30">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="10"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Vertical" Grid.Column="0">
<TextBlock Text="Device Name:" Style="{StaticResource i2_TB}"/>
<TextBlock Text="Device ID:" Style="{StaticResource i2_TB}" />
</StackPanel>
<StackPanel Orientation="Vertical" Grid.Column="2">
<TextBlock Text="{Binding DeviceName}" Style="{StaticResource i2_TB}" TextTrimming="CharacterEllipsis" />
<TextBlock Text="{Binding DeviceID}" Style="{StaticResource i2_TB}" TextTrimming="CharacterEllipsis" />
</StackPanel>
</Grid>
</DataTemplate>
I figured it out and its pretty simple
Firstly set an AlternationCount that matches your N. In my case I had 3 items per row and I wanted the 3rd one to be different.
<ItemsControl ItemTemplate="{StaticResource CTemplate}" AlternationCount="3">
Secondly set up your data triggers in the template itself, using the AlternationIndex as the data trigger
<DataTemplate.Triggers>
<Trigger Property="ItemsControl.AlternationIndex" Value="2">
<Setter Property="BorderThickness" Value="0,0,0,1" TargetName="controlHost"/>
</Trigger>
</DataTemplate.Triggers>
Win
I can think of 3 ways of doing this.
1st way
Have a simple property on the things you want to Template, then have a TemplateSelector that will pick correct template based on that property
2nd way
Or you could use a combination of TemplateSelector and ItemContainerGenerator.IndexFromContainer where you use the container and item in TemplateSelector to get index and return correct DataTemplate
3rd way
Or another idea may lie in the use of a ValueConverter/MarkupExtension.
You may need to have a MultiValueConverter setup something like this
Item[0] : DataTemplate standard resource
Item[1] : Alternative DataTemplate resource
Item[2] : The listbox (relative source lookup)
Item[3] : The listboxitem (relative source lookup)
Then you can use the ItemContainerGenerator.IndexFromContainer, and then just return the DataTemplate that matches the index.
I did something like this once. I think it was in this code :
http://www.codeproject.com/Articles/30021/WPF-Sticky-Notes-ListBox
If your items are of different types, then it could be as easy as providing a DataTemplate with no x:Key value set on them for each of the different types:
<DataTemplate DataType="{x:Type DataTypes:ThisType}">
...
</DataTemplate>
<DataTemplate DataType="{x:Type DataTypes:ThatType}">
...
</DataTemplate>
However, if your items are all of the same type then the standard way to do this is to create a named DataTemplate for each of the different looks that you want and to use a DataTemplateSelector. In the DataTemplateSelector.SelectTemplate method, you can decide which DataTemplate to display for each item:
public class TaskListDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
FrameworkElement element = container as FrameworkElement;
if (element != null && item != null && item is Task)
{
Task taskitem = item as Task;
if (taskitem.Priority == 1) return element.FindResource(
"importantTaskTemplate") as DataTemplate;
else return element.FindResource("myTaskTemplate") as DataTemplate;
}
return null;
}
}
Example taken from the linked page (below) on MSDN
You can find out more from the DataTemplateSelector Class page on MSDN.

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

Custom control or not in this case?

I would factor a graphic control, but I do not know how to do it.
I want to create a container control with templated elements.
ex:
<MyControl>
<MyControl.Elements>
<TextElement Value="{Binding Somedata}" />
<IntElement Value="{Binding OtherData}" />
</MyControl.Elements>
</MyControl>
The different elements are templates that I have created to always display the same way an item.
In simple example, MyControl will be a WrapPanel, TextElement a TextBlock and IntElement a TextBox. Elements will be WrapPanel's children.
Should I create a custom control for MyControl ?
And a dependency property for Elements ?
How create ElementBase for all templates ?
Thank you
It sounds like you simply want an ItemsControl.
<ItemsControl x:Name="itemsControl">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.Items>
<TextBlock Text="{Binding Somedata}" />
<TextBlock Text="{Binding OtherData}" />
</ItemsControl.Items>
</ItemsControl>

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>

Resources