sort of special accordion like control - wpf

I have put an example link in here:
http://activeden.net/item/xml-horizontal-vertical-accordion-banner-rotator/full_screen_preview/127714?ref=premiumtemplates
I try to achieve something similar (but far more basic) with WPF.
Not the flying text stuff, only the basic navigation idea.
I tried to build it with some expander controls and a stackpanel.
What I came up with is this:
<ItemsControl Grid.Row="1" IsTabStop="False" ItemsSource="{Binding Path=tabs,Mode=OneWay}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
I use MVVM, so there is also a template which is applied:
<DataTemplate DataType="{x:Type vm:TabulatorViewModel}">
<Expander HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ExpandDirection="{Binding .direction,Mode=OneWay}" IsExpanded="{Binding .isExpanded,Mode=TwoWay}" Header="{Binding .header,Mode=OneWay}" >
<Expander.Style>
<Style TargetType="{x:Type Control}">
<Setter Property="Template" Value="{StaticResource HorizontalExpanderRight}" />
<Style.Triggers>
<DataTrigger Binding="{Binding .direction}" Value="Left">
<Setter Property="Template" Value="{StaticResource HorizontalExpanderLeft}" />
</DataTrigger>
</Style.Triggers>
</Style>
</Expander.Style>
<StackPanel>
<Label Content="{Binding .seitenInhalt.Header,Mode=OneWay}"></Label>
<TextBox Text="{Binding .seitenInhalt.Inhalt,Mode=OneWay}"></TextBox>
<Button Content="zurück" Command="{Binding .seitenInhalt.MovePreviousCommand}" />
<Button Content="vor" Command="{Binding .seitenInhalt.MoveNextCommand}"/>
</StackPanel>
</Expander>
</DataTemplate>
So, this is working, at least kind of.
two screenshots from my current project to explain the issues:
Could not post picture because of reputation points.
All Items together should use the complete width of the stackpanel, not like in the picture. Could not post picture because of reputation points.
All items should use the complete width, but the one expanded item should have a bigger width then the rest. As on the picture, but the collapsed items should use the remaining space, each by the same amount filling the gap)
Any help would be great, I hope it is possible to understand my goal / issues.

I think what you want to use is a UniformGridPanel or WrapPanel, which will use the full width available, rather than a StackPanel which is made to use the minimum width possible. The panel overview is here.

You'll probably want to use a Grid with the Height/Width of the Columns/Rows set to * if the item is Expanded, or Auto if not.
Also, if you're using a Grid in an ItemsControl, you need to set the Grid.Row/Grid.Column, and Width/Height properties on the ContentPresenter, not the ItemTemplate since the ItemTemplate in a ItemsControl is always wrapped in a ContentPresenter.

Related

Click/focus on a ListBoxItem's content doesn't bubble up

I've got a ListBox that's declared like this:
<ListBox ItemsSource="{Binding Contracts}" SelectedItem="{Binding SelectedContract}">
<ListBox.ItemTemplate>
<DataTemplate>
<ListBoxItem Content="{Binding Name}">
<ListBoxItem.ToolTip>
<Grid>
[code omitted for reasons of clarity]
</Grid>
</ListBoxItem.ToolTip>
</ListBoxItem>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I expected the normal selection behavior since I play with the item's ToolTip rather than its content structure.
However, clicking an item's name doesn't focus/select that item. Only by clicking that tiny space between each item (easiest way would be the space between an item's name and the ListBox's border) the item gets focused/selected.
Of course, I googled around and thought I'd found the culprit (event doesn't bubble up). But any solution provided here on SO or elsewhere, e. g. adding code like this:
<ListBoxItem.Style>
<Style TargetType="ListBoxItem">
<Style.Triggers>
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter Property="IsSelected" Value="True" />
</Trigger>
</Style.Triggers>
</Style>
</ListBoxItem.Style>
turned out to not solve the problem. So, I assume I do something wrong and I'm just too blind to see it. And while there might be solutions using code-behind, I prefer to stick with clean and pure XAML.
Please help me, understanding my mistake and solving it.
if the purpose is add ToolTip for ListBoxItem, you can use ItemContainerStyle. ListBox creates ListBoxItems for each databound item, adding ListBoxItem into
DataTemplate isn't necessary, if it breaks some functionality, try to avoid it
<ListBox>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem" BasedOn="{StaticResource {x:Type ListBoxItem}}">
<Setter Property="ToolTip">
<Setter.Value>
<Grid>
<TextBlock Text="{Binding .}"/>
</Grid>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding .}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
edit: I used Snoop app to check your variant with ListBoxItem in DataTemplate. There is 2 ListBoxItems in visual tree of each ListBox element, maybe one of prevent selection of another

