読者です 読者をやめる 読者になる 読者になる

SourceChord

C#とXAML好きなプログラマの備忘録。最近はWPF系の話題が中心です。

AvalonEditを使ってみた

AvalonEditとは

http://avalonedit.net/

.net環境で使える超高機能なテキストエディタのライブラリです。
シンタックスハイライトや、複数行の折り畳み機能、コード補間などの機能をもったエディタをお手軽に作ることができます。
C#でVisualStudio的なIDEを作っているSharpDevelopというOSSプロジェクトがあるのですが、このIDEテキストエディタ部分に使われているのがAvalonEditというライブラリです。 http://www.icsharpcode.net/OpenSource/SD/

参考リンク

f:id:minami_SC:20151012002618p:plain:w450

使ってみる

準備

まずは、nugetからライブラリをインストールします。 NugetのGUIで「AvalonEdit」で検索するか、以下のPMコンソールから以下のコマンドでインストールします。

Install-Package AvalonEdit

エディタを表示してみる

テキストエディタのコントロールを表示するだけならメッチャ簡単。 名前空間の設定をして、↓のようにコントロールを配置するだけでOK。

<Window x:Class="AvalonEditTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:avalonEdit="http://icsharpcode.net/sharpdevelop/avalonedit"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:local="clr-namespace:AvalonEditTest"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        Title="MainWindow"
        Width="525"
        Height="350"
        mc:Ignorable="d">
    <Grid>
        <avalonEdit:TextEditor x:Name="textEditor"
                               Margin="10"
                               FontFamily="Consolas"
                               FontSize="10pt"
                               SyntaxHighlighting="C#"/>
    </Grid>
</Window>

f:id:minami_SC:20151012002722p:plain

よく使いそうなプロパティなど

この辺のプロパティが頻繁に使うことになりそう

プロパティ名 概要
SyntaxHighlighting シンタックスハイライトの言語指定を行う。C#JavaScriptなどの文字列で指定する
ShowLineNumbers 行番号の表示/非表示を設定する
HorizontalScrollBarVisibility 水平方向のスクロールバーの表示有無などを設定
VerticalScrollBarVisibility 垂直方向のスクロールバーの表示有無などを設定
Options 細かいオプションを指定するためのプロパティ。TextEditorOptions型で指定する。

エディタのテキストを取得する

TextEditorコントロールに表示しているテキストは、Textプロパティで取得/設定できます。

ただし、このTextプロパティはDependencyPropertyではなく通常のプロパティとなっています。
そのため、WPF標準のTextBoxコントロールなどのように、Textプロパティに対してデータバインディングをすることはできません。

とりあえず、コードビハインドからテキストを取得/設定してみるサンプル

MainWindow.xaml

<Window x:Class="AvalonEditTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:avalonEdit="http://icsharpcode.net/sharpdevelop/avalonedit"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:local="clr-namespace:AvalonEditTest"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        Title="MainWindow"
        Width="525"
        Height="350"
        mc:Ignorable="d">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <avalonEdit:TextEditor x:Name="textEditor"
                               Margin="10"
                               FontFamily="Consolas"
                               FontSize="10pt"
                               ShowLineNumbers="True"
                               SyntaxHighlighting="C#" />
        <StackPanel Grid.Row="1" Orientation="Horizontal">
            <Button Grid.Row="1"
                    Width="75"
                    Height="20"
                    Margin="5"
                    Click="GetText"
                    Content="GetText" />
            <Button Grid.Row="1"
                    Width="75"
                    Height="20"
                    Margin="5"
                    Click="SetText"
                    Content="SetText" />
        </StackPanel>
    </Grid>
</Window>

MainWindow.xaml.cs

    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void GetText(object sender, RoutedEventArgs e)
        {
            MessageBox.Show(this.textEditor.Text);
        }

        private void SetText(object sender, RoutedEventArgs e)
        {
            this.textEditor.Text = "テキストを設定してみる。";
        }
    }

f:id:minami_SC:20151012002733p:plain

もっと詳細なエディタの情報を取得する

