astel-labs.net

C#などのプログラミングTipsや雑記をつらつらと書くかもしれないブログです。

  • Home
RSS

WPF – TextBlockでのURLアドレスの検出とHyperlinkの改行

Posted on 2012年5月3日 by Nymphaea
No CommentsLeave a comment

DolChatMonitor で実装した後にサンプル公開用に準備をしていたのですが、公開用に機能のみを切り貼りしてからリファクタリングをするつもりがつい面倒になってそのまま放置していたのですが、ちょうど GW になったのでリファクタリングしないで(!)そのまま公開することにしました

WPF の TextBlock はかなり高機能なコントロールなのですが、内部的にそれなりに面倒な処理をしなければならないので、意外と単純なテキスト表示以上のサンプルは見かけません。

そこで、今回はタイトルの通り TextBox に入力した文字列から URL アドレスの検出と、Hyperlink の改行を行います
完成したイメージは以下のようになります

例では TextBlock の Hyperlink を MouseOver の状態でスクリーンショットを撮ったため赤く表示されています
また、HyperLink は URL アドレスとして検知している範囲でであればいくらでも改行できます

では、実際のコードの説明を簡単に・・・

<Window x:Class="HyperlinkSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <TextBox Name="SampleText" Margin="30,30,30,0" AcceptsReturn="True"
                 VerticalAlignment="Top" VerticalScrollBarVisibility="Auto" Height="110"
                 Text="ハイパーリンクてすとhttp://astel-labs.net/bl&#xd;&#xa;og/diary/2012/05/03-1.htmlリンクはクリックで&#xd;&#xa;デフォルトブラウザが起動します" />
        <TextBlock xmlns:link="clr-namespace:HyperlinkSample"
                   Margin="30,161,30,30" Foreground="Green"
                   Text="{Binding ElementName=SampleText, Path=Text}">
            <link:HyperlinkText.Inline>
                <Binding ElementName="SampleText" Path="Text" />
            </link:HyperlinkText.Inline>
        </TextBlock>
    </Grid>
</Window>

まずは Xaml からですが、こちらはテキスト入力用の TextBox と、本題の TextBlock を配置しています
一応説明しておくと、TextBox の Text プロパティに設定している「&#xd;&#xa;」は、Xaml のプロパティ内で改行をしたい場合に「\r\n」を文字コード化した書き方になります
プロパティではなく InnerText で改行を行う場合には以下のように xml:space=”preserve” 記述します

<TextBlock xml:space=”preserve”>文字列を&#xd;&#xa;改行する</TextBlock>

 

TextBlock では、TextBlock を継承したクラスで作成した Inline プロパティで Hyperlink の設定を行います
Inline プロパティのサンプルコードは以下のようになります

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Navigation;

namespace HyperlinkSample
{
    public class HyperlinkText : TextBlock
    {
        private static Encoding m_Enc = Encoding.GetEncoding( "Shift_JIS" );

        public static readonly DependencyProperty ArticleContentProperty =
            DependencyProperty.RegisterAttached(
                "Inline",
                typeof( string ),
                typeof( HyperlinkText ),
                new PropertyMetadata( null, OnInlinePropertyChanged ) );

        public static string GetInline( TextBlock element )
        {
            return (element != null) ? element.GetValue( ArticleContentProperty ) as string : string.Empty;
        }

        public static void SetInline( TextBlock element, string value )
        {
            if( element != null )
                element.SetValue( ArticleContentProperty, value );
        }

