Click here to Skip to main content
15,512,632 members
Articles / Desktop Programming / WPF
Article
Posted 11 Sep 2014

Stats

47.8K views
913 downloads
58 bookmarked

MathBinding

Rate me:
Please Sign up or sign in to vote.
4.99/5 (44 votes)
22 Jun 2016CPOL20 min read
Learn how to create a math expression compiler and a special WPF markup extension capable of using it to generate bindings.
XML
<Image 
  Source="{Binding Content}" 
  Canvas.Left="{Pfz:MathBinding [CenterX]-[Width]/2}"
  Canvas.Top="{Pfz:MathBinding [CenterY]-[Height]/2}"
  Width="{Binding Width}" 
  Height="{Binding Height}"
  />

Introduction

In this article I will present a WPF markup extension that allows users to declare bindings using complex math formulas.

The article is divided in two main parts:

  1. How to use the MathBinding;
  2. How it works under the hood.

The first part is probably what most developers will want, even those who don't have experience with WPF. The reason is simple: Using the MathBinding developers will be able to avoid the creation of lots of converter classes, avoiding the need to instantiate those converters as resources in the XAML, to reference them as converters in the bindings and making those bindings easier to read.

The second part, well, it is an advanced topic. It explains how to parse the math expressions, tokenize them, parse the tokens, generate LINQ expressions (that are compiled), to finally explain how to make the markup extension capable of using all this. Yeah, it is a lot and it is complex, yet I hope I am making it simpler than most documents that explain how to create a compiler.

1. Using the MathBinding

Starting from the most basic items, you need to add the libraries Pfz.MathEvaluation.dll and Pfz.MathEvaluation.Wpf.dll to your project. You can optionally add the MathParser.cs, the MathVariable.cs and the MathBinding.cs files directly into your project and avoid the DLL references.

Then, in any XAML that's going to use the MathBinding you will need to add its namespace. For example, in the sample application you will see this code in the MainWindow.xaml:

XML
<Window x:Class="MathExpressionEvaluatorSampleWPF.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:App="clr-namespace:MathExpressionEvaluatorSampleWPF"
        xmlns:Pfz="clr-namespace:Pfz.MathEvaluation.Wpf;assembly=Pfz.MathEvaluation.Wpf"
        Height="350" Width="525" Title="MathExpression Test Application"
        >

The bolded line shows the use of the namespace where the MathBinding is found. In this case, I used the name Pfz to reference the namespace, but you are free to use any name you want. Also, it is important to adapt the clr-namespace or the assembly declaration if you decide to put the files directly in your project or if you decide to rename the namespaces.

Considering you keep the Pfz name, you can declare any math binding by using Pfz:MathBinding expression. To see a real example, it can be like this:

XML
<TextBlock Text="{Pfz:MathBinding [Width]*[Height]}"/>

In the previous example, the expression will multiply the value coming from a property named Width with the value from a property named Height of the DataContext of the TextBlock.

What can we do with the MathBinding?

The MathBinding supports 5 operators (addition +, subtraction -, multiplication *, division / and modulo %), constant values, defining precedence with parenthesis, any property path by writing it inside square brackets (so, using [Property.SubProperty.Array[0]] is valid if such path is valid in a normal binding) and it can support any function that you register as long as such function uses only double as the input parameter types (if it has input parameters at all) and returns double.

To register functions, we should use the MathParser.StaticRegisterFunction() before the binding is loaded (so, registering all the math functions during the initialization of the application is a good idea).

For example, to register the Abs function, we can do:

C#
MathParser.StaticRegisterFunction(Math.Abs);

Actually the StaticRegisterFunction has many overloads, but all of them are there only to make it simpler to register functions from 0 to 3 parameters without declaring the delegate type explicitly.

If you want to register a function with more parameters, for example, you can use the more basic version of the StaticRegisterFunction, which receives any Delegate, giving the delegate type you want. For example:

C#
MathParser.StaticRegisterFunction(new MyDelegateWith10Parameters(MethodWith10Parameters));

And, to avoid naming conflicts as the math parser doesn't support function overloading or simply to give a specific name to your function (needed if you use anonymous delegates) you can give the name as the first parameter. For example:

