您的位置:首页 > 编程语言 > Qt开发

Qt浅谈之十:自动补全(QCompleter或QListView)

2014-06-30 17:19 148 查看


一、简介

QCompleter能实现QLineEdit根据输入自动补全的功能,根据单词列表提示完成单词输入,也可补全文件路径。类似于百度,输入关键字列出关联的匹配结果。 不过QCompleter无法自定义匹配规则(只能模糊匹配前N个字符),本文将简要介绍并使用QListView和QStringList组合完成自定义的规则。


二、运行图

(1)运行如下图1所示。




三、详解

1、QCompleter补全路径

(1)区分大小写,补全文件的路径,因焦点一移动弹出的下拉列表就回收,所以无法截图,可自行测试。

{    
    QDirModel *model = new QDirModel(this);
    search_line_edit = new QLineEdit(this);
    completer = new QCompleter(this);
    completer->setModel(model);
    search_line_edit->setCompleter(completer);
}


2、QCompleter补全文本

#include "widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    word_list<<"Java"<<"C++"<<"C#"<<"PHP"<<"Perl"<<"Python"<<"Delphi"<<"Ruby";
    search_line_edit = new QLineEdit(this);
    completer = new QCompleter(this);
    string_list_model = new QStringListModel(word_list, this);
    completer->setCaseSensitivity(Qt::CaseInsensitive);
    completer->setModel(string_list_model);
    search_line_edit->setCompleter(completer);
    connect(search_line_edit, SIGNAL(editingFinished()), this, SLOT(editComplete()));
}

void Widget::editComplete()
{
    QString text = search_line_edit->text();
    if(QString::compare(text, QString("")) != 0) {
        bool is_contains = word_list.contains(text, Qt::CaseInsensitive);
        if(!is_contains) {
           word_list<<text;
           string_list_model->setStringList(word_list);
           //completer->setModel(new QStringListModel(wordList, this));
        }
    }
}
每次编译完成后按回车键,会将不存在列表中的文本加入到改列表中。Qt::CaseSensitivity取值,Qt::CaseInsensitive:大小写不敏感;Qt::CaseSensitive:大小写敏感。默认为:Qt::CaseSensitive。

3、类似QComplater可自定义匹配规则

(1)单击插入列表,可以将新的文本加入到列表中,也可以按回车键(通过安装事件过滤器实现),将新文本加入到列表。

connect(button,SIGNAL(clicked()),this,SLOT(addWordToList()));

void Widget::addWordToList()
{
    if(edit->text().isEmpty())
        return;
    if (!edit->word_list.contains(edit->text())) {
       edit->word_list << edit->text();
    }
}

bool Widget::eventFilter(QObject *obj, QEvent *event)
{
  if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::NonClientAreaMouseMove) {
      QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
      if (mouseEvent->buttons() & Qt::LeftButton) {
          if (obj != edit->listView && obj != edit) {
              edit->listView->hide();
          }
      }
  }
  QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
  if (Qt::Key_Enter == keyEvent->key() || Qt::Key_Return == keyEvent->key()) {
      if (!edit->word_list.contains(edit->text())) {
         edit->word_list << edit->text();
      }
  }
  return QWidget::eventFilter(obj, event);
}



(2)变动

改变窗口的位置和大小,列表都要变动,都是通过事件相应在使下拉列表变宽,拉长或隐藏。

void Widget::moveEvent(QMoveEvent *event)
{
  if (!edit->listView->isHidden()) {
    edit->setCompleter(edit->text());
  }
}

void CompleteLineEdit::resizeEvent(QResizeEvent *event)
{
  if (!listView->isHidden()) {
    setCompleter(this->text());
  }
  QLineEdit::resizeEvent(event);
}




(4)键盘事件处理

void CompleteLineEdit::keyPressEvent(QKeyEvent *e) {
    if (!listView->isHidden()) {
        int key = e->key();
        int count = listView->model()->rowCount();
        QModelIndex currentIndex = listView->currentIndex();
        if (Qt::Key_Down == key) {
            // 按向下方向键时,移动光标选中下一个完成列表中的项
            int row = currentIndex.row() + 1;
            if (row >= count) {
                row = 0;
             }
            QModelIndex index = listView->model()->index(row, 0);
            listView->setCurrentIndex(index);
        } else if (Qt::Key_Up == key) {
            // 按向下方向键时,移动光标选中上一个完成列表中的项
            int row = currentIndex.row() - 1;
            if (row < 0) {
                 row = count - 1;
            }
            QModelIndex index = listView->model()->index(row, 0);
            listView->setCurrentIndex(index);
        } else if (Qt::Key_Escape == key) {
            // 按下Esc键时,隐藏完成列表
            listView->hide();
        } else if (Qt::Key_Enter == key || Qt::Key_Return == key) {
            // 按下回车键时,使用完成列表中选中的项,并隐藏完成列表
            if (currentIndex.isValid()) {
                QString text = listView->currentIndex().data().toString();
                setText(text);
            }
            listView->hide();
        } else if (Qt::Key_Delete == key) {
            QModelIndexList indexList = listView->selectionModel()->selectedRows();
            QModelIndex index;
            QString str;
            int i = 0;
            foreach(index, indexList) {
                str = index.data().toString();
                this->model->removeRow(index.row() - i);
                word_list.removeAll(str);
                ++i;
            }
        } else {
        // 其他情况,隐藏完成列表,并使用QLineEdit的键盘按下事件
            listView->hide();
            QLineEdit::keyPressEvent(e);
        }
    } else {
        QLineEdit::keyPressEvent(e);
    }
}
(5)匹配方式

void CompleteLineEdit::setCompleter(const QString &text) {
    if (text.isEmpty()) {
        listView->hide();
        return;
     }
    //if ((text.length() > 1) && (!listView->isHidden()))  return;

    // 如果完整的完成列表中的某个单词包含输入的文本,则加入要显示的完成列表串中
    QStringList tempStr;
    /*********匹配方式一*************/
//    foreach(QString word, word_list) {
//        if (word.contains(text, Qt::CaseInsensitive)) {
//          tempStr << word;
//        }
//    }
    /*********匹配方式二*************/
    foreach(QString word, word_list) {
        if (word.startsWith(text, Qt::CaseInsensitive)) {
            tempStr << word;
        }
    }

    model->setStringList(tempStr);
    listView->setModel(model);
// ...........
}
自定义匹配方式,可以startsWith定义为字符串是否与列表字符串的开头大小写匹配,也可以contains定义为列表字符串的包含匹配,还可以endsWith定义为字符串是否与列表字符串的结尾大小写匹配。总之根据需要完成规则匹配。

4、其他设计参考

可以设置为setWindowFlags(Qt::Popup),然后通过安装事件过滤器处理所有的事件请求。作为参考扩展知识点使用,下面的代码没有控制下拉列表框的高度,没有将新的文本加入到列表中。

#include "qfindedit.h"

QFindEdit::QFindEdit(QWidget *parent)
: QLineEdit(parent)
, m_bEditFocus(true)
{
	this->setWindowTitle("please input find word");
	m_stringListmodel = new QStringListModel(this);
	m_pFindWnd = new QListView(this);
    m_pFindWnd->setWindowFlags(Qt::Popup);
    m_pFindWnd->setEditTriggers(QAbstractItemView::NoEditTriggers);
    m_pFindWnd->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    m_pFindWnd->setSelectionBehavior(QAbstractItemView::SelectRows);
    m_pFindWnd->setSelectionMode(QAbstractItemView::SingleSelection);
//	m_pFindWnd->setParent(0, Qt::ToolTip);
    m_pFindWnd->setFocusPolicy(Qt::NoFocus);
    m_pFindWnd->setFocusProxy(this);
	
	m_stringList << "Biao" << "Bin" << "Huang" << "Hua" << "Hello" << "BinBin" << "Hallo";

	connect(this, SIGNAL(textEdited(const QString&)), this, SLOT(textEditedSlot(const QString&)));
	QObject::connect(m_pFindWnd, SIGNAL(clicked(QModelIndex)),
		this, SLOT(clickedSlot(QModelIndex)));
    QObject::connect(this, SIGNAL(activated(QModelIndex)),
        m_pFindWnd, SLOT(hide()));

	this->installEventFilter(this);
	m_pFindWnd->installEventFilter(this);

}

QFindEdit::~QFindEdit()
{
	delete m_pFindWnd;
}

QStringList& QFindEdit::stringList()
{
	return m_stringList;
}

void QFindEdit::showFindWnd(const QString& text)
{
	//效率较低,需要优化
	QStringList sl;  
	foreach(QString word, m_stringList) {  
		if (word.contains(text)) {  
			sl << word; 
		}  
	}

	if (sl.size() == 0) 
	{
		hideFineWnd();
		return;
	}
	m_stringListmodel->setStringList(sl);  
	m_pFindWnd->setModel(m_stringListmodel);  

	//高度需要优化
	m_pFindWnd->resize(rect().width(), 200);
	QPoint pTopleft = mapToGlobal(rect().bottomLeft());
	m_pFindWnd->move(pTopleft.x(), pTopleft.y());
	m_pFindWnd->show();
}

void QFindEdit::textEditedSlot(const QString& text)
{
	QString strText = text.trimmed();
	if (!strText.isEmpty())
	{
		showFindWnd(strText);
	}
	else
	{
		hideFineWnd();
	}
}

void QFindEdit::clickedSlot(QModelIndex modelIndex)
{
	setText(m_pFindWnd->model()->data(modelIndex).toString());
	hideFineWnd();
}

void QFindEdit::hideFineWnd()
{
	m_pFindWnd->hide();
}

bool QFindEdit::eventFilter(QObject *o, QEvent *e)
{
	if (m_bEditFocus && (o == this) && e->type() == QEvent::FocusOut) 
	{
        if (m_pFindWnd && m_pFindWnd->isVisible())
            return true;
	}

	if (o != m_pFindWnd) {
		return QLineEdit::eventFilter(o, e);
	}

	switch (e->type()) 
	{
	case QEvent::KeyPress: 
		{
            QKeyEvent *ke = static_cast<QKeyEvent *>(e);
            QModelIndex curIndex = m_pFindWnd->currentIndex();
			QModelIndexList selList = m_pFindWnd->selectionModel()->selectedIndexes();
			const int key = ke->key();

			if ((key == Qt::Key_Up || key == Qt::Key_Down) && selList.isEmpty() && curIndex.isValid() )
			{
				m_pFindWnd->setCurrentIndex(curIndex);
				return true;
			}

			switch (key) 
			{
			case Qt::Key_End:
			case Qt::Key_Home:
				if (ke->modifiers() & Qt::ControlModifier)
					return false;
				break;

			case Qt::Key_Up:
				if (!curIndex.isValid()) 
				{
					int rowCount = m_pFindWnd->model()->rowCount();
					QModelIndex lastIndex = m_pFindWnd->model()->index(rowCount - 1, m_pFindWnd->modelColumn());
					m_pFindWnd->setCurrentIndex(lastIndex);
					return true;
				} 
				else if (curIndex.row() == 0) 
				{
					return true;
				}
				return false;

			case Qt::Key_Down:
				if (!curIndex.isValid()) 
				{
					QModelIndex firstIndex = m_pFindWnd->model()->index(0, m_pFindWnd->modelColumn());
					m_pFindWnd->setCurrentIndex(firstIndex);
					return true;
				} 
				else if (curIndex.row() == m_pFindWnd->model()->rowCount() - 1) 
				{
					return true;
				}
				return false;
			}

            m_bEditFocus = false;
			this->event(ke);
			m_bEditFocus = true;
			if ( e->isAccepted() || !m_pFindWnd->isVisible()) {
				if (!this->hasFocus())
					hideFineWnd();
				if (e->isAccepted())
					return true;
			}

			switch (key) 
			{
			case Qt::Key_Return:
			case Qt::Key_Enter:
			case Qt::Key_Tab:
				hideFineWnd(); 
				if (curIndex.isValid()) 
				{  
					QString text = m_pFindWnd->currentIndex().data().toString();  
					setText(text);  
				}  
				break;

			case Qt::Key_F4:
				if (ke->modifiers() & Qt::AltModifier)
					hideFineWnd();
				break;

			case Qt::Key_Backtab:
			case Qt::Key_Escape:
				hideFineWnd();
				break;

			default:
				break;
			}

			return true;
		}
	case QEvent::MouseButtonPress: 
		if (!m_pFindWnd->underMouse()) 
		{
			hideFineWnd();
			return true;
		}
		return false;
	case QEvent::InputMethod:
	case QEvent::ShortcutOverride:
		QApplication::sendEvent(this, e);
		break;

	default:
		return false;
	}
	return false;
}


四、总结

(1)上述所有的代码已经打包上传到csdn上可登录下载(http://download.csdn.net/detail/taiyang1987912/7573181)。

(2)一般使用内部的QCompleter均能完成需求,特殊处理时才自定义规则。

(3)3.3中代码拖动窗口时,下拉列表不会隐藏,这个问题暂时解决不了。下拉列表的每一项的高度不知怎么获取,因此下拉列表框度比项的宽度要大。

(4)所用的Qt的库Qt4.6.2,GCC4.4.6 20120305 (Red Hat 4.4.6-4) 。系统是centos6.3。

(5)本人思路有限,若有更好的设计建议,也可发邮件沟通,在此先感谢!邮箱地址yang.ao@i-soft.com.cn。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: