Partially templated ListBox.ItemTemplate - silverlight

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>

Related

Map control within Listbox

I am trying to display multiple maps within an Listbox.
<Grid Name="MainGrid">
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Visible" >
<WrapPanel Name="wrap" >
<ListBox ItemsSource="{Binding MyList}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Border Margin="5" MinWidth="500" MinHeight="400" BorderThickness="2" BorderBrush="Black"
Width="200"
Height="200" >
<esri:MapView MouseDown="MapView_MouseDown" MouseUp="MapView_MouseUp" >
<esri:Map >
<esri:ArcGISTiledMapServiceLayer ID="BaseMap" ServiceUri="http://services.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer"/>
</esri:Map>
</esri:MapView>
</Border>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</WrapPanel>
</ScrollViewer>
</Grid>
The map does not get displayed. Only the logo "esri" gets displayed. But if I remove the listbox, it works fine. What could be the issue?
I am very sure that there are items in my listbox otherwise "esri" would not have appeared.
I have tried Itemscontrol as well but its the same result.
In WPF, we don't put UI elements into collections, we put data objects into collections. Each data item should contain whichever properties are needed to data bind to the UI control that you actually want to display. So your MyList collection should contain a collection of data objects.
Once you have created a custom class to contain these required properties, you will then need to declare a DataTemplate that defines which UI element(s) to display. (See the Data Templating Overview page on MSDN for further information on this).
<ControlTemplate x:Key="YourCustomControlTemplate">
<!-- Define your UI content here -->
</ControlTemplate>
Once you have declared your custom ControlTemplate for your custom data class, you should then put it into a Style for your data object:
<Style TargetType="{x:Type YourXamlPrefix:YourCustomDataClass}">
<Setter Property="Template" Value="{StaticResource YourCustomControlTemplate}" />
</Style>
Note that I have omitted the x:Key directive on this Style... you can add one, but without one (and as long as this Style has been declared in scope of the UI), the Style will be automatically applied to your data objects in the collection.

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

How to use template binding inside data template in custom control (Silverlight)

I am trying to create control which will take ItemsSource and InnerTemplate and will show all the items wrapped in CheckBoxes.
The control has 2 dependency properties:
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(CheckBoxWrapperList), null);
public static readonly DependencyProperty InnerTemplateProperty = DependencyProperty.Register("InnerTemplate", typeof(DataTemplate), typeof(CheckBoxWrapperList), null);
and here is the template:
<ControlTemplate TargetType="local:CheckBoxWrapperList">
<Grid>
<Grid.Resources>
<DataTemplate x:Key="wrapper">
<CheckBox>
<ContentPresenter ContentTemplate="{TemplateBinding InnerTemplate}" Content="{Binding}" />
</CheckBox>
</DataTemplate>
</Grid.Resources>
<ItemsControl ItemTemplate="{StaticResource wrapper}" ItemsSource="{TemplateBinding ItemsSource}" />
</Grid>
</ControlTemplate>
However, this approach does not work.
Binding in the ControlPresenter.ContentTemplate using TemplateBinding does not work.
However, when I don't use template binding and reference the template as static resource, then it works as expected.
Why cannot I use the template binding inside the content presenter in datatemplate?
What am I missing here? Any special markup required?
Is there a way to achieve the expected behavior?
Thanks in advance.
Silverlight and WPF
You can get around this with a relative source binding:
Instead of:
{TemplateBinding InnerTemplate}
You would use:
{Binding RelativeSource={RelativeSource AncestorType=local:CheckBoxWrapperList}, Path=InnerTemplate}
It's a bit messier but it works.
WinRT
WinRT doesn't have AncestorType. I've got something that works but it's kind of horrifying.
You can use an attached property to store a TemplateBinding value and then access it using ElementName...
<ControlTemplate TargetType="local:CheckBoxWrapperList">
<Grid x:Name="TemplateGrid" magic:Magic.MagicAttachedProperty="{TemplateBinding InnerTemplate}">
<Grid.Resources>
<DataTemplate x:Key="wrapper">
<CheckBox>
<ContentPresenter ContentTemplate="{Binding ElementName=TemplateGrid, Path=(magic:Magic.MagicAttachedProperty)}" Content="{Binding}" />
</CheckBox>
</DataTemplate>
</Grid.Resources>
<ItemsControl ItemTemplate="{StaticResource wrapper}" ItemsSource="{TemplateBinding ItemsSource}" />
</Grid>
</ControlTemplate>
I don't know if there's a better way for WinRT.
TemplateBinding can only be used within a ControlTemplate, you're using it within a DataTemplate. (The fact that the DataTemplate is within a ControlTemplate doesn't matter)

Listboxitem not aligning items properly

