C语言编写图形界面 | 移动小球示例

文章目录

  • 其他文章
  • 最终结果
  • 设计过程
    • 定义小球的属性
    • 窗口过程函数
    • 绘制小球
    • 空格回弹
    • 小球碰壁
  • 完整代码

其他文章

部分知识可以查看如下文章:
C语言编写注册窗口

最终结果

先放一下本篇文章最终结果展示图吧,如图,一个绿色的小球,在碰到窗口边缘的时候会自动弹回,并且按空格,会让小球反方向移动。
在这里插入图片描述

设计过程

定义小球的属性

把小球的属性定义成全局变量,使其可以在整个文件中使用。
我们需要设置小球的初始位置和速度,用于控制小球的移动。初始位置可以通过x和y两个坐标来表示。
代码如下所示:

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

窗口过程函数

接着我们需要声明窗口过程函数,该函数将作为窗口的消息处理函数,用于处理窗口的各种消息。

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

如上,我们声明 WindowProc 函数为窗口过程函数,然后我们需要将窗口过程函数的地址赋给WNDCLASS结构体变量的lpfnWndProc成员。代码如下。

wc.lpfnWndProc = WindowProc;

绘制小球

要想绘制小球,我们需要接收WM_PAINT窗口消息类型,该窗口消息用于绘图和更新窗口的外观。

case WM_PAINT:
{
	return 0;
}

最后的 return 0; 表示消息已经得到处理,不需要进一步传递处理。

接收到窗口消息后,首先通过调用 BeginPaint 函数获取绘图设备上下文(hdc)和绘图相关的信息(ps)。

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

然后,通过调用 GetClientRect 函数获取窗口的客户区矩形(rc),该矩形表示窗口的绘图区域。

RECT rc;
GetClientRect(hwnd, &rc);

接下来,通过调用 FillRect 函数,使用指定的画刷将窗口的客户区填充为指定的颜色。

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

这里使用 (HBRUSH)(COLOR_WINDOW + 1) 表示使用系统默认的窗口背景色。

然后,通过调用 CreatePen 和 CreateSolidBrush 创建画笔(hPen)和画刷(hBrush),并将它们选入绘图设备上下文(hdc)中,用于绘制小球。

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

通过Ellipse()方法,我们可以绘制出一个椭圆。

该方法的第一个参数是设备上下文句柄,这个参数指定了要在哪个设备上下文中进行绘制。第二和第三个参数是小球的左上角坐标,表示小球在窗口中的位置。分别是x和y的坐标,这就用我们之前声明的全局变量就好了。最后两个参数是我们小球的右下角坐标,我们可以通过对我们左上角的坐标+一个值,来规定小球的大小。比如说我们可以使用ballX + 20 和 ballY + 20 作为小球的右下角坐标,表示小球的宽高都是 20 像素。

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

然后,将不再使用的画笔(hPen)和画刷(hBrush)资源释放,以避免内存泄漏,通过调用 DeleteObject 函数进行资源删除。

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

最后,通过调用 EndPaint 函数结束绘图操作,并通知系统已完成窗口绘制。

EndPaint(hwnd, &ps);

完整代码如下所示:

// 窗口过程函数
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;
        }
    }
}

空格回弹

接下来我们实现空格回弹的操作。

在窗口消息处理函数中,我们检查窗口消息,如果窗口消息类型是 WM_KEYDOWN 消息,就表示当前有按键按下了。

case WM_KEYDOWN:
{
}

接下来我们检查wParam变量,查看按下的按键是否是空格。
wParam变量是窗口消息处理函数的一个参数,这个变量中有哪个按键被按下的消息,它是一个无符号整数,代表按下的键的虚拟键码。
虚拟码是定义在 Windows.h 头文件中的常量,也叫虚拟键码。
而我们空格键的虚拟码就是VK_SPACE.
通过如下代码判断按下的是否是空格。

if (wParam == VK_SPACE)

当空格键被按下时,我们改变小球的移动速度,将速度 velocityX 和 velocityY 的值取反,以改变小球的速度。通过将速度取反,使小球在水平和垂直方向上的运动方向都将改变。

最后,通过 return 0; 表示消息已经得到处理,不需要进一步传递处理。

如下所示:

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

小球碰壁

要知道小球是否碰壁,我们要时刻监视,通过WM_TIMER窗口消息,我们可以定时检查小球是否碰壁。
WM_TIMER表示定时器消息,它会周期性地触发。

case WM_TIMER:
{
    return 0;
}

当接收到 WM_TIMER 消息时,即定时器到达了指定的时间间隔时,会执行case后的代码块。

最后的 return 0; 表示消息已经得到处理,不需要进一步传递处理。

接下来,我们需要更新小球的位置。根据当前的速度 (velocityX 和 velocityY),将小球的水平和垂直位置分别增加对应的速度值。

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

然后,通过调用 GetClientRect 方法,可以获取到窗口的客户区域(注意,该区域不包括标题栏和边框),然后将其存储在一个RECT类型的变量中。

RECT rc;
GetClientRect(hwnd, &rc);

接下来,要根据小球的位置以及窗口的边界,检查小球是否与窗口边界发生碰撞。如果小球的横坐标 (ballX) 小于等于 0 或大于等于窗口的宽度减去小球的宽度,即小球到达了窗口的左右边界,那么将水平速度取反,从而使小球改变水平方向的运动。
如果小球的纵坐标 (ballY) 小于等于 0 或大于等于窗口的高度减去小球的高度,即小球到达了窗口的上下边界,那么将垂直速度取反,从而使小球改变垂直方向的运动。

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

最后,通过调用 InvalidateRect 函数,请求重新绘制窗口的客户区域,使得小球的位置更新后能够在窗口中显示出来。

InvalidateRect(hwnd, NULL, TRUE);

完整代码

#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);
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/87622.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

centos7安装JDK

1.将JDK压缩包复制到/opt/software路径下 2.解压JDK到/opt/module目录下 [rootkb135 software]# tar -zxvf jdk-8u381-linux-x64.tar.gz -C /opt/module 3.配置环境变量 修改profile文件 vim /etc/profile 添加环境变量 #JAVA_HOME export JAVA_HOME/opt/module/jdk1.8.0_…

【BUG】Docker启动MySQL报错

个人主页&#xff1a;金鳞踏雨 个人简介&#xff1a;大家好&#xff0c;我是金鳞&#xff0c;一个初出茅庐的Java小白 目前状况&#xff1a;22届普通本科毕业生&#xff0c;几经波折了&#xff0c;现在任职于一家国内大型知名日化公司&#xff0c;从事Java开发工作 我的博客&am…

隧道HTTP具备的条件

作为一名专业的爬虫代理供应商&#xff0c;我们都知道使用代理是保证爬虫的高效性和稳定性的重要手段之一。而隧道代理则是近年来备受推崇的一种代理形式&#xff0c;它通过将请求通过隧道传输&#xff0c;可以有效地隐藏爬虫的真实IP地址&#xff0c;提高爬虫的反爬能力。 在…

Java编程的未来:2023年值得关注的五个趋势

准备好进入Java编程这个不断发展的创新世界了吗&#xff1f;二十多年来&#xff0c;Java一直是编程世界不可或缺的一部分&#xff0c;其重要性始终没有改变。随着企业软件解决方案中对Java的需求持续增长&#xff0c;这一编程语言保持了其作为跨各种设备和集成系统创建复杂软件…

iOS开发之查看静态库(.a/.framework)中包含的.o文件和函数符号(ar,nm命令)

.a/.framework其实是把编译生成的.o文件&#xff0c;打包成一个.a/.framework文件。a的意思是archive/归档的意思。 查看静态库.a文件包含的内容用下面的命令解压&#xff1a; ar x xxx.a 用ar命令打包静态库&#xff1a; 参数r是将后面的*.o或者*.a文件添加到目标文件中 参数…

《数字图像处理-OpenCV/Python》连载(2)目录

《数字图像处理-OpenCV/Python》连载&#xff08;2&#xff09;目录 本书京东优惠购书链接&#xff1a;https://item.jd.com/14098452.html 本书CSDN独家连载专栏&#xff1a;https://blog.csdn.net/youcans/category_12418787.html 第一部分 OpenCV-Python的基本操作 第1章 …

5款黑科技软件,觉得有用的自行搜索下载

分享是一种神奇的东西&#xff0c;它使快乐增大&#xff0c;它使悲伤减小&#xff0c;坚持分享一些好用的软件给大家&#xff0c;今天继续为大家带来五款神器软件。 屏幕共享——Deskreen ​ Deskreen是一款可以将你的电脑屏幕无线投射到任何设备上的软件&#xff0c;只要你的…

python WSGI和ASGI的区别

用户到我们web应用中间经过的相关协议&#xff0c;具体介绍和pyhton相关的WSGI和ASGI&#xff0c;我先把结论列出来&#xff0c;详细描述请看下面介绍&#xff01; 请大家先记住这张图&#xff0c;带着问题和整个框架去看比较易于了解 CGI&#xff0c;WSGI&#xff0c;ASGI、…

做不做软测都能学的技能,一招化解磁盘空间不足!

如&#xff0c;我有一台服务器&#xff0c;磁盘空间为 50g 现在&#xff0c;使用了一段时间之后&#xff0c;磁盘空间不够了 磁盘空间不够&#xff0c;这个时候&#xff0c;如果你再执行某些写入磁盘的操作就会报错&#xff0c;无法执行。 测试服务器磁盘空间不够&#xff0c;…

HTTP与RPC的取舍

HTTP与RPC的取舍 HTTP和RPC都是常用的网络通信协议&#xff0c;它们各有优劣。选择何种协议&#xff0c;主要取决于应用的需求和场景。 HTTP和RPC都有各自的优点和缺点&#xff0c;首先我们对两种协议进行一个总结。 HTTP协议图 HTTP的优点&#xff1a; 广泛的支持&#xff1…

基于C++的QT实现贪吃蛇小游戏

文章目录&#xff1a; 一&#xff1a;效果演示 二&#xff1a;实现思路 三&#xff1a;代码实现 widget.h widget.cpp main.cpp 一&#xff1a;效果演示 效果图◕‿◕✌✌✌ 代码下载 二&#xff1a;实现思路 通过按键控制蛇的移动&#xff0c;每吃一个商品蛇身就会加长…

【TypeScript】声明文件

在 TypeScript 中&#xff0c;声明文件&#xff08;Declaration Files&#xff09;用于描述已有 JavaScript 代码库的类型信息&#xff0c;以便在 TypeScript 项目中使用这些代码库时获得类型支持。 当你在 TypeScript 项目中引用外部 JavaScript 模块或库时&#xff0c;可能会…

uniapp离线打包apk - Android Studio

uniapp 离线打包 基于uni-app的andiord 离线打包 开发工具及所需要的jar包​1.将下载的App离线SDK解压打开&#xff0c;找到HBuilder-Integrate-AS &#xff0c;在Android Studio打开2.打开HBuilder X&#xff0c;发行->原生app本地打包->生成本地打包app资源3.在“HBuil…

“解放 Arweave“优惠:4EVERLAND的无缝上传教程

为了进一步展示 Arweave 的能力&#xff0c;4EVERLAND 骄傲地推出了“解放 Arweave”活动。我们认识到 Arweave 在数据完整性、抗审查性以及长期保存方面的无与伦比的优势&#xff0c;因此我们与这个去中心化的存储巨头建立了强大的集成。 克服了过去与加密货币支付逻辑相关的…

72 # http 缓存策略

前面实现了一个 http-server&#xff0c;并且实现了 gzip 的压缩&#xff0c;下面通过前面几节学习的缓存知识来添加一下缓存。 大致就是先强制缓存 10s&#xff0c;然后采用协商&#xff08;对比&#xff09;缓存&#xff0c;大致图如下 在之前的 http-server 的代码基础上添…

[虚幻引擎 UE5] EditableText(可编辑文本) 限制只能输入数字并且设置最小值和最大值

本蓝图函数可以格式化 EditableText 控件输入的数据&#xff0c;让其只能输入一定范围内的整数。 蓝图函数 调用方法 下载蓝图&#xff08;5.2.1版本&#xff09;https://dt.cq.cn/archives/618

放苹果(巧用递归)--夏令营

题目 tips&#xff1a; 1.写递归要有递归边界条件&#xff0c;递归过程就是向边界不断靠近 这里注意&#xff1a;虽然题目给的m,n输入数据范围是>1的&#xff0c;但不代表边界就是这个&#xff1b; 首先&#xff0c;n0肯定是不存在的&#xff0c;所以n的边界肯定是1&#…

Spring Cloud Alibaba-实现服务调用的负载均衡

1. 什么是负载均衡 通俗的讲&#xff0c; 负载均衡就是将负载&#xff08;工作任务&#xff0c;访问请求&#xff09;进行分摊到多个操作单元&#xff08;服务器,组件&#xff09;上进行执行。 根据负载均衡发生位置的不同,一般分为服务端负载均衡和客户端负载均衡。 服务端负…

PyTorch学习笔记(十七)——完整的模型验证(测试,demo)套路

完整代码&#xff1a; import torch import torchvision from PIL import Image from torch import nnimage_path "../imgs/dog.png" image Image.open(image_path) print(image)# 因为png格式是四个通道&#xff0c;除了RGB三通道外&#xff0c;还有一个透明度通…

Android Lottie加载gson文件动画

一&#xff1a;Lottie的使用 在你工程的build.gradle文件里添加如下配置 implementation com.airbnb.android:lottie:3.4.0二&#xff1a;布局文件直接引入LottieAnimationView <com.airbnb.lottie.LottieAnimationViewandroid:id"id/lottie_view"android:layout…