How to make a grid-like listbox in xaml

时间:2015-06-25 18:43:32

标签: c# xaml windows-runtime grid windows-phone-8.1

How can I make a mosaic-like listbox where data can be entered like any other listbox, and that can adapt to virtually any screen resolution?

1 个答案:

答案 0 :(得分:1)

I have spent a while attempting to build a listbox with a mosaic-like presentation for my Popcorn time port to Windows phone 8.1 / RT, that would adapt to any screen dimension simply. Before I explain how I did it, here is a screenshot: There might be a cleaner way, but this works perfectly well: I started with Yi-Yun Luo's custom panel (https://social.msdn.microsoft.com/Forums/en-US/20691ca0-eae6-4c4e-b27e-3d5d37c83689/multicolumn-listbox?forum=silverlightnet) and after editing it, here is the responsive design version: public class MultiColumnPanel : Panel { public static readonly DependencyProperty ColumnWidthProperty = DependencyProperty.Register("ColumnWidth", typeof(double), typeof(MultiColumnPanel), new PropertyMetadata(0d)); public double ColumnWidth { get { return (double)this.GetValue(ColumnWidthProperty); } set { this.SetValue(ColumnWidthProperty, value); } } protected override Size MeasureOverride(Size availableSize) { Size size = new Size(); Size finalSize = availableSize; finalSize.Height = double.PositiveInfinity; int i = 0; while (i < this.Children.Count) { double tempHeight = 0d; for (int j = 0; j < (Convert.ToInt32(Window.Current.Bounds.Width / 160)); j++) { if ((i + j) >= this.Children.Count) { break; } UIElement element = this.Children[i + j]; if (element != null) { element.Measure(availableSize); tempHeight = Math.Max(tempHeight, element.DesiredSize.Height); } } size.Height += tempHeight; i += (Convert.ToInt32(Window.Current.Bounds.Width / 160)); ; } size.Width = this.ColumnWidth * (Convert.ToInt32(Window.Current.Bounds.Width / 160)); return size; } protected override Size ArrangeOverride(Size finalSize) { UIElementCollection children = this.Children; double heightDelta = 0d; int i = 0; while (i < this.Children.Count) { double tempHeight = 0d; for (int j = 0; j < (Convert.ToInt32(Window.Current.Bounds.Width / 160)); j++) { if ((i + j) >= this.Children.Count) { break; } UIElement element = children[i + j]; if (element != null) { Rect finalRect = new Rect(new Point(), finalSize); finalRect.X = j * this.ColumnWidth; finalRect.Y += heightDelta; tempHeight = Math.Max(tempHeight, element.DesiredSize.Height); finalRect.Height = element.DesiredSize.Height; finalRect.Width = Math.Max(this.ColumnWidth, element.DesiredSize.Width); element.Arrange(finalRect); } } heightDelta += tempHeight; i += (Convert.ToInt32(Window.Current.Bounds.Width / 160)); } return finalSize; } } I divided it by 160, because I wanted 3 columns on a 480*800 screen, but that is totally up to you. Now here is the xaml: <ScrollViewer x:Name="scrollviewer" ViewChanged="OnScrollViewerViewChanged" Margin="0"> <Viewbox Margin="0,0,0,0"> <ListBox Name="lstView" ScrollViewer.VerticalScrollMode="Disabled" Background="Transparent" BorderThickness="0" Foreground="White" Tapped="lstView_Tapped" SelectionChanged="lstView_SelectionChanged" DataContextChanged="myscrollviewer_ViewChanged" ItemContainerStyle="{StaticResource ListBoxItemStyle1}" ScrollViewer.VerticalScrollBarVisibility="Visible" Margin="0"> <ListBox.ItemsPanel> <ItemsPanelTemplate> <local:MultiColumnPanel ColumnWidth="132"/> </ItemsPanelTemplate> </ListBox.ItemsPanel> <ListBox.ItemTemplate> <DataTemplate> <Grid x:Name="ItemGrid" Width="132" Background="#00000000" Margin="0,0,0,0" Grid.ColumnSpan="2" Height="210" Tapped="ItemGrid_Tapped" HorizontalAlignment="Stretch"> <Grid.RenderTransform> <TranslateTransform x:Name="ListBoxItemMove" /> </Grid.RenderTransform> <Grid.Resources> <EventTrigger x:Name="event" RoutedEvent="Grid.Loaded"> <EventTrigger.Actions> <BeginStoryboard> <Storyboard x:Name="listboxIN"> <DoubleAnimation Duration="00:00:01" Storyboard.TargetProperty="X" From="-100" To="0" Storyboard.TargetName="ListBoxItemMove"> <DoubleAnimation.EasingFunction> <CircleEase EasingMode="EaseOut"/> </DoubleAnimation.EasingFunction> </DoubleAnimation> <DoubleAnimation Duration="00:00:01" From="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="ItemGrid"> <DoubleAnimation.EasingFunction> <CircleEase EasingMode="EaseOut"/> </DoubleAnimation.EasingFunction> </DoubleAnimation> </Storyboard> </BeginStoryboard> </EventTrigger.Actions> </EventTrigger> </Grid.Resources> <TextBlock Text="{Binding id}" Visibility="Collapsed"/> <TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding Name}" VerticalAlignment="Bottom" Margin="5,0,0,14" FontFamily="Segoe WP" FontSize="16" FontWeight="Thin" TextTrimming="CharacterEllipsis"/> <TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Year}" VerticalAlignment="Bottom" FontWeight="Black" FontFamily="Segoe WP" Opacity="0.25" Margin="5,0,0,0"/> <Image Grid.Row="0" Source="{Binding ImagePath}" Width="112" Height="173" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="5,0,0,0"/> </Grid> </DataTemplate> </ListBox.ItemTemplate> </ListBox> The animation isn't necessary, but it makes the app look more polished. It is very important to place the listbox in a viewbox in a scrollviewer. This is because the viewbox is necessary to adapt to small resolution changes that don't modify the amount of columns, and if you simply place the listbox in the viewbox, it's height will squash the entire control so that one doesn't need to scroll. So I disabled the listbox's internal scrollviewer, and wrapped one over the viewbox. I hope this helps the unexperienced programers (like me) and doesn't make you spend hours researching everything that I needed to make this relatively simple control.