C#
MathParser.StaticRegisterFunction
(
  "IsGreaterThan",
  (x, y) =>
  {
    if (x > y)
      return 1.0;

    return 0.0;
  }
);

When using a function that receives more than one parameter in the math binding, you should put the entire expression inside single quotes, or else the compiler will complain that the MathBinding doesn't have a constructor that receives 2 (or more) parameters.

So, to use the IsGreaterThan function, we can use this line:

XML
<Label Content="{Pfz:MathBinding 'IsGreaterThan([Width], [Height])'}"/>

Data Source

By default, like any binding, the data source comes from the object's DataContext property. But we have two ways to set different data sources:

  1. ElementName: By setting this property, all the paths inside the square brackets will be considered to come from that source. For example:

    XML
    <Label Content="{Pfz:MathBinding ElementName=alternativeSource, Expression=[ActualWidth]*2}"/>
  2. Sub-tags: By declaring the bindings as a sub-tag, we are free to declare more inner bindings. This is quite similar to how MultiBindings work, so each sub-binding can have its own source. This version is more verbose, but allows you to use many different sources in the same final expression. It is important to note that the variables that will hold the values for those sub-bindings will be named b0, b1, b2 etc. It is also possible to mix bindings declared in square brackets and bindings declared as sub-bindings. Example:

    XML
    <Label>
      <Label.Content>
        <Pfz:MathBinding Expression="[Width]-b0">
          <Binding ElementName="labelOtherSource" Path="ActualWidth"/>
        </Pfz:MathBinding>
      </Label.Content>
    </Label>

C# Code

If you want to create a MathBinding from the code behind, you can. You should use the MathBinding.Create() method to achieve this. The Create() method is what the ProvideValue() calls to create the real binding, so it is preferable that you call it directly instead of creating a MathBinding to then call its ProvideValue. Everything that you can do with the XAML can be done in the C# code and I really believe the C# version has some advantages when setting different sources.

In the sample application you can find this code:

C#
var actualHeightBinding = new Binding("ActualHeight");
var mathBinding = MathBinding.Create("Sqrt(b0-[Width]+[Width])", actualHeightBinding);

Obviously doing a -[Width]+[Width] becomes a no-operation (we subtract and then add the same value) but it is in the sample only to show that you can continue to use the normal way of writing bindings, plus the code-behind specific way of declaring bindings which, in this case, is the actualHeightBinding that must be accessed by the name b0.

The MathParser class

Well, you probably saw that there are two DLLs. One is the DLL with the MathBinding class, which is WPF specific, and the other is the one that does all the math parsing and is not WPF specific.

The MathBinding only does the job of translating things like [Width] into a variable name (like b0). The real math parsing (which actually involves a real compilation) is done by the MathParser class, and that's why you need to register the functions to the MathParser class, not to the MathBinding.

The MathParser class can be used directly if you simply need to parse math expressions for other reasons that aren't binding related.

The important things to know about the MathParser are:

  • You can register functions to specific instances. You are not forced to register all functions using the StaticRegisterFunction;
  • The variables you want to use from the expression are declared by using the DeclareVariable method. The variable name must start with a lower-case character. You must store the object returned by such method, as you need to change the Value of such a returned object to affect the variable of the compiled expression;
  • You can get a LINQ expression from a string expression, before it is compiled, if you want to have a LINQ expression for any reason;
  • Two or more expressions can be compiled using the same MathParser and they will share the MathVariable instances, so changing the variable for one of the generated delegates will affect the value of the other delegate if it is executed so you should usually only compile a single string math expression per MathParser, yet you are free to use it differently.

Performance

The algorithm is pretty slow for a single evaluation. This happens because it is slower to create a LINQ expression than it is to simply calculate the final value, as the expression generation involves creating new objects. Also, the LINQ expression is not immediately executable, so we need to compile such expression, wasting time again.

Yet, in the end there's a delegate that executes compiled code, avoiding any new parsing and being extremely fast. So, if you compile the expression once and then execute it many, many times, it will be faster than reparsing the expression every time. If you want a single execution, well, at this moment there's no optimization for this. It will be slow. Yet, slow is a relative term, as you will probably don't notice anything if you compile only 5 or 10 expressions. It will be slow if you compile thousands of expression and use them only once.

