Geeks With Blogs
Prabhu Kumar a tech twaddler..
In this post we'll learn how to create a windowed directdraw application for windows mobile. A windowed application is not much different from a regular full-screen application but you have to be a little careful because your application has to co-exist with GDI, peacefully. Note that I have used bits of code from Joel's code project article. Here is the code I call from WinMain():

    //Create the DirectDraw object the primary and auxillary surfaces
    if (InitDirectDraw(g_hWnd))
    {
        if (!CreateDirectDrawSurfaces(g_hWnd))
        {
            printf("CreateDirectDrawSurfaces failed!\r\n");
            FreeDirectDrawResources();
            return FALSE;
        }
    }
    else
    {
        printf("InitDirectDraw failed!\r\n");
        FreeDirectDrawResources();
        return FALSE;
    }

    printf("Checkpoint 1: InitDirectDraw and CreateDirectDrawSurfaces succeeded\r\n");

    //Initialize the surfaces with the image
    if (!InitSurfaces())
    {
        printf("InitSurfaces failed!\r\n");
        return FALSE;
    }

We will see what each one of the functions does, one by one.

BOOL InitDirectDraw(HWND hWnd)
{
    HRESULT result;

    result = DirectDrawCreate(NULL, &g_ddraw, NULL);
    if(SUCCEEDED(result))
    {
        g_ddraw->SetCooperativeLevel(hWnd, DDSCL_NORMAL);
        return TRUE;
    }

    printf("DirectDrawCreate failed!\r\n");
    return FALSE;

}

InitDirectDraw() is simple. The only difference here is the co-operative level, we use DDSCL_NORMAL unlike DDSCL_FULLSCREEN that we used before. The CreateDirectDrawSurfaces() function is below:

BOOL CreateDirectDrawSurfaces(HWND hWnd)
{
    DDSURFACEDESC ddsd;
    HRESULT hr;
    RECT rect = {0,};

    ZeroMemory(&ddsd, sizeof(DDSURFACEDESC));

    ddsd.dwSize = sizeof(DDSURFACEDESC);
    ddsd.dwFlags = DDSD_CAPS;
    ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;

    //create the primary surface and set the clipper
    if ( (g_ddraw->CreateSurface(&ddsd, &g_primarySurface, NULL)) == DD_OK)
    {
        if ( (g_ddraw->CreateClipper(0, &g_primaryClipper, NULL)) == DD_OK)
        {
            if ( (g_primaryClipper->SetHWnd(0, hWnd)) != DD_OK)
            {
                printf("SetHWnd on primary clip failed hr=0x%x\r\n", hr);
            }
            if ( (g_primarySurface->SetClipper(g_primaryClipper)) != DD_OK)
            {
                printf("SetClipper failed hr=0x%x\r\n", hr);
            }
        }
        else
        {
            printf("CreateClipper failed hr=0x%x\r\n", hr);
            return FALSE;
        }
    }
    else
    {
        printf("CreateSurface failed hr=0x%x\r\n", hr);
        return FALSE;
    }

    //get the size of the client area (this will be used for backbuffer)
    GetClientRect(hWnd, &rect);

    ZeroMemory(&ddsd, sizeof(DDSURFACEDESC));

    //create the back buffer
    ddsd.dwSize = sizeof(DDSURFACEDESC);
    ddsd.dwFlags = DDSD_WIDTH | DDSD_HEIGHT;
    ddsd.dwWidth = rect.right - rect.left;
    ddsd.dwHeight = rect.bottom - rect.top;

    printf("screen width:%d,  height:%d\r\n", ddsd.dwWidth, ddsd.dwHeight);

    if ( (g_ddraw->CreateSurface(&ddsd, &g_backBuffer, NULL)) != DD_OK)
    {
        printf("CreateSurface on back buffer failed hr=0x%x\r\n", hr);
        return FALSE;
    }

    ZeroMemory(&ddsd, sizeof(DDSURFACEDESC));

    //create a surface to hold the marbel's image
    ddsd.dwSize = sizeof(DDSURFACEDESC);
    ddsd.dwFlags = DDSD_WIDTH | DDSD_HEIGHT;
    ddsd.dwWidth = 48; //BMP's width
    ddsd.dwHeight = 48; //BMP's height

    if ( (hr = g_ddraw->CreateSurface(&ddsd, &g_marbleSurface, NULL)) != DD_OK)
    {
        printf("CreateSurface marble surface failed hr=0x%x\r\n", hr);
        return FALSE;
    }

    return TRUE;
}

