世紀末の墓

お気持ち.log

IT系寄りの雑記

webブラウザを自作したい!(4)

はい。
今回は前回言ったように、戻る・進む・再読込ボタンを作ろうぜ、っていう回です。

SS↓
最初は、「戻る」も「進む」もできないので暗い。
f:id:silmin:20170807220603p:plain:w300

helloと検索して進んだので「戻る」ができるようになった
f:id:silmin:20170807220613p:plain:w300

戻ったら逆に「進む」ができるようになった
f:id:silmin:20170807220621p:plain:w300

しっかり再読込もできるよ!!!



やることはめちゃ簡単です....................が、めちゃ大変でした。(は?)



今日の荒ぶり度合い(詳しくは余談で)↓

そう、実装内容とは全くと言っていいほど関係ない所で躓いていました^^;;;;;

そんなこんなで見ていきましょう。

戻る・進む・再読込ボタンの実装

はい、やることは簡単です。

ボタン配置して、それにClickイベント乗っけて、GoBack()なりGoForward()なりRefresh()なりをするだけです。(ホントに)

実際には画像貼るとか枠線消すとかしましたが、メインは↑のことするだけです。

ソースコード

ボタン達初期化
urlBox = new TextBox()
{
    Location = new Point( 90, 2 ),
    Size = new Size( 1000, 20 ),
    Font = new Font( "meiryo", 10 )
};
backButton = new Button()
{
    Location = new Point( 0, 0 ),
    Size = new Size( 30, 30 ),
    BackgroundImage = Image.FromFile( @"img\back_clear.png" ),
    BackgroundImageLayout = ImageLayout.Stretch,
    FlatStyle = FlatStyle.Flat
};
forwardButton = new Button()
{
    Location = new Point( 30, 0 ),
    Size = new Size( 30, 30 ),
    BackgroundImage = Image.FromFile( @"img\forward_clear.png" ),
    BackgroundImageLayout = ImageLayout.Stretch,
    FlatStyle = FlatStyle.Flat
};
refreshButton = new Button()
{
    Location = new Point( 60, 0 ),
    Size = new Size( 30, 30 ),
    BackgroundImage = Image.FromFile( @"img\refresh.png" ),
    BackgroundImageLayout = ImageLayout.Stretch,
    FlatStyle = FlatStyle.Flat
};

backButton.FlatAppearance.BorderSize = 0;
forwardButton.FlatAppearance.BorderSize = 0;
refreshButton.FlatAppearance.BorderSize = 0;

各ボタンの初期化を行っています。

backButton・・・戻るボタン
forwardButton・・・進むボタン
refreshButton・・・再読込ボタン

おそらく新要素はここ?

BackgroundImage = Image.FromFile( @"img\forward_clear.png" ),  //名前の通り背景画像設定
BackgroundImageLayout = ImageLayout.Stretch,                   //画像のスタイル?を変更(並べるとかフィットさせるとか中央とか)←ここではフィットさせてる
FlatStyle = FlatStyle.Flat                                     //ボタンのスタイルを変更

画像のパスの@は「\をエスケープ文字じゃなく階層区切りとしてみる」という意味があります。
@がない状態だと\はエスケープ文字と捉えられてしまい、\を使うには\\と記述する必要がありますが、先頭に@をつけることで、普通につかえてうれしいですね。
画像のスタイル?についてはここ↓
コントロールやフォームに画像を表示する: .NET Tips: C#, VB.NET
これはあくまで背景をいじってるだけなのでButton.Imageプロパティとは違います。
今回は文字も表示しないので変わりないと思い、これを採用。

ボタンのスタイルについてはここ↓
01.フラットスタイルの外観を変更する < Button Tips < コントロール Tips メニュー < C# 2005 Tips HOME < HIRO's.NET
昔の記事ですが全然問題ないかと。
今回はフラットにしてます。(かっちょいいので)

backButton.FlatAppearance.BorderSize = 0;
forwardButton.FlatAppearance.BorderSize = 0;
refreshButton.FlatAppearance.BorderSize = 0;

