#if 0

    #include "project.h"

    // ----------------------------------------------------------------
    // every function in this source file can be marked as   static
    // except for the "constructor" function
    // ----------------------------------------------------------------

    static MAINTYPE IMAGEId = SOB_TYPE_NO;

// to allow a widget to change the default handling of a SOB
//  1) create a COPY of the default SOB METHODS TABLE in the widget's code scope
//  2) overwrite the functional pointer, in that COPY, to the address of an
//     appropriate custom function.
//     We do that when the first instance of this widget class is instantiated.
//  3) and then the pointer, in the SOB data, to the default  SOB METHODS TABLE
//     with a pointer to our COPY
//     We do that, after step 2, when the first instance of this widget class is instantiated.

//  here we make SPACE for the COPY of the SOB METHODS TABLE

SOBMETHODSTABLE  IMAGESOBMETHODSTABLE;

WNDPROC    wpOrigIMAGEProc ;

IMAGEOBJECT* IMAGELastCreated;
// ---------------------------------

#if IMAGE_HAS_SUBCLASS

    // ----------------------------------------------------------------
    //  example of a custom  interception function     for a  subclassed   widget
    // ----------------------------------------------------------------

    static LRESULT CALLBACK IMAGE_SUBCLASS_proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    // (demo) function causes the widget to change colour when the mouse hovers over the widget.
    // does not work for all widget types

    TRACKMOUSEEVENT tme = { sizeof(tme) };
    static myInt trackingFlag = 0;
    static COLORREF bgRGBenter = 0;

    union
    {
        SOBDATA* thisSob;
        IMAGEOBJECT* thisObject;
    } promote = {};

    // be WARNED
    // try to find an OBJECT by the value of a (generic) property, in this case
    // it's  HWND , we default to finding it by it scanning the SOBDATA.
    // If we assume, as in this function, that it was triggered by widget type XYZ
    // then we would need a widget specific search function.
    // So scan the SOBDATA and then "cast" using the union to the appropriate widgettype.

    promote.thisSob = sob_find_hwnd(hwnd);
    if (promote.thisSob)
    {
        switch (msg)
        {
            case WM_NCHITTEST:

                if (!trackingFlag)
                {
                    tme.dwFlags = TME_LEAVE; // TME_HOVER;
                    tme.hwndTrack = hwnd;
                    tme.dwHoverTime = 1000;
                    TrackMouseEvent(&tme);

                    trackingFlag = 1;
                    bgRGBenter = promote.thisObject->bgRGB;

                    IMAGE.SetRGB(promote.thisObject, RGB(0xc6, 0xe7, 0xff));
                }
                break;

            case WM_MOUSEHOVER:

                break;

            case WM_MOUSELEAVE:
                if (trackingFlag)
                {
                    trackingFlag = 0;
                    IMAGE.SetRGB(promote.thisObject, bgRGBenter);
                }
                break;

            default:
                break;
        }
    }
    return CallWindowProc(wpOrigIMAGEProc, hwnd, msg, wParam, lParam);
}

#endif

// ---------------------------------

#if IMAGE_HAS_OWNERDRAWN
    // ----------------------------------------------------------------
    //  example of a custom  ownerdrawn handler
    // ----------------------------------------------------------------

    OWNDERDRAWNHANDLER IMAGE_OD_HANDLER;

static void  IMAGE_OD_HANDLER(SOBDATA* sData, LPDRAWITEMSTRUCT lpdis)
{
    canvasHANDLE* pCanvasHandle = sData->pCanvasHandle;

    if (sData->pCanvasHandle EQU NULL) return;
    int w, h, x, y;
    int xFactor, yFactor, Factor;

    RECT winRECT;

    GetClientRect(sData->hWnd, &winRECT);

    w = (int)pCanvasHandle->max_w;

    h = (int)pCanvasHandle->max_h;

    x = (int)(winRECT.right - winRECT.left);
    y = (int) ( winRECT.bottom - winRECT.top);
    if ((!x) LOR(!y)) return;
    xFactor = (x * 100) / w;
    yFactor = (y * 100) / h;

    if (x > w)   // magnify
    {
        Factor = xFactor;
        if (y > h)   // magnify
        {
            yFactor = (y * 100) / h;
            if (Factor > yFactor) Factor = yFactor;
        }
        else
        {
            Factor = -(h * 100) / y;
        }
    }
    else
    {
        Factor = -(w * 100) / x;
        if (y > h)   // magnify
        {
        }
        else
        {
            yFactor = -(h * 100) / y;
            if (Factor > yFactor) Factor = yFactor;
        }
    }
    if (Factor > 0)
    {
        w = w * Factor / 100;
        h = h * Factor / 100;
    }
    else
    {
        w = w * 100 / (-Factor);
        h = h * 100 / (-Factor);
    }

    //      if (!pCanvasHandle->DisableHalfTone)  //  0 = RGB, 1 = monochrome, else grayscale
    {
        SetStretchBltMode(lpdis->hDC, HALFTONE);
        SetBrushOrgEx(lpdis->hDC, 0, 0, NULL); ;
    }

    StretchBlt
    (lpdis->hDC
    // DST  x , y , width , height
    , 0
    , 0
    , w
    , h
    , pCanvasHandle->dc
    , 0, 0
    , (int)pCanvasHandle->max_w
    , (int)pCanvasHandle->max_h

    , SRCCOPY
    );

}

#endif

// ---------------------------------

#if IMAGE_HAS_RGB

    IMAGEFNC_SETVALUE IMAGE_SetRGB;
static  myUInt IMAGE_SetRGB(IMAGEOBJECT* thisObject, myUInt rgbValue)
{
    if (fnInvalidSobGeneric(SOBOBJ thisObject))return SOB_NOK;

    return sob_SetRGB(thisObject->psSobData, (COLORREF)rgbValue);
}
#endif

// ---------------------------------

#if IMAGE_HAS_STATE
    // some widget types have a  STATE
    // e.g. menu item ticked, radio button, check button

    IMAGEFNC_GETVALUE IMAGE_GetState;
static  myUInt IMAGE_GetState(IMAGEOBJECT* thisObject)
{
    if (fnInvalidSobGeneric(SOBOBJ thisObject))return SOB_NOK;

    return
    (  (myInt)SendMessage(thisObject->hWnd, BM_GETCHECK, 0, 0)
    EQU BST_CHECKED
    );
}

// ---------------------------------

IMAGEFNC_SETVALUE  IMAGE_SetState;
static  myUInt IMAGE_SetState(IMAGEOBJECT* thisObject, myUInt check)
{
    if (fnInvalidSobGeneric(SOBOBJ thisObject))return SOB_NOK;

    SOBDATA* pData = thisObject->ppSobData;

    SOBDATA* cData = sob_get_first_child(pData);
    while (cData)
    {
        if (cData->type EQU  IMAGEId)
        {
            SendMessage
            (cData->hWnd
            , BM_SETCHECK
            , (WPARAM)BST_UNCHECKED
            , 0
            );
        }
        cData = sob_get_next_child(cData);
    }

    if (check) check = BST_CHECKED;
    else  check = BST_UNCHECKED;

    SendMessage
    (thisObject->hWnd
    , BM_SETCHECK
    , (WPARAM)check
    , 0
    );
    return (check EQU BST_CHECKED);
}

// ---------------------------------

IMAGEFNC_SHAKE IMAGE_ToggleState;
static  myUInt IMAGE_ToggleState(IMAGEOBJECT* thisObject)
{
    if (fnInvalidSobGeneric(SOBOBJ thisObject))return SOB_NOK;

    return  IMAGE.SetState(thisObject, (IMAGE.GetState(thisObject)  EQU 0));
}

#endif

// ---------------------------------

#if  IMAGE_HAS_EVENTHANDLER

    // example of a EVENTHANDLER: in this case when the widget is clicked on with the mouse
    // Careful this  function is only valid for widgets that receive
    //   WM_COMMAND messages   carrying a  BN_CLICKED notification

    static void IMAGE_object_clicked(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, SOBDATA * sData)
{
    IMAGEOBJECT* thisObject = (IMAGEOBJECT*)lParam;
    WORD NotificationCode = HIWORD(wParam);
    WORD identifier = LOWORD(wParam);

    switch (NotificationCode)
    {
        case CBN_SELCHANGE :
            swprintf_s
            (txtBuffer, szTxtBuffer, L" IMAGE clicked 0x%04x  0x%04x  0x%04x\n"
            , thisObject->sIndex
            , NotificationCode
            , identifier);    addText(txtBuffer);

            SOB.Report((SOBDATA*)thisObject);

            //          IMAGE.ToggleState(thisObject);

            break;

        default:
            #if DebuggingAid
                swprintf_s
                (txtBuffer, szTxtBuffer, L" IMAGE unhandled notification 0x%04x  0x%04x  0x%04x\n"
                , thisObject->sIndex
                , NotificationCode
                , identifier);    addText(txtBuffer);
            #endif

            break;
    }
}

IMAGEFNC_SETEVENTHANDLER IMAGE_SetEventHandler;
static myUInt IMAGE_SetEventHandler(IMAGEOBJECT* thisObject, EVENTHANDLER value)
{
    if (fnInvalidSobGeneric(SOBOBJ thisObject))return SOB_NOK;

    return SOB.SetEventHandler(SOBOBJ thisObject, value);
}

#endif

// ---------------------------------

#if  IMAGE_HAS_EMPTY
    // some widget types can be emptied e.g. combo boxes, RichTextEdit ..

    IMAGEFNC_SHAKE IMAGE_Empty;
static  myUInt IMAGE_Empty(IMAGEOBJECT* thisObject)
{
    if (fnInvalidSobGeneric(SOBOBJ thisObject))return SOB_NOK;

    if (thisObject->pCanvasHandle NEQU NULL)
    {
        free(thisObject->pCanvasHandle);
        thisObject->pCanvasHandle = NULL;
    }

    InvalidateRect((SOBOBJ thisObject)->hWnd, (RECT*)NULL, TRUE);

    return  SOB_OK;

}

#endif

// ---------------------------------

#if  IMAGE_HAS_GREY
    // some widget types can be greyed e.g. menu items, some button types (rare but possible)

    IMAGEFNC_SETVALUE IMAGE_GetGrey;
static  myUInt IMAGE_GetGrey(IMAGEOBJECT* thisObject)
{
    if (fnInvalidSobGeneric(SOBOBJ thisObject))return SOB_NOK;

    return  SOB_OK;
}

IMAGEFNC_SETVALUE IMAGE_SetGrey;
static  myUInt IMAGE_SetGrey(IMAGEOBJECT* thisObject, myUInt value)
{
    if (fnInvalidSobGeneric(SOBOBJ thisObject))return SOB_NOK;

    return  SOB_OK;
}

#endif

// ---------------------------------

#if  IMAGE_HAS_SELECTION
    // some widget types have selectable items
    // e.g. COMBO boxes, edit fields

    IMAGEFNC_GETVALUE IMAGE_GetSelectionIndex;
static  myUInt IMAGE_GetSelectionIndex(IMAGEOBJECT* thisObject)
{
    #if IMAGE_IS_2D
        return (myInt)SendMessage(thisObject->hWnd, CB_GETCURSEL, 0, 0);
    #endif

    if (fnInvalidSobGeneric(SOBOBJ thisObject))return SOB_NOK;

    return  SOB_OK;
}

IMAGEFNC_SETVALUE IMAGE_SetSelectionIndex;
static  myUInt IMAGE_SetSelectionIndex(IMAGEOBJECT* thisObject, myUInt value)
{
    #if IMAGE_IS_2D
        // Sets the ZERO based index to be  the currently selected item of the combo
        return (int)SendMessage(thisObject->hWnd, CB_SETCURSEL, value, 0);
    #endif

    if (fnInvalidSobGeneric(SOBOBJ thisObject))return SOB_NOK;

    return  SOB_OK;
}

