0.引言
\qquad
看了CSDN上很多關于C程序圖形化界面的介紹,有的代碼繁瑣難解,不方便調試修改;有的不夠詳細。本文提供的代碼簡單、易于移植、容易理解,望急需使用C語言制作圖形化界面的朋友采納。
\qquad
對easyx尚不熟悉的朋友不需要擔心,我敢打包票它只需10分鐘就可以上手,而它為你節省的時間可能是3個小時甚至更多。關于easyx的簡單應用請參考一篇我以前寫的關于C程序可視化的博文。
→【C語言實現動畫控制】←
\qquad
本文的講解是循序漸進的,讀者應該重點關注每個步驟的理解,兩步之間代碼的變化,即可完全理解本文。
1.素材準備
- easyx的下載鏈接如下:(本文使用的版本是2014冬至版)
https://www.easyx.cn/downloads/
注:使用easyx需要注意它兼容的編譯器(下載的幫助文件會寫),不同的easyx兼容的編譯器不同,但總是和visual C++6兼容(和字符編碼有關),本文以visual C++6編譯器為例書寫代碼。 - easyx的最新英文幫助文檔鏈接(下載2014冬至版會自帶中文幫助文檔):
https://docs.easyx.cn/en-us/intro - 如果你成功下載了easyx2014冬至版,那么解壓后把頭文件(easyx.h和graphic.h)和lib文件(amd64)分別放在VC文件夾默認的include文件夾和lib文件夾中。右鍵你的VC程序,選擇打開文件所在位置,然后找到MFC文件夾,友情提供兩個文件夾的位置截圖。
- 建議編譯的C文件以cpp后綴保存。
2.編程
2.1.創建你的界面
\qquad 創建一個480×360的窗口,我們需要使用initgraph()函數,閑言少敘,讓我們直接看一段代碼:
#include <graphics.h> // 引用圖形庫頭文件
#include <conio.h>
#include <stdio.h>
#include <windows.h> //用到了定時函數sleep()
#include <math.h>
int main()
{
int i;
short win_width,win_height;//定義窗口的寬度和高度
win_width = 480;win_height = 360;
initgraph(win_width,win_height);//初始化窗口(黑屏)
for(i=0;i<256;i+=5)
{
setbkcolor(RGB(i,i,i));//設置背景色,原來默認黑色
cleardevice();//清屏(取決于背景色)
Sleep(15);//延時15ms
}
closegraph();//關閉繪圖界面
}
\qquad
這段代碼很容易理解,運行這段程序,就會出現逐漸明亮的屏幕。因為不斷刷新背景色為
R
G
B
(
i
,
i
,
i
)
RGB(i,i,i)
RGB(i,i,i)。C語言中的顏色使用十六進制表示的,RGB函數可以將0~255范圍內的三個整數三原色轉換成這個十六進制。
\qquad
cleardevice()函數用于清屏,是界面內所有元素都被清空,一般只會在初始化出現。
\qquad
Sleep()是毫秒級延遲,當然界面變亮時間不一定是準確的15ms×255/5=0.765s,因為其他語句還需要執行時間。
\qquad
closegraph():關閉繪圖界面。注意,如果初始化了繪圖界面但沒有在主函數結束前關閉它,可能會引發一些莫名其妙的錯誤!所以這個函數一定要有!
2.2.創建按鈕
\qquad 我們嘗試在界面創建幾個按鈕,按鈕需要的操作是繪制矩形和打印文字。雖然看著簡單,但是里面還是有點學問,為了方便大家理解,還是先放上代碼和注釋。
#include <graphics.h> // 引用圖形庫頭文件
#include <conio.h>
#include <stdio.h>
#include <windows.h> //用到了定時函數sleep()
#include <math.h>
int r1[]={30,20,130,60};//輸入按鈕的矩形參數
int r2[]={170,20,220,60};//運行按鈕的矩形參數
int r3[]={260,20,310,60};//退出按鈕的矩形參數
int main()
{
int i;
short win_width,win_height;//定義窗口的寬度和高度
win_width = 480;win_height = 360;
initgraph(win_width,win_height);//初始化窗口(黑屏)
for(i=0;i<256;i+=5)
{
setbkcolor(RGB(i,i,i));//設置背景色,原來默認黑色
cleardevice();//清屏(取決于背景色)
Sleep(15);//延時15ms
}
RECT R1={r1[0],r1[1],r1[2],r1[3]};//矩形指針R1
RECT R2={r2[0],r2[1],r2[2],r2[3]};//矩形指針R2
RECT R3={r3[0],r3[1],r3[2],r3[3]};//矩形指針R3
LOGFONT f;//字體樣式指針
gettextstyle(&f); //獲取字體樣式
_tcscpy(f.lfFaceName,_T("宋體")); //設置字體為宋體
f.lfQuality = ANTIALIASED_QUALITY; // 設置輸出效果為抗鋸齒
settextstyle(&f); // 設置字體樣式
settextcolor(BLACK); //BLACK在graphic.h頭文件里面被定義為黑色的顏色常量
drawtext("輸入參數",&R1,DT_CENTER | DT_VCENTER | DT_SINGLELINE);//在矩形區域R1內輸入文字,水平居中,垂直居中,單行顯示
drawtext("運行",&R2,DT_CENTER | DT_VCENTER | DT_SINGLELINE);//在矩形區域R2內輸入文字,水平居中,垂直居中,單行顯示
drawtext("退出",&R3,DT_CENTER | DT_VCENTER | DT_SINGLELINE);//在矩形區域R3內輸入文字,水平居中,垂直居中,單行顯示
setlinecolor(BLACK);
rectangle(r1[0],r1[1],r1[2],r1[3]);
rectangle(r2[0],r2[1],r2[2],r2[3]);
rectangle(r3[0],r3[1],r3[2],r3[3]);
system("pause");//暫停,為了顯示
closegraph();
return 0;
}
\qquad
這里需要特別介紹的是矩形指針
p
R
e
c
t
pRect
pRect,它使用句柄RECT定義,并且不可以中途再次賦值。之所以要設置矩形指針了為了打印字體的時候以矩形為邊界自動填充。它的格式是RECT r={X1,Y1,X2,Y2}
,X1和X2是矩形的左邊和右邊的橫坐標,Y1和Y2是矩形的上邊和下邊的縱坐標,這一點和rectangle()繪制空心矩形函數參數排列一致。后面的DT_CENTER | DT_VCENTER | DT_SINGLELINE
就是描述填充格式的常量。使用drawtext書寫文字不需要再計算文字的坐標和設置大小,會方便很多。
\qquad
LOGFONT是字體樣式指針,通過gettextstyle()函數來獲取當前的字體類型,再通過settextstyle()函數加以設置。這里只修改了字體的名稱和顯示質量,還可以修改斜體、下劃線等屬性,更詳細的部分請參考幫助文檔。
2.3.鼠標操作
2.3.1.單擊特效
\qquad
作為一個圖形化界面的C程序,鼠標操作總不能少吧。在講解程序前先別著急,簡單為大家科普一下鼠標事件:
\qquad
鼠標是輸入設備,只要發生以下的事件,就會暫存在鼠標消息列表中,我們的操作系統就會依次響應列表中的鼠標消息事件,常用的鼠標事件如下:
- WM_MOUSEMOVE——鼠標移動
- WM_MOUSEWHEEL——鼠標滾輪滾動
- WM_LBUTTONDOWN——鼠標左鍵按下
- WM_LBUTTONUP——鼠標左鍵彈起
- WM_LBUTTONDBLCLK——鼠標左鍵雙擊
- WM_RBUTTONDOWN——鼠標右鍵按下
- WM_RBUTTONUP——鼠標右鍵彈起
- WM_RBUTTONDBLCLK——鼠標左鍵雙擊
- WM_MBUTTONDOWN——鼠標中鍵按下
- WM_MBUTTONUP——鼠標中鍵彈起
- WM_MBUTTONDBLCLK——鼠標中鍵雙擊
\qquad 我們只需要根據不斷獲取鼠標消息隊列的消息并根據消息依次進行響應即可。
\qquad 相信大家已經迫不及待了,那么請看下面一個簡單的程序。
#include <graphics.h> // 引用圖形庫頭文件
#include <conio.h>
#include <stdio.h>
#include <windows.h> //用到了定時函數sleep()
#include <math.h>
int r1[]={30,20,130,60};//輸入按鈕的矩形參數
int r2[]={170,20,220,60};//運行按鈕的矩形參數
int r3[]={260,20,310,60};//退出按鈕的矩形參數
int main()
{
int i;
short win_width,win_height;//定義窗口的寬度和高度
win_width = 480;win_height = 360;
initgraph(win_width,win_height);//初始化窗口(黑屏)
for(i=0;i<256;i+=5)
{
setbkcolor(RGB(i,i,i));//設置背景色,原來默認黑色
cleardevice();//清屏(取決于背景色)
Sleep(15);//延時15ms
}
RECT R1={r1[0],r1[1],r1[2],r1[3]};//按鈕1的矩形區域
RECT R2={r2[0],r2[1],r2[2],r2[3]};//按鈕2的矩形區域
RECT R3={r3[0],r3[1],r3[2],r3[3]};//按鈕2的矩形區域
LOGFONT f;
gettextstyle(&f); //獲取字體樣式
_tcscpy(f.lfFaceName,_T("宋體")); //設置字體為宋體
f.lfQuality = ANTIALIASED_QUALITY; // 設置輸出效果為抗鋸齒
settextstyle(&f); // 設置字體樣式
settextcolor(BLACK); //BLACK在graphic.h頭文件里面被定義為黑色的顏色常量
drawtext("輸入參數",&R1,DT_CENTER | DT_VCENTER | DT_SINGLELINE);//在矩形區域R1內輸入文字,水平居中,垂直居中,單行顯示
drawtext("運行",&R2,DT_CENTER | DT_VCENTER | DT_SINGLELINE);//在矩形區域R2內輸入文字,水平居中,垂直居中,單行顯示
drawtext("退出",&R3,DT_CENTER | DT_VCENTER | DT_SINGLELINE);//在矩形區域R3內輸入文字,水平居中,垂直居中,單行顯示
setlinecolor(BLACK);
rectangle(r1[0],r1[1],r1[2],r1[3]);
rectangle(r2[0],r2[1],r2[2],r2[3]);
rectangle(r3[0],r3[1],r3[2],r3[3]);
MOUSEMSG m;//鼠標指針
setrop2(R2_NOTXORPEN);//二元光柵——NOT(屏幕顏色 XOR 當前顏色)
while(true)
{
m = GetMouseMsg();//獲取一條鼠標消息
if(m.uMsg==WM_LBUTTONDOWN)
{
for(i=0;i<=10;i++)
{
setlinecolor(RGB(25*i,25*i,25*i));//設置圓顏色
circle(m.x,m.y,2*i);
Sleep(25);//停頓2ms
circle(m.x,m.y,2*i);//抹去剛剛畫的圓
}
FlushMouseMsgBuff();//清空鼠標消息緩存區
}
}
system("pause");//暫停,為了顯示
closegraph();
return 0;
}
\qquad
每點擊鼠標以下,應該可以看到鼠標點擊處有一個逐漸擴大并淡出的圓(截圖無法清晰,在畫面的中右側),當循環體內Sleep的視覺大于20ms后視覺效果很強。
\qquad
每響應一次鼠標左鍵單擊事件,都會調用一次清空鼠標消息緩存區的函數FlushMouseMsgBuff()
,如果沒有這個函數會怎么樣呢?如果我們快速連續地單擊鼠標左鍵N次,那么特效就會播放N次,如果特效播放速度比單擊的速度慢,那么即使你停下來了,程序仍然會接著播放單擊特效,因為你的左鍵單擊仍然在鼠標的消息隊列m.uMsg
中的鼠標消息沒有響應完。
\qquad
這里需要解釋的是一個二元光柵設置函數setrop2()
,二元光柵是混合背景色和當前顏色的模式。我們這里采用的方式是同或(NOT XOR)的方式,若底色為白色(1),則當前顏色不變;若底色是黑色(0),則當前顏色反色。為什么需要采用這種方式呢?因為我們在第二次抹去原來的圓的時候不能采用白色,否則如果背景色原來就為黑(比如按鈕和文字),就也會被抹成白色。而背景色與任意一個顏色同或兩次都為其本身,即可起到還原背景色的效果。這里的背景色與cleardevice()前面那個背景色不同,這里的是指執行這一條繪畫指令之前屏幕上的顏色。
2.3.2.光標感應
\qquad
我們希望鼠標移到按鈕上時按鈕會有所變化,移開按鈕時又會回到原樣。這里我們采用一種簡單的填充顏色的方法,就是按鈕變色。我們需要解決一個問題就是按鈕變色了但是按鈕的文字不能被覆蓋,那么我們還是需要使用到二元光柵。只是我們這次的模式改成了同或。
\qquad
為了方便起見,存放三個按鈕的數組我們合并為了一個二維數組,在鼠標事件中更容易使用和分配任務。
#include <graphics.h> // 引用圖形庫頭文件
#include <conio.h>
#include <stdio.h>
#include <windows.h> //用到了定時函數sleep()
#include <math.h>
int r[3][4]={{30,20,130,60},{170,20,220,60},{260,20,310,60}};//三個按鈕的二維數組
int button_judge(int x,int y)
{
if(x>r[0][0] && x<r[0][2] && y>r[0][1] && y<r[0][3])return 1;
if(x>r[1][0] && x<r[1][2] && y>r[1][1] && y<r[1][3])return 2;
if(x>r[2][0] && x<r[2][2] && y>r[2][1] && y<r[2][3])return 3;
return 0;
}
int main()
{
int i,event=0;
short win_width,win_height;//定義窗口的寬度和高度
win_width = 480;win_height = 360;
initgraph(win_width,win_height);//初始化窗口(黑屏)
for(i=0;i<256;i+=5)
{
setbkcolor(RGB(i,i,i));//設置背景色,原來默認黑色
cleardevice();//清屏(取決于背景色)
Sleep(15);//延時15ms
}
RECT R1={r[0][0],r[0][1],r[0][2],r[0][3]};
RECT R2={r[1][0],r[1][1],r[1][2],r[1][3]};
RECT R3={r[2][0],r[2][1],r[2][2],r[2][3]};
LOGFONT f;
gettextstyle(&f); //獲取字體樣式
_tcscpy(f.lfFaceName,_T("宋體")); //設置字體為宋體
f.lfQuality = ANTIALIASED_QUALITY; // 設置輸出效果為抗鋸齒
settextstyle(&f); // 設置字體樣式
settextcolor(BLACK); //BLACK在graphic.h頭文件里面被定義為黑色的顏色常量
drawtext("輸入參數",&R1,DT_CENTER | DT_VCENTER | DT_SINGLELINE);//在矩形區域R1內輸入文字,水平居中,垂直居中,單行顯示
drawtext("運行",&R2,DT_CENTER | DT_VCENTER | DT_SINGLELINE);//在矩形區域R2內輸入文字,水平居中,垂直居中,單行顯示
drawtext("退出",&R3,DT_CENTER | DT_VCENTER | DT_SINGLELINE);//在矩形區域R3內輸入文字,水平居中,垂直居中,單行顯示
setlinecolor(BLACK);
rectangle(r[0][0],r[0][1],r[0][2],r[0][3]);
rectangle(r[1][0],r[1][1],r[1][2],r[1][3]);
rectangle(r[2][0],r[2][1],r[2][2],r[2][3]);
MOUSEMSG m;//鼠標指針
while(true)
{
m = GetMouseMsg();//獲取一條鼠標消息
switch(m.uMsg)
{
case WM_MOUSEMOVE:
setrop2(R2_XORPEN);
setlinecolor(LIGHTCYAN);//線條顏色為亮青色
setlinestyle(PS_SOLID, 3);//設置畫線樣式為實現,10磅
setfillcolor(WHITE);//填充顏色為白色
if(button_judge(m.x,m.y)!=0)
{
if(event != button_judge(m.x,m.y))
{
event = button_judge(m.x,m.y);//記錄這一次觸發的按鈕
fillrectangle(r[event-1][0],r[event-1][1],r[event-1][2],r[event-1][3]);//有框填充矩形(X1,Y1,X2,Y2)
}
}
else
{
if(event != 0)//上次觸發的按鈕未被修正為原來的顏色
{
fillrectangle(r[event-1][0],r[event-1][1],r[event-1][2],r[event-1][3]);//兩次同或為原來顏色
event = 0;
}
}
break;
case WM_LBUTTONDOWN:
setrop2(R2_NOTXORPEN);//二元光柵——NOT(屏幕顏色 XOR 當前顏色)
for(i=0;i<=10;i++)
{
setlinecolor(RGB(25*i,25*i,25*i));//設置圓顏色
circle(m.x,m.y,2*i);
Sleep(30);//停頓30ms
circle(m.x,m.y,2*i);//抹去剛剛畫的圓
}
break;
FlushMouseMsgBuff();//清空鼠標消息緩存區
}
}
system("pause");//暫停,為了顯示
return 0;
}
\qquad
這里我們運用了兩次設置二元光柵的函數setrop2,在鼠標的移動條件內(case MOUSEMOVE
),我們使用的是屏幕顏色和當前顏色異或,這是為什么呢?因為fillrectangle()
函數是畫一個有框填充矩形,我們讓這個有框填充矩形和原按鈕的一樣大。由于線條的顏色為亮青色,填充顏色為白色(1),白色的填充顏色和屏幕顏色異或,取的是屏幕顏色的反色。按鈕的邊框是黑色(0),它與亮青色異或,則會保留原來的亮青色。
\qquad
與同或一樣,異或兩次等于沒有執行操作,所以可以還原到原屏幕畫布的顏色。
2.3.3.進度條
\qquad
既然涉及到進度條了,那么就應該涉及到正式程序了,這里我不想涉及太多的專業知識影響大家理解,但又不能讓程序運行得太快以至于看不出進度條的變化,所以我設計了一個簡單的彈性球軌跡作圖程序。
\qquad
假設球半徑為R,初始高度為
h
0
h_0
h0?,初速度為0(自由落體),非彈性碰撞時能量損失率為
α
\alpha
α。計算部分子函數如下:
int simulation()
{
float dt = 0.01;//仿真間隔10ms
long int N = (long int)(sim_t/dt);//迭代次數
float *h=(float*)calloc(N,sizeof(float));//高度
float *v=(float*)calloc(N,sizeof(float));//速度(豎直方向)
long int i;//迭代變量
for(i=1;i<N;i++)
{
if(h[i-1]>R)//未發生碰撞
{
v[i]=v[i-1]-9.8*dt;//速度計算
}
else//發生碰撞,動能損失alpha,速度損失alpha的開方
{
v[i]=-sqrt(alpha)*v[i-1];
}
}
free(h);
free(v);//釋放內存
return 0;
}
\qquad 當然,我們還需要繪圖網格,定義繪圖網格的函數如下:
void init_figure()
{
int i;
setrop2(R2_COPYPEN);//當前顏色
setlinecolor(BLACK);
setlinestyle(PS_SOLID);//實線
rectangle(30,100,420,330);//外框線
setlinestyle(PS_DOT);//點線
for(i=30+39;i<420;i+=39)
{
line(i,100,i,330);//豎直輔助線
}
for(i=100+23;i<330;i+=23)
{
line(30,i,420,i);//水平輔助線
}
}
\qquad 注意,我們使用了rectangle()空心矩形函數繪制網格外框架,使用了line函數依次畫出了輔助線。
\qquad 我們現在的目標就是將h的坐標轉換到網格上去,繪制出球心的軌跡,這樣似乎并不復雜,只需要對simulation()函數稍加修改即可。
int simulation()
{
float dt = 0.01;//仿真間隔10ms
float dy = 230/h0;//單位縱坐標
long int N = (long int)(sim_t/dt);//迭代次數
float *h=(float*)calloc(N,sizeof(float));//高度
float *v=(float*)calloc(N,sizeof(float));//速度(豎直方向)
long int i;//迭代變量
float process_duty;//進度
init_figure();//初始化圖像網格
setrop2(R2_COPYPEN);//當前顏色
//計算步驟
h[0]=h0;v[0]=0;
for(i=1;i<N;i++)
{
if(h[i-1]>R)//未發生碰撞
{
v[i]=v[i-1]-9.8*dt;//速度計算
}
else//發生碰撞,動能損失alpha,速度損失alpha的開方
{
v[i]=-sqrt(alpha)*v[i-1];
}
h[i]=h[i-1]+v[i]*dt;//高度計算
process_duty = (i+1)/(float)(N);
putpixel(30+(int)(process_duty*390),330-(int)(h[i]*dy),RED);//畫點putpixel(X,Y,color*)
Sleep(dt*1000);//延時
}
free(h);
free(v);
return 0;
}
\qquad
這里的新函數putpixel(X,Y,color*)
是畫像素點的函數,適合刻畫不連續或不規則的移動軌跡。
\qquad
現在我們只剩下了刻畫進度條的函數了,進度條的刷新很明顯是應該放在for循環里面的,那么我們采用什么進度條的格式呢?進度條可以有圓形、扇形、長條連續型、長條不連續型等多種,我們這里采用的是環形進度條,將進度數字顯示在環中心。請看以下的對simulation()函數改進的代碼:
//仿真運行
int simulation()
{
char t[3];//百分值的字符
char *out_text;//帶百分號的百分字符
float dt = 0.01;//仿真間隔10ms
float dy = 230/h0;//單位縱坐標
long int N = (long int)(sim_t/dt);//迭代次數
float *h=(float*)calloc(N,sizeof(float));//高度
float *v=(float*)calloc(N,sizeof(float));//速度(豎直方向)
long int i;//迭代變量
float process_duty;//進度
RECT r={370,35,400,65};//百分值顯示區域的矩形指針
init_figure();//初始化圖像網格
setrop2(R2_COPYPEN);//當前顏色
setfillcolor(WHITE);
setlinecolor(WHITE);
fillrectangle(354,19,411,81);//覆蓋原進度條區域
setlinestyle(PS_NULL);//無線條
setbkmode(TRANSPARENT);//設置文字填充背景為透明
//計算步驟
h[0]=h0;v[0]=0;
BeginBatchDraw();//開始緩存區
for(i=1;i<N;i++)
{
if(h[i-1]>R)//未發生碰撞
{
v[i]=v[i-1]-9.8*dt;//速度計算
}
else//發生碰撞,動能損失alpha,速度損失alpha的開方
{
v[i]=-sqrt(alpha)*v[i-1];
}
setfillcolor(WHITE);
setlinecolor(WHITE);
fillrectangle(354,19,416,81);//覆蓋原進度條區域
h[i]=h[i-1]+v[i]*dt;//高度計算
process_duty = (i+1)/(float)(N);
setlinestyle(PS_SOLID);
putpixel(30+(int)(process_duty*390),330-(int)(h[i]*dy),RED);
setfillcolor(BLUE);
setlinestyle(PS_NULL);
fillpie(355,20,415,80,0,process_duty*2*PI);
setfillcolor(WHITE);
fillcircle(385,50,20);
sprintf(t,"%d",(int)(process_duty*100.0));//整型轉換為字符串
out_text = strcat(t,"%");//添加一個百分號
drawtext(out_text,&r,DT_CENTER | DT_VCENTER | DT_SINGLELINE);
Sleep(dt*1000);
FlushBatchDraw();//刷新緩存區
}
EndBatchDraw();//結束緩存區
free(h);
free(v);
return 0;
}
\qquad
這里我們需要多加載一個頭文件<string.h>。
\qquad
首先需要計算進度條的坐標,把環形進度條區域用白色矩形刷新掉,環形進度條需要一個扇形和圓形的組合,扇形的角度是0~360°。這里我們用到了fillpie(X1,Y1,X2,Y2,start_angle,end_angle)
,前四個參數為橢圓扇形的外接矩形坐標,后兩個參數分別為起始角和終止角(弧度制)。每過一次迭代都重新計算終止角(起始角始終為0),即可起到扇形角度逐漸增長的效果,再用一個白色填充圓覆蓋中心部分即可變成環形進度條。
\qquad
FlushBatchDraw()
函數是刷新緩存區的函數,與BeginBatchDraw()
和EndBatchDraw()
一起使用,如果我們繪圖之后不想立即顯示,而想批量繪圖最后一起刷新畫板,用緩存區的方法再合適不過了。
3.完整代碼及效果
#include <graphics.h> // 引用圖形庫頭文件
#include <conio.h>
#include <stdio.h>
#include <windows.h> //用到了定時函數sleep()
#include <math.h>
#include <string.h>
#define PI 3.1416
int r[3][4]={{30,20,130,60},{170,20,220,60},{260,20,310,60}};//三個按鈕的二維數組
float alpha,R,h0,sim_t;//碰撞時的能量損失率,球的半徑、初始高度、仿真時間
//按鈕判斷函數
int button_judge(int x,int y)
{
if(x>r[0][0] && x<r[0][2] && y>r[0][1] && y<r[0][3])return 1;
if(x>r[1][0] && x<r[1][2] && y>r[1][1] && y<r[1][3])return 2;
if(x>r[2][0] && x<r[2][2] && y>r[2][1] && y<r[2][3])return 3;
return 0;
}
//初始化圖像
void init_figure()
{
int i;
setrop2(R2_COPYPEN);//當前顏色
setlinecolor(BLACK);
setlinestyle(PS_SOLID);//實線
rectangle(30,100,420,330);//外框線
setlinestyle(PS_DOT);//點線
for(i=30+39;i<420;i+=39)
{
line(i,100,i,330);//豎直輔助線
}
for(i=100+23;i<330;i+=23)
{
line(30,i,420,i);//水平輔助線
}
}
//仿真運行
int simulation()
{
char t[3];//百分值的字符
char *out_text;
float dt = 0.01;//仿真間隔10ms
float dy = 230/h0;//單位縱坐標
long int N = (long int)(sim_t/dt);//迭代次數
float *h=(float*)calloc(N,sizeof(float));//高度
float *v=(float*)calloc(N,sizeof(float));//速度(豎直方向)
long int i;//迭代變量
float process_duty;//進度
RECT r={370,35,400,65};//百分值顯示區域的矩形指針
init_figure();//初始化圖像網格
setrop2(R2_COPYPEN);//當前顏色
setfillcolor(WHITE);
setlinecolor(WHITE);
fillrectangle(354,19,411,81);//覆蓋原進度條區域
setlinestyle(PS_NULL);//無線條
setbkmode(TRANSPARENT);//設置文字填充背景為透明
//計算步驟
h[0]=h0;v[0]=0;
BeginBatchDraw();//開始緩存區
for(i=1;i<N;i++)
{
if(h[i-1]>R)//未發生碰撞
{
v[i]=v[i-1]-9.8*dt;//速度計算
}
else//發生碰撞,動能損失alpha,速度損失alpha的開方
{
v[i]=-sqrt(alpha)*v[i-1];
}
setfillcolor(WHITE);
setlinecolor(WHITE);
fillrectangle(354,19,416,81);//覆蓋原進度條區域
h[i]=h[i-1]+v[i]*dt;//高度計算
process_duty = (i+1)/(float)(N);
setlinestyle(PS_SOLID);
putpixel(30+(int)(process_duty*390),330-(int)(h[i]*dy),RED);
setfillcolor(BLUE);
setlinestyle(PS_NULL);
fillpie(355,20,415,80,0,process_duty*2*PI);
setfillcolor(WHITE);
fillcircle(385,50,20);
sprintf(t,"%d",(int)(process_duty*100.0));//整型轉換為字符串
out_text = strcat(t,"%");//添加一個百分號
drawtext(out_text,&r,DT_CENTER | DT_VCENTER | DT_SINGLELINE);
Sleep(dt*1000);
FlushBatchDraw();//刷新緩存區
}
EndBatchDraw();//結束緩存區
free(h);
free(v);
return 0;
}
int main()
{
int i,event=0;
char s[30];//輸入字符串變量
short win_width,win_height;//定義窗口的寬度和高度
win_width = 480;win_height = 360;
initgraph(win_width,win_height);//初始化窗口(黑屏)
for(i=0;i<256;i+=5)
{
setbkcolor(RGB(i,i,i));//設置背景色,原來默認黑色
cleardevice();//清屏(取決于背景色)
Sleep(30);//延時30ms
}
RECT R1={r[0][0],r[0][1],r[0][2],r[0][3]};
RECT R2={r[1][0],r[1][1],r[1][2],r[1][3]};
RECT R3={r[2][0],r[2][1],r[2][2],r[2][3]};
LOGFONT f;//字體樣式指針
gettextstyle(&f); //獲取字體樣式
_tcscpy(f.lfFaceName,_T("宋體")); //設置字體為宋體
f.lfQuality = ANTIALIASED_QUALITY; // 設置輸出效果為抗鋸齒
settextstyle(&f); // 設置字體樣式
settextcolor(BLACK); //BLACK在graphic.h頭文件里面被定義為黑色的顏色常量
drawtext("輸入參數",&R1,DT_CENTER | DT_VCENTER | DT_SINGLELINE);//在矩形區域R1內輸入文字,水平居中,垂直居中,單行顯示
drawtext("運行",&R2,DT_CENTER | DT_VCENTER | DT_SINGLELINE);//在矩形區域R2內輸入文字,水平居中,垂直居中,單行顯示
drawtext("退出",&R3,DT_CENTER | DT_VCENTER | DT_SINGLELINE);//在矩形區域R3內輸入文字,水平居中,垂直居中,單行顯示
setlinecolor(BLACK);
rectangle(r[0][0],r[0][1],r[0][2],r[0][3]);
rectangle(r[1][0],r[1][1],r[1][2],r[1][3]);
rectangle(r[2][0],r[2][1],r[2][2],r[2][3]);
MOUSEMSG m;//鼠標指針
while(true)
{
m = GetMouseMsg();//獲取一條鼠標消息
switch(m.uMsg)
{
case WM_MOUSEMOVE:
setrop2(R2_XORPEN);
setlinecolor(LIGHTCYAN);//線條顏色為亮青色
setlinestyle(PS_SOLID, 3);//設置畫線樣式為實現,10磅
setfillcolor(WHITE);//填充顏色為白色
if(button_judge(m.x,m.y)!=0)
{
if(event != button_judge(m.x,m.y))
{
event = button_judge(m.x,m.y);//記錄這一次觸發的按鈕
fillrectangle(r[event-1][0],r[event-1][1],r[event-1][2],r[event-1][3]);//有框填充矩形(X1,Y1,X2,Y2)
}
}
else
{
if(event!=0)//上次觸發的按鈕未被修正為原來的顏色
{
fillrectangle(r[event-1][0],r[event-1][1],r[event-1][2],r[event-1][3]);//兩次同或為原來顏色
event = 0;
}
}
break;
case WM_LBUTTONDOWN:
setrop2(R2_NOTXORPEN);//二元光柵——NOT(屏幕顏色 XOR 當前顏色)
for(i=0;i<=10;i++)
{
setlinecolor(RGB(25*i,25*i,25*i));//設置圓顏色
circle(m.x,m.y,2*i);
Sleep(20);//停頓30ms
circle(m.x,m.y,2*i);//抹去剛剛畫的圓
}
//按照按鈕判斷左鍵單擊后的操作
switch(button_judge(m.x,m.y))
{
//復原按鈕原型
case 1:
InputBox(s,30,"請輸入碰撞時的能量損失率、球的半徑、初始高度、仿真時間");
sscanf(s,"%f%f%f%f",&alpha,&R,&h0,&sim_t);//將輸入字符串依次掃描到全局變量里面
FlushMouseMsgBuffer();//單擊事件后清空鼠標消息
break;
case 2:
simulation();//仿真運行
FlushMouseMsgBuffer();//單擊事件后清空鼠標消息
break;
case 3:
closegraph();//關閉繪圖環境
exit(0);//正常退出
default:
FlushMouseMsgBuffer();//單擊事件后清空鼠標消息
//printf("\r\n(%d,%d)",m.x,m.y);//打印鼠標坐標,方便調試時確定區域
break;
}
break;
}
}
return 0;
}
希望本文對您有幫助,謝謝閱讀。