Keeping elements on top using winapi - c

I've written a little app in winapi which draws animated background like so:
case WM_PAINT:
SelectObject(canvas, images[frame]);
StretchBlt(hdc, 0, 0, 640, 320, canvas, 0, 0, 64, 32, SRCCOPY);
break;
case WM_TIMER:
SelectObject(canvas, images[frame]);
StretchBlt(hdc, 0, 0, 640, 320, canvas, 0, 0, 64, 32, SRCCOPY);
frame += 1;
if (frame == sizeof(images)/sizeof(images[0]))
frame = 0;
break;
It works flawlessly but generates some problems when it comes to drawing other elements. I got some labels in the same window.
Right after first frame is drawn in WM_PAINT labels are still visible. When I replace canvas' (HDC canvas = CreateCompatibleDC(hdc)) image with second frame (in WM_TIMER), labels suddenly disappear - I guess they stay under that new layer that is drawn.
Is there any way to keep some elements always on top or bring them to top right after I redraw the bitmap?

Related

Simple Stretched Raylib Fullscreen Option?

The code for a simple Raylib program is listed below (based on the Raylib example shapes_logo_raylib). Running the program shows a version of the Raylib logo: a black square outline which fills about a third of the (800x450) window.
It's not hard to make a fullscreen version of the program, with calls such as GetCurrentMonitor(), SetWindowSize(), GetMonitorWidth(), GetMonitorHeight()
SetConfigFlags(FLAG_WINDOW_RESIZABLE), or ToggleFullscreen(). But then, while the black square remains a similar size as before, it occupies (top left) a much smaller proportion of the larger (fullscreen) window. Is there an option to display a larger "stretched" version of the original windowed image on the fullscreen window?
#include "raylib.h"
int main(void)
{
InitWindow(800, 450, "raylib [shapes] example - raylib logo using shapes");
while (!WindowShouldClose())
{
BeginDrawing()
ClearBackground(RAYWHITE);
DrawRectangle(screenWidth/2 - 128, screenHeight/2 - 128, 256, 256, BLACK);
DrawRectangle(screenWidth/2 - 112, screenHeight/2 - 112, 224, 224, RAYWHITE);
DrawText("raylib", screenWidth/2 - 44, screenHeight/2 + 48, 50, BLACK);
EndDrawing();
}
CloseWindow();
return 0;
}
I made the following additions to your code to make it work on any window size. It draws the stuff you want to draw onto a RenderTexture2D and then draws said texture onto the screen. I've only tested it with resizable windows, but it should work in any window mode, including exclusive fullscreen.
In short:
Request render texture using LoadRenderTexture(int, int)
Use BeginTextureMode(RenderTexture2D) and EndTextureMode() to draw onto the texture
Draw the texture using DrawTexturePro(Texture2D, Rectangle, Rectangle, Vector2, float, Color), the first rectangle is the size of the texture, the second the size of the screen. If it looks mirrored, invert the height of the input texture.
Unload the render texture when done.
I added comments to all my additions/changes to highlight what needs to be changed.
#include "raylib.h"
// This was just missing from your code, but I wanted to show a compilable version of the code
int screenWidth = 800;
int screenHeight = 450;
int main(void)
{
InitWindow(800, 450, "raylib [shapes] example - raylib logo using shapes");
// This should use the flag FLAG_FULLSCREEN_MODE which results in a possible ToggleFullscreen() call later on
SetWindowState(FLAG_WINDOW_RESIZABLE);
// Request a texture to render to. The size is the screen size of the raylib example.
RenderTexture2D renderTexture = LoadRenderTexture(screenWidth, screenHeight);
while (!WindowShouldClose())
{
// Instead of using BeginDrawing() we render to the render texture. Everything else stays unchanged
BeginTextureMode(renderTexture);
ClearBackground(RAYWHITE);
DrawRectangle(screenWidth / 2 - 128, screenHeight / 2 - 128, 256, 256, BLACK);
DrawRectangle(screenWidth / 2 - 112, screenHeight / 2 - 112, 224, 224, RAYWHITE);
DrawText("raylib", screenWidth / 2 - 44, screenHeight / 2 + 48, 50, BLACK);
// We need to end the texture mode separately
EndTextureMode();
// Let's draw the texture. The source rect is the size of the texture, the destination rect is of the same size as the screen. For some reason, the texture was flipped vertically, so I had to invert the source rects "height" to flip the UV.
BeginDrawing();
DrawTexturePro(
renderTexture.texture,
Rectangle{ 0, 0, static_cast<float>(renderTexture.texture.width), static_cast<float>(-renderTexture.texture.height) },
Rectangle{ 0, 0, static_cast<float>(GetScreenWidth()), static_cast<float>(GetScreenHeight()) },
Vector2{ 0, 0 },
0,
WHITE);
EndDrawing();
}
// Unload the texture handle again to make a clean exit.
UnloadRenderTexture(renderTexture);
CloseWindow();
return 0;
}
I hope this answers your question.

Blank screen after rendering text

