MVVM with richtextbox

Eduardo Gomez 3,416 Reputation points
2021-05-14T16:45:09.037+00:00

Hello

I am implementing a richtextbox on my WPF, using MVVM, but I found that is very problematic, could somebody help me with MVVMFY, I also find out that WPF is very troublesome to work with, especially when you make dependency properties and user controls. You have to rebuild every time, so it can be aware of changes, which is a pain.

Anyway,

  • As I mentioned earlier, I do not know how to work with MVVM and the rich textbox

My app is almost finished, but I want to get rid of all my code behind, and put it in my view model

This is the UI for the notes Page

                        <ToggleButton BorderBrush="Transparent"
                                      Background="Transparent"
                                      Name="SpeechBton"
                                      FontSize="18"
                                      Tag="0"
                                      Click="SpeechBton_Click">
                            <TextBlock Text="Speech" />
                        </ToggleButton>
                        <ToggleButton Margin="20,0,0,0"
                                      Name="boldBton"
                                      FontSize="18"
                                      Tag="1"
                                      Click="SpeechBton_Click"
                                      BorderBrush="Transparent"
                                      Background="Transparent">
                            <TextBlock Text="B" />
                        </ToggleButton>
                        <ToggleButton Margin="20,0,0,0"
                                      Name="italicBton"
                                      FontSize="18"
                                      Tag="2"
                                      Click="SpeechBton_Click"
                                      BorderBrush="Transparent"
                                      Background="Transparent">
                            <TextBlock Text="I" />
                        </ToggleButton>
                        <ToggleButton Margin="20,0,0,0"
                                      FontSize="18"
                                      Name="UndelineBton"
                                      Tag="3"
                                      Click="SpeechBton_Click"
                                      BorderBrush="Transparent"
                                      Background="Transparent">
                            <TextBlock Text="U" />
                        </ToggleButton>
                        <Fluent:ComboBox Width="100"
                                         x:Name="FontsComboBox"
                                         ItemsSource="{Binding fonts, Mode=TwoWay}"
                                         SelectionChanged="FontsComboBox_SelectionChanged"
                                         IsEditable="False" />
                        <Fluent:ComboBox Width="50"
                                         ItemsSource="{Binding FontSizes, Mode=TwoWay}"
                                         x:Name="SizeConboBox"
                                         SelectionChanged="SizeConboBox_SelectionChanged" />
                    </StackPanel>
                </ToolBar>
            </ToolBarTray>
            <RichTextBox Grid.Row="1"
                         x:Name="NoteContent"
                         TextChanged="NoteContent_TextChanged"
                         SelectionChanged="NoteContent_SelectionChanged"
                         Foreground="Black"/>
            <StatusBar Grid.Row="2">
                <TextBlock x:Name="NumOfCharacters" />
            </StatusBar>

My VM for notes

  public ICommand ExitCommand { get; set; }
    public ICommand NewNotebookCommand { get; set; }
    public ICommand NewNoteCommand { get; set; }
    public ICommand RenameCommand { get; set; }
    public List<int> FontSizes { get; set; }
    public IOrderedEnumerable<FontFamily> fonts { get; set; }
    public ObservableCollection<Notebook> Notebooks { get; set; }
    public ObservableCollection<Note> Notes { get; set; }

    private Notebook _SelectedNoteBook;
    public Notebook SelectedNoteBook {
        get { return _SelectedNoteBook; }
        set {
            if (_SelectedNoteBook != value) {
                _SelectedNoteBook = value;
                RaisePropertyChanged();
                GetNotes();
            }
        }
    }


    private bool _IsVisible;
    public bool IsVisible {
        get { return _IsVisible; }
        set {
            if (_IsVisible != value) {
                _IsVisible = value;
                RaisePropertyChanged();
            }
        }
    }

    public NotesWindowVM() {

        IsVisible = false;
        Notes = new ObservableCollection<Note>();
        Notebooks = new ObservableCollection<Notebook>();
        fonts = Fonts.SystemFontFamilies.OrderBy(f => f.Source);
        FontSizes = new List<int>();
        for (int i = 7; i < 72; i++) {
            FontSizes.Add(i);
        }
        RenameCommand = new Command(() => {
            IsVisible = true;
        });
        ExitCommand = new Command(() => {
            Application.Current.Shutdown();
        });
        NewNotebookCommand = new Command(() => {
            CreateNewNotebook();
        });
        NewNoteCommand = new HelperCommand {
            ExecuteDelegate = x => CreateNewNote(SelectedNoteBook.Id),
            CanExecuteDelegate = x => SelectedNoteBook != null
        };

        GetNoteBooks();
    }

    private void CreateNewNote(int NotebookId) {
        Note note = new() {
            NotebookId = NotebookId,
            CreatedAt = DateTime.Now,
            UpdatedAt = DateTime.Now,
            Title = "New Note"
        };
        Database.Insert(note);

        GetNotes();
    }

    private void CreateNewNotebook() {
        Notebook notebook = new() { Name = $"Notebook" };
        Database.Insert(notebook);

        GetNoteBooks();
    }

    private void GetNoteBooks() {

        var notebooks = Database.Read<Notebook>();

        Notebooks.Clear();
        foreach (var item in notebooks) {
            Notebooks.Add(item);
        }
    }

    private void GetNotes() {

        if (SelectedNoteBook != null) {
            var notes = Database.Read<Note>().Where(
                n => n.NotebookId == SelectedNoteBook.Id)
                .ToList();

            Notes.Clear();
            foreach (var item in notes) {
                Notes.Add(item);
            }
        }
    }
}