IMAGEFNC_GETVALUE IMAGE_GetItemCount;
static  myUInt IMAGE_GetItemCount(IMAGEOBJECT* thisObject)
{
    #if IMAGE_IS_2D

        return (myUInt)SendMessage(thisObject->hWnd, CB_GETCOUNT, 0, 0);
    #endif
    if (fnInvalidSobGeneric(SOBOBJ thisObject))return SOB_NOK;

    return  SOB_OK;
}

IMAGEFNC_NEWITEMTEXT IMAGE_AddItem;
static myUInt IMAGE_AddItem(IMAGEOBJECT* thisObject, TCHAR* pText)
{

    #if IMAGE_IS_2D
        int index;

        index = (int)SendMessage(thisObject->hWnd, CB_ADDSTRING, 0, (LPARAM)pText);
        SendMessage(thisObject->hWnd, CB_SETITEMDATA, (WPARAM)index, (LPARAM)index);
        return (myUInt)index;
    #endif

    if (fnInvalidSobGeneric(SOBOBJ thisObject))return SOB_NOK;

    return  SOB_OK;
}

IMAGEFNC_SETITEMTEXT IMAGE_GetItemText;
static TCHAR* IMAGE_GetItemText(IMAGEOBJECT* thisObject, myUInt value)
{
    if (fnInvalidSobGeneric(SOBOBJ thisObject))return NULL;

    return  (TCHAR*)_TEXT("");
}

IMAGEFNC_SETITEMTEXT IMAGE_SetItemText;
static myUInt IMAGE_SetItemText(IMAGEOBJECT* thisObject, myUInt value, TCHAR* pText)
{
    if (fnInvalidSobGeneric(SOBOBJ thisObject))return SOB_NOK;

    return  SOB_OK;
}

IMAGEFNC_SETVALUE IMAGE_DeleteIndex;
static  myUInt IMAGE_DeleteIndex(IMAGEOBJECT* thisObject, myUInt value)
{
    #if IMAGE_IS_2D
        // Sets the ZERO based index to be  the currently selected item of the combo
        return (int)SendMessage(thisObject->hWnd, CB_DELETESTRING, value, 0);
    #endif
    if (fnInvalidSobGeneric(SOBOBJ thisObject))return SOB_NOK;

    return  SOB_OK;
}

#endif

// ---------------------------------
#if IMAGE_HAS_TITLE

    IMAGEFNC_GETTEXT  IMAGE_GetTitle;
static TCHAR* IMAGE_GetTitle(IMAGEOBJECT* thisObject)
{
    if (fnInvalidSobGeneric(SOBOBJ thisObject))return NULL;
    return   SOB.GetTitle(SOBOBJ thisObject);
}

// ---------------------------------

IMAGEFNC_SETTEXT  IMAGE_SetTitle;
static myUInt IMAGE_SetTitle(IMAGEOBJECT* thisObject, TCHAR* txt)
{
    if (fnInvalidSobGeneric(SOBOBJ thisObject))return SOB_NOK;

    return SOB.SetTitle(SOBOBJ thisObject, txt);
}
#endif

// ---------------------------------

SOBFNC_GETTEXT IMAGE_SOB_GetType;
static TCHAR* IMAGE_SOB_GetType(SOBDATA* thisSob)
{
    static TCHAR objText[] = TEXT("IMAGE");
    return  objText;
}

IMAGEFNC_GETTEXT IMAGE_GetType;
static  TCHAR* IMAGE_GetType(IMAGEOBJECT* thisObject)
{
    if (fnInvalidSobGeneric(SOBOBJ thisObject))return NULL;
    return   IMAGE_SOB_GetType(SOBOBJ thisObject);
}

// ---------------------------------

