Thursday, January 24, 2008

Label Persistence on an Image in WPF

One of my side projects is an application that presents the user with an image of the human body with labels on different parts of the anatomy. The user can then click on the label and be presented with information about that body part. Here is an example of a skeleton with its skull labeled:

I am writing my application in Visual Basic using WPF. WPF is a very powerful technology that allows rapid user interface development; however, it does not have a native mechanism for label persistence. That is, if I label a part of an image, the label will not automatically reconfigure itself with the image if the user resizes the application. This means if the user resizes my application while it is displaying the above image, the skull label will no longer be on top of the skull in the image.

The solution to this problem turned out to be trivial, I am just surprised that Microsoft did not create a feature to handle this user scenario; a scenario that, I would think, is relatively common.
To solve this problem, I had to store several data points.
  1. The original height and width of the image.

  2. The 4 margin distances (distances between the left, right, top, and bottom of the label from the left, right, top, and bottom of the image, respectively).

  3. The width and height of the label itself.

Normally a control object in a user interface is only represented by two data points, its x and y coordinates; however, I had to represent the label with the 6 data points mentioned in 2. and 3. to maintain label persistence.

Now for the algorithm:

  1. On a resize event, I calculate the change of both the height and the width of the image.

    Dim widthMultiplier As Double = AnatomyImage.ActualWidth / oldWidth

    Dim heightMultiplier As Double = AnatomyImage.ActualHeight / oldHeight

  2. I then use these multipliers to reevaluate the margins, height, and width of the label.

    Dim x As New Thickness With {.Top = Label1.Margin.Top * heightMultiplier, _
    .Bottom = Label1.Margin.Bottom * heightMultiplier, _
    .Left = Label1.Margin.Left * widthMultiplier, _
    .Right = Label1.Margin.Right * widthMultiplier _
    Label1.Margin = x
    Label1.Width = Label1.ActualWidth * widthMultiplier
    Label1.Height = Label1.ActualHeight * heightMultiplier

Thus, the label is always in the same relative location on the image because its height, width, and margins still "own" the same relative percentage of the image.

...Seriously though, why was label persistence not a property in WPF?!

No comments: