wpf - 类似 GoogleImages 的列表框,带有详细信息面板

标签 wpf listbox

您将如何设计类似 Google 图片的组件?

我不确定如何处理选择时显示在每个图像下方的详细信息面板。

详细信息面板:
- 显示在两行图像之间。
- 将左侧和右侧的元素固定到位。

enter image description here

解决方案:

感谢LittleBit为该问题提供了非常好的解决方案。根据他的提示,我创建了以下通用组件,负责处理在使 LittleBit 解决方案投入生产时最终会遇到的一些细节。

文件:DetailedList.cs

public class DetailedList : ListBox
{
    #region DetailsTemplate
    public DataTemplate DetailsTemplate
    {
        get { return (DataTemplate)GetValue( DetailsTemplateProperty ); }
        set { SetValue( DetailsTemplateProperty, value ); }
    }

    public static readonly DependencyProperty DetailsTemplateProperty =
        DependencyProperty.Register( nameof( DetailsTemplate ), typeof( DataTemplate ), typeof( DetailedList ) );
    #endregion

    static DetailedList()
    {
        Type ownerType = typeof( DetailedList );

        DefaultStyleKeyProperty.OverrideMetadata( ownerType,
            new FrameworkPropertyMetadata( ownerType ) );

        StyleProperty.OverrideMetadata( ownerType,
            new FrameworkPropertyMetadata( null, ( depObj, baseValue ) =>
            {
                var element = depObj as FrameworkElement;
                if( element != null && baseValue == null )
                    baseValue = element.TryFindResource( ownerType );

                return baseValue;
            } ) );
    }
}

文件:StretchGrid.cs

internal class StretchGrid : Grid
{
    private Expander _expander;

    #region ParentPanel
    public Panel ParentPanel
    {
        get { return (Panel)this.GetValue( ParentPanelProperty ); }
        set { this.SetValue( ParentPanelProperty, value ); }
    }

    public static readonly DependencyProperty ParentPanelProperty = DependencyProperty.Register(
        nameof( ParentPanel ), typeof( Panel ), typeof( StretchGrid ), new PropertyMetadata( null, ParentPanelChanged ) );

    private static void ParentPanelChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
    {
        var stretchGrid = d as StretchGrid;
        if( stretchGrid == null ) return;

        if( e.NewValue is Panel panel )
            panel.SizeChanged += stretchGrid.UpdateMargins;
    }
    #endregion

    public StretchGrid()
    {
        this.Loaded += StretchGrid_Loaded;
    }

    private void StretchGrid_Loaded( object sender, RoutedEventArgs e )
    {
        _expander = this.FindLogicalParent<Expander>();

        _expander.Expanded += UpdateMargins;
        _expander.SizeChanged += UpdateMargins;

        this.UpdateMargins( null, null );
    }

    private void UpdateMargins( object sender, RoutedEventArgs e )
    {
        if( ParentPanel == null ) return;
        if( _expander == null ) return;

        Point delta = _expander.TranslatePoint( new Point( 0d, 0d ), ParentPanel );

        //Create negative Margin to allow the Grid to be rendered outside of the Boundaries (full row under the item)
        this.Margin = new Thickness( -delta.X, 0, delta.X + _expander.ActualWidth - ParentPanel.ActualWidth, 0 );
    }
}

文件:DetailedList.xaml

 <Style x:Key="{x:Type cc:DetailedList}" TargetType="{x:Type cc:DetailedList}"
       BasedOn="{StaticResource {x:Type ListBox}}">

    <Style.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="NoButtonExpander.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Style.Resources>

    <Setter Property="Grid.IsSharedSizeScope" Value="True"/>
    <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled"/>
    <Setter Property="SelectionMode" Value="Single"/>

    <Setter Property="ItemsPanel">
        <Setter.Value>
            <ItemsPanelTemplate>
                <WrapPanel Width="{Binding ActualWidth, RelativeSource={RelativeSource AncestorType={x:Type cc:DetailedList}}}" 
                           Tag="{Binding RelativeSource={RelativeSource Self}}" />
            </ItemsPanelTemplate>
        </Setter.Value>
    </Setter>

    <Setter Property="ItemContainerStyle">
        <Setter.Value>
            <Style TargetType="{x:Type ListBoxItem}">
                <Setter Property="FocusVisualStyle" Value="{x:Null}"/>
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="{x:Type ListBoxItem}">

                            <Border Name="Border" SnapsToDevicePixels="True">
                                <Expander Style="{StaticResource NoButtonExpander}" VerticalAlignment="Top"
                                      IsExpanded="{Binding IsSelected, RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}}}" 
                                      Width="{Binding ActualWidth, RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}}}">

                                    <Expander.Header>
                                        <Grid>
                                            <Grid.RowDefinitions>
                                                <RowDefinition SharedSizeGroup="A"/>
                                            </Grid.RowDefinitions>

                                            <ContentPresenter Grid.Row="0"/>
                                        </Grid>
                                    </Expander.Header>

                                    <cc:StretchGrid ParentWrapPanel="{Binding Path=Tag, 
                                        RelativeSource={RelativeSource AncestorType={x:Type WrapPanel}}}">

                                        <ContentPresenter ContentTemplate="{Binding DetailsTemplate, 
                                            RelativeSource={RelativeSource AncestorType={x:Type cc:DetailedList}}}"/>

                                    </cc:StretchGrid>
                                </Expander>
                            </Border>

                            <ControlTemplate.Triggers>
                                <Trigger Property="IsSelected" Value="True">
                                    <Setter TargetName="Border" Property="Background"
                                            Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
                                </Trigger>
                            </ControlTemplate.Triggers>

                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </Setter.Value>
    </Setter>
</Style>

文件:NoButtonExpander.xaml

<Style x:Key="ExpanderRightHeaderStyle" TargetType="{x:Type ToggleButton}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type ToggleButton}">
                <Border Padding="{TemplateBinding Padding}">
                    <Grid Background="Transparent" SnapsToDevicePixels="False">
                        <ContentPresenter HorizontalAlignment="Center" Margin="0,4,0,0" Grid.Row="1" RecognizesAccessKey="True" SnapsToDevicePixels="True" VerticalAlignment="Top"/>
                    </Grid>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

<Style x:Key="ExpanderUpHeaderStyle" TargetType="{x:Type ToggleButton}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type ToggleButton}">
                <Border Padding="{TemplateBinding Padding}">
                    <Grid Background="Transparent" SnapsToDevicePixels="False">
                        <ContentPresenter Grid.Column="1" HorizontalAlignment="Left" Margin="4,0,0,0" RecognizesAccessKey="True" SnapsToDevicePixels="True" VerticalAlignment="Center"/>
                    </Grid>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

<Style x:Key="ExpanderLeftHeaderStyle" TargetType="{x:Type ToggleButton}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type ToggleButton}">
                <Border Padding="{TemplateBinding Padding}">
                    <Grid Background="Transparent" SnapsToDevicePixels="False">
                        <ContentPresenter HorizontalAlignment="Center" Margin="0,4,0,0" Grid.Row="1" RecognizesAccessKey="True" SnapsToDevicePixels="True" VerticalAlignment="Top"/>
                    </Grid>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

<Style x:Key="ExpanderHeaderFocusVisual">
    <Setter Property="Control.Template">
        <Setter.Value>
            <ControlTemplate>
                <Border>
                    <Rectangle Margin="0" SnapsToDevicePixels="true" Stroke="Black" StrokeThickness="1" StrokeDashArray="1 2"/>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

<Style x:Key="ExpanderDownHeaderStyle" TargetType="{x:Type ToggleButton}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type ToggleButton}">
                <Border Padding="{TemplateBinding Padding}">
                    <Grid Background="Transparent" SnapsToDevicePixels="False">
                        <ContentPresenter Grid.Column="1" HorizontalAlignment="Left" Margin="4,0,0,0" RecognizesAccessKey="True" SnapsToDevicePixels="True" VerticalAlignment="Center"/>
                    </Grid>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

