个人学习与记录,代码贴的很全,源码链接见文末。
主要实现是在linux端通过连接USB摄像头获取视频数据,传输到windows客户端进行显示。使用v4l2接口获取USB视频帧,将视频帧数据通过socket发送到客户端,客户端使用gdi+技术解析数据并显示到MFC窗口,同时使用select实现多客户端同时连接。
使用V4L2获取视频帧部分详细见V4L2接口的简单使用。
然后就是编写socket服务端,并使用select实现多客户端的同时连接,详细代码如下:
linux服务端
socket头文件:
#ifndef MY_SERVER_H
#define MY_SERVER_H
#include <iostream>
#include<sys/socket.h>
#include<sys/un.h>
#include<netinet/in.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <netdb.h>
#include <stdio.h>
#include <map>
#include <netinet/tcp.h>
#include <pthread.h>
#include <signal.h>
using namespace std;
#define MAX_COUNT 20
#define BUF_SIZE 100
#define PORT 8888
extern pthread_mutex_t mutex;
enum Type { HEART, OTHER };
typedef struct PACKET_HEAD
{
Type type;
int length;
}HEAD;
class CServSocket
{
public:
CServSocket();
virtual ~CServSocket();
bool Accept();
bool Listen(int port);
void Send(char* sendbuf, int len);
void Recv();
void getLocalAddr(char *ip);
std::string getCliAddr(int fd);
void Close();
void InitSelect();
void ServerSelect();
bool setNoDelay(bool nodelay);
bool setIntOptions(int option, int value);
private:
int sockfd;
int connsockfd;
struct sockaddr_in servAddr, cliAddr;
int client[MAX_COUNT];
fd_set server_set;
int max_fd ;
map<int,int> mmap;
};
#endif
socket源文件:
#include "Server.h"
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
CServSocket::CServSocket():max_fd(-1),sockfd(-1),connsockfd(-1)
{
for(int i=0;i<MAX_COUNT;++i)
{
client[i] = -1;
}
}
CServSocket::~CServSocket()
{
Close();
}
void CServSocket::Close()
{
for(int i=0;i<MAX_COUNT;++i)
{
if(client[i] >=0)
{
::close(client[i]);
}
}
if(sockfd)
{
::close(sockfd);
}
}
bool CServSocket::Listen(int port) //启动并开始监听
{
if(port < 0)
{
std::cout <<"the port err\n";
return false;
}
sockfd = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); //启动socket
if(sockfd < 0)
{
std::cout << "create socket err\n";
return false;
}
char ip[20];
memset(ip,0,20);
getLocalAddr(ip); //获得本地ip地址
if(ip)
{
servAddr.sin_addr.s_addr = inet_addr(ip);
}
else
{
servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
}
servAddr.sin_family = AF_INET;
servAddr.sin_port = htons(port);
//setIntOptions(SO_KEEPALIVE, 1);
//setNoDelay(true);
setIntOptions(SO_REUSEADDR,1); //打开地址复用
if(::bind(sockfd, (struct sockaddr*)&servAddr, sizeof(servAddr)) < 0) //绑定socket
{
std::cout << "bind err\n";
return false;
}
if(::listen(sockfd, 0) < 0) //监听
{
std::cout << "listen err\n";
return false;
}
return true;
}
void CServSocket::ServerSelect() //执行select阻塞判断
{
int ret = 0;
while(1)
{
InitSelect();
//检测描述符状态
ret = select(max_fd+1,&server_set,NULL,NULL,NULL);
if(ret < 0)
{
std::cout <<"select error\n";
break;
}
else if(ret == 0)
{
std::cout<<"select out_time\n";
continue;
}
else
{
if(!Accept())
{
std::cout << "accept error\n";
continue;
}
Recv();
}
}
}
void CServSocket::InitSelect()//初始化select
{
FD_ZERO(&server_set);
FD_SET(sockfd,&server_set);
max_fd = max_fd > sockfd ? max_fd : sockfd;
for(int i=0 ; i<MAX_COUNT ; ++i)
{
if(client[i]>=0)
{
FD_SET(client[i],&server_set);
max_fd = max_fd > client[i] ? max_fd : client[i];
}
}
}
bool CServSocket::Accept() //接受来自Client的请求
{
if(FD_ISSET(sockfd,&server_set))
{
socklen_t len = sizeof(cliAddr);
if((connsockfd = ::accept(sockfd, (struct sockaddr*)&cliAddr, &len)) <0)
{
std::cout<<"accept error\n";
return false;
}
int i;
for(i=0 ; i<MAX_COUNT ; ++i)
{
if(client[i] < 0)
{
pthread_mutex_lock(&mutex);//多线程互斥锁
client[i] = connsockfd;
FD_SET(connsockfd,&server_set);
pthread_mutex_unlock(&mutex);
mmap.insert(make_pair(connsockfd,0));//用于保存客户端计数心跳
max_fd = max_fd > connsockfd ? max_fd : connsockfd;
break;
}
}
if(i == MAX_COUNT)
{
std::cout<<"the number of connections is full\n";
return false;
}
else
{
printf("accpet a new client: %s[%d]\n", inet_ntoa(cliAddr.sin_addr) , cliAddr.sin_port);
}
}
return true;
}
bool CServSocket::setIntOptions(int option, int value) //socket相关信息设定
{
bool res = false;
if(sockfd)
{
res = (setsockopt(sockfd, SOL_SOCKET, option, (const void*)&value, sizeof(value)) == 0);
}
return res;
}
bool CServSocket::setNoDelay(bool nodelay) //设置发送包是否支持延迟
{
bool res = false;
if(sockfd)
{
int ndelay = nodelay?1:0;
res = (setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY,(const void*)&ndelay, sizeof(ndelay)) == 0);
res =true;
}
return res;
}
void CServSocket::getLocalAddr(char *ip) //获得本地的ip地址
{
char ipaddr[20]={'\0'};
const char* shellstr = "ifconfig | sed -n '2p' | awk -F'[ :]+' '{printf $4}'";
FILE *fp = popen(shellstr, "r"); //通过管道执行shell命令
fread(ipaddr, sizeof(char), sizeof(ipaddr), fp);
if(ipaddr)
{
strcpy(ip, ipaddr);
}
pclose(fp);
}
std::string CServSocket::getCliAddr(int fd) //获取远端客户机的信息
{
char cliip[20];
socklen_t size = sizeof(cliAddr);
if(getpeername(fd, (sockaddr*)&cliAddr, &size)) //获得客户机的ip地址和端口号
{
strcpy(cliip, "0.0.0.0");
}
else
{
sprintf(cliip, "%s[%d]",inet_ntoa(cliAddr.sin_addr),cliAddr.sin_port);
}
return std::string(cliip);
}
void CServSocket::Send(char *sendbuf, int len) //发送消息
{
if(sendbuf==NULL || len < 0)
{
std::cout<<"read stream error\n";
return;
}
for(int i=0;i<MAX_COUNT;++i)
{
if(client[i] < 0)
{
continue;
}
int dataleft = len, total = 0, ret =0;
while(total < len)
{
pthread_mutex_lock(&mutex);//多线程互斥锁
ret = ::send(client[i], sendbuf+total, dataleft, 0);
pthread_mutex_unlock(&mutex);
if(ret < 0)
{
return;
}
total += ret;
dataleft = len-total;
}
if(total != len)
{
printf("client(%d) send error\n",i);
}
}
}
void CServSocket::Recv() //接收来自客户端的消息
{
char recvbuf[BUF_SIZE];
int recvlen;
for(int i=0;i<MAX_COUNT;++i)
{
if(client[i] < 0)
{
continue;
}
if(FD_ISSET(client[i],&server_set))
{
HEAD head;
memset(recvbuf, 0, sizeof(recvbuf));
recvlen = ::recv(client[i], recvbuf, sizeof(head), 0);
if(recvlen > 0)
{
memset(&head,0,sizeof(head));
memcpy(&head,recvbuf,sizeof(head));
if(head.type == HEART)//如果是心跳包,计数归零
{
mmap.find(client[i])->second = 0;
continue;
}
else //如果是数据包,接收显示
{
memset(recvbuf, 0, sizeof(recvbuf));
recvlen = ::recv(client[i], recvbuf, head.length, 0);
if(recvlen != head.length)
{
printf("recv error\n");
}
else
{
printf("%s:%s\n",getCliAddr(client[i]).c_str(),recvbuf);
}
continue;
}
}
}
else //如果没有来自客户端的消息,心跳计数+1,达到5则认为客户端已死,断开
{
mmap.find(client[i])->second +=1;
if(mmap.find(client[i])->second < 5)
continue;
}
pthread_mutex_lock(&mutex);//多线程互斥锁
::close(client[i]);
FD_CLR(client[i],&server_set);
mmap.erase(mmap.find(client[i]));
client[i] = -1;
printf("client(%d) exit!\n",i);
pthread_mutex_unlock(&mutex);
}
}
主函数:
#include "V4l2.h"
#include "Server.h"
void* GrabThread(void*);
V4l2 myV4l2;
CServSocket servSocket;
int main()
{
signal(SIGPIPE,SIG_IGN); //忽略SIGPIPE,防止异常中断进程
myV4l2.CheckDev();
myV4l2.InitDev();
myV4l2.StartCapture();
pthread_t ptr;
if(pthread_create(&ptr,NULL,GrabThread,NULL) != 0)//创建读取视频帧线程
{
std::cout<<"create video thread failed!\n";
exit(1);
}
if(!servSocket.Listen(PORT))//启动socket并监听端口
{
std::cout<<"socket start failed!\n";
exit(1);
}
std::cout<<"dev init complete, waiting for connect...\n";
servSocket.ServerSelect(); //执行select
return 0;
}
void* GrabThread(void* arg)
{
int length;
char img_buf[BUFFER_SIZE];
while(1)
{
length = 0;
bzero(img_buf, BUFFER_SIZE);
myV4l2.ReadStream(img_buf,&length); //读取视频数据
//发送数据
servSocket.Send((char*)&length,sizeof(int));
servSocket.Send(img_buf,sizeof(char)*length);
}
return NULL;
}
Windows客户端
MFC对话框头文件:
// ClientDlg.h: 头文件
//
#pragma once
#include "afxcmn.h"
// CClientDlg 对话框
class CClientDlg : public CDialog
{
// 构造
public:
CClientDlg(CWnd* pParent = NULL); // 标准构造函数
~CClientDlg();
// 对话框数据
#ifdef AFX_DESIGN_TIME
enum { IDD = IDD_CLIENT_DIALOG };
#endif
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持
#define BUFFER_SIZE 1000000
// 实现
protected:
HICON m_hIcon;
// 生成的消息映射函数
virtual BOOL OnInitDialog();
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
DECLARE_MESSAGE_MAP()
private:
typedef struct DATA
{
int length;
char RecvBuffer[BUFFER_SIZE];
}IMG_DATA;
enum Type { HEART, OTHER };
typedef struct PACKET_HEAD
{
Type type;
int length;
}HEAD;
IMG_DATA img_data; //图像数据
CIPAddressCtrl ip_address;//IP地址变量
CRect m_rect; //记录对话框位置
BOOL con_flag = FALSE; //标记按钮状态
SOCKET ClientSocket; //声明socket
CWinThread *plotThread = NULL; //声明线程
static UINT PlotImageThread(LPVOID param);//声明线程函数
CWinThread *heartThread = NULL; //声明线程
static UINT HeartBeatThread(LPVOID param);//声明线程函数
CRITICAL_SECTION g_cs;//临界变量
public:
afx_msg void OnBnClickedConnect();
afx_msg void OnSize(UINT nType, int cx, int cy);
BOOL ConnectSocket();
BOOL SendMsg(const char *msg);
BOOL ReceiveImg();
int Recv(char *recv_buf, int len);
BOOL DrawImage();
virtual void OnCancel();
virtual void OnOK();
afx_msg void OnClose();
};
MFC对话框源文件:
// ClientDlg.cpp: 实现文件
//
#include "stdafx.h"
#include "Client.h"
#include "ClientDlg.h"
#include "afxdialogex.h"
#include "MoveDlg.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// 用于应用程序“关于”菜单项的 CAboutDlg 对话框
class CAboutDlg : public CDialogEx
{
public:
CAboutDlg();
// 对话框数据
#ifdef AFX_DESIGN_TIME
enum { IDD = IDD_ABOUTBOX };
#endif
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持
// 实现
protected:
DECLARE_MESSAGE_MAP()
};
CAboutDlg::CAboutDlg() : CDialogEx(IDD_ABOUTBOX)
{
}
void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
}
BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx)
END_MESSAGE_MAP()
// CClientDlg 对话框
CClientDlg::CClientDlg(CWnd* pParent /*=NULL*/)
: CDialog(IDD_CLIENT_DIALOG, pParent)
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
InitializeCriticalSection(&g_cs);//初始化临界区
}
CClientDlg::~CClientDlg()
{
DeleteCriticalSection(&g_cs);//删除临界区
}
void CClientDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
DDX_Control(pDX, IDC_IPADDRESS1, ip_address);
}
BEGIN_MESSAGE_MAP(CClientDlg, CDialog)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_BN_CLICKED(IDC_CONNECT, &CClientDlg::OnBnClickedConnect)
ON_WM_SIZE()
ON_WM_CLOSE()
END_MESSAGE_MAP()
// CClientDlg 消息处理程序
BOOL CClientDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// 将“关于...”菜单项添加到系统菜单中。
// IDM_ABOUTBOX 必须在系统命令范围内。
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != NULL)
{
BOOL bNameValid;
CString strAboutMenu;
bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
ASSERT(bNameValid);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
}
// 设置此对话框的图标。 当应用程序主窗口不是对话框时,框架将自动
// 执行此操作
SetIcon(m_hIcon, TRUE); // 设置大图标
SetIcon(m_hIcon, FALSE); // 设置小图标
// TODO: 在此添加额外的初始化代码
GetClientRect(&m_rect); //获取对话框的大小
const char * strIP = "192.168.3.71";
DWORD dwIP;
inet_pton(AF_INET, strIP, &dwIP);
ip_address.SetAddress(ntohl(dwIP));
SetDlgItemTextW(IDC_EDIT1, _T("8888"));
return TRUE; // 除非将焦点设置到控件,否则返回 TRUE
}
void CClientDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
if ((nID & 0xFFF0) == IDM_ABOUTBOX)
{
CAboutDlg dlgAbout;
dlgAbout.DoModal();
}
else
{
CDialog::OnSysCommand(nID, lParam);
}
}
// 如果向对话框添加最小化按钮,则需要下面的代码
// 来绘制该图标。 对于使用文档/视图模型的 MFC 应用程序,
// 这将由框架自动完成。
void CClientDlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this); // 用于绘制的设备上下文
SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
// 使图标在工作区矩形中居中
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
// 绘制图标
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialog::OnPaint();
}
}
//当用户拖动最小化窗口时系统调用此函数取得光标
//显示。
HCURSOR CClientDlg::OnQueryDragIcon()
{
return static_cast<HCURSOR>(m_hIcon);
}
void CClientDlg::OnBnClickedConnect()
{
// TODO: 在此添加控件通知处理程序代码
if (!con_flag)
{
if (!ConnectSocket())
{
MessageBox(_T("Socket连接出错!"));
return;
}
if (!SendMsg("hello"))
{
MessageBox(_T("发送消息出错!"));
return;
}
plotThread = ::AfxBeginThread(PlotImageThread, this, THREAD_PRIORITY_NORMAL, 0, 0, NULL);//绘图线程
if (plotThread == NULL)
{
MessageBox(_T("线程创建失败!"));
return;
}
heartThread = ::AfxBeginThread(HeartBeatThread, this, THREAD_PRIORITY_NORMAL, 0, 0, NULL);//心跳线程
if (heartThread == NULL)
{
MessageBox(_T("线程创建失败!"));
return;
}
con_flag = TRUE;
GetDlgItem(IDC_CONNECT)->SetWindowTextW(_T("断开连接"));
}
else
{
::shutdown(ClientSocket, SD_SEND);
WaitForSingleObject(plotThread->m_hThread, INFINITE);//等待绘图线程结束
EnterCriticalSection(&g_cs);//防止心跳线程不知道已关闭socket
::closesocket(ClientSocket);
ClientSocket = NULL;
LeaveCriticalSection(&g_cs);
GetDlgItem(IDC_CONNECT)->EnableWindow(FALSE);
GetDlgItem(IDC_CONNECT)->SetWindowTextW(_T("正在断开,请稍等"));
WaitForSingleObject(heartThread->m_hThread, INFINITE);//等待心跳线程结束
con_flag = FALSE;
GetDlgItem(IDC_CONNECT)->SetWindowTextW(_T("连接"));
GetDlgItem(IDC_CONNECT)->EnableWindow(TRUE);
Invalidate();
}
}
void CClientDlg::OnSize(UINT nType, int cx, int cy)
{
CDialog::OnSize(nType, cx, cy);
// TODO: 在此处添加消息处理程序代码
Invalidate();
MoveDlg::OnSize(this, m_rect, nType, cx, cy);
}
BOOL CClientDlg::ConnectSocket()
{
/* Create Socket */
ClientSocket = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (ClientSocket == INVALID_SOCKET)
{
MessageBox(_T("Socket创建失败!"));
return FALSE;
}
struct sockaddr_in ClientAddr;
ClientAddr.sin_family = AF_INET;
BYTE f1, f2, f3, f4;
ip_address.GetAddress(f1, f2, f3, f4);
char ip[20];
sprintf_s(ip, 20, "%d.%d.%d.%d", f1, f2, f3, f4);
inet_pton(AF_INET, ip, &ClientAddr.sin_addr);
CString port;
GetDlgItem(IDC_EDIT1)->GetWindowTextW(port);
ClientAddr.sin_port = htons(_ttoi(port));
memset(ClientAddr.sin_zero, 0x00, 8);
/* connect socket */
if (::connect(ClientSocket, (struct sockaddr*)&ClientAddr, sizeof(ClientAddr)) == SOCKET_ERROR)
{
MessageBox(_T("请求连接出错!"));
return FALSE;
}
return TRUE;
}
BOOL CClientDlg::SendMsg(const char * msg)
{
HEAD head;
char buf[10];
head.type = OTHER;
head.length = (int)strlen(msg);
memset(buf, 0, 10);
memcpy(buf, &head, sizeof(head));
//MessageBox(_T("测试1!"));
EnterCriticalSection(&g_cs);//锁住连续发两个包,防止插入心跳包,对端接收混乱
//MessageBox(_T("测试2!"));
if (::send(ClientSocket, buf, sizeof(head), 0) == SOCKET_ERROR)
{
return FALSE;
}
if (::send(ClientSocket, msg, head.length, 0) == SOCKET_ERROR)
{
return FALSE;
}
LeaveCriticalSection(&g_cs);
return TRUE;
}
BOOL CClientDlg::ReceiveImg()
{
int iResult = 0;
int lenReaded = 0;
int lenLeaved = 0;
img_data.length = 0;
iResult = Recv((char*)&img_data.length, sizeof(int));
if (iResult <= 0)
{
return FALSE;
}
memset(img_data.RecvBuffer, 0, BUFFER_SIZE);
lenLeaved = img_data.length;
while (lenReaded < img_data.length)
{
iResult = Recv(img_data.RecvBuffer + lenReaded, lenLeaved);
if (iResult <= 0)
{
return FALSE;
}
lenReaded += iResult;
lenLeaved = img_data.length - lenReaded;
}
return TRUE;
}
int CClientDlg::Recv(char * recv_buf, int len)
{
if (ClientSocket == INVALID_SOCKET || recv_buf == NULL)
{
return -1;
}
return ::recv(ClientSocket, recv_buf, len, 0);
}
BOOL CClientDlg::DrawImage()
{
if (img_data.length <= 0)
{
return FALSE;
}
HGLOBAL hImageData = ::GlobalAlloc(GMEM_MOVEABLE, img_data.length);//创建全局内存对象
if (hImageData == NULL)
{
return FALSE;
}
BYTE* pImgData = (BYTE*)::GlobalLock(hImageData);
if (pImgData == NULL)
{
::GlobalFree(hImageData);
return FALSE;
}
::memcpy(pImgData, img_data.RecvBuffer, img_data.length);//进行内存拷贝
::GlobalUnlock(hImageData);
IStream *pStmImg = NULL;
if (::CreateStreamOnHGlobal(hImageData, FALSE, &pStmImg) != S_OK)//创建数据流
{
pStmImg->Release();
::GlobalFree(hImageData);
return FALSE;
}
Image *image = Image::FromStream(pStmImg);//通过流获得图像对象
pStmImg->Release();
CWnd* pwnd = GetDlgItem(IDC_DISPLAY);//IDC_DISPLAY 为图像控件的 ID
CDC* dc = pwnd->GetDC(); //获取图像控件的设备上下文
CRect rect;
pwnd->GetClientRect(&rect); //获取客户区域的信息
Graphics graph(dc->GetSafeHdc());
graph.DrawImage(image, 0, 0, rect.Width(), rect.Height()); //在指定图像控件的区域中绘制图像
ReleaseDC(dc); //释放资源
::GlobalFree(hImageData);
return TRUE;
}
UINT CClientDlg::PlotImageThread(LPVOID param)//绘图线程
{
CClientDlg *myDlg = (CClientDlg*)param;
myDlg->Invalidate();
while (1)
{
if (!myDlg->ReceiveImg())
{
break;
}
if (!myDlg->DrawImage())
{
break;
}
}
return 0;
}
UINT CClientDlg::HeartBeatThread(LPVOID param)//心跳线程
{
CClientDlg *myDlg = (CClientDlg*)param;
HEAD head;
char buf[10];
while (1)
{
head.type = HEART;
head.length = 0;
memset(buf, 0, 10);
memcpy(buf, &head, sizeof(head));
EnterCriticalSection(&myDlg->g_cs);
if (myDlg->ClientSocket == NULL)
{
break;
}
if (::send(myDlg->ClientSocket, buf, sizeof(head), 0) == SOCKET_ERROR)
{
myDlg->MessageBox(_T("发送心跳包出错!"));
break;
}
LeaveCriticalSection(&myDlg->g_cs);
Sleep(3000);
}
LeaveCriticalSection(&myDlg->g_cs);
return 0;
}
void CClientDlg::OnClose()
{
CDialog::OnCancel();
}
void CClientDlg::OnCancel(){}
void CClientDlg::OnOK(){}
MFC窗口大小拖动头文件:
#pragma once //MoveDlg.h
class MoveDlg
{
public:
MoveDlg();
~MoveDlg();
static void OnSize(const CDialog *dlg, CRect &rect, UINT nType, int cx, int cy);
static void ChangeSize(const CDialog *dlg, CRect &rect, UINT nID, int x, int y);
};
MFC窗口大小拖动源文件:
#include "stdafx.h"
#include "MoveDlg.h"
MoveDlg::MoveDlg(){}
MoveDlg::~MoveDlg(){}
void MoveDlg::OnSize(const CDialog * dlg, CRect &rect, UINT nType, int cx, int cy)
{
if (nType != SIZE_MINIMIZED) //判断窗口是不是最小化了,因为窗口最小化之后 ,窗口的长和宽会变成0,当前一次变化的时就会出现除以0的错误操作
{
CWnd* pChildWnd = dlg->GetWindow(GW_CHILD);
UINT nCtrlID = 0;
while (pChildWnd != NULL) //遍历对每个控件做改变
{
nCtrlID = pChildWnd->GetDlgCtrlID();
ChangeSize(dlg, rect, nCtrlID, cx, cy);
pChildWnd = pChildWnd->GetWindow(GW_HWNDNEXT);
}
dlg->GetClientRect(&rect); //最后要更新对话框的大小,当做下一次变化的旧坐标
}
}
void MoveDlg::ChangeSize(const CDialog *dlg, CRect &m_rect, UINT nID, int x, int y)//nID为控件ID,x, y分别为对话框的当前长和宽
{
CWnd *pWnd = dlg->GetDlgItem(nID);
if (pWnd != NULL) //判断是否为空,因为在窗口创建的时候也会调用OnSize函数,但是此时各个控件还没有创建,Pwnd为空
{
CRect rec;
pWnd->GetWindowRect(&rec); //获取控件变化前的大小
dlg->ScreenToClient(&rec); //将控件大小装换位在对话框中的区域坐标
rec.left = rec.left*x / m_rect.Width(); //按照比例调整空间的新位置
rec.top = rec.top*y / m_rect.Height();
rec.bottom = rec.bottom*y / m_rect.Height();
rec.right = rec.right*x / m_rect.Width();
pWnd->MoveWindow(rec); //伸缩控件
}
}
全部代码下载链接:全部源码