and I want to get rid of all this and implemented it on my VM

code-behind for notes

    public NotesWindow() {
        InitializeComponent();

        var speechRecognizer = SpeechConfig.FromSubscription(key, region);
        var audioConfig = AudioConfig.FromDefaultMicrophoneInput();
        recog = new SpeechRecognizer(speechRecognizer, audioConfig);

        recog.Recognized += SpeechRecognizer_Recognized;
    }
    private async void SpeechRecognizer_Recognized(object sender, SpeechRecognitionEventArgs e) {
        if (e.Result.Reason == ResultReason.RecognizedSpeech) {
            await Dispatcher.InvokeAsync(() => NoteContent.AppendText($"{e.Result.Text}"));

        }
    }

    private void NoteContent_TextChanged(object sender, TextChangedEventArgs e) {
        FlowDocument doc = NoteContent.Document;
        int count = new TextRange(doc.ContentStart, doc.ContentEnd).Text.Length;
        NumOfCharacters.Text = $"Characters: {count}";
    }

    private async void SpeechBton_Click(object sender, RoutedEventArgs e) {

        var toggle = sender as System.Windows.Controls.Primitives.ToggleButton;
        switch (toggle.Tag) {
            case "0":
                if (!isRecognizing) {
                    await recog.StartContinuousRecognitionAsync();

                    isRecognizing = true;
                    SpeechBton.IsChecked = true;
                } else {
                    await recog.StopContinuousRecognitionAsync();

                    isRecognizing = false;
                    SpeechBton.IsChecked = false;
                }
                break;
            case "1":
                bool isChecked = toggle.IsChecked ?? false;
                if (isChecked) {
                    NoteContent.Selection.ApplyPropertyValue(FontWeightProperty, FontWeights.Bold);
                } else {
                    NoteContent.Selection.ApplyPropertyValue(FontWeightProperty, FontWeights.Normal);
                }
                break;
            case "2":
                bool isEnable = toggle.IsChecked ?? false;
                if (isEnable) {
                    NoteContent.Selection.ApplyPropertyValue(FontStyleProperty, FontStyles.Italic);
                } else {
                    NoteContent.Selection.ApplyPropertyValue(FontStyleProperty, FontStyles.Normal);
                }
                break;
            case "3":
                bool isPressed = toggle.IsChecked ?? false;
                if (isPressed) {
                    NoteContent.Selection.ApplyPropertyValue(Inline.TextDecorationsProperty, TextDecorations.Underline);
                } else {

                    (NoteContent.Selection.GetPropertyValue(Inline.TextDecorationsProperty) as TextDecorationCollection)
                        .TryRemove(TextDecorations.Underline, out TextDecorationCollection decorations);
                    NoteContent.Selection.ApplyPropertyValue(Inline.TextDecorationsProperty, decorations);
                }
                break;
        }
    }

    private void NoteContent_SelectionChanged(object sender, RoutedEventArgs e) {

        var selectionFontWeight = NoteContent.Selection.GetPropertyValue(Inline.FontWeightProperty);
        boldBton.IsChecked = (selectionFontWeight != DependencyProperty.UnsetValue)
            && selectionFontWeight.Equals(FontWeights.Bold);

        var selectionFontStyle = NoteContent.Selection.GetPropertyValue(Inline.FontStyleProperty);
        italicBton.IsChecked = (selectionFontStyle != DependencyProperty.UnsetValue)
            && selectionFontStyle.Equals(FontStyles.Italic);

        var selectionFontDecoration = NoteContent.Selection.GetPropertyValue(Inline.TextDecorationsProperty);
        UndelineBton.IsChecked = (selectionFontDecoration != DependencyProperty.UnsetValue)
            && selectionFontDecoration.Equals(TextDecorations.Underline);

        FontsComboBox.SelectedItem = NoteContent.Selection.GetPropertyValue(FontFamilyProperty);
        SizeConboBox.Text = (NoteContent.Selection.GetPropertyValue(FontSizeProperty)).ToString();
    }

    private void FontsComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) {
        if (FontsComboBox.SelectedItem != null) {
            NoteContent.Selection.ApplyPropertyValue(FontFamilyProperty, FontsComboBox.SelectedItem);
        }
    }

    private void SizeConboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) {
        NoteContent.Selection.ApplyPropertyValue(FontSizeProperty, SizeConboBox.Text);
    }
}

}

Windows Presentation Foundation
Windows Presentation Foundation
A part of the .NET Framework that provides a unified programming model for building line-of-business desktop applications on Windows.
2,706 questions
XAML
XAML
A language based on Extensible Markup Language (XML) that enables developers to specify a hierarchy of objects with a set of properties and logic.
786 questions
{count} votes

