Graphical interface written in C language | Example of moving ball

Other articles

For some knowledge, you can view the following article:
Writing a registration window in C language

Final Results

Let’s take a look at the final result display of this article. As shown in the picture, a green ball will automatically bounce back when it hits the edge of the window. Pressing the space button will cause the ball to move in the opposite direction.
Insert image description here

designing process

Define the properties of the ball

Define the properties of the ball as global variables so that they can be used throughout the file.
We need to set the initial position and speed of the ball to control the movement of the ball. The initial position can be represented by two coordinates, x and y.
The code looks like this:

// 小球的初始位置和速度
int ballX = 50;
int ballY = 50;
int velocityX = 5;
int velocityY = 5;

window procedure function

Next we need to declare the window procedure function, which will be used as the message processing function of the window to process various messages of the window.

// 窗口过程函数
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    
    
    switch (uMsg)
    {
    
    
        //......
    }
}

As above, we declare the WindowProc function as a window procedure function, and then we need to assign the address of the window procedure function to the lpfnWndProc member of the WNDCLASS structure variable. code show as below.

wc.lpfnWndProc = WindowProc;

Draw a ball

To draw the ball, we need to receive the WM_PAINT window message type, which is used to draw and update the appearance of the window.

case WM_PAINT:
{
    
    
	return 0;
}

The final return 0; indicates that the message has been processed and no further processing is required.

After receiving the window message, first obtain the drawing device context (hdc) and drawing-related information (ps) by calling the BeginPaint function.

PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);

Then, get the client area rectangle (rc) of the window by calling the GetClientRect function, which represents the drawing area of ​​the window.

RECT rc;
GetClientRect(hwnd, &rc);

Next, by calling the FillRect function, use the specified brush to fill the client area of ​​the window with the specified color.

FillRect(hdc, &rc, (HBRUSH)(COLOR_WINDOW + 1));

Using (HBRUSH)(COLOR_WINDOW + 1) here means using the system default window background color.

Then, create a pen (hPen) and a brush (hBrush) by calling CreatePen and CreateSolidBrush, and select them into the drawing device context (hdc) for drawing the ball.

// 设置画笔和画刷
HPEN hPen = CreatePen(PS_SOLID, 2, RGB(255, 0, 0));
HBRUSH hBrush = CreateSolidBrush(RGB(0, 255, 0));
SelectObject(hdc, hPen);
SelectObject(hdc, hBrush);

Through the Ellipse() method, we can draw an ellipse.

The first parameter of this method is the device context handle, which specifies the device context in which to draw. The second and third parameters are the coordinates of the upper left corner of the ball, indicating the position of the ball in the window. They are the coordinates of x and y respectively. Just use the global variables we declared before. The last two parameters are the coordinates of the lower right corner of our ball. We can specify the size of the ball by adding a value to the coordinates of our upper left corner. For example, we can use ballX + 20 and ballY + 20 as the coordinates of the lower right corner of the ball, which means that the width and height of the ball are 20 pixels.

// 绘制小球
            Ellipse(hdc, ballX, ballY, ballX + 20, ballY + 20);

Then, release the no longer used pen (hPen) and brush (hBrush) resources to avoid memory leaks, and delete the resources by calling the DeleteObject function.

 // 释放资源
DeleteObject(hPen);
DeleteObject(hBrush);

Finally, the drawing operation ends by calling the EndPaint function and notifies the system that the window drawing has been completed.

EndPaint(hwnd, &ps);

The complete code is as follows:

// 窗口过程函数
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    
    
    switch (uMsg)
    {
    
    
        case WM_PAINT:
        {
    
    
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hwnd, &ps);
            
            // 清除窗口内容
            RECT rc;
            GetClientRect(hwnd, &rc);
            FillRect(hdc, &rc, (HBRUSH)(COLOR_WINDOW + 1));
            
            // 设置画笔和画刷
            HPEN hPen = CreatePen(PS_SOLID, 2, RGB(255, 0, 0));
            HBRUSH hBrush = CreateSolidBrush(RGB(0, 255, 0));
            SelectObject(hdc, hPen);
            SelectObject(hdc, hBrush);

            // 绘制小球
            Ellipse(hdc, ballX, ballY, ballX + 20, ballY + 20);

            // 释放资源
            DeleteObject(hPen);
            DeleteObject(hBrush);

            EndPaint(hwnd, &ps);
            return 0;
        }
    }
}

Space bounce

Next we implement the space rebound operation.

In the window message processing function, we check the window message. If the window message type is a WM_KEYDOWN message, it means that a key is currently pressed.

case WM_KEYDOWN:
{
    
    
}

Next we check the wParam variable to see if the pressed key is a space.
The wParam variable is a parameter of the window message processing function. This variable contains information about which key was pressed. It is an unsigned integer that represents the virtual key code of the pressed key.
The virtual code is a constant defined in the Windows.h header file, also called a virtual key code.
The virtual code of our space bar is VK_SPACE.
Use the following code to determine whether the pressed space is a space.

if (wParam == VK_SPACE)