Issue when setting a Style for StackPanel elements in an ItemsControl

If I create a simple StackPanel and place some items in it and define a style to add margins, all works well, as demonstrated by this quick example:
<StackPanel Grid.Row="1" Orientation="Vertical">
<StackPanel.Resources>
<Style TargetType="module:ModuleElement">
<Setter Property="Margin" Value="20"/>
</Style>
</StackPanel.Resources>
<module:ModuleElement/>
<module:ModuleElement/>
</StackPanel>
Obviously module:ModuleElement is my UserControl, but the issue remains if I use a default control, like a typical Button.
However, I don't want just a StackPanel - I want an ItemsControl. So I defined my control as such:
<ItemsControl Grid.Row="1" Margin="20,0,20,0">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical">
<StackPanel.Resources>
<Style TargetType="module:ModuleElement">
<Setter Property="Margin" Value="0,20,0,0"/>
</Style>
</StackPanel.Resources>
</StackPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.Items>
<module:ModuleElement/>
<module:ModuleElement/>
</ItemsControl.Items>
</ItemsControl>
The few items added in the code above are for testing purposes - in this case the margins are default values (0).
What's going on? Why isn't this working? What do I need to do to fix this? I could of course set the margins manually for each element, but I feel I missing something and would much rather see it defined in a Style...
You might put the Style into the ItemsControl Resources instead of the StackPanel Resources:
<ItemsControl Grid.Row="1" Margin="20,0,20,0">
<ItemsControl.Resources>
<Style TargetType="module:ModuleElement">
<Setter Property="Margin" Value="0,20,0,0"/>
</Style>
</ItemsControl.Resources>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.Items>
<module:ModuleElement/>
<module:ModuleElement/>
</ItemsControl.Items>
</ItemsControl>
Resource lookup in Silverlight traverses the "object tree" (see here) which as far as I understand is not the visual tree, but the tree defined in XAML.

VerticalAlignment in WrapPanel - How to make it work correctly?

I am having a kinda strange problem.
Here is the situation. I have a ListView, with a custom defined GroupStyle to make data appear in Expanders, in which there is a WrapPanel containing StackPanels (which contain one ToggleButton + one custom control)
The custom control in the StackPanel is not visible by default, and becomes visible when the ToggleButton is checked.
However, when I check a ToggleButton, the custom control appears, and all the other control situated on the same line will move to a vertical center.
Ideally, I'd like these other members to stay on top. I've tried to set VerticalAlignment="Top" everywhere I could, it doesn't change anyway.
Imaged problem:
The initial state of an expander:
Once the ToggleButton is clicked, here is what happens:
As you can see, the button "Test Analysis" is moved to stay at the center. I want it to stay in the same place than the original.
Moreover, I've defined all of these styles in separate DataTemplates objects.
Here is some light code (I just removed what's useless for the problem here, won't make you read tons of XAML code :) ):
The expander's content property:
<Expander.Content>
<WrapPanel Background="White" VerticalAlignment="Top">
<ItemsPresenter VerticalAlignment="Top"/>
</WrapPanel>
</Expander.Content>
The list's ItemsPanel :
<ItemsPanelTemplate >
<WrapPanel
Width="{Binding (FrameworkElement.ActualWidth),
RelativeSource={RelativeSource
AncestorType=Expander}}"
ItemWidth="{Binding (ListView.View).ItemWidth,
RelativeSource={RelativeSource AncestorType=ListView}}"
ItemHeight="{Binding (ListView.View).ItemHeight,
RelativeSource={RelativeSource AncestorType=ListView}}" />
</ItemsPanelTemplate>
The list's ItemsTemplate :
<StackPanel Orientation="Vertical" Height="Auto" Width="Auto" VerticalAlignment="Top" >
<ToggleButton BorderBrush="{x:Null}" Background="{x:Null}" IsChecked="{Binding Value.IsToLaunch, Mode=TwoWay}"
Command="{Binding DataContext.UpdateGroupCheckingsCommand,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type UserControl}}}">
<Grid Background="Transparent">
<!-- Blah blah blah about the ToggleButton's content -->
</Grid>
</ToggleButton>
<my:UcReleaseChooser>
<!-- Blah blah blah about being visible if ToggleButton is Checked -->
</my:UcReleaseChooser>
</StackPanel>
Try setting the verticalalignment property to Top or the VerticalContentAlignment property of the listview to Stretch
<Style TargetType="ListView">
<Setter Property="VerticalContentAlignment" Value="Top"/>
</Style>
or
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="VerticalContentAlignment" Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>

