csammisrun

A rare situation

WPF versus IM typing notifications: how to type a 10 word sentence, get some coffee, and continue watching it render

with 5 comments

Hello and welcome to what I hope will be several write-ups on some of the stranger things that happen in shaim. As the project barrels towards its next significant release and we gain both developers and users, the system as a whole is undergoing something of a critical examination. Today’s topic concerns typing in the conversation window, something that until recently has been an extremely slow experience and every shaim user feedback to date has called out as being a barricade to using it on a day to day basis.

WPF and text input

shaim’s UI is written in Windows Presentation Foundation, which is pretty great for displaying all manner of things in very shiny ways, but has some problems with input and rich content input in particular. shaim, for better or for worse, uses a great deal of rich content input by way of the RichTextBox (RTB) class, extended by shaim to include support for HTML.

“What sort of problems does WPF have with text input?”

In general, it’s slow. In an effort to support everything under the sun, the WPF implementation of the RTB seems to have outgrown itself. Projects like SharpDevelop that require specialized text editing with rich content support tend to roll their own. There’s a noticeable lag between typing a stream of characters and seeing the results appear, and doing anything in an event handler on text input events naturally slows the process a bit more.

shaim and typing notifications

Most IM networks support the concept of typing notifications, small notification packets that indicate when a user is typing to another user. In general, this is a pretty simple concept: send a notification when typing starts, send one when typing stops. If the actual message is received, the client can assume that means typing has stopped (until the next notification is received). shaim’s typing notification support is based around AIM’s very special implementation of the feature, which features not two but three notification types:

  • Typing started: pretty clear
  • Typing paused: there is text in the user input field, but it hasn’t been sent yet
  • Typing stopped: the text field has been cleared

shaim handles the RTB’s TextChanged event to provide support for sending typing notifications. The basic flow is as follows:

In TextChanged event:
  Is text blank?
    Send TypingStopped
  If text is not blank and TypingStarted has not been sent
    Send TypingStarted
    Start a 2 second timer
      When the timer elapses, send TypingPaused unless TypingStopped was sent in the interim

Pretty simple, and it’s hard to boil that down any more with the three-state model. But it was dog slow, even slower than usual, and here’s why:

Is text blank?

It turns out that this is a bit more of a puzzler than you might expect. The RTB doesn’t expose a Text property, unlike a normal TextBox, but that fact itself isn’t unexpected: an RTB can hold markup, images, and all sorts of things that don’t belong in a string. Even so, it would be nice to have the plain text for various reasons. shaim’s RichEditControl fakes the funk by exposing a PlainText property that extracts the text content from the RTB.

So the test for blank text became

if (UserInput.PlainText.Length == 0)

Great, until you realize that this is being called in an event handler that is raised on every text change event – every single keystroke. The text extraction process isn’t cheap, and profiling showed that this caused a great deal of the text entry slowdown.

The next idea was to give the RichEditControl a new property, IsEmpty, that simply compared the RTB’s ContentStart and ContentEnd TextPointers to see if they were equal. If they were equal, the content was zero-length and the RTB was empty.

if (UserInput.IsEmpty)

Though it was indeed faster code-wise, there was still a very perceptible typing lag.

Okay, just forget it

It turns out that accessing any of the RTB properties from an event handler chain that is updating those properties isn’t cheap and was causing what amounted to unacceptable lag. The final solution? Don’t use the editor’s properties to determine when to send typing notifications.

In TextChanged event:
  If TypingStarted has not been sent
    Send TypingStarted
    Start a 2 second timer
      When the timer elapses, send TypingPaused unless TypingStopped was sent in the interim

Note the absence of the check on the text length…whoops, and also the absence of the TypingStopped notification. This has a strange effect in clients that support all three notification types: Pidgin turns a conversation tab orange when it gets TypingPaused and won’t reset it until TypingStopped (or a new message) is received, and chatting with a shaim client results in a rather orange tab a lot of the time. To compensate, a second timer is used:

In TextChanged event:
 ...
    When the timer elapses, if TypingStopped was not sent in the interim
      Send TypingPaused
      Start a 10 second timer that sends TypingStopped when it elapses

Conclusion: Don’t touch WPF input controls in tight loops, and AIM’s indelible mark

Is it a perfect solution? Well no, not completely, but the three-state notification model isn’t a perfect solution either. Most protocol plugins actually boil it down to only the TypingStarted and TypingStopped states, translating TypingPaused to TypingStopped. Solving this problem made me go back and think about why we’re even supporting the three-state model, and it turns out that shaim’s UI supports it because AIM supports it (shaim was originally AIM-only). It’s entirely possible that we’ll remove it from the framework at some point.

Written by Chris

March 22nd, 2008 at 4:51 pm

5 Responses to 'WPF versus IM typing notifications: how to type a 10 word sentence, get some coffee, and continue watching it render'

Subscribe to comments with RSS or TrackBack to 'WPF versus IM typing notifications: how to type a 10 word sentence, get some coffee, and continue watching it render'.

  1. Why not just check every two (or whatever) seconds to see if the text is blank or if it hasn’t changed since the last check? Or reset the timer in the TextChanged event? (That might be slow, I am not a .NET expert.)

    Kurper

    23 Mar 08 at 10:14 am

  2. We actually do reset the timer that fires TypingPaused in the TextChanged event to ensure that it doesn’t get fired until the user’s really done typing, it’s not expensive. Checking whether the text is blank in that timer event and sending TypingPaused or TypingStopped accordingly is a good idea :) If we stick with the three-state notification model, that’ll be how we end up implementing it.

    That said, I’m still personally leaning towards cutting down to the two-state model just to reduce complexity all around. It’s an interesting question though – should the shaim framework support most expansive of all its protocols’ solutions and let the individual drivers deal with it, or should it support the least common denominator, reducing complexity but possibly edging out some feature support on one or two networks? Open ended debates itt

    Chris

    26 Mar 08 at 10:43 am

  3. [...] the last post about some of the odd things that shaim does, I talked about how WPF’s rich text input makes [...]

  4. [...] WPF versus IM typing notifications [...]

  5. [...] WPF versus IM typing notifications [...]

Leave a Reply