DCSIMG
July 2012 - Posts - Pavel's Blog
Sign in | Join | Help

Pavel's Blog

Pavel is a software guy that is interested in almost everything
software related... way too much for too little time

July 2012 - Posts

WPF 4.5: Accessing bound collections on non UI threads

Published at Jul 21 2012, 12:23 PM by pavely

The single threaded behavior of WPF (and other UI technologies) requires that anything tied to the user interface be manipulated on the UI thread, incuding data bound objects. There are several ways to do that, assuming the code is on a non-UI thread, such as using Dispatcher.(Begin)Invoke, capturing and using the current SynchronizationContext, etc.

Specifically, if some collection is data bound, items cannot be added or removed from it from a non-UI thread. WPF 4 (and earlier) throws an exception, because the data binding mechanism expects to be notified of changes (e.g. ObservableCollection<T>) on the UI thread.

One of the improvements in WPF4.5 is the ability to manipulate bound collections on a non-UI thread without requiring marshalling the manipulating code to the UI thread. The way to do that is not obvious, however.

Let’s assume we have a simple ObservableCollection<int> that is bound to some ListBox named _list. To enable manipulation on a non-UI thread, we must first define a lock object to be used by our and WPF’s code so that the collection is not corrupted. To do that we need to call the static BindingOperations.EnableCollectionSynchronization method, providing the collection in question and the object to be used as a lock. Here’s a complete example in the code behind file:

  1. public partial class MainWindow : Window {
  2.     ObservableCollection<int> _numbers = new ObservableCollection<int>();
  3.     object _lock = new object();
  4.  
  5.     public MainWindow() {
  6.         InitializeComponent();
  7.  
  8.         _list.ItemsSource = _numbers;
  9.         BindingOperations.EnableCollectionSynchronization(_numbers, _lock);
  10.  
  11.         ThreadPool.QueueUserWorkItem(_ => {
  12.             var rnd = new Random();
  13.             for(; ; ) {
  14.                 lock(_lock) {
  15.                     _numbers.Add(rnd.Next(1000));
  16.                 }
  17.                 Thread.Sleep(1000);
  18.             }
  19.         });
  20.     }
  21. }

The lock itself can be any object, provided it’s a reference type, for which System.Object fits perfectly, as it’s always a reference type. This is the concept for the C# lock keyword (calling Monitor.Enter/Exit behind the scenes). Notice also that although we’re providing the lock to WPF, we still need to use it explicitly in our non-UI thread manipulating code to ensure the collection is not corrupted.

This feature can certainly improve performance because it does not require thread switching for every update. It does require locking, however, which is not ideal, but merely a fact of life in a multithreaded world.

Windows 8 Metro: Detecting scroll changes in ListView

Published at Jul 02 2012, 09:27 PM by pavely

I had a requirement in a Metro app I’m working on to detect scrolling in a ListView (GridView is practically the same), or more precisely, detect whether the selected item goes off the visible ListView area, and if so, switch some items in the ListView so that the selected item be visible again; this is not an entirely accurate description, but it’s close enough for our purposes. An easy one, right?

Searching the ListView class (and its bases) yields no useful results on scrolling. In WPF, the ScrollViewer element has an attached event, ScrollChanged. This can be used (in WPF) to detect scrolling on any control that has a ScrollViewer as part of its control template (such as a ListBox).

Surprisingly (at least it was for me), the WinRT ScrollViewer has no such event. This makes it hard to detect scroll changes.

Unless I missed something obvious (which I don’t think I did), I had to try something unorthodox. I expected at least to get a chance to ask for the scroll position. ScrollViewer has a VerticalOffset and HorizontalOffset properties, which I theoretically could have used with a timer, to detect changes in the vertical scroll offset; but alas, in WinRT it’s not an attached property as it is in WPF. Bummer!

So, how can we solve this without creating a new template and hook into that?

I created a DispatcherTimer to expire every second, and when it did, I calculated the location of the selected item like so:

 

  1. auto view = _cvs->View;
  2. auto xform = _listView->TransformToVisual((UIElement^)_listView->ItemContainerGenerator->ContainerFromIndex(view->CurrentPosition));
  3. auto point = xform->TransformPoint(Point(0, 0));

Yes, this is C++/CX code… (see my previous post on that), but in this case (at least), the translation to C# is straightforward.

The view variable is an ICollectionView (returned from a CollectionViewSource object (_cvs)). TransformToVisual returns a GeneralTransform object that we can use to translate Points and Rects relative to the specified element (in this case the selected item). Transforming the origin point (0,0) of that element returns a new Point; if this point falls outside the boundaries of the ListView, then some scrolling occurred that obscured the selected item.

I find it annoying that Windows 8 with WinRT is generally less powerful than WPF. I expect a new platform to be more powerful and flexible, not less so. Granted, there are some new niceties that I wish WPF had (such as Transitions), but overall, I feel a lot is missing relative to WPF (not so much relative to Silverlight, but even Silverlight 5 is more powerful – the DataTemplate.DataType property is an obvious example).

Hopefully, things will improve at RTM time (and beyond).