Qt自定义控件--pagenavigation(页面导航)

以前在使用自定义的table组件的时候,因为数据量比较大,一次性全部显示的话界面会造成卡顿的现象,分页显示的话,就需要一个很常见的组件–页面导航。虽然用了现成的,但用了别人的,总会有一I些不舒服。

这次在自定义 DateTime 组件的时候,突然对页面导航也有了一些想法,这不立马就安排上了。

先看下效果:
Qt自定义控件--pagenavigation(页面导航)_第1张图片

我们在前面自定义 DateTime 组件的时候,不是根据不同信号在不停地刷新 42 个 button 的显示状态,那么页面导航不也是一样的道理吗?所以就有了下面的这个按钮类。


class ButtonNumber : public QPushButton
{
	Q_OBJECT
	Q_PROPERTY(int id READ id WRITE setId DESIGNABLE true)
	Q_PROPERTY(QVariant data READ data WRITE setData DESIGNABLE true)
	
public:
	explicit ButtonNumber(QWidget *parent = 0);
	explicit ButtonNumber(int id, QWidget *parent = 0);
	~ButtonNumber();
    //...
	PageNavigation::Display role() const
	{
		return m_role;
	}

	void setRole(const PageNavigation::Display& role);
private:

	int m_id{ -1 };
	QVariant m_data{ QVariant() };
	PageNavigation::Display m_role{ PageNavigation::INVAILD };
	PageNavigation::NumberType m_type{ PageNavigation::NORMAL_NUMBER };
};

同样的用了一个枚举进行按钮样式的转换。

void ButtonNumber::setRole(const PageNavigation::Display& role)
{
	m_role = role;

	QString property = "";
	if (role.testFlag(PageNavigation::PAGE_NORMAL))
	{
		property = "normal-number";     //普通的数字
	}
	if (role.testFlag(PageNavigation::PAGE_GUIDE_LEFT))
	{
		property = "guide-left";       //向左的跳跃,每次跳跃为5
	}
	if (role.testFlag(PageNavigation::PAGE_GUIDE_RIGHT))
	{
		property = "guide-right";       //向由的跳跃,每次跳跃为5
	}
	if (role.testFlag(PageNavigation::SELECT_NUMBER))
	{
		property = "select-number";     //当前选中的页码
	}

	setProperty("type", property);

	style()->unpolish(this);
	style()->polish(this);
}

然后我画了画,至少我们得需要11个按钮,左右各一个用来进行下一页或者前一页的切换,这个是最普遍存在的需求,不容改变的。

中间有9个按钮,用来显示页码。为什么是9个呢? 因为九九归一,因为好看。。。

下面是页面导航类的定义,这个类的定义其实是很简单的,只有几个公共接口。

class PageNavigationWidget : public QWidget
{
    Q_OBJECT

public:
    explicit PageNavigationWidget(QWidget *parent = nullptr);
    ~PageNavigationWidget();

	void setTotal(int total);

	int total() const
	{
		return m_total;
	}

	int current() const
	{
		return m_current;
	}

	PageNavigationWidget(const PageNavigationWidget& other) = delete;
	PageNavigationWidget& operator=(const PageNavigationWidget& other) = delete;
	
private:
    void initPage();
	void updateUi();

signals:
	void signal_page(int index);

private:
    Ui::PageNavigationWidget *ui;
	QList<QVariant> m_btn{};

	int m_total{ 0 };
	int m_current{ 0 };
};

设置数据的总数,因为只有知道了数据的总数,你才能根据每页的行数和总数计算出页码的总数。

void setTotal(int total);

void PageNavigationWidget::setTotal(int total)
{
	m_total = total % PageNavigation::CURRENT_PAGE_NUMBER ? total / PageNavigation::CURRENT_PAGE_NUMBER + 1 : total / PageNavigation::CURRENT_PAGE_NUMBER;
	m_current = m_total < m_current ? m_total : m_current;
	if (m_total > 0 && m_current == 0)
	{
		m_current = 1;
	}
	ui->lbTotal->setText(QString::fromLocal8Bit("共 %1 页 %2 行.").arg(m_total).arg(total));
	updateUi();
}

还有一个信号,这个信号主要是为了在点击了按钮之后,向调用者返回当前页码的序号,以便调用者进行相应的数据切换和展现。

void signal_page(int index);

做这个之前,我的想法是这样的:

  • 界面上存在固定数量的按钮,这些按钮分两类,一类是普通的按钮,功能单一;另一类是显示页码的按钮,这些按钮会根据点击或者其他的改变进行刷新
  • 每次刷新界面,也就意味这需要给调用者发送信号告诉调用者当前选择的是哪个页码
  • 页面的数量可能会远大于固定的按钮数量,因此必须要有跳跃式的翻页功能
  • 第一个按钮始终显示1,最后一个按钮始终显示最后一页,其他的按钮进行页面刷新

按照上面的步骤,就会很容易写出代码,这也是我们常说的,做事情之前,一定要先想清楚,想清楚了,做起来就会很简单。

就像爱因斯坦说过的,如果给我1个小时解答一道决定我生死的问题,我会花55分钟弄清楚这道题到底在问什么。一旦清楚它到底在问什么,剩下的5分钟足够回答这个问题。

所以在类的构造中,先准备好了固定数量的按钮。并且每个按钮都关联了槽函数。

for (int index = 0; index < PageNavigation::BUTTON_NUMBER; ++index)
{
    auto* btn = new ButtonNumber;
    btn->setFixedSize(QSize(30, 30));
    
    ui->horiPage->addWidget(btn);
    m_btn.append(QVariant::fromValue(static_cast<void*>(btn)));
    
    connect(btn, &QPushButton::clicked, this, [this](){});
    ...
}

接下来是两个普通按钮的功能以及无限制跳跃的功能,这三个的功能基本都是改变当前页面,刷新界面,发送信号。大致如下:

connect(ui->spinPage, &QSpinBox::editingFinished, this, [this]()
{
    int val = ui->spinPage->value();
    m_current = val > total() ? total() : val;
    emit signal_page(m_current);
    updateUi();
});

需要注意的是,普通按钮左右不要越界。

接下来就是页面按钮的更新,这个我是这样想的:

  • 每次更新前,先将所有按钮初始化
  • 如果页面的总数小于9,就按照页面的数量来,剩余的按钮进行隐藏处理
  • 如果页面的总数大于10,并且当前页面小于8,就意味着左边不需要跨页的按钮,右边需要,相反,当前页面 + 7 小于总页数时,刚好相反
  • 如果当前页面刚好介于这两个上面的中间时,左右都需要跨页的按钮

按照上面的顺序开始了代码编写。首先,将所有按钮初始化:

for (const auto& button : m_btn)
{
	auto* btn = static_cast<ButtonNumber*>(button.value<void*>());
	if (Q_NULLPTR == btn)
	{
		continue;
	}

	btn->setRole(PageNavigation::PAGE_NORMAL);

	btn->setVisible(true);
}

紧接着进行第一步的判断。

if (total() <= PageNavigation::BUTTON_NUMBER)
{
	for (int index = 0; index < m_btn.size(); ++index)
	{
		auto* btn = static_cast<ButtonNumber*>(m_btn[index].value<void*>());
		if (Q_NULLPTR == btn)
		{
			continue;
		}

		btn->setData(index + 1);
		btn->setType(PageNavigation::NORMAL_NUMBER);
		btn->setRole(m_current == btn->data().toInt() ? PageNavigation::SELECT_NUMBER : PageNavigation::PAGE_NORMAL);

		if (index < total())
		{
			continue;
		}
		btn->setVisible(false);
	}
}

接下来是当前页面小于7的,并且总数大于10,这个需要设置右边的跨页。

else if (m_current < 8)
{
	for (int index = 0; index < 7; ++index)
	{
		auto* btn = static_cast<ButtonNumber*>(m_btn[index].value<void*>());
		if (Q_NULLPTR == btn)
		{
			continue;
		}

		btn->setData(index + 1);
		btn->setType(PageNavigation::NORMAL_NUMBER);
		btn->setRole(m_current == btn->data().toInt() ? PageNavigation::SELECT_NUMBER : PageNavigation::PAGE_NORMAL);
	}

	auto* btn = static_cast<ButtonNumber*>(m_btn[7].value<void*>());
	btn->setData("");
	btn->setType(PageNavigation::GUIDE_RIGHT_NUMBER);
	btn->setRole(PageNavigation::PAGE_GUIDE_RIGHT);

	btn = static_cast<ButtonNumber*>(m_btn[8].value<void*>());
	btn->setData(total());
	btn->setType(PageNavigation::NORMAL_NUMBER);
	btn->setRole(PageNavigation::PAGE_NORMAL);
}

后面两部是相差不大的,只不过是调换了一下跨页显示按钮的方向和两者均显示。代码就不贴上去了。

完成之后,测试代码,效果大致在预期之内,只有两点,就是在当前小于8和当前页 + 7 小于总数这两个 case 下,如果我们点击了7或者3,是不会更新界面的,也就是下一步或者前一页的按钮没了,而这个时候我们总是期望下一页或前一页的按钮能自动显示,所以修改了下数字,将上面的 8 和 7 分别减少了 1 。修改后,效果可以达到我们的预期。

至此,自定义的 PageNavigation 组件就完成了。

测试代码

你可能感兴趣的:(Qt,qt,c++,开发语言,自定义控件,页面导航)