Using Images in Property Editors

   

This section introduces the techniques required to render images in the Object Inspector for custom property editors. Some property editors already render images in the Object Inspector. For example, a property of type TColor will appear automatically in the Object Inspector as other TColor properties do. However, there are many more types of properties that could benefit from the use of images when editing the property. To facilitate this, you must not only inherit from TpropertyEditor or a descendant, but must also inherit from ICustomPropertyEditor; ICustomPropertyEditor provides six new methods , five of which can be overridden. The declarations for those five functions are as follows :

 void __fastcall ListMeasureWidth(const AnsiString Value,                                           Graphics::TCanvas* ACanvas,                                           int& AWidth);  void __fastcall ListMeasureHeight(const AnsiString Value,                                            Graphics::TCanvas* ACanvas,                                            int& AHeight);  void __fastcall ListDrawValue(const AnsiString Value,                                        Graphics::TCanvas* ACanvas,                                        const TRect& ARect,                                        bool ASelected);  void __fastcall PropDrawValue(Graphics::TCanvas* ACanvas,                                        const TRect& ARect,                                        bool ASelected);  void __fastcall PropDrawName(Graphics::TCanvas* ACanvas,                                       const TRect& ARect,                                       bool ASelected); 

The remaining method, to be used in conjunction with the Xxxx DrawValue methods, is declared as

 AnsiString __fastcall GetVisualValue(); 

These are listed in Table 5.8, along with a description of the purpose of each. Note that you must implement all five of the overridable methods or your class will not link.

Table 5.8. New Methods of ICustomPropertyEditor to Allow Custom Images

Method

Purpose

ListMeasureWidth()

This is used to allow the default width of an entry in the drop-down list to be modified. As the width of the overall drop-down list is set to that of the widest entry or greater, this is effectively the minimum width of the drop-down list.

ListMeasureHeight()

This is used to allow the default height of each list entry to be modified. Unless a large image is displayed (as is the case with TCursor properties), this method does not generally need to be overridden.

ListDrawValue()

This is called to render each property value in the drop-down list.

PropDrawValue()

This is called to render the selected property value for the property when it does not have focus. When the property has focus, the current property value is shown as an editable AnsiString .

PropDrawName()

This is called to render the property name in the Object Inspector. It is not required often.

GetVisualValue()

This is used to return the displayable value of the property. This method is used in conjunction with the ListDrawValue() and PropDrawValue() methods to render the AnsiString representation of the property value.

Where in the Object Inspector these methods are used is illustrated in Figure 5.3. You can see that the three most important methods to override are ListMeasureWidth() , ListDrawValue() , and PropDrawValue() .

Figure 5.3. Areas in the Object Inspector that are affected by the new overridable TPropertyEditor methods.

graphics/05fig03.gif

To create your own custom images in the Object Inspector, you must derive a new property editor class from TPropertyEditor or from a class derived from TPropertyEditor . Which you choose depends on the type of the property that the editor is for. For example, a property of type int would descend from TIntegerProperty . Refer to the section "Creating Custom Property Editors," earlier in this chapter, for more information. A new property editor class can then be defined according to the format in Listing 5.10. As an example, the editor is derived from TEnumProperty .

Listing 5.10 Definition Code for a Property Editor That Renders Custom Images
 #include "VCLEditors.hpp"  class TCustomImagePropertyEditor : public TenumProperty,ICustomPropertyEditor  {     typedef TEnumProperty inherited;  public:     void __fastcall ListMeasureWidth(const AnsiString Value,                                              Graphics::TCanvas* ACanvas,                                              int& AWidth);     void __fastcall ListMeasureHeight(const AnsiString Value,                                               Graphics::TCanvas* ACanvas,                                               int& AHeight);     void __fastcall ListDrawValue(const AnsiString Value,                                           Graphics::TCanvas* ACanvas,                                           const TRect& ARect,                                           bool ASelected);     void __fastcall PropDrawValue(Graphics::TCanvas* ACanvas,                                           const TRect& ARect,                                           bool ASelected);     void __fastcall PropDrawName(Graphics::TCanvas* ACanvas,                                          const TRect& ARect,                                          bool ASelected);  protected:     #pragma option push -w-inl     inline __fastcall virtual        TCustomImagePropertyEditor(const _DesignIntf::di_IDesigner  ADesigner,                                   int APropCount)                                   : TEnumProperty(ADesigner,                                                   APropCount)     { }     #pragma option pop  public:     #pragma option push -w-inl     inline __fastcall virtual ~TCustomImagePropertyEditor(void)     { }     #pragma option pop  }; 

It is assumed that only the drawing behavior of the property editor is to be modified. The remainder of the class is not altered .

The implementation of each of the five functions is discussed in the sections that follow. For each of the methods, comments will indicate the code that should be present in each method. This will be followed by the actual code used to produce the images shown in Figure 5.4, which shows a finished property editor in use.

Figure 5.4. The TShapeTypePropertyEditor in use.

graphics/05fig04.jpg

As an example, a property editor for the TShapeType enumeration from the TShape component will be developed. The class definition for such a property editor is exactly the same as that shown in Listing 5.10. However, the class is called TShapeTypePropertyEditor . The parameters used in the five image-rendering methods are detailed in Table 5.5 so that an overall picture of how they are used can be developed.

Table 5.5. Parameters for Custom Image-Rendering Methods

Method

Purpose

AWidth

This is the current width in pixels of the AnsiString representation of the value as it will be displayed in the Object Inspector, including leading and trailing space.

AHeight

This is the default height of the display area for the current item. Typically, this is 2 pixels greater than the height of ACanvas->TextHeight("Ag") , where Ag is chosen simply to remind the reader that the actual font height of the current font is returned, that is the ascender height (from A ) plus the descender height (from g ). Adding 2 pixels allows a 1-pixel border. Remember that the ascender height also includes the internal leading height (used for accents, umlauts, and tildes in non-English character sets), typically 2 to 3 pixels. Refer to Figure 5.5 for clarification .

Figure 5.5. Calculating text height.

graphics/05fig05.gif

ACanvas

This encapsulates the device context for the current item in the Object Inspector.

ARect

This represents the client area of the region to be painted .

ASelected

This parameter is true when the list item is currently selected in the Object Inspector.

Figure 5.5 shows a diagram illustrating how the height of text is calculated.

Figure 5.6 shows the relationship between the parameters in Table 5.5 and the actual rendering of an image and text in the Object Inspector. This figure will be referred to throughout the discussion, and additional information is therefore shown.

Figure 5.6. The relationship between image-rendering parameters and actual display.

graphics/05fig06.gif

The ListMeasureWidth() Method

Initially, AWidth is equal to the return value of ACanvas->TextWidth(Value) . However, if an image is added to the display, the width of the image must be added to AWidth to update it. This method, called during the width calculation phase of the drop-down list, enables you to do this. If a square image region is required, AWidth can simply be adjusted by adding ACanvas->TextHeight("Ag")+2 to its current value. This is because this value will equal the default AHeight value, as previously mentioned in Table 5.5. (Also, see Figure 5.6, in which ACanvas->TextHeight("Ag")+2 is 18 ( 16+2 ) pixels.) Remember that Ag could be replaced by any characters . If a larger image is required, a multiple of this value can be used or a constant can be added to the width. If the image width is known, this can simply be added to the current AWidth value. The code is shown in Listing 5.11.

