ItemsControl, ItemsPanel and ItemsPresenter (Silverlight, XAML) - silverlight

I'm utterly confused by these 3 terms, when to use which? What's the relationship and they are children of which controls?
Is it correct to say this is the tree:
ItemsControl > ItemsPresenter > ItemsPanel

ItemsControl is conceptually a control that houses items. Try to simply think of this control as a holder for zero or more objects.
ItemsPresenter is a bit tougher to explain, but this is part of the ItemsControl template that will define where the items are placed within it. Your ItemsControl's template can be anything you like, say a Grid with some pretty pictures around it, inside this template, you would place the ItemsPresenter where ever you want your items to be, say right in the middle of your grid. (this example is taken from msdn and simplified for ease of reading)
<Style TargetType="HeaderedItemsControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type HeaderedItemsControl}">
<Grid>
<Rectangle Stroke="Black" Fill="Red"/>
<ItemsPresenter Margin="2,0,0,0"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The ItemsPanel is the panel (or container) that controls the layout of the items in your ItemsControl. So if you want your items that you have added to your ItemsControl to display in a horizotal way, then yor items panel could simply be a StackPanel with its Orientation property set to Horizontal.
This all make sense?

I think that this pretty much explains how the things are done by Silverlight:
ItemsPanelTemplate Class:
Specifies the panel that the ItemsPresenter creates for the layout of the items of an ItemsControl.
ItemsPanelTemplate Class
<Style TargetType="local:myItemsControl">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:myItemsControl">
....
<ItemsPresenter />
....
Basically, the ItemPresenter (specified in the Template) will be replaced with whatever is specified in the ItemsPanelTemplate.
So, the Template can be extended to include a header and all child will be placed under this header:
<Grid> <TextBlock Text="Header"/> <ItemsPresenter /> </Grid>

Related

Ensuring AdornmentDecorator within a ContentPresenter, but without a ControlTemplate

I've searched around quite a bit and can't seem to crack this nut.
I've got an app with a main view that changes dynamically, and to do this I use content presenter with a binding to a control:
<ScrollViewer Grid.Column="2" x:Name="StepScrollViewer">
<StackPanel Margin="20,20,20,500">
<ContentPresenter Content="{Binding MainControl}"/>
</StackPanel>
</ScrollViewer>
Then I change the MainControl at runtime in my view model. The problem is that the controls getting bound don't reliably display their error templates... I suspect it is for the reasons discussed here:
Validation ErrorTemplate not showing on data errors
But the fix for this problem doesn't seem to work for me because I'm not using a control template around my content presenter. When I wrap an AdornmentDecorator tag around my content presenter, it doesn't seem to fix the problem. It DOES work if I put an AdornmentDecorator inside each control I load into the contentpresenter (as the root element), but I'd like to avoid this repetition if possible.
Any insights?
UPDATE
I tried this approach suggested by Dennis, but to no avail. The control binds okay, but it works no better than the current approach (also shown commented below). Note: I tried it both with the AdornerDecorator as a singleton element the way Dennis has it, and surrounding the ContentPresenter, as shown below. Neither show any difference - the adorners around my controls all disappear when the MainControl binding is changed.
<UserControl.Resources>
<Style x:Key="MainContentControl" TargetType="{x:Type ContentControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ContentControl}">
<Grid>
<AdornerDecorator>
<ContentPresenter Content="{Binding MainControl}"/>
</AdornerDecorator>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Grid>
.....
<ScrollViewer Grid.Column="2" x:Name="StepScrollViewer">
<StackPanel Margin="20,20,20,500" >
<ContentControl Style="{StaticResource MainContentControl}"/>
</StackPanel>
</ScrollViewer>
<!-- THE BELOW WORKS IF I SURROUND EACH BOUND CONTROL WITH adornerdecorator -->
<ScrollViewer Grid.Column="2" x:Name="StepScrollViewer">
<StackPanel Margin="20,20,20,500">
<ContentPresenter Content="{Binding MainControl}"/>
</StackPanel>
</ScrollViewer>
-->
Instead of using a ContentPresenter directly, I would instead use a ContentControl. A ContentControl is the base class for controls that contain other elements and have a Content property, e.g. Button.
Then you can override the template to have an AdornerDecorator next to the ContentControl. This is different to what you previously tried as now the ContentPresenter is part of the same visual tree as the Adorner.
<Style TargetType="{x:Type ContentControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ContentControl}">
<AdornerDecorator>
<ContentPresenter/>
</AdornerDecorator>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Edit: Forgot that the AdornerDecorator needs to wrap the container, not just sit side-by-side.