        private static void OnInlinePropertyChanged( DependencyObject obj, DependencyPropertyChangedEventArgs e )
        {
            var tb = obj as TextBlock;
            var msg = e.NewValue as string;
            if( tb == null || msg == null )
                return;

            // 末尾の改行コードを取り除く
            msg = msg.TrimEnd( new char[] { '\n', '\r' } );

            // 改行位置の取得
            var nl = new List<int>();
            int i = 0;
            while( (i = msg.IndexOf( "\r\n", i )) >= 0 ) {
                nl.Add( i - (nl.Count * 2) );
                i += 2;
            }
            nl.Sort();

            // 正規表現でURLアドレスを検出
            var regex = new Regex( @"http(s)?://([\w-]+\.)+[\w-]+(/[A-Z0-9-.,_/?%&=]*)?",
                RegexOptions.IgnoreCase | RegexOptions.Singleline );
            var text = msg.Replace( "\r\n", "" );
            var matchs = regex.Matches( text );
            if( matchs.Count > 0 ) {
                tb.Text = null;
                tb.Inlines.Clear();

                int pos = 0;
                int l = 0;
                foreach( Match match in matchs ) {
                    var index = match.Groups[0].Index;
                    var length = match.Groups[0].Length;
                    var tag = match.Groups[0].Value;

                    // 文章前部の非リンク文字列を挿入
                    if( pos < index ) {
                        while( pos < text.Length ) {
                            if( nl.Count - l > 0 && nl[l] < index ) {
                                var buff = text.Substring( pos, nl[l] - pos );
                                tb.Inlines.Add( new Run( buff ) );
                                tb.Inlines.Add( new LineBreak() );
                                pos = nl[l];
                                l++;
                            }
                            else {
                                var buff = text.Substring( pos, index - pos );
                                tb.Inlines.Add( new Run( buff ) );
                                pos = index;
                                break;
                            }
                        }
                    }

                    // リンクの作成
                    var link = new Hyperlink();
                    link.TextDecorations = null;
                    link.Foreground = tb.Foreground;
                    link.NavigateUri = new Uri( tag );
                    link.RequestNavigate += new RequestNavigateEventHandler( RequestNavigate );
                    link.MouseEnter += new MouseEventHandler( link_MouseEnter );
                    link.MouseLeave += new MouseEventHandler( link_MouseLeave );

                    while( pos < text.Length ) {
                        if( nl.Count - l > 0 && nl[l] < index + length ) {
                            var buff = text.Substring( pos, nl[l] - pos );
                            link.Inlines.Add( new Run( buff ) );
                            link.Inlines.Add( new LineBreak() );
                            pos = nl[l];
                            l++;
                        }
                        else {
                            var buff = text.Substring( pos, index + length - pos );
                            link.Inlines.Add( new Run( buff ) );
                            pos = index + length;
                            break;
                        }
                    }

                    // Hyperlinkの追加
                    tb.Inlines.Add( link );
                }
                // 文章後部の非リンク文字列を挿入
                while( pos < text.Length ) {
                    if( nl.Count - l > 0 ) {
                        var buff = text.Substring( pos, nl[l] - pos );
                        tb.Inlines.Add( new Run( buff ) );
                        tb.Inlines.Add( new LineBreak() );
                        pos = nl[l];
                        l++;
                    }
                    else {
                        var buff = text.Substring( pos, text.Length - pos );
                        tb.Inlines.Add( new Run( buff ) );
                        pos = text.Length;
                        break;
                    }
                }
            }
            else
                tb.Text = msg;
        }

        private static void RequestNavigate( object sender, RequestNavigateEventArgs e )
        {
            try {
                // URLリンクが選択された場合、既定のアプリケーション(ブラウザ)を起動する
                Process.Start( new ProcessStartInfo( e.Uri.AbsoluteUri ) );
                // 処理した場合はtrueを返す
                e.Handled = true;
            }
            catch {
                // 特に何もしない
            }
        }

        private static void link_MouseEnter( object sender, MouseEventArgs e )
        {
            var link = sender as Hyperlink;
            if( link == null )
                return;

            // リンクにカーソルを当てたときは文字色を赤くする
            link.Foreground = Brushes.Red;
        }

        private static void link_MouseLeave( object sender, MouseEventArgs e )
        {
            var link = sender as Hyperlink;
            var parent = link.Parent as TextBlock;
            if( link == null || parent == null )
                return;

            // リンクからカーソルが離れたときは文字色をデフォルトカラーに戻す
            link.Foreground = parent.Foreground;
        }
    }
}

TextBox で入力した文字列は TextBlock にデータバインドしているため、文字が入力されるたびに Hyperlink の検出が行われます
Hyperlink  の検出は正規表現で行っており、 以下の書式に一致する文字列を URL として認識します

http(s)?://([\w-]+\.)+[\w-]+(/[A-Z0-9-.,_/?%&=]*)?

詳しくはネットで調べてください・・・

 

というか、いろいろと説明が面倒になってきたのでサンプルコードの説明はコードを見て感じてください!

Categories: C#, WPF, プログラミング

 

WPF – 文字の縁取りをする
ホスティング会社やいろんなものを変えてみた

コメントを残す

コメントを投稿するにはログインしてください。

  • 2012年5月
    日 月 火 水 木 金 土
     12345
    6789101112
    13141516171819
    20212223242526
    2728293031  
    « 4月   7月 »
  • 最近の投稿

    • raspberry pi に nginx をセットアップする
    • VSCode を日本語化する
    • raspberry pi に VSCode をインストールする
    • WindowsのプロダクトIDを取得する
    • SQL Server – money型とdecimal型を併用した金額計算
  • アーカイブ

    • 2020年3月
    • 2016年12月
    • 2014年6月
    • 2014年5月
    • 2013年7月
    • 2012年11月
    • 2012年7月
    • 2012年5月
    • 2012年4月
    • 2011年4月
    • 2011年1月
    • 2010年11月
    • 2010年10月
    • 2010年9月
    • 2010年8月
    • 2010年7月
    • 2010年6月
    • 2010年5月
    • 2010年4月
  • カテゴリー

    • C#
    • jQuery
    • OpenCV
    • raspberry pi
    • SharePoint
    • Silverlight
    • SQL Server
    • WCF
    • WPF
    • プログラミング
    • 未分類
    • 雑記
© astel-labs.net. Proudly Powered by WordPress | Nest Theme by YChong

このブログ内で公開されているソースコードおよびサンプルプログラムに関わるライセンスはすべて修正BSDライセンス(New BSD License)として公開しています。
但し、サンプルプログラムに含まれる外部アセンブリが同様のライセンスとは限りませんので、利用する前に必ずすべてのライセンスの確認を行ってください。