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
og/diary/2012/05/03-1.htmlリンクはクリックで
デフォルトブラウザが起動します" /> <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 プロパティに設定している「
」は、Xaml のプロパティ内で改行をしたい場合に「\r\n」を文字コード化した書き方になります
プロパティではなく InnerText で改行を行う場合には以下のように xml:space=”preserve” 記述します
<TextBlock xml:space=”preserve”>文字列を
改行する</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-.,_/?%&=]*)?
詳しくはネットで調べてください・・・
というか、いろいろと説明が面倒になってきたのでサンプルコードの説明はコードを見て感じてください!
コメントを残す
コメントを投稿するにはログインしてください。