How can I make an "Accordion Widget" in WPF? - wpf

The goal:
I'm trying to achieve something like this in WPF:
(source: wordpress.org)
An initial solution:
At the moment, I'm trying to use an ItemsControl with an ItemTemplate composed of an Expander.
I want a consistent look for the Header portion of the Expander, but I want the Content portion of the Expander to be completely flexible. So, it's basically a set of "portlets" stacked vertically, where each portlet has a consistent title bar but different content.
The code so far:
This is what I have at the moment:
<ItemsControl
Grid.Row="2"
Grid.Column="2">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Expander>
<Expander.HeaderTemplate>
<DataTemplate>
<StackPanel
Orientation="Horizontal">
<TextBlock
FontSize="14"
FontWeight="Bold"
Text="Title_Of_Expander_Goes_Here" />
<TextBlock
Margin="10,0,0,0"
FontWeight="Bold"
FontSize="18"
Text="*" />
</StackPanel>
</DataTemplate>
</Expander.HeaderTemplate>
<Expander.Template>
<ControlTemplate
TargetType="Expander">
<Border
BorderThickness="1">
<ContentPresenter />
</Border>
</ControlTemplate>
</Expander.Template>
</Expander>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.Items>
<StackPanel>
<TextBlock
FontSize="14"
FontWeight="Bold"
Text="Users:" />
<wt:DataGrid
Margin="0,1,0,0"
AutoGenerateColumns="False"
CanUserAddRows="True"
CanUserDeleteRows="True"
ItemsSource="{Binding Source={StaticResource Main_SystemUsers}, XPath=//Users/*}">
<wt:DataGrid.Columns>
<wt:DataGridTextColumn
Header="User Name"
Binding="{Binding XPath=#UserName}" />
<wt:DataGridComboBoxColumn
Header="Role"
ItemsSource="{Binding Source={StaticResource Main_UserRoles}, XPath=//Roles/*}"
SelectedValueBinding="{Binding XPath=#Role}" />
</wt:DataGrid.Columns>
</wt:DataGrid>
<StackPanel
Margin="0,10,0,0"
Orientation="Horizontal">
<Button
Content="Add New User..." />
<Button
Margin="10,0,0,0"
Content="Delete User..." />
</StackPanel>
</StackPanel>
</ItemsControl.Items>
</ItemsControl>
Discussion:
The only thing that shows up when I run this is the DataGrid of users and the buttons ("Add New User" and "Delete User") below it. There is no Expander or title bar. Also, even if I did see one, I'm not sure how to set up a Binding for the title that appears on the title bar. I know how to do bindings if I use ItemsSource, but I wanted to set my items declaratively.
The question:
How should I go about this? I'm looking for either a fix for what I have now or a clean-sheet solution.
Edit:
What I ended up doing was replacing the ItemsControl with a StackPanel and just writing a style for my expanders. This proved to be much simpler, and there really was no benefit to the ItemsControl since I needed to declare custom content for each item anyway. The one issue remaining was how to achieve a custom title for each expander. That's where #Thomas Levesque's suggestion to use TemplateBinding came in. All I had to do was replace Text="Title_Of_Expander_Goes_Here" in my header's template (see code above) with Text="{TemplateBinding Content}".

You're not seeing the Expander because you redefined its template. This one should work better :
...
<Expander.Template>
<ControlTemplate
TargetType="Expander">
<Border
BorderThickness="1">
<Expander Content="{TemplateBinding Content}" Header="{TemplateBinding Header}"/>
</Border>
</ControlTemplate>
</Expander.Template>
...

Personally I think a TreeView control would give you a much better base to work from, especially if you're using Expression Blend as a basis to create new/blank Templates from for items. Seeing the default Templates is extremely enlightening and gives you much more fine-grained control and better understanding and insight into how things work by default. Then you can go to town on them. It also looks like you're working with Hierchical Data and TreeViews inherently lend themselves well to working with such data.

Related

In WPF 4.0, switching tabs resets usercontrol UI

