用户界面

我不打算写个Windows界面。因为这个软件的全部就是个浏览器。我准备将”浏览器“进行到底,所以我选择使用html作为我们的用户界面。我也并不打算从头开始写一个浏览器,我使用了《内嵌IE网页窗口中消除IE默认脚本设置影响的方法》中基于WTL的浏览器代码。(转载请指明出于breaksoftware的csdn博客)界面如下

我观察了下12306的页面,它预留了5个乘客信息,所以我也就预留了5个乘客信息。因为我不会写HTML和javascript,我就从12306中copy出相应的页面元素,并加以修改。在此感谢下12306网页设计同学,帮我完成了不少我不会的东西。

一般来说,我们可能一次性不会购买5个人的票。所以在上面的界面中,你想填多少人信息就填多少人信息,我会在代码中读取这些人的信息。当我们填完乘客信息后,我们要输入车次信息。然后我们要点击一下确定,我会在代码中捕获点击确定的操作,并将已经填写的信息读入内存。在之后的抢票过程中,我们将使用到这些信息。最后,我们就要点击最下面那个超链接,跳转到12306这个页面,开始我们真正的抢票工作。

我们来看一下源代码。首先是界面的,我列一个人的信息代码出来:

<tr class="passenger_class" id="passenger_1"><td style="width: 6%"><div id="passenger_1_index">第1位</div></td><td id="seat"><select><option value="1">硬座</option><option value="2">软座</option><option value="3">硬卧</option><option value="4">软卧</option><option value="6">高级软卧</option><option value="M">一等座</option><option value="O">二等座</option><option value="P">特等座</option><option value="9">商务座</option></select> </td><td style="width: 20%"><label><strong>请确认所选车次有该坐席</strong> </label></td><td id="ticket"><select><option value="1">成人票</option><option value="2">儿童票</option><option value="3">学生票</option><option value="4">残军票</option></select></td><td id="name"><input name="passenger_1_name" type="text" id="passenger_1_name" size="12" maxlength="20" class="input_20txt" value=""/></td><td id="cardtype"><select><option value="1">二代身份证</option><option value="2">一代身份证</option><option value="C">港澳通行证</option><option value="G">台湾通行证</option><option value="B">护照</option></select></td><td id="cardno"><input name="passenger_1_cardno" type="text" id="passenger_1_cardno" size="20" maxlength="35" style="text-transform: uppercase;" class="input_20txt" value=""/></td><td id="mobileno"><input name="passenger_1_mobileno" type="text" id="passenger_1_mobileno" size="11" maxlength="20" class="input_20txt" value=""/></td>
</tr>

因为我并不知道用户选择的车次有什么类型的座位,所以我将所有的座位都列了出来。

     <select><option value="1">硬座</option><option value="2">软座</option><option value="3">硬卧</option><option value="4">软卧</option><option value="6">高级软卧</option><option value="M">一等座</option><option value="O">二等座</option><option value="P">特等座</option><option value="9">商务座</option></select> 

这儿要特别注意下所有option的value字段,这些值不是我乱取的。而是我检查了12306页面的很多火车信息后收集到的。我们会在之后记录用户所选席别时,记录这些值,因为这些值将在操作12306页面时派上用场。

其他元素应该没什么可以解释的,只是要注意所有Select下的Option的Value值和12306上对应的元素的Value值一致。

我们保存单个用户的结构体是

struct StSinglePassengerInfo{ListCString ListSeat;CString cstrTicket;CString cstrName;CString cstrCardtype;CString cstrCardNo;CString cstrMobileNo;
};

注意一下ListSeat这个字段,这个字段保存的一个CString的队列。它记录着一系列席别代码。在我最开始设计这个软件时,我是希望用户可以选择一系列可以接受的席别,同时是按优先级关系排列。这样可以最大程度上满足用户的需求。但是我已无心把这个功能继续做下去,所以设计界面时,只能让用户选择一个席别。

还有一个需要我们关注的是“确定”超链接的代码

