WPF Word Like Editor

Bakgrunnen for denne posten var at jeg hadde behov for en tekst editor kontroll a la RadEditor for asp.net. Eller TinyEditor for .net eller en mini Word om du vil.
Jeg søkte etter ferdige komponenter men fant bare store kontroller som var beregnet på å lage ide’er eller kode håndteringsverktøy.
Det første jeg til sist gjorde når jeg skjønte at jeg ble nødt til å lage en selv, var å opprette et WPF UserControl Library. Dette for at jeg skal kunne gjenbruke koden min. I dette biblioteket opprettet jeg en usercontrol kalt JHEditor.
Det var 3 ting jeg ønsket med denne editoren. Den skulle være enkel, støtte de enkleste editor funksjonene og samtidig exponere en Text egenskap. Slik at jeg kunne si noe sånt som enEditor.Text = “jsdlkjølkasldkjf” eller string t = enEditor.Text. Altså omtrent som en hvilke som helst tekstbox. Men den store forskjellen ligger i at jeg har behov for å stile teksten. Omtrent som styles i html. Løsningen havnet etterhvert på RichTextBox, denne støttet det jeg trengte.
Men ikke helt, for ikke kunne jeg stile teksten ordentlig og ei heller sette den eller hente den ut. Grunnen er at RichTextBox ikke har et felt for Text eller et Content eller noe sånt. Nei den bruker et FlowDocument til å presentere teksten i. I tillegg har ikke RichTextBox noen former for toolbars osv. Dermed fant jeg ut at jeg måtte til nesten fra bunden av.
Editoren består dermed av tre hovedbestanddeler fant jeg ut. Et textvindu til å editere teksten i, en verktøylinje i toppen og en text verdi som kan hentes ut eller settes.
Slik endte min JHEditor.xaml med å se ut:

<UserControl x:Class=»JHControls.JHEditor.JHEditor»
            xmlnshttp://schemas.microsoft.com/winfx/2006/xaml/presentation&raquo;
            xmlns:xhttp://schemas.microsoft.com/winfx/2006/xaml&raquo;
            xmlns:mchttp://schemas.openxmlformats.org/markup-compatibility/2006&#8243; 
            xmlns:dhttp://schemas.microsoft.com/expression/blend/2008&#8243; 
            mc:Ignorable=»d» 
            d:DesignHeight=»300″ d:DesignWidth=»600″ HorizontalAlignment=»Stretch» VerticalAlignment=»Stretch» Background=»{x:Null}»>
   
   
<Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height=»43″ />
            <RowDefinition Height=»*» />
        </Grid.RowDefinitions>
      
       
<ToolBar Grid.Column=»0″ Grid.Row=»0″ Height=»43″ HorizontalAlignment=»Stretch» Margin=»15,0,15,0″ Name=»toolBar1″ VerticalAlignment=»Top» Width=»Auto» Background=»{x:Null}»>
            <ToggleButton Command=»EditingCommands.ToggleBold» CommandTarget=»{Binding ElementName=rtfText}»>
               