IMAGEFNC_SETVALUE IMAGE_SetShow;
static  myUInt IMAGE_SetShow(IMAGEOBJECT* thisObject, myUInt value)
{
    // this will need customising if the widget is complex
    // i.e. is really a member of a set of  widgets that can not all appear at the same time
    // e.g. a radio button     or an  overlay set

    if (fnInvalidSobGeneric(SOBOBJ thisObject))return SOB_NOK;

    return SOB.SetShow(SOBOBJ thisObject, value);
}

// ---------------------------------

#if IMAGE_HAS_STRETCH

    IMAGEFNC_SETVALUE IMAGE_SetStretch;
static  myUInt IMAGE_SetStretch(IMAGEOBJECT* thisObject, myUInt value)
{

    if (fnInvalidSobGeneric(SOBOBJ thisObject))return SOB_NOK;

    SOB.SetStretch(SOBOBJ thisObject, value);

    return SOB.SetShow(SOBOBJ thisObject, value);
}
#endif

// ---------------------------------

IMAGEFNC_GETVALUE IMAGE_GetUserData ;
static myUInt IMAGE_GetUserData (IMAGEOBJECT* thisObject)
{
    if (fnInvalidSobGeneric(SOBOBJ thisObject))return NULL;
    return SOB.GetUserData(SOBOBJ thisObject);
}

// ---------------------------------

IMAGEFNC_SETVALUE IMAGE_SetUserData;
static myUInt IMAGE_SetUserData(IMAGEOBJECT* thisObject, myUInt value)
{
    if (fnInvalidSobGeneric(SOBOBJ thisObject))return SOB_NOK;
    return SOB.SetUserData (SOBOBJ thisObject, value);
}
// ---------------------------------

#if  IMAGE_HAS_LAST

    IMAGEFNC_LASTOBJECT IMAGE_LastWidget;
static  IMAGEOBJECT* IMAGE_LastWidget()
{
    return IMAGELastCreated;
}
#endif

// ---------------------------------

IMAGEFNC_SETTEXT IMAGE_Open;

static  myUInt IMAGE_Open(IMAGEOBJECT* thisObject, TCHAR* pName)
{
    if (fnInvalidSobGeneric(SOBOBJ thisObject))return SOB_NOK;

    if (thisObject->pCanvasHandle NEQU NULL)
    {
        free(thisObject->pCanvasHandle);
        thisObject->pCanvasHandle = NULL;
    }
    //  (SOBOBJ thisObject)->pCanvasHandle = canvas_from_file_gdip(pName);

    InvalidateRect((SOBOBJ thisObject)->hWnd, (RECT*)NULL, TRUE);

    return  SOB_OK;
}

// ----------------------------------------------------------------
// the "destructor" function
// ----------------------------------------------------------------
SOBFNC_SHAKE IMAGE_SOB_Delete;
static myUInt IMAGE_SOB_Delete(SOBDATA* thisSob)
{
    // we need this function so that when a CONTAINER is deleted
    // it can trigger a delete of any widget related things of it's child
    // SOB OBJECTs here:-

    if (fnInvalidSobGeneric(thisSob)) return SOB_NOK;

    if (thisSob->pCanvasHandle NEQU NULL)
    {
        free(thisSob->pCanvasHandle);
        thisSob->pCanvasHandle = NULL;
    }

    SOB.Delete(thisSob); // trigger the generic Delete

    return SOB_OK;
}

IMAGEFNC_SHAKE IMAGE_Delete;
static  myUInt IMAGE_Delete(IMAGEOBJECT* thisObject)
{
    // this will need customising if the widget is complex
    // i.e. is really a set of widgets
    // e.g. an  overlay set

    if (fnInvalidSobGeneric(SOBOBJ thisObject))return SOB_NOK;

    return   IMAGE_SOB_Delete(SOBOBJ thisObject);
}

// ----------------------------------------------------------------
//  inheritance from the underlying SOB
// ----------------------------------------------------------------

// if an inherited function is to be customised/overwritten
// then do it HERE:

IMAGEFNC_GETTEXT  fnGetTypeTextCustom;
static TCHAR* fnGetTypeTextCustom(SOBDATA* sData)
{
    return (TCHAR*)_TEXT("IMAGE");
}

