Sunday, September 7, 2014

AvalonEdit – Search panel customization

AvalonEdit serves as the underlying text editor for SharpDevelop, the free and open source alternative to Visual Studio. The team at SharpDevelop have decoupled a lot of functionalities and have provided the text editor as a separate Nuget Package (available here).

While configurable syntax highlighting and code-completion is a very well-known and advertised feature of AvalonEdit, few are aware that AvalonEdit ships with an out-of-the-box Search panel functionality.

Search panel in action

ILSpy Search panel

Image source: AvalonEdit default search panel in ILSpy

Implementation

After referencing the TextEditor in XAML and in code-behind, simply call the install method on the SearchPanel class to enable integrated search panel functionality.

SearchPanel.Install(HostsEditor);

The above snippet will enable the search panel functionality in the editor when the user presses the “CTRL + F” shortcut.


There is a handy Uninstall method available too to disable the search panel functionality.


Customizing the search panel’s look and feel


While the default UI for the search panel might be sufficient for your application, you might need to style the panel in general, to match the look and feel of your application.


While developing EasyHosts (an open-source hosts file editor), I had to re-style the default look and feel of the search panel to match the theme of the application (shown below). Re-styling the search panel template is a breeze thanks to WPF.


EasyHosts Search panel


Search panel template


The default search panel template is available on Github.


Note: EasyHosts uses MahApps.Metro UI theme, so brush references below will have to be replaced by your custom theme brushes.




