Prodige Software Corporation

A Resizable Vector Graphics Calculator with Per-Pixel Translucency

Contents

Introduction

This article describes the construction of a simple calculator with a user interface completely defined using VG.net vector graphics. The application has no window border, and the edges are anti-aliased with per-pixel translucency, as seen in the close-up below:

Grab any of the calculator corners to resize it using the mouse. Because this calculator uses vector graphics, you can resize it without any of the pixel scaling problems inherent in bitmap graphics:

The techniques in this article are ideal for applications that use skinning and fancy user interface features, such as gel buttons.

What is a TranslucentForm?

VG.net makes it easy to create a vector graphics interface without writing any code. With the TranslucentForm class added in version 2.7, now you can build your entire user interface with vector graphics, replacing even the window frame and system buttons, such as minimize and close, with custom graphics. For the first time on the Windows platform, you can create a resizable user interface with per-pixel translucency, without writing any code!

A TranslucentForm takes the place of both a Form and a Canvas, turning a VG.net Picture into a complete user interface.

Source Code

The source code for this sample is installed with the Extras package included with the full version of VG.net.

Turning a Picture into a Complete User Interface

Unlike a regular Windows Forms application, the TranslucentCalculator project contains no Form class. Instead, a single Picture-derived class, MainPicture, defines the user interface. This Picture was created using the VG.net Picture Designer integrated in Visual Studio .NET.

After creating the MainPicture class, we added the following lines of code to the bottom of MainPicture.cs:

[STAThread]
static void Main() 
{
    TranslucentForm form = new MainPicture().DisplayInTranslucentForm();
    form.Text = "VG.net Calculator";
    Application.Run(form);
}

This is the main entry point for the application. In the code above, we first create a new MainPicture object, and then call DisplayInTranslucentForm to display it in a TranslucentForm. The size of the TranslucentForm will match the MainPicture automatically.

Building the MainPicture

We began construction of the MainPicture by adding to a blank Picture background objects, a shadow, and a set of objects delimiting a keypad area:

To this, we added a Group of Rectangle elements for the numeric display:

Adding Minimize, Close, and Resize Window Operations

If there is no windows frame, how is the user able to move the window, minimize the window, and close the application?

To solve this problem, VG.net version 2.7 adds a property to the Element class called WindowOperation. This property is specifically for use in conjunction with the TranslucentForm. The property type is an enum, which can have one of several values, each one specifying a window operation triggered by the Element:

  • None
  • Move
  • ResizeLeftTop
  • ResizeRightTop
  • ResizeLeftBottom
  • ResizeRightBottom
  • Minimize
  • Maximize
  • Close

We added a Pie to each of the four corners of the MainPicture, to serve as resizing handles. In this screenshot, the Pie used as a resizing handle is colored red:

In the screenshot above, we set the Pie WindowOperation property to ResizeRightTop. Once this property is set, the TranslucentForm takes care of handling the mouse events for the Pie and triggering a resize operation - there is no need to write any code. After we assigned the WindowOperation property to a Pie on each corner, we made the Pies transparent, so they could respond to mouse events without being visible.

We created "Close" and "Minimize" buttons using a custom Picture class called WindowOperationButton. In this screenshot, the Close WindowOperation is assigned:

When the user clicks the button, the application is closed. You do not need to write an event handler.

The GelRectangleButton Class

In a VG.net application, you can create Picture-derived classes that are graphical components, and then reuse these components within other Pictures. In this sense, a Picture is like a graphical Control. We call these components Sub Pictures.

The MainPicture uses two types of Sub Pictures, each a custom class: the GelRectangleButton and the WindowOperationButton. In the screenshot below, we are editing the GelRectangleButton class in the Picture Designer:

The GelRectangleButton consists of three objects: a glossy shine, a gel button body using a PathGradientFill, and a shadow, also using a PathGradientFill.

The PathGradientFill objects do not define any colors; they are using the default colors, black and white. We did this so we could later apply a Style to the body object. The Style will define colors for the fill, and the Style and local fill settings are combined by VG.net using a feature called appearance inheritance: the union of local, ancestor, and Style properties to produce a final appearance.

The WindowOperationButton Class

The WindowOperationButton class is a button you can use for Minimize or Close window operations. It consists of a body Rectangle and two Polylines. One Polyline represents the Close icon (an X), and the other a minimize icon (a horizontal line). Both "icons" are visible in this screenshot:

The WindowOperationButton is smart enough to make the correct icon visible when the WindowOperation property changes. It does this by listening to the PropertyChanged event.

The WindowOperationButton has a gradient fill at the level of the Picture. The body Rectangle inherits this appearance, but the fill colors will not be used at run-time. As with the GelRectangleButton, we will assign a Style to the body Rectangle that will override the colors at the Picture level. In the Button class discussion, below, we present below more details on this Style assignment.

The Button Base Class

As use the calculator, notice the button colors change when the mouse is over a button. When you press the mouse button down over a button, the colors change again. The following mouse events trigger color changes: MouseEnter, MouseLeave, MouseDown, and MouseUp.

Since all the buttons have a similar visual behavior, we created a base class called Button to collect this common behavior. The base class changes colors when mouse events are raised. This eliminates the need to handle low-level mouse events in the MainPicture class.

