Click here to Skip to main content
15,888,527 members
Please Sign up or sign in to vote.
2.00/5 (1 vote)
See more:
I need some help with window api 32 bit programming in C with Visual C++ 2008.

Load a picture path/filename from command line in a window.
But if it is bigger with working scrollbars.
A mouseclick give the xy of the picture not from the window client area.

Has anyone an advice or example ?
Posted
Updated 26-Mar-15 14:55pm
v3
Comments
Sergey Alexandrovich Kryukov 26-Mar-15 23:53pm    
Not clear. Example of what? What do you want to do with the size, scroll bars, mouse click?
—SA
peterkoller123 27-Mar-15 3:51am    
Its a learning game for little children.

A big picture with a ball, an elephant, a house etc
Programm ask where is the elephant and child click on picture.
Get the xy coodinates of the mouseclick and program say "good" or "try again".
The picture must be scrollable in the client area.



I had a bit of spare time this evening while waiting for someone, so decided to have a bit of a play. I haven't bothered with error checking, you should add that yourself. I've also elected to program in a style similar to the one MS used when programming many of the different window-classes, as found in portions of the (re-engineered) Wine project. (https://github.com/wine-mirror/wine/tree/master/dlls/comctl32[^])

I've tried to make it very simple to interact with.

First, you must register the class - you can do this during program startup, before anything is shown on the screen.

Next, the two messages I've defined. IM_SETIMAGE and IM_GETCLICKPOS. You can send these messages explicitly, or you can use the supplied macros.
If you elect to send these messages yourself, you need only know:

IM_SETIMAGE expects the lParam to hold the HBITMAP of the new image. The return value is the previous image (if any, NULL otherwise)

IM_GETCLICKPOS takes no parameters and returns the position within the image of the last click pos (you get notified of clicks through a WM_COMMAND message). The lo-order word contains the X position and the hi-order word contains the Y position. Positions to the left/above the image return negative values.

The background colour isn't customizable from within the containing application, though you could easily add code to handle this situation, or more simply, edit the function onImgWindowEraseBackground.

The code's far from perfect, but should help provide a good starting point. You'll still have a bit of work. While you mention passing the name of a file to the program in order to choose the image, I'd suggest that you approach this with a little more sophistication and create a 'config' file of sorts. In this file, you could include things like (1) the name of the image file (2) the number of areas of interest (3) co-ordinate pairs for the points of a polygon that enclose these areas.

Perhaps something like this:
------------config.txt------------
Image: image.bmp
Poly Count: 3
Apple: 10,10, 20,10, 20,20, 10,20
Water Melon: 100,100, 200,100, 200,200, 100,200
Triangle: 50,50, 75,75, 25,75
----------------------------------


For a basic example of use, I've thrown-together the following dialog app. (written with Code::Blocks, built with MinGW)

main.cpp
C++
#include <windows.h>
#include <commctrl.h>
#include <stdio.h>
#include "resource.h"
#include "../scrollImage/imgWindow.h"

HINSTANCE hInst;

BOOL CALLBACK DlgMain(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch(uMsg)
    {
        case WM_INITDIALOG:
        {
            HBITMAP img;
            img = (HBITMAP)LoadImage(hInst, "image.bmp", IMAGE_BITMAP, 0,0, LR_LOADFROMFILE);
            ImageWindow_SetImage( GetDlgItem(hwndDlg, IDC_IMAGE_WINDOW), img);
        }
        return TRUE;

        case WM_CLOSE:
        {
            EndDialog(hwndDlg, 0);
        }
        return TRUE;

        case WM_COMMAND:
        {
            switch(LOWORD(wParam))
            {
                case IDC_IMAGE_WINDOW:
                    LPARAM clickPos = ImageWindow_GetClickPos( (HWND)lParam );
                    short xPos = LOWORD(clickPos);
                    short yPos = HIWORD(clickPos);
                    char msg[32];
                    sprintf(msg, "Click pos: %d,%d\n", xPos, yPos);
                    SetDlgItemText(hwndDlg, IDC_CLICKPOS_OUTPUT, msg);
                    break;
            }
        }
        return TRUE;
    }
    return FALSE;
}


int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
    hInst=hInstance;
    InitCommonControls();
    IMGWINDOW_Register();
    return DialogBox(hInst, MAKEINTRESOURCE(DLG_MAIN), NULL, (DLGPROC)DlgMain);
}