エディタで扱っているテキストをもっと詳細に扱いたい場合には、Documentプロパティを使います。
このプロパティでは、現在のドキュメントの状態をTextDocument型で取得できます。
こいつはDependencyPropertyになってるんで、VM側にTextDocument型のプロパティを作っておけば、バインドして使うことができます。

VMにTextDocument型のプロパティを作って、いろいろバインドしてみました。

MainWindow.xaml

<Window x:Class="AvalonEditTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:avalonEdit="http://icsharpcode.net/sharpdevelop/avalonedit"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:local="clr-namespace:AvalonEditTest"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        Title="MainWindow"
        Width="525"
        Height="350"
        mc:Ignorable="d">
    <Window.DataContext>
        <local:MainWindowViewModel />
    </Window.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="100" />
        </Grid.RowDefinitions>
        <avalonEdit:TextEditor x:Name="textEditor"
                               Margin="10"
                               Document="{Binding Document}"
                               FontFamily="Consolas"
                               FontSize="10pt"
                               ShowLineNumbers="True"
                               SyntaxHighlighting="C#" />
        <StackPanel Grid.Row="1" Orientation="Horizontal">
            <TextBlock Margin="5" Text="{Binding Document.TextLength, StringFormat=文字数:\{0:D\}}" />
            <TextBlock Margin="5" Text="{Binding Document.LineCount, StringFormat=行数:\{0:D\}}" />
        </StackPanel>
        <TextBox Grid.Row="2"
                 Margin="5"
                 IsReadOnly="True"
                 Text="{Binding Document.Text}" />
    </Grid>
</Window>

MainWindowViewModel.cs

    class MainWindowViewModel : BindableBase
    {
        private TextDocument document = new TextDocument();
        public TextDocument Document
        {
            get { return document; }
            set { this.SetProperty(ref this.document, value); }
        }
    }

f:id:minami_SC:20151012002743p:plain

ファイルの読み書き

このTextEditorコントロールには、指定したファイルを読み込むためのLoadメソッドが用意されています。 同様にSaveメソッドでファイルへの書き込みができます。
また、ファイルを読み込んでから、エディタ上のテキストが変更されているかどうかの状態を、TextEditorのIsModifiedプロパティで取得することができます。

以下のサンプルでは、Loadボタンで指定のファイルを読み込みます。
そして、テキストになんらかの編集を加えると、IsModifiedフラグがtrueになり「編集済み」という文字が表示ます。
そしてSaveボタンでファイルを保存するとIsModifiedフラグがクリアされ、「編集済み」という表示がまた消えることが確認できます。

MainWindow.xaml

<Window x:Class="AvalonEditTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:avalonEdit="http://icsharpcode.net/sharpdevelop/avalonedit"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:local="clr-namespace:AvalonEditTest"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        Title="MainWindow"
        Width="525"
        Height="350"
        mc:Ignorable="d">
    <Window.Resources>
        <BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <avalonEdit:TextEditor x:Name="textEditor"
                               Margin="10"
                               Document="{Binding Document}"
                               FontFamily="Consolas"
                               FontSize="10pt"
                               HorizontalScrollBarVisibility="Auto"
                               ShowLineNumbers="True"
                               SyntaxHighlighting="C#"
                               VerticalScrollBarVisibility="Auto" />
        <StackPanel Grid.Row="1" Orientation="Horizontal">
            <Button Grid.Row="1"
                    Width="75"
                    Height="20"
                    Margin="5"
                    Click="OnLoad"
                    Content="Load" />
            <Button Grid.Row="1"
                    Width="75"
                    Height="20"
                    Margin="5"
                    Click="OnSave"
                    Content="Save" />
            <TextBlock Text="編集済み" Visibility="{Binding IsModified, ElementName=textEditor, Converter={StaticResource BooleanToVisibilityConverter}}" />
        </StackPanel>
    </Grid>
</Window>

MainWindow.xaml.cs

    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void OnLoad(object sender, RoutedEventArgs e)
        {
            this.textEditor.Load(@"Sample.cs");
        }

        private void OnSave(object sender, RoutedEventArgs e)
        {
            this.textEditor.Save(@"Sample.cs");
        }
    }

f:id:minami_SC:20151012002801p:plain