Accepted answer
  1. DaisyTian-1203 11,621 Reputation points
    2021-05-19T06:47:08.34+00:00

    I will show you a sample of RichTextBox with MVVM. Here is my code for you:
    XAMl code:

     <Window.DataContext>  
            <local:ViewModel></local:ViewModel>  
        </Window.DataContext>  
        <StackPanel>  
            <TextBox Text="{Binding  model.ID}" Width="200" Height="40"/>  
            <TextBox Text="{Binding model.Name}" Width="200" Height="40" Margin="0 20 0 20"/>  
            <RichTextBox Name="rtf" local:RichTextBoxHelper.DocumentXaml="{Binding model.Description}" Width="200" Height="40" />  
            <WrapPanel VerticalAlignment="Center" Width="100">  
                <ToggleButton x:Name="BoldButton" Command="EditingCommands.ToggleBold" CommandTarget="{Binding ElementName=rtf}" ToolTip="Bold" Width="20" Margin="0,1,0,1">  
                    <Image Source="/fontbold.png" Stretch="None" SnapsToDevicePixels="True" />  
                </ToggleButton>  
                <ToggleButton x:Name="ItalicButton" Command="EditingCommands.ToggleItalic" CommandTarget="{Binding ElementName=rtf}" ToolTip="Bold" Width="20" Margin="0,1,0,1">  
                    <Image Source="/fontitalic.png" Stretch="None" SnapsToDevicePixels="True" />  
                </ToggleButton>  
            </WrapPanel>  
             
        </StackPanel>  
    

    RichTextBoxHelper.cs code:

    using System;  
    using System.Collections.Generic;  
    using System.IO;  
    using System.Text;  
    using System.Threading;  
    using System.Windows;  
    using System.Windows.Controls;  
    using System.Windows.Documents;  
    using System.Windows.Markup;  
      
    namespace RichTextBoxMVVM  
    {  
        public class RichTextBoxHelper : DependencyObject  
        {  
            private static HashSet<Thread> _recursionProtection = new HashSet<Thread>();  
      
            public static string GetDocumentXaml(DependencyObject obj)  
            {  
                return (string)obj.GetValue(DocumentXamlProperty);  
            }  
      
            public static void SetDocumentXaml(DependencyObject obj, string value)  
            {  
                _recursionProtection.Add(Thread.CurrentThread);  
                obj.SetValue(DocumentXamlProperty, value);  
                _recursionProtection.Remove(Thread.CurrentThread);  
            }  
      
            public static readonly DependencyProperty DocumentXamlProperty = DependencyProperty.RegisterAttached(  
                "DocumentXaml",  
                typeof(string),  
                typeof(RichTextBoxHelper),  
                new FrameworkPropertyMetadata(  
                    "",  
                    FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,  
                    (obj, e) => {  
                        if (_recursionProtection.Contains(Thread.CurrentThread))  
                            return;  
      
                        var richTextBox = (RichTextBox)obj;  
                    try  
                        {  
                            var stream = new MemoryStream(Encoding.UTF8.GetBytes(GetDocumentXaml(richTextBox)));  
                            var doc = (FlowDocument)XamlReader.Load(stream);  
      
                        richTextBox.Document = doc;  
                        }  
                        catch (Exception)  
                        {  
                            richTextBox.Document = new FlowDocument();  
                        }  
      
                    richTextBox.TextChanged += (obj2, e2) =>  
                        {  
                            RichTextBox richTextBox2 = obj2 as RichTextBox;  
                            if (richTextBox2 != null)  
                            {  
                                SetDocumentXaml(richTextBox, XamlWriter.Save(richTextBox2.Document));  
                            }  
                        };  
                    }  
                )  
            );  
        }  
    }  
      
    

    ViewModel.cs and Model.cs code:

      public class ViewModel  
        {  
            public Model model { get; set; }  
      
            public ViewModel()  
            {  
                model = new Model()  
                {  
                    ID = "001",  
                    Name = "Daisy",  
                    Description = "<FlowDocument xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"><Paragraph Foreground=\"Red\"><Bold>Hello, this is RichTextBox</Bold></Paragraph></FlowDocument>"  
                };  
            }  
        }  
      
        public class Model  
        {  
            public string ID { get; set; }  
            public string Name { get; set; }  
            public string Description { get; set; }  
        }  
    

    The result picture is:
    97720-3.gif

    By the way, you may need to improve the way you ask questions. It's better for you to make your question clear without excess irrelevant code, and it not a good way to ask multiple functions to be implemented on the one question. You could create a new question to ask your Speech problem.


    If the response is helpful, please click "Accept Answer" and upvote it.
    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

    0 comments No comments

1 additional answer

Sort by: Most helpful
  1. Ken Tucker 5,846 Reputation points
    2021-05-16T00:45:58.027+00:00

    For the second part if you have set the Window DataContext you should be able to do this

     <MenuItem Header="Rename"  Command="{Binding Rename">