resource.h
C++
#ifndef IDC_STATIC
#define IDC_STATIC (-1)
#endif

#define DLG_MAIN                                100
#define IDC_CLICKPOS_OUTPUT                     40000
#define IDC_IMAGE_WINDOW                        40001


resource.rc
C++
// Generated by ResEdit 1.6.2
// Copyright (C) 2006-2014
// http://www.resedit.net
#include <windows.h>
#include <commctrl.h>
#include <richedit.h>
#include "resource.h"

//
// Dialog resources
//
LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
DLG_MAIN DIALOG 0, 0, 185, 166
STYLE DS_3DLOOK | DS_CENTER | DS_MODALFRAME | DS_SHELLFONT | WS_CAPTION | WS_VISIBLE | WS_POPUP | WS_SYSMENU
CAPTION "Dialog"
FONT 8, "Ms Shell Dlg"
{
    LTEXT           "", IDC_CLICKPOS_OUTPUT, 7, 151, 171, 8, SS_LEFT, WS_EX_LEFT
    CONTROL         "", IDC_IMAGE_WINDOW, "imgWindow32", 0x50020000, 7, 7, 171, 139, 0x00000000
}




Finally, here's the meat and bones of the image display and click-pos calculation.
Again, quite simple even if a little lengthy.
imgWindow.h
C++
#ifndef imgWindow_h
    #define imgWindow_h

#include <windows.h>

// externably callable functions
//
BOOL IMGWINDOW_Register();
void IMGWINDOW_Unregister();


// window class-name definitions
//
#define IMGWINDOW_CLASSA "imgWindow32"
#define IMGWINDOW_CLASSW L"imgWindow32"

#ifdef UNICODE
    #define IMGWINDOW_CLASS IMGWINDOW_CLASSW
#else
    #define IMGWINDOW_CLASS IMGWINDOW_CLASSA
#endif // UNICODE


// available user-defined messages for this class
//
#define IM_SETIMAGE     WM_USER + 1
#define IM_GETCLICKPOS  WM_USER + 2

// macros for interacting with the control using the above-defined messages
//
#define ImageWindow_SetImage(hWnd, hBitmap) SendMessage(hWnd, IM_SETIMAGE, 0, (LPARAM)hBitmap)
#define ImageWindow_GetClickPos(hWnd) SendMessage(hWnd, IM_GETCLICKPOS, 0, 0)

#endif // imgWindow_h



imgWindow.cpp
C++
#include "imgWindow.h"

#ifndef min
    #define min(a,b) a < b ? a : b
#endif // min

typedef struct
{
    HWND self;
    short  clickX, clickY;
    BOOL mouseDown;
    int xScrollPos, yScrollPos;
    int xImgOfs, yImgOfs;
    HBITMAP bkg;
    HDC memDC;
    HBITMAP oldMemBmp;
    int bmpWidth, bmpHeight;
    RECT clientRect;
} IMGWINDOW_INFO;


#define IMGWINDOW_GetInfoPtr(hWindow) ((IMGWINDOW_INFO *)GetWindowLongPtr (hWindow, 0))


