用C#/WPF显示缩略图网格

3
在我的C# / WPF应用程序中,我想要显示来自图像文件目录树(目前为.bmp格式,但最终会有其他格式)的图像缩略图网格。未来,我可能会允许用户单击缩略图以查看更大的版本,或将鼠标悬停在上面以查看一些技术细节,但现在我只想显示缩略图。
图片数量是不可预测的,我的指示是如果有比屏幕可容纳的图片更多,则启用滚动(而不是缩小缩略图)。
我有一个递归例程来遍历树并识别要显示的文件...
   private bool WalkTree(String sRoot)
    {
        string sDirectoryName;
        string sFileName;
        int iDirectoryCount = 0;
        DirectoryInfo DirInfo;

        DirInfo = new DirectoryInfo(sRoot);
        // Get a list of all the files in this directory.
        foreach (FileInfo fi in DirInfo.GetFiles("*.bmp"))
        {
            sFileName = fi.Name;
            // DO SOMETHING WITH FILE FOUND HERE
        } 

        // Now get a list of all the subfolders in this directory.
        foreach (DirectoryInfo di in DirInfo.GetDirectories())
        {
            sDirectoryName = di.Name;
            WalkTree(sRoot + "\\" + sDirectoryName);  //recurse!!
            iDirectoryCount++;
        }
        return true;
    }  // End WalkTree 

那么,有什么好的方法可以做到这一点吗?我应该使用哪个XAML控件来放置所有这些内容?是网格(Grid)吗?我能添加行并在递归和发现更多文件时滚动它吗?还是我应该两次遍历树——一次获取计数并在页面中配置行和列,第二次实际显示缩略图?或者我对此的想法完全错误?我觉得这个问题在过去已经被解决了很多次,肯定有一个规范的设计模式,但我找不到。

1
使用一个UniformGrid作为ListBox的项面板。 - poke
@poke - 如果我使用数据绑定,所有的图像不都必须先加载到内存中吗?我知道如何将数据绑定到集合类和数据库,但我不确定如何将其绑定到一个目录树中的一组_n_图像文件,其中直到运行时才会发现文件数量和树的拓扑结构。 - user316117
1
这在很大程度上取决于您要绑定的数据。例如,您可以仅使用文件名填充列表。这样,该列表不会占用太多内存。 - poke
使用像@poke所说的ListBox,并设置IsVirtualizing = true。http://msdn.microsoft.com/en-us/library/system.windows.controls.virtualizingstackpanel.isvirtualizing(v=vs.110).aspx - TTat
为什么不使用“AllDirectories”选项,而是递归调用函数呢? foreach (FileInfo fi in DirInfo.GetFiles("*.bmp", SearchOption.AllDirectories)) - StillLearnin
显示剩余3条评论
1个回答

5

这些注释描述了如何进行布局部分。它相对容易,使用哪种解决方案取决于实际的布局。UniformGrid是可以的。在这里使用一个ItemsControl就足够了:

<ItemsControl ItemsSource="{Binding Images}">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <Grid>
                <ScrollViewer>
                    <UniformGrid />
                </ScrollViewer>
            </Grid>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Image Source="{Binding}" Width="100" Height="100" />
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl> 

问题是:绑定ItemsSource属性的Images是什么?你手头有一些文件名的字符串列表,但你需要的是包含ImageSource的集合。没有绕过将图片加载到内存中的方法。如果要处理大量且尺寸较大的图像,需要高效地进行处理,以避免应用程序消耗大量内存。一种选择是将图像缩小为缩略图。其次,当你缩放图像时,这也是一个很好的机会来裁剪它们,以获得固定的长宽比,并使网格看起来漂亮。
这里需要将Images作为BitmapImage的集合,以使其可以绑定到Image控件的ImageSource属性上。
public ObservableCollection<BitmapImage> Images { get; set; }

以下是翻译的结果:

这基本上就是传到// DO SOMETHING WITH FILE FOUND HERE的内容:

var image = CreateBitmap(path);
var width = image.PixelWidth;
var height = image.PixelHeight;

// Crop image (cut the side which is too long)
var expectedHeightAtCurrentWidth = width*4.0/3.0;
var expectedWidthAtCurrentHeight = height*3.0/4.0;
var newWidth = Math.Min(expectedWidthAtCurrentHeight, width);
var newHeight = Math.Min(expectedHeightAtCurrentWidth, height);            
var croppedImage = CropImage(image, (int)newWidth, (int)newHeight);

// Scale to with of 100px
var ratio= 100.0 / newWidth;
var scaledImage = ScaleImage(croppedImage, ratio);
Images.Add(scaledImage);

使用以下功能创建、缩放和裁剪图像:
private static BitmapImage CreateBitmap(string path)
{
    var bi = new BitmapImage();
    bi.BeginInit();
    bi.UriSource = new Uri(path);
    bi.EndInit();
    return bi;
}

private BitmapImage ScaleImage(BitmapImage original, double scale)
{
    var scaledBitmapSource = new TransformedBitmap();
    scaledBitmapSource.BeginInit();
    scaledBitmapSource.Source = original;
    scaledBitmapSource.Transform = new ScaleTransform(scale, scale);
    scaledBitmapSource.EndInit();
    return BitmapSourceToBitmap(scaledBitmapSource);
}

private BitmapImage CropImage(BitmapImage original, int width, int height)
{
    var deltaWidth = original.PixelWidth - width;
    var deltaHeight = original.PixelHeight - height;
    var marginX = deltaWidth/2;
    var marginY = deltaHeight/2;
    var rectangle = new Int32Rect(marginX, marginY, width, height);
    var croppedBitmap = new CroppedBitmap(original, rectangle);
    return BitmapSourceToBitmap(croppedBitmap);
}

private BitmapImage BitmapSourceToBitmap(BitmapSource source)
{
    var encoder = new PngBitmapEncoder();
    var memoryStream = new MemoryStream();
    var image = new BitmapImage();

    encoder.Frames.Add(BitmapFrame.Create(source));
    encoder.Save(memoryStream);

    image.BeginInit();
    image.StreamSource = new MemoryStream(memoryStream.ToArray());
    image.EndInit();
    memoryStream.Close();
    return image;
}

当我尝试粘贴XAML时,我遇到了可怕的System.Reflection.TargetInvocationException异常已被调用目标抛出。,就在我刚粘贴它的时候。 - user316117
嗯,我不知道。我已经使用了大部分的代码。你能打包一个示例项目吗?然后我会看一下它。 - Marc

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接