We define an enum to keep track of the Button states:

public enum ButtonState
{
    Normal,
    MouseOver,
    MouseDown
}

We will use Style changes to change colors. The Button class has string fields, and corresponding properties, for the name of the Style for each ButtonState:

public class Button : Picture
{
    private string normalStyle = "";
    private string overStyle = "";
    private string downStyle = "";
    private ButtonState state;

    public Button()
    {
        state = ButtonState.Normal;
    }

    public string BodyNormalStyle
    {
        get { return normalStyle; }
        set
        {
            normalStyle = value;
            UpdateStyles();
        }
    }

    public string BodyMouseOverStyle
    {
        get { return overStyle; }
        set
        {
            overStyle = value;
            UpdateStyles();
        }
    }

    public string BodyMouseDownStyle
    {
        get { return downStyle; }
        set
        {
            downStyle = value;
            UpdateStyles();
        }
    }
    // additional code...
}

When the mouse events are raised, we change the Style property on an Element called "body". In this code excerpt, we show only OnMouseEnter and OnMouseLeave overrides. The OnMouseDown and OnMouseUp overrides are similar:

    public override void OnMouseEnter(InputEventArgs args)
    {
        state = ButtonState.MouseOver;
        UpdateStyles();
        base.OnMouseEnter(args);
    }

    public override void OnMouseLeave(InputEventArgs args)
    {
        state = ButtonState.Normal;
        UpdateStyles();
        base.OnMouseLeave(args);
    }

    protected virtual void UpdateStyles()
    {
        Element body = Elements["body"];
        if (body == null)
            return;
        switch (state)
        {
            case ButtonState.Normal:
                body.Style = normalStyle;
                break;
            case ButtonState.MouseOver:
                body.Style = overStyle;
                break;
            case ButtonState.MouseDown:
                body.Style = downStyle;
                break;
        }
    }

Note how we look up the correct Element by using the name "body." This look up is necessary because the Button base class has no child Elements; it does not define any appearance. We use a naming convention, "body", to find the correct Element. A class derived from Button can override the UpdateStyles method to make additional visual changes when the ButtonState changes.

The WindowOperationButton class derives from Button. To make use of the BodyNormalStyle, BodyMouseOverStyle, and BodyMouseDownStyle properties, we first add suitable Styles to MainPicture:

After we add the Styles, we can set the various style properties for a WindowOperationButton instance:

Separating the User Interface from Application Logic

The TranslucentCalculator project also demonstrates how to separate a user interface from application logic. We collected all the application logic in the class called CalculatorBrain. Click events raised by buttons within the MainPicture trigger calculator operations in CalculatorBrain.

To make it possible for you to create a different user interface, we decoupled CalculatorBrain from MainPicture using a class called CommandButton. If you wish to create an alternative user interface, with custom buttons, derive your custom button class from the CommandButton class. CommandButton derives from Button, as seen in this class hierarchy:

Like the Button class, the CommandButton class contains no visual Elements. It provides a single property, CalculatorCommand:

public class CommandButton : Button
{
    private CalculatorCommand command;

    public CommandButton()
    {
        command = CalculatorCommand.None;
    }

    public CalculatorCommand CalculatorCommand
    {
        get { return command; }
        set { command = value; }
    }
}

This property is of type CalculatorCommand, an enum:

public enum CalculatorCommand
{
    None,

    Numeral0,
    // ... numerals between 0 and 9 ...
    Numeral9,
    NumeralDecimal,

    MemoryPlus,
    MemoryMinus,
    MemoryClear,
    MemoryRecall,

    Plus,
    Minus,
    Multiply,
    // ... remaining commands ...
}

When we create MainPicture, we set the CalculatorCommand property on each button that will trigger an operation:

The CalculatorBrain constructor is passed a Picture. The constructor looks for CommandButtons within the Picture, and uses the CalculatorCommand property to attach the correct Click event handler to each CommandButton:

    RecursiveElementIterator iterator = 
		new RecursiveElementIterator(picture.Elements);
    foreach (Element e in iterator)
    {
        CommandButton button = e as CommandButton;
        if (button == null)
            continue;
        
        switch (button.CalculatorCommand)
        {
            case CalculatorCommand.Numeral0:
            // ... remaining digit cases here ...
            case CalculatorCommand.Numeral9:
            case CalculatorCommand.NumeralDecimal:
                button.Click += new InputEventHandler(NumericClick);
                break;
            case CalculatorCommand.Plus:
            case CalculatorCommand.Minus:
            case CalculatorCommand.Multiply:
            case CalculatorCommand.Divide:
                button.Click += new InputEventHandler(BinaryOperatorClick);
                break;
            // ... remaining CalcuatorCommand values ...
        }
    }

Create a Custom Calculator

To create a custom calculator user interface, add a new Picture to the project, or copy the existing MainPicture class. Add CommandButton derived objects and set their CalculatorCommand properties. In your Picture constructor, create a CalculatorBrain object and pass in your Picture.

To use a different kind of button, create a class derived from CommandButton, and add visual elements in the Picture Designer. Set the SerializeNames property to true, so names can be serialized, and name one object "body" if you wish to take advantage of automatic Style switching.

We have provided a Picture containing alternative button ideas. It is called ButtonIdeas: