Create a ListBox with items that expand when selected (Accordion)

There are solutions to this problem for older XAML-based user interfaces (WPF / SL), but they are not easy to adapt to the universal Windows platform.

I am trying to create a list of items that show only limited information in the default state and expand when it is selected to quickly edit some data.
I have not found a way to create this expanding behavior, although it is similar to what the Windows 10 Mail application does with conversations. When a conversation message is selected, other messages of this type expand or go down.

Below is an example of a list in which I would like to display only the name first.

<ListBox ItemsSource="{x:Bind Persons}"> <ListBox.ItemContainerStyle> <Style TargetType="ListBoxItem"> <Setter Property="HorizontalContentAlignment" Value="Stretch" /> </Style> </ListBox.ItemContainerStyle> <ListBox.ItemTemplate> <DataTemplate x:DataType="src:Person"> <StackPanel HorizontalAlignment="Stretch" Width="Auto"> <TextBlock Text="{x:Bind Path=Name, Mode=OneWay}" Margin="12, 15, 12, 0" FontSize="18.667" /> <TextBox HorizontalAlignment="Stretch" Margin="12, 12, 12, 0" FontSize="18.667" Text="{x:Bind Path=Name, Mode=TwoWay}" /> <TextBlock Text="Date of birth" Margin="12, 15, 12, 0" /> <DatePicker Margin="12, 5, 12, 0" Date="{x:Bind Path=DateOfBirth, Mode=TwoWay}" /> <TextBlock Text="Domicile" Margin="12, 15, 12, 0" /> <TextBox Margin="12, 5, 12, 0" Text="{x:Bind Path=Domicile, Mode=OneWay}" /> </StackPanel> </DataTemplate> </ListBox.ItemTemplate> </ListBox> 

In WPF, this behavior can be achieved using Style.Triggers triggers, but they are no longer available.

GitHub source code

+5
source share
4 answers

Here is what you want to do. You want to use the ListViewItem.IsSelected property that was originally set by ListView. Then you want to respond to this change in value and set a visual state that displays or hides your data.

Like this:

 class MyModel { public bool IsSelected { get; set; } } class MyList : Windows.UI.Xaml.Controls.ListView { protected override void PrepareContainerForItemOverride(DependencyObject element, object item) { var model = item as MyModel; var listViewItem = element as Windows.UI.Xaml.Controls.ListViewItem; var binding = new Windows.UI.Xaml.Data.Binding { Source = model, Mode = Windows.UI.Xaml.Data.BindingMode.TwoWay, Path = new PropertyPath(nameof(model.IsSelected)), }; listViewItem.SetBinding(Windows.UI.Xaml.Controls.ListViewItem.IsSelectedProperty, binding); base.PrepareContainerForItemOverride(element, item); } } 

It's funny, but this code is somewhat based on the code used for a variable-size flow wrap. Here you can read the original article.

http://blog.jerrynixon.com/2012/08/windows-8-beauty-tip-using.html

If you want to know more about visual states, you can read the blog article I wrote on the same subject.

http://blog.jerrynixon.com/2013/11/windows-81-how-to-use-visual-states-in.html

Something like that:

 <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <Interactivity:Interaction.Behaviors> <Core:DataTriggerBehavior Binding="{Binding IsSelected}" Value="True"> <Core:GoToStateAction StateName="BigVisualState"/> </Core:DataTriggerBehavior> <Core:DataTriggerBehavior Binding="{Binding IsSelected}" Value="False"> <Core:GoToStateAction StateName="LittleVisualState"/> </Core:DataTriggerBehavior> </Interactivity:Interaction.Behaviors> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="VisualStateGroup"> <VisualState x:Name="BigVisualState"/> <VisualState x:Name="LittleVisualState"/> </VisualStateGroup> </VisualStateManager.VisualStateGroups> </Grid> 

If you want to learn more about the behavior of XAML in Windows applications, you can read the article I wrote on this subject.

http://blog.jerrynixon.com/2013/10/everything-i-know-about-behaviors-in.html

I also recorded a course that you might like.

https://mva.microsoft.com/en-US/training-courses/designing-your-xaml-ui-with-blend-jump-start-8260?l=p2dPykKy_5104984382

Hope this helps.

Good luck

+1
source