XAML: UniformGrid where you can define cells as not unifrom in size?

As a part of an ItemsControl style I'd like to have something that works similar ot Grid/UniformGrid as an ItemsPanel.
In its simplest form, the Style would look like this:
<Style TargetType="{x:Type ItemsControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<ItemsPresenter />
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<UniformGrid Columns="2" />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style>
<Setter Property="Control.Margin" Value="10"/>
</Style>
</Setter.Value>
</Setter>
</Style>
and I would use it like this:
<WrapPanel Orientation="Vertical">
<ItemsControl>
<TextBlock Text="Label1:"/>
<TextBlock Text="ThisWillBeBound" />
</ItemsControl>
<ItemsControl>
<TextBlock Text="Label2:"/>
<TextBlock Text="ThisWillBeBound" />
</ItemsControl>
<ItemsControl>
<TextBlock Text="Label3:"/>
<TextBlock Text="ThisWillBeBound" />
</ItemsControl>
<ItemsControl>
<TextBlock Text="Label4:"/>
<TextBlock Text="ThisWillBeBoundButIsSomewhatLongerThenTheOtherProperties" />
</ItemsControl>
<ItemsControl>
<TextBlock Text="Label5:"/>
<TextBlock Text="ThisWillBeBound" />
</ItemsControl>
<ItemsControl>
<TextBlock Text="Label6:"/>
<TextBlock Text="ThisWillBeBound" />
</ItemsControl>
</WrapPanel>
As you can see, you would throw a Label for a property into this plus a binding to that property and they would stick together even if things would wrap around becuase of different sizes of the app window for example.
The nice thing here about theUniformGrid is that it seems that if one Label is longer the others will get the same space meaning the bound properties would line up neatly (why this happens I don't understand because these should be seperate UniformGrids I guess). Problem here is that the cell width of the Label part and the Property part is the same, so if the property is long there will be a large gap between the Label and the Property.
Using normal Grid instead would mean I'd have to set the Grid.Column="0" and Grid.Column="1" everyplace I put items into this ItemsControl. That's not really what I wan't.
Is there a way for me to have some sort of grid that lines up like the uniform one but doesn't stratch all columns when one only needs to be and assumes you are putting into columns the same way you declare the items?
The way I've typically seen this sort of scenario handled is via a combination of Grid+IsSharedSizeScope+ListView+DataTemplate.
In this case, the label text (i.e. "Label1:") and entry text (i.e. "ThisWillBeBound") would be in a single item type and a collection of that type of item would be bound to the list view. The Grid.IsSharedSizeScope allows for sharing column size info between different entries while using a template - very handy.
There's a great example of this at http://www.wpftutorial.net/DataTemplates.html. Is this the sort of scenario you're looking for?

WPF ListBox virtualization screws up displayed items

Problem
We need to efficiently display a large (>1000) number of objects in a WPF ListBox control.
We are relying on the WPF ListBox’s virtualization (via VirtualizingStackPanel) to efficiently display these items.
Bug: The WPF ListBox control does not display items correctly when using virtualization.
How to Reproduce
We have distilled the problem to the standalone xaml shown below.
Copy and paste the xaml into XAMLPad.
Initially, there is no selected item in the ListBox, so as expected, all items are the same size and they completely fill the available space.
Now, click on the first item.
As expected, because of our DataTemplate, the selected item will expand to show additional information.
As expected, this causes the horizontal scrollbar to appear, since the selected item is now wider than the available space.
Now use the mouse to click and drag the horizontal scrollbar to the right.
Bug: the non-selected visible items no longer stretch to fill the available space. All the visible items should be the same width.
Is this a known bug?
Is there any way to fix this, either via XAML or programmatically?
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >
<Page.Resources>
<DataTemplate x:Key="MyGroupItemTemplate">
<Border Background="White"
TextElement.Foreground="Black"
BorderThickness="1"
BorderBrush="Black"
CornerRadius="10,10,10,10"
Cursor="Hand"
Padding="5,5,5,5"
Margin="2"
>
<StackPanel>
<TextBlock Text="{Binding Path=Text, FallbackValue=[Content]}" />
<TextBlock x:Name="_details" Visibility="Collapsed" Margin="0,10,0,10" Text="[xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx]" />
</StackPanel>
</Border>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type ListBoxItem}},Path=IsSelected}"
Value="True">
<Setter Property="TextElement.FontWeight"
TargetName="_details"
Value="Bold"/>
<Setter Property="Visibility"
TargetName="_details"
Value="Visible"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Page.Resources>
<DockPanel x:Name="LayoutRoot">
<Slider x:Name="_slider"
DockPanel.Dock="Bottom"
Value="{Binding FontSize, ElementName=_list, Mode=TwoWay}"
Maximum="100"
ToolTip="Font Size"
AutoToolTipPlacement="BottomRight"/>
<!--
I want the items in this ListBox to completly fill the available space.
Therefore, I set HorizontalContentAlignment="Stretch".
By default, the WPF ListBox control uses a VirtualizingStackPanel.
This makes it possible to view large numbers of items efficiently.
You can turn on/off this feature by setting the ScrollViewer.CanContentScroll to "True"/"False".
Bug: when virtualization is enabled (ScrollViewer.CanContentScroll="True"), the unselected
ListBox items will no longer stretch to fill the available horizontal space.
The only workaround is to disable virtualization (ScrollViewer.CanContentScroll="False").
-->
<ListBox x:Name="_list"
ScrollViewer.CanContentScroll="True"
Background="Gray"
Foreground="White"
IsSynchronizedWithCurrentItem="True"
TextElement.FontSize="28"
HorizontalContentAlignment="Stretch"
ItemTemplate="{DynamicResource MyGroupItemTemplate}">
<TextBlock Text="[1] This is item 1." />
<TextBlock Text="[2] This is item 2." />
<TextBlock Text="[3] This is item 3." />
<TextBlock Text="[4] This is item 4." />
<TextBlock Text="[5] This is item 5." />
<TextBlock Text="[6] This is item 6." />
<TextBlock Text="[7] This is item 7." />
<TextBlock Text="[8] This is item 8." />
<TextBlock Text="[9] This is item 9." />
<TextBlock Text="[10] This is item 10." />
</ListBox>
</DockPanel>
</Page>
I spent more time attempting this than I probably should have, and couldn't get it to work. I understand what's going on here, but in pure XAML, I'm having trouble figuring out how to solve the issue. I think I see how to solve the problem, but it involves a converter.
Warning: Things are going to get complicated as I explain my conclusions.
The underlying problem comes from the fact that the Width of the controls stretch to the Width of their container. When virtualization is enabled, the Width will not change. In the underlying ScrollViewer inside of ListBox, the ViewportWidth property corresponds to the Width you see. When another control stretches out further (you select it), the ViewportWidth is still the same, but the ExtentWidth shows the full width. Binding the width of all controls to that of the ExtentWidth should work...
But it doesn't. I set the FontSize to 100 for quicker testing in my case. When an item is selected, ExtentWidth="4109.13. Going down the tree to your ControlTemplate's Border, I see ActualWidth="4107.13". Why the 2 pixel difference? ListBoxItem contains a Border with 2 Pixel padding, causing the ContentPresenter to render slightly smaller.
I added the following Style with help from here to allow me to directly access the ExtentWidth:
<Style x:Key="{x:Type ListBox}" TargetType="ListBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBox">
<Border
Name="Border"
Background="White"
BorderBrush="Black"
BorderThickness="1"
CornerRadius="2">
<ScrollViewer
Name="scrollViewer"
Margin="0"
Focusable="false">
<StackPanel IsItemsHost="True" />
</ScrollViewer>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter TargetName="Border" Property="Background"
Value="White" />
<Setter TargetName="Border" Property="BorderBrush"
Value="Black" />
</Trigger>
<Trigger Property="IsGrouping" Value="true">
<Setter Property="ScrollViewer.CanContentScroll" Value="false"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Note I added a name to ScrollViewer for this purpose.
Then, I attempted to bind the Width of your Border to the ExtentWidth:
Width="{Binding ElementName=scrollViewer, Path=ExtentWidth}"
However, because of that 2 pixel padding, the controls will resize in an infinite loop, with the padding adding 2 pixels to the ExtentWidth, which resizes the Border width, which adds 2 more pixels to the ExtentWidth, etc. until you delete the code and refresh.
If you added a Converter that subtracted 2 from the ExtentWidth, I think this might work. However, when the scroll bar does not exist (you have not selected anything), ExtentWidth="0". Thus, binding to MinWidth instead of Width may work better so the items appear correctly when no scroll bar is visible:
MinWidth="{Binding ElementName=scrollViewer, Path=ExtentWidth, Converter={StaticResource PaddingSubtractor}}"
A better solution would be if you could directly databind the MinWidth of the ListBoxItem itself. You could bind directly to ExtentWidth, and no converter would be necessary. However I have no idea how to get access to that item.
Edit: For organization sake, here's the clip required to do that. Makes everything else unnecessary:
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="MinWidth" Value="{Binding Path=ExtentWidth, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ScrollViewer}}}" />
</Style>
Thanks to Will's great analysis!
Based on Will's suggestion: "A better solution would be if you could directly databind the MinWidth of the ListBoxItem itself...However I have no idea how to get access to that item", I was able to implement that using pure xaml, as follows:
<ListBox x:Name="_list"
Background="Gray"
Foreground="White"
IsSynchronizedWithCurrentItem="True"
TextElement.FontSize="28"
HorizontalContentAlignment="Stretch"
ItemTemplate="{DynamicResource MyGroupItemTemplate}">
<!-- Here is Will's suggestion, implemented in pure xaml. Seems to work.
Next problem is if you drag the Slider to the right to increase the FontSize.
This will make the horizontal scroll bar appear, as expected.
Problem: the horizontal scroll bar never goes away if you drag the Slider to the left to reduce the FontSize.
-->
<ListBox.Resources>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="MinWidth" Value="{Binding Path=ExtentWidth, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ScrollViewer}}}" />
</Style>
</ListBox.Resources>
<TextBlock Text="[1] This is item 1." />
<TextBlock Text="[2] This is item 2." />
<TextBlock Text="[3] This is item 3." />
<TextBlock Text="[4] This is item 4." />
<TextBlock Text="[5] This is item 5." />
<TextBlock Text="[6] This is item 6." />
<TextBlock Text="[7] This is item 7." />
<TextBlock Text="[8] This is item 8." />
<TextBlock Text="[9] This is item 9." />
<TextBlock Text="[10] This is item 10." />
</ListBox>
I got the idea from Adam Nathan's great book, "Windows Presentation Foundation Unleashed".
So, this seems to fix the original problem.
New Problem
You notice that there is a Slider control in the xaml that let's you increase/decrease the ListBox font. The idea here was to allow the user the ability to scale the ListBox content up or down for easier visibility.
If you first drag the Slider to the right to increase the FontSize, this will make the horizontal scroll bar appear, as expected. The new problem is that the horizontal scroll bar never goes away if you drag the Slider to the left to reduce the FontSize.
Any ideas?

Resources