// ----------------------------------------------------------------
// the "constructor" function
// ----------------------------------------------------------------

IMAGEFNC_GETOBJECT IMAGE_Create;
static IMAGEOBJECT* IMAGE_Create(IMAGECREATEPARAMETERS)
{
    union
    {
        SOBDATA* thisSob;
        IMAGEOBJECT* thisObject;
    } promote = {};

    // ---------------------------------
    // in some cases a given widget can only be assigned to certain types of container
    // this is probably the best case to catch such exceptions and
    // permit / block them as appropriate

    // ---------------------------------
    // use the FONT assigned to the PARENT as guidelines for text size

    SOBDATA* pData = sob_get_base(gpSobIndex);
    int nbrLines = 1;  // some CONSTRUCTOR functions want 2 input parameters,
    // typicaly the 2nd parameter is the height of the widget
    // give as the nbr of text lines

    #if IMAGE_IS_2D
        nbrLines= nbrLines* lines;
    #endif

    int height = 0;
    int width = 0;

    int_sob_text_size(pData, txt, &width, &height);

    // But the HEIGHT only covers text, we need to increase it to allow multiple text
    // lines   AND for any SOB surround
    int objHeight = height * nbrLines + 8;
    int objWidth = width + 20;

    // ---------------------------------
    // A  Screen OBject ( SOB )  is really a structure
    //    the GENERIC structure
    //    and a ptr to the VTABLE
    // ---------------------------------
    // An OBJECT instance is really a structure
    //    the GENERIC structure
    //    and a  ptr to the OBJECT's private functions
    // ---------------------------------
    // So a   SOB   and an    OBJECT     have identical structures
    // WHY:
    // This way we can create a SOB as a sort of generic OBJECT.
    // Some functions, predominantly concerned with
    //    screen size management
    //    screen position management
    //   are OBJECT independent.
    //
    // Then we can have an OBJECT specific "view" were each OBJECT type
    // can have its own strongly typed structure.
    // ---------------------------------

    if (!IMAGEId)
    {
        // ---------------------------------
        #if DeveloperMode

            union
            {
                myUInt* pEntry;
                void * pVoid;
            } reRef = {};

            reRef.pVoid = & IMAGE;
            myUInt* pEntry = reRef.pEntry;

            int entryCount = sizeof(IMAGEMETHODTABLE) / sizeof(void*);
            int i;
            for (i = 0; i < entryCount; i++)
            {
                if ((!*pEntry)   LOR(*pEntry BAND 0x07))
                { 
                    swprintf_s(txtBuffer, szTxtBuffer, _TEXT("IMAGE methods table, entry: %02i,  not fully/correctly populated. 0x%llx\n"), i, *pEntry);
                    ReportError(txtBuffer);
                }
                pEntry++;
            }

        #endif

        // ---------------------------------

        IMAGEId = int_next_maintype();

        // to allow a widget to change the default handling of a SOB
        //  1) create a COPY of the default SOB METHODS TABLE in the widget's code scope
        //  2) overwrite the functional pointer, in that COPY, to the address of an
        //     appropriate custom function.
        //     We do that when the first instance of this widget class is instantiated.
        //  3) and then the pointer, in the SOB data, to the default  SOB METHODS TABLE
        //     with a pointer to our COPY
        //     We do that, after step 2, when the first instance of this widget class is instantiated.

        // ---------------------------------
        //    SPACE for the COPY of the SOB METHODS TABLE  was made at the TOP of this source file
        //    SOBMETHODSTABLE  IMAGESOBMETHODSTABLE;
        // ---------------------------------
        // populate the COPY with the default SOB METHID TABLE

        fnInheritSOBmethods( &  IMAGESOBMETHODSTABLE);

        // ---------------------------------
        // now populate those entries, in the COPY of the  SOB METHODS TABLE , that are
        // to be widget specific

        IMAGESOBMETHODSTABLE.GetType = & IMAGE_SOB_GetType;

        IMAGESOBMETHODSTABLE.Delete = & IMAGE_SOB_Delete;
    }

    // ---------------------------------

    promote.thisSob = sob_new_new(IMAGEId, SOB_CONTAINER_NO, objWidth, objHeight);
    if (promote.thisSob EQU NULL)
    {
        int_return_maintype();  //  give the custom IMAGEId back
        return NULL;
    }

    // ---------------------------------
    // replace the default SOB METHOD TABLE with our widget's custom table

    promote.thisSob->sVtbl = &IMAGESOBMETHODSTABLE;

    promote.thisSob->txtRGB = -1; // use system default
    promote.thisSob->bgRGB  = -1; // use system default

    // ---------------------------------
    // create an instance of the  IMAGE widget on the display

    PCWSTR objClassName = _TEXT(  "BUTTON");
    DWORD objStyle = WS_VISIBLE | WS_CHILD |   BS_PUSHBUTTON | BS_OWNERDRAW ; // | BS_OWNERDRAW;

    (void)sob_CreateWindowExNew(promote.thisSob, 0, objClassName, txt, objStyle);

    #if IMAGE_HAS_SUBCLASS
        // ---------------------------------
        // if you need to subclass the widget (i.e. intercept its internal message handling)
        // then insert the interception function here.

        wpOrigIMAGEProc = (WNDPROC)SetWindowLongPtr(promote.thisObject->hWnd, GWLP_WNDPROC, (LONG_PTR)IMAGE_SUBCLASS_proc);
    #endif

    #if IMAGE_HAS_OWNERDRAWN
        // ---------------------------------
        // if this widget is ownderdraw then link in the handler here

        promote.thisSob->ownerDrawnHandler = IMAGE_OD_HANDLER;
    #endif

    #if IMAGE_HAS_EVENTHANDLER
        // ---------------------------------
        // All OBJECTS are created with a prime/main  EVENTHANDLER
        // For this particular OBJECT the prime/main  EVENTHANDLER
        // is a left mouse click

        // Normally the  "user"  sets a, probably different, EVENTHANDLER for
        // each instantiated widget.

        // If you want you can set a default EVENTHANDLER which the
        // "user" can overwrite for a specific OBJECT
        promote.thisSob->eventHandler = (EVENTHANDLER)IMAGE_object_clicked;
    #endif

    IMAGELastCreated = promote.thisObject;

    return   promote.thisObject;
}