Access property of element in controltemplate in XAML

I want to use templated ComboBoxItems which consist of an Image and a Label. If I assign the template to a ComboBoxItem, can I somehow set the Source-Property of the Image? The goal is to use the same template for different ComboBoxItems but with different pictures in each Item.
I also thought about binding the Image.Source-Property in the Template, but this fails because the "parent" ComboBoxItem has of course no Source-Property I could bind to.
The code illustrates my problem:
<Style x:Key="ComboBoxPictureItem" TargetType="{x:Type ComboBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ComboBoxItem">
<StackPanel Orientation="Horizontal">
<Image x:Name="StatusImage" />
<Label x:Name="StatusLabel" Content="Green"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<ComboBox>
<ComboBoxItem Style="{StaticResource ResourceKey=ComboBoxPictureItem}"
-> sth. like: StatusImage.Source="PathToMyImage.png"/>
</ComboBox>
Thank you!
You should use template bindings to expose internal properties, e.g. bind the Label's content to the ComboBoxItem's content:
<Label Content="{TemplateBinding Content}"/>
If you now set the Content outside it is transferred to the label, you can do the same for the image, you may run out of properties though so if you want to do things that way you could inherit from ComboBoxItem and create more properties.
Here i do not think you want to mess with control templates really, just use the ItemTemplate to specify how the items look.

Silverlight: Make all descendants of an element have a margin?

Is there a way in Silverlight 4 to dictate that all elements within a StackPanel must have a margin, instead of specifying margin="10,0" on each one?
I'm afraid it's not possible declaratively in XAML with the StackPanel directly. It's the conceptual philosophy in Silverlight/WPF that a panel should not modify properties of its children. So you could implement your own Panel that does so anyway, or you could use an ItemsControl like that:
<ItemsControl>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentPresenter Margin="10,0" Content="{Binding Content}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
[...]
</ItemsControl>
An ItemsControl uses a StackPanel by default, you can use its ItemsPanel property to define another Panel as an ItemsPanelTemplate if you wish so.
The way I'd do it is by defining implicit styles in the StackPanel's resources, for each control type that will be used within the StackPanel. To save defining the value repeatedly for each control type, you can create a named base style that targets FrameworkElement and defines the style(s), from which the style for each control type can inherit. An example is below:
<StackPanel Orientation="Horizontal">
<StackPanel.Resources>
<Style x:Key="CommonStyle" TargetType="FrameworkElement">
<Setter Property="Margin" Value="10,0" />
</Style>
<Style TargetType="Button" BasedOn="{StaticResource CommonStyle}" />
<Style TargetType="TextBlock" BasedOn="{StaticResource CommonStyle}" />
<Style TargetType="CheckBox" BasedOn="{StaticResource CommonStyle}" />
</StackPanel.Resources>
<Button>Button</Button>
<TextBlock Text="Text" />
<CheckBox>Check Box</CheckBox>
</StackPanel>
Note how each control in the StackPanel will have the margin applied, without needing to define it on each control.
Hope this helps...
Chris Anderson
PS. Blatant self promotion - this is based upon the inheritance trick in my book Pro Business Applications with Silverlight 4 :).
Put your stackpanel within a Border element and set the Border Padding to "10 0"
You can also do this programmatically; your StackPanel has a Children collection. You could use this to iterate through them and set the margin.

WPF Toolbar - Custom Style