int onImgWindowCreate(HWND hwnd, WPARAM wParam, LPARAM lParam)
{
    IMGWINDOW_INFO *infoPtr = (IMGWINDOW_INFO*)LocalAlloc(LPTR, sizeof(IMGWINDOW_INFO));
    if (!infoPtr)
        return -1;

    ZeroMemory(infoPtr, sizeof(infoPtr));
    infoPtr->self = hwnd;

    infoPtr->clickX = -1;
    infoPtr->clickY = -1;

    HDC mDC;
    mDC = GetDC(hwnd);
    infoPtr->memDC = CreateCompatibleDC(mDC);
    infoPtr->oldMemBmp = (HBITMAP) GetCurrentObject(infoPtr->memDC, OBJ_BITMAP);
    ReleaseDC(hwnd, mDC);

    GetClientRect(hwnd, &infoPtr->clientRect);

    SetWindowLong(hwnd, 0, (LONG)infoPtr);      // possible x64 problem here //
    return 0;
}

int onImgWindowDestroy(HWND hwnd)
{
    IMGWINDOW_INFO *infoPtr = (IMGWINDOW_INFO*)LocalAlloc(LPTR, sizeof(IMGWINDOW_INFO));
    SelectObject(infoPtr->memDC, infoPtr->oldMemBmp);
    DeleteDC( infoPtr->memDC );
    LocalFree(infoPtr);
    return 0;
}

int onImgWindowEraseBackground(HWND hwnd, WPARAM wParam, LPARAM lParam)
{
    RECT clientRect;
    HBRUSH bkBrush;
    IMGWINDOW_INFO *infoPtr = IMGWINDOW_GetInfoPtr(hwnd);
    bkBrush = CreateSolidBrush( RGB(255,0,255) );
    GetClientRect(hwnd, &clientRect);
    FillRect( (HDC)wParam, &clientRect, bkBrush);
    DeleteObject(bkBrush);
    return 1;
}

int doImgWindowScroll(HWND hwnd, WPARAM wParam, int orientation, int windowMin, int windowMax)
{
    int windowSize = windowMax - windowMin + 1;
    switch (LOWORD(wParam))
    {
        case SB_THUMBTRACK:
            SetScrollPos(hwnd, orientation, HIWORD(wParam), true);
            break;

        case SB_PAGERIGHT:
            SetScrollPos(hwnd, orientation, GetScrollPos(hwnd, orientation)+windowSize, true);
            break;

        case SB_PAGELEFT:
            SetScrollPos(hwnd, orientation, GetScrollPos(hwnd, orientation)-windowSize, true);
            break;

        case SB_LINELEFT:
            SetScrollPos(hwnd, orientation, GetScrollPos(hwnd, orientation)-16, true);
            break;

        case SB_LINERIGHT:
            SetScrollPos(hwnd, orientation, GetScrollPos(hwnd, orientation)+16, true);
            break;
    }
    IMGWINDOW_INFO *mData = IMGWINDOW_GetInfoPtr(hwnd);
    int scrollPos = GetScrollPos(hwnd, orientation);
    if (orientation == SB_HORZ)
        mData->xScrollPos = scrollPos;
    else if (orientation == SB_VERT)
        mData->yScrollPos = scrollPos;

    InvalidateRect(hwnd, NULL, true);
    return 0;
}


int onImgWindowHscroll(HWND hwnd, WPARAM wParam, LPARAM lParam)
{
    RECT clientRect;
    GetClientRect(hwnd, &clientRect);
    return doImgWindowScroll(hwnd, wParam, SB_HORZ, clientRect.left, clientRect.right);
}

int onImgWindowVscroll(HWND hwnd, WPARAM wParam, LPARAM lParam)
{
    RECT clientRect;
    GetClientRect(hwnd, &clientRect);
    return doImgWindowScroll(hwnd, wParam, SB_VERT, clientRect.top, clientRect.bottom);
}