<td><a style="width: 60px;" href="http://settingok">确定</a>
</td>

我们点击“确定”按钮后,页面理论上要跳转到“http://settingok”这个页面。而实际上,我们只是利用“跳转”这个操作,让我们的C++代码中捕获到用户已经设置OK了。我们并不希望页面真的发生跳转。所以我们对BeforeNavigate2消息映射函数做了处理,让跳转到“http://settingok”的请求终止,并读取用户设置的乘客信息和车次信息。

void CBrowserHost::BeforeNavigate2(IDispatch *pDisp, VARIANT *url,VARIANT *Flags, VARIANT *TargetFrameName, VARIANT *PostData,VARIANT *Headers, VARIANT_BOOL *Cancel)
{do  {if ( NULL != url ) {CString cstrUrl((LPWSTR)(url->bstrVal));if ( 0 == cstrUrl.CompareNoCase(SETTINGOK) ) {*Cancel = VARIANT_TRUE;CComPtr<IWebBrowser2> spWeb;HRESULT hr = pDisp->QueryInterface(IID_IWebBrowser2, (LPVOID*)&spWeb);CHECKHRPOINTER(hr, spWeb);CComPtr<IDispatch> dispDoc;hr = spWeb->get_Document(&dispDoc);CHECKHRPOINTER(hr, dispDoc);CComPtr<IHTMLDocument2> spDoc;hr = dispDoc->QueryInterface( IID_IHTMLDocument2, (LPVOID*)&spDoc);CHECKHRPOINTER(hr, spDoc);StTrainNoPassengerInfo stTrainPassenger;hr = m_dealSettingPage.GetTrainNoPassengersInSettingPage(spDoc, stTrainPassenger);hr = m_AutoMan.SetTrainNoPassengers(stTrainPassenger);}……}……} while (0);
}

上面代码中m_dealSettingPage是我处理页面的类CDeal12306WebPage的对象。GetTrainNoPassengersInSettingPage将解析网页保存乘客和车次信息。m_AutoMan是我们之前说的“人”线程,此时我们将告诉该线程所有信息,让它准备开始工作。

HRESULT CDeal12306WebPage::GetTrainNoPassengersInSettingPage( CComPtr<IHTMLDocument2> & spDoc,StTrainNoPassengerInfo & stTrainPassenger )
{HRESULT hr = E_FAIL;do {CComPtr<IHTMLElement> spBody;hr = spDoc->get_body(&spBody);CHECKHRPOINTER(hr, spBody);CComPtr<IHTMLElement> spTable;hr = GetElementByID(spBody, L"passengertable", spTable);CHECKHRPOINTER(hr, spTable);CComPtr<IHTMLElement> spTBody;hr = GetElementByIndex( spTable, 0, spTBody);CHECKHRPOINTER(hr, spTBody);for ( int i = 0; i < MAXPASSENGERCOUNT; i++ ) {CString cstrTrID;cstrTrID.Format(PASSENGERID, i + 1);CComPtr<IHTMLElement> spTr;hr = GetElementByID( spTBody, cstrTrID, spTr);CHECKHRPOINTER(hr, spTr);StSinglePassengerInfo stSinglePassenger;hr = GetPassengerInfo(spTr, stSinglePassenger);CHECKHR(hr);if ( FALSE == stSinglePassenger.cstrName.IsEmpty() &&FALSE == stSinglePassenger.cstrTicket.IsEmpty() &&FALSE == stSinglePassenger.cstrCardNo.IsEmpty() &&FALSE == stSinglePassenger.cstrCardtype.IsEmpty() &&FALSE == stSinglePassenger.cstrMobileNo.IsEmpty() &&0 != stSinglePassenger.ListSeat.size()) {stTrainPassenger.vecPassengerInfo.push_back(stSinglePassenger);}}hr = GetTrainNoInSettingPage(spDoc, stTrainPassenger.cstrTrainNo);} while (0);return hr;
}

这段代码大致意思是在“设置”页面中,找到id为passengertable的元素spTable,然后找到spTable下第一个元素spTBody。spTBody下保存着每个乘客的信息,其中第一个乘客信息保存在id是“passenger_1”的元素下,第二个保存在“passenger_2”元素下……当单个乘客所有信息都不为空时,将其保存在一个stTrainPassenger.vecPassengerInfo中。最后我们要获取火车车次的信息,将其保存在stTrainPassenger.cstrTrainNo中。
        上面的函数大部分是经过封装的。其中几个经常用的函数是

 HRESULT GetElementCollection(CComPtr & spElem,CComPtr & spElemCollection );enum EQUERYTYPE {EID,ETAGNAME,ECLASSNAME,};// 通过ID获取指定节点下第一个ID为cstrID的子节点HRESULT GetElementByID(CComPtr<IHTMLElement> & spElem, const CString & cstrID,CComPtr<IHTMLElement> & spResElem);// 通过ClassName获取指定节点下第一个class为cstrClassName的子节点HRESULT GetElementByClassName(CComPtr<IHTMLElement> & spElem, const CString & cstrClassName,CComPtr<IHTMLElement> & spResElem);// 通过TagName获取指定节点下第一个tag为cstrTagName的子节点HRESULT GetElementByTagsName(CComPtr<IHTMLElement> & spElem, const CString & cstrTagName,CComPtr<IHTMLElement> & spResElem);// 通过ID获取指定节点下第lindex子节点HRESULT GetElementByIndex(CComPtr<IHTMLElement> & spElem, LONG lIndex,CComPtr<IHTMLElement> & spResElem);HRESULT GetElement(CComPtr & spElem, EQUERYTYPE eType, const CString & cstrValue,CComPtr & spResElem);& spElem,CComPtr & spElemCollection );enum EQUERYTYPE {EID,ETAGNAME,ECLASSNAME,};// 通过ID获取指定节点下第一个ID为cstrID的子节点HRESULT GetElementByID(CComPtr<IHTMLElement> & spElem, const CString & cstrID,CComPtr<IHTMLElement> & spResElem);// 通过ClassName获取指定节点下第一个class为cstrClassName的子节点HRESULT GetElementByClassName(CComPtr<IHTMLElement> & spElem, const CString & cstrClassName,CComPtr<IHTMLElement> & spResElem);// 通过TagName获取指定节点下第一个tag为cstrTagName的子节点HRESULT GetElementByTagsName(CComPtr<IHTMLElement> & spElem, const CString & cstrTagName,CComPtr<IHTMLElement> & spResElem);// 通过ID获取指定节点下第lindex子节点HRESULT GetElementByIndex(CComPtr<IHTMLElement> & spElem, LONG lIndex,CComPtr<IHTMLElement> & spResElem);HRESULT GetElement(CComPtr & spElem, EQUERYTYPE eType, const CString & cstrValue,CComPtr & spResElem);
 

对应的实现代码是

HRESULT CDeal12306WebPage::GetElementCollection( CComPtr<IHTMLElement> & spElem, CComPtr<IHTMLElementCollection> & spElemCollection )
{HRESULT hr = S_FALSE;do {CComPtr<IDispatch> spDispatch;hr = spElem->get_children(&spDispatch);CHECKHR(hr);hr = spDispatch->QueryInterface( IID_IHTMLElementCollection, (LPVOID*)&spElemCollection);CHECKHR(hr);} while (0);return hr;
}HRESULT CDeal12306WebPage::GetElementByID( CComPtr<IHTMLElement> & spElem, const CString & cstrID, CComPtr<IHTMLElement> & spResElem )
{return GetElement( spElem, EID, cstrID, spResElem );
}HRESULT CDeal12306WebPage::GetElementByClassName( CComPtr<IHTMLElement> & spElem, const CString & cstrClassName, CComPtr<IHTMLElement> & spResElem )
{return GetElement( spElem, ECLASSNAME, cstrClassName, spResElem );
}HRESULT CDeal12306WebPage::GetElementByTagsName( CComPtr<IHTMLElement> & spElem, const CString & cstrTagName, CComPtr<IHTMLElement> & spResElem )
{return GetElement( spElem, ETAGNAME, cstrTagName, spResElem );
}HRESULT CDeal12306WebPage::GetElementByIndex( CComPtr<IHTMLElement> & spElem, LONG lIndex, CComPtr<IHTMLElement> & spResElem )
{HRESULT hr = E_FAIL;do {CComPtr<IHTMLElementCollection> spElemCollecion;hr = GetElementCollection( spElem, spElemCollecion);CHECKHR(hr);LONG lCollecionCount = 0;hr = spElemCollecion->get_length(&lCollecionCount);CHECKHR(hr);if ( lCollecionCount < lIndex + 1) {break;}CComVariant VarIndex = lIndex;CComPtr<IDispatch> spDisp;hr = spElemCollecion->item(VarIndex, VarIndex, &spDisp);CHECKHRPOINTER(hr,spDisp);hr = spDisp->QueryInterface(IID_IHTMLElement, (LPVOID*)&spResElem);} while (0);return hr;
}HRESULT CDeal12306WebPage::GetElement( CComPtr<IHTMLElement> & spElem, EQUERYTYPE eType, const CString & cstrValue, CComPtr<IHTMLElement> & spResElem )
{HRESULT hr = E_FAIL;do {CComPtr<IHTMLElementCollection> spElemCollection;hr = GetElementCollection( spElem, spElemCollection);CHECKHRPOINTER(hr,spElemCollection);LONG lCollecionCount = 0;hr = spElemCollection->get_length(&lCollecionCount);CHECKHR(hr);for ( long i = 0; i < lCollecionCount; i++ ) {CComVariant VarIndex = i;CComPtr<IDispatch> spDispatchElem;hr = spElemCollection->item( VarIndex, VarIndex, &spDispatchElem );CHECKHRPOINTER(hr,spDispatchElem);CComPtr<IHTMLElement> spElem;hr = spDispatchElem->QueryInterface(IID_IHTMLElement, (LPVOID*)& spElem );CHECKHRPOINTER(hr, spElem);CComBSTR bstrValue;switch (eType) {case EID: {hr = spElem->get_id(&bstrValue);}break;case ETAGNAME: {hr = spElem->get_tagName(&bstrValue);}break;case ECLASSNAME: {hr = spElem->get_className(&bstrValue);}break;default:break;}CString cstrV((LPWSTR)bstrValue);if ( 0 == cstrV.CompareNoCase( cstrValue )) {spResElem = spElem;break;}}} while (0);return hr;
}

在获取乘客和车次信息时用到的其他封装函数的实现是

HRESULT CDeal12306WebPage::GetPassengerInfo( CComPtr<IHTMLElement> & spElem, StSinglePassengerInfo & stSinglePassenger )
{HRESULT hr = E_FAIL;do {CString cstrSeat;hr = GetOptionValueHelper(spElem, L"seat", cstrSeat);CHECKHR(hr);stSinglePassenger.ListSeat.push_back(cstrSeat);hr = GetOptionValueHelper(spElem, L"ticket", stSinglePassenger.cstrTicket );CHECKHR(hr);hr = GetOptionValueHelper(spElem, L"cardtype", stSinglePassenger.cstrCardtype);CHECKHR(hr);hr = GetInputValueHelper(spElem, L"name", stSinglePassenger.cstrName);CHECKHR(hr);hr = GetInputValueHelper(spElem, L"cardno", stSinglePassenger.cstrCardNo);CHECKHR(hr);hr = GetInputValueHelper(spElem, L"mobileno", stSinglePassenger.cstrMobileNo);} while (0);return hr;
}HRESULT CDeal12306WebPage::GetOptionValueHelper( CComPtr<IHTMLElement> & spElem, const CString& cstrID, CString& cstrValue )
{HRESULT hr = E_FAIL;do {CComPtr<IHTMLElement> spTd;hr = GetElementByID(spElem, cstrID, spTd);CHECKHRPOINTER(hr, spTd);CComPtr<IHTMLElement> spSel;hr = GetElementByIndex(spTd, 0, spSel);CHECKHRPOINTER(hr, spSel);CComPtr<IHTMLSelectElement> spSelect;hr = spSel->QueryInterface(IID_IHTMLSelectElement, (LPVOID*)&spSelect);CHECKHRPOINTER(hr, spSelect);CComBSTR bstrValue;hr = spSelect->get_value(&bstrValue);CHECKHR(hr);cstrValue = bstrValue;} while (0);return hr;
}HRESULT CDeal12306WebPage::GetInputValueHelper( CComPtr<IHTMLElement> & spElem,const CString& cstrID, CString & cstrValue )
{HRESULT hr = E_FAIL;do {CComPtr<IHTMLElement> spTd;hr = GetElementByID(spElem, cstrID, spTd);CHECKHRPOINTER(hr, spTd);CComPtr<IHTMLElement> spInput;hr = GetElementByIndex(spTd, 0, spInput);CHECKHRPOINTER(hr, spInput);CComPtr<IHTMLInputElement> spInputElem;hr = spInput->QueryInterface(IID_IHTMLInputElement, (LPVOID*)&spInputElem);CHECKHRPOINTER(hr, spInputElem);CComBSTR bstrValue;hr = spInputElem->get_value(&bstrValue);CHECKHR(hr);cstrValue = bstrValue;} while (0);return hr;
}HRESULT CDeal12306WebPage::GetTrainNoInSettingPage( CComPtr<IHTMLDocument2> & spDoc, CString & cstrValue )
{HRESULT hr = E_FAIL;do {CComPtr<IHTMLElement> spBody;hr = spDoc->get_body(&spBody);CHECKHRPOINTER(hr, spBody);CComPtr<IHTMLElement> spTable;hr = GetElementByID(spBody, L"trainnotable", spTable);CHECKHRPOINTER(hr, spTable);CComPtr<IHTMLElement> spTBody;hr = GetElementByIndex( spTable, 0, spTBody);CHECKHRPOINTER(hr, spTBody);CComPtr<IHTMLElement> spTr;hr = GetElementByIndex(spTBody, 0, spTr);CHECKHRPOINTER(hr, spTr);CComPtr<IHTMLElement> spTd;hr = GetElementByID(spTr, L"trainno", spTd);CHECKHRPOINTER(hr, spTd);CComPtr<IHTMLElement> spInput;hr = GetElementByIndex(spTd, 0, spInput);CHECKHRPOINTER(hr, spInput);CComPtr<IHTMLInputElement> spInputElem;hr = spInput->QueryInterface(IID_IHTMLInputElement, (LPVOID*)&spInputElem);CHECKHRPOINTER(hr, spInputElem);CComBSTR bstrValue;hr = spInputElem->get_value(&bstrValue);CHECKHR(hr);cstrValue = bstrValue;} while (0);return hr;
}

如何定制一款12306抢票浏览器——用户界面相关推荐

  1. 如何定制一款12306抢票浏览器——完结篇

    差不多花了一个星期的业余时间去完成了相关的编码.最后也只是使用了5篇文章将整个大题流程和使用的关键技术介绍了一下.其中有很多酸甜苦辣,其中记忆最为深刻的就是我对图像做了处理后,tesseract-oc ...

  2. 如何定制一款12306抢票浏览器——启动“人”线程

    启动"人"线程 在<如何定制一款12306抢票浏览器--构架>一文中,我们提到"人"线程.对于熟悉Window编程的同学来说,线程间通信和信息传递不 ...

  3. 如何定制一款12306抢票浏览器——构架

    快春节了,火车票一票难求.虽然黄牛市场冷淡了,但是互联网"娱乐界"却越来越闹腾了.先是猎豹等浏览器推出抢票专版(插件),然后是铁道部约谈金山,之后流传工信部叫停抢票插件,之后再是工 ...

  4. 如何定制一款12306抢票浏览器——实现自动查询和预订功能

    检查是否进入订票页面 判断是否进入订票页面,我是确定了两个标准:(转载请指明出于breaksoftware的csdn博客) 1 网址是否为http://www.12306.cn/mormhweb/ky ...

  5. 如何定制一款12306抢票浏览器——处理预订页面和验证码自动识别功能

    判断是否进入预订页面 我们先看一下预订页面的结构(转载请指明出于breaksoftware的csdn博客) 可以见得,这个页面也是嵌入了两个IFrame.关于IFrame的跨域问题,我已经在前一篇文章 ...

  6. 分享12306抢票心得-终极秒杀思路篇

    12306抢票的关键拼的就是整点出票的速度,快的几秒钟,慢的几分钟,本文提供终极抢票攻略,通过多线程扫描上万个CDN,来大幅度提升出票速度. 准备一:需要了解CDN和切站的机制,请参考: 分享1230 ...

  7. python编程实践(3):python+selenium实现12306抢票脚本

    又到了一年一度的春运时节,抢个票? 1.设计思路 如果我们要买一张火车票,我们会怎么做?打开12306,登陆,输入出发地和目的地,选择出行日期,然后点击查询,有余票的话就下单购买,没有票就点刷新或者等 ...

  8. 12306 抢票,极限并发带来的思考

    点击上方"蓝色字体",选择"置顶或者星标" 你关注的就是我关心的! 来源:https://juejin.im/post/5d84e21f6fb9a06ac824 ...

  9. 从12306抢票极限并发到秒杀系统核心架构

    12306抢票,极限并发带来的思考? 每到节假日期间,一二线城市返乡.外出游玩的人们几乎都面临着一个问题:抢火车票!虽然现在大多数情况下都能订到票,但是放票瞬间即无票的场景,相信大家都深有体会.尤其是 ...

最新文章

  1. ACM 竞赛高校联盟 练习赛 第二场 BC
  2. where 1=1 是什么鬼?
  3. docker环境给elasticsearch安装ik分词
  4. 【机器学习】用摸鱼学来解释隐马尔可夫模型(HMM)
  5. 6-7 使用函数输出水仙花数_「Java」再议printf函数
  6. log4j 禁止类输出日志_log4j 2过滤spring日志遇到的问题
  7. 【机器视觉学习笔记】伽马变换(C++)
  8. matlab可以使用词云分析吗,利用豆瓣短评数据生成词云
  9. 域名可以转让注册人吗_网店可以转让吗?
  10. 【檀越剑指大厂--jvm】jvm总结
  11. 本地SVN帐号密码破解
  12. VMware安装win7操作系统
  13. 3.8086/8088微处理器结构
  14. signature=4a882a48c4a4b2b41835e11b6fafa69f,ABB 38SC980002R375
  15. 【DP】HDU6357 Hills And Valleys
  16. VR游戏开发干货教程:如何创建一个VR项目
  17. selenium自动化测试-鼠标键盘操作
  18. Windows 11系统设置文件夹默认显示方式为“大图标”的方法
  19. Craps 赌博游戏 含押注
  20. 控制类(Controller)

热门文章

  1. 刺客信条奥德赛无法加载库_点评刺客信条起源、奥德赛、英灵殿,哪个最好玩?...
  2. Halcon:(2)电路板检测实验
  3. PointNet++:(1)网络完成的任务分析
  4. 【物料钢卷识别与定位】:1.读取数据(获取原始数据)
  5. 力扣(LeetCode)刷题,简单题+中等题(第17期)
  6. 【camera】2.相机成像原理和数学模型
  7. 微信支付android不弹出支付密码窗口,微信支付没弹出支付窗口
  8. 吴恩达神经网络和深度学习——第四周笔记
  9. Easy3D:一个轻量级、易用、高效的C++库,用于处理和渲染3D数据
  10. CG游戏道具全流程制作视频教程 Artstation – Stylized Game Asset