I created an extensible ListView control for UWP - you can find it here in the GitHub repository. This is actually a ported version of WPF Expander , which I adapted to work with the universal Windows platform.

You can find my question and answer here at fooobar.com/questions/1242060 / ....

0
source

Like Chris Said, we can add a property to the ViewModel to control the expanding behavior, but this is necessary to change the ViewModel. If you do not want to do this, here is another approach:

First, we need two DataTemplate , one to display brief information, and the other to display details. For instance:

 <Page.Resources> <!-- brief information template --> <DataTemplate x:Key="ItemTemplate" x:DataType="src:Person"> <StackPanel Width="Auto" HorizontalAlignment="Stretch"> <TextBlock Margin="12, 15, 12, 0" FontSize="18.667" Text="{x:Bind Path=Name, Mode=OneWay}" /> <TextBox Margin="12, 12, 12, 0" HorizontalAlignment="Stretch" FontSize="18.667" Text="{x:Bind Path=Name, Mode=TwoWay}" /> </StackPanel> </DataTemplate> <!-- details expand template --> <DataTemplate x:Key="SelectedTemplate" x:DataType="src:Person"> <StackPanel Width="Auto" HorizontalAlignment="Stretch"> <TextBlock Margin="12, 15, 12, 0" FontSize="18.667" Text="{x:Bind Path=Name, Mode=OneWay}" /> <TextBox Margin="12, 12, 12, 0" HorizontalAlignment="Stretch" FontSize="18.667" Text="{x:Bind Path=Name, Mode=TwoWay}" /> <StackPanel> <TextBlock Margin="12, 15, 12, 0" Text="Date of birth" /> <DatePicker Margin="12, 5, 12, 0" Date="{x:Bind Path=DateOfBirth, Mode=TwoWay}" /> <TextBlock Margin="12, 15, 12, 0" Text="Domicile" /> <TextBox Margin="12, 5, 12, 0" Text="{x:Bind Path=Domicile, Mode=OneWay}" /> </StackPanel> </StackPanel> </DataTemplate> </Page.Resources> 

Then, in the ListBox set the default ItemTemplate to compose the information template.

 <ListBox ItemTemplate="{StaticResource ItemTemplate}" ItemsSource="{x:Bind Persons}" SelectionChanged="ListBox_SelectionChanged"> <ListBox.ItemContainerStyle> <Style TargetType="ListBoxItem"> <Setter Property="HorizontalContentAlignment" Value="Stretch" /> </Style> </ListBox.ItemContainerStyle> </ListBox> 

Finally, add an event handler to the SelectionChanged event, and in this change to the ContentTemplate handler for the selected and unselected item.

 private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e) { var listBox = sender as ListBox; //get unselected item var unselectedPerson = e.RemovedItems.FirstOrDefault() as Person; if (unselectedPerson != null) { //get unselected item container var unselectedItem = listBox.ContainerFromItem(unselectedPerson) as ListBoxItem; //set ContentTemplate unselectedItem.ContentTemplate = (DataTemplate)this.Resources["ItemTemplate"]; } //get selected item container var selectedItem = listBox.ContainerFromItem(listBox.SelectedItem) as ListBoxItem; selectedItem.ContentTemplate = (DataTemplate)this.Resources["SelectedTemplate"]; } 
0
source

Here is one solution using MVVM:

 <ListBox ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}"> <ListBox.ItemTemplate> <DataTemplate> <StackPanel> <TextBlock Text="Title" /> <TextBlock Text="Details" Visibility="{Binding IsSelected, Converter={StaticResource VisibilityConverter}}" /> </StackPanel> </DataTemplate> </ListBox.ItemTemplate> </ListBox> 
 public class ViewModel : BindableBase { private Item _selectedItem; public Item[] Items { get; } public Item SelectedItem { get { return _selectedItem; } set { if (_selectedItem != null) _selectedItem.IsSelected = false; if (value != null) value.IsSelected = true; SetProperty(ref _selectedItem, value); } } } public class Item : BindableBase { private bool _isSelected; public bool IsSelected { get { return _isSelected; } set { SetProperty(ref _isSelected, value); } } } 

Another solution would be to edit ListBoxItem.ControlTemplate instead of ListBox.ItemTemplate, where you can see the data binding to the ListBoxItem.IsSelected property, use visual states.

0
source

Source: https://habr.com/ru/post/1242059/


All Articles