viernes, 9 de abril de 2010

Problems with bound FlowDocuments in ListViews

I’ve been working with WPF for a time. And it’s a technology I’ve fallen in love with. It’s really powerful, yet it is really simple in some ways. Requires a thinking switch. If you have worked with Windows Forms, you’ll tend to  think Windows-Forms-like, so you need to learn to think WPF-like.

However, I have encountered a problem that can arise when you bind a list of a custom entity that has a FlowDocument property to a ListView. Let me try to explain.

I create a custom entity, like this one:

public class MyEntity
    {
        private FlowDocument _myFlowDocument;

        public FlowDocument MyFlowDocument
        {
            get
            {
                return _myFlowDocument;
            }
            set
            {
                _myFlowDocument = value;
            }
        }
    }

then, in my Window, I create a listview bound to a list of this custom entity. Like this:

<ListView ItemsSource="{Binding Path=MyList, ElementName=mainWindow}">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <FlowDocumentScrollViewer MaxHeight="200" Document="{Binding MyFlowDocument}" />
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>

 

Then you run the application, and you see the list with the flowdocuments, which is nice. You scroll down to see all the items (I have created 200 in my test). And it works. You scroll up again and the application explodes, with an exception that says “Document belongs to another FlowDocumentScrollViewer already.” Huh? What’s happening?

The problem happens because of 2 different things:

  • FlowDocument is a little special and keeps its own state when bound. Trying to bind a single FlowDocument to 2 controls throws that exception
  • ListView, by default uses a VirtualizingStackPanel. Meaning that it only creates controls (based on its template) that are inside its viewport. Items not yet visited don’t have their controls created, and items that go out of scope have their controls destroyed.

So what happens is quite obvious. When you start the application, first items have their templates rendered and they bind their FlowDocument. When you scroll down, that first items go out of scope and the controls are destroyed. When you scroll up again, controls are created again, but the same old controls, new ones. And when they try to bind to FlowDocument, they remember it is already bound.

My solution is to disable VirtualizingStackPanel for that ListView, with the attached property VirtualizingStackPanel.IsVirtualizing="False". Warning: This causes the whole visual tree to be created just once in the beginning, causing the application to start a little more slow, and to use more memory. However, there are times that you haven’t many options.

Etiquetas: