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

[WTL/Win32]_[中级]_[MVP架构在实际项目中的应用]

场景

  1. 在开发WindowsmacOS的界面软件时,Windows用的是WTL/Win32技术,而macOS用的是Cocoa技术。而两种技术的本地语言一个主打是C++,另一个却是Object-c。界面软件的源码随着项目功能增多而增多,这就会给同步WindowsmacOS的功能造成很大负担。 有大部分的调用底层逻辑,界面数据搜索等代码逻辑会重复写两遍,用C++Object-C写。初步估计这部分代码至少占界面代码的50%。这无疑会影响产品发布速度,影响软件质量(可能有些逻辑两个平台不能完全一样),增大了开发的工作量,这部分时间无疑是浪费了的。有什么办法可以把这部分代码进行重用?

说明

  1. 界面代码往往就是获取数据,显示数据,之后点击按钮处理数据。这就需要获取数据,处理数据需要和平台相关的界面代码剥离,显示数据部分依赖平台的框架进行数据绘制。 按照这种逻辑,最好的办法就是数据获取和处理使用C++语言处理,处理这些和界面无关的逻辑代码。 当然如果有特殊情况也可以用.mm文件(这种是Object-CC++混编的文件后缀)来调用Object-C平台接口处理。比如presenter_mac.mmpresenter_win.cpp

  2. 界面架构MVP架构可以满足这个要求。当然这和Object-cC++可以混编有些关系,如果是Swift语言,不能直接调用C++类,需要通过桥接调用Object-C,再通过Object-C调用C++来处理。 使用C++处理跨平台逻辑,最好使用C++11以上标准,因为这个标准多了很多有用的库和特性节省MVP架构的很多代码,如lambda,thread,functional等。

  3. 看看MVP架构的分层,主要是以下三层。很好理解,Presenter作为ViewModel的通讯层,起到了连接视图和底层模型逻辑处理的作用,也起到了跨平台时处理不同平台界面框架获取数据的本地实现的桥梁。

View <-> Presenter <-> Model
  1. 这里说的Presenter可根据界面语言的实现进行基于本地的实现,比如macOSPresenter层需要处理NSString作为字符串存储的数据,而在Windows下需要处理std::wstring作为字符串处理的数据。这些数据如果传递给Model,那么需要转换为std::stringUTF8编码进行处理。

  2. 在这个三层模型里,依赖关系需要注意设计, 切不可以互相依赖

    • 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;
    }

例子

  1. 以下的虚拟listviewMVP架构的实现。

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程序员研修院

参考

  1. 观察者模式在项目中实际使用例子

  2. 观察者模式在项目中实际使用例子2

  3. QQ NT全新重构,探寻24岁QQ大重构背后的思考_跨端开发

相关文章:

[WTL/Win32]_[中级]_[MVP架构在实际项目中的应用]

场景 在开发Windows和macOS的界面软件时&#xff0c;Windows用的是WTL/Win32技术&#xff0c;而macOS用的是Cocoa技术。而两种技术的本地语言一个主打是C,另一个却是Object-c。界面软件的源码随着项目功能增多而增多&#xff0c;这就会给同步Windows和macOS的功能造成很大负担…...

《Windows API每日一练》5.2 按键消息

上一节中我们得知&#xff0c;Windows系统的按键消息有很多类型&#xff0c;大部分按键消息都是由Windows系统的默认窗口过程处理的&#xff0c;我们自己只需要处理少数几个按键消息。这一节我们将详细讲述Windows系统的所有按键消息及其处理方式。 本节必须掌握的知识点&…...

adb 截屏和录屏命令

adb 录屏命令 screenrecord 简介 screenrecord 是一个 shell 命令 支持 Android 4.4(API level 19)以上 支持视频格式: mp4 一些限制 某些设备可能无法直接录制,原因是分辨率太高,如果遇到此类问题&#xff0c;请试着指定较低的分辨率 不支持录制过程中屏幕旋转,如果录制…...

springboot相关的一些知识

SpringBoot可以同时处理多少请求 SpringBoot默认的内嵌容器是Tomcat&#xff0c;所以SpringBoot可以同时处理多少请求取决于Tomcat。 SpringBoot中处理请求数量相关的参数有四个&#xff1a; server.tomcat.thread.min-spare&#xff1a;最少的工作线程数&#xff0c;默认大小…...

DP:完全背包+多重背包问题

完全背包和01背包的区别就是&#xff1a;可以多次选 一、完全背包&#xff08;模版&#xff09; 【模板】完全背包_牛客题霸_牛客网 #include <iostream> #include<string.h> using namespace std; const int N1001; int n,V,w[N],v[N],dp[N][N]; //dp[i][j]表示…...