これはボタンの枠線の太さを変更しています。
0に設定しているので実質枠無し、にしています。(かっちょいいので)

新しいメソッド群
private void Browser_CanGoBackChanged( object sender, EventArgs e )
{
    backButton.Enabled = browser.CanGoBack;
    if( backButton.Enabled ) backButton.BackgroundImage = Image.FromFile( @"img\back.png" );
    else backButton.BackgroundImage = Image.FromFile( @"img\back_clear.png" );
}

private void Browser_CanGoForwardChanged( object sender, EventArgs e )
{
    forwardButton.Enabled = browser.CanGoForward;
    if( forwardButton.Enabled ) forwardButton.BackgroundImage = Image.FromFile( @"img\forward.png" );
    else forwardButton.BackgroundImage = Image.FromFile( @"img\forward_clear.png" );
}

private void backButton_Click( object sender, EventArgs e )
{
    browser.GoBack();
    this.ActiveControl = null; 
}

private void forwardButton_Click( object sender, EventArgs e )
{
    browser.GoForward();
    this.ActiveControl = null; 
}

private void refreshButton_Click( object sender, EventArgs e )
{
    browser.Refresh();
    this.ActiveControl = null; 
}

CanGo....Changedというイベントは、「戻る」または「進む」の可不可が変更されると、発火します。
CanGoBackやCanGoForwardはboolで、戻れるかどうか、進めるかどうか、を持っています。

可能か不可能かで画像を変更したかったので書きました。

...Button_Clickというイベントは、文字通りそのボタンがクリックされたら発火します。
GoBack()やGoForward()、Refresh()はそれぞれ戻る・進む・再読込を行うメソッドです。

this.ActiveControl = null;

これは移動した時に、何らかのボタンを選択してしまっていることがあるので、ちょっとかっこ悪いかなぁと思って書きました。
これはデフォルトなのでしょうか。コードの意味は文字通りです。

余談

最初に言った躓いたところについてです。
僕は普段、自分用フォームアプリケーション作るだけならVisualStudioは使わずvimで書いて、付属のcscコンパイルしています。
VSは嫌いなのでね
UWPとかやるなら話は別だよ。

そのコンパイルオプションで /target:winexe というものがあるのですが、これを付けると実行したときにコンソールが開かないので嬉しいです。
というか、実行時にコンソールいちいち開かれても邪魔なのでね・・・^^;
それで、そのオプションを付けると.exeファイルが作られた直後に消失するという現象がおきまして。(は?????)
これには名前があるのかどうかわかりませんが、かなり焦りました。
なんか、色々デバッグした感じ、イベント貼ってないButtonがあって、そのボタンについて

Button.FlatAppearance.BorderSize = 0;

をするとその現象が起きるということがわかりました。

なにを言ってるのかわからないと思います。
僕もわかりませんので。(だれか早く教えて)

今日はこれにて。
明日時間があればお気に入り機能つけたいと思います。ノシ

webブラウザを自作したい!(3)

今回は前回言ってたように戻る進む再読込とかとか





ではなく、「サイトのアイコンを適用する」です。

サイトのアイコンを適用する

え?それだけ?

はいそれだけです(それ以外にもちょっとあるよ)


コレがわりと苦労しました。

簡単そうに見えてこれ持ってるプロパティとかなさそうで、html内部からURL探して持ってくる必要がありました。

もっといい方法ありそうですが、とりあえずぶん殴る形式になっちゃいました。知ってる人おしえてください・・・><

それではSSから
f:id:silmin:20170806094419p:plainf:id:silmin:20170806094435p:plain

まあこんな感じで変わってますね。

ソースコード

private void Browser_Navigated( object sender, WebBrowserNavigatedEventArgs e )
{
    //this.Text = browser.DocumentTitle;
    this.urlBox.Text = browser.Url.ToString();

    string iconUrl = getIconUrl( e );
    this.Icon = getIcon( iconUrl );
}

private string getIconUrl( WebBrowserNavigatedEventArgs e )
{
    foreach( HtmlElement linkTag in this.browser.Document.GetElementsByTagName("link") )
    {
        string relAttribute = linkTag.GetAttribute("rel");
        string iconUrl;

        if( relAttribute == "shortcut icon" || relAttribute == "icon" )
        {
            iconUrl = linkTag.GetAttribute("href");
            if( iconUrl.StartsWith("http") ) return iconUrl; 
            else if( iconUrl.StartsWith("/") ) return "http://" + e.Url.Host + iconUrl;
            else return e.Url.ToString() + iconUrl;
        }
    }
    return "http://" + e.Url.Host + "/favicon.ico";
}

//url -> Icon
private Icon getIcon( string url )
{
    WebClient webClient = new WebClient();
    using( MemoryStream stream = new MemoryStream( webClient.DownloadData( url ) ) )
    {
        Icon icon = new Icon( stream );
        return icon;
    }
}

private void Browser_DocumentCompleted( object sender, WebBrowserDocumentCompletedEventArgs e )
{
    this.Text = browser.DocumentTitle;
}

ざっくりいじった所だけ貼りました。

getIconUrl()でアイコンのURLを引っぱってきて、getIcon()でURLからアイコンを取得する感じです。

private string getIconUrl( WebBrowserNavigatedEventArgs e )
{
    foreach( HtmlElement linkTag in this.browser.Document.GetElementsByTagName("link") )
    {
        string relAttribute = linkTag.GetAttribute("rel");
        string iconUrl;

        if( relAttribute == "shortcut icon" || relAttribute == "icon" )
        {
            iconUrl = linkTag.GetAttribute("href");
            if( iconUrl.StartsWith("http") ) return iconUrl; 
            else if( iconUrl.StartsWith("/") ) return "http://" + e.Url.Host + iconUrl;
            else return e.Url.ToString() + iconUrl;
        }
    }
    return "http://" + e.Url.Host + "/favicon.ico";
}

ホントにぶん殴りコードですね・・・^^;
とりあえずlinkタグのやつを全部抽出してその中のrel探してきて、その属性がicon系なら引っ張るって感じですね。
それでも無かったらfavicon.icoにあるんじゃねってことで最後です。

C#スクレイピングしたこと無かったので苦戦しました・・・。
でも完成してみたら、割りと読みやすいというかわかりやすいコードになりました。

もっといい方法ないかなぁ(ボソッ

private Icon getIcon( string url )
{
    WebClient webClient = new WebClient();
    using( MemoryStream stream = new MemoryStream( webClient.DownloadData( url ) ) )
    {
        Icon icon = new Icon( stream );
        return icon;
    }
}

getIcon()はurlからアイコンを持ってくるメソッドです。
urlの示すデータを読み込んで、それをアイコンとして読み出す、ってイメージでしょうか。


んでそれらを↓でまとめるって感じになってます。

private void Browser_Navigated( object sender, WebBrowserNavigatedEventArgs e )
{
    //this.Text = browser.DocumentTitle;
    this.urlBox.Text = browser.Url.ToString();

    string iconUrl = getIconUrl( e );   //url持ってきて
    this.Icon = getIcon( iconUrl );     //アイコンにする
}


ここで気がついた人もいるかもですが、タイトルを取得するイベントを

WebBrowser.Navigated()

WebBrowser.DocumentCompleted()

に変更しました。

Navigatedだとまだサイトに案内できただけで、中身をすべてロードできていない(?)状態らしいです。
実際に、Navigatedだとタイトルを取得できないサイトが複数ありました。(←これはタイミングの問題でもあるかも)

DocumentCompletedは、サイトの中身のロードが完了すると発生するイベントなので、これを利用しようという結論になりました。これだと全部タイトルがしっかり適用されました。

大した変更じゃないけど、結構重要なのでやっておきました。



それでは今回はここまで。
次回こそは、戻る進む再読込つくろう!(全然難しくないんだけどね())

webブラウザを自作したい!(2)

URLボックスの実装

見た目
f:id:silmin:20170803212614p:plain:w250f:id:silmin:20170803212617p:plain:w250

↑こんな風にしっかり変わります。

ちゃんと入力してEnterキーで飛ぶこともできるよ!
これがめちゃめちゃ時間かかったんだよなぁ(なんで)

あとちゃっかりタイトルもサイトによって変わるようにいじってます。

ソースコード

using System;
using System.Drawing;
using System.Windows.Forms;

//mainは割愛

class Browser : Form
{
    WebBrowser browser;
    TextBox urlBox;

    public Browser()
    {
        //item
        browser = new WebBrowser()
        {
            Location = new Point( 0, 30 ),
            Size = new Size( 1200, 800 )
        };
        urlBox = new TextBox()
        {
            Location = new Point( 30, 2 ),
            Size = new Size( 1000, 20 ),
            Font = new Font( "meiryo", 10 )
        };
        this.Controls.Add( browser );
        this.Controls.Add( urlBox );

        //event handler
        this.Shown += new EventHandler( this.Browser_Load );
        this.SizeChanged += new EventHandler( this.ReSized );
    }
    
    private void Browser_Load( object sender, EventArgs e )
    {
        this.ClientSize = new Size( 1200, 830 );
        this.Text = "My Browser";

        //script error block
        browser.ScriptErrorsSuppressed = true;

        //event handler
        browser.Navigated += new WebBrowserNavigatedEventHandler( this.Browser_Navigated );
        urlBox.KeyDown += new KeyEventHandler( this.urlBox_KeyDown );

        //first page
        string url = "http://www.google.co.jp";
        browser.Navigate( url );
    }

    private void urlBox_KeyDown( object sender, KeyEventArgs e )
    {
        if( e.KeyCode == Keys.Enter )  
        {
            if( urlBox.Text != string.Empty )
            {
                browser.Navigate( urlBox.Text );
            }
        }
    }

    private void Browser_Navigated( object sender, WebBrowserNavigatedEventArgs e )
    {
        this.Text = browser.DocumentTitle;
        this.urlBox.Text = browser.Url.ToString();
    }

    private void ReSized( object sender, EventArgs e )
    {
        int width = this.ClientSize.Width;
        int height = this.ClientSize.Height;
        this.browser.Size = new Size( width, height - 30 );
    }
}

前回に比べてちょっと増えましたね(当たり前)

urlBox = new TextBox()                //URLいれるやつ
{
    Location = new Point( 30, 2 ),
    Size = new Size( 1000, 20 ),
    Font = new Font( "meiryo", 10 )
};

TextBoxは結局フォントの大きさでheightが決まるみたいなので適当に目分量で決めました。
ゆるしてくださいなんでもしません。(しません。)
フォントがメイリオなのに深い意味はありません。思いついたのがメイリオだっただけです。

private void Browser_Navigated( object sender, WebBrowserNavigatedEventArgs e )
{
    this.Text = browser.DocumentTitle;            //タイトルを変更
    this.urlBox.Text = browser.Url.ToString();    //URLボックスに今のページのURLを格納
}

WebBrowser.Navigatedイベントはページ移動したときに発生するイベントらしいです。(雑)
だったらそこでタイトルとURL更新すればいいじゃないということで更新。

private void urlBox_KeyDown( object sender, KeyEventArgs e )
{
    if( e.KeyCode == Keys.Enter )              //押されたキーがEnterキーだったら
    {
        if( urlBox.Text != string.Empty )      //URLボックスが空じゃなかったら
        {
            browser.Navigate( urlBox.Text );   //そのURL先に移動
        }
    }
}

TextBox(Control).KeyDownイベントはキーが押下されたときに発生するイベントらしいです。
それ利用して移動させちゃいます。

先程言った時間のかかったポイントは e,KeyCode の部分です。


は?
普通じゃん。



そう今思えば普通なんですね。

ですが僕は Ctrl, Shift, Alt のような修飾キーの押されたモノを持つ Control.ModifierKeys と一生懸命比較していたためできなかった。
雑魚確定ですねorz


ここまでで一応今回の目標(?)は達成なんですが、1つやることがあります。

//script error block
browser.ScriptErrorsSuppressed = true;

これです。
これはスクリプトエラーを表示しないというフラグです。
trueで表示しないとなります。(デフォルト:false)

これしないとスクリプトエラー出まくってまともにブラウジングできなかったのでしておきます。

次回は戻る・進む・再読込機能とお気に入り機能付けたいなぁ・・・・(時間をくれ)

webブラウザを自作したい!(1)

今回はC#を用いてwebブラウザを作りたいと思います。
進捗があがり次第記事を書いていく形になると思いますし、完成形は考えて無いので(n)まで続きます。(どこまで続くかわからない)

それと、何故作るかというと...........作りたいからです。(理由はいらないですね)

基盤を作る

(1)なのでとりあえずは基盤というか、そんな感じのモノを作る。

今回作ったのは、実行したらgoogleに繋がるだけの代物。
UIなんて気にするな。↓の感じで。

f:id:silmin:20170801212856p:plain

タイトルバーの下のスペースはアドレスバーとか入れようかなって思って空けた。

ソースコード

using System;
using System.Drawing;
using System.Windows.Forms;

namespace Webbrowser
{
    class Program
    {
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.Run( new Browser() );
        }
    }

    class Browser : Form
    {
        WebBrowser browser;

        public Browser()
        {
            browser = new WebBrowser()
            {
                Location = new Point( 0, 30 ),
                Size = new Size( 1200, 800 )                  
            };
            this.Controls.Add( browser );

            this.Shown += new EventHandler( this.Browser_Load );
            this.SizeChanged += new EventHandler( this.ReSized );

            string url = "http://www.google.co.jp";
            browser.Navigate( url );
        }

        private void Browser_Load( object sender, EventArgs e )
        {
            this.ClientSize = new Size( 1200, 830 );
            this.Text = "My Browser";
        }

        private void ReSized( object sender, EventArgs e )
        {
            int width = this.ClientSize.Width;
            int height = this.ClientSize.Height;
            this.browser.Size = new Size( width, height - 30 );
        }
    }
}

