Windows Phone 7 Web Browser 控制項應用 Part 1

前些日子,有關注我Blog的朋友應該都已經注意到,我發表了Web Browser (Ph) Application上架Marketplace的相關資訊,也分享了退件及通過審核的一些過程。

現在讓我將開發這個應用程式的經驗與大家分享。

Understanding Web Browser Control of Windows Phone 7 – Part 1

 

 

/黃忠成

 

 

The Web Browser (Ph) Application

 

 

   前些日子,有關注我Blog的朋友應該都已經注意到,我發表了Web Browser (Ph) Application上架Marketplace的相關資訊,也分享了退件及通過審核的一些過程。

現在讓我將開發這個應用程式的經驗與大家分享。

 

   除了個人懶惰沒看清認證的一些規則導致退件的經驗之外,Web Browser(Ph)於開發中有兩個問題是最重要的,第一個當然是中文輸入法,此問題的解法其實很簡單,

就是必須要擁有相關的字及注音碼,接著就只要做介面就好了,如果對此有興趣及需要,我建議讀者們參考David老師與光岩資訊提供的控制項(因為我的字庫其實並不完整)。

 

   第二個問題就是,Web Browser (Ph) 是如何知道,使用者點選到了某個輸入框? 又是如何將輸入的中文字放到輸入框裡? Web Browser控制項其實用法分為簡易

與深入兩種,簡易的將其拿來做為顯示某個網頁用,深度的用法則是在顯示網頁後,嘗試將整個網頁的控制權拿在手裡,Web Browser (Ph)即是深度的用法。

 

   OK,那麼Web Browser(Ph)如何知道使用者點選到了某個輸入框?很簡單,Web Browser(Ph)有個函式: InvokeScript,可以讓開發者在Web Browser讀入網頁後,

呼叫網頁中的某個JavaScript,原意是想讓開發者在使用NavigateToString、Navigate瀏覽本機網頁時能有個溝通的管道。

   而Web Browser(Ph)將此技術發揮到極致,經測試得知,InvokeScript其實不僅只能在瀏覽本機網頁,瀏覽一般網頁時也可正常運作,這意味著Web Browser(Ph)

可以在使用者瀏覽網頁後,以InvokeScript來呼叫特定的JavaScript函式,那這有什麼用呢?

 

   細想一下,有哪個JavaScript函式可以讓你列舉出網頁上所有的input type=text控制項?  document.getElementsByTagName !!  但此路不通,因為InvokeScript僅能呼叫函式,

像getElementsByTagName這種掛在某個物件下的函式是無法透過InvokeScript呼叫的。

 

   所以我們得找另一條路,有哪個函式可以讓InvokeScript正常呼叫,又能達到與getElementsByTagName一樣的效果? 答案很明白,就是eval函式,這個函式可以讓我們執行

大多數的JavaScript程式碼,這便是Web Browser (Ph)所仰賴的關鍵技術。

 

 

關於Tel/Sms Tag

 

  Web Browser控制項很強,但是其有一點與內建的IE不同,那就是在內建的IE下是可以解析<a href=”tel:0984xxxxx”/> Tag的,透過這個機制,使用者可以在瀏覽到使用此Tag的網頁時,

點選該連結來撥出電話,但Web Browser控制項目前並不支援這個機制,這是已知的限制

   這些tel  tag最常出現在搜尋引擎的網頁,例如Google Maps。

圖1

於內建的IE上點選電話部份的連結,會啟動撥號動作。

圖2

 

但Web Browser控制項並不支援這種應用,也就是其不認識<a href=”tel:0984xxxxx”/>,要解決這個問題,我們得自己來處理,前面所提到的InvokeScript+Eval的技巧正巧可用上。

  首先,我們得想辦法在Web Browser載入網頁後,執行一段JavaScript,搜尋網頁中所有的<a> 元素,緊接著解析其href attribute,只要發現是以tel: 開頭,即將其改為#,

並掛上onclick事件來處理使用者按下連結的後續動作。

 

MainPage.xaml

 <phone:PhoneApplicationPage

    x:Class="DetectTelTag.MainPage"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"

    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"

    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

    mc:Ignorable="d"d:DesignWidth="480"d:DesignHeight="768"

    FontFamily="{StaticResourcePhoneFontFamilyNormal}"

    FontSize="{StaticResourcePhoneFontSizeNormal}"

    Foreground="{StaticResourcePhoneForegroundBrush}"

    SupportedOrientations="Portrait"Orientation="Portrait"

    shell:SystemTray.IsVisible="True">

 

    <!--LayoutRoot is the root grid where all page content is placed-->

    <Gridx:Name="LayoutRoot"Background="Transparent">

        <Grid.RowDefinitions>

            <RowDefinitionHeight="Auto"/>

            <RowDefinitionHeight="*"/>

        </Grid.RowDefinitions>

 

        <!--TitlePanel contains the name of the application and page title-->

        <StackPanelx:Name="TitlePanel"Grid.Row="0"Margin="12,17,0,28">

            <TextBlockx:Name="ApplicationTitle"Text="MY APPLICATION"Style="{StaticResourcePhoneTextNormalStyle}"/>

            <TextBlockx:Name="PageTitle"Text="Maps"Margin="9,-7,0,0"Style="{StaticResourcePhoneTextTitle1Style}"/>

        </StackPanel>

 

        <!--ContentPanel - place additional content here-->

        <Gridx:Name="ContentPanel"Grid.Row="1"Margin="12,0,12,0">

            <phone:WebBrowserHorizontalAlignment="Left" Name="webBrowser1"VerticalAlignment="Top"Height="537"Width="456"Margin="0,64,0,0"LoadCompleted="webBrowser1_LoadCompleted"/>

            <ButtonContent="Go"Height="72"HorizontalAlignment="Left"Name="button1"VerticalAlignment="Top"Width="160"Click="button1_Click" />

        </Grid>

    </Grid>

 

</phone:PhoneApplicationPage>

MainPage.xaml.cs

				using System;

using System.Collections.Generic;

using System.Linq;

using System.Net;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Documents;

using System.Windows.Input;

using System.Windows.Media;

using System.Windows.Media.Animation;

using System.Windows.Shapes;

using Microsoft.Phone.Controls;

using Microsoft.Phone.Tasks;

using System.Windows.Threading;

 

namespace DetectTelTag

{

    public partial class MainPage : PhoneApplicationPage

    {

        DispatcherTimer _timer = new DispatcherTimer();

 

        // Constructor

        public MainPage()

        {

            InitializeComponent();

            _timer.Tick += (s, args) =>

            {

                try

                {

                    //呼叫DetectActiveTel來偵測使用者是否有按下某個tel連結

                    string para = (string)webBrowser1.InvokeScript("DetectActiveTel");

                    if (!string.IsNullOrEmpty(para))

                    {

                        //使用者按下了tel連結,啟動撥號

                        string[] parameters = para.Split(';');

                        PhoneCallTask task = new PhoneCallTask();

                        task.PhoneNumber = parameters[0];

                        task.DisplayName = parameters[1];

                        task.Show();

                    }

                }

                catch (Exception)

                {

                }

            };

            _timer.Interval = TimeSpan.FromSeconds(1);

        }

 

        private void button1_Click(object sender, RoutedEventArgs e)

        {

            //必須在Navigate前,設定IsScriptEntabled為true才能使用InvokeScript

            webBrowser1.IsScriptEnabled = true;

            webBrowser1.Navigate(

newUri("http://maps.google.com.tw/?q=Taipei%20101", UriKind.Absolute));

        }

 

        //此事件發生於網頁載入後

        private void webBrowser1_LoadCompleted(object sender,

System.Windows.Navigation.NavigationEventArgs e)

        {

            try

            {

                //Script Injection

                webBrowser1.InvokeScript("eval",

@"window.currentActiveTel = '';

window.currentActiveName = '';

 

window.DetectActiveTel=function() {

    if (window.currentActiveTel != '') {

        var result = window.currentActiveTel + ';' + window.currentActiveName;

        window.currentActiveTel = '';

        window.currentActiveName = '';

        return result;

    }

}

 

window.ReactiveTelTag=function() {

    var elem = event.srcElement;

    if (elem.getAttribute('tel') != null) {

        currentActiveTel = elem.getAttribute('tel');

        currentActiveName = elem.innerHTML;

    }

    return false;

}

 

window.ScanTelTag=function(elem) {

    if (elem.getAttribute('href') != null && elem.getAttribute('href').indexOf('tel:') == 0) {

        var tel = elem.getAttribute('href').substring(4);

        elem.setAttribute('href', '#');

        elem.setAttribute('tel', tel);

        elem.attachEvent('onclick', ReactiveTelTag);

    }

}

 

window.Initialize=function() {

    var elems = document.getElementsByTagName('a');

    for (var i = 0; i < elems.length; i++)

        ScanTelTag(elems[i]);

}");

                //初始化

                webBrowser1.InvokeScript("Initialize");

                //啟動偵測連結按下的Timer

                _timer.Start();

            }

            catch (Exception)

            {

            }

        }

    }

}

 

 這段程式碼有幾個地方需要特別解釋,一是WebBrowser的IsScriptEnabled屬性,這個屬性值必須在呼叫Navigate來導向某個網頁前設定,當此值設定為true時,InvokeScript才能正常呼叫。

   此程式在呼叫Navigate,Web Browser載入網頁後,以InvokeScript+Eval的技巧注入一段JavaScript程式至該網頁中,其中的Initialize便是搜尋網頁中所有的a 元素,並且判別是否是

<a href=”tel:xxx” />格式,如確認為此格式,便先將其href改為#,阻止預設的連結導向行為,接著掛載onclick事件,於使用者點下該連結時,將原先tel: 後面的電話號碼放到currentActiveTel全域變數中。

   最後一個步驟就是以DispatcherTime每秒呼叫一次DetectStatus函式來偵測currentActiveTel的狀態,當currentActivceTel有值時,便代表著使用者按下某個tel 連結。

 

圖3

圖4

 

類似的sms:xxxx 格式也可以用同樣的手法解決。

 

範例下載:

 

http://cid-1e6d55012e5efedb.office.live.com/self.aspx/%e5%85%ac%e9%96%8b/WP7%5E_UWP/DetectTelTag.zip