基于dcmtk的dicom工具 第二章 图像接受StoreSCP(1)
系列文章目录
文章目录
- 系列文章目录
- 前言
- 一、新建项目StoreSCP
- 二、对话框界面
- 1. vs2017切换到"资源视图",为对话框添加控件
- 2. 为对话框控件添加变量
- 三、代码
- 1. 变量默认值
- 2. 日志输出Edit控件背景色改为黑色
- 3. 自定义消息
- 4. addLog函数,对话框日志函数
- 5. 按钮响应函数
- 6. 工具类Utilities
前言
dicom图像接受工具StoreSCP,参考dcmtk3.6.9 storescp 源码路径:dcmnet\apps\storescp.cc。
由于后面的工具都以类似的界面出现,本章详细介绍工具的界面开发过程。
下一章介绍主业务类CStoreServer。获取界面上的 Port,Local AET,存储路径,日志级别等参数,设置到CStoreServer中,即可实现服务的启动/停止。
CStoreServer类通过三个消息与界面通信:
- 输出日志,发送消息UM_ADD_LOG
- 服务启动,发送消息UM_SERVER_START
- 服务停止,发送消息UM_SERVER_STOP
界面完成如图:
一、新建项目StoreSCP
- vs2017打开上一章的DicomTools解决方案
- 在解决方案上点击右键,弹出菜单,点击菜单"添加"->“新建项目”
- 新建MFC项目,名称StoreSCP,选择:基于对话框,在静态库中使用MFC
- 完成后解决方案下就有两个项目,上一章的DcmFileEditor,本章的StoreSCP
二、对话框界面
1. vs2017切换到"资源视图",为对话框添加控件
- Port: Edit,监听端口
- Local AET:Edit,服务应用实体名称
- 存储路径: Edit,保存接受图像的根目录
- 日志级别:ComboBox,设置日志级别
- 日志:Edit,多行,自动垂直滚动条,显示日志
- 启动服务按钮
- 选择路径,默认路径按钮
- 清空日志按钮
2. 为对话框控件添加变量
- 在对话框上选中控件,点击鼠标右键,弹出菜单,选择"添加变量"
- 在弹出的添加变量对话框中,选择变量类型、输入变量名称等
- 所有变量如下:
private:UINT m_port; // 监听端口CString m_aet; // Local AETCString m_rootDir; // 存储目录CComboBox m_cmbLL; // 日志级别CEdit m_log; // 日志输出
三、代码
1. 变量默认值
在构造函数和OnInitDialog()函数中设置变量默认值
- m_port: 3000
- m_aet: xg-storescp
- m_rootDir: 程序目录/RecvFiles
- m_cmbLL: 添加四种日志级别"调试",“通知”,“警告”,“错误”
CStoreSCPDlg::CStoreSCPDlg(CWnd* pParent /*=nullptr*/): CDialogEx(IDD_STORESCP_DIALOG, pParent), m_port(3000), m_aet(_T("xg-storescp")), m_rootDir(_T(""))
{
}BOOL CStoreSCPDlg::OnInitDialog()
{CDialogEx::OnInitDialog();...// TODO: 在此添加额外的初始化代码m_rootDir = GetAppPath().c_str();m_rootDir += _T("RecvFiles");m_cmbLL.AddString(_T("调试"));m_cmbLL.AddString(_T("通知"));m_cmbLL.AddString(_T("警告"));m_cmbLL.AddString(_T("错误"));m_cmbLL.SetCurSel(1);UpdateData(FALSE);return TRUE; // 除非将焦点设置到控件,否则返回 TRUE
}
2. 日志输出Edit控件背景色改为黑色
-
在"类向导"中为对话框添加WM_CTLCOLOR消息
-
创建黑色画刷
CStoreSCPDlg::CStoreSCPDlg(CWnd* pParent /*=nullptr*/): CDialogEx(IDD_STORESCP_DIALOG, pParent), m_port(3000), m_aet(_T("xg-storescp")), m_rootDir(_T(""))
{m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);m_brBlack.CreateSolidBrush(RGB(0, 0, 0)); // 黑色画刷
}
- 在WM_CTLCOLOR消息响应函数中添加代码
HBRUSH CStoreSCPDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{HBRUSH hbr = CDialogEx::OnCtlColor(pDC, pWnd, nCtlColor);// TODO: 在此更改 DC 的任何特性// 日志输出Eidt控件背景色改为黑色if (pWnd && pWnd->m_hWnd == m_log.GetSafeHwnd()){pDC->SetBkColor(0);pDC->SetTextColor(RGB(220, 220, 220));return m_brBlack;}// TODO: 如果默认的不是所需画笔,则返回另一个画笔return hbr;
}
效果如下:
3. 自定义消息
因为后面主业务类CStoreServer中的线程将输出日志到界面Edit中,所以采用发送消息的方式输出日志。CStoreServer类会发送三个消息到对话框中:日志,服务启动,服务停止
添加三个消息响应:
- 消息ID, 在头文件StoreSCPDlg.h中添加
#define UM_ADD_LOG WM_USER + 10
#define UM_SERVER_START WM_USER + 20
#define UM_SERVER_STOP WM_USER + 30
- 消息响应函数,在头文件StoreSCPDlg.h中添加
afx_msg LRESULT OnAddLog(WPARAM wParam, LPARAM lParam);afx_msg LRESULT OnServerStart(WPARAM wParam, LPARAM lParam);afx_msg LRESULT OnServerStop(WPARAM wParam, LPARAM lParam);
- 绑定消息,在StoreSCPDlg.cpp中添加
BEGIN_MESSAGE_MAP(CStoreSCPDlg, CDialogEx)ON_WM_SYSCOMMAND()ON_WM_PAINT()ON_WM_QUERYDRAGICON()ON_WM_CTLCOLOR()ON_BN_CLICKED(IDC_BUTTON_START, &CStoreSCPDlg::OnBnClickedButtonStart)ON_BN_CLICKED(IDC_BUTTON_DIR, &CStoreSCPDlg::OnBnClickedButtonDir)ON_BN_CLICKED(IDC_BUTTON_DEFDIR, &CStoreSCPDlg::OnBnClickedButtonDefdir)ON_BN_CLICKED(IDC_BUTTON_CLEAR_LOG, &CStoreSCPDlg::OnBnClickedButtonClearLog)ON_CBN_SELCHANGE(IDC_COMBO_LL, &CStoreSCPDlg::OnSelchangeComboLl)ON_MESSAGE(UM_ADD_LOG, &CStoreSCPDlg::OnAddLog)ON_MESSAGE(UM_SERVER_START, &CStoreSCPDlg::OnServerStart)ON_MESSAGE(UM_SERVER_STOP, &CStoreSCPDlg::OnServerStop)ON_WM_SIZE()ON_WM_GETMINMAXINFO()
END_MESSAGE_MAP()
- 三个消息响应函数
LRESULT CStoreSCPDlg::OnAddLog(WPARAM wParam, LPARAM lParam)
{CString logStr = (LPCTSTR)wParam;if (m_log.m_hWnd && IsWindow(m_log.m_hWnd)){int nLmt = m_log.GetLimitText();int nTextLength = m_log.GetWindowTextLength();if ((int)(nTextLength + logStr.GetLength()) >= nLmt){int nPos = m_log.LineIndex(100);m_log.SetSel(0, nPos);m_log.ReplaceSel(_T(""), FALSE);}m_log.SetSel(nTextLength, nTextLength);m_log.ReplaceSel(logStr);m_log.PostMessage(EM_SETSEL, -1, -1);}return 0;
}LRESULT CStoreSCPDlg::OnServerStart(WPARAM wParam, LPARAM lParam)
{CString msg;msg.Format(_T("服务已启动,监听端口[%d]"), m_port);addLog(msg);GetDlgItem(IDC_BUTTON_START)->SetWindowText(_T("停止服务"));return 0;
}LRESULT CStoreSCPDlg::OnServerStop(WPARAM wParam, LPARAM lParam)
{addLog(_T("服务已停止."));GetDlgItem(IDC_BUTTON_START)->SetWindowText(_T("启动服务"));return 0;
}
4. addLog函数,对话框日志函数
void CStoreSCPDlg::addLog(CString str)
{std::string timeStr = GetTimeStr(5);CString logStr;logStr.Format(_T("[%s][App] %s\r\n"), timeStr.c_str(), str);::SendMessage(this->m_hWnd, UM_ADD_LOG, (WPARAM)(LPCTSTR)logStr, 0);
}
5. 按钮响应函数
- 启动服务
void CStoreSCPDlg::OnBnClickedButtonStart()
{// TODO: 在此添加控件通知处理程序代码if (m_storeSCP.GetState() == 0) {m_storeSCP.Start();}else {m_storeSCP.Stop();}
}
- 选择存储路径按钮
void CStoreSCPDlg::OnBnClickedButtonDir()
{// TODO: 在此添加控件通知处理程序代码TCHAR szFolderPath[MAX_PATH] = { 0 };BROWSEINFO sInfo;::ZeroMemory(&sInfo, sizeof(BROWSEINFO));sInfo.pidlRoot = 0;sInfo.lpszTitle = _T("选择归档目录:");sInfo.ulFlags = BIF_BROWSEFORCOMPUTER | BIF_NEWDIALOGSTYLE | BIF_RETURNONLYFSDIRS;sInfo.lpfn = BrowseCallbackProc;sInfo.lParam = (LPARAM)(LPCTSTR)m_rootDir;// 显示文件夹选择对话框 LPITEMIDLIST lpidlBrowse = ::SHBrowseForFolder(&sInfo);if (lpidlBrowse != NULL){if (::SHGetPathFromIDList(lpidlBrowse, szFolderPath)){m_rootDir = szFolderPath;UpdateData(FALSE);}::CoTaskMemFree(lpidlBrowse);}
}
- 默认路径按钮
void CStoreSCPDlg::OnBnClickedButtonDefdir()
{m_rootDir = GetAppPath().c_str();m_rootDir += _T("RecvFiles");UpdateData(FALSE);
}
- 清空日志按钮
void CStoreSCPDlg::OnBnClickedButtonClearLog()
{// TODO: 在此添加控件通知处理程序代码m_log.SetSel(0, -1);m_log.ReplaceSel(_T(""), FALSE);
}
6. 工具类Utilities
头文件Utilities.h
#pragma once
#include <string>
#include <vector>std::string Replace(std::string& src, const std::string& old, const std::string& s);// 返回程序运行目录, 以"\"结尾
std::string GetAppPath();bool MakePath(const std::string dir);std::vector<std::string> Split(const std::string& str, const std::string& sep);//大写
std::string toUppercase(std::string& src);
//小写
std::string toLowercase(std::string& src);/*获取当前系统日期,时间* format = 0 20160131* format = 1 2016-01-31* format = 2 2016.01.31* format = 3 2016/01/31* format = 4 20160131 120101* format = 5 2016-01-31 12:01:01* format = 6 2016/01/31 12:01:01*/
std::string GetTimeStr(int format /*= 0*/);// 编码转换
std::wstring AtoW(const char* pszText);
std::string WtoA(const wchar_t *pwszText);
std::string WtoUTF8(const wchar_t *pwszText);
std::wstring UTF8toW(const char* pszText);
std::string AtoUTF8(const std::string &src);
std::string UTF8toA(const std::string &src);
头文件Utilities.cpp
#include "pch.h"
#include "Utilities.h"#include <io.h>
#include <direct.h>#include <algorithm>std::string Replace(std::string& src, const std::string& old, const std::string& s)
{int count = 0;const size_t ssize = s.size();const size_t osize = old.size();for (size_t pos = src.find(old, 0);pos != std::string::npos;pos = src.find(old, pos + ssize)){src.replace(pos, osize, s);count++;}return src;
}std::string GetAppPath()
{std::string strPath;char szFile[MAX_PATH] = { 0 };if (0 != GetModuleFileNameA(NULL, szFile, MAX_PATH)){strPath = szFile;size_t pos = strPath.rfind("\\");strPath = strPath.substr(0, pos);strPath += "\\";}return strPath;
}bool MakePath(const std::string dir)
{size_t pos = std::string::npos;std::string path = dir;Replace(path, "/", "\\");if (path.find('\\') == std::string::npos){if (path.length() == 2 && path.find(":") != std::string::npos)return true;elsereturn false;}if (path[path.length() - 1] == '\\')path = path.substr(0, path.length() - 1);if (_access(path.c_str(), 0) != -1)return true;pos = path.rfind("\\");std::string root = path.substr(0, pos);if (root.length() > 0 && !MakePath(root))return false;if (_mkdir(path.c_str()) != 0)return false;return true;
}std::vector<std::string> Split(const std::string& str, const std::string& sep)
{std::vector<std::string> vec;if (str.empty()){return vec;}std::string tmp;std::string::size_type pos_begin = str.find_first_not_of(sep);std::string::size_type comma_pos = 0;while (pos_begin != std::string::npos){comma_pos = str.find(sep, pos_begin);if (comma_pos != std::string::npos){tmp = str.substr(pos_begin, comma_pos - pos_begin);pos_begin = comma_pos + sep.length();}else{tmp = str.substr(pos_begin);pos_begin = comma_pos;}vec.push_back(tmp);tmp.clear();}return vec;
}std::string toUppercase(std::string& src)
{transform(src.begin(), src.end(), src.begin(), ::toupper);return src;
}std::string toLowercase(std::string& src)
{transform(src.begin(), src.end(), src.begin(), ::tolower);return src;
}std::string GetTimeStr(int format /*= 0*/)
{time_t t = time(0);struct tm pTime;localtime_s(&pTime, &t);char tmp[64] = { 0 };std::string str;switch (format){case 0:strftime(tmp, sizeof(tmp), "%Y%m%d", &pTime);break;case 1:strftime(tmp, sizeof(tmp), "%Y-%m-%d", &pTime);break;case 2:strftime(tmp, sizeof(tmp), "%Y.%m.%d", &pTime);break;case 3:strftime(tmp, sizeof(tmp), "%Y/%m/%d", &pTime);break;case 4:strftime(tmp, sizeof(tmp), "%Y%m%d %H%M%S", &pTime);break;case 5:strftime(tmp, sizeof(tmp), "%Y-%m-%d %H:%M:%S", &pTime);break;case 6:strftime(tmp, sizeof(tmp), "%Y/%m/%d %H:%M:%S", &pTime);break;default:strftime(tmp, sizeof(tmp), "%Y%m%d", &pTime);break;}str = tmp;return str;
}std::wstring AtoW(const char* pszText)
{// 空指针if (pszText == NULL) return L"";// 计算长度int nNeedSize = MultiByteToWideChar(CP_ACP, 0, pszText, -1, NULL, 0);if (0 == nNeedSize) return L"";// 分配空间,转换std::wstring strRet(L"");wchar_t *pRet = new wchar_t[nNeedSize + 1];memset(pRet, 0, (nNeedSize + 1) * sizeof(wchar_t));if (0 == MultiByteToWideChar(CP_ACP, 0, pszText, -1, pRet, nNeedSize)){}else{strRet = pRet;}delete[]pRet;return strRet;
}std::string WtoA(const wchar_t *pwszText)
{// 空指针输入if (pwszText == NULL) return "";// 无法计算需要的长度.int nNeedSize = WideCharToMultiByte(CP_ACP, 0, pwszText, -1, NULL, 0, NULL, NULL);if (0 == nNeedSize) return "";char *pRet = new char[nNeedSize];memset(pRet, 0, nNeedSize);std::string strRet("");if (0 == WideCharToMultiByte(CP_ACP, 0, pwszText, -1, pRet, nNeedSize, NULL, NULL)){}else{strRet = pRet;}delete[]pRet;return strRet;
}std::string WtoUTF8(const wchar_t *pwszText)
{// 空指针输入if (pwszText == NULL) return "";// 无法计算需要的长度.int nNeedSize = WideCharToMultiByte(CP_UTF8, 0, pwszText, -1, NULL, 0, NULL, NULL);if (0 == nNeedSize) return "";// 分配空间,转换.char *pRet = new char[nNeedSize + 1]; memset(pRet, 0, nNeedSize + 1);std::string strRet("");if (0 == WideCharToMultiByte(CP_UTF8, 0, pwszText, -1, pRet, nNeedSize, NULL, NULL)){}else{strRet = pRet;}delete[]pRet;return strRet;
}std::wstring UTF8toW(const char* pszText)
{// 空指针if (pszText == NULL) return L"";// 计算长度int nNeedSize = MultiByteToWideChar(CP_UTF8, 0, pszText, -1, NULL, 0);if (0 == nNeedSize) return L"";// 分配空间,转换std::wstring strRet(L"");wchar_t *pRet = new wchar_t[nNeedSize + 1];memset(pRet, 0, (nNeedSize + 1) * sizeof(wchar_t));if (0 == MultiByteToWideChar(CP_UTF8, 0, pszText, -1, pRet, nNeedSize)){}else{strRet = pRet;}delete[]pRet;return strRet;
}std::string AtoUTF8(const std::string &src)
{return WtoUTF8(AtoW(src.c_str()).c_str());
}std::string UTF8toA(const std::string &src)
{return WtoA(UTF8toW(src.c_str()).c_str());
}