I have opened a window which shows two rects on the screen then using SDL_TTF to show the mouse position on the screen.
The bit I am having hard time understanding is why after rendering text the the two rects before it do not show up.
I am using SDL_RenderFillRect to draw two rects on screen
SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
SDL_RenderFillRect(renderer, rect1);
SDL_SetRenderDrawColor(renderer, 0, 0, 255, 255);
SDL_RenderFillRect(renderer, rect2);
Code for rendering the text is
// define string with mouse x, y coords
sprintf(someString, "x: %d, y: %d", mouse.x, mouse.y);
SDL_Point textPos = {10, 10};
WriteText(renderer, font, someString, textPos, (SDL_Color){255, 255, 255, 255});
SDL_Surface *fontSurface = TTF_RenderText_Blended(font, someString, COLOR_BLACK); // create font surface
SDL_Texture *fontTexture = SDL_CreateTextureFromSurface(renderer, fontSurface); // create the texture
// get clip width and height from fontsurface clip rect
SDL_Rect *fontRect = &fontSurface->clip_rect;
fontRect->x = pos.x;
fontRect->y = pos.y;
SDL_RenderCopy(renderer, fontTexture, NULL, fontRect); // copy text to the renderer
// delete surface and texture
SDL_FreeSurface(fontSurface);
SDL_DestroyTexture(fontTexture);
It to shows the mouse positon top left corner of the window. However this makes the rest of the window blank.
To prevent this my work around is having to draw something on the screen after calling SDL_RendererCopy (and weirdly before calling SDL_DestroyTexture too) For example drawing single point
...
SDL_RenderCopy(renderer, fontTexture, NULL, fontRect); // copy text to the renderer
// why is this needed??
SDL_RenderDrawPoint(renderer, 0, 0);
// delete surface and texture
SDL_FreeSurface(fontSurface);
SDL_DestroyTexture(fontTexture); // have to draw a point before this
...
This then shows the two rects rendered before the text
If I set dstRect to NULL when calling SDL_RenderCopy then the text spans the whole window but I can see what was rendered before underneath the text.
Why am I having to draw a point after calling SDL_RenderCopy to stop what was rendered before from not showing up?
NOTE: Link to full source code https://pastebin.com/tRSFT0PV
This is a bug in SDL 2.0.10. It's fixed by https://hg.libsdl.org/SDL/rev/6ee12b88beed and this fix will ship with 2.0.11. Sorry about that!

Draw a line next to word in WinAPI

How do I draw a line like this that is right next to a word like "Counts", in WinAPI with C?
Using Dialog Resources
Create a static text control with no text that is 1 or 2 pixels in height, turn on the border (WS_BORDER), and set its style to Static Edge (WS_EX_STATICEDGE). Then create a static text control with the word "Counts" in it on top of that. Then use CreateDialog() or DialogBox() to show the dialog box.
IDD_DIALOG1 DIALOGEX 0, 0, 172, 63
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Dialog"
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
LTEXT "",IDC_STATIC,6,12,156,1,WS_BORDER,WS_EX_STATICEDGE
LTEXT "Counts ",IDC_STATIC,6,8,26,8
END
Note: This is verbatim what Visual Studio generated using the dialog designer.
Creating Static Controls Using CreateWindow() (as suggested by Jonathan Potter)
LRESULT OnCreate( HWND hWnd, LPCREATESTRUCT lpCreateStruct )
{
// Get default gui font
NONCLIENTMETRICS metrics;
metrics.cbSize = sizeof(NONCLIENTMETRICS);
SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &metrics, NULL);
HFONT hFont = CreateFontIndirect(&metrics.lfMessageFont);
// Create the line
CreateWindowEx(WS_EX_STATICEDGE, _T("STATIC"), NULL, WS_CHILD|WS_VISIBLE|WS_BORDER,
10, 17, 280, 1, hWnd, NULL, lpCreateStruct->hInstance, NULL);
// Create the Counts label
HWND hwndCounts = CreateWindow(_T("STATIC"), _T("Counts "), WS_CHILD|WS_VISIBLE,
10, 10, 50, 26, hWnd, NULL, lpCreateStruct->hInstance, NULL);
// Apply the default gui font
SendMessage(hwndCounts, WM_SETFONT, (WPARAM)hFont, TRUE);
// Cleanup the font object
DeleteObject(hFont);
}
Drawing manually on the WM_PAINT event
void OnPaint( HWND hWnd )
{
// Get the default font
NONCLIENTMETRICS metrics;
metrics.cbSize = sizeof(NONCLIENTMETRICS);
SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &metrics, NULL);
HFONT hFont = CreateFontIndirect(&metrics.lfMessageFont);
// Setup HDC
RECT rect;
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
// Select the default font
SelectObject(hdc, hFont);
// Draw the line using the button shadow
SelectObject(hdc, GetStockObject(DC_PEN));
SetDCPenColor(hdc, GetSysColor(COLOR_BTNSHADOW));
MoveToEx(hdc, 10, 17, NULL);
LineTo(hdc, 280, 17);
// Draw the word Counts overtop of the line
SetRect(&rect, 10, 10, 280, 22);
SetTextColor(hdc, GetSysColor(COLOR_BTNTEXT));
SetBkColor(hdc, GetSysColor(COLOR_BTNFACE));
DrawText(hdc, TEXT("Counts "), -1, &rect, DT_NOCLIP);
// Cleanup the font object
DeleteObject(hFont);
// Quit painting
EndPaint(hWnd, &ps);
}
Note: Something I did not account for in this example is the height of the default font. You will want to adjust the code for that.
Here is a screenshot of the output of this method.
In your example, it looked like a single one pixel line, so that's what I drew, but if you'd like to make the line look more like a 'Fixed 3D' or 'lowered bevel line' (which is what the group box tends to draw for it's border line), then you can draw another line below it with the button highlight color.
SetDCPenColor(hdc, GetSysColor(COLOR_BTNHIGHLIGHT));
MoveToEx(hdc, 10, 18, NULL);
LineTo(hdc, 280, 18);
As pointed out by Ben Voigt, it might be better to do this with DrawEdge though.
RECT line;
SetRect(&line, 10, 17, 280,17);
DrawEdge(hdc, &line, EDGE_ETCHED, BF_TOP );
Creating a Group Box Control (suggested by Hans Passant)
Hans Passant's suggestion of doing this with a Group Box did work when I tested it. It still drew a rectangle, and when you enabled visual styles it was very difficult to see. Nevertheless, this should get you started if you want to give it a go.
HWND hwndGroup = CreateWindow(_T("Button"), _T("Counts "),
WS_CHILD|WS_VISIBLE|BS_GROUPBOX, 10, 10, 280, 2, hWnd, NULL,
lpCreateStruct->hInstance, NULL);
SendMessage(hwndGroup, WM_SETFONT, (WPARAM)hFont, TRUE);
Additional Note
Something else I would like to suggest is that you use can use Spy++ which comes with Visual Studio to analyze the window you are looking at. This will tell you at the very least if it's a child control, or whether they are painting it manually. If it's a child control you will also be able to see the rectangle and styles that are applied to it, as well lots of additional information.

Is it possible to create an XOR pen like DrawFocusRect()?

The Win32 GDI DrawFocusRect(HDC, const RECT*) function draws the dotted outline of a rectangle on the desired devince context. The cool thing about this function is it draws the dots using an XOR function so that when you call it a second time on the same device context and rectangle, it erases itself:
RECT rc = { 0, 0, 100, 100 };
DrawFocusRect(hdc, &rc); // draw rectangle
DrawFocusRect(hdc, &rc); // erase the rectangle we just drew
I want to achieve the same dotted line effect as DrawFocusRect() but I just want a line, not a whole rectangle. I tried doing this by passing a RECT of height 1 to DrawFocusRect() but this doesn't work because it XORs the "bottom line" of the rectange on top of the top line so nothing gets painted.
Can I create a plain HPEN that achieves the same effect as DrawFocusRect() so I can draw just a single line?
As #IInspectable commented, you want to use SetROP2(). The other half of the battle is creating the correct pen. Here is how the whole thing shakes out:
HPEN create_focus_pen()
{
LONG width(1);
SystemParametersInfo(SPI_GETFOCUSBORDERHEIGHT, 0, &width, 0);
LOGBRUSH lb = { }; // initialize to zero
lb.lbColor = 0xffffff; // white
lb.lbStyle = BS_SOLID;
return ExtCreatePen(PS.GEOMETRIC | PS.DOT, width, &lb, 0, 0);
}
void draw_focus_line(HDC hdc, HPEN hpen, POINT from, POINT to)
{
HPEN old_pen = SelectObject(hdc, hpen);
int old_rop = SetROP2(R2_XORPEN);
MoveToEx(hdc, from.x, from.y, nullptr);
LineTo(hdc, to.x, to.y);
SelectObject(hdc, old_pen);
SetROP2(old_rop);
}

Color of the lines are not changing?

I want to draw a white line in my window:
case WM_PAINT:
{
hdc=GetDC(hWnd);
SelectObject(hdc, GetStockObject(WHITE_BRUSH));
MoveToEx(hdc, 0, 0, 0);
LineTo(hdc, 100, 100);
ReleaseDC(hWnd, hdc);
}
but the color is still black. What's Wrong?
You are trying to set a brush for your line when you should be using a pen. A brush is used to fill the interior of a shape while a pen is used to draw the lines.
MSDN says this about pens:
A pen is a graphics tool that an application can use to draw lines and
curves. Drawing applications use pens to draw freehand lines, straight
lines, and curves.
And this about brushes:
A brush is a graphics tool that applications use to paint the interior
of polygons, ellipses, and paths.
Your code would need to be something more like this:
case WM_PAINT:
{
PAINTSTRUCT ps;
hdc=BeginPaint(hWnd, &ps); // Used instead of GetDC in WM_PAINT
HPEN hPen = CreatePen(PS_SOLID, 1, RGB(255,255,255));
HPEN hOldPen = SelectObject(hdc, hPen);
MoveToEx(hdc, 0, 0, 0);
LineTo(hdc, 100, 100);
SelectObject(hdc, hOldPen);
DeleteObject(hPen);
EndPaint(hWnd, &ps); // Used instead of ReleaseDC in WM_PAINT
}

Resources