Fortunately, bindings compile their expressions only once and reutilise the compiled expression to recalculate their values when there's a change to a property they are bound to.

2. Under the hood

The easy part is finished. If you only want to use the library, you can download the sample application and you can start writing your own MathBinding expressions.

Now it is the moment in which I will explain how to create such a solution. I am trying to make it simple but, as any compiler, it is an advanced topic.

How it works?

The algorithm is divided in three steps:

  1. The string expression is split into tokens;
  2. The tokens are visited generating a LINQ expression;
  3. The LINQ expression is compiled.

If the LINQ expressions didn't exist, I would probably create my own classes that represent the same thing and I would need to make make all of them capable of compiling their content to a DynamicMethod. Even if this looks hard, it is actually easier than the parsing, yet it will be boring. Fortunately, I avoided this fatigating step by using LINQ expressions.

So, let's undertand each step.

Step 1 - Tokenization

-x * Sqrt(y) + 5.7 / (-y + x)

The first step is tokenization. During this step we want to isolate and categorize each entity. For example, in the previous expression, the Sqrt is composed of four characters, yet we consider it as a single token, of type function call. The 5.7 is in a similar situation, being composed of 3 characters and representing only a single token, of type value.

There are cases in which a single character really represents a single token, be it in a small value, like 1 (yet numbers are of variable size) or on specific situations, like the operators and parenthesis. The important thing is that we split those tokens, so in the next step we will be able to go from one token to the next (or even to the previous) without having to deal with the string anymore, so we can advance to the next token or return to the previous one by adding one or subtracting one from an index.

Also, it is important to note that spaces don't count as a token (yet they end a token, so 1 1 will not be interpreted as 11).

So, an expression like:

C#
1+     57.12345/Sqrt(x)
// I know, it has many unused spaces

Will generate the following tokens:

Type Content (if any)
Value 1
Addition  
Value 57.12345
Division  
FunctionName Sqrt
OpenParenthesis  
VariableName x
CloseParenthesis  

To make things pretty simple, I am immediately capable of identifying function names and variable names without having to evaluate if the next token is a parenthesis opening. Uppercase starting words are always function names while lowercase starting words are always variable names. It was my option to do this to make the next step simpler. Most programming languages use the same token type for both string types (lowercase and uppercase starting words), making the next step more complicated.

Note: Some very optimized parsers could read each token as part of the second step, having a special logic if they ever need to return to a previous token. Yet I prefer to read the tokens first, generating a list, then work over the list of tokens in the second step. This allows me to read my own code with ease and to navigate forward and backwards without problems.

Step 2 - LINQ Expression generation and priorities

C#
-x * Sqrt(y) + 5.7 / (-y + x)

I will return to this bizarre expression. It actually doesn't generate any meaningful result to me. I am only using it because it has all the important traits I want to deal with.

It starts with a symbol. Expressions are usually composed of a math variable or value plus a variable number of operators followed by another sub-expression. But when we start with a -, we are starting with an operator.

That expression also uses variables, functions calls, values (constants, if you prefer), parenthesis (to force a priority) and operators with different priorities (we must not forget that a division has a higher priority than an addition, so the 5.7 must be divided before being added to the sub-expression at its left).

Well, maybe the most interesting trait is that we start dealing with the tokens from left to right. So, how can we make the division and the parenthesis have a higher priority than things coming at their left?

This is where we use recursive coding. I will not write the real code here, but I will try to present a simplified algorithm:

Parse
  GetTokens and then
  ParseOneOrMoreIncludingSigns
ParseOneOrMoreIncludingSigns
  if currentToken is addition
    AdvanceToken, effectively ignoring this token
    return ParseOneOrMoreLowPriority
  
  if currentToken is subtraction
    AdvanceToken
	leftLinqExpression = Expression.Subtract(0, the result from GetValue)
	return ParseOneOrMoreLowPriorityLoop giving our left LINQ expression as the initial expression.

  // In all other situations we don't advance (so we don't
  // lose the first token) and...
  return the ParseOneOrMoreLowPriority directly
ParseOneOrMoreLowPriority
  leftLinqExpression = ParseOneOrMoreHighPriority
  return ParseOneOrMoreLowPriorityLoop giving our left LINQ expression as the initial expression.