购物返利系统的安全性:防范欺诈与数据保护

购物返利系统的安全性&#xff1a;防范欺诈与数据保护 大家好&#xff0c;我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01; 购物返利系统作为一种电子商务模式&#xff0c;通过向消…...

从WebM到MP3:利用Python和wxPython提取音乐的魔法

前言 有没有遇到过这样的问题&#xff1a;你有一个包含多首歌曲的WebM视频文件&#xff0c;但你只想提取其中的每一首歌曲&#xff0c;并将它们保存为单独的MP3文件&#xff1f;这听起来可能有些复杂&#xff0c;但借助Python和几个强大的库&#xff0c;这个任务变得异常简单。…...

图片转pdf,图片转pdf在线转换,在线图片转pdf

图片转PDF&#xff0c;听起来似乎是一个简单的操作&#xff0c;但实际上&#xff0c;它涉及到许多细节和技巧。有时候我们需要将图片转换为PDF格式&#xff0c;以便于分享、打印或保存。那么&#xff0c;如何将图片转换成PDF呢&#xff1f;接下来&#xff0c;我将为您详细介绍几…...

SpringBoot3使用Swagger3

SpringBoot3使用Swagger3 项目中的后端接口进行简单的前端展示一、依赖引入二、快速启动1.在application.yml中配置2.或者properties文件,则配置3.启动项目访问swagger 三、使用注解标注接口Swagger配置文件Swagger 注解迁移举例五种常用ApiApiOperationApiImplicitParamApiMod…...

【51单片机基础教程】点亮led

文章目录 前言51单片机点亮LED的原理硬件部分软件部分51单片机的寄存器编程步骤proteus仿真点亮一个led 点亮多个ledproteus仿真代码 流水灯 总结 前言 单片机&#xff08;Microcontroller Unit, MCU&#xff09;是一种集成电路&#xff0c;广泛应用于各种电子产品中。作为嵌入…...

Docker之overlay2的迁移

原因 docker默认将文件及其容器放置在了系统盘的挂载区内&#xff0c;如果长期使用会发现系统挂载区被overlay2挤爆了,因此在一开始我们将其迁移在大容量外挂磁盘上,就可以避免系统盘被挤爆,放心使用. 具体操作 # 停止容器 systemctl stop docker# 修改容器配置&#xff0c…...

CentOS中的rename命令

目录 CentOS中的rename命令基本语法使用示例注意事项安装prename CentOS中的rename命令 在CentOS系统中&#xff0c;rename命令通常是指util-linux包中提供的版本&#xff0c;它用于批量重命名文件&#xff0c;但与Perl版本的rename命令相比&#xff0c;功能较为简单&#xff…...

redis.conf 参数详解,方便进行性能优化配置

以下是redis.conf中一些常见参数的详细说明&#xff1a; daemonize&#xff1a;是否以后台进程运行&#xff0c;默认为no&#xff1b; pidfile&#xff1a;如以后台进程运行&#xff0c;则需指定一个pid&#xff0c;默认为/var/run/redis.pid&#xff1b;bind&#xff1a;绑定主…...

微信小程序登录流程详情及Java代码

一、流程图 说明&#xff1a; 调用 wx.login() 获取 临时登录凭证code &#xff0c;并回传到开发者服务器。 调用 auth.code2Session 接口&#xff0c;换取 用户唯一标识 OpenID 和 会话密钥 session_key。 获取手机号&#xff0c;调用wx.getPhoneNumber() &#xff0c;获取加密…...

c++qt合并两张灰度图像

需求&#xff1a;将两张尺寸相同的灰度图像进行合并&#xff0c;合并后的图像&#xff0c;每个像素点灰度值为两张原图对应像素点灰度值之和。若超过255&#xff0c;则最大为255。 方法一&#xff1a; 将图像读取为cv::Mat&#xff0c;再调用opencv的cv::add方法&#xff0c;进…...

Uniapp通过年月日时间转变星期格式

效果图 参靠微信小程序&#xff1a;日常记一记 代码 <view v-for"(d,index) in dataList" >{{getWeekDay(d.ctime)}} //时间格式&#xff1a;2024-06-21</view> js export default {data(){return {dataList:[],//时间数组}},onLoad() {this.loadList…...

如何编写和执行高效的测试计划

如何编写和执行高效的测试计划 1. 测试计划概述2. 测试阶段详解3. 测试计划模板4. 关键注意事项总结 1. 测试计划概述 测试计划是指导整个测试过程的重要文档&#xff0c;其中包含了测试策略、资源分配、进度安排以及风险评估等内容。 一个完善的测试计划应当包括以下几个主要…...

【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核心思想 约定大于配置&#xff0c;简化繁琐的配置 Spring Boot自动配置原理 SpringBootApplication: Spring Boot应用标注在某个类上说明这个类是SpringBoot的主配置类&#xff0c;Spr…...

实现可扩展的电商返利平台:技术选型与挑战

实现可扩展的电商返利平台&#xff1a;技术选型与挑战 大家好&#xff0c;我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01; 在当今数字化和电商兴盛的时代&#xff0c;返利平台成为…...

React 第五十五节 Router 中 useAsyncError的使用详解

前言 useAsyncError 是 React Router v6.4 引入的一个钩子&#xff0c;用于处理异步操作&#xff08;如数据加载&#xff09;中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误&#xff1a;捕获在 loader 或 action 中发生的异步错误替…...

椭圆曲线密码学(ECC)

一、ECC算法概述 椭圆曲线密码学&#xff08;Elliptic Curve Cryptography&#xff09;是基于椭圆曲线数学理论的公钥密码系统&#xff0c;由Neal Koblitz和Victor Miller在1985年独立提出。相比RSA&#xff0c;ECC在相同安全强度下密钥更短&#xff08;256位ECC ≈ 3072位RSA…...

R语言AI模型部署方案:精准离线运行详解

R语言AI模型部署方案:精准离线运行详解 一、项目概述 本文将构建一个完整的R语言AI部署解决方案,实现鸢尾花分类模型的训练、保存、离线部署和预测功能。核心特点: 100%离线运行能力自包含环境依赖生产级错误处理跨平台兼容性模型版本管理# 文件结构说明 Iris_AI_Deployme…...

shell脚本--常见案例

1、自动备份文件或目录 2、批量重命名文件 3、查找并删除指定名称的文件&#xff1a; 4、批量删除文件 5、查找并替换文件内容 6、批量创建文件 7、创建文件夹并移动文件 8、在文件夹中查找文件...

系统设计 --- MongoDB亿级数据查询优化策略

系统设计 --- MongoDB亿级数据查询分表策略 背景Solution --- 分表 背景 使用audit log实现Audi Trail功能 Audit Trail范围: 六个月数据量: 每秒5-7条audi log&#xff0c;共计7千万 – 1亿条数据需要实现全文检索按照时间倒序因为license问题&#xff0c;不能使用ELK只能使用…...

SpringBoot+uniapp 的 Champion 俱乐部微信小程序设计与实现,论文初版实现

摘要 本论文旨在设计并实现基于 SpringBoot 和 uniapp 的 Champion 俱乐部微信小程序&#xff0c;以满足俱乐部线上活动推广、会员管理、社交互动等需求。通过 SpringBoot 搭建后端服务&#xff0c;提供稳定高效的数据处理与业务逻辑支持&#xff1b;利用 uniapp 实现跨平台前…...

python如何将word的doc另存为docx

将 DOCX 文件另存为 DOCX 格式&#xff08;Python 实现&#xff09; 在 Python 中&#xff0c;你可以使用 python-docx 库来操作 Word 文档。不过需要注意的是&#xff0c;.doc 是旧的 Word 格式&#xff0c;而 .docx 是新的基于 XML 的格式。python-docx 只能处理 .docx 格式…...

汇编常见指令

汇编常见指令 一、数据传送指令 指令功能示例说明MOV数据传送MOV EAX, 10将立即数 10 送入 EAXMOV [EBX], EAX将 EAX 值存入 EBX 指向的内存LEA加载有效地址LEA EAX, [EBX4]将 EBX4 的地址存入 EAX&#xff08;不访问内存&#xff09;XCHG交换数据XCHG EAX, EBX交换 EAX 和 EB…...

[ACTF2020 新生赛]Include 1(php://filter伪协议)

题目 做法 启动靶机&#xff0c;点进去 点进去 查看URL&#xff0c;有 ?fileflag.php说明存在文件包含&#xff0c;原理是php://filter 协议 当它与包含函数结合时&#xff0c;php://filter流会被当作php文件执行。 用php://filter加编码&#xff0c;能让PHP把文件内容…...

Ubuntu Cursor升级成v1.0

0. 当前版本低 使用当前 Cursor v0.50时 GitHub Copilot Chat 打不开&#xff0c;快捷键也不好用&#xff0c;当看到 Cursor 升级后&#xff0c;还是蛮高兴的 1. 下载 Cursor 下载地址&#xff1a;https://www.cursor.com/cn/downloads 点击下载 Linux (x64) &#xff0c;…...