<Image Width=»32″ Height=»32″ Source=»Images/format-text-bold.png»
                           ToolTipService.ToolTip=»Bold» ToolTip=»Bold» />
            </ToggleButton>
            <ToggleButton Command=»EditingCommands.ToggleItalic» CommandTarget=»{Binding ElementName=rtfText}»>
                <Image Width=»32″ Height=»32″ Source=»Images/format-text-italic.png»
                           ToolTipService.ToolTip=»Italic» ToolTip=»Italic» />
            </ToggleButton>
            <ToggleButton Command=»EditingCommands.ToggleUnderline» CommandTarget=»{Binding ElementName=rtfText}»>
                <Image Width=»32″ Height=»32″ Source=»Images/format-text-underline.png»
                           ToolTipService.ToolTip=»Underline» ToolTip=»Underline» />
            </ToggleButton>
            <Button Command=»EditingCommands.AlignLeft» CommandTarget=»{Binding ElementName=rtfText}»>
                <Image Width=»32″ Height=»32″ Source=»Images/format-justify-left.png»
                           ToolTipService.ToolTip=»Align Left» ToolTip=»Align Left» />
            </Button>
            <Button Command=»EditingCommands.AlignCenter» CommandTarget=»{Binding ElementName=rtfText}»>
                <Image Width=»32″ Height=»32″ Source=»Images/format-justify-center.png»
                           ToolTipService.ToolTip=»Align Center» ToolTip=»Align Center» />
            </Button>
            <Button Command=»EditingCommands.AlignRight» CommandTarget=»{Binding ElementName=rtfText}»>
                <Image Width=»32″ Height=»32″ Source=»Images/format-justify-right.png»
                           ToolTipService.ToolTip=»Align Right» ToolTip=»Align Right» />
            </Button>
            <Button Command=»EditingCommands.IncreaseFontSize» CommandTarget=»{Binding ElementName=rtfText}»>
                <Image Width=»32″ Height=»32″ Source=»Images/format-text-increase.png»
                           ToolTipService.ToolTip=»Increase Font» ToolTip=»Increase Font» />
            </Button>
            <Button Command=»EditingCommands.DecreaseFontSize» CommandTarget=»{Binding ElementName=rtfText}»>
                <Image Width=»32″ Height=»32″ Source=»Images/format-text-decrease.png»
                           ToolTipService.ToolTip=»Decrease Font» ToolTip=»Decrease Font» />
            </Button>
        </ToolBar>
 
        <Border Grid.Column=»0″ Grid.Row=»1″ CornerRadius=»10″ BorderThickness=»3″ Margin=»1,-2,1,1″ Background=»White» BorderBrush=»#FFB4AFAF»>
            <RichTextBox x:Name=»rtfText» HorizontalAlignment=»Stretch» VerticalAlignment=»Stretch» Margin=»5″ Background=»{x:Null BorderBrush=»{x:Null}»>
           
           
</RichTextBox>
        </Border>
       
   
</Grid
>
</
UserControl>

La oss se på de “viktigste” bitene:

<RichTextBox x:Name=»rtfText» HorizontalAlignment=»Stretch» VerticalAlignment=»Stretch» Margin=»5″ Background=»{x:Null BorderBrush=»{x:Null}»>
</RichTextBox>

Denne gir deg en richTextBox som teksten presenteres i og som man kan editere teksten i.

<ToolBar Grid.Column=»0″ Grid.Row=»0″ Height=»43″ HorizontalAlignment=»Stretch» Margin=»15,0,15,0″ Name=»toolBar1″ VerticalAlignment=»Top» Width=»Auto» Background=»{x:Null}»>
            <ToggleButton Command=»EditingCommands.ToggleBold» CommandTarget=»{Binding ElementName=rtfText}»>
                <Image Width=»32″ Height=»32″ Source=»Images/format-text-bold.png»
                           ToolTipService.ToolTip=»Bold» ToolTip=»Bold» />
            </ToggleButton
>
</
ToolBar>

Så har vi toolbar. Det denne gjør er å gi deg en standardisert måte å putte knapper inn i en og samme kontroll. Her har jeg lagt til en ToggleButton som ved førsteklikk er trykket inn og ved klikk nummer to blir klikket ut igjen akkurat slik word har med sine stiler.
Det som er viktig å merke seg her er at alle knapper i toolbaren må ha en CommandTarget som bindes til vår RichTextBox vist under.

CommandTarget=»{Binding ElementName=rtfText}»

I tillegg trenger vi å koble kommandoen til knappen opp mot RichTextboxen sin EditingCommands bibliotek.
Dette biblioteket inneholder en hel masse kommandoer som RichTextBox implementerer, dermed kan du fortelle knappen at når noen klikker på deg skal du sende denne kommandoen angitt i Command atributten til denne kontrollen angitt i CommandTarget atributten. Totalt støtter RichTextBox 47 slike forskjellige kommandoer. En oversikt finner du her EditingCommands Class.

Som dere ser av min ToolBar implementasjon har jeg kun tatt med det enkleste jeg ønsker at mine brukere skal ha mulighet til å gjøre. Men her kan man bygge inn alt man vil av funksjonalitet. Finnes ikke kommandoen som du ønsker å bruke så fortvil ikke da kan den implementeres som en vanlig Click event på knappen og håndteres i codebehind fila.

Vi har dermed implementert 2 av de tre egenskapene vi ville at kontrollen vår skulle ha. Nemlig et text område brukerne kan editere i og som viser stilene de ønsker å bruke, samt at vi har lagt til knapper som gir brukerne mulighet til å endre stiler. PS tastatursnarveier fungerer også uavhengig av ToolBaren vår.
Det siste vi ikke har fått til enda er å eksponere teksten som skrives inn, og eller editeres, inn eller ut til kontrollen vår. Dette må vi tilgjengeliggjøre i codebehind fila vår. Vi må ha en public property(atributt) som lar oss sette og hente ut texten. Dessverre er det slik at vår RichTextBox ikke har noen text  atributt vi kan bruke. Den har et innhold som er bassert på et FlowDocument. Dette gjør at man kan gjøre mye spennede med RichTextBoxen men for vår del er det overkill. Men det er FlowDocumentet som gjør at vi faktisk kan få et visuelt godt dokument og ikke bare ren tekst slik en ordinær flerlinjet tekstboks ville gitt oss. FlowDocumentet støtter stilering akkurat som html men i form av xaml.

Slik er implementeringen min av codebehind fila:

using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.IO;
 
namespace JHControls.JHEditor
{
    /// <summary>
    /// Interaction logic for JHEditor.xaml
    /// </summary>
    public partial class JHEditor : UserControl
    {
 
 
        public string Text
        {
            get 
            {
                TextRange tr = new TextRange(
                    rtfText.Document.ContentStart,
                    rtfText.Document.ContentEnd);
                MemoryStream ms = new MemoryStream();
                tr.Save(ms, DataFormats.Xaml);
                string xamlString =
                   ASCIIEncoding.Default.GetString(ms.ToArray());
                return xamlString;
                //return (string)GetValue(TextProperty);
            }
            set { SetValue(TextProperty, value); }
        }
 
        public static readonly DependencyProperty TextProperty =
            DependencyProperty.Register(«Text», typeof(string), typeof(JHEditor), new PropertyMetadata(OnTextChanged));
 
        public JHEditor()
        {
            InitializeComponent();
        }
 
        private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            JHEditor control = (JHEditor)d;
            if (e.NewValue == null)
                control.rtfText.Document = new FlowDocument(); //Document is not amused by null 🙂
 
            TextRange tr = new TextRange(control.rtfText.Document.ContentStart, control.rtfText.Document.ContentEnd);
            tr.Text = (string)e.NewValue;
           
        }  
    }
}

For å ta de viktigste tingene her uten å forklare alt i detalj så lager vi en public property Text som vi kobler sammen med en dependency property.
I Visual Studio er snippeten for dette propdp vær dog obs på at jeg registrerer en OnTextChanged metode til denne dependecy propertyen.
Dette gjør at settes teksten til kontrollen så vil denne metoden fyre og vi får mulighet til å sette teksten inn i RichTextboxen vår. Dette gjøres via en TextRange.

Tilsvarende så returnerer vi en komplett TextRange via en MemoryStream slik at vi kan hente ut texten lagret i dokumentet til RichTextBoxen vår.
Dette er tungvindt men den eneste gode måten å løse dette på. Dermed har vi nå knyttet opp vår Text atributt til vår kontroll og det gjenstår kun å bruke den.

For å bruke kontrollen vår i vårt prosjekt må vi gjøre 3 ting.
For det første må vi høyreklikke på References mappa i GUI prosjektet vårt og legge til en referanse til biblioteket vårt. Dette gjør vi ved å velge prosject eller browse og blad oss frem til bilioteket vårt.

For det andre må vi implementere et xmlns namespace som gjør at vi kan referer til biblioteket vårt i den klassen kontrollen skal brukes. Dette gjøres i Window tagen i xaml fila der du skal bruke kontrollen, eller i usercontrol tagen hvis du skal bruke den inne i en usercontrol.

xmlns:JHCtrl=»clr-namespace:JHControls.JHEditor;assembly=JHControls»

For det tredje må kontrollen i seg selv legges til slik vi legger til knapper ol.

<JHCtrl:JHEditor Grid.Row=»0″ x:Name=»txtInnlevering»></JHCtrl:JHEditor>

Her er også et eksempel der vi bruker DependencyPropertyen Text

<JHCtrl:JHEditor Grid.Row=»0″ x:Name=»txtInnlevering» Text=»TestText»></JHCtrl:JHEditor>

Vanskeliger enn dette var det faktisk ikke. Det tok meg sikkert rundt 5 timer å finne ut av det og det vanskeligste var å få RichTextBoxen til å spille på lag.
Men når det var gjort så var det bare enkel skuring resten av veien.
Veien videre for en slik komponent ville vært å hekte opp tastatur trykk med visuelt på knappene og hindre tastaturtrykk som du ikke har knapper til. Feks er det ikke sikkert du ønsker å la de angre dermed kan du skru av ctrl+z
Man kan også se for seg et scenario der man utvider kontrollen med mange flere dependencyproperties som lar deg velge enkel, normal eller avansert toolbar, mulighet for å styre toolbaren sin plassering, mulighet for å definere ribbons osv osv.

Men de avanserte funksjonene overlater jeg til andre. Eller om noen er interessert kanskje vi kunne startet et codeplex prosjekt 

Her er et skjermbilde av kontrollen i bruk etter implementering Smilefjes

JHEditor

JH

Dette innlegget ble publisert i Kontroll bibliotek, Wpf(Windows Presentation Foundation). Bokmerk permalenken.

1 svar til WPF Word Like Editor

  1. That was interesting piece of writing!!

Legg igjen en kommentar