ParseOneOrMoreLowPriorityLoop(currentLinqExpression)
  while currentToken is a low priority operator
    AdvanceToken
    otherExpression = ParseOneOrMoreHighPriority
    currentLinqExpression = makeLinqExpression(currentLinqExpression, operator, otherExpression)

  return currentLinqExpression
ParseOneOrMoreHighPriority(currentLinqExpression)
  currentLinqExpression = GetValue
  while currentToken is a high priority operator
    AdvanceToken
    otherExpression = GetValue
    currentLinqExpression = makeLinqExpression(currentLinqExpression, operator, otherExpression)

  return currentLinqExpression
GetValue
  switch(currentToken type)
    case variableName: Generate a LINQ expression that access a MathVariable
	case functionName: for each parameter the function expects, call a ParseOneOrMoreIncludingSigns, looking for a comma as separator
	case value: return a LINQ Expression.Constant(the value itself)
	case parenthesisOpening: calls ParseOneOrMoreIncludingSigns telling that the end character is a parenthesis closing.
	anything else: an exception is thrown.

This approach actually solves all the ordering problems. Each time that we do something like:

C#
a + b / c + d

The low-priority parsing method will process x + and will ask the high priority method to do its parsing. Such high priority method will be able to parse the b / c but will not be able to process the next + operator, returning a LINQ expression that already "did" the division task to the low priority parser, which will put such entire division sub-expression as the right expression that it was waiting and will continue the loop, being capable of doing the + d operation.

As an alternative explanation, we could see the flow like this:

  • We start analyzing if there's a signal (+ or -). There isn't, so we go to the low-priority method directly.
  • The low priority method asks a high-priority method to run, which in turns ask to read a value, which returns a read-variable expression (the a). When it returns, we are in the high-priority method again but, as the + operator isn't a high-priority operator, it returns to the low-priority method.
  • The low-priority method then enters its loop, asking the high-priority method to do its job again. This time the high-priority method is capable of doing b/c before returning.
  • In any case, the loop had a "currentLinqExpression" that was originally filled with an access to the a variable, and it combines such expression with the new result b / c expression, becoming a correctly ordered a + (b/c). This entire expression becomes the current expression of the loop and a new addition is encountered. It calls a high-priority method again, yet it ends-up only getting a single variable, and so the loop combines the current expression it had with the new operation and variable, becoming equivalent to (a + (b/c)) + d

So, even if we didn't add any parenthesis in our expression, it will generate the exactly same LINQ expression it would've generated if we really gave the expression entirely ordered by parenthesis. So, to the compiled expression, the use of excessive parenthesis will not affect the performance at all, because the parenthesis simply don't exist.

Step 3 - Compiling

Well, I hope I made it clear how the LINQ expression is generated, because the last step is simply wrapping it with a Expression<Func<double>> and asking it to be compiled. As I already said, I avoided the step of generating a DynamicMethod with IL code by using LINQ expressions.

With this we have finished with the MathParser and we have compiled a math expression given as string.

MathBinding

In my opinion, the MathParser may be the hardest logic part, yet I consider the MathBinding to be the hardest one by lack of knowledge on how WPF bindings and markup extensions work. Maybe it is my problem, maybe it is really some lack of documentation, I don't know.

I already saw many "Math Parsers" for WPF and I never saw one that actually compiled the expressions like the MathBinding. Yet, what usually made me avoid them is one of these factors:

  • They work over a single property, so I can add, subtract, multiply or divide values by a constant, but nothing else;
  • They use a very verbose approach, having to declare each property set as a sub-tag, the binding as a sub-tag of that tag and then adding one object per sub-binding. For example, instead of this:

    XML
    <Image 
      Source="{Binding Content}" 
      Canvas.Left="{Pfz:MathBinding [CenterX]-[Width]/2}"
      Canvas.Top="{Pfz:MathBinding [CenterY]-[Height]/2}"
      Width="{Binding Width}" 
      Height="{Binding Height}"
      />

    We would need to write something similar to this:

    XML
    <Image Source="{Binding Content}" Width="{Binding Width}" Height="{Binding Height}">
      <Canvas.Left>
        <Pfz:MathBinding Expression="x-y/2">
          <Binding Path="CenterX" />
          <Binding Path="Width" />
        </Pfz:MathBinding>
      </Canvas.Left>
      <Canvas.Top>
        <Pfz:MathBinding Expression="x-y/2">
          <Binding Path="CenterY" />
          <Binding Path="Height" />
        </Pfz:MathBinding>
      </Canvas.Left>
    </Image>