<Style TargetType="editor:SearchPanel">

    <Setter Property="Template">

        <Setter.Value>

            <ControlTemplate TargetType="{x:Type editor:SearchPanel}">

                <Border Background="{DynamicResource WindowBackgroundBrush}" 

                        BorderBrush="{DynamicResource AccentColorBrush}" 

                        BorderThickness="1,0,1,1" HorizontalAlignment="Right" 

                        VerticalAlignment="Top" Cursor="Arrow">

                    <StackPanel Orientation="Horizontal">

                        <TextBox Name="PART_searchTextBox" Focusable="True" 

                                BorderBrush="{DynamicResource AccentColorBrush}" 

                                Width="150" Height="Auto" Margin="3,3,0,3">

                            <TextBox.Text>

                                <Binding Path="SearchPattern" 

                                        RelativeSource="{RelativeSource TemplatedParent}" 

                                        UpdateSourceTrigger="PropertyChanged">

                                    <Binding.ValidationRules>

                                        <ExceptionValidationRule />

                                    </Binding.ValidationRules>

                                </Binding>

                            </TextBox.Text>

                        </TextBox>

 

                        <!-- FindNext button -->

                        <Button Margin="0,1,1,1" Height="30" Width="30" Command="editor:SearchCommands.FindNext" 

                            ToolTip="{Binding Localization.FindNextText, 

                                    RelativeSource={RelativeSource TemplatedParent}}" 

                            Padding="1" Style="{DynamicResource MetroAccentButton}"

                            BorderThickness="0" BorderBrush="Transparent">

                            <Path Data="F1M-218.342,2910.79L-234.066,2926.52 -233.954,2926.63 -225.428,2926.63 -210.87,2912.07 -206.495,2907.7 -225.313,2888.88 -234.066,2888.88 -218.342,2904.6 -259.829,2904.6 -259.829,2910.79 -218.342,2910.79z" 

                                Style="{DynamicResource DefaultButtonPathStyle}" />

                        </Button>

 

                        <!-- FindPrevious (set visibility if required) button -->

                        <Button Margin="1" Height="30" Width="30" Command="editor:SearchCommands.FindPrevious" 

                            ToolTip="{Binding Localization.FindPreviousText, 

                            RelativeSource={RelativeSource TemplatedParent}}" 

                            Padding="1" Style="{DynamicResource AccentedSquareButtonStyle}" 

                            BorderThickness="0" BorderBrush="Transparent" Visibility="Collapsed">

                            <Path Data="F1M-185.925,-2026.96L-203.062,-2048.74C-197.485,-2056.51 -197.433,-2067.31 -203.64,-2075.2 -211.167,-2084.76 -225.019,-2086.42 -234.588,-2078.89 -244.154,-2071.36 -245.808,-2057.51 -238.282,-2047.94 -231.986,-2039.95 -221.274,-2037.5 -212.337,-2041.31L-195.262,-2019.61 -185.925,-2026.96z M-231.201,-2053.51C-235.653,-2059.17 -234.674,-2067.36 -229.02,-2071.81 -223.36,-2076.26 -215.169,-2075.29 -210.721,-2069.63 -206.269,-2063.97 -207.245,-2055.78 -212.902,-2051.33 -218.559,-2046.88 -226.752,-2047.86 -231.201,-2053.51z" 

                                Stretch="Uniform" Fill="{DynamicResource IdealForegroundColorBrush}" 

                                Width="16" Height="16" />

                        </Button>

 

                        <StackPanel Orientation="Horizontal">

                            <ToggleButton Width="36" Height="36" Margin="0" Cursor="Hand"

                                ToolTip="{Binding Localization.MatchCaseText, RelativeSource={RelativeSource TemplatedParent}}"

                                IsChecked="{Binding MatchCase, RelativeSource={RelativeSource TemplatedParent}}"

                                Style="{DynamicResource MetroCircleToggleButtonStyle}" Content="aA" FontWeight="Bold" FontFamily="Consolas,Courier New,Courier">

                            </ToggleButton>

 

                            <ToggleButton Width="36" Height="36" Margin="0" Cursor="Hand" Style="{DynamicResource MetroCircleToggleButtonStyle}" 

                                ToolTip="{Binding Localization.MatchWholeWordsText, RelativeSource={RelativeSource TemplatedParent}}"

                                IsChecked="{Binding WholeWords, RelativeSource={RelativeSource TemplatedParent}}"

                                Content="Ab" FontWeight="Bold" FontFamily="Consolas,Courier New,Courier">

                            </ToggleButton>

 

                            <ToggleButton Width="36" Height="36" Margin="0" Cursor="Hand" Style="{DynamicResource MetroCircleToggleButtonStyle}" 

                                ToolTip="{Binding Localization.UseRegexText, RelativeSource={RelativeSource TemplatedParent}}"

                                IsChecked="{Binding UseRegex, RelativeSource={RelativeSource TemplatedParent}}"

                                Content="a*" FontWeight="Bold" FontFamily="Consolas,Courier New,Courier">

                            </ToggleButton>

                        </StackPanel>

 

                        <!-- Search Panel close button -->

                        <Button Height="16" Width="16" HorizontalAlignment="Right" Padding="0"

                                Background="Transparent" Cursor="Hand"

                                VerticalAlignment="Top" Command="editor:SearchCommands.CloseSearchPanel"

                                VerticalContentAlignment="Center" HorizontalContentAlignment="Center">

                            <Path Data="F1M54.0573,47.8776L38.1771,31.9974 54.0547,16.1198C55.7604,14.4141 55.7604,11.6511 54.0573,9.94531 52.3516,8.23962 49.5859,8.23962 47.8802,9.94531L32.0026,25.8229 16.1224,9.94531C14.4167,8.23962 11.6511,8.23962 9.94794,9.94531 8.24219,11.6511 8.24219,14.4141 9.94794,16.1198L25.8255,32 9.94794,47.8776C8.24219,49.5834 8.24219,52.3477 9.94794,54.0534 11.6511,55.7572 14.4167,55.7585 16.1224,54.0534L32.0026,38.1745 47.8802,54.0534C49.5859,55.7585 52.3516,55.7572 54.0573,54.0534 55.7604,52.3477 55.763,49.5834 54.0573,47.8776z" 

                                Height="10" Width="10" Stretch="Uniform" Fill="Red" Margin="0" />

                        </Button>

                    </StackPanel>

                </Border>

            </ControlTemplate>

        </Setter.Value>

    </Setter>


</Style>




Happy Coding!