完整代码见:https://github.com/netease-im/NIM_Duilib_Framework/pull/151

核心代码(思路):

appitem.h

#pragma once

#define APP_HEIGHT 90
#define APP_WIDTH 90
#define EACH_LINE 6 #include <string>
//app的具体信息,这里假定有id,name,_icon,_isFrequent自行拓展
struct AppItem
{
std::string _id;
std::wstring _name;
std::wstring _icon;
bool _isFrequent=false;
}; //App UI类
class AppItemUi : public ui::VBox
{
public:
static AppItemUi* Create(const AppItem& item);
virtual void DoInit();
void SetAppdata(const AppItem& item,bool refresh);
void FixPos(int step,int index=-); //前进/后退多少步 目前应该有-1 0 1
inline int getIndex() const { return index_; }
inline const AppItem& getAppData() const { return app_data_; }
private:
AppItem app_data_;
int index_ = ; //第几个
ui::Control* app_icon_ = nullptr;
ui::Label* app_name_ = nullptr;
}; //AppWindow 拖动显示窗口类
//最好半透明
class AppWindow : public ui::WindowImplBase
{
public:
AppWindow();
~AppWindow(); static AppWindow* CreateAppWindow(HWND hParent, POINT pt, const AppItem& Item)
{
AppWindow* ret = new AppWindow;
ret->SetBeforeCreate(Item, pt);
ret->Create(hParent, L"", WS_POPUP, WS_EX_TOOLWINDOW);
pThis_ = ret;
//需要改变下pos,延后到initWindows
return ret;
} /**
* 一下三个接口是必须要覆写的接口,父类会调用这三个接口来构建窗口
* GetSkinFolder 接口设置你要绘制的窗口皮肤资源路径
* GetSkinFile 接口设置你要绘制的窗口的 xml 描述文件
* GetWindowClassName 接口设置窗口唯一的类名称
*/
virtual std::wstring GetSkinFolder() override;
virtual std::wstring GetSkinFile() override;
virtual std::wstring GetWindowClassName() const override; /**
* 收到 WM_CREATE 消息时该函数会被调用,通常做一些控件初始化的操作
*/
virtual void InitWindow() override;
/**
* 收到 WM_CLOSE 消息时该函数会被调用
*/
virtual LRESULT OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); //其他的功能函数
void SetBeforeCreate(const AppItem& Item, POINT pt){ item_ = Item; pt_ = pt; }
void AdjustPos();
void InstallHook();
void UnInstallHook();
static LRESULT CALLBACK LowLevelMouseProc(int nCode, WPARAM wParam, LPARAM lParam);
private:
AppItem item_;
ui::Box* origin_owner = nullptr; //
POINT pt_;
static HHOOK mouse_Hook_ ;
static AppWindow* pThis_;
};

appitem.cpp

#include "stdafx.h"
#include "appitem.h" AppItemUi* AppItemUi::Create(const AppItem& item)
{
AppItemUi* uiItem = new AppItemUi;
uiItem->SetAppdata(item, false);
ui::GlobalManager::FillBoxWithCache(uiItem, L"movecontrol/app_item.xml");
return uiItem;
} void AppItemUi::DoInit()
{
app_icon_ = static_cast<ui::Control*>(FindSubControl(L"app_icon"));
if (app_icon_)
{
app_icon_->SetBkImage(app_data_._icon);
}
app_name_ = static_cast<ui::Label*>(FindSubControl(L"app_name"));
if (app_name_)
{
app_name_->SetText(app_data_._name);
} //绑定事件 } void AppItemUi::SetAppdata(const AppItem& item,bool refresh)
{
app_data_ = item;
if (refresh)
{
if (app_icon_)
{
app_icon_->SetBkImage(app_data_._icon);
}
if (app_name_)
{
app_name_->SetText(app_data_._name);
}
}
} void AppItemUi::FixPos(int step, int index)
{
if (index != -)
{
index_ = index;
}
index_ += step; ui::UiRect marginRect = { (index_ % EACH_LINE)*APP_WIDTH, (index_ / EACH_LINE)*APP_HEIGHT,, }; SetMargin(marginRect);
} AppWindow::AppWindow()
{ } AppWindow::~AppWindow()
{ } std::wstring AppWindow::GetSkinFolder()
{
return L"movecontrol";
} std::wstring AppWindow::GetSkinFile()
{
return L"app_window.xml";
} std::wstring AppWindow::GetWindowClassName() const
{
return L"movecontrol";
} void AppWindow::InitWindow()
{
ui::VBox* root = static_cast<ui::VBox*>(FindControl(L"root"));
if (root)
{
auto app_item = AppItemUi::Create(item_);
root->Add(app_item);
} //设置消息钩子,不然无法即时移动
InstallHook(); //移动到合适的位置
AdjustPos(); } LRESULT AppWindow::OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
//清理hook
UnInstallHook();
pThis_ = nullptr; return ;
} HHOOK AppWindow::mouse_Hook_; AppWindow* AppWindow::pThis_; void AppWindow::AdjustPos()
{
//移动到合适位置,并接管鼠标
//移植pos的位置,注意去掉阴影
//
ui::UiRect rcCorner = GetShadowCorner();
POINT ptCursor;
::GetCursorPos(&ptCursor);
//左上角的位置
ptCursor.x -= pt_.x;
ptCursor.y -= pt_.y; ::SetWindowPos(GetHWND(), NULL, ptCursor.x - rcCorner.left, ptCursor.y - rcCorner.top, -, -, SWP_NOSIZE | SWP_NOZORDER | SWP_SHOWWINDOW);
} void AppWindow::InstallHook()
{
if (mouse_Hook_) UnInstallHook();
mouse_Hook_ = SetWindowsHookEx(WH_MOUSE_LL,
(HOOKPROC)AppWindow::LowLevelMouseProc, GetModuleHandle(NULL), NULL);
} void AppWindow::UnInstallHook()
{
if (mouse_Hook_) {
UnhookWindowsHookEx(mouse_Hook_);
mouse_Hook_ = NULL; //set NULL
}
} LRESULT CALLBACK AppWindow::LowLevelMouseProc(int nCode, WPARAM wParam, LPARAM lParam)
{
if (nCode == HC_ACTION)
{
if (wParam == WM_MOUSEMOVE &&::GetKeyState(VK_LBUTTON) < )
{
MOUSEHOOKSTRUCT *pMouseStruct = (MOUSEHOOKSTRUCT *)lParam;
if (NULL != pMouseStruct)
{
if (pThis_)
{
pThis_->AdjustPos();
}
}
}
else if (wParam == WM_LBUTTONUP)
{
//鼠标弹起,无论什么时候都需要销毁窗口
if (pThis_)
{
//通知主窗口事件
::PostMessage(GetParent(pThis_->GetHWND()), WM_LBUTTONUP, , );
pThis_->Close();
}
}
} return CallNextHookEx(mouse_Hook_, nCode, wParam, lParam);
}

layouts_form.h

#pragma once
#include "AppDb.h"
enum ThreadId
{
kThreadUI
};
class LayoutsForm : public ui::WindowImplBase
{
public:
LayoutsForm(const std::wstring& class_name, const std::wstring& theme_directory, const std::wstring& layout_xml);
~LayoutsForm(); /**
* 一下三个接口是必须要覆写的接口,父类会调用这三个接口来构建窗口
* GetSkinFolder 接口设置你要绘制的窗口皮肤资源路径
* GetSkinFile 接口设置你要绘制的窗口的 xml 描述文件
* GetWindowClassName 接口设置窗口唯一的类名称
*/
virtual std::wstring GetSkinFolder() override;
virtual std::wstring GetSkinFile() override;
virtual std::wstring GetWindowClassName() const override; /**
* 收到 WM_CREATE 消息时该函数会被调用,通常做一些控件初始化的操作
*/
virtual void InitWindow() override; /**
* 收到 WM_CLOSE 消息时该函数会被调用
*/
virtual LRESULT OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
/**
* @brief 接收到鼠标左键弹起消息时被调用
* @param[in] uMsg 消息内容
* @param[in] wParam 消息附加参数
* @param[in] lParam 消息附加参数
* @param[out] bHandled 返回 true 则继续派发该消息,否则不再派发该消息
* @return 返回消息处理结果
*/
virtual LRESULT OnLButtonUp(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); public:
static void ShowCustomWindow(const std::wstring& class_name, const std::wstring& theme_directory, const std::wstring& layout_xml); private:
//drag-drop相关
bool OnProcessAppItemDrag(ui::EventArgs* param);
void DoDrag(ui::Control* pAppItem, POINT pt_offset);
void DoBeforeDrag();
void DoDraging(POINT pt_offset);
bool DoAfterDrag(ui::Box* check); private:
std::wstring class_name_;
std::wstring theme_directory_;
std::wstring layout_xml_; ui::Box* frequent_app_=nullptr;
ui::Box* my_app_ = nullptr; bool is_drag_state_=false;
POINT old_drag_point_;
AppItemUi* current_item_ = nullptr; };

layouts_form.cpp

#include "stdafx.h"
#include "layouts_form.h"
using namespace ui;
using namespace std; LayoutsForm::LayoutsForm(const std::wstring& class_name, const std::wstring& theme_directory, const std::wstring& layout_xml)
: class_name_(class_name)
, theme_directory_(theme_directory)
, layout_xml_(layout_xml)
{
} LayoutsForm::~LayoutsForm()
{
} std::wstring LayoutsForm::GetSkinFolder()
{
return theme_directory_;
} std::wstring LayoutsForm::GetSkinFile()
{
return layout_xml_;
} std::wstring LayoutsForm::GetWindowClassName() const
{
return class_name_;
} void LayoutsForm::InitWindow()
{
//添加应用。应用有可能是服务器下发的,一般本地也有保存的
//loadFromDb
//getFromServer---->后台可以先保存到db,再post个消息出来,界面重新从db load。 //作为demo,先写死
std::vector<AppItem> applist;
CAppDb::GetInstance().LoadFromDb(applist); frequent_app_ = static_cast<ui::Box*>(FindControl(L"frequent_app"));
my_app_ = static_cast<ui::Box*>(FindControl(L"my_app")); for (const auto& item: applist)
{
AppItemUi* pAppUi = AppItemUi::Create(item);
pAppUi->AttachAllEvents(nbase::Bind(&LayoutsForm::OnProcessAppItemDrag, this, std::placeholders::_1));
if (item._isFrequent)
{
pAppUi->FixPos(, frequent_app_->GetCount());
frequent_app_->Add(pAppUi);
}
else
{
pAppUi->FixPos(, my_app_->GetCount());
my_app_->Add(pAppUi);
}
}
} LRESULT LayoutsForm::OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
PostQuitMessage(0L);
return __super::OnClose(uMsg, wParam, lParam, bHandled);
} LRESULT LayoutsForm::OnLButtonUp(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
if (current_item_ == nullptr)
{
return __super::OnLButtonUp(uMsg,wParam,lParam,bHandled);
} Box* pParent = current_item_->GetParent();
pParent->SetAutoDestroy(true); if (!DoAfterDrag(frequent_app_) && !DoAfterDrag(my_app_))
{
//回滚
pParent->AddAt(current_item_, current_item_->getIndex());
//从index处开始补缺口
for (int index = current_item_->getIndex()+; index < pParent->GetCount(); ++index)
{
AppItemUi* _pItem = dynamic_cast<AppItemUi*>(pParent->GetItemAt(index));
if (_pItem)
{
_pItem->FixPos(+);
}
}
} //更新App信息到数据库
CAppDb::GetInstance().SaveToDb(current_item_->getAppData()); is_drag_state_ = false;
current_item_ = nullptr;
SetForegroundWindow(m_hWnd);
SetActiveWindow(m_hWnd);
return __super::OnLButtonUp(uMsg, wParam, lParam, bHandled);
} void LayoutsForm::ShowCustomWindow(const std::wstring& class_name, const std::wstring& theme_directory, const std::wstring& layout_xml)
{
LayoutsForm* window = new LayoutsForm(class_name, theme_directory, layout_xml);
window->Create(NULL, class_name.c_str(), WS_OVERLAPPEDWINDOW & ~WS_MAXIMIZEBOX, );
window->CenterWindow();
window->ShowWindow();
} //得想办法抓起鼠标弹起的一刻
bool LayoutsForm::OnProcessAppItemDrag(ui::EventArgs* param)
{
switch (param->Type)
{
case kEventMouseMove:
{
if (::GetKeyState(VK_LBUTTON) >= )
break;
if (!is_drag_state_)
{
break;
}
//检测位移
LONG cx = abs(param->ptMouse.x - old_drag_point_.x);
LONG cy = abs(param->ptMouse.y - old_drag_point_.y);
if (cx < && cy < )
{
break;
}
//在拖拽模式下
//获取鼠标相对AppItem的位置
ui::UiRect rect = param->pSender->GetPos(); //左上角有效
POINT pt = { param->ptMouse.x - rect.left, param->ptMouse.y - rect.top }; DoDrag(param->pSender, pt);
is_drag_state_ = false;
}
break;
case kEventMouseButtonDown:
{
is_drag_state_ = true;
old_drag_point_ = param->ptMouse;
}
break;
case kEventMouseButtonUp:
{
is_drag_state_ = false;
//DoDrop }
break;
}
return true;
} void LayoutsForm::DoDrag(ui::Control* pAppItem, POINT pos)
{
current_item_ = dynamic_cast<AppItemUi*>(pAppItem);
if (nullptr==current_item_)
{
return;
}
DoBeforeDrag();
DoDraging(pos); } void LayoutsForm::DoBeforeDrag()
{
//抠出该项目,后面的项目全部左移
ASSERT(current_item_);
if (current_item_)
{
Box* pParent = current_item_->GetParent();
ASSERT(pParent);
pParent->SetAutoDestroy(false); //子控件不销毁
pParent->Remove(current_item_); //从index处开始补缺口
for (int index = current_item_->getIndex(); index < pParent->GetCount(); ++index)
{
AppItemUi* _pItem = dynamic_cast<AppItemUi*>(pParent->GetItemAt(index));
if (_pItem)
{
_pItem->FixPos(-);
}
}
}
} void LayoutsForm::DoDraging(POINT pos)
{
//这里注意,如果只是父控件内部移动的话,会简单很多
//设置下current_item_的setmargin,重新add回去,先保留在父控件的最后一个
//index_保存之前的位置(防取消),当鼠标弹起时,再设置下合理的值,包括在父控件的位置 //跨进程移动的话,需要借用drag-drop,也是可以实现的,这里从略 //本Demo实现的是跨父控件移动(兼容父控件内部移动),并且可以移动出窗口范围,因此创建临时窗口
//非常遗憾,当临时窗口创建时,临时窗口并没有即时的拖拽感,这里采取Hook方法,在mousemove消息移动。 //这里创建新窗口 当然得确保不能重复有窗口,这里省略
AppWindow* pWindow = AppWindow::CreateAppWindow(GetHWND(), pos, current_item_->getAppData());
ASSERT(pWindow);
} bool LayoutsForm::DoAfterDrag(ui::Box* check)
{
//获取鼠标的位置
POINT pt;
GetCursorPos(&pt);
ScreenToClient(m_hWnd, &pt);
int findIndex = ;
UiRect rectBox = check->GetPos();
if (rectBox.IsPointIn(pt))
{
//最好是重合面积更大的,这里根据鼠标位置来了
for (findIndex = ; findIndex < check->GetCount(); findIndex++)
{
auto control = check->GetItemAt(findIndex);
UiRect rectCtrl = control->GetPos();
if (rectCtrl.IsPointIn(pt))
{
//插入到该index
break;
}
}
//合理安排区域
if (findIndex < check->GetCount())
{
current_item_->FixPos(, findIndex);
check->AddAt(current_item_, findIndex);
//从index处开始补缺口
for (int index = findIndex + ; index < check->GetCount(); ++index)
{
AppItemUi* _pItem = dynamic_cast<AppItemUi*>(check->GetItemAt(index));
if (_pItem)
{
_pItem->FixPos(+);
}
}
return true;
}
else
{
//放到最后面
current_item_->FixPos(, findIndex);
check->Add(current_item_);
return true;
}
}
else
{
return false;
} }

Duilib的控件拖拽排序,支持跨容器拖拽(网易云信版本)的更多相关文章

  1. duilib List控件,横向滚动时列表项不移动或者显示错位的bug的修复

    转载请说明出处,谢谢~~:http://blog.csdn.net/zhuhongshu/article/details/42264673 关于这个bug的修复我之前写过一篇博客,连接为:http:/ ...

  2. android 自定义空间 组合控件中 TextView 不支持drawableLeft属性

    android 自定义空间 组合控件中 TextView 不支持drawableLeft属性.会报错Caused by: android.view.InflateException: Binary X ...

  3. 【新功能前瞻】SpreadJS 纯前端表格控件V12.2:打印增强、拖拽填充等六大特性

    新版本来袭:葡萄城 SpreadJS 纯前端表格控件的全新版本 V12.2 将于8月正式发布! 作为一款备受华为.招商银行.中国平安.苏宁易购等行业专家和前端开发者认可的纯 JavaScript 电子 ...

  4. duilib修复ActiveXUI控件bug,以支持flash透明动态背景

    转载请说明原出处,谢谢~~ 昨天在QQ控件里和同学说起QQ2013登陆窗体的开发,从界面角度考虑,单单一个登陆界面是很容易做出来的.腾讯公司为了 防止各种盗号行为可谓煞费苦心,QQ2013采用了动态背 ...

  5. duilib List控件,横向滚动时列表项不移动或者移动错位的bug的修复

    转载请说明出处,谢谢~~ 这篇博客已经作废,只是留作记录,新的bug修复博客地址:http://blog.csdn.net/zhuhongshu/article/details/42264673 之前 ...

  6. duilib combo控件,当鼠标滚动时下拉列表自动关闭的bug的修复

    转载请说明出处,谢谢~~ 群里有朋友提到了使用Combo控件时,当下拉列表出现,此时鼠标滚轮滚动,下拉列表就自动消失了.我看了一下源码,这个bug的修复很简单. CComboUI控件被单击时创建CCo ...

  7. c++ builder TListView控件按字符串排序(根据网上代码亲测ok)

    //--------------------------------------------------------------------------- /* 首先将一个列表框控件安放在Form上, ...

  8. 增加duilib edit控件的提示功能和多种文字颜色

    转载请说明原出处,谢谢~~:http://blog.csdn.net/zhuhongshu/article/details/41786407 duilib的CEditUI控件内部使用了win32的原生 ...

  9. 修复duilib CEditUI控件和CWebBrowserUI控件中按Tab键无法切换焦点的bug

    转载请说明原出处,谢谢~~:http://blog.csdn.net/zhuhongshu/article/details/41556615 在duilib中,按tab键会让焦点在Button一类的控 ...

随机推荐

  1. C# 多线程任务分配辅助类

    1)首先实现一个多线程的辅助类,代码如下: public class ThreadMulti { public delegate void DelegateComplete(); public del ...

  2. 11.EL(表达式语言)

    一.EL概述 EL(Expression Language,表达式语言)是JSP2.0 中引入的新内容.通过EL可以简化在JSP中对对象的引用,从而规范页面代码,增加程序的可读性和可维护性. 1.EL ...

  3. 顺序表应用2:多余元素删除之建表算法(SDUT 3325)

    题解: 每次询问一遍,如果已经存在就不用插入表中了. #include <stdio.h> #include <stdlib.h> #include <string.h& ...

  4. zabbix监控远端主机

    接着上一篇博客,zabbix监控搭建起来以后,怎么用来监控其他主机呢,这一篇就来简单讲一下,希望对大家有所帮助. 安装一些依赖包 [root@winter ~]# yum install curl c ...

  5. 用 Docker 搭建 ORACLE 数据库开发环境

    用 Docker 搭建 ORACLE 数据库开发环境 需要安装 ORACLE 数据库做开发,直接安装的话因为各类平台的限制,非常复杂,会遇到很多问题. 还好,现在有 Docker 化的部署方式,省去很 ...

  6. 剑指offer-字符串的排列

    题目描述 输入一个字符串,按字典序打印出该字符串中字符的所有排列.例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba. 输入描述: 输 ...

  7. 读取天气信息,并通过QQ邮箱发送至指定邮箱

    from email.mime.text import MIMEText from email.header import Header from smtplib import SMTP_SSL im ...

  8. websphere gc策略调整

    根据应用服务器处理的特性,适配不同的gc策略,验证程序最适合程序的gc策略: server.xml路径: xmlcells/PBOCCell/nodes/PBOCNode01/servers/PBOC ...

  9. Php+Redis函数使用总结

    因项目需求,冷落了redis,今天再重新熟悉一下: <?php //连接 $redis = New Redis(); $redis->connect('127.0.0.1','6379', ...

  10. echo、print、print_r之间的区别

    echo php语句:可以一次输出多个值,多个值之间用逗号隔开:没有返回值,输出标量的值.print 函数:只能打印简单类型变量的值(标量,如int,string),返回值为布尔型print_r 函数 ...