前两天,产品开发到WIFI模块了,PM丢给我一张图片,很简单,只有一个标签,再加一个按钮,大概就是下面这个样子。
简单吧
当WIFI在关闭状态下,整个页面就只有上面那俩控件,整个页面不可谓不简单。

但是作为没有需求文档的我,一看就傻眼了。这就好比甲方爸爸跟你说:我的需求非常简单,你给我开发一个手机淘宝出来就行了。

真有那么简单吗(黑人问号)?粗略地脑部了一下所有硬性需求和隐形需求之后,我问了PM一句:翻页的,还是滑动的?
PM回:滑动的,跟苹果那种一样。
“收到”

没办法,开发这件事,总得有个人难受。程序猿不难受,用户就得难受。PM不想难受,程序猿就得难受。程序猿不想难受,老板就难受,你这个月钱包也别想好受。

好吧,那就硬着头皮分析一下,这样一个看似无比简单的页面,到底需要哪些功能点。

首先介绍一下我的环境,Linux环境下使用Qt,交叉编译到ARM板上调试,ARM板自带WIFI模块,大部分Linux命令我都可以自由地使用。

首先,需要通过控制台命令来获取WIFI信息,按照图片上画的,至少要得到WIFI名称、信号强度和加密方式。获取WIFI的命令有很多,但是简单地调用命令获取WIFI信息文件无法直接使用,还需要提取到其中的字符串信息。假定我们通过一些命令,获取到了这些内容,还要通过代码将它们传递给主程序。做到这一点,前期的准备工作才算做完。

这一块的内容,可以参考我之前发的博客:ARM上搜索WIFI并解析字符串

顺便补充一下,自定义WIFI按钮的纯代码实现可以看我这篇博客:Qt自定义开关按钮控件

接下来才算正式开始。在第一次进入WIFI页面,WIFI按钮默认是关闭的,点击WIFI按钮,就有一大堆事情要处理了。首先,要搜索附近的WIFI,并显示到页面的下方。搜索并提取信息已经在上面处理了,我们只需要通过system命令在主程序中,逐行读取它们即可。

而接下来的显示就比较麻烦了,首先要有一个大致的布局。因为要实现拖动,所以需要建一个大的ScrollArea,然后在里面放一个widget,作为拖动的幕布。在这个widget里,需要逐个地塞入WIFI内容,它们是一个个小的widget,里面包含WIFI名、信号强度和加密方式这三种控件,并排板。用代码来说基本就是这样:

vLayout = new QVBoxLayout;
{
	gridLayout = new QGridLayout;
	
	wifiWidget[curLine] = new QWidget(this);
	
	gridLayout -> addWidget(wifiNameLabel[curLine],0,0,1,3);
	gridLayout -> addWidget(lockLabel[curLine],0,3,1,1);
	gridLayout -> addWidget(signLevelLabel[curLine],0,4,1,1);
	
	wifiWidget[curLine] -> setLayout(gridLayout);
	vLayout -> addWidget(wifiWidget[curLine]);
}
scrollWidget -> setLayout(vLayout);

因为ScrollArea自带边框和滚动条,如果要设计成苹果那种风格,就要修改ScrollArea的属性。

    scrollArea -> setFrameShape(QFrame::NoFrame);   //无边框
    scrollArea -> setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);   //关闭滚动条

到这里,差不多就有一个能看的WIFI页面了,此时还无法拖动。

接下来,就要设置显示在WIFI列表的内容。通过readLine()等方式,获取到WIFI名,传递到WIFI名 Label上。同理,获取信号强度,计算一下WIFI质量,并传递给信号Label对应的图片。最后看一下有没有加密,现在一般都是WPA2加密了,没有密码的WIFI不会解析到这样的内容。

然后就是仔细地排版,如果希望相邻的WIFI信息中间设置一个可见的间隔线,可以设置给widget设置stylesheet。

border-bottom: 1px solid black;

打开按钮暂时到这,接下来是关闭按钮

点击关闭按钮后,要断开WIFI,这里还是用system命令来操作。然后,隐藏ScrollArea和里面的widget,使页面恢复成之前只有两个控件的样子。

开闭按钮事件,建议写在一起并分别判断,然后把WIFI搜索和断开连接的方法分离。

接下来说鼠标事件
在WIFI按钮打开的状态下,用户可以点击任意的一条WIFI,进入输密码连接的页面。由于点击的不是button而是widget,没法用Qt里的槽函数,所以可以使用eventFilter来替代。
我们给每一个WIFI的widget安装eventFilter。

installEventFilter(this);

然后编辑eventFilter。
首先要通过某种鼠标事件触发eventFilter,一般来说鼠标的release事件最为适合。

顺便,前面的页面还不能拖动,为了实现拖动,我们需要修改鼠标事件,主要是控制坐标的变化。

void mouseMoveEvent(QMouseEvent *e)
{
    if (!m_bMousePressed)
    {
        return;
    }

    QPoint currentPt = e->pos();

    int dist = m_PressPosition.y() - currentPt.y();

    scrollArea->verticalScrollBar()->setValue(scrollArea->verticalScrollBar()->value() + dist);

    m_PressPosition = currentPt;

    keeping = true;
}

void mousePressEvent(QMouseEvent *e)
{
    m_bMousePressed = true;

    m_PressPosition = e->pos();
}

void mouseReleaseEvent(QMouseEvent *e)
{
    Q_UNUSED(e);
    m_bMousePressed = false;

    m_PressPosition.setX(0);
    m_PressPosition.setY(0);

    keeping = false;
}

这里的keeping,是用来监控鼠标是不是按住的状态的。如果不加这个,在判断press或者release事件会有问题,如果是press触发eventFilter,你会发现拖动时就直接跳转了;如果是release触发eventFilter,那么在拖动页面后,会立刻跳转到你一开始点击的那个wifi连接页。所以这样的一个监控flag必不可少。

有了鼠标事件,就可以编写eventFilter了。在eventFilter的最前面先加入keeping的判断,要是现在用户在keeping(拖动状态),那就不要响应,直接return。首先要获取到点击的是哪条WIFI,这一点我们可以通过监视obj->objectName().contains的widget名来确定。在前面定义WIFI widget的时候我们可以设置一个自定义的WIFI名,在里面加入WIFI的各种信息,然后监视点击的obj包含的WIFI名来确定点击的是哪个WIFI widget。确定了点击的WIFI后,下面就和写槽函数一样了,弹出输入密码的页面即可。

这个页面可以看做一个自定义的QMessageBox,里面包含输入密码的QLineEdit,加入、取消按钮、关闭按钮云云。关于自定义QMessageBox,我这篇博客里有介绍。
Qt自定义弹出窗口

这里说一下加入。同样需要通过system命令来控制。只是你需要把用户在WIFI页面选择的WIFI的名称,以及在此页面输入的密码传递进system命令中去,具体细节不多讲了,不难。

最后是记忆功能

为什么要记忆呢?页面的开关默认是关闭的,当用户连接WIFI成功,退出此页面,又一不小心重新进入此页面后,虽然WIFI还连通着,但是页面只有光秃秃的两个控件,用户肯定会黑人问号。因此需要记忆下用户在离开页面时的开关状态。
我们可以把开关的开闭状态利用QSettings写道配置文件里,关于这部分的操作,可以看我这篇博客。
Qt使用QSettings保存和读取用户信息

最关键的就是读写的时机,这个要根据实际情况来控制。此外,用户重新进入页面,而按钮为开状态,那么还要再搜索一遍WIFI并显示出来。

除此之外,还有一大堆细节问题。篇幅问题,这篇文章就介绍这么一个大体的框架,还有一些细节的功能点我没写进来。总之,这部分代码实际写起来,远比上面写的还要麻烦的多。而且由于没有文档,很多功能都是突然想到,然后一顿修改。

所以啊,尽管PM只给我看了两个控件,其中的逻辑却深不见底。很多看似简单的需求,那只是提出需求的人没有仔细思考、或者缺乏思考的能力,看不到问题的本质。

对于PM来说,一份尽量详尽的需求文档和概要设计文档,对于程序猿是很重要的,虽然程序猿大多时候不看这些文档,但这些文档相当于一份字典——软件开发的字典。只有全盘理解了其中的内容,才能快速地形成软件架构,写出漂亮的、易维护、高重用的代码。

至于这部分的代码,我就不提供了,公司保密要求,呵呵。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