<Style x:Key="NoButtonExpander" TargetType="{x:Type Expander}">
    <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
    <Setter Property="Background" Value="Transparent"/>
    <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
    <Setter Property="VerticalContentAlignment" Value="Stretch"/>
    <Setter Property="BorderBrush" Value="Transparent"/>
    <Setter Property="BorderThickness" Value="1"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type Expander}">
                <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" CornerRadius="3" SnapsToDevicePixels="true">
                    <DockPanel>
                        <ToggleButton x:Name="HeaderSite" ContentTemplate="{TemplateBinding HeaderTemplate}" ContentTemplateSelector="{TemplateBinding HeaderTemplateSelector}" Content="{TemplateBinding Header}" DockPanel.Dock="Top" Foreground="{TemplateBinding Foreground}" FontWeight="{TemplateBinding FontWeight}" FocusVisualStyle="{StaticResource ExpanderHeaderFocusVisual}" FontStyle="{TemplateBinding FontStyle}" FontStretch="{TemplateBinding FontStretch}" FontSize="{TemplateBinding FontSize}" FontFamily="{TemplateBinding FontFamily}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" IsChecked="{Binding IsExpanded, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" Margin="1" MinWidth="0" MinHeight="0" Padding="{TemplateBinding Padding}" Style="{StaticResource ExpanderDownHeaderStyle}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"/>
                        <ContentPresenter x:Name="ExpandSite" DockPanel.Dock="Bottom" Focusable="false" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" Visibility="Collapsed" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
                    </DockPanel>
                </Border>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsExpanded" Value="true">
                        <Setter Property="Visibility" TargetName="ExpandSite" Value="Visible"/>
                    </Trigger>
                    <Trigger Property="ExpandDirection" Value="Right">
                        <Setter Property="DockPanel.Dock" TargetName="ExpandSite" Value="Right"/>
                        <Setter Property="DockPanel.Dock" TargetName="HeaderSite" Value="Left"/>
                        <Setter Property="Style" TargetName="HeaderSite" Value="{StaticResource ExpanderRightHeaderStyle}"/>
                    </Trigger>
                    <Trigger Property="ExpandDirection" Value="Up">
                        <Setter Property="DockPanel.Dock" TargetName="ExpandSite" Value="Top"/>
                        <Setter Property="DockPanel.Dock" TargetName="HeaderSite" Value="Bottom"/>
                        <Setter Property="Style" TargetName="HeaderSite" Value="{StaticResource ExpanderUpHeaderStyle}"/>
                    </Trigger>
                    <Trigger Property="ExpandDirection" Value="Left">
                        <Setter Property="DockPanel.Dock" TargetName="ExpandSite" Value="Left"/>
                        <Setter Property="DockPanel.Dock" TargetName="HeaderSite" Value="Right"/>
                        <Setter Property="Style" TargetName="HeaderSite" Value="{StaticResource ExpanderLeftHeaderStyle}"/>
                    </Trigger>
                    <Trigger Property="IsEnabled" Value="false">
                        <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

最佳答案

我的方法是使用经过一些修改的ListView

  • ListViewItemsPanel 必须具有“wrap”功能,因此我使用 Wrappanel 在领域。
  • ListViewItemItemContainter 必须有两种状态。图像预览状态和详细 View 因此 Expander 应该可以解决问题。
  • 图像的详细信息必须显示在图像下的整个列中。我们需要在 ListViewItem 边界之外呈现的东西(稍后会详细介绍)

现在看起来像这样 Image Details messed up 由于边界,图像细节现在变得困惑。 Height 没有问题,因为它只是向下移动下一列,直到它适合。问题是 Width 及其位置(它没有左对齐)。

我创建了一个小型 CustomControl,即 StretchGrid,它在整个列上呈现其 Content 并左对齐。此 StretchGrid 获取到左边界的相对距离,并将其设置为负 Margin.Left 以正确渲染它。现在它看起来像这样(我希望这正是您正在寻找的)。 Image Details properly displayed

现在是ListViewStyle

<!-- Image List with Detail -->
<Style x:Key="PicList" TargetType="{x:Type ListView}">
    <!-- Only one Picture can be selected -->
    <Setter Property="SelectionMode" Value="Single"/>

    <!-- Enable Multi-Line with a WrapPanel Around the Items -->
    <Setter Property="ItemsPanel">
        <Setter.Value>
            <ItemsPanelTemplate>
                <WrapPanel Width="{Binding (ListView.ActualWidth),RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}}" Tag="{Binding RelativeSource={RelativeSource Self}}" />
            </ItemsPanelTemplate>
        </Setter.Value>
    </Setter>

    <!-- Override Display area of the Item -->
    <Setter Property="ItemContainerStyle">
        <Setter.Value>
            <Style TargetType="{x:Type ListViewItem}">

                <!-- Define Image Item -->
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="{x:Type ListBoxItem}">
                            <!-- Use Expander Header as Preview/Thumbnail and display Details below when expanded -->
                            <Expander x:Name="PicThumbnail" Style="{StaticResource NoButtonExpander}" IsExpanded="{Binding (ListViewItem.IsSelected), RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListViewItem}}}" Width="{Binding (ListViewItem.ActualWidth), RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListViewItem}}}">
                                <Expander.Header>
                                    <!-- Thumbnail/Preview Section -->
                                    <StackPanel>
                                        <Image Source="/XAML;component/Assets/Images/Thumb.png" Height="16" Width="16" />
                                        <Label Content="{Binding Name}"/>
                                    </StackPanel>
                                </Expander.Header>
                                <!-- Self stretching Grid (Custom Control) -->
                                <cc:StretchGrid x:Name="PicDetails" Background="LightGray" ParentWrappanel="{Binding Path=Tag, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type WrapPanel}}}">
                                    <!-- Picture Detail Section (quick & dirty designed, replace later) -->
                                    <Image ClipToBounds="False" Source="/XAML;component/Assets/Images/Highres.png" Width="128" Height="128" HorizontalAlignment="Left" Margin="10,0" />
                                    <Rectangle Fill="Black" Height="128" Width="5" HorizontalAlignment="Left"/>
                                    <Rectangle Fill="Black" Height="128" Width="5" HorizontalAlignment="Right"/>
                                    <StackPanel Margin="150,0">
                                        <Label Content="{Binding Name}"/>
                                        <Label Content="Description: Lorem"/>
                                        <Label Content="Category: Ipsum"/>
                                        <Label Content="Owner: Dolor"/>
                                        <Label Content="Size: 5kB"/>
                                    </StackPanel>
                                </cc:StretchGrid>
                            </Expander>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
                <!-- Brings selected element to front, details see edit -->
                <Style.Triggers>
                    <Trigger Property="IsSelected" Value="True">
                        <Setter Property="Panel.ZIndex" Value="1" />
                    </Trigger>
                </Style.Triggers>
            </Style>
        </Setter.Value>
    </Setter>
