用户界面
我不打算写个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;
}