Search code examples
c#textuwpthemesricheditbox

UWP RichEditBox text color issue when saved in dark mode and opened in light mode


Basically in my app I have a RichEditBox which is required to save its data on TextChanged event and load the text from saved settings OnLoaded event, after weeks of experimentation I was able to reproduce the issue in an minimal app for you guys to test.

Aim : Ultimately no matter I use dark or light theme to save the RTF text in this rich edit box, whenever it is loaded again in whatever theme it should show the correct text color in both dark and light themes. and during the running app if the user changes theme of their device, the text color should also change as expected. I am not sure how to save the rtf text here it ignores the text color maybe?

Reproduce the bug here : https://github.com/touseefbsb/RichEditBoxColorBug

  1. Make sure your device theme is on "Dark".
  2. Run the app and add some text into the RichEditBox (the textblock and button on top are just to make sure the app doesnt focus automatically on richeditbox when page loads).

image 1

image 2

  1. click somewhere else on screen to loose focus from richeditbox, then close the app.
  2. Run the app again you you'll notice the text you entered before is already there as expected, now close the app.

image 3

  1. Turn the theme of ur device to "Light" and again run the app, now you will notice the richeditbox seems to be empty.

image 4

  1. But actually it isnt empty, the issue is the textcolor is white just like the color of richeditbox while the text color shouldve been black in light theme. This can be proved just by selecting the text with cursor and notice the highlighted text appears.

image 5

Note

Everytime you change something and try to test the whole flow again just make sure to change the key string in both Loaded and TextChanged events, to make sure entirely new RTF value is being saved and being loaded later, the key in loaded and textchanging events must always match and should be changed everytime you want to start from step 1.

image 6

CODE

Xaml

 <StackPanel>
    <TextBlock>abc</TextBlock>
    <Button>abc</Button>
    <RichEditBox
        x:Name="REB"
        Height="60"
        AcceptsReturn="True"
        BorderThickness="0"
        Loaded="REB_Loaded"
        PlaceholderText="placeholder."
        TextChanged="REB_TextChanged"
        TextWrapping="Wrap" />
</StackPanel>

Code Behind

private void REB_Loaded(object sender, RoutedEventArgs e)
    {
        var localSettings = ApplicationData.Current.LocalSettings;
        var localValue = localSettings.Values["ts5"] as string; // Change the key value on every new test
        var text = string.IsNullOrEmpty(localValue) ? string.Empty : localValue;
        REB.Document.SetText(TextSetOptions.FormatRtf, text);
    }

    private void REB_TextChanged(object sender, RoutedEventArgs e)
    {
        var localSettings = ApplicationData.Current.LocalSettings;
        REB.Document.GetText(TextGetOptions.FormatRtf, out var tmpNar);
        if (!string.IsNullOrEmpty(tmpNar) && !string.IsNullOrWhiteSpace(tmpNar))
        {
            localSettings.Values["ts5"] = tmpNar; // Change the key value on every new test
        }
    }

MISC Info

Windows 10 device version : 1903

Project target and min sdk version : 1903


Solution

  • I had similar issues trying to convert RTF to HTML from a RichEditBox.

    Intro

    As long as we assume you don't allow font color changes it is not that hard. Both suggested options would also work if you allow font color changes through the document, but this introduces a lot of work and trade-off (i.e. do you invert colors selected in light theme when displaying them in dark?, certain colors look better with black background, others with white, etc.)

    1. Change the ITextDocument

    This option is quite simple and works quite well. Underneath the RichEditBox there is an ITextDocument which contains the actual text (accessed through RichEditBox.Document). After you set the text of this document you can also set the font color (it is even possible to change to font color for certain parts of the text this way):

    REB_Loaded

    private void REB_Loaded(object sender, RoutedEventArgs e)
    {
        var localSettings = ApplicationData.Current.LocalSettings;
        var localValue = localSettings.Values["ts4"] as string;
        var text = string.IsNullOrEmpty(localValue) ? string.Empty : localValue;
        REB.Document.SetText(TextSetOptions.FormatRtf, text);
    
        // Select all text currently in the RichtEditBox 
        // and make it white or black depending on the currently requested theme
        REB.Document.GetRange(0, text.Length).CharacterFormat.ForegroundColor =
            Window.Current.Content is FrameworkElement fe
                ? fe.ActualTheme == ElementTheme.Dark
                    ? Windows.UI.Colors.White
                    : Windows.UI.Colors.Black
                : Windows.UI.Colors.Black; // Assume light theme if actual theme cannot be determined
    }
    

    I have tested this and this seems to work as well.

    2. Change the RTF

    A more low-level method would be to just change the raw RTF just after you load it from the LocalSettings and just before you set the text of the RichEditBox. If you inspect the raw RTF you would see something like this:

    {\rtf1\fbidis\ansi\ansicpg1252\deff0\nouicompat\deflang2057{\fonttbl{\f0\fnil\fcharset0 Segoe UI;}{\f1\fnil Segoe UI;}}
    {\colortbl ;\red255\green255\blue255;}
    {\*\generator Riched20 10.0.19041}\viewkind4\uc1 
    \pard\tx720\cf1\f0\fs21\lang1033 test text\f1\par}
    

    The thing to note here is the second line: {\colortbl ...}, this part defines the font color. If you now just change the 255's to 0's you change the font from white to black. I have written two extensions methods and a quick test in your code seems to work:

    Extension class

    public static class Extensions
    {
        public static string ConvertWhiteTextToBlack(this string s)
            => s.Replace("\\red255\\green255\\blue255", "\\red0\\green0\\blue0");
    
        public static string ConvertBlackTextToWhite(this string s)
            => s.Replace("\\red0\\green0\\blue0", "\\red255\\green255\\blue255");
    }
    

    REB_Loaded

    private void REB_Loaded(object sender, RoutedEventArgs e)
    {
        var localSettings = ApplicationData.Current.LocalSettings;
        var localValue = localSettings.Values["ts4"] as string;
        var text = string.IsNullOrEmpty(localValue) ? string.Empty : localValue;
    
        System.Diagnostics.Debug.WriteLine("[REB_Loaded (start)]" + text);
        // Make black text white if dark theme is requested
        text = Window.Current.Content is FrameworkElement fe
            ? fe.ActualTheme == ElementTheme.Light
                ? text.ConvertWhiteTextToBlack()
                : text.ConvertBlackTextToWhite()
            : text.ConvertWhiteTextToBlack(); // Assume light theme if actual theme cannot be determined
        System.Diagnostics.Debug.WriteLine("[REB_Loaded (end)]" + text);
    
        REB.Document.SetText(TextSetOptions.FormatRtf, text);
    }
    

    P.S. I hope these solutions are also applicable outside your MVCE and in your main app. If not post a reply, and I'll try to help you out.
    P.P.S. You don't need to change your whole PC theme to change the app from dark to light. Instead just set the RequestedTheme property in the Page header in MainPage.xaml to either Light or Dark.