I have a border that contains a TabControl in a HeaderedContentControl:
<Border Grid.Column="1"
Style="{StaticResource MainBorderStyle}">
<HeaderedContentControl
Content="{Binding Path=Workspaces}"
ContentTemplate="{StaticResource WorkspacesTemplate}"
Header="Decision Workspaces"
Style="{StaticResource MainHCCStyle}"/>
</Border>
The TabControl is defined in a static resource:
<DataTemplate x:Key="ClosableTabItemTemplate">
<DockPanel Width="120" ToolTip="{Binding Path=DisplayName, Mode=OneTime}">
<Button
Command="{Binding Path=CloseCommand}"
Content="X"
Cursor="Hand"
DockPanel.Dock="Right"
Focusable="False"
FontFamily="Courier"
FontSize="9"
FontWeight="Bold"
Margin="0,1,0,0"
Padding="0"
VerticalContentAlignment="Bottom"
Width="16" Height="16"
/>
<ContentPresenter
Content="{Binding Path=DisplayName, Mode=OneTime}"
VerticalAlignment="Center"
/>
</DockPanel>
</DataTemplate>
<!--
This template explains how to render the 'Workspace' content area in the main window.
-->
<DataTemplate x:Key="WorkspacesTemplate">
<TabControl
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource ClosableTabItemTemplate}"
Margin="4"
/>
</DataTemplate>
The Workspaces property which is bound to the HeaderedContentControl's Content property, has a collection of UserControls, which are rendered in the tabs. This all works fine.
The problem is that when i select a row in a grid in one of the UserControls, switch to a different tab, and then return, the selected row is reset. The same happens if a RowDetails is open - when I switch away and back to the tab, it is collapsed.
Any way around this?
Edit: After looking at the proposed solutions for the TabControl behaviour, I'm wandering if I might ditch it altogether. Any ideas for a UI that will allow me to keep several relatively complex UserControls and switch between them, not loosing the visuals in the process?
Thanks!
This is a common problem with the TabControl. Since it only displays the content of the selected tab. If your tab items are not visuals themselves and are presented with a DataTemplate, then the controls will be created and released as you switch tabs.
There are two solutions to this problem here and here, which attempt to retain the visuals for each tab.

How can I hide a control so that it no longer takes up space in WPF?

I have a DataTemplate that I am using for a cell in a gridview. I would like to switch between the progress bar and the text/link block. Is there a way to hide an element so that it is removed from the flow and takes up no space while it is hidden (like "display:none" in CSS)? Is there a better way to approach this?
DataTemplate looks like:
<DataTemplate x:Key="DataTemplate2">
<StackPanel Height="40">
<TextBlock Visibility="{Binding ButtonVisibility}">
<Hyperlink Click="btn_Authorise">
<InlineUIContainer>
<TextBlock Text="{Binding Button}" />
</InlineUIContainer>
</Hyperlink>
</TextBlock>
<ProgressBar Value="{Binding Progress}"
Visibility="{Binding ProgressVisibility}"
Height="15"
Width="150"
Background="{DynamicResource NormalBrush}"
BorderThickness="0"
BorderBrush="#FF8D8D8D"
Style="{DynamicResource ProgressBarStyle1}" />
</StackPanel>
</DataTemplate>
Visibility.Collapsed is probably what you need (as opposed to Visibility.Hidden which still makes the control take part in layout calculations)
Also see the Visibility enumeration reference.
Yep.
Visibility is an enumeration, Visible, Hidden, and Collapsed.
Hidden is just non-visible, whereas Collapsed means it takes no space also

WPF: get the content of a GroupBox to fill available space

