首页 > 解决方案 > 在 ListView 中动态调整图像大小

问题描述

我目前在使用 DataTemplate 将图像放入 WrapPanel 然后放入 ListView 的 ListView 中调整图像大小时遇到​​问题。这是程序的图像:

程序

我目前使用它作为我的数据模板:

<DataTemplate x:Key="ItemTemplate">
        <WrapPanel>
            <Image
                Width="300"
                Height="300" 
                Stretch="Fill" 
                Source="{Binding}"/>
        </WrapPanel>
    </DataTemplate>

然后我在我的 ListView 中使用这个数据模板:

<ListView Grid.Row="0" Grid.RowSpan="3"
              Name="MovieListView"
              ItemTemplate="{StaticResource ItemTemplate}"
              ItemsSource="{Binding Path = movie_posters_list}"
              Background="Black"
              SelectionChanged="MovieListView_SelectionChanged">
        <ListView.ItemsPanel>
            <ItemsPanelTemplate>
                <UniformGrid  Columns="6" HorizontalAlignment="Center"/>
            </ItemsPanelTemplate>
        </ListView.ItemsPanel>
    </ListView>

这是填充 ListView 源代码的代码:

public void LoadImages()
    {
        List<String> filenames = new List<String>(System.IO.Directory.EnumerateFiles(movie_poster_path, "*.jpg"));

        foreach (String filename in filenames)
        {
            this.movie_posters_list.Add(new BitmapImage(new Uri(filename)));
            Console.WriteLine("filename " + filename);
            Match regex_match = Regex.Match(filename.Trim(), regex_pattern);
        }
        MovieListView.ItemsSource = movie_posters_list;
    }

所以目前,我可以在 XAML 中编辑数据模板并更改图像的宽度和高度,它会在我加载程序时反映出来。我还可以更改 UniformGrid 中的列数,它会在我加载程序时反映出来。有什么方法可以在后面的代码中动态更改这些值,以便我可以滑动滑块来更改 UniformGrid 中的列的大小和数量?

问题补充: 这里我有3列,两边都有黑色空间,因为没有足够的空间来显示第四列。
3ColumnsWithBlackSpace 我想尝试调整逻辑,以便当我有 3 列时,它将调整图像大小,以便 3 列将水平填充可用空间。这个窗口永远不会像我全屏一样调整大小。

这是一个示例,我希望它显示为 4 列。
4列完美

如果我将滑块向下拖动一点,它将显示 4 列这样的列,这是我不想要的。
4列不完美

所以基本上我希望所有图像始终填充屏幕上的最大空间,我希望滑块在有足够空间容纳下一列图像时捕捉。

它的运行示例,当滑块一直向右(最大图像大小)时,它将完全适合 4 列,因为我将滑块向左拖动,它将捕捉到完全适合视图中 5 列的位置,并且很快。这可能吗?

我想显示的视觉效果:

最小列数:4

最大列数:11

当我移动滑块时,我希望它只允许显示如下图像:

4列:
4列

5列:
5列

6列:
6列

7列:
7列

8列:
8列

9列:
9列

10列:
10列

11列:
11列

标签: c#wpfimagexamllistview

解决方案


解决方案之一是创建转换器。

例子:

using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
using System.Windows.Markup;

namespace Converters
{
    public class WidthToColumnsConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            if (values?.Length == 2 && (values[0] is double widthImage) && (values[1] is double widthPanel))
                return (int)Math.Floor(widthPanel / widthImage);
            return DependencyProperty.UnsetValue;
        }

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
        public static WidthToColumnsConverter Instance { get; } = new WidthToColumnsConverter();


    }
    public class WidthToColumnsConverterExtension : MarkupExtension
    {
        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            return WidthToColumnsConverter.Instance;
        }
    }
}
    <Grid>
        <FrameworkElement.Resources>
            <DataTemplate x:Key="ItemTemplate">
                <Image Width="{Binding Value, ElementName=slider}"
                       Height="{Binding ActualWidth, RelativeSource={RelativeSource Self}}" 
                       Stretch="Fill"
                       Source="{Binding}"/>
            </DataTemplate>
        </FrameworkElement.Resources>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Slider x:Name="slider"
                Minimum="50"  Maximum="500" SmallChange="5" TickFrequency="5"
                Value="300"/>
        <ListView x:Name="MovieListView"
                  Grid.Row="1"
                  Grid.RowSpan="3"
                  ItemTemplate="{DynamicResource ItemTemplate}"
                  Background="Black"
                  SelectionChanged="MovieListView_SelectionChanged"
                  ItemsSource="{Binding Path = movie_posters_list}">
            <ListView.ItemsPanel>
                <ItemsPanelTemplate>
                    <UniformGrid  HorizontalAlignment="Center">
                        <UniformGrid.Columns>
                            <MultiBinding Converter="{cnvs:WidthToColumnsConverter}">
                                <Binding Path="Value" ElementName="slider"/>
                                <Binding Path="ActualWidth" ElementName="MovieListView"/>
                            </MultiBinding>
                        </UniformGrid.Columns>
                    </UniformGrid>
                </ItemsPanelTemplate>
            </ListView.ItemsPanel>
        </ListView>
    </Grid>