Well, I think you understand why I wanted something different.

As I already explained, the MathParser is independent from WPF. It doesn't know about object properties, Dependency Properties or the like. I didn't want to make it WPF specific, as I think the math parsing capability is very powerful in other situations.

The only communication we have to an expression after it is compiled are the variables and, considering the actual version of the code, it is our responsibility to declare the variables before compiling the expression. The variable are also lower-case, so we can't simply write an expression like:

C#
CenterX-Width/2

So, I decided to add a pre-parsing step. To make such pre-parser work without replicating the entire tokenization logic, I decided to use the [] as scape characters. In fact, I wanted to use the {} characters, which are the most common ones on many different string interpolation algorithms, but such characters already have a special meaning in XAML.

So, property paths must be written like this:

C#
[Property]

or

C#
[Property.SubProperty.AnotherSubProperty]

And this is why we write:

C#
[CenterX]-[Width]/2

Instead of writing:

C#
CenterX-Width/2

Even if it has some extra characters, I really think it is still small and clear, avoiding large amounts of boilerplate code.

MarkupExtension, MultiBinding and Pre-parsing

Normal Bindings can only be bound to a single property, but an expression like [CenterX]-[Width]/2 must be bound to two expressions.

Well, WPF supports MultiBindings, so my initial idea was to sub-class it. Apparently I didn't know how Bindings are expected to work and I didn't found a document talking about it. I should not inherit from a Binding or from a MultiBinding, even if those classes aren't sealed. There are no useful methods to override. Inheriting from the BindingBase, which is the base class for a Binding, is also useless, as the overridable method is internal.

Yet, Bindings in general are MarkupExtensions, and we can create our own MarkupExtensions. A MarkupExtension has one very important method: ProvideValue. Such method is responsible for, well, providing the value that will fill the property. Yet a Binding doesn't provide a single value and the provide value will not be magically called every time the value of a property we want to observe is changed.

Well, my first try was to return a MultiBinding instead of returning a direct value. That didn't work. Again, this was my lack of knowledge about how Bindings work.

In any case, I can create a MultiBinding configured the way I need and call its ProvideValue from the ProvideValue I am implementing. It will actually return a BindingExpression. I really didn't look how that class works. It works and my MathBinding is working by delegating the real Binding work to a private multi-binding instead of inheriting from one. This is composition and for some things is much better than inheritance, as I don't want users to manipulate the MathBinding as if it was a normal MultiBinding.

So, the important thing that I still didn't explain is: How do we create that MultiBinding?

This task is actually divided into three steps:

  1. Pre-parsing the math expression generating as many sub-bindings as needed;
  2. Building a new expression replacing path properties by variable names;
  3. Creating an IMultiValueConverter that uses the arguments to fill the math variables and invokes the compiled delegate generated by the original MathParser.

Pre-parsing and new expression building

I already explained a little about the pre-parsing. Binding paths are encoded inside [] characters. It would be possible to write the expressions like CenterX-Width/2, but this will require another expression parser, and I wanted something pretty simple. The real parser was already made and independent from WPF. I didn't want to change it and I didn't want to replicate part of its code here. So, the pre-parser enters a loop to find a [ character. If it finds one, it searcher for a ] too. If it doesn't find one it will throw an exception, as this is something it needs to find.

The text written inside the [ and ] are used directly as the path to create a normal Binding, which is added to the private MultiBinding. This same search also declares a new MathVariable or reutilises one if the same path was already used before. At the same time a new string is built using the variable names instead of the [property paths].

The expression inside the [] characters can contain more [] characters, as long as each opening [ is matched with a closing ]. Such matching is needed by the normal bindings, so I don't think it will ever become a problem and it allows us to access indexed properties. For example: [Array[0]].

I chose to name the generated variables that are given to the MathParser as b0, b1, b2 etc. The b is from binding and it is lowercase because math parser only accepts lower-case names.

MultiBinding Converter