void imgWindowSetScrollInfo(HWND hwnd)
{
    int mWidth;
    RECT mRect;
    SCROLLINFO si = {};

    IMGWINDOW_INFO *infoPtr = IMGWINDOW_GetInfoPtr(hwnd);

    GetClientRect(hwnd, &mRect);
    mWidth = mRect.right - mRect.left + 1;

    si.cbSize = sizeof(si);
    si.fMask = SIF_ALL;
    si.nMin = 0;
    si.nMax = infoPtr->bmpWidth;// - mWidth;// - mWidth;
    si.nPage = mWidth;
    si.nPos = 0;
    infoPtr->xScrollPos = 0;
    SetScrollInfo(hwnd, SB_HORZ, &si, false);

    int mHeight;
    mHeight = mRect.bottom-mRect.top + 1;
    si.cbSize = sizeof(si);
    si.fMask = SIF_ALL;
    si.nMin = 0;
    si.nMax = infoPtr->bmpHeight;// - mHeight;
    si.nPage = mHeight;
    si.nPos = 0;
    infoPtr->yScrollPos = 0;
    SetScrollInfo(hwnd, SB_VERT, &si, true);
}

LRESULT onImgWindowSetImage(HWND hwnd, WPARAM wParam, LPARAM lParam)
{
    IMGWINDOW_INFO *infoPtr = (IMGWINDOW_INFO *)GetWindowLongPtr(hwnd, 0);

    HBITMAP oldImg;
    oldImg = infoPtr->bkg;
    infoPtr->bkg = (HBITMAP) lParam;

    BITMAP bm;
    GetObject(infoPtr->bkg, sizeof(bm), &bm);
    infoPtr->bmpWidth = bm.bmWidth;
    infoPtr->bmpHeight = bm.bmHeight;

    if (infoPtr->bmpWidth > (infoPtr->clientRect.right-infoPtr->clientRect.left) )
        ShowScrollBar(hwnd, SB_HORZ, true);
    else
        ShowScrollBar(hwnd, SB_HORZ, false);

    if (infoPtr->bmpHeight > (infoPtr->clientRect.bottom-infoPtr->clientRect.top) )
        ShowScrollBar(hwnd, SB_VERT, true);
    else
        ShowScrollBar(hwnd, SB_VERT, false);

    imgWindowSetScrollInfo(hwnd);

    SelectObject(infoPtr->memDC, infoPtr->bkg);
    InvalidateRect(infoPtr->self, NULL, true);
    return (LRESULT)oldImg;
}


// window's background fades-in when hovered
LRESULT CALLBACK IMGWINDOW_WindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    IMGWINDOW_INFO *infoPtr = (IMGWINDOW_INFO *)GetWindowLongPtr(hwnd, 0);

    switch (message)                  // handle the messages
    {
        case WM_CREATE:
            return onImgWindowCreate(hwnd, wParam, lParam);

        case WM_DESTROY:
            return onImgWindowDestroy(hwnd);

        // lParam = (HBITMAP) img
        case IM_SETIMAGE:
            return onImgWindowSetImage(hwnd, wParam, lParam);

        case WM_ERASEBKGND:
            return onImgWindowEraseBackground(hwnd, wParam, lParam);

        case WM_PAINT:
            HDC hdc;
            PAINTSTRUCT ps;
            int mWidth, mHeight;
            RECT mRect;
            GetClientRect(hwnd, &mRect);
            mWidth = mRect.right-mRect.left + 1;
            mHeight = mRect.bottom-mRect.top + 1;
            hdc = BeginPaint(hwnd, &ps);

            int xOfs,yOfs;
            xOfs = yOfs = 0;

            if (infoPtr->bmpWidth < mWidth)
                xOfs = (mWidth-infoPtr->bmpWidth)/2;

            if (infoPtr->bmpHeight < mHeight)
                yOfs = (mHeight-infoPtr->bmpHeight)/2;
            infoPtr->xImgOfs = xOfs;
            infoPtr->yImgOfs = yOfs;


            int drawWidth, drawHeight;
            drawWidth = min(infoPtr->bmpWidth, mWidth);
            drawHeight = min(infoPtr->bmpHeight, mHeight);

            int imgXofs, imgYofs;
            imgXofs = infoPtr->xScrollPos;
            imgYofs = infoPtr->yScrollPos;
            BitBlt(
                    hdc,
                    xOfs, yOfs,
                    drawWidth,drawHeight,
                    infoPtr->memDC,
                    imgXofs,imgYofs,
                    SRCCOPY);

            EndPaint(hwnd, &ps);
            return 0;

        case WM_HSCROLL:
            return onImgWindowHscroll(hwnd, wParam, lParam);

        case WM_VSCROLL:
            return onImgWindowVscroll(hwnd, wParam, lParam);

        case WM_SIZE:
            GetClientRect(hwnd, &infoPtr->clientRect);
            InvalidateRect(hwnd, NULL, true);
            return 0;

        case WM_GETDLGCODE:
            return DLGC_STATIC;

        case WM_LBUTTONDOWN:
            infoPtr->mouseDown = true;
            return 0;

        case WM_LBUTTONUP:
            {
                // save the point ready to use in response to the IM_GETCLICKPOS message
                infoPtr->clickX = (short)(LOWORD(lParam) + infoPtr->xScrollPos - infoPtr->xImgOfs);
                infoPtr->clickY = (short)(HIWORD(lParam) + infoPtr->yScrollPos - infoPtr->yImgOfs);

                // tell the paent window that we were clicked with a WM_COMMAND message
                short myNotifIdCode = 0;
                if (infoPtr->mouseDown == true)
                    SendMessage(GetParent(infoPtr->self), WM_COMMAND, MAKEWPARAM(GetDlgCtrlID(infoPtr->self),myNotifIdCode), (LPARAM)infoPtr->self);
            }
            infoPtr->mouseDown = false;
            break;

        case IM_GETCLICKPOS:
            return MAKELPARAM(infoPtr->clickX, infoPtr->clickY);
    }
    return DefWindowProc (hwnd, message, wParam, lParam);
}


