Jump to content
  • Advertisement
Sign in to follow this  
Nanven

Using device contexts in Win32 API

This topic is 4693 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I'm still kind of new to this but I had an odd problem crop up. Basically, I have an application that is double buffered and I load a few bitmaps as such:
diceImages[0] = LoadImage(NULL, "dice1.bmp", IMAGE_BITMAP, NULL, NULL, LR_LOADFROMFILE);
diceImages[1] = LoadImage(NULL, "dice2.bmp", IMAGE_BITMAP, NULL, NULL, LR_LOADFROMFILE);
diceImages[2] = LoadImage(NULL, "dice3.bmp", IMAGE_BITMAP, NULL, NULL, LR_LOADFROMFILE);
diceImages[3] = LoadImage(NULL, "dice4.bmp", IMAGE_BITMAP, NULL, NULL, LR_LOADFROMFILE);
diceImages[4] = LoadImage(NULL, "dice5.bmp", IMAGE_BITMAP, NULL, NULL, LR_LOADFROMFILE);
diceImages[5] = LoadImage(NULL, "dice6.bmp", IMAGE_BITMAP, NULL, NULL, LR_LOADFROMFILE);
diceImages[6] = LoadImage(NULL, "dice1locked.bmp", IMAGE_BITMAP, NULL, NULL, LR_LOADFROMFILE);
diceImages[7] = LoadImage(NULL, "dice2locked.bmp", IMAGE_BITMAP, NULL, NULL, LR_LOADFROMFILE);
diceImages[8] = LoadImage(NULL, "dice3locked.bmp", IMAGE_BITMAP, NULL, NULL, LR_LOADFROMFILE);
diceImages[9] = LoadImage(NULL, "dice4locked.bmp", IMAGE_BITMAP, NULL, NULL, LR_LOADFROMFILE);
diceImages[10] = LoadImage(NULL, "dice5locked.bmp", IMAGE_BITMAP, NULL, NULL, LR_LOADFROMFILE);
diceImages[11] = LoadImage(NULL, "dice6locked.bmp", IMAGE_BITMAP, NULL, NULL, LR_LOADFROMFILE);




The images load fine. The first six have a white background and are black and white BMP's only. The last six are colored bitmaps with a semi-green background. The idea is when a user clicks on the dice it "locks" it so when they roll the dice that one won't be rolled and as such the background color changes to indicate such. I had this working with global variables (with color) and when I started changing it more towards an object oriented approach(an app class) to avoid global variables, I didn't really change any of the underlying logic, just the scope of the variables. However, now the colored dice are entirely black. When it BitBlt's as such:
SelectObject(m_hImageDC, diceImages[imageIndex]);
BitBlt(*m_hCurrentDC, diceDrawRect.left,  diceDrawRect.top, 50, 50, m_hImageDC, 0, 0, SRCCOPY);

It does the first six fine(assume because the background is white) but for the colored background(green) it's changing it to black for some reason. So I was wondering if there was anything with the DC that would make all color not white or black to one of those because I really didn't change anything and this is puzzling me. Thanks for any help. [Edited by - Nanven on December 29, 2005 7:55:37 PM]

Share this post


Link to post
Share on other sites
Advertisement
I am guessing the bitmap selected into *m_hCurrentDC is either not a compatible bitmap or it was created compatible with the wrong DC. i.e. somewhere you should have something like:

*m_hCurrentDC = CreateCompatibleDC(NULL /*or possibly another dc*/);
HBITMAP hBitmap = CreateCompatibleBitmap(NULL /*or the dc above*/, x, y);

In particular, the dc passed to CreateCompatibleBitmap should *not* be *m_hCurrentDC.

btw, *m_hCurrentDC looks really wierd IMHO. If you're going to use hungarianisms m_phCurrentDC is probably a better name.

Share this post


Link to post
Share on other sites
Well, I was in the process of changing everything over from global variables to a basic app class so some of the names aren't exactly completely standardized yet.

And aye.. the image DC is created like:

m_hImageDC = CreateCompatibleDC(NULL);

Like I said, the funny thing is, this was WORKING when they were global variables in WinMain. The only thing that should of changed should of been the variables scope. And it *almost* is still working, if it weren't for the fact the DC seems to be on a white/black color scale and not drawing the colors of the bitmap. (Testable by using colors to make a design, when the bitmap is drawn, the shapes are drawn, but in black, but it was doing color before when it was global... really weird)

m_hCurrentDC is just my way of coding a basic app that can be either double buffered or not but regardless of that you can still use it's drawing functions. Also did that so I could destroy the buffer after it's been created during runtime and the code would still work, it would probably just start flashing after that ;) So it points to the default HDC or the double buffered one. I'll post some more code if needed (or perhaps i'll just zip the files so it will be more clear) as I feel i'm not explaining the problem that well.

Share this post


Link to post
Share on other sites
Well, i've been able to narrow it down to the fact it's something to do with how my application is double buffered. Here's what it looks like without the double buffer:

Colored dice

With:

Black/white dice

As you can see, the non-colored dice draw just fine, it's just the dice with color fail to work. And it only draws it completely black when I have a double buffer up and running. Here's some code real quick(from different files):