PS在确定元素的确切面板尺寸时存在细微差别。
在我展示的示例中,在某些情况下,宽度中的元素可能会采用稍大的尺寸,并且会显示一个水平滚动条。
这是因为该示例将父列表框作为计算的基础。
但它除了元素之外,还包含 Border 和 Vertical ScrollBar。

您可以使用此绑定消除 Border 的影响:

<ItemsPanelTemplate>
    <UniformGrid  HorizontalAlignment="Center">
        <UniformGrid.Columns>
            <MultiBinding Converter="{cnvs:WidthToColumnsConverter}">
                <Binding Path="Value" ElementName="slider"/>
                <Binding Path="ActualWidth"
                         RelativeSource="{RelativeSource Mode=FindAncestor,
                                                         AncestorType={x:Type ScrollContentPresenter}}"/>
            </MultiBinding>
        </UniformGrid.Columns>
    </UniformGrid>
</ItemsPanelTemplate>

但这仍然给出了大小而不考虑垂直滚动条占用的空间。我没有找到一种简单的方法来通过绑定为元素分配位置。您可以从宽度中减去一些常数以考虑垂直滚动条的大小。

        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            if (values?.Length == 2 && (values[0] is double widthImage) && (values[1] is double widthPanel))
                return (int)Math.Floor((widthPanel - 30) / widthImage);
            return DependencyProperty.UnsetValue;
        }

PSS转换器通过一个参数传递的值,面板的宽度必须减小。

    public class WidthToColumnsConverter : IMultiValueConverter
    {
        public static readonly DoubleConverter DoubleConverter = (DoubleConverter)TypeDescriptor.GetConverter(typeof(double));
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            double param = 0;
            if (DoubleConverter.IsValid(parameter))
                param = (double)DoubleConverter.ConvertFrom(parameter);
            if (values?.Length == 2 && (values[0] is double widthImage) && (values[1] is double widthPanel))
                return (int)Math.Floor((widthPanel - param) / widthImage);
            return DependencyProperty.UnsetValue;
        }

示例:

<ItemsPanelTemplate>
    <UniformGrid  HorizontalAlignment="Center">
        <UniformGrid.Columns>
            <!--The parameter value can be changed during the execution (Debug) of the project.
                This makes the process of selecting it for a specific layout very convenient.-->
            <MultiBinding Converter="{cnvs:WidthToColumnsConverter}"
                          ConverterParameter="35">
                <Binding Path="Value" ElementName="slider"/>
                <Binding Path="ActualWidth"
                            RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type ScrollContentPresenter}}"/>
            </MultiBinding>
        </UniformGrid.Columns>
    </UniformGrid>
</ItemsPanelTemplate>

补充:

它的运行示例,当滑块一直向右(最大图像大小)时,它将完全适合 4 列,因为我将滑块向左拖动,它将捕捉到完全适合视图中 5 列的位置,并且很快。这可能吗?

如果我理解正确,那么您需要调整图像的大小,以便它们加起来总是填满面板的整个宽度。
如果是这样,这是 UniformGrid 的默认行为。
您只需要从元素中删除显式尺寸并将面板的宽度绑定到为其提供的空间。

例子:

    <Grid>
        <FrameworkElement.Resources>
            <DataTemplate x:Key="ItemTemplate">
                <Image Stretch="Fill"
                       Source="{Binding}"/>
            </DataTemplate>
            <RelativeSource x:Key="ancestorScroll"
                            Mode="FindAncestor"
                            AncestorType="{x:Type ScrollContentPresenter}"/>
        </FrameworkElement.Resources>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Slider x:Name="slider"
                Minimum="50"  Maximum="500" SmallChange="5" TickFrequency="5"
                Value="300"/>
        <ListView x:Name="MovieListView"
                  HorizontalContentAlignment="Stretch"
                  VerticalContentAlignment="Stretch"
                  Grid.Row="1"
                  Grid.RowSpan="3"
                  ItemTemplate="{DynamicResource ItemTemplate}"
                  Background="Black"
                  SelectionChanged="MovieListView_SelectionChanged"
                  ItemsSource="{Binding Path = movie_posters_list}">
            <ListView.ItemsPanel>
                <ItemsPanelTemplate>
                    <!--Explicit setting of the Panel width from the size of the parent container.
                    If necessary, you can add a converter that corrects this value.-->
                    <UniformGrid  HorizontalAlignment="Center"
                                  Width="{Binding ActualWidth,
                                        RelativeSource={StaticResource ancestorScroll}}">
                        <UniformGrid.Columns>

                            <!--The parameter value can be changed during the execution (Debug) of the project.
                            This makes the process of selecting it for a specific layout very convenient.-->
                            <MultiBinding Converter="{cnvs:WidthToColumnsConverter}"
                                          ConverterParameter="35">
                                <Binding Path="Value" ElementName="slider"/>
                                <Binding Path="ActualWidth"
                                         RelativeSource="{StaticResource ancestorScroll}"/>
                            </MultiBinding>
                        </UniformGrid.Columns>
                    </UniformGrid>
                </ItemsPanelTemplate>
            </ListView.ItemsPanel>
        </ListView>
    </Grid>
</Window>

推荐阅读