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

用户界面

我不打算写个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<ihtmlelement> &amp; spElem,
		CComPtr<ihtmlelementcollection> &amp; spElemCollection );

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

对应的实现代码是

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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值