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

news2024/9/23 16:23:49

文章目录

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

其他文章

部分知识可以查看如下文章:
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);
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《数字图像处理-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章 …

一篇搞懂浏览器的工作原理(万字详解)

摘要 本文是学习极客时间上的课程&#xff0c;进而整理出的浏览器工作原理。 第一部分&#xff1a;浏览器的进程和线程 &#xff08;1&#xff09;进程和线程的区别&#xff1f; 在浏览器中&#xff0c;各个进程负责处理自己的事情&#xff0c;而不同的进程中&#xff0c;也…

Android多线程开发核心知识点

什么是线程并发安全 线程安全的本质是能够让并发线程&#xff0c;有序的运行(这个有序可能是先来后到的排队&#xff0c;有可能有人插队&#xff0c;但是不管怎么着&#xff0c;同一时刻只能一个线程有权访问同步资源)&#xff0c;线程执行的结果&#xff0c;能够对其他线程可…

C语言实例_数据压缩与解压

一、压缩与解压介绍 数据压缩是通过一系列的算法和技术将原始数据转换为更紧凑的表示形式&#xff0c;以减少数据占用的存储空间。数据解压缩则是将压缩后的数据恢复到原始的表示形式。 数据可以被压缩打包并减少空间占用的原因有以下几个方面&#xff1a; &#xff08;1&am…

Python支持下Noah-MP陆面模式站点、区域模拟及可视化分析

熟悉陆表过程的主要研究内容以及陆面模型在生态水文研究中的地位和作用&#xff1b;深入理解Noah-MP 5.0模型的原理&#xff0c;掌握Noah-MP模型&#xff08;2023年最新发布的5.0版本&#xff09;所需的系统环境与编译环境的搭建方法及模型实践运行&#xff0c;熟悉linux系统操…

高忆管理大盘行情:美股开盘时间?

股票商场是一个充满生机的环境&#xff0c;而美股作为全球最具生机和最具影响力的股票商场之一&#xff0c;被投资者和买卖员广泛关注。美股开盘时刻是众多投资者和买卖员最为关心的问题之一&#xff0c;由于不同的买卖时刻会对在商场中进行投资产生重要影响。那么&#xff0c;…

虹科分享 | 如何构建深度防御层 | 自动移动目标防御

在当今的威胁环境中&#xff0c;多层纵深防御是安全团队获得安心的唯一方法之一。 为什么?有两个原因: 1、攻击面越来越大 随着DevOps等远程工作和数字化转型项目的兴起&#xff0c;攻击面已经超出了大多数安全团队的定义能力。创建一个完全安全的网络边界是不可能的。正如…

计算机视觉-图形处理基础

一、环境安装 1、anaconda安装 官网下载&#xff1a;Free Download | Anaconda 2、打开anaconda prompt测试是否安装成功 输入conda env list命令显示虚拟环境清单即可 二、创建开发虚拟环境 1、创建python3.8虚拟环境 conda create --name demo_py3.8 python3.8 2、激…

ubuntu18 编译ROS代码 报“cxx_attribute_deprecated”is not know to CXX compiler

编译代码报错如下&#xff1a; 通常用两种解决方式&#xff1a; 1&#xff1a;先检查一下 编写的CMakeList.txt的文件中&#xff0c;所包含、链接的文件和库是否都存在且引用正确&#xff08;仔细的查看了一下&#xff0c;都是正确的&#xff09; 2&#xff1a;查看一下cmake…

iOS 17 及 Xcode 15.0 Beta7 问题记录

1、iOS 17 真机调试问题 iOS 17之后&#xff0c;真机调试Beta版本必须使用Beta版本的Xcode来调试&#xff0c;用以前复制DeviceSupport 方式无法调试&#xff0c;新的Beta版本Xcode中&#xff0c;已经不包含 iOS 17目录。如下图&#xff1a; 解决方案&#xff1a; 1&#x…

数据驱动的胜利:体育赛事的可视化之道

当前&#xff0c;体育赛事遍地开花&#xff0c;智能体育也不断成熟。体育赛事不断与物联网、数字孪生等新一代信息技术深入融合&#xff0c;创新运动方式&#xff0c;推动全民健身&#xff0c;促进产业升级。数字孪生可视化平台易知微也一直致力于体育领域的数字化发展&#xf…

Elexcon2023深圳国际电子展开幕,飞凌嵌入式重装亮相!

8月23日&#xff0c;Elexcon2023深圳国际电子展正式开幕&#xff0c;近600家全球优质品牌厂商齐聚深圳会展中心&#xff08;福田&#xff09;&#xff0c;并有上万名观众来到现场参观、交流。飞凌嵌入式携多款多类型的智能主控产品及热门行业解决方案亮相盛会&#xff0c;展位号…

基于 SpringBoot+Vue+Java 的财务管理系统,附源码,教程

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝30W,Csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 文章目录 一 简介第二.主要技术第三、部分效果图第四章 系统设计4.1功能结构4.2 数据库设计4.2.1 数据库E/R…

如何使用图形数据库构建实时推荐引擎

推荐&#xff1a;使用 NSDT场景编辑器助你快速搭建可二次编辑的3D应用场景 “这是给你的”&#xff0c;“为你推荐的”或“你可能也喜欢”&#xff0c;是大多数数字业务中必不可少的短语&#xff0c;特别是在电子商务或流媒体平台中。 尽管它们看起来像一个简单的概念&#xf…

Linux 设置mysql开机自启动和安装JDK

0目录 1.mysql设置开机自启动 2.linux安装jdk 1.mysql设置开机自启动 去到cd /etc/rc.d/init.d目录 创建一个sh脚本 编辑脚本 设置开机自启动 重启 检查是否自启动 2.linux安装jdk 下载安装包 放在opt目录下 新建soft文件夹&#xff08;opt目录下&#xff09;…

window版本的kibana的安装,使用,启动

前言&#xff1a;本篇是根据小破站UP主遇见狂神说学习篇&#xff0c; 这里是我做的笔记&#xff0c;&#xff08;ElasticSearch7.6入门学习笔记-KuangStudy-文章&#xff09;&#xff0c;up主的文档肯定比我更清晰&#xff0c;但我还是记录一下嘿嘿嘿&#xff0c;下面直接开始 …

用户管理与用户权限

数据库用户管理 新建用户 CREATE USER 用户名来源地址 [IDENTIFIED BY [PASSWORD] 密码]; 用户名&#xff1a;指定将创建的用户名. 来源地址&#xff1a;指定新创建的用户可在哪些主机上登录&#xff0c;可使用IP地址、网段、主机名的形式&#xff0c;本地用户可用localhost&…

【全站最全】被苹果、谷歌和Microsoft停产的产品(三)

2016 Narrative Clip 2012 – 2016 HARDWARE Narrative Clip was small wearable capable of automatically take a picture every 30 seconds whilst being worn throughout the day, a practice known as "life-logging". Pebble 2013 – 2016 HARDWARE Pe…

C#+WPF上位机开发(模块化+反应式)作者:重庆教主

在上位机开发领域中&#xff0c;C#与C两种语言是应用最多的两种开发语言&#xff0c;在C语言中&#xff0c;与之搭配的前端框架通常以QT最为常用&#xff0c;而C#语言中&#xff0c;与之搭配的前端框架是Winform和WPF两种框架。今天我们主要讨论一下C#和WPF这一对组合在上位机开…

SUI 将通过 SUI Foundation 资助 Footprint Analytics 解析其公链数据,为生态系统提供支持

2023 年 8 月 23 日消息&#xff0c;SUI 基金会宣布将赞助链上数据提供商 Footprint Analytics&#xff0c;旨在协助解析公链数据&#xff0c;为 SUI 的生态系统及其开发者提供全方位支持。 Footprint Analytics 将提供完整的数据报表和 API 接口&#xff0c; SUI 链的用户可以…