Всем привет. Сегодня хочу поделиться опытом работы с контролами AvalonEdit и AvalonDock
Итак, моя цель - сделать блокнот с подсветкой синтаксиса, intellisense и оконным интерфейсом, как у Visual Studio 2010.
В качестве оправной точки я использую статьи по работе с данными контролами (Using AvalonEdit (WPF Text Editor), WPF Docking Library). Ознакомившись с данным материалом, я начал с оконного интерфейса. Это оказалось очень просто:
Используем DockingManager на главной странице:
И DocumentContent для дочерних документов
Полный листинг я приводить не буду, так как исходники всё равно опубликую, а загромождать кодом блог нет смысла.
После этих манипуляций, можно получить что то вроде вот этого
Далее необходимо добавить главное меню. Там будет всего пара пунктов
Названия элементов говорят сами за себя. Следующий шаг - добавление с закладки текстового редактора. Это делается просто - сначала добавляем панель инструментов, потом сам редактор:
Следует пояснить назначение одного из обработчиков, указанных в листинге
После этих работ, блокнот стал выглядеть весьма привлекательно
Однако, как оказалось, в AvalonEdit не встроена поддержка подсветки SQL и он не может автоматически распознать, подсветку чего включить при открытии файла.
Первая проблема решилась очень быстро - я просто погуглил, и нашел определение синтаксиса для SQL в одном из проектов на кодеплексе, и мне осталось лишь его встроить в редактор.
Так как все определения синтаксиса, указанные в подобных файлах, содержат список расширений файлов, которые ассоциируются с определением, то среди классов библиотеки AvalonEdit я легко нашел тот, что поможет мне получить определение из имени файла. Таким образом, добавить автоматическое определение синтаксиса по расширению открываемого файла в блокнот оказалось делом тривиальным.
Написав совсем немного кода, мне удалось сделать так, что можно добавлять файлы определения синтаксиса в уже готовую программу, просто положив их в определенную папку.
Следующей моей задачей было прикрутить intellisense. Так как я в основном пользуюсь C# и SQL, то мне будет достаточно сделать интелисенс только для них. Для себя я представил реализацию как просто набор ключевых слов синтаксиса, которые фильтруются по мере того, как пользователь набирает слово. Наборы ключевых слов я просто взял из MSDN (C# 2010, SQL Server 2008) и разделил их на 2 файла соответственно. Затем мне нужно было осуществить привязку текущего правила подсветки синтаксиса и набора ключевых слов. Это я сделал при помощи простого XML файла
Для загрузки правил из этого файла я, как легко догадаться, использую XML десериализацию. Это очень удобно - благодаря этому XML файлу я могу добавлять/удалять ключевые слова для любого правила подсветки синтаксиса, которое я также могу добавить в будущем, и всё это без перекомпиляции программы.
Дальше всё просто - мне достаточно отследить пару событий текстового редактора, определить, стоит ли мне показывать окно с ключевыми словами - и если стоит, то получить список ключевых слов и отобразить его.
Вот интерфейс класса CompletionWindowResolver
Как видно, тут необходимо просто определиться с окном intellisense - получить его экземпляр и отобразить. Вот моя реализация метода Resolve
Для пояснения
И мой интерфейс провайдера
Таким образом я могу определить всю логику, по которой строится список подсказок в окне, что даёт полную свободу действий.
В результате всех этих манипуляций, у меня получился блокнот с поддержкой пдсветки синтаксиса, автодополнением и удобными закладками. Иконку для приложения я взял среди бесплатных и выложил проект на кодеплекс.
Это всё. Всем спасибо.
Итак, моя цель - сделать блокнот с подсветкой синтаксиса, intellisense и оконным интерфейсом, как у Visual Studio 2010.
В качестве оправной точки я использую статьи по работе с данными контролами (Using AvalonEdit (WPF Text Editor), WPF Docking Library). Ознакомившись с данным материалом, я начал с оконного интерфейса. Это оказалось очень просто:
Используем DockingManager на главной странице:
- <ad:DockingManager x:Name="DockManager" IsAnimationEnabled="True" Grid.Row="1" Margin="0,0,0,0"
- ActiveContent="{Binding ElementName=Content1}" DocumentClosing="DockManagerDocumentClosing" >
- <ad:ResizingPanel Orientation="Horizontal">
- <ad:DocumentPane x:Name="DocumentHost">
- </ad:DocumentPane>
- </ad:ResizingPanel>
- </ad:DockingManager>
- <ad:DocumentContent x:Class="DotNetNotepad.UI.Document"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
- xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
- xmlns:ad="clr-namespace:AvalonDock;assembly=AvalonDock"
- xmlns:avalonedit="http://icsharpcode.net/sharpdevelop/avalonedit"
- mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300">
- <Grid Background="White">
После этих манипуляций, можно получить что то вроде вот этого
Далее необходимо добавить главное меню. Там будет всего пара пунктов
- <Menu>
- <MenuItem Header="File">
- <MenuItem Header="New" Click="NewClick">
- </MenuItem>
- <MenuItem Header="Open" Click="OpenFileClick">
- </MenuItem>
- <Separator/>
- <MenuItem Header="Exit" Click="ExitClick">
- </MenuItem>
- </MenuItem>
- </Menu>
- <ad:DocumentContent x:Class="DotNetNotepad.UI.Document"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
- xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
- xmlns:ad="clr-namespace:AvalonDock;assembly=AvalonDock"
- xmlns:avalonedit="http://icsharpcode.net/sharpdevelop/avalonedit"
- mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300">
- <Grid>
- <Grid.RowDefinitions>
- <RowDefinition Height="32"/>
- <RowDefinition/>
- </Grid.RowDefinitions>
- <ToolBar DockPanel.Dock="Top">
- <ToolBar.Resources>
- <Style TargetType="{x:Type Image}">
- <Style.Triggers>
- <DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType={x:Type ButtonBase}, AncestorLevel=1}, Path=IsEnabled}" Value="False">
- <Setter Property="Opacity" Value="0.30" />
- </DataTrigger>
- </Style.Triggers>
- </Style>
- </ToolBar.Resources>
- <Button Click="SaveFile" >
- <Image Source="Images/Save.png" Height="16"/>
- </Button>
- <Separator/>
- <Button Command="Cut">
- <Image Source="Images/Cut.png" Height="16"/>
- </Button>
- <Button Command="Copy">
- <Image Source="Images/Copy.png" Height="16"/>
- </Button>
- <Button Command="Paste">
- <Image Source="Images/Paste.png" Height="16"/>
- </Button>
- <Button Command="Delete">
- <Image Source="Images/Delete.png" Height="16"/>
- </Button>
- <Separator/>
- <Button Command="Undo">
- <Image Source="Images/Undo.png" Height="16"/>
- </Button>
- <Button Command="Redo">
- <Image Source="Images/Redo.png" Height="16"/>
- </Button>
- <Separator/>
- <CheckBox IsChecked="{Binding ElementName=textEditor,Path=WordWrap}">
- <Image Source="Images/WordWrap.png" Height="16"/>
- </CheckBox>
- <CheckBox IsChecked="{Binding ElementName=textEditor,Path=ShowLineNumbers}">
- <TextBlock Width="16" TextAlignment="Center">#</TextBlock>
- </CheckBox>
- <CheckBox IsChecked="{Binding ElementName=textEditor,Path=Options.ShowEndOfLine}">
- <TextBlock Width="16" TextAlignment="Center">¶</TextBlock>
- </CheckBox>
- <!--Тут происходит биндинг вариантов подсветки синтаксиса, которые загружены в редактор-->
- <ComboBox Name="highlightingComboBox"
- SelectedItem="{Binding SyntaxHighlighting, ElementName=textEditor}"
- ItemsSource="{Binding Source={x:Static avalonedit:HighlightingManager.Instance}, Path=HighlightingDefinitions}"
- SelectionChanged="HighlightingComboBoxSelectionChanged"/>
- </ToolBar>
- <!--По умолчанию редактор настроен на подсветку XML-->
- <avalonedit:TextEditor
- x:Name="textEditor"
- FontFamily="Consolas"
- FontSize="10pt"
- ShowLineNumbers="True"
- SyntaxHighlighting="XML"
- Grid.Row="1">
- <avalonedit:TextEditor.ContextMenu>
- <ContextMenu>
- <MenuItem Command="Cut" Header="Cut" />
- <MenuItem Command="Copy" Header="Copy" />
- <MenuItem Command="Paste" Header="Paste" />
- <MenuItem Command="Delete" Header="Delete" />
- <MenuItem Command="Undo" Header="Undo" />
- <MenuItem Command="Redo" Header="Redo" />
- </ContextMenu>
- </avalonedit:TextEditor.ContextMenu>
- </avalonedit:TextEditor>
- </Grid>
- </ad:DocumentContent>
- /// <summary>
- /// Позволяет устанавливать стратегии разбиения текста на части
- /// </summary>
- FoldingManager _foldingManager;
- /// <summary>
- /// Сама стратегия разбиения
- /// </summary>
- AbstractFoldingStrategy _foldingStrategy;
- /// <summary>
- /// Обработчик меняет стратегию разбиения текста на части. Подсветка синтаксиса меняется автоматом.
- /// </summary>
- /// <param name="sender"></param>
- /// <param name="e"></param>
- void HighlightingComboBoxSelectionChanged(object sender, SelectionChangedEventArgs e)
- {
- // SyntaxHighlighting - это свойство, определяющее текущее правило подсветки синтаксиса
- if (textEditor.SyntaxHighlighting == null)
- {
- _foldingStrategy = null;
- }
- else
- {
- switch (textEditor.SyntaxHighlighting.Name)
- {
- case "XML":
- _foldingStrategy = new XmlFoldingStrategy();
- textEditor.TextArea.IndentationStrategy = new ICSharpCode.AvalonEdit.Indentation.DefaultIndentationStrategy();
- break;
- case "C#":
- case "C++":
- case "PHP":
- case "Java":
- textEditor.TextArea.IndentationStrategy = new ICSharpCode.AvalonEdit.Indentation.CSharp.CSharpIndentationStrategy(textEditor.Options);
- _foldingStrategy = null;
- break;
- default:
- textEditor.TextArea.IndentationStrategy = new ICSharpCode.AvalonEdit.Indentation.DefaultIndentationStrategy();
- _foldingStrategy = null;
- break;
- }
- }
- if (_foldingStrategy != null)
- {
- if (_foldingManager == null)
- _foldingManager = FoldingManager.Install(textEditor.TextArea);
- _foldingStrategy.UpdateFoldings(_foldingManager, textEditor.Document);
- }
- else
- {
- if (_foldingManager != null)
- {
- FoldingManager.Uninstall(_foldingManager);
- _foldingManager = null;
- }
- }
- }
Однако, как оказалось, в AvalonEdit не встроена поддержка подсветки SQL и он не может автоматически распознать, подсветку чего включить при открытии файла.
Первая проблема решилась очень быстро - я просто погуглил, и нашел определение синтаксиса для SQL в одном из проектов на кодеплексе, и мне осталось лишь его встроить в редактор.
- <?xml version="1.0"?>
- <!--
- Copyright 2005-2009 Paul Kohler (http://pksoftware.net/MiniSqlQuery/). All rights reserved.
- This source code is made available under the terms of the Microsoft Public License (Ms-PL)
- http://minisqlquery.codeplex.com/license
- -->
- <SyntaxDefinition name = "SQL" extensions = ".sql">
- <Properties>
- <Property name="LineComment" value="--"/>
- </Properties>
- <Digits name = "Digits" bold = "true" italic = "false" color = "Blue"/>
- <RuleSets>
- <RuleSet ignorecase = "true">
- <Delimiters>&<>~!%^*()-+=|\#/{}[]:;"' , .?</Delimiters>
- <Span name="String" bold="false" italic="false" color="Red" stopateol="false">
- <Begin>'</Begin>
- <End>'</End>
- </Span>
- <Span name = "LineComment" bold = "false" italic = "false" color = "Green" stopateol = "true">
- <Begin>--</Begin>
- </Span>
- <Span name = "BlockComment" bold = "false" italic = "false" color = "Green" stopateol = "false">
- <Begin>/*</Begin>
- <End>*/</End>
- </Span>
- <KeyWords name="JoinKeywords" bold="true" italic="false" color="Purple">
- <Key word="INNER" />
- <Key word="JOIN" />
- <Key word="LEFT" />
- <Key word="RIGHT" />
- <Key word="OUTER" />
- <Key word="UNION" />
- </KeyWords>
- <KeyWords name="AliasKeywords" bold="false" italic="false" color="Maroon">
- <Key word="AS" />
- </KeyWords>
- <KeyWords name="ComparisonKeywords" bold="true" italic="false" color="Navy">
- <Key word="AND" />
- <Key word="OR" />
- <Key word="LIKE" />
- </KeyWords>
- <KeyWords name="SpecializedKeywords" bold="true" italic="false" color="Gray">
- <Key word="TOP" />
- <Key word="LIMIT" />
- <Key word="OPENDATASOURCE" />
- <Key word="GO" />
- </KeyWords>
- <KeyWords name="DestructiveKeywords" bold="true" italic="false" color="Red">
- <Key word="DROP" />
- <Key word="DELETE" />
- <Key word="TRUNCATE" />
- </KeyWords>
- <KeyWords name="SqlKeywordsBold" bold="true" italic="false" color="Blue">
- <Key word="BEGIN" />
- <Key word="END" />
- <Key word="EXEC" />
- <Key word="CREATE" />
- <Key word="COMMIT" />
- <Key word="RAISERROR" />
- <Key word="ROLLBACK" />
- <Key word="TRAN" />
- <Key word="TRANSACTION" />
- <Key word="USE" />
- <Key word="USER" />
- <Key word="VIEW" />
- </KeyWords>
- <KeyWords name="SqlKeywordsNormal" bold="false" italic="false" color="Blue">
- <Key word="ADD" />
- <Key word="ALL" />
- <Key word="ANY" />
- <Key word="ASC" />
- <Key word="BETWEEN" />
- <Key word="BREAK" />
- <Key word="BY" />
- <Key word="CASCADE" />
- <Key word="CASE" />
- <Key word="CHECK" />
- <Key word="CHECKPOINT" />
- <Key word="CLOSE" />
- <Key word="COALESCE" />
- <Key word="COLLATE" />
- <Key word="COLUMN" />
- <Key word="COMPUTE" />
- <Key word="CONSTRAINT" />
- <Key word="CONTAINS" />
- <Key word="CONTINUE" />
- <Key word="CONVERT" />
- <Key word="CROSS" />
- <Key word="CURSOR" />
- <Key word="DECLARE" />
- <Key word="DEFAULT" />
- <Key word="DESC" />
- <Key word="DISTINCT" />
- <Key word="DOUBLE" />
- <Key word="ELSE" />
- <Key word="ESCAPE" />
- <Key word="EXCEPT" />
- <Key word="EXECUTE" />
- <Key word="EXISTS" />
- <Key word="EXIT" />
- <Key word="FETCH" />
- <Key word="FOR" />
- <Key word="FROM" />
- <Key word="FULL" />
- <Key word="FUNCTION" />
- <Key word="GOTO" />
- <Key word="GROUP" />
- <Key word="HAVING" />
- <Key word="IDENTITY" />
- <Key word="IDENTITY_INSERT" />
- <Key word="IDENTITYCOL" />
- <Key word="IF" />
- <Key word="IN" />
- <Key word="INSERT" />
- <Key word="INTO" />
- <Key word="IS" />
- <Key word="KEY" />
- <Key word="NOCHECK" />
- <Key word="NOT" />
- <Key word="NULL" />
- <Key word="NULLIF" />
- <Key word="OF" />
- <Key word="OFF" />
- <Key word="OFFSETS" />
- <Key word="ON" />
- <Key word="OPEN" />
- <Key word="ORDER" />
- <Key word="OVER" />
- <Key word="PRECISION" />
- <Key word="PROC" />
- <Key word="PROCEDURE" />
- <Key word="PUBLIC" />
- <Key word="READ" />
- <Key word="READTEXT" />
- <Key word="REFERENCES" />
- <Key word="RESTORE" />
- <Key word="RESTRICT" />
- <Key word="RETURN" />
- <Key word="ROWCOUNT" />
- <Key word="RULE" />
- <Key word="SAVE" />
- <Key word="SELECT" />
- <Key word="SET" />
- <Key word="SETUSER" />
- <Key word="SOME" />
- <Key word="TABLE" />
- <Key word="THEN" />
- <Key word="TO" />
- <Key word="TRIGGER" />
- <Key word="UNIQUE" />
- <Key word="UPDATE" />
- <Key word="VALUES" />
- <Key word="VARYING" />
- <Key word="WAITFOR" />
- <Key word="WHEN" />
- <Key word="WHERE" />
- <Key word="WHILE" />
- <Key word="WITH" />
- </KeyWords>
- </RuleSet>
- </RuleSets>
- </SyntaxDefinition>
- var ext = Path.GetExtension(fileName);
- if (ext != null)
- {
- var def = HighlightingManager.Instance.GetDefinitionByExtension(ext);
- if (def != null)
- {
- textEditor.SyntaxHighlighting = def;
- }
- }
Следующей моей задачей было прикрутить intellisense. Так как я в основном пользуюсь C# и SQL, то мне будет достаточно сделать интелисенс только для них. Для себя я представил реализацию как просто набор ключевых слов синтаксиса, которые фильтруются по мере того, как пользователь набирает слово. Наборы ключевых слов я просто взял из MSDN (C# 2010, SQL Server 2008) и разделил их на 2 файла соответственно. Затем мне нужно было осуществить привязку текущего правила подсветки синтаксиса и набора ключевых слов. Это я сделал при помощи простого XML файла
- <?xml version="1.0" encoding="utf-8"?>
- <ArrayOfKeywordsFileOption xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
- <KeywordsFileOption HighlightingName="SQL" Filename="keywords_sql.txt" />
- <KeywordsFileOption HighlightingName="C#" Filename="keywords_csharp.txt" />
- </ArrayOfKeywordsFileOption>
Дальше всё просто - мне достаточно отследить пару событий текстового редактора, определить, стоит ли мне показывать окно с ключевыми словами - и если стоит, то получить список ключевых слов и отобразить его.
- private CompletionWindow _completionWindow;
- void TextEditorTextAreaTextEntered(object sender, TextCompositionEventArgs e)
- {
- ICompletionWindowResolver resolver = new CompletionWindowResolver(textEditor.Text, textEditor.CaretOffset, e.Text, textEditor);
- _completionWindow = resolver.Resolve();
- }
- void TextEditorTextAreaTextEntering(object sender, TextCompositionEventArgs e)
- {
- if (e.Text.Length > 0 && _completionWindow != null)
- {
- if (!char.IsLetterOrDigit(e.Text[0]))
- {
- _completionWindow.CompletionList.RequestInsertion(e);
- }
- }
- }
- public interface ICompletionWindowResolver
- {
- CompletionWindow Resolve();
- }
- public CompletionWindow Resolve()
- {
- var hiName = string.Empty;
- if (_target.SyntaxHighlighting != null)
- {
- hiName = _target.SyntaxHighlighting.Name;
- }
- var cdata = _dataProviders.SelectMany(x => x.GetData(_text, _position, _input, hiName)).ToList();
- int count = cdata.Count;
- if (count > 0)
- {
- var completionWindow = new CompletionWindow(_target.TextArea);
- var data = completionWindow.CompletionList.CompletionData;
- foreach (var completionData in cdata)
- {
- data.Add(completionData);
- }
- completionWindow.Show();
- completionWindow.Closed += delegate
- {
- completionWindow = null;
- };
- return completionWindow;
- }
- return null;
- }
- private readonly List<ICompletionDataProvider> _dataProviders = new List<ICompletionDataProvider>();
- public interface ICompletionDataProvider
- {
- IEnumerable<ICompletionData> GetData(string text, int position, string input, string highlightingName);
- }
В результате всех этих манипуляций, у меня получился блокнот с поддержкой пдсветки синтаксиса, автодополнением и удобными закладками. Иконку для приложения я взял среди бесплатных и выложил проект на кодеплекс.
Это всё. Всем спасибо.
А эту штука может работать с файлом, в котором смешаны 2 синтаксиса? Например, PHP и HTML.
ОтветитьУдалитьОдновременно может поддерживаться только одно правило подсветки синтаксиса на один документ. Но ничто не мешает самому определить правила подсветки синтаксиса. Это означает, что подсветить можно что угодно и как угодно.
ОтветитьУдалитьСпасибо. Отличная статья!
ОтветитьУдалитьОтличная статья, а как вы осуществили выпадающие меню при нажатии на Ctrl+Space?
ОтветитьУдалитьЭтот функционал встроен в AvalonEdit, мне осталось только подписаться на нужные события.
УдалитьА незнаиет под Windows 8 METRO есть подобный контрол?
УдалитьК сожалению под windows 8 я ещё не начал писать :)
УдалитьCпасибо за ответ)
УдалитьКак Вы осуществили создание нового документа?
ОтветитьУдалитьВсе исходники открыты, лежат на кодеплексе.
УдалитьСпасибо!
УдалитьСпасибо очень полезно.
УдалитьНе смог найти как в AvalonEdit подчеркнуть слово.
Есть ли такой сервис?
Добрый день. К сожалению, не могу ответить на вопрос. Даже если бы и знал ответ, прошло уже полтора года с момента публикации, многое уже могло измениться. Однако, это же WPF, возможно, можно подчеркнуть текст штатными средствами.
УдалитьДа, в AvalonEdit можно подчёркивать слова.
УдалитьВот пример текстового редактора http://poet.of.by/ru/, в котором реализована проверка орфографии с выделением ошибок и много других интересных вещей.
Код программы пока что закрыт, но я могу проконсультировать желающих по некоторым техническим деталям. Контакты есть на сайте.
Всего вам наилучшего
А вы не подскажите кка создать правило, которое будет работать на подобии интерпретатора html, читать строки с тегами, например `Description: This is an item number 1.` и выводить форматированный текст, опуская теги?
ОтветитьУдалитьНе уверен, что понял вопрос. Куда выводить? В посте нет преобразований текста, тут только подсветка и автокомплит.
УдалитьСпасбо, помогло!
ОтветитьУдалить