February 2007 - Posts

Dispatch It

In WPF, like most UI frameworks, UI elements can only be updated from the thread they were created on. If you do background work, and want to affect the UI from a different thread, you'll have to dispatch it. The Dispatcher class has a CheckAccess() method (which is marked as EditorBrowsableState.Never, making it invisible to intellisense for some reason.)

Here's how you would normally use it:

delegate void CallMeDelegate(Button b);

void
CallMe(Button b)
{
    if (!Dispatcher.CheckAccess())
    {
        Dispatcher.Invoke(DispatcherPriority.Normal, new CallMeDelegate(CallMe), b);
        return;
    }

    b.Foreground = Brushes.Red;
}

You have to create a delegate, check for access and dispach if necessary. However, there's a smarter way, if you allow for a bit of Reflection:

void CallMe(Button b)
{
    if (UIHelper.EnsureAccess(MethodBase.GetCurrentMethod(), this, b))
    {
        b.Foreground = Brushes.Red;
    }
}

What MethodBase.GetCurrentMethod() does is actually give you a method descriptor of the calling method (quite useful for a few scenarios! Too bad they don't have a GetCallerMethod() as well...) The EnsureAccess() method then checks with the dispatcher if we're on the right thread, and if not, dynamically dispatches it.

Last note: Dispatchers run a prioritized queue, so it can be handy to set the DispatcherPriority to something other than Normal. For example, if you set a Dependency Property's value, and want to do something after the UI was updated, try dispatching it with ContextIdle priority.

For more information about Dispatchers, you should read Nick Kramer's whitepaper.

Update: I also discovered the existence of the DispatcherSynchronizationContext, which inherits from the good old SynchronizationContext of .NET 2.0. The original one uses ThreadPool to queue items, while the WPF one uses the Dispatcher mechanism, which is more suitable for WPF. Note that this way you cannot specify a priority. I still believe the way I described above is slightly better.

Another Update: I've changed a few things in the implementation. Now it checks whether the object it receives is a DispatcherObject, and if so, uses its Dispatcher instead of the Application's. This is good for (the rare) cases where your UI itself runs in more than one thread.

Posted by aelij with 1 comment(s)
Filed under: ,

BitmapEffect Begone

You can do really neat things with Bitmap Effects in WPF. Shadow, Bevel, Outer Glow can all have a great impact on how your application looks. But you should be aware that they don't come cheap. They are rendered in software, which yields very poor performance. Also, ClearType is turned off on elements that have them applied, so your text becomes blurry.

So, what to do?

  • Abstinence. Now, I'm not really a prude, but in this case, minimizing the use of bitmap effects can significantly improve performance.
  • Apply only on simple Visuals. Probably the most important advice here. If you want to apply an effect on a complex Visual, use layers! Apply the BitmapEffect on a simple Shape (e.g. Rectangle, Path), and use a Grid, for example, to position it below your complex Visual. Do not apply the effect on a Decorator (such as a Border) that contains your Visual, since that will cause the entire visual tree to suffer from the effect.
  • Avoid Animations. Especially on large Visuals, avoid animating the BitmapEffect's properties, and animating elements that have effects applied on them. TextBoxes, for example, animate the cursor frequently when they are focused, so if an element that has an effect contains a TextBox, it is forced to render itself entirely every blink.
  • Use Bitmaps. Yes, it's true bitmaps don't scale like vectors, but in some places they are a very viable alternative.

  • How nine-grid images work
    Use Nine-Grid Images. Have you ever wondered how the themes on XP and Vista work? You can stretch a button as much as you like and it still looks good. They use nine grid images. The idea is very simple: divide the image to nine areas and stretch it as shown in the illustration. The effect is that the proportions of the corners and borders are always maintained. I've attached a project with a NineGridBorder class that can be used to draw these images. After I wrote it, I discovered another implementation, so I took the best of both of them. 
  • Vectorize. When exporting from Expression Design to XAML, you have the option to either rasterize (i.e. create a bitmap) or vectorize some of the effects. For example, when applying soft edges to a vector drawing, ED exporter will create a Canvas that contains a few layers with different opacities, which will simulate the effect.
  • Use WpfPerf. This is a great tool that comes with the Windows SDK. You can use Perforator to detect whether your careless colleagues used BitmapEffects or other ill-advised features that may hinder your application's performance. Check "Draw software rendering with purple tint" to immediately view what causes problems. Try to resize the window or run animations while this is checked.
Posted by aelij with 4 comment(s)
Filed under:

Give me back my ClearType

WPF has a separate ClearType rendering system, which is better than GDI's (mostly because it also does y-direction antialiasing; read more here and in the WPF Text Blog.)

However, there are some situations in which WPF cannot use ClearType, and has to resort to grayscale antialiasing (it cannot render aliased text because of its pixel independent architecture) which comes out pretty blurry for small text sizes, in my opinion.

Here's when WPF can't use ClearType:

  • Window/Popup AllowsTransparency = true. This creates an HwndSource with UsesPerPixelOpacity = true (i.e. a layered window.) All context menus and tooltips in WPF have this turned on with no trivial way of turning it off. More on this later on.
  • Visual (or some ancestor) has a Bitmap Effect. (Sidebar: You should really avoid using those on any complex Visual. They yield the worse performance. If you want a shadow under something, take an empty Rectangle or some other Shape, apply the effect on it and put it under the more complex Visual.)
  • Text from another Visual appearing in a VisualBrush.
  • Rendering a Visual using RenderTargetBitmap.
  • Setting the HwndTarget.BackgroundColor to Transparent (needed for extending DWM glass to client area.)

Also, there are registry settings that can enable, disable or configure ClearType, both system-wide and WPF-specific.

In Vista, ClearType is turned on by default, so WPF also uses it. In XP, however, it is not. And so by default WPF is rendering grayscaled text. From my experience, ClearType is better even on CRT monitors, so when I write WPF applications, if ClearType is off, I display a message recommending to turn it on, and make the WPF-specific registry changes.

Now, for my woes. Take a look at these screenshots:

 

 

The left one is with ClearType, the right one without. I hope the difference is clear. As I mentioned earlier, context menus, tooltips and combo box use the Popup class, which has the AllowsTransparancy set to true. This is hardcoded. The reason for this is obvious: the designers of WPF wanted you to be able to customize these windows as you saw fit. And it can truly be used to do wonderful things (see this styled tooltips example. Quite effortless, if you consider what you had to do to get this done in Win32.) But I think readability is more important in these cases. At any rate, this should be configurable.

Aside from the text issues, layered windows' performance is much worse than normal windows. Even under Vista, where they are hardware-accelerated, the menu highlight is lagging after the mouse sometimes.

Frustrated a bit, I came up with a somewhat dubious solution to these issues.

For ComboBoxes and MenuItems, I created an attached Dependency Property, which, when attached to a control, attempts to find the "PART_Popup" in its template and set its AllowsTransparency property to false. Caveats:

  • You lose the animation when opening a combo box (slide) or a menu (fade).
  • You lose the shadow.
  • If you apply a transform, the popup will not match it (Then again, who would want a skewed combo box? But a rotated menu might be useful.)

For ContextMenus and ToolTips, I create subclasses, overrode IsOpenProperty metadata and added an additional changed handler. The Framework Property Metadata documentation states that:

The actual property system behavior for PropertyChangedCallback is that implementations for all metadata owners in the hierarchy are retained and added to a table, with order of execution by the property system being that the most derived class's callbacks are invoked first. Inherited callbacks run only once, counting as being owned by the class that placed them in metadata.

Either I don't understand it well or the documentation is wrong, since the method I specified ran after the original method. To solve that, I wrote a class that inherits FrameworkPropertyMetadata and reverses the execution order, so I could create the Popup myself without setting the AllowsTransparency to true. Caveats:

  • I use reflection to get to private fields. Yes, I know it's bad... :P
  • Again, you lose shadows and animations.
  • Tooltips have rounded corners by default (at least on Vista) so you'll see gray 1-pixel dots on the corners. But you can change the tooltip's default style to get rid of this.

I tried to regain the shadows using cheaper means (CS_DROPSHADOW window class style) but it's difficult to reach.

You may think I'm crazy to go through all of that just for a few blurry texts, but I think this really impacts the overall readability of my applications.

Posted by aelij with 7 comment(s)
Filed under: ,
More Posts