The MultiBinding is only responsible for calling its Converter providing all the new property values when at least one of them changes. There's no big secret here. If we use an expression that binds to two properties, our converter will receive two values in an array.

Considering the expression compiled with the MathParser uses the MathVariables, the job of the converter is to read all those values, convert them to double (as they may be of any type) and fill the appropriate MathVariables, which are in the same order in an array generated when the MathExpression was compiled.

After filling the variables, it is enough to call it and receive the value. As a final detail, the converter uses a Convert.ChangeType() over that value to convert it to the expected value type, so it is possible to use a MathBinding over decimal or int properties, independently of the fact that the MathParser only deals with doubles.

The Sample

The sample application has a window showing a table where the left column is composed of small formulas using its own Width and Height and the right column shows the actual result of those formulas.

You can resize the window to see the values changing. Obviously, the application doesn't do anything useful and its only purpose is to be a source code sample that shows how to use many different formulas, including user declared functions and bindings done in C# code.

I hope you like to use the MathBinding or even the MathParser directly.

Version History

  • 22 Jun 2016. Added download for Store Apps. Unfortunately, only the MathParser is available, the MathBinding is not;
  • 16 Sep 2014. The MathBinding now supports setting the ElementName, which will apply to all inner bindings declared in the expression, as well as it supports declaring sub-bindings the same way a MultiBinding works, which become named as b0, b1 etc;
  • 14 Sep 2014. Made the RegisterFunction methods work with delegates implemented by methods with a different number of parameters. This is rare, but can happen as optimizations for static methods or when open delegates are used;
  • 11 Sep 2014. Initial version.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Microsoft
United States United States
I started to program computers when I was 11 years old, as a hobbyist, programming in AMOS Basic and Blitz Basic for Amiga.
At 12 I had my first try with assembler, but it was too difficult at the time. Then, in the same year, I learned C and, after learning C, I was finally able to learn assembler (for Motorola 680x0).
Not sure, but probably between 12 and 13, I started to learn C++. I always programmed "in an object oriented way", but using function pointers instead of virtual methods.

At 15 I started to learn Pascal at school and to use Delphi. At 16 I started my first internship (using Delphi). At 18 I started to work professionally using C++ and since then I've developed my programming skills as a professional developer in C++ and C#, generally creating libraries that help other developers do their work easier, faster and with less errors.

Want more info or simply want to contact me?
Take a look at: http://paulozemek.azurewebsites.net/
Or e-mail me at: paulozemek@outlook.com

Codeproject MVP 2012, 2015 & 2016
Microsoft MVP 2013-2014 (in October 2014 I started working at Microsoft, so I can't be a Microsoft MVP anymore).

Comments and Discussions

 
GeneralRe: Great minds and all that Pin
Paulo Zemek16-Sep-14 5:17
Paulo Zemek16-Sep-14 5:17 
GeneralRe: Great minds and all that Pin
Sacha Barber16-Sep-14 6:38
Sacha Barber16-Sep-14 6:38 
GeneralRe: Great minds and all that Pin
Paulo Zemek16-Sep-14 6:41
Paulo Zemek16-Sep-14 6:41 
GeneralRe: Great minds and all that Pin
Sacha Barber16-Sep-14 7:40
Sacha Barber16-Sep-14 7:40 
GeneralRe: Great minds and all that Pin
Paulo Zemek16-Sep-14 8:35
Paulo Zemek16-Sep-14 8:35 
GeneralRe: Great minds and all that Pin
Sacha Barber16-Sep-14 8:57
Sacha Barber16-Sep-14 8:57 
GeneralRe: Great minds and all that Pin
Paulo Zemek16-Sep-14 8:58
Paulo Zemek16-Sep-14 8:58 
GeneralMy vote of 5 Pin
Champion Chen12-Sep-14 19:39
Champion Chen12-Sep-14 19:39 
bookmarked.
GeneralRe: My vote of 5 Pin
Paulo Zemek13-Sep-14 12:52
Paulo Zemek13-Sep-14 12:52 
GeneralMy vote of 5 Pin
Fernando E. Braz11-Sep-14 13:30
Fernando E. Braz11-Sep-14 13:30 
GeneralRe: My vote of 5 Pin
Paulo Zemek11-Sep-14 13:36
Paulo Zemek11-Sep-14 13:36 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.