BOOL IMGWINDOW_Register(void)
{
    WNDCLASS wndClass;

    ZeroMemory (&wndClass, sizeof(WNDCLASS));
    wndClass.style         = CS_DBLCLKS | CS_VREDRAW | CS_HREDRAW | CS_OWNDC;//CS_GLOBALCLASS | CS_DBLCLKS | CS_SAVEBITS;
    wndClass.lpfnWndProc   = IMGWINDOW_WindowProc;
    wndClass.cbClsExtra    = 0;
    wndClass.cbWndExtra    = sizeof(IMGWINDOW_INFO *);   // accessed via getwindowlong(hWnd, nIndex)
    wndClass.hCursor       = LoadCursor(0, IDC_ARROW);
    wndClass.hbrBackground = 0;
    wndClass.lpszClassName = IMGWINDOW_CLASS;

    if (RegisterClass(&wndClass) != 0)
        return true;
    else
        return false;
}

void IMGWINDOW_Unregister(void)
{
    UnregisterClass(IMGWINDOW_CLASS, NULL);
}
 
Share this answer
 
Comments
Sergey Alexandrovich Kryukov 27-Mar-15 16:35pm    
Well, at least it gives the idea. My 5.
—SA
enhzflep 28-Mar-15 0:03am    
If so, then the aim has been achieved.
Thank-you once again. :)
S.
peterkoller123 29-Mar-15 16:37pm    
Many thanks for your work.
enhzflep 29-Mar-15 21:42pm    
You're most welcome. :)
Thank you for the clarification. Everything is well described here: https://msdn.microsoft.com/en-us/library/windows/desktop/ms645533%28v=vs.85%29.aspx[^].

Samples? It would not be too hard, but who will spend so much time? Who of us would waste so much time for writing it all in pure C. Why? Few simple exercises could be useful for learning, but meticulously baying all those elephants… :-)

How about this: you read the documentation and try to write code, ask separate questions when you face problems?

—SA
 
Share this answer
 
Comments
enhzflep 27-Mar-15 15:17pm    
:laughs:
Me! I had a play for a while, it's really quite quick and easy once one has a suitable skeleton, ready for customization as needed. Oddly enough (or not!?), I find the process quite cathartic.

Nice JS calculator application article you posted the other day too, btw. :)
Sergey Alexandrovich Kryukov 27-Mar-15 16:34pm    
I do understand that. I myself experimented developing pure native windows class library based on pure Win32, and it really was "cathartic", or at least very useful of understanding of Window machinery. But I did not find it prudent to develop it serious, wanted only to publish an article explaining the principles of such libraries, with rudimentary demo. That would really be not very trivial but interesting and useful (I did not write the article, after all). But developing real application, for deployment and production? Come on...

(And please don't hesitate to mention in your comments that you also posted a solution; if you do, I might be interested to look at it; I did not immediately realized that you also answered...)

—SA
enhzflep 28-Mar-15 0:01am    
Indeed. An enjoyable use of time does not necessarily equate to good economic sense. I've also written a bunch of code that wraps many of the standard controls and introduces a few trivial ones, in the aim of writing a font-editor to create fonts for use in embedded systems. While enjoyable to write, it hardly makes sense when there's WxWidgets, Qt etc available, which each make for an instantly cross-platform application. I guess some of us are just masochists! :D
However, writing in such a style as the code I have presented here makes it available to the greatest number of languages and programmes without further modification. I could just as easily leave it in a DLL and use that from ASM, C, Basic, C, C++, Python, etc, etc. This is often a valuable quality in itself. Ease of consumption and ease of writing are often at odds with one-another, I've found.

Gotcha, I'll try to remember in the future to mention the fact that there's another solution which you may care to glance over. :)
peterkoller123 29-Mar-15 16:38pm    
Many thanks for your work, too.
In Visual Basic it is sooooo easy.
In future I will write windows elements in VB and code that must be faster
in C as dll. All infos I get from google.

VB
Public Class MyCalcHelp
    Public allstr As String = ""
    Public tmpstr As String = ""
    Public mytext As String = ""
    Public counter As Integer = 0


    Private Sub MyCalcHelp_KeyUp(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles Me.KeyUp
        If e.KeyValue = Keys.Escape Then
            Application.Exit()
        End If

        If e.KeyValue = Keys.Enter And tmpstr <> "" Then
            allstr = allstr & tmpstr
            'MessageBox.Show(Me.Top.ToString)
            tmpstr = ""

            If counter = 0 Then
                mytext = "question1"
                TextBox1.Text = mytext
                counter += 1
            ElseIf counter = 1 Then
                mytext = "question2"
                TextBox1.Text = mytext
                counter += 1
            ElseIf counter = 2 Then
                mytext = "question3"
                TextBox1.Text = mytext
                counter += 1
            ElseIf counter = 3 Then
                mytext = "question4"
                TextBox1.Text = mytext
                counter += 1
            End If
        End If
    End Sub


    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        For Each argument As String In My.Application.CommandLineArgs
            PictureBox1.ImageLocation = argument
            Exit For
        Next

        Me.WindowState = FormWindowState.Maximized
        Panel1.Width = Me.Width
        Panel1.Top = Me.Top + 12
        Panel1.Height = Me.Height
        Panel1.Left = Me.Left + 12

        TextBox1.Text = "Starting..."

        If counter = 0 Then
            mytext = "question1"
            TextBox1.Text = mytext
            counter += 1
        End If
    End Sub

    Private Sub PictureBox1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles PictureBox1.Click
        Dim ScreenPos As Point = PictureBox1.PointToScreen(New Point(0, 0))
        tmpstr = Control.MousePosition().X.ToString & " " & Control.MousePosition().Y.ToString & vbCrLf
        TextBox1.Text = mytext & "  " & tmpstr
        MessageBox.Show(ScreenPos.X.ToString & "  " & ScreenPos.Y.ToString)
    End Sub

    Private Sub TextBox1_MouseMove(ByVal sender As System.Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles TextBox1.MouseMove
        If TextBox1.Location.Y = 0 Then
            TextBox1.Location = New Point(10, 200)
        Else
            TextBox1.Location = New Point(10, 0)
        End If
    End Sub

End Class
 
Share this answer
 
v2

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900