[WTL/Win32]_[中级]_[MVP架构在实际项目中的应用]
场景
- 在开发
Windows
和macOS
的界面软件时,Windows
用的是WTL/Win32
技术,而macOS
用的是Cocoa
技术。而两种技术的本地语言一个主打是C++
,另一个却是Object-c
。界面软件的源码随着项目功能增多而增多,这就会给同步Windows
和macOS
的功能造成很大负担。 有大部分的调用底层逻辑,界面数据搜索等代码逻辑会重复写两遍,用C++
和Object-C
写。初步估计这部分代码至少占界面代码的50%
。这无疑会影响产品发布速度,影响软件质量(可能有些逻辑两个平台不能完全一样),增大了开发的工作量,这部分时间无疑是浪费了的。有什么办法可以把这部分代码进行重用?
说明
-
界面代码往往就是获取数据,显示数据,之后点击按钮处理数据。这就需要获取数据,处理数据需要和平台相关的界面代码剥离,显示数据部分依赖平台的框架进行数据绘制。 按照这种逻辑,最好的办法就是数据获取和处理使用
C++
语言处理,处理这些和界面无关的逻辑代码。 当然如果有特殊情况也可以用.mm
文件(这种是Object-C
和C++
混编的文件后缀)来调用Object-C
平台接口处理。比如presenter_mac.mm
和presenter_win.cpp
。 -
界面架构
MVP
架构可以满足这个要求。当然这和Object-c
和C++
可以混编有些关系,如果是Swift
语言,不能直接调用C++
类,需要通过桥接调用Object-C
,再通过Object-C
调用C++
来处理。 使用C++
处理跨平台逻辑,最好使用C++11
以上标准,因为这个标准多了很多有用的库和特性节省MVP
架构的很多代码,如lambda
,thread
,functional
等。 -
看看
MVP
架构的分层,主要是以下三层。很好理解,Presenter作为View
和Model
的通讯层,起到了连接视图和底层模型逻辑处理的作用,也起到了跨平台时处理不同平台界面框架获取数据的本地实现的桥梁。
View <-> Presenter <-> Model
-
这里说的
Presenter
可根据界面语言的实现进行基于本地的实现,比如macOS
下Presenter
层需要处理NSString
作为字符串存储的数据,而在Windows
下需要处理std::wstring
作为字符串处理的数据。这些数据如果传递给Model
,那么需要转换为std::string
的UTF8
编码进行处理。 -
在这个三层模型里,依赖关系需要注意设计, 切
不可以互相依赖
。View
依赖Presenter
接口,View
里有Presenter
的成员变量,Presenter
的实例需要通过方法注入。这样如果View
更换不同的Presenter
也可以通过注入的方法。View
通过成员变量presenter_
调用它的方法。异步处理通过传入std::function
绑定的方法给Presenter
,当Presenter
处理完之后再调用绑定的方法,类似回调函数的处理。
private:shared_ptr<Presenter> presenter_ = nullptr;... void CView::setPresenter(shared_ptr<Presenter> presenter) {presenter_ = presenter; }
Presenter
依赖Model
接口,注意说的接口是根据实际项目的复杂度来定义虚拟类接口,如果实现只有一个,定义一个普通类就行。Presenter
通过成员变量model_
调用它的方法。异步处理通过传入std::function
绑定的方法给Model
,当Model
处理完之后再调用绑定的方法,类似回调函数的处理。
protected:shared_ptr<Model> model_;... void Presenter::setModel(shared_ptr<Model> model) {model_ = model; }
例子
- 以下的虚拟
listview
是MVP
架构的实现。
View.h
// View.h : interface of the CView class
//
/#pragma once#include <utility>
#include <string>
#include <vector>
#include <memory>
#include <atlmisc.h>
#include <atlctrls.h>
#include <atlctrlx.h>
#include <GdiPlus.h>using namespace std;class Presenter;enum
{kMyButtonId = WM_USER+1,kMyButtonId2,kMyButtonId3,kMyListViewId
};class CView : public CWindowImpl<CView>
{
public:DECLARE_WND_CLASS(NULL)BOOL PreTranslateMessage(MSG* pMsg);BEGIN_MSG_MAP_EX(CView)MSG_WM_CREATE(OnCreate)MESSAGE_HANDLER(WM_PAINT, OnPaint)NOTIFY_HANDLER(kMyListViewId,NM_CLICK,OnNMClickListResult)NOTIFY_HANDLER(kMyListViewId,LVN_GETDISPINFO,OnGetListViewData)NOTIFY_HANDLER(kMyListViewId,LVN_ODCACHEHINT,OnPrepareListViewData)NOTIFY_HANDLER(kMyListViewId,LVN_ODFINDITEM,OnFindListViewData)COMMAND_RANGE_HANDLER_EX(kMyButtonId,kMyButtonId3,OnCommandIDHandlerEX)REFLECT_NOTIFICATIONS()END_MSG_MAP()void setPresenter(shared_ptr<Presenter> presenter);protected:
// Handler prototypes (uncomment arguments if needed):
// LRESULT MessageHandler(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
// LRESULT CommandHandler(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
// LRESULT NotifyHandler(int /*idCtrl*/, LPNMHDR /*pnmh*/, BOOL& /*bHandled*/)int OnCreate(LPCREATESTRUCT lpCreateStruct);LRESULT OnPaint(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/);void UpdateLayout();LRESULT OnNMClickListResult(int idCtrl,LPNMHDR pnmh,BOOL &bHandled);LRESULT OnGetListViewData(int idCtrl,LPNMHDR pnmh,BOOL &bHandled);LRESULT OnPrepareListViewData(int idCtrl,LPNMHDR pnmh,BOOL &bHandled);LRESULT OnFindListViewData(int idCtrl,LPNMHDR pnmh,BOOL &bHandled);void OnCommandIDHandlerEX(UINT uNotifyCode, int nID, CWindow wndCtl);void ReloadMockData();void ReloadListView();private:std::wstring GetControlText(HWND hwnd,wchar_t* buf = NULL);CListViewCtrl listview_;CFont font_normal_;CFont font_bold_;CBrushHandle brush_white_;CBrushHandle brush_hollow_;CBrush brush_red_;CButton buttonReloadMockData_;CButton buttonReloadListView_;CButton buttonDeleteListViewOneRow_;private:shared_ptr<Presenter> presenter_ = nullptr;
};
View.cpp
// View.cpp : implementation of the CView class
//
/#include "stdafx.h"
#include "resource.h"
#include <utility>
#include <sstream>
#include <stdint.h>
#include <assert.h>
#include <Strsafe.h>#include "View.h"
#include <CommCtrl.h>
#include <string>
#include <regex>
#include "Presenter.h"
#include "Photo.h"using namespace std;BOOL CView::PreTranslateMessage(MSG* pMsg)
{return FALSE;
}LRESULT CView::OnPaint(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{CPaintDC dc(m_hWnd);CMemoryDC mdc(dc,dc.m_ps.rcPaint);CRect rect_client;GetClientRect(&rect_client);mdc.FillSolidRect(rect_client,RGB(255,255,255));//TODO: Add your drawing code herereturn 0;
}static HFONT GetFont(int pixel,bool bold,const wchar_t* font_name)
{LOGFONT lf; memset(&lf, 0, sizeof(LOGFONT)); // zero out structure lf.lfHeight = pixel; // request a 8-pixel-height fontif(bold){lf.lfWeight = FW_BOLD; }lstrcpy(lf.lfFaceName, font_name); // request a face name "Arial"HFONT font = ::CreateFontIndirect(&lf);return font;
}std::wstring CView::GetControlText(HWND hwnd,wchar_t* buf)
{auto length = ::GetWindowTextLength(hwnd);bool bufNull = false;if(!buf){buf = new wchar_t[length+1]();bufNull = true;}::GetWindowText(hwnd,buf,length+1);std::wstring str(buf);if(bufNull)delete []buf;return str;
}static std::wstring GetProductBinDir()
{static wchar_t szbuf[MAX_PATH]; GetModuleFileName(NULL,szbuf,MAX_PATH); PathRemoveFileSpec(szbuf);int length = lstrlen(szbuf);szbuf[length] = L'\\';szbuf[length+1] = 0;return std::wstring(szbuf);
}LRESULT CView::OnGetListViewData(int idCtrl,LPNMHDR pnmh,BOOL &bHandled)
{NMLVDISPINFO* plvdi = (NMLVDISPINFO*) pnmh;auto iItem = plvdi->item.iItem;if (-1 == iItem)return 0;auto count = presenter_->getPhotoCount();if(count <= iItem)return 0;auto photo = presenter_->getPhoto(iItem);if(plvdi->item.mask & LVIF_TEXT){switch(plvdi->item.iSubItem){case 0:StringCchCopy(plvdi->item.pszText, plvdi->item.cchTextMax, to_wstring((int64_t)iItem+1).c_str());break;case 1:StringCchCopy(plvdi->item.pszText, plvdi->item.cchTextMax, photo->name.c_str());break;case 2:StringCchCopy(plvdi->item.pszText, plvdi->item.cchTextMax, photo->format.c_str());break;case 3:StringCchCopy(plvdi->item.pszText, plvdi->item.cchTextMax, photo->createDate.c_str());break;}}return 0;
}LRESULT CView::OnPrepareListViewData(int idCtrl,LPNMHDR pnmh,BOOL &bHandled)
{return 0;
}LRESULT CView::OnFindListViewData(int idCtrl,LPNMHDR pnmh,BOOL &bHandled)
{ return 0;
}LRESULT CView::OnNMClickListResult(int idCtrl,LPNMHDR pnmh,BOOL &bHandled)
{return 0;
}void CView::OnCommandIDHandlerEX(UINT uNotifyCode, int nID, CWindow wndCtl)
{switch(nID){case kMyButtonId:{ReloadMockData();MessageBox(L"刷新模拟数据完成");break;}case kMyButtonId2:{ReloadListView();MessageBox(L"重新加载表格数据完成");break;}case kMyButtonId3:{int iItem = -1;while((iItem = listview_.GetNextItem(iItem,LVNI_SELECTED)) != -1){presenter_->removePhoto(iItem);listview_.DeleteItem(iItem);iItem--;}MessageBox(L"已删除");break;}}
}void CView::ReloadListView()
{listview_.SetItemCount(0);presenter_->clearPhotos();
}void CView::ReloadMockData()
{presenter_->loadPhotos([this]() {listview_.SetItemCount(presenter_->getPhotoCount());});}int CView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{font_normal_ = ::GetFont(16,false,L"Arial");font_bold_ = ::GetFont(16,true,L"Arial");brush_hollow_ = AtlGetStockBrush(HOLLOW_BRUSH);brush_white_ = AtlGetStockBrush(WHITE_BRUSH);brush_red_.CreateSolidBrush(RGB(255,0,0));// 1.创建CListViewCtrllistview_.Create(m_hWnd,0,NULL,WS_CHILD | WS_TABSTOP |WS_VISIBLE|LVS_ALIGNLEFT|LVS_REPORT|LVS_SHOWSELALWAYS|WS_BORDER|LVS_OWNERDATA,0,kMyListViewId);listview_.SetExtendedListViewStyle(LVS_EX_FULLROWSELECT|LVS_EX_GRIDLINES|LVS_EX_DOUBLEBUFFER);listview_.SetFont(font_normal_);auto header = listview_.GetHeader();header.SetFont(font_bold_);listview_.SetBkColor(RGB(255,255,255));listview_.InsertColumn(0,L"No.",LVCFMT_LEFT,40);listview_.InsertColumn(1,L"Name",LVCFMT_LEFT,100);listview_.InsertColumn(2,L"Format",LVCFMT_LEFT,100);listview_.InsertColumn(3,L"Create Date",LVCFMT_LEFT,100);// 2.创建按钮buttonReloadMockData_.Create(m_hWnd,0,L"加载新数据",WS_CHILD|WS_VISIBLE,0,kMyButtonId);buttonReloadMockData_.SetFont(font_normal_);buttonReloadListView_.Create(m_hWnd,0,L"刷新表格",WS_CHILD|WS_VISIBLE,0,kMyButtonId2);buttonReloadListView_.SetFont(font_normal_);buttonDeleteListViewOneRow_.Create(m_hWnd,0,L"删除选中行",WS_CHILD|WS_VISIBLE,0,kMyButtonId3);buttonDeleteListViewOneRow_.SetFont(font_normal_);UpdateLayout();return 0;
}void CView::UpdateLayout()
{CRect rect;GetClientRect(&rect);CClientDC dc(m_hWnd);dc.SelectFont(font_normal_);CSize size_control(500,300);CRect rect_control = CRect(CPoint(20,20),size_control);listview_.MoveWindow(rect_control);CSize size_button;buttonReloadMockData_.GetIdealSize(&size_button);rect_control = CRect(CPoint(rect_control.left,rect_control.bottom+10),size_button);buttonReloadMockData_.MoveWindow(rect_control);CSize sizeButton2;buttonReloadListView_.GetIdealSize(&sizeButton2);rect_control = CRect(CPoint(rect_control.right+10,rect_control.top),sizeButton2);buttonReloadListView_.MoveWindow(rect_control);CSize sizeButton3;buttonDeleteListViewOneRow_.GetIdealSize(&sizeButton3);rect_control = CRect(CPoint(rect_control.right+10,rect_control.top),sizeButton3);buttonDeleteListViewOneRow_.MoveWindow(rect_control);
}void CView::setPresenter(shared_ptr<Presenter> presenter)
{presenter_ = presenter;
}
Presenter.h
#ifndef PRESENTER_H
#define PRESENTER_H#include <vector>
#include <memory>
#include <functional>using namespace std;class Photo;
class Model;typedef function<void()> FuncSimple;class Presenter
{public:shared_ptr<Photo> getPhoto(int i);void clearPhotos();size_t getPhotoCount();void removePhoto(int nItem);void loadPhotos(FuncSimple func);void setModel(shared_ptr<Model> model);protected:vector<shared_ptr<Photo>> photos_;protected:shared_ptr<Model> model_;FuncSimple funcLoadFinish_;
};#endif // !PRESENTER_H
Presenter.cpp
#include "stdafx.h"
#include "Presenter.h"
#include "Photo.h"
#include "Model.h"
#include "dispatch_queue.h"shared_ptr<Photo> Presenter::getPhoto(int i)
{return (i < photos_.size()) ? photos_[i] : nullptr;
}void Presenter::clearPhotos()
{photos_.clear();
}size_t Presenter::getPhotoCount()
{return photos_.size();
}void Presenter::removePhoto(int nItem)
{if (nItem < getPhotoCount()) {auto ite = photos_.begin() + nItem;photos_.erase(ite);}
}void Presenter::loadPhotos(FuncSimple func)
{funcLoadFinish_ = func;model_->loadPhoto([this](vector<shared_ptr<Photo>>* photos) {// 需要把数据发送到主线程更新,这样才不会出现多线程访问共享数据冲突。DispatchQueue::DispatchAsync(DispatchQueue::DispatchGetMainQueue(), new FuncSimple([this, photos]() {photos_.insert(photos_.end(), photos->begin(), photos->end());if (funcLoadFinish_)funcLoadFinish_();delete photos;}));});
}void Presenter::setModel(shared_ptr<Model> model)
{model_ = model;
}
Model.h
#ifndef MODEL_H
#define MODEL_H#include <functional>
#include <memory>
#include <vector>
#include "Photo.h"using namespace std;typedef function<void(vector<shared_ptr<Photo>>*)> FuncLoadPhoto;class Model
{
public:void loadPhoto(FuncLoadPhoto func);protected:FuncLoadPhoto funcLoadPhoto_;
};#endif
Model.cpp
#include "stdafx.h"
#include "Model.h"
#include <thread>
#include "Photo.h"using namespace std;void Model::loadPhoto(FuncLoadPhoto func)
{funcLoadPhoto_ = func;// 模拟异步加载数据thread t1([this]() {wchar_t buf[MAX_PATH] = { 0 };int index = 0;auto photos = new vector<shared_ptr<Photo>>();for (int i = 0; i < 10000; ++i, ++index) {auto photo = new Photo();wsprintf(buf, L"Name-%d", index);photo->name = buf;wsprintf(buf, L"Format-%d", index);photo->format = buf;wsprintf(buf, L"createDate-%d", index);photo->createDate = buf;photos->push_back(move(shared_ptr<Photo>(photo)));}if (funcLoadPhoto_)funcLoadPhoto_(photos);});t1.detach();
}
dispatch_queue.h
#ifndef __DISPATCH_QUEUE_H
#define __DISPATCH_QUEUE_H#include <Windows.h>
#include <WinUser.h>
#include <functional>enum
{WMC_DISPATCH_MAIN_QUEUE = WM_USER+1000
};typedef struct DispatchQueueObject1
{DWORD threadId;HWND m_hwnd;UINT msg;
}DispatchQueueObject;class DispatchQueue
{
public:static DWORD GetMainThreadId();static bool IsCurrentMainThread();static void DispatchQueueInit(HWND hwnd);static DispatchQueueObject* DispatchGetMainQueue();static void FreeDispatchMainQueue(DispatchQueueObject* dqo);static void DispatchAsync(DispatchQueueObject* queue,std::function<void()>* callback);
};#endif
dispatch_queue.cpp
#include "stdafx.h"#include "dispatch_queue.h"static HWND gMainHwnd = NULL;
static DWORD gMainThreadId = 0;bool DispatchQueue::IsCurrentMainThread()
{return GetMainThreadId() == GetCurrentThreadId();
}DWORD DispatchQueue::GetMainThreadId()
{return gMainThreadId;
}void DispatchQueue::DispatchQueueInit(HWND hwnd)
{gMainHwnd = hwnd;gMainThreadId = GetCurrentThreadId();
}void DispatchQueue::DispatchAsync(DispatchQueueObject* queue,std::function<void()>* callback)
{if(queue->threadId){::PostThreadMessage(queue->threadId,queue->msg,(WPARAM)callback,0);}else{::PostMessage(queue->m_hwnd,queue->msg,(WPARAM)callback,0);}FreeDispatchMainQueue(queue);
}DispatchQueueObject* DispatchQueue::DispatchGetMainQueue()
{DispatchQueueObject* object = (DispatchQueueObject*)malloc(sizeof(DispatchQueueObject));memset(object,0,sizeof(DispatchQueueObject));object->m_hwnd = gMainHwnd;object->msg = WMC_DISPATCH_MAIN_QUEUE;return object;
}void DispatchQueue::FreeDispatchMainQueue(DispatchQueueObject* dqo)
{free(dqo);
}
MainFrm.h
// MainFrm.h : interface of the CMainFrame class
//
/#pragma once#include "View.h"
#include "Presenter.h"
#include "Model.h"
#include "dispatch_queue.h"class CMainFrame : public CFrameWindowImpl<CMainFrame>, public CUpdateUI<CMainFrame>,public CMessageFilter, public CIdleHandler
{
public:DECLARE_FRAME_WND_CLASS(NULL, IDR_MAINFRAME)CView m_view;virtual BOOL PreTranslateMessage(MSG* pMsg);virtual BOOL OnIdle();BEGIN_UPDATE_UI_MAP(CMainFrame)END_UPDATE_UI_MAP()BEGIN_MSG_MAP(CMainFrame)MESSAGE_HANDLER(WM_CREATE, OnCreate)MESSAGE_HANDLER(WM_DESTROY, OnDestroy)COMMAND_ID_HANDLER(ID_APP_EXIT, OnFileExit)COMMAND_ID_HANDLER(ID_FILE_NEW, OnFileNew)COMMAND_ID_HANDLER(ID_APP_ABOUT, OnAppAbout)MESSAGE_HANDLER(WMC_DISPATCH_MAIN_QUEUE, OnDispatchMainQueueEvent)CHAIN_MSG_MAP(CUpdateUI<CMainFrame>)CHAIN_MSG_MAP(CFrameWindowImpl<CMainFrame>)END_MSG_MAP()// Handler prototypes (uncomment arguments if needed):
// LRESULT MessageHandler(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
// LRESULT CommandHandler(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
// LRESULT NotifyHandler(int /*idCtrl*/, LPNMHDR /*pnmh*/, BOOL& /*bHandled*/)LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/);LRESULT OnDestroy(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled);LRESULT OnFileExit(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/);LRESULT OnFileNew(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/);LRESULT OnAppAbout(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/);LRESULT OnDispatchMainQueueEvent(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled){std::function<void()>* func = (std::function<void()>*)wParam;(*func)();delete func;bHandled = TRUE;return 0;}
};
MainFrm.cpp
// MainFrm.cpp : implmentation of the CMainFrame class
//
/#include "stdafx.h"
#include "resource.h"#include "aboutdlg.h"
#include "MainFrm.h"BOOL CMainFrame::PreTranslateMessage(MSG* pMsg)
{if(CFrameWindowImpl<CMainFrame>::PreTranslateMessage(pMsg))return TRUE;return m_view.PreTranslateMessage(pMsg);
}BOOL CMainFrame::OnIdle()
{return FALSE;
}LRESULT CMainFrame::OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{// 注册线程接收异步主线程消息的窗口DispatchQueue::DispatchQueueInit(m_hWnd);auto presenter = make_shared<Presenter>();auto model = make_shared<Model>();presenter->setModel(model);m_view.setPresenter(presenter);m_hWndClient = m_view.Create(m_hWnd, rcDefault, NULL, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, WS_EX_CLIENTEDGE);// register object for message filtering and idle updatesCMessageLoop* pLoop = _Module.GetMessageLoop();ATLASSERT(pLoop != NULL);pLoop->AddMessageFilter(this);pLoop->AddIdleHandler(this);return 0;
}LRESULT CMainFrame::OnDestroy(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
{// unregister message filtering and idle updatesCMessageLoop* pLoop = _Module.GetMessageLoop();ATLASSERT(pLoop != NULL);pLoop->RemoveMessageFilter(this);pLoop->RemoveIdleHandler(this);bHandled = FALSE;return 1;
}LRESULT CMainFrame::OnFileExit(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
{PostMessage(WM_CLOSE);return 0;
}LRESULT CMainFrame::OnFileNew(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
{// TODO: add code to initialize documentreturn 0;
}LRESULT CMainFrame::OnAppAbout(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
{CAboutDlg dlg;dlg.DoModal();return 0;
}
项目下载
https://download.csdn.net/download/infoworld/89445554
注意: 关于WTL
的开发学习可以订阅我的课程:
使用WTL进行Windows桌面应用开发-第一部_在线视频教程-CSDN程序员研修院
参考
-
观察者模式在项目中实际使用例子
-
观察者模式在项目中实际使用例子2
-
QQ NT全新重构,探寻24岁QQ大重构背后的思考_跨端开发
相关文章:
[WTL/Win32]_[中级]_[MVP架构在实际项目中的应用]
场景 在开发Windows和macOS的界面软件时,Windows用的是WTL/Win32技术,而macOS用的是Cocoa技术。而两种技术的本地语言一个主打是C,另一个却是Object-c。界面软件的源码随着项目功能增多而增多,这就会给同步Windows和macOS的功能造成很大负担…...
《Windows API每日一练》5.2 按键消息
上一节中我们得知,Windows系统的按键消息有很多类型,大部分按键消息都是由Windows系统的默认窗口过程处理的,我们自己只需要处理少数几个按键消息。这一节我们将详细讲述Windows系统的所有按键消息及其处理方式。 本节必须掌握的知识点&…...
adb 截屏和录屏命令
adb 录屏命令 screenrecord 简介 screenrecord 是一个 shell 命令 支持 Android 4.4(API level 19)以上 支持视频格式: mp4 一些限制 某些设备可能无法直接录制,原因是分辨率太高,如果遇到此类问题,请试着指定较低的分辨率 不支持录制过程中屏幕旋转,如果录制…...
springboot相关的一些知识
SpringBoot可以同时处理多少请求 SpringBoot默认的内嵌容器是Tomcat,所以SpringBoot可以同时处理多少请求取决于Tomcat。 SpringBoot中处理请求数量相关的参数有四个: server.tomcat.thread.min-spare:最少的工作线程数,默认大小…...
DP:完全背包+多重背包问题
完全背包和01背包的区别就是:可以多次选 一、完全背包(模版) 【模板】完全背包_牛客题霸_牛客网 #include <iostream> #include<string.h> using namespace std; const int N1001; int n,V,w[N],v[N],dp[N][N]; //dp[i][j]表示…...
购物返利系统的安全性:防范欺诈与数据保护
购物返利系统的安全性:防范欺诈与数据保护 大家好,我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编,也是冬天不穿秋裤,天冷也要风度的程序猿! 购物返利系统作为一种电子商务模式,通过向消…...
从WebM到MP3:利用Python和wxPython提取音乐的魔法
前言 有没有遇到过这样的问题:你有一个包含多首歌曲的WebM视频文件,但你只想提取其中的每一首歌曲,并将它们保存为单独的MP3文件?这听起来可能有些复杂,但借助Python和几个强大的库,这个任务变得异常简单。…...
图片转pdf,图片转pdf在线转换,在线图片转pdf
图片转PDF,听起来似乎是一个简单的操作,但实际上,它涉及到许多细节和技巧。有时候我们需要将图片转换为PDF格式,以便于分享、打印或保存。那么,如何将图片转换成PDF呢?接下来,我将为您详细介绍几…...
SpringBoot3使用Swagger3
SpringBoot3使用Swagger3 项目中的后端接口进行简单的前端展示一、依赖引入二、快速启动1.在application.yml中配置2.或者properties文件,则配置3.启动项目访问swagger 三、使用注解标注接口Swagger配置文件Swagger 注解迁移举例五种常用ApiApiOperationApiImplicitParamApiMod…...
【51单片机基础教程】点亮led
文章目录 前言51单片机点亮LED的原理硬件部分软件部分51单片机的寄存器编程步骤proteus仿真点亮一个led 点亮多个ledproteus仿真代码 流水灯 总结 前言 单片机(Microcontroller Unit, MCU)是一种集成电路,广泛应用于各种电子产品中。作为嵌入…...
Docker之overlay2的迁移
原因 docker默认将文件及其容器放置在了系统盘的挂载区内,如果长期使用会发现系统挂载区被overlay2挤爆了,因此在一开始我们将其迁移在大容量外挂磁盘上,就可以避免系统盘被挤爆,放心使用. 具体操作 # 停止容器 systemctl stop docker# 修改容器配置,…...
CentOS中的rename命令
目录 CentOS中的rename命令基本语法使用示例注意事项安装prename CentOS中的rename命令 在CentOS系统中,rename命令通常是指util-linux包中提供的版本,它用于批量重命名文件,但与Perl版本的rename命令相比,功能较为简单ÿ…...
redis.conf 参数详解,方便进行性能优化配置
以下是redis.conf中一些常见参数的详细说明: daemonize:是否以后台进程运行,默认为no; pidfile:如以后台进程运行,则需指定一个pid,默认为/var/run/redis.pid;bind:绑定主…...
微信小程序登录流程详情及Java代码
一、流程图 说明: 调用 wx.login() 获取 临时登录凭证code ,并回传到开发者服务器。 调用 auth.code2Session 接口,换取 用户唯一标识 OpenID 和 会话密钥 session_key。 获取手机号,调用wx.getPhoneNumber() ,获取加密…...
c++qt合并两张灰度图像
需求:将两张尺寸相同的灰度图像进行合并,合并后的图像,每个像素点灰度值为两张原图对应像素点灰度值之和。若超过255,则最大为255。 方法一: 将图像读取为cv::Mat,再调用opencv的cv::add方法,进…...
Uniapp通过年月日时间转变星期格式
效果图 参靠微信小程序:日常记一记 代码 <view v-for"(d,index) in dataList" >{{getWeekDay(d.ctime)}} //时间格式:2024-06-21</view> js export default {data(){return {dataList:[],//时间数组}},onLoad() {this.loadList…...
如何编写和执行高效的测试计划
如何编写和执行高效的测试计划 1. 测试计划概述2. 测试阶段详解3. 测试计划模板4. 关键注意事项总结 1. 测试计划概述 测试计划是指导整个测试过程的重要文档,其中包含了测试策略、资源分配、进度安排以及风险评估等内容。 一个完善的测试计划应当包括以下几个主要…...
【MySQL连接器(Python)指南】03-MySQL连接器(Python)安装
文章目录 前言1. 从二进制发行版中安装连接器1.1 使用pip安装MySQL连接器1.2 使用MySQL Yum Repository安装1.3 使用Debian软件包安装连接器2. 从源代码发行版安装连接器2.1 在Windows上源码安装2.2 在类Unix系统上源码安装3. 验证连接器安装总结前言 MySQL连接器(Python),用于…...
Spring Boot组件化与参数校验
Spring Boot组件化与参数校验 Spring Boot版本选择 2.3.x版本 2.6.x版本 Spring Boot核心思想 约定大于配置,简化繁琐的配置 Spring Boot自动配置原理 SpringBootApplication: Spring Boot应用标注在某个类上说明这个类是SpringBoot的主配置类,Spr…...
实现可扩展的电商返利平台:技术选型与挑战
实现可扩展的电商返利平台:技术选型与挑战 大家好,我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编,也是冬天不穿秋裤,天冷也要风度的程序猿! 在当今数字化和电商兴盛的时代,返利平台成为…...
从0开始C++(三):构造函数与析构函数详解
目录 构造函数 构造函数的基本使用 构造函数也支持函数重载 构造函数也支持函数参数默认值 构造初始化列表 拷贝构造函数 浅拷贝和深拷贝 析构函数 总结 练习一下ヽ( ̄▽ ̄)ノ 构造函数 构造函数的基本使用 构造函数是一种特殊的成…...
行车记录仪文件夹“0字节”现象解析与恢复策略
一、行车记录仪文件夹“0字节”现象描述 行车记录仪作为现代驾驶中的必备设备,其储存的视频数据对于事故记录和取证至关重要。然而,有时车主们可能会遇到这样一个问题:行车记录仪的某个文件夹内的文件突然变成了0字节大小,无法正…...
呼叫中心系统的功能都有哪些?okcc呼叫中心pscc磐石云呼叫系统部署
当前电话营销普及到各行各业,方便快捷成了大部分企业在宣传自己公司的产品时必用的一种营销方式,但是电话营销在管理上也存在许多问题。例如:销售员与客户沟通前,未能详细了解客户的资料;多名销售员重复拨打同一个客户…...
2024.06.08校招 实习 内推 面经
绿*泡*泡VX: neituijunsir 交流*裙 ,内推/实习/校招汇总表格 1、提前批 | 中电锦江2025届提前批招聘 提前批 | 中电锦江2025届提前批招聘 2、实习 | 国电电力2025届暑期实习生计划启动! 实习 | 国电电力2025届暑期实习生计划启动&#x…...
Polyplus——转染试剂专业供应商
PolyPlus-transfection是一家专业的转染试剂研发和生产的生物技术公司,拥有20年的的转染试剂研发经验,通过创新的核酸转染解决方案支持基因和细胞治疗、生物制剂制造和生命科学研究。目前已经通过了ISO 9001: 2000质量体系认证,已经开发了一系…...
微服务架构-线上治理、线下治理与架构演进
目录 一、线上治理 1.1 概述 1.2 线上预案体系 1.2.1 概述 1.2.2 变更引起的故障 1.2.3 流量和容量变化引起的故障 1.2.4 依赖故障 1.2.5 机房、网络等硬件和环境故障 1.2.6 其他 1.2.7 故障的场景化 1.3 基于Metric的预案自动触发 1.4 治理参数动态调整 1.4.1 举例…...
网络安全:什么是SQL注入
文章目录 网络安全:什么是SQL注入引言SQL注入简介工作原理示例代码 攻击类型为什么SQL注入危险结语 网络安全:什么是SQL注入 引言 在数字化时代,数据安全成为了企业和个人最关心的问题之一。SQL注入(SQL Injection)是…...
从零开始精通Onvif之网络配置
💡 如果想阅读最新的文章,或者有技术问题需要交流和沟通,可搜索并关注微信公众号“希望睿智”。 概述 网络配置是Onvif规范中的重要组成部分,允许用户通过网络远程配置和管理设备的网络设置,比如:DHCP、IP地…...
在 macOS 上使用 Homebrew 安装和配置 Python 及 Tk 库
在 macOS 上,系统自带的 /usr/bin/python3 版本较旧,且直接升级系统自带的 Python 版本可能会影响系统稳定性。因此,推荐使用 Homebrew 来安装和管理 Python 及其相关库。本文将详细介绍如何通过 Homebrew 安装和配置 Python 3 及 Tk 库&…...
【机器学习 复习】第2章 线性回归及最大熵模型
一、概念 1.回归就是用一条曲线对数据点进行拟合,该曲线称为最佳拟合曲线,这个拟合过程称为回归。 2.一个自变量 叫 一元线性回归,大于一个自变量 叫 多元线性回归。 (1)多元回归:两个x,一个…...
做网站的好公司/企业网络营销策划案例
一、Cult3D开发软件Cult3D 开发软件可以让你在建立好的模型上增加互动效果。先看看你是否适合阅读该教程,假如你符合以下条件,该文档非常适合您: 1.正安装Cult3D开发包应用程序; 2.你正建立一个Cult3D动画; 3.你正把一…...
深圳p2p网站建设/百度一下浏览器
在LoadRunner中有两个常用函数:Web_submit_form和Web_submit_data,为什么会有两个不同却功能相似的函数?区别在哪里? 首先,从工具的角度来说,厂商推荐使用Web_submit_form函数,因为这个函数看起来更易用&a…...
电商模板网站免费/千锋教育官方网
前言:使用“宇宙最强IDE”开发项目时,都需要根据不同情况选择一个项目模板,来满足开发需求:如下VS为我们提供了基础的项目模板,但现有项目模板未包含基础功能如:日志输出、审计日志、SwaggerUI、认证等&…...
戴南做网站/郑州seo优化外包顾问阿亮
高手必读 网络端口安全防护技巧放送众所周知,计算机之间通信是通过端口进行的,例如你访问一个网站时,Windows就会在本机开一个端口(例 如1025端口),然后去连接远方网站服务器的一个端口,别人访问…...
网站开发培训/有哪些免费网站可以发布广告
阿里云全云民计算优惠活动上售卖的突发性能实例293元一年,很便宜很实惠,那么性能如何?什么是突发性能实例?阿里云突发性能实例t5怎么样?值得买吗?阿里云百科来说说阿里云新推出的突发性能实例t5吧ÿ…...
关于b2c网站开发的分析/浏览器下载安装2022最新版
扫码登录。之前项目使用的是 ajax轮询的方式。感觉太low了。所以这次用webSocket的方式进行实现好。废话不多说!咱们开始!!一、首先咱们需要一张表这表是干啥的呢?就是记录一下谁扫码了。谁登录了。User_Token表字段如下ÿ…...