I'm facing an irritating problem with WPF GroupBox, hope someone can help me out. Basically the problem is this: I have a listview inside a GroupBox, but no matter what I do I can't seem to be able to make it fill the GroupBox.
Here is the basic code:
<GroupBox Grid.Row="2" Header="Field" Visibility="{Binding ElementName=radioUnbound, Path=IsChecked, Converter={StaticResource bool2vis}}" Margin="0" VerticalContentAlignment="Stretch">
<ListView ItemsSource="{Binding ElementName=nnf1, Path=UnboundFields}" x:Name="listUnbound" SelectionChanged="listSelectionChanged" VerticalAlignment="Stretch" >
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding name}" Margin="2"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</GroupBox>
I tried encasing the list inside Grids, StackPanels, DockPanel, etc... but no matter what I try I always invariably end up with this:
I tried your code in XamlPad it works as you would expect it. Make sure you don't have global styles that set your ListView or GroupBox appearance.
You can clear global styles by putting this in the resources section of the GroupBox's parent control:
<Style TargetType="GroupBox" />
<Style TargetType="ListView" />

SL3 Dataform Datafields in Border

Can you surround Datafields in a border? I have a large form that needs to be organized into sections, "Customer INformation" for instance.
Is there a way to surround these with a border?
I kinda get the feeling you are not specifying your own edit template for the control but a letting data form do it for you. I get this feeling because if you are already using an edit template you wouldn't be asking the question. Consider this:-
<dataform:DataForm x:Name="dataForm">
<dataform:DataForm.EditTemplate>
<DataTemplate>
<StackPanel>
<Border BorderBrush="Black" BorderThickness="2">
<StackPanel>
<dataform:DataField>
<TextBox Text="{Binding ID, Mode=TwoWay}" />
</dataform:DataField>
<dataform:DataField>
<TextBox Text="{Binding Name, Mode=TwoWay}" />
</dataform:DataField>
</StackPanel>
</Border>
<dataform:DataField>
<CheckBox IsChecked="{Binding Test, Mode=TwoWay}" />
</dataform:DataField>
</StackPanel>
</DataTemplate>
</dataform:DataForm.EditTemplate>
</dataform:DataForm>
Adding a border around the ID and Name fields is a simple case of placing them in their own StackPanel and putting that in a Border. Basically with a template you can do anything you want with the form appearance, in fact you don't even need the DataField if you feel you can do a better job with label placement etc in your own XAML.

Force TextBlock to wrap in WPF ListBox

I have a WPF listbox which displays messages. It contains an avatar on the left side and the username and message stacked vertically to the right of the avatar. The layout is fine until the message text should word wrap, but instead I get a horizontal scroll bar on the listbox.
I've Googled and found solutions to similar issues, but none of them worked.
<ListBox HorizontalContentAlignment="Stretch" ItemsSource="{Binding Path=FriendsTimeline}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Border BorderBrush="DarkBlue" BorderThickness="3" CornerRadius="2" Margin="3" >
<Image Height="32" Width="32" Source="{Binding Path=User.ProfileImageUrl}"/>
</Border>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Path=User.UserName}"/>
<TextBlock Text="{Binding Path=Text}" TextWrapping="WrapWithOverflow"/> <!-- This is the textblock I'm having issues with. -->
</StackPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Contents of the TextBlock can be wrapped using property TextWrapping.
Instead of StackPanel, use DockPanel/Grid.
One more thing - set ScrollViewer.HorizontalScrollBarVisibility property to Disabled value for the ListBox.
Updated Hidden to Disabled based on comment from Matt. Thanks Matt.
The problem might not be located in the ListBox. The TextBlock won't wrap, if one of the parent controls provides enough space, so that it hasn't the need to wrap. This might be caused by a ScrollViewer control.
If you want to prevent TextBlock to grow, and you want it to just fit in the size of the listbox, you should set the width of it explicitly.
In order to change it dynamically, it means not a fix value, but you need to bind it to its proper parent element in the visual tree. You can have something like this:
<ListBox ItemsSource="{Binding MyItems}" Name="MyListBox">
<ListBox.Resources>
<Style TargetType="ListBoxItem">
<Setter Property="Width"
Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ScrollContentPresenter}, Path=ActualWidth}" />
</Style>
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Title}" TextWrapping="Wrap" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
If it does not work, try to find the proper elements (which has to be binded to what) with the Live Visual Tree in Visual Studio.

Resources