Determining the Control's Display

Within your control, you can write code to determine and modify the visual representation of your control. The following sections provide background information on control display and information on how to manage it.

In this topic you can find information about the following:

Control Dimensions

There are three main dimensions associated with a control: bounds, client, and display coordinates. The bounds of a control are the outer window coordinates of the control. The bounds are always expressed in parent-client coordinates, that is, with respect to the dimensions of the parent.

Client coordinates are the dimensions of the area that the control can draw into. The client area of a control is always based at (0, 0) and is sized based on the inner-client owned area of the control. The client coordinates do not encompass non-client areas such as borders applied to the windows (for example, WS_BORDER, WS_EX_CLIENTEDGE) and the title bar for top-level windows.

The display coordinates are virtual client coordinates that define the area in which child controls appear. Display coordinates can define an area larger than that defined by the client coordinates. A good example of the difference between client coordinates and display coordinates is a form with autoscrolling. When autoscrolling is enabled for a form, the display size of the form can become bigger than the client size, resulting in a virtual form larger than the physical window. When you move the scroll bars on the form, the display coordinates change.

Note   Because the bounds of controls are always in parent-client coordinates, when a form is scrolled, the child control’s bounds will change to reflect their actual position relative to the client coordinates of the parent.

In most controls, you only need to work with client coordinates. Display coordinates are important only if you write a control that is going to host other controls (such as a TabControl control), and you want to enable docking or other layout mechanisms.

Location and Size

A control's layout is determined by the combination of values of several properties that the control inherits from the Control class:

These properties can be set at run time to change the location and size of the control.

If the control's location or size changes at run time, the control fires notification events. The layout event is fired when anything changes on a control that would cause it to reapply any layout. Examples include adding child controls, changing the control's boundaries, or performing some other control-specific event such as changing a property value. The resize event is fired only when the bounds of the control changes.

The default WFC layout logic is processed in Control.onLayout and Form.onLayout. The dock and anchor of a child control are applied in the layout event of the parent. Thus a panel would lay out all children, and the parent of the panel would lay out that panel.

Updating Visual Display

Any visual control must provide a representation of itself. Unless your control is subclassing another control, you must add custom paint logic to your control by overriding the onPaint method. Call the superclass' onPaint method to display the control, and then add your own logic to customize the display.

The onPaint event receives a PaintEvent object that you can use to get an instance of a Graphics object. You then call methods of the Graphics object to update the control's display. The following shows a simple example of how to display text in a control by updating the text property:

protected void onPaint(PaintEvent p) {
   super.onPaint(p);
   Graphics g = p.graphics;
   g.drawString(getText(), 0, 0);
}

The Graphics object in WFC is very rich and full featured. You can draw almost any primitive structure including arcs, ellipses, rectangles, polygons, lines, and points. The following table lists commonly-used properties and methods of the Graphics object.

Graphics object member Description
setPen method Specifies a Pen object that defines how lines and borders around objects are drawn.
setBrush method Specifies a Brush object that fills a control (for example, in the clearRect method).
setBackColor method Specifies the color displayed behind text.
setTextColor method Specifies the foreground color for text.
setFont method Specifies the font with which the text will be drawn.
drawArc method Draws an elliptical arc.
drawImage method Draws an image.
drawString method Displays a string; includes support for word wrapping, alignment, clipping, and so forth.

Painting in WFC uses the standard Win32 model in which a region of the control is invalidated. This effectively requests a repaint, but does not immediately perform the paint procedure. At the next free cycle, the paint event is sent asynchronously to the control. When the event reaches the control, the control first calls eraseBackground to clear the area that is about to be painted. The paint event then occurs, clipped to the invalid region.

Paint events coalesce, so the control will receive only one paint event for multiple invalidated regions of the control. The coalesced paint event that the control receives is clipped to the union of all the invalid regions.

The following example illustrates how you can call the invalidate method to request that the control repaint itself. In this instance, the invalidate method is called without a Rectangle object parameter to indicate the clipped region, so the entire control will be repainted:

public void setAlignment(int value) {
    if (!AlignStyle.valid(value))
        throw new WFCInvalidEnumException("value", value,
          AlignStyle.class);
    align = value;
    invalidate();   // Repaint control when property changes
}

Note   If you modify multiple properties (or the same property twice), the control will not perform the entire paint operation twice.

To force a paint to occur synchronously, you can call the control's update method, which forces the control to perform any pending paint events immediately.

Eliminating Flicker while Painting

To eliminate flicker in your control, you should consider overriding the onEraseBackground event. The default implementation of this event clears the background of the control with the current value of the backColor property. However, it is not always necessary to repaint the entire area of the control, and doing so unnecessarily can cause flickering. This is primarily the case with controls that have a large area or complex paint logic.

In the example above, the drawString method is used to place text in the control. If the background is not cleared by the eraseBackground method, it would look as if it had never been painted, producing a "transparent" look.

Note   The control is not actually transparent. However, because the old contents of that screen area are not repainted, they are still visible.

To create a control that looks solid with no flicker, avoid painting the background for areas that will be repainted again with foreground information. The easiest way to do this is to ensure that the onPaint method accounts for the entire clientRect area. You can then skip background drawing completely by overriding the onEraseBackground event specifying that the event has been handled, as in the following example:

    protected void onEraseBackground(EraseBackgroundEvent event) {
        event.handled = true;
    }
    
    protected void onPaint(PaintEvent p) {
        Graphics g = p.graphics;
        g.clearRect(getClientRect());
        g.drawString(getText(), 0, 0);
    }

This is a small example with simple code in the onPaint method. You might therefore not see much benefit from overriding the onEraseBackground event in this case. However, in your own control where the paint code might be more complex, using the illustrated technique greatly reduces flicker.

Another technique to increase the visual stability of a control is to double-buffer your on-screen image. In this technique, you maintain a bitmap of the entire client area, and then create a Graphics object based on that image. Performance of graphical updates against the buffer is much quicker than normal.

You should use the double-buffering technique only if you have overridden onEraseBackground and optimized your paint code already and are still experiencing flicker. Double-buffering is a resource-intensive operation, because you are maintaining an extra copy of the control's image. For large images, this can require substantial memory (the exact amount depends on the size of the image and its color depth). Maintaining the buffer can also slow performance if the buffer stores a large area.

To use a double buffer in your control, do the following:

The following example draws a simple star pattern inside the client area of the control. If you did not include a double buffer, the control would flicker noticeably:

// Star.java
import com.ms.wfc.ui.*;
import com.ms.wfc.core.*;

public class Star extends Control 
{
    // Create buffer
    Bitmap buffer = null;
    // Override onResize in order to recreate buffer at new size
    protected void onResize(Event e) {
        if (buffer != null) {
            buffer.dispose();   // Frees resources
            buffer = null;
        }
        Point s = getClientSize();
        buffer = new Bitmap(s.x, s.y);
        invalidate();   // Forces Windows to redraw entire control
        super.onResize(e);
    }
    
    protected void onEraseBackground(EraseBackgroundEvent event) {
        event.handled = true;
    }
    
    protected void onPaint(PaintEvent pe) {
        Graphics g = buffer.getGraphics();
        Rectangle client = getClientRect();
        
        // Explicitly set backColor and pen based on buffer's values
        g.setBackColor(getBackColor());
        g.setPen(new Pen(getForeColor()));
        g.clearRect(client);
        int x = 0;
        int y = 0;
        int xCenter = client.width/2;
        int yCenter = client.height/2;
        
        // Draws a star
        for (; x<client.width; x+=4) {
            g.drawLine(x, y, xCenter, yCenter);
        }
        for (; y<client.height; y+=4) {
            g.drawLine(x, y, xCenter, yCenter);
        }
        for (; x>=0; x-=4) {
            g.drawLine(x, y, xCenter, yCenter);
        }
        for (; y>=0; y-=4) {
            g.drawLine(x, y, xCenter, yCenter);
        }
        g.dispose();
        
        pe.graphics.drawImage(buffer, 0, 0);
    }
    
    public static class ClassInfo extends Control.ClassInfo {
    }
}