こんな感じになった。クソコードっていうな

はい。
後々改造していくし、まだハリボテなので許して。

WebBrowser browser;

こいつはwebブラウザに必要な事(低レイヤの部分?)を全部まるっとやってくれるすごいやつだ!
これをAddするだけで表示ができちゃう!正直びっくりした。
コレに頼ればできちゃうけど、中身もちゃんと理解できるようになりたいですね.....。
まあ今は使えるものは使いましょっと^^

string url = "http://www.google.co.jp";  //urlをstring型変数に格納
browser.Navigate( url );                 //urlをNavigateメソッドに渡す

WebBrowser.Navigate()は指定したURLに飛んでくれるメソッド。
圧倒的便利感。

//ウインドウのサイズが変更されたときに発火するメソッド
private void ReSized( object sender, EventArgs e )
{
    int width = this.ClientSize.Width;                   //現在のウインドウの横幅を格納
    int height = this.ClientSize.Height;                 //現在のウインドウの縦幅を格納
    this.browser.Size = new Size( width, height - 30 );  //表示領域を現在のモノに更新
}

一応リサイズにも対応できるようにしたよ。

あいさつ。

こんにちは。

当ブログでは僕の勉強記録や思ったこと等を書いていきます。

続くかどうかわからないし本人が忘れる可能性までありますが、どうか温かい目で見ていてくれると嬉しいです。

基本IT関連のブログになるかと思われます。

書いた中で、間違ってる事や補足等があったらガンガンまさかりぶん投げてくれて結構ですのでよろしくお願いします^^

僕がここに書いたことが、僕の後続(初心者)の方々の役に立ったら嬉しいです。

 

それと更新したら都度ツイートしますが、全然投稿されない.....Twitterには居るのになぁって時は、忘れている可能性大ですので言ってくれると助かります。

 

それではよろしくお願いします^^

 

↓最近買ったthink padちゃん(13インチ)

f:id:silmin:20170801170312j:plain かわゆい

f:id:silmin:20170801170325j:plainもちろんUS配列