When the space bar is pressed, we change the moving speed of the ball and invert the values ​​of velocity velocityX and velocityY to change the speed of the ball. By inverting the velocity, the direction of the ball's movement will change both horizontally and vertically.

Finally, return 0; indicates that the message has been processed and no further processing is required.

As follows:

        case WM_KEYDOWN:
        {
    
    
            // 按下空格键时改变小球速度
            if (wParam == VK_SPACE)
            {
    
    
                velocityX = -velocityX;
                velocityY = -velocityY;
            }
            return 0;
        }

The ball hits the wall

To know whether the ball hits the wall, we need to monitor it at all times. Through the WM_TIMER window message, we can regularly check whether the ball hits the wall.
WM_TIMER represents a timer message, which is triggered periodically.

case WM_TIMER:
{
    
    
    return 0;
}

When the WM_TIMER message is received, that is, when the timer reaches the specified time interval, the code block after the case will be executed.

The final return 0; indicates that the message has been processed and no further processing is required.

Next, we need to update the ball's position. According to the current velocity (velocityX and velocityY), increase the horizontal and vertical positions of the ball by the corresponding velocity values.

// 更新小球的位置
ballX += velocityX;
ballY += velocityY;

Then, by calling the GetClientRect method, you can get the client area of ​​the window (note that this area does not include the title bar and border), and then store it in a RECT type variable.

RECT rc;
GetClientRect(hwnd, &rc);

Next, based on the position of the ball and the boundary of the window, check whether the ball collides with the window boundary. If the abscissa of the ball (ball sports.
If the ordinate of the ball (ballY) is less than or equal to 0 or greater than or equal to the height of the window minus the height of the ball, that is, the ball reaches the upper and lower boundaries of the window, then the vertical speed is inverted, so that the ball changes the direction of the vertical direction. sports.

if (ballX <= 0 || ballX >= rc.right - 20)
{
    
    
    velocityX = -velocityX;
}
if (ballY <= 0 || ballY >= rc.bottom - 20)
{
    
    
    velocityY = -velocityY;
}

Finally, by calling the InvalidateRect function, the client area of ​​the window is requested to be redrawn so that the updated position of the ball can be displayed in the window.

InvalidateRect(hwnd, NULL, TRUE);

Complete code

#include <windows.h>

// 小球的初始位置和速度
int ballX = 50;
int ballY = 50;
int velocityX = 5;
int velocityY = 5;
// 窗口过程函数
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
// 程序入口
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    
    
    // 注册窗口类
    WNDCLASS wc = {
    
    0};
    wc.lpfnWndProc = WindowProc;
    wc.hInstance = hInstance;
    wc.lpszClassName = "MyWindowClass";
    RegisterClass(&wc);
    
    // 创建窗口
    HWND hwnd = CreateWindowEx(
        0,
        "MyWindowClass",
        "移动小球",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        800,
        600,
        NULL,
        NULL,
        hInstance,
        NULL);

    // 设置定时器,控制小球移动
    SetTimer(hwnd, 1, 30, NULL);

    // 显示窗口
    ShowWindow(hwnd, nCmdShow);

    // 消息循环
    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0))
    {
    
    
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return 0;
}

// 窗口过程函数
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    
    
    switch (uMsg)
    {
    
    
        case WM_PAINT:
        {
    
    
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hwnd, &ps);
            
            // 清除窗口内容
            RECT rc;
            GetClientRect(hwnd, &rc);
            FillRect(hdc, &rc, (HBRUSH)(COLOR_WINDOW + 1));
            
            // 设置画笔和画刷
            HPEN hPen = CreatePen(PS_SOLID, 2, RGB(255, 0, 0));
            HBRUSH hBrush = CreateSolidBrush(RGB(0, 255, 0));
            SelectObject(hdc, hPen);
            SelectObject(hdc, hBrush);

            // 绘制小球
            Ellipse(hdc, ballX, ballY, ballX + 20, ballY + 20);

            // 释放资源
            DeleteObject(hPen);
            DeleteObject(hBrush);

            EndPaint(hwnd, &ps);
            return 0;
        }
        case WM_KEYDOWN:
        {
    
    
            // 按下空格键时改变小球速度
            if (wParam == VK_SPACE)
            {
    
    
                velocityX = -velocityX;
                velocityY = -velocityY;
            }
            return 0;
        }
        case WM_TIMER:
        {
    
    
            // 更新小球的位置
            ballX += velocityX;
            ballY += velocityY;

            // 碰到窗口边界时改变方向
            RECT rc;
            GetClientRect(hwnd, &rc);
            if (ballX <= 0 || ballX >= rc.right - 20)
            {
    
    
                velocityX = -velocityX;
            }
            if (ballY <= 0 || ballY >= rc.bottom - 20)
            {
    
    
                velocityY = -velocityY;
            }

            // 重绘窗口
            InvalidateRect(hwnd, NULL, TRUE);
            return 0;
        }
        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;
        default:
            return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }
}

Guess you like

Origin blog.csdn.net/weixin_44499065/article/details/132472271