// ----------------------------------------------------------------
// the external view
// ----------------------------------------------------------------

// BE WARNED: must be in the same sequence as the struct defn in the  header file
// if you create custom functions / methods for this widget then they
// MUST be in the struct defn and initialised into this struct instance.

IMAGEMETHODTABLE  IMAGE =
{
    IMAGE_Create
    , IMAGE_Delete

    #if IMAGE_HAS_RGB
        , IMAGE_SetRGB
    #endif

    #if IMAGE_HAS_EMPTY
        , IMAGE_Empty
    #endif

    #if IMAGE_HAS_STATE
        , IMAGE_GetState
        , IMAGE_SetState
        , IMAGE_ToggleState

    #endif

    #if IMAGE_HAS_EVENTHANDLER
        , IMAGE_SetEventHandler
    #endif

    #if  IMAGE_HAS_GREY
        , IMAGE_GetGrey
        , IMAGE_SetGrey
    #endif

    #if  IMAGE_HAS_SELECTION
        , IMAGE_GetSelectionIndex
        , IMAGE_SetSelectionIndex

        , IMAGE_AddItem

        , IMAGE_GetItemCount

        , IMAGE_GetItemText
        , IMAGE_SetItemText

        , IMAGE_DeleteIndex

    #endif

    #if IMAGE_HAS_TITLE
        , IMAGE_GetTitle
        , IMAGE_SetTitle
    #endif

    #if IMAGE_HAS_STRETCH
        , IMAGE_SetStretch
    #endif

    #if IMAGE_HAS_LAST
        , IMAGE_LastWidget
    #endif

    , IMAGE_Open

};

#endif