We create three surfaces here, a primary surface, a back buffer and an additional surface to hold our marble's image. Another thing you'll note is the presence of a DirectDraw Clipper object. The clipper object is used to manage clip lists. Basically, it helps to contain the drawings of our application within the client area of the window. We create the clipper using CreateClipper() method on the directdraw object. Next, we call SetHWnd() on the clipper object and pass in the handle to our main window. The clipper object will use this handle to find out the client area of the window and determine what part to clip. We set the clipper on the primary surface by using SetClipper() method of the directdraw surface object.

We create the back buffer next. You'll see that we use the client area co-ordinates of the main window to decide the width and height of the back buffer. I did not do this initially and used 240 for width and 320 for height. This led to a problem, the image on the screen appeared skewed. The problem was that while Blt'ing, the destination rect on the primary surface was the size of the client area of the main window, which is a little smaller than 240x320 to make way for the task bar and the menu bar. When the back buffer, whose size is 240x320, was Blt onto a smaller surface the image was shrunk to fit. So the image appeared, well, shrunk.

BOOL InitSurfaces()
{
    HBITMAP hbm;

    //load the bitmap resource
    hbm = DDGetBitmapHandle(g_hInst, szMarbleBitmap);

    if (hbm == NULL)
    {
        printf("DDGetBitmapHandle failed\r\n");
        return FALSE;
    }

    DDCopyBitmap(g_marbleSurface, hbm, 0, 0, 48, 48);

    DeleteObject(hbm);

    return TRUE;
}

InitSurfaces() initializes the marble surface with the marble's image. The DD*() function you see above are from ddutil.cpp which come with the SDK samples. Make sure that the null check "if (hbm == NULL)" isn't written as "if(hbm = NULL)", that'll save you a good hour of debugging (;


Next up are UpdateFrame() and DisplayFrame() functions. UpdateFrame() updates the back buffer by Blt()'ing the marble surface on it and DisplayFrame() displays the drawing by Blt()'ing the back buffer onto the primary surface.

void DisplayFrame()
{
    HRESULT hr;
    POINT p;
    RECT destRect;

    p.x = p.y = 0;

    ClientToScreen(g_hWnd, &p);
    GetClientRect(g_hWnd, &destRect);

    OffsetRect(&destRect, p.x, p.y);

    //blt the back buffer on the primary surface
    while (TRUE)
    {
        hr = g_primarySurface->Blt(&destRect, g_backBuffer, NULL, 0, NULL);

        if (hr == DD_OK)
            break;

        if (hr != DDERR_WASSTILLDRAWING)
            break;
    }
}


Again, we use ClientToScreen(), GetClientRect() and OffsetRect() functions to determine the position of the destination rectangle on the primary surface.

Another important thing is to prevent your application from drawing when it's not supposed to, for e.g. when it doesn't have the focus. So we need to maintain a state variable to determine whether we have focus or not and draw only when we our application has the focus. The following code is under the WndProc() function:

        case WM_ACTIVATE:
            SHHandleWMActivate(hWnd, wParam, lParam, &s_sai, FALSE);
            switch(LOWORD(wParam))
            {
                case WA_ACTIVE:
                case WA_CLICKACTIVE:
                    g_bHasFocus = TRUE;   
                    break;

                case WA_INACTIVE:
                    g_bHasFocus = FALSE;
                    break;

                default:
                    break;
            }
            break;


Pretty straight forward. Also,

        case WM_CANCELMODE:
            g_bHasFocus = FALSE;
            DisplayTextOnScreen(TEXT("Tap here to continue.."));
            //DisplayFrame();
            break;

        case WM_ENTERMENULOOP:
            g_bHasFocus = !(BOOL)wParam;
            break;

        case WM_EXITMENULOOP:
            g_bHasFocus = TRUE;
            break;

 
WM_ENTERMENULOOP and WM_EXITMENULOOP are called whenever user clicks on a menu or leaves a menu. In case, where the Start menu was pressed, my window did not receive these messages, instead it was sent a WM_CANCELMODE message. WM_CANCELMODE is sent whenever a dialog box or a message box is displayed. But when I click on the Start menu again to close the popup, no message was sent to the main window. So I decided to display a small text on the screen which says "Tap here to continue.." and it works pretty well. But there is one problem however, which I'll come to in a moment. I use DisplayTextOnScreen() to draw text directly on the primary surface,

void DisplayTextOnScreen(TCHAR *tszStr)
{
    HDC hdc;
    RECT rc = {0,}, destRect = {0,}, srcRect = {0,};
    int nMsg;
    SIZE size = {0,};
    HRESULT hr;

    if (g_primarySurface->GetDC(&hdc) == DD_OK)
    {
        SetBkColor(hdc, RGB(115, 115, 115));
        SetTextColor(hdc, RGB(255, 255, 255));
        GetClientRect(g_hWnd, &rc);

        nMsg = lstrlen(tszStr);
        GetTextExtentPoint(hdc, tszStr, nMsg, &size);

        ExtTextOut(hdc,
            (rc.right - size.cx)/2 - 25,
            rc.bottom - size.cy*2,
            //(rc.bottom - size.cy)/2,// - (rc.bottom - size.cy)/4,
            ETO_OPAQUE,
            NULL,
            tszStr,
            nMsg,
            NULL);


        g_primarySurface->ReleaseDC(hdc);
    }
}

And of course, you also have to handle WM_LBUTTONDOWN or WM_LBUTTONUP events to find out if the user clicked on the client area and then change the state variable so the marble can continue to move again.

There you go.

All the while writing this post I was taking regular backups and the thing never crashed. One of the reasons that makes me love those Murphy's laws (:

And as always, here are a few screenshots,


         




          

You can tell which screen is the problem, right? Do you need a clue? Well, the problem is that when I click on Start and then on Menu Screen 4 happens. And vice-versa too. I am yet to figure out how to solve this, I do however have a slight idea of why it happens. Feel free to help me out.


Update: The problem mentioned above (Screen 4) is solved. Look here.
Posted on Friday, September 25, 2009 11:40 AM Applications | Back to top


Comments on this post: Applications: Creating a windowed DirectDraw application

# The Painting Problem
Requesting Gravatar...
LOL, since you derived from my code you also derived from a problem in my code. I'll have to ask you to pardon me as the code of mine that you looked at was the first DirectDraw program that I've ever written.

I'm not looking at my code but chances are that I didn't do anything in the WM_PAINT event. I believe that what is happening is when you click on the start menu the main application windows looses focus. To prevent the program from killing the battery in the background it is set to stop using it's usual processing loop. Instead of repainting the screen all the time like it does when it has focus it is making the blocking call to wait for a windows message. When you open a menu that is covering part of the screen which should be repainted when the menu is closed. But since I did nothing in the WM_PAINT event it's not getting repainted.

To correct this in response to the WM_PAINT message you should re-render your scene.
Left by Joel Johnson on Sep 30, 2009 7:35 PM

# re: Applications: Creating a windowed DirectDraw application
Requesting Gravatar...
Thanks Joel, I don't know how this missed me (;

Look here for the update:

http://geekswithblogs.net/TechTwaddle/archive/2009/10/01/applications-painting-problems-with-windowed-directdraw-app.aspx
Left by Prabhu Kumar on Oct 01, 2009 1:21 PM

Your comment:
 (will show your gravatar)


Copyright © TechTwaddle | Powered by: GeeksWithBlogs.net