当前位置: 首页 > 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;返利平台成为…...

从0开始C++(三):构造函数与析构函数详解

目录 构造函数 构造函数的基本使用 构造函数也支持函数重载 构造函数也支持函数参数默认值 构造初始化列表 拷贝构造函数 浅拷贝和深拷贝 析构函数 总结 练习一下ヽ(&#xffe3;▽&#xffe3;)&#xff89; 构造函数 构造函数的基本使用 构造函数是一种特殊的成…...

行车记录仪文件夹“0字节”现象解析与恢复策略

一、行车记录仪文件夹“0字节”现象描述 行车记录仪作为现代驾驶中的必备设备&#xff0c;其储存的视频数据对于事故记录和取证至关重要。然而&#xff0c;有时车主们可能会遇到这样一个问题&#xff1a;行车记录仪的某个文件夹内的文件突然变成了0字节大小&#xff0c;无法正…...

呼叫中心系统的功能都有哪些?okcc呼叫中心pscc磐石云呼叫系统部署

当前电话营销普及到各行各业&#xff0c;方便快捷成了大部分企业在宣传自己公司的产品时必用的一种营销方式&#xff0c;但是电话营销在管理上也存在许多问题。例如&#xff1a;销售员与客户沟通前&#xff0c;未能详细了解客户的资料&#xff1b;多名销售员重复拨打同一个客户…...

2024.06.08校招 实习 内推 面经

绿*泡*泡VX&#xff1a; neituijunsir 交流*裙 &#xff0c;内推/实习/校招汇总表格 1、提前批 | 中电锦江2025届提前批招聘 提前批 | 中电锦江2025届提前批招聘 2、实习 | 国电电力2025届暑期实习生计划启动&#xff01; 实习 | 国电电力2025届暑期实习生计划启动&#x…...

Polyplus——转染试剂专业供应商

PolyPlus-transfection是一家专业的转染试剂研发和生产的生物技术公司&#xff0c;拥有20年的的转染试剂研发经验&#xff0c;通过创新的核酸转染解决方案支持基因和细胞治疗、生物制剂制造和生命科学研究。目前已经通过了ISO 9001: 2000质量体系认证&#xff0c;已经开发了一系…...

微服务架构-线上治理、线下治理与架构演进

目录 一、线上治理 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注入

文章目录 网络安全&#xff1a;什么是SQL注入引言SQL注入简介工作原理示例代码 攻击类型为什么SQL注入危险结语 网络安全&#xff1a;什么是SQL注入 引言 在数字化时代&#xff0c;数据安全成为了企业和个人最关心的问题之一。SQL注入&#xff08;SQL Injection&#xff09;是…...

从零开始精通Onvif之网络配置

&#x1f4a1; 如果想阅读最新的文章&#xff0c;或者有技术问题需要交流和沟通&#xff0c;可搜索并关注微信公众号“希望睿智”。 概述 网络配置是Onvif规范中的重要组成部分&#xff0c;允许用户通过网络远程配置和管理设备的网络设置&#xff0c;比如&#xff1a;DHCP、IP地…...

在 macOS 上使用 Homebrew 安装和配置 Python 及 Tk 库

在 macOS 上&#xff0c;系统自带的 /usr/bin/python3 版本较旧&#xff0c;且直接升级系统自带的 Python 版本可能会影响系统稳定性。因此&#xff0c;推荐使用 Homebrew 来安装和管理 Python 及其相关库。本文将详细介绍如何通过 Homebrew 安装和配置 Python 3 及 Tk 库&…...

【机器学习 复习】第2章 线性回归及最大熵模型

一、概念 1.回归就是用一条曲线对数据点进行拟合&#xff0c;该曲线称为最佳拟合曲线&#xff0c;这个拟合过程称为回归。 2.一个自变量 叫 一元线性回归&#xff0c;大于一个自变量 叫 多元线性回归。 &#xff08;1&#xff09;多元回归&#xff1a;两个x&#xff0c;一个…...

仿牌网站怎么做301跳转/中国十大互联网公司

2、 RMQ算法&#xff08;转载&#xff09; 对于该问题&#xff0c;最容易想到的解决方案是遍历&#xff0c;复杂度是O(n)。但当数据量非常大且查询很频繁时&#xff0c;该算法也许会存在问题。 本节介绍了一种比较高效的在线算法&#xff08;ST算法&#xff09;解决这个问题。…...

国外做的比较的ppt网站有哪些方面/抖音推广平台

行为树里有很多任务&#xff0c;导入这个包用来实现A星寻路或者其它导航。 线上安装&#xff1a; 搜索Behavior Designer Movement 导入Behavior Designer - Movement 导入后Actions任务里会多一个Movement...

如何在海外推广网站/如何提高网站在百度的排名

文章目录一、引言1.1 介绍二、环境搭建2.1 下载2.2 导入依赖三、页面元素3.1 布局3.2 字体图标3.3 按钮3.4 表单3.5 数据表格3.5.1 动态表格3.5.2 分页参数3.5.3 显示工具栏3.5.4 操作按钮3.5.5 操作按钮回调3.6 导航3.7 动画四、内置模块4.1 layer4.1.1 弹窗方法4.1.2 弹窗属性…...

wordpress指定分类不显示/设计网站都有哪些

Cars [Link](Problem - E - Codeforces) 题意 给你nnn辆车在一个数轴上&#xff0c;和mmm个关系&#xff0c; 1.ab1.a\ b1.a b&#xff1a;a&#xff0c;b不会相交&#xff0c;2.ab2.a\ b2.a b&#xff1a;a&#xff0c;b会相交&#xff0c;要求你通过合理的安排每辆车朝向L…...

做视频网站服务器要求/爱上链外链购买交易

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 熔化焊接与热切割找答案根据新熔化焊接与热切割考试大纲要求&#xff0c;安全生产模拟考试一点通将熔化焊接与热切割模拟考试试题进行汇编&#xff0c;组成一套熔化焊接与热切割全真模拟考试试题&#xff0c;学员可通…...

大连网站制作师/最近的头条新闻

问题 因为发明专利是累计数量&#xff0c;所以笔者希望对同一证券名称的缺失值用其上一年的数值进行填充。所以对于下图中列出的缺失值应该不予处理。如果直接使用replace 发明专利数量发明专利数量[_n-1] if 发明专利数量.命令&#xff0c;会出现“科志股份”的缺失值全部被填…...