Listing 5.11 Overriding the ListMeasureWidth() Method
 void __fastcall     TShapeTypePropertyEditor::ListMeasureWidth(const AnsiString Value,                                                Graphics::TCanvas* ACanvas,                                                int& AWidth)  {     AWidth += (ACanvas->TextHeight("Ag")+2) + 0; // 0 can be replaced                                                  // by a constant  } 

The ListMeasureHeight() Method

AHeight must not be given a value smaller than ACanvas->TextHeight("Ag")+2 because this would clip the text displayed. Therefore, two choices are available. A constant value can be added to the current AHeight , normally to maintain a constant ratio with the image width, or AHeight can be changed directly. If it is changed directly, the new value must be greater than ACanvas->TextHeight("Ag")+2 ; otherwise , this value should be used. The code is shown in Listing 5.12.

Listing 5.12 Overriding the ListMeasureHeight() Method
 void __fastcall     TShapeTypePropertyEditor::ListMeasureHeight(const AnsiString Value,                                                 Graphics::TCanvas* ACanvas,                                                 int& AHeight)  {     AHeight += 0; // 0 could be replaced by a constant value  }  // OR :  void __fastcall     TShapeTypePropertyEditor::ListMeasureHeight(const AnsiString Value,                                                 Graphics::TCanvas* ACanvas,                                                 int& AHeight)  {     if( (ACanvas->TextHeight("Ag")+2) < ImageHeight )     {        AHeight = ImageHeight;     }  } 

The ListDrawValue() Method

This method does most of the hard work. It is this method that renders each item in the drop-down list by drawing directly onto the list item's canvas. To write well-behaved code, this method should have the layout in Listing 5.13.

This listing works for the case where you are inheriting from a property editor that implements IcustomPropertyEditor. If you do not inherit from such an editor, it is inappropriate to call the inherited methods, since they have no implementation.

To get an appreciation of what the actual rendering code is doing, refer to Figure 5.6. For the big picture, refer to Figure 5.3.

Listing 5.13 A Template for Overriding the ListDrawValue() Method
 void __fastcall     TCustomImagePropertyEditor::ListDrawValue(const AnsiString Value,                                               Graphics::TCanvas* ACanvas,                                               const TRect& ARect,                                               bool ASelected)  {     // Declare an int vRight to indicate the right most edge of the image.     // The v prefix is used to indicate that it is a variable. This used     // to follow the convention used in VCLEditors.pas.     try     {        // Step 1 - Save ACanvas properties that we are going to change.        // Step 2 - Frame the area to be modified. This is required so that any        //          previous rendering on the canvas is overwritten. For example        //          when the IDE selection rendering is applied, i.e. the        //          property value is surrounded by a dashed yellow and black        //          line and the AnsiString representation is highlighted in        //          clNavy, and focus then moves to another list value the        //          modified parts of ACanvas are cleared, ready for the custom        //          rendering. If the entire ACanvas is going to be changed then        //          this operation is not required.        // Step 3 - Perform any preparation required. For example paint a        //          background colour and place a highlight box around the image        //          of the list value if ASelected is true.        //        //          To choose a colour to match the current text used by windows        //          select clWindowText, this is useful as an image border, hence        //          this is often selected as a suitable ACanvas->Pen colour.        //        //          To give the appearance of a clear background, clear border or        //          both set the ACanvas->Brush and/or ACanvas->Pen colour to        //          clWindow.        //        //          To use a colour the same as the Object Inspector choose        //          clBtnFace.                // Step 4 - Determine the value of the current list item.        // Step 5 - Draw the required image onto ACanvas.        // Step 6 - Restore modified ACanvas properties to their original values.     }     __finally     {        // Perform the following operation to render the AnsiString        // representation of the current item, i.e. Value, onto ACanvas.        // 1. Either call the parents ListDrawValue method passing vRight as the        //    l (left) parameter of the Rect variable, i.e.        //        //    TEnumProperty::ListDrawValue(Value,        //                                 ACanvas,        //                                 Rect(vRight,        //                                      ARect.Top,        //                                      ARect.Right,        //                                      ARect.Bottom),        //                                 ASelected);        //    which becomes:        //        //    inherited::ListDrawValue(Value,        //                             ACanvas,        //                             Rect(vRight,        //                                  ARect.Top,        //                                  ARect.Right,        //                                  ARect.Bottom),        //                             ASelected);        //        //    using our typedef which is more maintainable.         // 2. Or perform this operation directly by calling the TextRect() member        //    function directly removing the need to call the parent version of        //    this (ListDrawValue()) virtual function        //    i.e.        //    ACanvas->TextRect( Rect(vRight,        //                            ARect.Top,        //                            ARect.Right,        //                            ARect.Bottom),        //                       vRight+1,        //                       ARect.Top+1,        //                       Value );     }  } 

Actual code based on the template in Listing 5.13 is shown in Listing 5.14. The code renders each item in the drop-down list. Each item in the list consists of an image followed by text representing the enum value to which the item refers. Figure 5.4 shows an image of the rendered drop-down list.

Once again, keep in mind that this assumes there is an inherited implementation for the ICustomPropertyEditor member functions. If that is not the case, do not call the inherited member functions.

Listing 5.14 An Implementation of the ListDrawValue() Method
 void __fastcall     TShapeTypePropertyEditor::ListDrawValue(const AnsiString Value,                                             Graphics::TCanvas* ACanvas,                                             const TRect& ARect,                                             bool ASelected)  {     // Declare vRight ('v' stands for variable)     int vRight = ARect.Bottom - ARect.Top + ARect.Left;     try     {        // Step 1 - Save ACanvas properties that we are going to change        TColor vOldPenColor = ACanvas->Pen->Color;        TColor vOldBrushColor = ACanvas->Brush->Color;        // Step 2 - Frame the area to be modified.        ACanvas->Pen->Color = ACanvas->Brush->Color;        ACanvas->Rectangle(ARect.Left, ARect.Top, vRight, ARect.Bottom);        // Step 3 - Perform any preparation required.        if(ASelected)                            // Choose a Pen colour        {                                        // depending on whether           ACanvas->Pen->Color = clYellow;       // the list value is        }                                        // selected or not        else        {           ACanvas->Pen->Color = clBtnFace;        }        ACanvas->Brush->Color = clBtnFace;       // Choose a background color to                                                 // match the Object Inspector        ACanvas->Rectangle( ARect.Left + 1,      // Draw the background onto                            ARect.Top + 1,       // the Canvas using the                            vRight - 1,          // current Pen and the                            ARect.Bottom - 1 );  // current Brush :-)        // Step 4 - Determine the value of the current list item        TShapeType ShapeType = TShapeType(GetEnumValue(GetPropType(), Value));        // Step 5 - Draw the required image onto ACanvas        ACanvas->Pen->Color = clBlack;        ACanvas->Brush->Color = clWhite;        switch(ShapeType)        {           case stRectangle   : ACanvas->Rectangle(ARect.Left+2,                                                   ARect.Top+4,                                                   vRight-2,                                                   ARect.Bottom-4);                                break;           case stSquare      : ACanvas->Rectangle(ARect.Left+2,                                                   ARect.Top+2,                                                   vRight-2,                                                   ARect.Bottom-2);                                break;           case stRoundRect   : ACanvas->RoundRect(ARect.Left+2,                                                   ARect.Top+4,                                                   vRight-2,                                                   ARect.Bottom-4,                                                   (ARect.Bottom-ARect.Top-6)/2,                                                   (ARect.Bottom-ARect.Top-6)/2);                                break;           case stRoundSquare : ACanvas->RoundRect(ARect.Left+2,                                                   ARect.Top+2,                                                   vRight-2,                                                   ARect.Bottom-2,                                                   (ARect.Bottom-ARect.Top)/3,                                                   (ARect.Bottom-ARect.Top)/3);                                break;           case stEllipse     : ACanvas->Ellipse(ARect.Left+1,                                                 ARect.Top+2,                                                 vRight-1,                                                 ARect.Bottom-2);                                break;           case stCircle      : ACanvas->Ellipse(ARect.Left+1,                                                 ARect.Top+1,                                                 vRight-1,                                                 ARect.Bottom-1);                                break;           default : break;        }        // Step 6 - Restore modified ACanvas properties to their original values        ACanvas->Pen->Color = vOldPenColor;        ACanvas->Brush->Color = vOldBrushColor;     }     __finally     {        // Render the AnsiString representation onto ACanvas        // Use method 1, call the parent method        inherited::ListDrawValue(Value,                                 ACanvas,                                 Rect(vRight,                                      ARect.Top,                                      ARect.Right,                                      ARect.Bottom),                                 ASelected);     }  } 

Step 4 in Listing 5.14 is of crucial importance to the operation of ListDrawValue() . The value of the drop-down list item is determined here. This allows a decision to be made in Step 5 as to what should be rendered. For enumerations such as TShapeType , the AnsiString representation of the value must be converted to an actual value. The code that performs this is

 TShapeType ShapeType = TShapeType(GetEnumValue(GetPropType(), Value)); 

GetEnumValue() is declared in $(BCB)\Include\Vcl\TypInfo.hpp and returns an int value. This int value is used to construct a new TShapeType variable called ShapeType . The function GetPropType() returns a pointer to a TTypeInfo structure containing the TypeInfo for the property type (in this case TShapeType ). This could alternatively have been obtained using

 *GetPropInfo()->PropType 

This is similar to the approach used to obtain type information when registering property editors (see the section "Obtaining a TTypeInfo* (PTypeInfo) from an Existing Property and Class for a Non-VCL Type," earlier in this chapter, for more details) and can be used more generally. Value is the AnsiString representation of the current enumeration value. GetPropType() and GetPropInfo() are both member functions of TPropertyEditor and, as such, are declared in $(BCB)\Include\Vcl\VCLEditors.hpp . Techniques such as these are indispensable to writing property editors, so it is important to be aware of them.

Each of the images is rendered according to the bounding ARect parameter. This means that the code does not need to be modified to enlarge or reduce the rendered images. To do this, simply change the values of AWidth and AHeight . Changing the constant in the ListMeasureWidth() and ListMeasureHeight() methods to 10 , for example, will increase the rendered image size in the drop-down list by 10 pixels in each direction. Note that the image in the property value region will not be affected.

The PropDrawValue() Method

This method is responsible for rendering the current property value in the Object Inspector. The height of the area to be rendered is fixed ( ARect.Bottom - ARect.Top ) so there is less flexibility over the images that can be rendered compared with images rendered in the drop-down list. The code required for this operation is the same as that required to render the same value in the drop-down list. The only difference is the value of the ARect parameter. The rendering can, therefore, be carried out by the ListDrawValue() method, passing the PropDrawValue() parameters as arguments. The code for this member function is shown in Listing 5.15.

Listing 5.15 An Implementation of the PropDrawValue() Method
 void __fastcall     TShapeTypePropertyEditor::PropDrawValue(Graphics::TCanvas* ACanvas,                                             const TRect& ARect,                                             bool ASelected)  {     if( GetVisualValue() != "" )     {        ListDrawValue(GetVisualValue(), ACanvas, ARect, ASelected);     }     else     {        // As in the ListDrawValue method either the parent method can be called        // or the code required to render the text called directly, i.e.        //        // inherited::PropDrawValue(ACanvas, ARect, ASelected);        //        // or:        //        // ACanvas->TextRect( ARect,        //                    ARect.Left+1,        //                    ARect.Top+1,        //                    GetVisualValue() );        //        // For comparison the text is rendered directly, i.e.        ACanvas->TextRect( ARect,                           ARect.Left+1,                           ARect.Top+1,                           GetVisualValue() );     }  } 

The PropDrawName() Method

This is the last of the overridable methods for custom rendering and the one least often required. It controls the rendering of the property Name (see Figure 5.3). As with the PropDrawValue() method, the height of the drawing region is fixed. This method has limited use, but it can be used to add symbols to properties that exhibit certain behavior, read-only properties, for instance (such as About properties). Overuse should be avoided because it might confuse rather than help users.

Another possible use is to add an image to TComponent -derived properties to indicate the component required. This method is not used in the TShapeTypePropertyEditor example, but the required code, should it be needed, is shown in Listing 5.16.

Listing 5.16 An Implementation of the PropDrawName() Method
 void __fastcall     TCustomImagePropertyEditor::PropDrawValue(Graphics::TCanvas* ACanvas,                                               const TRect& ARect,                                               bool ASelected)  {     if( GetName() != "" )     {        // Write a function to render the desired image, similar to        // the ListDrawValue() method, i.e.        //        // PropDrawNameValue(GetName(), ACanvas, ARect, ASelected); // Must be                                                                    // defined     }     else     {        // As in the PropDrawValue method either the parent method can be called        // or the code required to render the text called directly, i.e.        //        // inherited::PropDrawName(ACanvas, ARect, ASelected);        //        // or:        //        // ACanvas->TextRect( ARect,        //                    vRect.Left+1,        //                    ARect.Top+1,        //                    GetName() );        //        // For comparison the text is rendered directly, i.e.        ACanvas->TextRect( ARect,                           ARect.Left+1,                           ARect.Top+1,                           GetName() );     }  } 

The TImageListPropertyEditor from the EnhancedEditors package (see Table 5.1) does implement this method to display an icon representing a TImageList component for TCustomImageList* properties. Listing 5.17 shows its implementation of this method for comparison. Note that ImageListPropertyImage is a resource loaded in the property editor's constructor.

Listing 5.17 An Alternative Implementation of the PropDrawName() Method
 void __fastcall     TImageListPropertyEditor::PropDrawName (Graphics::TCanvas* ACanvas,                                            const TRect& ARect,                                            bool ASelected)  {     TRect ValueRect = ARect;     try     {        // Clear the canvas using the current pen and brush        ACanvas->FillRect(ARect);        if(GetName() != "")        {           if(Screen->PixelsPerInch > 96) // If Large fonts           {              ACanvas->Draw( ARect.Left + 1,                             ARect.Top + 2,                             ImageListPropertyImage );           }           else // Otherwise small fonts           {              ACanvas->Draw( ARect.Left + 1,                             ARect.Top,                             ImageListPropertyImage );           }           ValueRect = Rect( ARect.Left + 16 + 2,                             ARect.Top,                             ARect.Right,                             ARect.Bottom );        }     }     __finally     {        // Whether or not we successfully draw the image we must draw the text        inherited::PropDrawName(ACanvas, ValueRect, ASelected);     }  } 

The code in Listing 5.17 is reasonably straightforward. Of note is the try / __finally block to ensure that the text is always rendered. The code inside the try block is similar to that in Listing 5.16; the only difference is that the ImageListPropertyImage resource is positioned differently, depending on whether the screen is using large or small fonts. After the ImageListPropertyImage resource is rendered, the Rect for rendering the text is offset to allow for the width of the resource, which in this case is 16 pixels.


   
Top


C++ Builder Developers Guide
C++Builder 5 Developers Guide
ISBN: 0672319721
EAN: 2147483647
Year: 2002
Pages: 253

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net