views:

379

answers:

1

I am learning how to use data binding in WPF for a TreeView. I am procedurally creating the Binding object, setting Source, Path, and Converter properties to point to my own classes. I can even go as far as setting IsAsync and I can see the GUI update asynchronously when I explore the tree. So far so good!

My problem is that WPF eagerly evaluates parts of the tree prior to them being expanded in the GUI. If left long enough this would result in the entire tree being evaluated (well actually in this example my tree is infinite, but you get the idea). I would like the tree only be evaluated on demand as the user expands the nodes. Is this possible using the existing asynchronous data binding stuff in the WPF?

As an aside I have not figured out how ObjectDataProvider relates to this task.


My XAML code contains only a single TreeView object, and my C# code is:

public partial class Window1 : Window
{
    public Window1() {
        InitializeComponent();

        treeView.Items.Add( CreateItem(2) );
    }

    static TreeViewItem CreateItem(int number)
    {
        TreeViewItem item = new TreeViewItem();
        item.Header = number;

        Binding b = new Binding();
        b.Converter = new MyConverter();
        b.Source = new MyDataProvider(number);
        b.Path = new PropertyPath("Value");
        b.IsAsync = true;
        item.SetBinding(TreeView.ItemsSourceProperty, b);

        return item;
    }

    class MyDataProvider
    {
        readonly int m_value;

        public MyDataProvider(int value) {
            m_value = value;
        }

        public int[] Value {
            get {
                // Sleep to mimick a costly operation that should not hang the UI
                System.Threading.Thread.Sleep(2000);
                System.Diagnostics.Debug.Write(string.Format("Evaluated for {0}\n", m_value));
                return new int[] {
                    m_value * 2, 
                    m_value + 1,
                };
            }
        }
    }

    class MyConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
            // Convert the double to an int.
            int[] values = (int[])value;
            IList<TreeViewItem> result = new List<TreeViewItem>();
            foreach (int i in values) {
                result.Add(CreateItem(i));
            }
            return result;
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
            throw new InvalidOperationException("Not implemented.");
        }
    }    
}

Note: I have previously managed to do lazy evaluation of the tree nodes by adding WPF event handlers and directly adding items when the event handlers are triggered. I'm trying to move away from that and use data binding instead (which I understand is more in spirit with "the WPF way").

+3  A: 

A generic solution (as I'm not sure if your code is not mock)

  1. Create your model containing parent and children (In this case it is an int and a list of int)
  2. Create a ViewModel having a property IsExpanded in addition to the Model's properties
  3. Bind your IsExpanded property to the TreeViewItem's IsExpanded property in the view(xaml)
  4. In the IsExpanded property's setter, fill in your Children list using the Dispatcher, the priority being Background. Each addition of item into your Children List should trigger the PropertyChanged event.

Check out the MVVM design pattern, if you're not familiar with. Here is a good video by Jason

Veer
This was an excellent video. I'll wait until I digest the content and get a working app before I accept the answer.
pauldoo
The video shows how to have asynchronous populating of things to the View layer as the Model layer does background work.
pauldoo
@pauldoo: Finally, you got it working?
Veer
My solution was to use the MVVM model for the async updates as done in the video linked by you. This combined with some ModelView bindings for "IsExpanded" to trigger the background work, and everything is great. :)
pauldoo