当前位置: 首页 > news >正文

基于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类通过三个消息与界面通信:

  1. 输出日志,发送消息UM_ADD_LOG
  2. 服务启动,发送消息UM_SERVER_START
  3. 服务停止,发送消息UM_SERVER_STOP

界面完成如图:
在这里插入图片描述


一、新建项目StoreSCP

  1. vs2017打开上一章的DicomTools解决方案
  2. 在解决方案上点击右键,弹出菜单,点击菜单"添加"->“新建项目”
    在这里插入图片描述
  3. 新建MFC项目,名称StoreSCP,选择:基于对话框,在静态库中使用MFC
    在这里插入图片描述
  4. 完成后解决方案下就有两个项目,上一章的DcmFileEditor,本章的StoreSCP
    在这里插入图片描述

二、对话框界面

1. vs2017切换到"资源视图",为对话框添加控件

  • Port: Edit,监听端口
  • Local AET:Edit,服务应用实体名称
  • 存储路径: Edit,保存接受图像的根目录
  • 日志级别:ComboBox,设置日志级别
  • 日志:Edit,多行,自动垂直滚动条,显示日志
  • 启动服务按钮
  • 选择路径,默认路径按钮
  • 清空日志按钮
    在这里插入图片描述

2. 为对话框控件添加变量

  1. 在对话框上选中控件,点击鼠标右键,弹出菜单,选择"添加变量"
    在这里插入图片描述
  2. 在弹出的添加变量对话框中,选择变量类型、输入变量名称等
    在这里插入图片描述
  3. 所有变量如下:
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控件背景色改为黑色

  1. 在"类向导"中为对话框添加WM_CTLCOLOR消息

  2. 创建黑色画刷

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));    // 黑色画刷
}
  1. 在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类会发送三个消息到对话框中:日志,服务启动,服务停止
添加三个消息响应:

  1. 消息ID, 在头文件StoreSCPDlg.h中添加
#define UM_ADD_LOG      WM_USER + 10
#define UM_SERVER_START WM_USER + 20
#define UM_SERVER_STOP  WM_USER + 30
  1. 消息响应函数,在头文件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);
  1. 绑定消息,在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()
  1. 三个消息响应函数
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. 按钮响应函数

  1. 启动服务
void CStoreSCPDlg::OnBnClickedButtonStart()
{// TODO: 在此添加控件通知处理程序代码if (m_storeSCP.GetState() == 0) {m_storeSCP.Start();}else {m_storeSCP.Stop();}
}
  1. 选择存储路径按钮
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);}
}
  1. 默认路径按钮
void CStoreSCPDlg::OnBnClickedButtonDefdir()
{m_rootDir = GetAppPath().c_str();m_rootDir += _T("RecvFiles");UpdateData(FALSE);
}
  1. 清空日志按钮
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());
}
http://www.lryc.cn/news/590238.html

相关文章:

  • windows内核研究(进程与线程-等待链表和调度链表和线程切换)
  • 非控制器(如 Service、工具类)中便捷地获取当前 HTTP 请求的上下文信息
  • 16路串口光纤通信FPGA项目实现指南
  • 数据结构-1(顺序表)
  • 关于 OpenAI 的反思
  • GESP2025年6月认证C++四级( 第三部分编程题(2)排序)
  • 多态,内部类(匿名内部类),常用API(1)
  • HTTP vs HTTPS
  • 【React Native】布局文件-顶部导航栏
  • 从零开始学习 Redux:React Native 项目中的状态管理
  • 3D TOF 安全防护传感器
  • Ubuntu 上 GBase 8s 实例重启与字符集踩坑实录
  • 在UE中如何给骨骼网格体赋予动画
  • conda activate 时报错: CondaError: Run ‘conda init‘ before ‘conda activate‘
  • React Native 在 Web 前端跨平台开发中的优势与实践
  • Django ORM 查询工具对象详解
  • 基于WebRTC技术实现一个在线课堂系统
  • 线上分享:解码eVTOL安全基因,构建安全飞行生态
  • 主机安全---开源wazuh安装
  • 前端面试题(React 与 Vue)
  • Elasticsearch+Logstash+Filebeat+Kibana部署
  • [时序数据库-iotdb]时序数据库iotdb的安装部署
  • C++11 std::uninitialized_copy_n 原理与实现
  • 边缘计算革命:AWS Snowcone在智慧工厂的落地实践(2025工业4.0实战指南)
  • Jenkins+Docker(docker-compose、Dockerfile)+Gitee实现自动化部署
  • 【时序数据库-iotdb】时序数据库iotdb的可视化客户端安装部署--dbeaver
  • Datawhale AI夏令营笔记-TF-IDF方法
  • 玩转Docker | 使用Docker部署vnStat网络流量监控服务
  • java之-文件预览终极解决方案
  • java工具类Hutool