1

I have a listview with 150 items. Each item has different background color. If I scroll down a vertical scroll bar of list view, I can separate items to three parts:

  1. top hidden items, items is hidden on the top of listview.
  2. displayed items.
  3. bottom hidden items, items is hidden on the bottom of listview.

I want to save all items to image.

I try to implement base on this guide How to render a WPF UserControl to a bitmap without creating a window MainWindow.xaml:

<Window x:Class="CaptureListEx.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:CaptureListEx"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="80"/>
        </Grid.RowDefinitions>
        <ListView Grid.Row="0"
                  Name="ListViewCtrl"
                  Margin="10"
                  BorderThickness="1"
                  ItemsSource="{Binding listViewItem}"
                  ScrollViewer.VerticalScrollBarVisibility="Auto"
                  ScrollViewer.HorizontalScrollBarVisibility="Auto">

        </ListView>
        <Button Grid.Row="1"
                Width="60"
                Height="30"
                Content="Capture"
                Name="Capture"
                Click="Capture_Click"
                >

        </Button>
    </Grid>
</Window>

MainWindow.xaml.cs:

using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace CaptureListEx
{
    public partial class MainWindow : Window
    {
        public ObservableCollection<string> listViewItem { get; set; } = new ObservableCollection<string>();
        public MainWindow()
        {
            InitializeComponent();
            DataContext = this;

            for(int i = 0; i <= 150; i++)
            {
                string temp = "This is item" + i;
                listViewItem.Add(temp);
            }
        }

        private void Capture_Click(object sender, RoutedEventArgs e)
        {
            SaveFileDialog dlg = new SaveFileDialog()
            {
                DefaultExt = ".jpg",
                Filter = "JPG image (*.jpg)|*.jpg|All files (*.*)|*.*"
            };
            Nullable<bool> result = dlg.ShowDialog();
            if (result == false) return;
            if (File.Exists(dlg.FileName) && new FileInfo(dlg.FileName).Length != 0)
                File.Delete(dlg.FileName);

            double actualWidth = ListViewCtrl.ActualWidth;
            ListViewCtrl.Measure(new System.Windows.Size(Double.PositiveInfinity, Double.PositiveInfinity));
            ListViewCtrl.Arrange(new Rect(0, 0, actualWidth, ListViewCtrl.DesiredSize.Height));
            double actualHeight = ListViewCtrl.ActualHeight;

            RenderTargetBitmap renderTarget = new RenderTargetBitmap((int)ListViewCtrl.ActualWidth, (int)ListViewCtrl.ActualHeight, 96, 96, PixelFormats.Pbgra32);

            renderTarget.Render(ListViewCtrl);

            PngBitmapEncoder encoder = new PngBitmapEncoder();
            encoder.Frames.Add(BitmapFrame.Create(renderTarget));

            using (System.IO.FileStream stream = new System.IO.FileStream(dlg.FileName, System.IO.FileMode.Create, System.IO.FileAccess.Write))
            {
                encoder.Save(stream);
            }
        }
    }
}

I expect I will get the image of all items with background color as displayed in UI, but the actual I only get image of displayed items and bottom hidden items.

Problem: Bottom hidden items do not have background color format. Top hidden items are not in image. Could someone please help me make the right image. Thanks!

Jacky
  • 15
  • 6
  • are you sure you want to create an image and not something more suitable for data storage like XML or xls? – Denis Schaf May 17 '19 at 11:38
  • @DenisSchaf Thank you. I really need to create image. – Jacky May 20 '19 at 02:01
  • @Sinatr Could you provide your code that resized ListView, drawn and saved it to good looking screen shot. Is it different my code ? – Jacky May 20 '19 at 02:10
  • No, I scroll ListView by Mouse. My test project is very simple. It has a Button and a ListView with 150 row items. click the button, It call function above to take image. @Sinatr did you try the same solution like my code and can't reproduce problem ? – Jacky May 20 '19 at 07:26
  • @Sinatr I haved create a simple project to focus this problem and update code in my post. I still get problem when scroll down scrollbar to middle and capture image – Jacky May 20 '19 at 10:20

1 Answers1

0

I only get image of displayed items and bottom hidden items

This happens because ListView when not limited by container dimensions, will only try to resize itself to fit as many items as it can starting from the first displayed item. In other words it doesn't try to somehow display "top hidden items" (how you call them).

I am not sure if this behavior is possible to change to make true auto-sizable ListView, but an easy workaround is to scroll to the very first item, make a screenshot and then restore position.

Add this before measuring (you will need FindChild method):

var scroll = ListViewCtrl.FindChild<ScrollViewer>(); 
var offset = scroll.VerticalOffset; // store offset
scroll.ScrollToTop();
Dispatcher.Invoke(() => { }, DispatcherPriority.Background); // do events

And then after render restore position:

scroll.ScrollToVerticalOffset(offset);

Why "do events"? You have to wait until wpf actually perform scrolling before measuring. As many things in wpf that one is also deferred, i.e. doesn't occurs instantly.

Sinatr
  • 20,892
  • 15
  • 90
  • 319