otter brower 多标签浏览器分析

源码地址:https://github.com/OtterBrowser/otter-browser

win上编译:本人使用的是qt5.15.2 安装时带上webengine

 

它用qt实现了多tab窗口的拖动 

QT拖动原理

拖放的基本原理

  1. 拖放操作包括两个动作:拖动(drag)和放下(drop 或称为放置)。当被拖动时拖动的数据会 被存储为 MIME 类型的对象,MIME 类型使用 QMimeData 类来描述。MIME 类型通常由剪贴板和拖放系统使用,以识别不同类型的数据。

  2. 拖动点(drag site):拖动的起始位置。

  3. 放下点(drop site):被拖动的对象放下的位置,若部件不能接受拖动的对象,Qt 会改变光 标的形状(一个禁用形状)来向用户进行说明。

  4. 启动拖放:拖放通过调用 QDrag::exec()函数而启动,该函数是一个阻塞函数(但不会阻塞主事件循环),这意味着在拖放操作结束之前,不会返回该函数,调用 QDrag::exec() 函数后,Qt 拥有对拖动对象的所有权,并会在必要时将其删除。

  5. 结束拖放:当用户放下拖动或取消拖动操作时结束拖放。

拖放产生的过程及事件

  1. 启动拖放后,会使数据被拖动,这时需要按住鼠标按键才能拖动需要拖动的数据,松开鼠标按键时意味着拖动结束。
  2. 默认情况下,部件不接受放下事件。使用 QWidget::setAcceptDrops()函数可设置部件是否接受放下事件(即,拖放完成时发送的事件)。只有在部件接受放下事件的情形下, 才会产生以下事件:
    • QDragEnterEvent:拖动进入事件。当拖动操作进入部件时,该事件被发送到部件, 忽略该事件,将会导至后续的拖放事件不能被发送。通常在该部件上光标会在外观上 显示为禁用的图形。
    • QDragMoveEvnet:拖动移动事件。当拖动操作正在进行时,以及当具有焦点时按下 键盘的修饰键(比如 Ctrl)时,发送该事件,要使部件能接收到该事件,则该部件必须接 受 QDragEnterEvent 事件。
    • QDropEvent:放下事件。在完成拖放操作时发送该事件,即当用户在部件上放下一个 对象时,发送此事件。要使部件能接收到该事件,则该部件必须接受 QDragEnterEvent 事件,且不能忽略 QDragMoveEvent 事件。
    • QLeaveEvent:当拖放操作离开部件时发送该事件,注意:要使部件能接收到该事件, 必须要使拖动先进入该部件(即产生 QDragEnterEvent 事件),然后再离开该部件,才会产生 QLeaveEvent 事件。因很少使用该事件,因此本文不做重点介绍。
  3. 注:必须接受是指必须重新实现该事件的处理函数并接受该事件,不能忽略是指在处件事理函数中不明确调用 ignore()函数忽略该事件,这意味着可以不必重新实现该事件的处理函数。
  4. 以上事件产生的顺序为:QDragEnterEventQDragMoveEvnetQDropEvent

image-20210911065547816

编写拖放程序的步骤

  1. 在需要接受放下数据的部件上调用 QWidget::setAcceptDrops()函数以使该部件能接受拖放事件。otter-browser\src\ui\TabBarWidget.cpp中:

    TabHandleWidget::TabHandleWidget(Window *window, TabBarWidget *parent) : QWidget(parent),
    	m_window(window),
    	m_tabBarWidget(parent),
    	m_dragTimer(0),
    	m_isActiveWindow(false),
    	m_isCloseButtonUnderMouse(false),
    	m_wasCloseButtonPressed(false)
    {
    	handleLoadingStateChanged(window->getLoadingState());
    	setAcceptDrops(true);
    	setMouseTracking(true);
  2. 启动拖放:通常在 mousePressEvent()或 mouseMoveEvent()函数中启动拖放,记住启动拖放就是调用 QDrag 对象的 exec()函数,因此也可以在 keyPressEvent()等函数中启动拖放(因很少这样做,所以本文不介绍这种情况下的拖放)。在此步把需要拖动的数据保存在 QMimeData 对象中。

    /*
    mouseMoveEvent()函数中启动拖放,记住启动拖放就是调用QDrag对象的exec()函数,
    在此步把需要拖动的数据保存在QMimeData对象中
    */
    void TabBarWidget::mouseMoveEvent(QMouseEvent *event)
    {
    	tabHovered(tabAt(event->pos()));
    
    	if (!m_isDraggingTab && !m_dragStartPosition.isNull())
    	{
    		m_isDraggingTab = ((event->pos() - m_dragStartPosition).manhattanLength() > QApplication::startDragDistance());
    	}
        
        //如果已经在拖动且 鼠标已经离开了TabBar
    	if (m_isDraggingTab && !rect().adjusted(-10, -10, 10, 10).contains(event->pos()))
    	{
    		m_isDraggingTab = false;
            //模拟鼠标发送一个鼠标释放事件给自己,从而调用mouseReleaseEvent函数来清空相应标志位
    		QMouseEvent mouseEvent(QEvent::MouseButtonRelease, event->pos(), Qt::LeftButton, Qt::LeftButton, event->modifiers());
    
    		QApplication::sendEvent(this, &mouseEvent);
            //脱离标签页
    		m_isDetachingTab = true;
    
    		updateSize();
            //找到tabBar所在的窗体
    		const MainWindow *mainWindow(MainWindow::findMainWindow(this));
    
    		if (mainWindow)
    		{
                //找到drag的标签页window
                const Window *window(mainWindow->getWindowByIdentifier(m_draggedWindow));
    
    			if (window)
    			{
                    //设置MineData
    				QMimeData *mimeData(new QMimeData());
    				mimeData->setText(window->getUrl().toString());
    				mimeData->setUrls({window->getUrl()});
    				mimeData->setProperty("x-url-title", window->getTitle());
    				mimeData->setProperty("x-window-identifier", window->getIdentifier());
                    
                    //绘制缩略图
    				const QPixmap thumbnail(window->createThumbnail());
    				QDrag *drag(new QDrag(this));//创建QDrag
    				drag->setMimeData(mimeData);
    				drag->setPixmap(thumbnail.isNull() ? window->getIcon().pixmap(16, 16) : thumbnail);
    				drag->exec(Qt::CopyAction | Qt::MoveAction);
    
    				m_isDetachingTab = false;
    
    				if (!drag->target())
    				{
    					Application::triggerAction(ActionsManager::DetachTabAction, {{QLatin1String("tab"), window->getIdentifier()}}, parentWidget());
    				}
    			}
    		}
    
    		return;
    	}
    
    	if (!m_isIgnoringTabDrag && !m_isDetachingTab)
    	{
    		QTabBar::mouseMoveEvent(event);
    	}
    }
  3. 重新实现需要接受放下数据的部件的 dragEnterEvent()事件处理函数。

    //当拖动操作进入部件时,该事件被发送到部件,忽略该事件,将会导至后续的拖放事件不能被发送,
    //此时在该部件上光标通常会在外观上显示为禁用的图形
    void TabBarWidget::dragEnterEvent(QDragEnterEvent *event)
    {
    	if (event->mimeData()->hasText() || event->mimeData()->hasUrls() || (event->source() && !event->mimeData()->property("x-window-identifier").isNull()))
    	{
    		event->accept();
            
    		m_dragMovePosition = event->pos();//获取到进入的位置=》计算tab页应该放置的位置
    
    		update();
    	}
    }
  4. 根据需要重新实现 dragMoveEvent 或 dropEvent()函数。

    /*
    拖动移动事件。当拖动操作正在进行时,以及当具有焦点时按下键盘的修饰键(比如Ctrl)时,
    发送该事件,要使部件能接收到该事件,则该部件必须接受QDragEnterEvent事件
    */
    void TabBarWidget::dragMoveEvent(QDragMoveEvent *event)
    {
    	m_dragMovePosition = event->pos();
    
    	update();
    }
    
    /*
    当拖放操作离开部件时发送该事件,
    注意:要使部件能接收到该事件,必须要使拖动先进入该部件(即产生QDragEnterEvent事件),然后再离开该部件,才会产生
    很少使用该事件
    */
    void TabBarWidget::dragLeaveEvent(QDragLeaveEvent *event)
    {
    	Q_UNUSED(event)
    
    	m_dragMovePosition = {};
    
    	update();
    }
void TabBarWidget::dropEvent(QDropEvent *event)
{
	const int dropIndex(getDropIndex());

	if (event->source() && !event->mimeData()->property("x-window-identifier").isNull())
	{
		event->setDropAction(Qt::MoveAction);
		event->accept();

		int previousIndex(-1);
		const quint64 windowIdentifier(event->mimeData()->property("x-window-identifier").toULongLong());

		if (event->source() == this)
		{
			for (int i = 0; i < count(); ++i)
			{
				const Window *window(getWindow(i));

				if (window && window->getIdentifier() == windowIdentifier)
				{
					previousIndex = i;

					break;
				}
			}
		}

		if (previousIndex < 0)
		{
			MainWindow *mainWindow(MainWindow::findMainWindow(this));

			if (mainWindow)
			{
				const QVector<MainWindow*> mainWindows(Application::getWindows());

				for (int i = 0; i < mainWindows.count(); ++i)
				{
					if (mainWindows.at(i))
					{
						Window *window(mainWindows.at(i)->getWindowByIdentifier(windowIdentifier));

						if (window)
						{
							mainWindows.at(i)->moveWindow(window, mainWindow, {{QLatin1String("index"), dropIndex}});

							break;
						}
					}
				}
			}
		}
		else if (previousIndex != dropIndex && (previousIndex + 1) != dropIndex)
		{
			moveTab(previousIndex, (dropIndex - ((dropIndex > previousIndex) ? 1 : 0)));
		}
	}
	else if (event->mimeData()->hasText() || event->mimeData()->hasUrls())
	{
		MainWindow *mainWindow(MainWindow::findMainWindow(this));
		bool canOpen(mainWindow != nullptr);

		if (canOpen)
		{
			const QVector<QUrl> urls(Utils::extractUrls(event->mimeData()));

			if (urls.isEmpty())
			{
				const InputInterpreter::InterpreterResult result(InputInterpreter::interpret(event->mimeData()->text(), (InputInterpreter::NoBookmarkKeywordsFlag | InputInterpreter::NoSearchKeywordsFlag)));

				if (result.isValid())
				{
					switch (result.type)
					{
						case InputInterpreter::InterpreterResult::UrlType:
							mainWindow->triggerAction(ActionsManager::OpenUrlAction, {{QLatin1String("url"), result.url}, {QLatin1String("index"), dropIndex}});

							break;
						case InputInterpreter::InterpreterResult::SearchType:
							mainWindow->search(result.searchQuery, result.searchEngine, SessionsManager::NewTabOpen);

							break;
						default:
							break;
					}
				}
			}
			else
			{
				if (urls.count() > 1 && SettingsManager::getOption(SettingsManager::Choices_WarnOpenMultipleDroppedUrlsOption).toBool())
				{
					QMessageBox messageBox;
					messageBox.setWindowTitle(tr("Question"));
					messageBox.setText(tr("You are about to open %n URL(s).", "", urls.count()));
					messageBox.setInformativeText(tr("Do you want to continue?"));
					messageBox.setIcon(QMessageBox::Question);
					messageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel);
					messageBox.setDefaultButton(QMessageBox::Yes);
					messageBox.setCheckBox(new QCheckBox(tr("Do not show this message again")));

					if (messageBox.exec() == QMessageBox::Cancel)
					{
						canOpen = false;
					}

					SettingsManager::setOption(SettingsManager::Choices_WarnOpenMultipleDroppedUrlsOption, !messageBox.checkBox()->isChecked());
				}

				if (canOpen)
				{
					for (int i = 0; i < urls.count(); ++i)
					{
						mainWindow->triggerAction(ActionsManager::OpenUrlAction, {{QLatin1String("url"), urls.at(i)}, {QLatin1String("index"), (dropIndex + i)}});
					}
				}
			}
		}

		if (canOpen)
		{
			event->setDropAction(Qt::CopyAction);
			event->accept();
		}
		else
		{
			event->ignore();
		}
	}
	else
	{
		event->ignore();
	}

	m_dragMovePosition = {};

	update();
}

一个简单拖动的示例:

#include <QWidget>
#include <QPushButton>
#include <QDrag>
#include <QMimeData>
#include <QDragEnterEvent>

class QDragBtn:public QPushButton
{
    Q_OBJECT
public:
    QDragBtn(QString t="",QWidget *p=0):QPushButton(t,p){}

    void mousePressEvent(QMouseEvent *e){  	//在该事件中启动拖放
        QDrag *dg=new QDrag(this);
        //将须要拖动的数据放入QMimedata对象中,该对象用于保存须要传递的数据,数据的内
        //容彻底由程序员自行设定。一般为界面上所选择内容。
        QMimeData *md=new QMimeData;
        md->setText("FFF");    			//这是md中存储的内容(即拖放时传递的数据)。
        dg->setMimeData(md);   			//步骤1:设置拖动的数据。该函数会得到md的全部权。
        dg->exec();
    }
    //步骤2:启动拖放
    void dragEnterEvent(QDragEnterEvent  * e){
        //步骤3:处理是否接受拖动事件。
          e->accept();  			//接受拖动进入事件
        //e->ignore();  	/*若忽略该事件,则不会再发送以后的事件,拖放至此结束,这会致使鼠标光标显示为禁用的图形。*/
     }

    void dropEvent(QDropEvent  * e){
        //步骤4:处理拖动中的数据(固然也可不做任何处理)
            setText(e->mimeData()->text());	//设置此部件的文本为拖动对象中的文本。
            //此事件不影响后续事件,可接受也可忽略。
            //e->accept();
            //e->ignore();
        }
};

class Widget:public QWidget
{
    Q_OBJECT
public:
    Widget(QWidget *p=0):QWidget(p){
        QDragBtn *pb1=new QDragBtn("AAA",this);
        pb1->move(22,22);

        QDragBtn *pb2=new QDragBtn("BBB",this);
        pb2->move(199,22);
        pb2->setAcceptDrops(true);     		//pb2接受放下事件
        pb1->setAcceptDrops(0); //pb1禁止放下事件。
    }
};
#include <QApplication>
#include "m.h"


int main(int argc, char *argv[]){
    QApplication app(argc,argv);
    Widget w;
    w.resize(400,300);
    w.show();
    return app.exec();
}

在这里插入图片描述

拖放动作(或称为放置动作)

  1. 拖放动作是指用户希望怎样处理拖放的数据,比如移动、复制、还是创建由目标到源的链 接等。拖放动作由 Qt::DropAction 枚举描述。

  2. 可能的拖放动作、实际的拖放动作以及建议的拖放动作 :

  • 可能的拖放动作:是指用户在拖放时可能会执行的拖放动作,用户在拖放时通常可由用户选择,比如可以选择移动、复制或链接等动作中的一种,这些动作都是可能的拖放动作。可能的拖放动作在QDrag::exec()函数的第 1 个参数中指定,同时该函数的返回值是最终的实际拖放动作。
  • 实际的拖放动作:是指拖放被最终放置时实际执行的动作,实际拖放动作在 dropEvent() 函数中使用 QDropEvent::setDropAction()函数(还需在之后调用 accept()函数)设置,该函数会影响 QDrag::exec()函数的返回值。
  • 建议的拖放动作:是指当用户执行拖动而不使用修饰键时的默认动作,建议拖放动作可在 QDrag::exec()函数的第 2 个参数中指定,该参数的设置会影响拖动时鼠标光标右下角的外观,另外 QDropEvent::acceptProposedAction()函数表示设置执行操作为建议操作并接受该事件。
  • 以上三种拖放动作常常相互关联,比如用户在拖动时通常可以执行移动、复制或链接等动作(可能的拖放动作)中的一种,然而应用程序在拖放被最终放置时并不知道用户 到底需要执行哪种操作,若用户未指定需要执行的可能的拖放动作中的哪一种动作时, 应用程序可以使用设置的建议动作,作为需要执行的动作。

3、各拖放动作之间的关系

  1. QDrag::exec()函数的规则
    • 若 QDrag::exec()未指定建议拖放动作,则依顺序移动、复制、链接进行选择
    • 若 QDrag::exec()函数在第 2 个参数上指定了建议拖放动作,但该动作不在可能的拖放动作组合之中,则使用默认的复制拖放动作。
  2. QDropEvent::setDropAction()函数的规则
    • 使用 setDropAction()函数设置拖放动作之后应使用 accept()函数,而不应使用 QDropEvent::acceptProposedAction()函数(因为该函数会重置拖放动作为建议拖放动作)
    • 若 QDropEvent::setDropAction()函数设置的拖放动作不在可能的拖放动作组合之中,则使用建议拖放动作。
  3. dropEvent()函数的规则,该函数是否接受事件直接影响到 QDrag::exec()函数的返回值, 其规则如下 :
    • 若在该函数内调用 ignore(),则 exec()函数返回 Qt::IgnoreAction
    • 若在该函数内调用 accept(),则 exec()函数返回在该函数中使用 setDropAction() 函数设置的拖放动作。

拖放动作及拖放的程序设计原则#

  1. 若在 mouseMoveEvent()函数中启动拖放,则可以编写避免用户因为手握鼠标抖动而产生的拖动,这比在 mousePressEvent()函数中启动拖放效果更好。

  2. 在 QDrag::exec()函数的参数中指定可能的拖放动作,比如在其中同时指定移动、复制、 链接等;但最终是否接受这些动作,由后续的事件处理函数进行判断,详见后文。另 外需要注意的是 QDrag::exec()函数虽是阻塞函数,但在执行完该函数(比如释放鼠标按钮完成拖放时)后程序会返回该函数,然后接着执行之后的语句,exec()函数返回的是用户实际执行的动作。

  3. dragEnterEvent()函数通常根据该部件或实际使用情况进行筛选,比如若该部件不接受图片数据,则忽略对该动作的接受,从而阻止事件被进一步传递。

  4. 若重新实现了 dragMoveEvent()函数,则还可以在该函数内进行进一步的设置,比如 默认为复制动作,若用户拖动的同时按下了 Shift 键,则设置为移动动作,若按下了 Ctrl 键,则为复制动作,若按下了 Alt 键则为链接动作等,在该函数中的设置可以影 响鼠标光标在外观上的显示,比如复制会在光标右下角显示一个“+”符号等。另外, 在该函数内还可以设置用户拖动到该部件中的范围,比如拖动到某矩形范围内时,该 部件才接受拖放,否则被忽略等。注意:若在 dragEnterEvent()函数内也设置了拖放动 作,同样会改变光标的外观但只会改变进入部件时的外观,光标最终的外观以 dragMoveEvent()函数设置的为准(因为该函数位于 dragEnterEvent()之后执行)。

    void dragMoveEvent(QDragMoveEvent *e)
    {
        /*以下设置会改变鼠标光标的外观。若拖动的同时按下了 CTRL、ALT、SHIFT 键,则把施放动作设置为复制、移动、链接,否则为复制。*/
        if (e->keyboardModifiers() == Qt::CTRL)
            e->setDropAction(Qt::CopyAction);
        else if (e->keyboardModifiers() == Qt::SHIFT)
            e->setDropAction(Qt::MoveAction);
        else if (e->keyboardModifiers() == Qt::ALT)
            e->setDropAction(Qt::LinkAction);
        else
            e->setDropAction(Qt::CopyAction);
        //若光标位于矩形 r 之内,则接受该事件,否则忽略该事件。
        QRect r(0, 0, 111, 33);
        if (r.contains(e->pos()))
        {
            e->accept();
        }
        else
            e->ignore();
    }

  5. 在 dropEvent()函数内最终决定对拖放的数据的处理,以及用户实际执行的拖放动作, 因此该函数决定着 QDrag::exec()函数的返回值,这里要注意的是,对于移动动作,通常原始数据应由源部件(启动拖放的部件)进行删除,因此当 dropEvent()处理完数据之后,应把拖放动作设置为移动,QDrag::exec()函数会返回在 dropEvent()函数中设置的 动作,然后源部件根据 QDrag::exec()的返回值是否是移动动作,而作出是否删除原始数据的决定。注:dragEnterEvent()和 dragMoveEvent()对拖放动作的设置不会影响 QDrag::exec()的返回值。

  6. 注意:在源部件中创建的 QMimeData 和 QDrag 对象不应由程序员销毁,因为 Qt 会自动销毁,若程序员销毁了,则可能会出现多次 delete 同一个指针的错误。

  7. 以上只是通常在各函数中的做法,当然你也可以不按照这些步骤来实现,从之前的拖放示例可以看到,对拖放的处理完全是任意的。

  8. 注意:Qt为某些部件提供了一些标准的拖放支持,在继承这些部件实现拖放时需要重新实现 dragEnterEvent()dropEvent(),另外还可能需要重新实现 dragMoveEvent() 函数,以避免与标准实现的拖放支持相冲突或产生预料之外的结果。

示例:

#ifndef M_H
#define M_H

#include <QWidget>
#include <QPushButton>
#include <QDrag>
#include <QMimeData>
#include <QDragEnterEvent>
#include <QDebug>
class QDragBtn:public QPushButton
{
    Q_OBJECT
public:
    QDragBtn(QString t="",QWidget *p=0):QPushButton(t,p){}

    void  mousePressEvent(QMouseEvent *e){
        pos=e->pos(); //第一次点击鼠标的位置
    }

    void  mouseMoveEvent(QMouseEvent *e){
        //若拖动的距离大于5个像素则启用拖放(避免抖动)。
        //注:鼠标移动距离也可以使用如下语句判断,其中manhattanLength()表示曼哈顿距离,startDragDistance
        //表示系统推荐的拖动起始距离。
        //if((e->pos()-p).manhattanLength()>=QApplication::startDragDistance())
        if((e->pos()-pos).x()>=5||(e->pos()-pos).y()>=5) {
            QDrag *dg=new QDrag(this);
            QMimeData *md=new QMimeData;
            md->setText("FFF");
            dg->setMimeData(md);
            //启动拖放,该数据可复制、移动、连接,具体是否接受这些动做,需由后续程序决定。
            //若drogEvent()函数把拖放动做设置为移动,则须要对原始数据做进一步处理,注意:在完成拖放
            //后会返回exec()函数继续执行其后的语句。。
            if(dg->exec(Qt::CopyAction|Qt::MoveAction|Qt::LinkAction)==Qt::MoveAction){
                 qDebug()<<"AAA";  //本示例没有可删除的原始数据,所以只简单输出字符串用于测试。
            }

        }

    }

    void dragEnterEvent(QDragEnterEvent  * e){
        //若拖动的数据中不包含文本FFF则忽略该事件,不然接受该事件。
         if(e->mimeData()->text()!="FFF") {
             e->ignore();
         }
         else{
             e->accept();
         }
    }

    void dragMoveEvent(QDragMoveEvent  * e){
        /*如下设置会改变鼠标光标的外观。若拖动的同时按下了CTRL、ALT、SHIFT键,则把施放动做设置为复制、移动、连接,不然为复制。*/
        if(e->keyboardModifiers()==Qt::CTRL)
            e->setDropAction(Qt::CopyAction);
        else if(e->keyboardModifiers()==Qt::SHIFT)
            e->setDropAction(Qt::MoveAction);
        else if(e->keyboardModifiers()==Qt::ALT)
            e->setDropAction(Qt::LinkAction);
        else
            e->setDropAction(Qt::CopyAction);

        //限制拖动的范围:若光标位于矩形r以内,则接受该事件,不然忽略该事件。
        QRect r(0,0,100,50);
        if(r.contains(e->pos())){
            e->accept()	;
        }
        else {
            e->ignore();
        }

      }

        void dropEvent(QDropEvent  * e){
    /*若拖动的源和目标在同一个部件,则什么也不作。注意应使用return;跳出函数,若使用ignore()或accept(),程序还会继续执行以后的语句。*/
            if(e->source()==this)
                return;
            setText(e->mimeData()->text());
            e->setDropAction(Qt::MoveAction); 	//把拖放动做设置为移动。
            e->accept();
        }

public:
        QPoint pos;
};

class Widget:public QWidget
{
    Q_OBJECT
public:
    Widget(QWidget *p=0):QWidget(p){
        QDragBtn *pb1=new QDragBtn("AAA",this);
        pb1->move(22,22);

        QDragBtn *pb2=new QDragBtn("BBB",this);
        pb2->resize(100,100);
        pb2->move(199,22);
        pb2->setAcceptDrops(true);     		//pb2接受放下事件
        pb1->setAcceptDrops(0); //pb1禁止放下事件。
    }
};

#endif // M_H

#include <QApplication>
#include "m.h"


int main(int argc, char *argv[]){
    QApplication app(argc,argv);
    Widget w;
    w.resize(400,300);
    w.show();
    return app.exec();
}

在这里插入图片描述

使用拖放打开文件

拖放文件的基本步骤:文件需要使用 QFile 类来打开,然后才能读取或存入其内容,因此对 拖放的文件进行处理,其实就是获取文件的地址,而地址是使用 URL 来表示的,因此首先 需要判断拖放的数据是否含有 URL,然后读取出 URL 中保存的文件的地址,再打开文件, 然后读取文件的内容,有关流和文件的内容本章暂时不用深入了解,明白以下示例代码的作用即可。

#include <QWidget>
#include <QPushButton>
#include <QDrag>
#include <QMimeData>
#include <QDragEnterEvent>
#include <QDebug>
class QDragBtn:public QPushButton
{
    Q_OBJECT
public:
    QDragBtn(QString t="",QWidget *p=0):QPushButton(t,p){}

    void dragEnterEvent(QDragEnterEvent *e){
        //若拖动的数据包含一个 URL 则接受该事件,否则忽略该事件。
        if (e->mimeData()->hasUrls())
        {
            e->accept();
        }
        else
            e->ignore();
    }

    void dropEvent(QDropEvent *e){
        const QMimeData *pm = e->mimeData();
        QList<QUrl> u = pm->urls();          //读取出 URL 的地址列表。
        QString pth = u.at(0).toLocalFile(); //将地址转换为 QString
        if (!pth.isEmpty())
        {                    //判断地址 pth 是否为空
            QFile file(pth); //创建文件 file
            if (!file.open(QIODevice::ReadOnly))
                ;                  //以只读方式打开文件
            QTextStream in(&file); //创建流用于读取文件的内容。
            setText(in.readAll());
        }
    } //读出文件的内容,并设置为该部件的文本

public:
        QPoint pos;
};

class Widget:public QWidget
{
    Q_OBJECT
public:
    Widget(QWidget *p=0):QWidget(p){
        QDragBtn *pb1=new QDragBtn("AAA",this);
        pb1->move(22,22);

        QDragBtn *pb2=new QDragBtn("BBB",this);
        pb2->resize(100,100);
        pb2->move(199,22);
        pb2->setAcceptDrops(true);     		//pb2接受放下事件
        pb1->setAcceptDrops(0); //pb1禁止放下事件。
    }
};

#include <QApplication>
#include "m.h"


int main(int argc, char *argv[]){
    QApplication app(argc,argv);
    Widget w;
    w.resize(400,300);
    w.show();
    return app.exec();
}

效果:

按钮1 不接受拖动

 

按钮2 接受拖动,默认时复制

 

 将文件内容显示

拖放自定义类型数据

子类化 QMimeData

1、MIME 类型数据的存储 由 AAA 向 BBB 拖动时 拖动完成后按钮 BBB 的图标和文本都被成功修改。 MIME 类型不属于 Qt 类型或 C++类型,与这些类型关联的数据需要被存储,为此需要指 定一个存储 MIMI 类型的 Qt 类型(或 C++类型),并使用一个对象来存储该类型的数据, 比如对于 MIME 类型 XXX,与 XXX 关联的数据为两个字符串"SSS"和"TTT",那么"SSS" 和"TTT"需要被存储在应用程序中,比如可以使用 QByteArray 类型的对象 ba 来存储它们 (也可使用 QList 或其他类来存储),那么与 MIME 类型相关联的类型是 QByteArray 类型, MIME 类型的数据"SSS"和"TTT"被存储在 ba 中。

2、由以上原理可知,QMimeData 的本质就是用于把需要用于传输的数据保存在该类的对象中,因此最小的子类化 QMimeData 类只需在该类中定义一些保存数据的成员变量就可以了。并不需要重新实现 hasFormat()formats()retrieveData()等虚函数,但这样做的话, 没有任何对 MIME 类型的限制和指定,因此 MIME 类型就变得毫无意义了

demo窗口

 

 可以拖拽testpage3 可以合并到上面那个窗口:

代码:tabwidget.7z-互联网文档类资源-CSDN下载

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值