class BasicApp {
protected:
std::string m_appName;
int m_height;
int m_width;
HINSTANCE m_hInstance;
HWND m_hWnd;
int m_menuID;
HDC m_hDC;
HDC m_hBackDC;
HDC *m_phCurrentDC;
HBITMAP m_hBitmap;
PAINTSTRUCT m_ps;
std::vector<HWND> m_windowObjects;
public:
BasicApp(std::string n, HINSTANCE hi, int w, int h, int menuID);
~BasicApp();

std::string GetName() { return m_appName; }

bool Initialize();
bool AddObject(std::string clName, std::string obName, int id,
int x, int y, int w, int h, int style, int flags);
void CreateBackBuffer();
void DestroyBackBuffer();
void Run();
virtual void Idle();
void ClearDC();
virtual LRESULT AppWndProc(UINT msg, WPARAM wParam, LPARAM lParam);
};

bool BasicApp::Initialize() {
WNDCLASSEX wc;

// Define the windows class
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = m_hInstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wc.lpszMenuName = MAKEINTRESOURCE(m_menuID);
wc.lpszClassName = m_appName.c_str();

// Register it
if(!RegisterClassEx(&wc))
return false;

// Create the main application window
m_hWnd = CreateWindowEx(NULL, m_appName.c_str(), m_appName.c_str(), WS_CAPTION | WS_SYSMENU,
CW_USEDEFAULT, CW_USEDEFAULT,
m_width, m_height,
NULL, NULL, m_hInstance, (LPVOID)this);

// If the window didn't get created, that's a bad thing
if(!m_hWnd)
return false;

// Get a device context for drawing
m_hDC = GetDC(m_hWnd);
m_phCurrentDC = &m_hDC;

return true;
}

void BasicApp::CreateBackBuffer() {
DestroyBackBuffer(); // Just incase
m_hBackDC = CreateCompatibleDC(m_hDC);
m_hBitmap = CreateCompatibleBitmap(m_hBackDC, m_width, m_height);
SelectObject(m_hBackDC, m_hBitmap);
m_phCurrentDC = &m_hBackDC;
BitBlt(*m_phCurrentDC, 0, 0, m_width, m_height, NULL, 0, 0, WHITENESS);
}

void BasicApp::DestroyBackBuffer() {
if(m_hBitmap) {
DeleteObject(m_hBitmap);
m_hBitmap = NULL;
}

if(m_hBackDC) {
DeleteDC(m_hBackDC);
m_hBackDC = NULL;
}

m_phCurrentDC = &m_hDC;
}

void BasicApp::ClearDC() {
BitBlt(*m_phCurrentDC, 0, 0, m_width, m_height, NULL, 0, 0, WHITENESS);
}




That handles all the BasicApp drawing components. Here's the Yahtzee app:


class YahtzeeGame : public BasicApp {
private:
HANDLE m_diceImages[NUMIMAGES*2];
RECT m_diceDrawRect[NUMIMAGES];
Player m_players[4];
Player *m_pCurrentPlayer;
int m_playerIndex;
HDC m_hImageDC;
public:
YahtzeeGame(std::string n, HINSTANCE hi, int w, int h, int menuID):BasicApp(n,hi,w,h,menuID){GameInit();}
~YahtzeeGame(){GameShutdown();}

bool GameInit();
void GameShutdown();
void GameLoop();
void Idle();
void Render();
LRESULT AppWndProc(UINT msg, WPARAM wParam, LPARAM lParam);
};

bool YahtzeeGame::GameInit() {
// Seed the random number generator
srand((unsigned)time(NULL));

// Load the bitmaps for unlocked dice
m_diceImages[0] = LoadImage(NULL, "dice1.bmp", IMAGE_BITMAP, NULL, NULL, LR_LOADFROMFILE);
m_diceImages[1] = LoadImage(NULL, "dice2.bmp", IMAGE_BITMAP, NULL, NULL, LR_LOADFROMFILE);
m_diceImages[2] = LoadImage(NULL, "dice3.bmp", IMAGE_BITMAP, NULL, NULL, LR_LOADFROMFILE);
m_diceImages[3] = LoadImage(NULL, "dice4.bmp", IMAGE_BITMAP, NULL, NULL, LR_LOADFROMFILE);
m_diceImages[4] = LoadImage(NULL, "dice5.bmp", IMAGE_BITMAP, NULL, NULL, LR_LOADFROMFILE);
m_diceImages[5] = LoadImage(NULL, "dice6.bmp", IMAGE_BITMAP, NULL, NULL, LR_LOADFROMFILE);

// Load the bitmaps for locked dice
m_diceImages[6] = LoadImage(NULL, "dice1locked.bmp", IMAGE_BITMAP, NULL, NULL, LR_LOADFROMFILE);
m_diceImages[7] = LoadImage(NULL, "dice2locked.bmp", IMAGE_BITMAP, NULL, NULL, LR_LOADFROMFILE);
m_diceImages[8] = LoadImage(NULL, "dice3locked.bmp", IMAGE_BITMAP, NULL, NULL, LR_LOADFROMFILE);
m_diceImages[9] = LoadImage(NULL, "dice4locked.bmp", IMAGE_BITMAP, NULL, NULL, LR_LOADFROMFILE);
m_diceImages[10] = LoadImage(NULL, "dice5locked.bmp", IMAGE_BITMAP, NULL, NULL, LR_LOADFROMFILE);
m_diceImages[11] = LoadImage(NULL, "dice6locked.bmp", IMAGE_BITMAP, NULL, NULL, LR_LOADFROMFILE);

// Initialize the draw rects
for(int i = 0; i < NUMIMAGES; i++) {
m_diceDrawRect.left = (50 * i + (20 * i));
m_diceDrawRect.top = 20;
m_diceDrawRect.right = (50 * (i + 1) + (20 * i));
m_diceDrawRect.bottom = 70;
}

// Give player 1 control at the start of the game
m_playerIndex = 0;
m_pCurrentPlayer = &m_players[m_playerIndex];

m_hImageDC = CreateCompatibleDC(NULL);

AddObject("BUTTON", "Roll", 50001, 300, 100, 80, 20, WS_EX_STATICEDGE, WS_CHILD | WS_VISIBLE);

return true;
}

void YahtzeeGame::Render() {
ClearDC();
int imageIndex = 0;

// Draw the players name
TextOut(*m_phCurrentDC, 0, 0, m_pCurrentPlayer->getName().c_str(), (int)m_pCurrentPlayer->getName().length());

// Draw the current players dice if he's taken atleast 1 turn(rolled the dice)
if(m_pCurrentPlayer->getTurn() > 0) {
for(int i = 0; i < NUMDICE; i++) {
if(!m_pCurrentPlayer->getDice(i)->locked())
imageIndex = (m_pCurrentPlayer->getDiceValue(i) - 1);
else
imageIndex = (m_pCurrentPlayer->getDiceValue(i) + 5);

SelectObject(m_hImageDC, m_diceImages[imageIndex]);
BitBlt(*m_phCurrentDC, m_diceDrawRect.left, m_diceDrawRect.top,
50, 50, m_hImageDC, 0, 0, SRCCOPY);
}
} else {
TextOut(*m_phCurrentDC, 30, 30, "Press the roll button to roll the dice", 38);
}
}

// and just throwing my WMPAINT message in here just incase it matters
case WM_PAINT:
{
m_hDC = BeginPaint(m_hWnd, &m_ps); // Validate

Render();
if(m_hBackDC)
BitBlt(m_hDC, 0, 0, m_width, m_height, m_hBackDC, 0, 0, SRCCOPY);

EndPaint(m_hWnd, &m_ps); // Release the DC

return TRUE;
}




So is there anything wrong with that code that would cause colored bitmaps to be drawn in black because of how I handled the double buffering(since they only draw black with the double buffer).

Share this post


Link to post
Share on other sites
I'm not totally following your code so I'll just leave some comments:

  • When working with Win32 API functions, it is always a good advice to check the return value for errors, your Render () method should return a bool value to indicate the success/failure of the performed operation. Or you may assert the returned code.
  • Since you have two determined HDCs, it is probably not necessary to have m_phCurrentDC. You would draw to the back buffer then copy the result to the front buffer, m_phCurrentDC would be an advantage in swap mode, but this is not the case.
  • You don't need to SelectObject () each rendering call, perhaps it is the main problem as Microsoft says:
    Quote:
    This function returns the previously selected object of the specified type. An application should always replace a new object with the original, default object after it has finished drawing with the new object.

    An application cannot select a bitmap into more than one DC at a time.

    Share this post


    Link to post
    Share on other sites
    Well, I have five different dice images. If I select the "one" dice image into the DC, sure, I wouldn't have to reselect that everytime, but what happens when the next dice I need to draw is something other than one? I need to put that into the DC instead. Unless I create 10 different image DC's? Would you suggest that?

    I was also trying to set up this application to be loose. I do kind of want it so it has the option to be double buffered, not autmatically set up that way. As such, if there is no back buffer, my code still works.. which is what I was going for and allowed for easy testing to see if the dice worked without the double buffer and it did.

    As far as Render() being bool, I agree. I threw this thing together and realized after the fact I should probably try to get some coding standards, etc, in. But I didn't want to recode everything, so a lot of the code isn't completely revised yet, but I am trying to figure out this problem before going any farther.

    Well, anyway, thanks for your comments.. and this thing is still puzzling me! Heh.

    As a side note, because I am so puzzled by this, here's the complete code, solution, projects and everything I have so far: Code.

    Share this post


    Link to post
    Share on other sites
    It is quite surprising that the problem doesn't lay on your code but your data. You've selected incompatible bitmaps between DCs. Your unlocked dices are in monochrome while your locked dices are in 24-bit. Actually I didn't think of it until I swapped the LoadBitmap () functions a bit. All that's been done, good luck.

    Share this post


    Link to post
    Share on other sites
    Sign in to follow this  

    • Advertisement
    ×

    Important Information

    By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

    We are the game development community.

    Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

    Sign me up!