I want to change the standard style of a wpf toolbar. I use the following Style in the Control Resources, which works fine:
<Style x:Key="{x:Type ToolBar}" TargetType="{x:Type ToolBar}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToolBar}">
<Border>
<DockPanel VerticalAlignment="Stretch" Height="38">
</DockPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
However, if i add items to a toolbar, these items are not shown:
<ToolBar x:Name="myToolbar">
<Label>test</Label>
</ToolBar>
I do NOT want to add the items in the template, but in a specific toolbar which uses that template.
Can anyone give me a hint?
The problem is that you replaced the toolbar's control template with your own. But you haven't specified in that template where items should be shown. Typically you would do it either by adding an ItemsPresenter:
<ControlTemplate TargetType="{x:Type ToolBar}">
<Border>
<DockPanel VerticalAlignment="Stretch" Height="38">
<ItemsPresenter/>
</DockPanel>
</Border>
</ControlTemplate>
Or by setting IsItemsHost="True" on a panel inside the template:
<ControlTemplate TargetType="{x:Type ToolBar}">
<Border>
<DockPanel IsItemsHost="True" VerticalAlignment="Stretch" Height="38">
</DockPanel>
</Border>
</ControlTemplate>
But, if you try to replace the items panel for the ToolBar (as in my second example), it will not work, because ToolBar expects the ToolBarPanel to be the items panel.
A template defines how a control is rendered, for some controls everything is within the template for others the control expects to find named elements within the template which it will manipulate. If you look at the ToolBar template you can see that the toolbar expects to find an element named PART_ToolBarPanel and an element named PART_ToolBarOverflowPanel within the template:
http://msdn.microsoft.com/en-us/library/aa970772.aspx
For example, it needs to locate the element named PART_ToolBarPanel in order to add the items to the toolbar.
If you want to add new elements to the toolbar template, typically you would start by copying the existing template, then start adding / removing element.

Attach xaml style to element without explicitly stating it

I have a problem styling/templating an AccordionItem in the accordion control from the silverlight toolkit. For some reason, the child controls are Horizontally Aligned Left. The only way I can get to fix this is to edit the ExpandableContentControlStyle on the AccordionItem.
The style is located below:
<Style x:Key="ExpandableContentControlStyle1" TargetType="layoutPrimitivesToolkit:ExpandableContentControl">
<Setter.Value>
<ControlTemplate TargetType="layoutPrimitivesToolkit:ExpandableContentControl">
<ContentPresenter x:Name="ContentSite" Cursor="{TemplateBinding Cursor}" Margin="0" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" ContentTemplate="{TemplateBinding ContentTemplate}" HorizontalAlignment="Stretch" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Now my problem is that to have this style being attached to the AccordionItem, I have to set it:
<layoutToolkit:Accordion HorizontalAlignment="Stretch">
<layoutToolkit:AccordionItem Header="Hello" BorderBrush="{x:Null}" ExpandableContentControlStyle="{StaticResource ExpandableContentControlStyle1}"/>
<layoutToolkit:AccordionItem Header="Haha" BorderBrush="{x:Null}"/>
</layoutToolkit:Accordion>
But those AccordionItem will be generated from an ItemSource. What I'd like to do is to have that style be applied to the generated AccordionItem without setting it.
PS. The above problem can become obsolete if I can just find out how to edit the (ContentPresenter x:Name="ContentSite") from the parent Accordion. I cannot edit it from none of the following template properties:
ContentTemplate
ItemContainerStyle
AccordionButtonStyle
ItemsPanel
ItemTemplate
If anyone knows what is going on with that, I'd appreciate the help or you can just help with styling of multiple elements.
I haven't used the Accordion control myself, though typically you set the ItemContainerStyle to the style you want for each item in the list. For instance, if you wanted a specific ListBoxItem style on a ListBox, you set the ItemContainerStyle to the ListBoxItem style you want. I glanced at the source for the Accordion and this seems to hold true for that control as well. Try setting the ItemContainerStyle property of the Accordion to your ExpandableContentControlStyle1.
<layoutToolkit:Accordion
HorizontalAlignment="Stretch"
ItemContainerStyle="{StaticResource ExpandableContentControlStyle1}">
</layoutToolkit:Accordion>
To set the style outside of the control itself, create a style for the Accordion. If you're using Silverlight 4, you can use implicit styles. Put the following style in the <UserControl.Resources> section of your page.
<Style TargetType="layoutToolkit:Accordion">
<Setter Property="ItemContainerStyle" Value="{StaticResource ExpandableContentControlStyle1}"/>
</Style>
Otherwise, with Silverlight 3 you'll have to explicitly give the style a Key and explicitly set the style on the Accordion control.
<Style x:Key="Control_Accordion" TargetType="layoutToolkit:Accordion">
<Setter Property="ItemContainerStyle" Value="{StaticResource ExpandableContentControlStyle1}"/>
</Style>
<layoutToolkit:Accordion
Style="{StaticResource Control_Accordion}"
HorizontalAlignment="Stretch">
</layoutToolkit:Accordion>

Resources