Ok I got the following DataTemplate:
Style="{StaticResource LBStyle}" HorizontalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<Label Content="{Binding Name}" x:Name="txt" Grid.Column="0"></Label>
<StackPanel Orientation="Vertical" Grid.Column="1">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{z:txt text=time}" Width="50">:</TextBlock>
<TextBlock Text="{Binding Path=Value, Converter={StaticResource DecFix}}" />
</StackPanel>
<StackPanel Orientation="Horizontal" Width="50">
<TextBlock Text="Norm.">:</TextBlock>
<TextBlock Text="{Binding Path=NormaalWaarde, Converter={StaticResource DecFix}}" />
</StackPanel>
</StackPanel>
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsCurrentStep}" Value="True">
<Setter TargetName="txt" Property="FontWeight" Value="Bold"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ListBox.ItemTemplate>
Basically what I want is for each item to have some text on the left side, which stretches with the width of the listbox and 2 values on the right side, which stays on the right side.
I've tried this with many different kind of panels, grids etc. And they all seem to just put the values on the right side wrapped together to the text on the left side like this:
link text
(please use the link to see what I mean, I cannot post pictures yet)
If i put the same datatemplate standalone somewhere else it just does what it should do. Does anyone have a suggestion?
Edit: I put a gridsplitter between the listbox and the rest of the window and it looks like the listbox is stretching indefinately. This is how the listbox is positioned amongst other elements in my window: (the listbox is in the tagger usercontrol)
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="2.5*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
</Grid.RowDefinitions>
<vid:TagControl x:Name="Tagger" Grid.Column="0"></vid:TagControl>
<GridSplitter ResizeDirection="Columns" Width="20" />
<vid:DShowPlayer x:Name="DShowPlayer1" Grid.Column="1"></vid:DShowPlayer>
</Grid>
The problem here is certainly not related to your DataTemplate at all, or whether or not you are using data binding. Something in the ListBox or ListBoxItem style or ControlTemplate is causing a control to have HorizontalAlignment not set to "Stretch" at some level. (Or you could be using custom Measure or Arrange code that ignores HorizontalAlignment at some level)
I would suspect your Style="{StaticResource LBStyle} is the culprit, or some other ListBox property that isn't shown.
The default ListBox style contains a template with a Border and a ScrollViewer, none of which contains a HorizontalAlignment setting
The default ListBox ItemsPanel template consists of a StackPanel, which also doesn't contain a HorizontalAlignment setting
The default ListBoxItem template contains a template with a Border and ContentPresenter. Only the Border contains a HorizontalAlignment setting, and it is set (via a style setter) to the containing ListBox's HorizontalContentAlignment, which you have set to "Stretch".
If run in isolation the code you posted works fine. Search your style (and other properties, code-behind settings, etc) for any template replacement or setting that overrides the default HorizontalAlignment handling.
If the problem isn't apparent at first, remove settings one at a time until it stops working, then look closer at what you removed.
None of the DataTemplate examples I can find state this explicitly, but it appears to be the case that DataTemplates only apply when your list items come from an ItemsSource binding. If you directly populate the Items collection, the DataTemplate isn't used.
Adding an ItemTemplate in Expression Blend, the command text reads "Generated Item Template" - which seems to mean the template is only used when the item is generated indirectly.
And every example I can find uses a data binding for the item source.
You should use Grid instead of DockPanel with 3 columns, 1st column as 1 * , 2nd and 3rd as fixed length...

Bind page title to a text block in a style

OK, I have defined a style for Navigation Window. I have successfully styled Navigation buttons and even added page breadcrumbs to the Navigation menu. What I want is to add Page title next to the breadcrumbs:
Style x:Key="{x:Type NavigationWindow}" TargetType="NavigationWindow">
<Setter Property="SnapsToDevicePixels" Value="true"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="NavigationWindow">
<DockPanel Background="{StaticResource WindowBackgroundBrush}">
...
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="16"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
....
<StackPanel Grid.Column="4" Orientation="Horizontal">
<TextBlock Foreground="Gray"
VerticalAlignment="Center"
Text="{Binding Path=Title,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Page}}}" />
</StackPanel>
</Grid>
</DockPanel>
...
</ControlTemplate>
</Setter.Value>
</Setter>
The binding doesn't work for the last TextBlock. (However it works just fine if not used within a style, but in regular XAML page code-behind) I have no idea why. Help? How to make it display the current page title? Thanks.
The problem is that within the ControlTemplate, there is no ancestor of type Page. The control you apply the template to may have an ancestor of type Page, but the ControlTemplate itself doesn't know about that. It only knows about ancestors in its own logical tree.
To help mitigate this issue, the WPF designers added the TemplateBinding markup extension, which allows you to apply the value of a property on the templated control to a property in the control template.
So, on the NavigationWindow, you should create a property that exposes the Title of the Page. Then you can use the following mark-up to bind to it:
Text="{TemplateBinding TitleProperty}"

Resources