</Style>

注意:有必要添加对 StretchGrid 的引用。

以及StretchGrid自定义控件

class StretchGrid : Grid
{
    //Reference for parent expander (used to calculate Grid Margin)
    private Expander m_expander;

    //Property for relativesource Binding to Wrappanel in Style
    public WrapPanel ParentWrappanel
    {
        get { return (WrapPanel)this.GetValue(Wrappanel); }
        set { this.SetValue(Wrappanel, value); }
    }

    //DependencyProperty for RelativeSource Binding to Wrappanel in Style (Note: the Binding is set inside the Style, not here programmatically in PropertyMetaData)
    public static readonly DependencyProperty Wrappanel = DependencyProperty.Register("ParentWrappanel", typeof(WrapPanel), typeof(StretchGrid), new PropertyMetadata(null));

    //Constructor
    public StretchGrid() : base()
    {
        Application.Current.MainWindow.Loaded += Init;
        Application.Current.MainWindow.SizeChanged += UpdateMargins;
    }

    private void Init(object sender, RoutedEventArgs e)
    {
        m_expander = (Expander)this.Parent;                 //Change when xaml markup hirarchy changes
        if(m_expander != null)                              //(or make it similar to the Wrappanel with
        {                                                   //RelativeSource Binding)

            m_expander.Expanded += UpdateMargins;             //Update when expander is expanded
            m_expander.SizeChanged += UpdateMargins;          //Update when the expander changes the Size

            //Update all StretchGrids on Initialization
            UpdateMargins(null, null);
        }
    }

    //Calculate Grid Margin when an according Event is triggered
    private void UpdateMargins(object sender, RoutedEventArgs e)
    {
        if(ParentWrappanel != null)
        {
            Point delta = m_expander.TranslatePoint(new Point(0d, 0d), ParentWrappanel);

            //Create negative Margin to allow the Grid to be rendered outside of the Boundaries (full column under the Image)
            this.Margin = new Thickness(-delta.X, 0, delta.X + m_expander.ActualWidth - ParentWrappanel.ActualWidth, 0);
            //Theese Values arent calculated exavtly, just broad for example purpose
        }
    }
}

将其合并到现有代码中有点棘手。一个有效的示例可能会有所帮助,并且可以找到 HERE .

旁注:如果我有更多时间,我会将这些东西打包到更大的自定义控件中,以像普通的 UIElement 一样使用它(例如 Border)。这将大大提高可重用性和可用性。


编辑

ListView 中的“最新”元素也位于 Z-Index 的顶部,因此它位于展开的 StretchGrid 的“上方”,并且无法单击某些控件因为它们位于下一个 ListviewItem 的“后面”。

要解决此问题,请添加

<Style.Triggers>
    <Trigger Property="IsSelected" Value="True">
        <Setter Property="Panel.ZIndex" Value="1" />
    </Trigger>
</Style.Triggers>

ListViewItem样式。现在,当它可见时,它将把自己放置在其他控件的顶部。

关于wpf - 类似 GoogleImages 的列表框,带有详细信息面板,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52622015/

相关文章:

wpf - 获取 ProgressBar 来填充 StatusBarItem

c# - 如何跟踪 WPF 命令?

c# - WPF隐藏和显示按钮取决于datacontext的值

wpf - 让数据绑定(bind) WPF Listbox 生成子类 ListboxItems

javascript - 使用jquery取消确认后如何防止更改选择选项?

c# - WPF 用户控件依赖属性值

c# - ListView 中的 BindingGroup 与 DataTemplate

wpf - 单击按钮时对 WPF 列表框进行排序?

C# 数据库错误,从另一个列表框更改列表框中的项目

c# - 如何将 WPF 中的列表框绑定(bind)到通用列表?