This project has moved and is read-only. For the latest updates, please go here.

RichTextBox: SetText blocks UI

Feb 21, 2011 at 7:55 AM

Hello Brian,

 

I have sort of a protocoll editor with decisions and tasks. Every item has a datatemplated RichTextBox.

When I have 20 of those items for an agenda I have 20 RichTextBoxes which call rtfFormatter.SetText 20 times which results in a freezed UI because

every SetText needs 200ms showing ONE DINA4 page of formatted rtf text. My problem is, that I have multiple views for that protocoll editor. And when I switch between them

the appropriate data is loaded which is then blocking the switching or better said my alpha transition between the views.

Can  you offer a solution for this scenario please or any good advice?

thank you in advance.

 

Bastien

Feb 22, 2011 at 4:28 PM

My first thought is that you should load the data on a background thread so your UI stays responsive.  If you are not familar with using a background worker, this post may be helpful:

http://elegantcode.com/2009/07/03/wpf-multithreading-using-the-backgroundworker-and-reporting-the-progress-to-the-ui/

Let me know if this helps.

Feb 26, 2011 at 9:31 PM

thats my code:

 

private void SetUIText(FlowDocument doc, string text)
        {
            TextRange tr = new TextRange(doc.ContentStart, doc.ContentEnd);
            using (MemoryStream ms = new MemoryStream(Encoding.ASCII.GetBytes(text)))
            {
                tr.Load(ms, DataFormats.Rtf);
            }
        }

        public void SetText(FlowDocument document, string text)
        {
            var bgWorker = new BackgroundWorker();

            var data = new DataStruct();
            data.Doc = document;
            data.Text = text;

            bgWorker.DoWork += delegate(object s, DoWorkEventArgs args)
            {

                FlowDocument doc = ((DataStruct)args.Argument).Doc;
                String txt = ((DataStruct)args.Argument).Text;

                SetUIText(doc, text);

            };
            bgWorker.RunWorkerAsync(data);
        }

The code runs normally and when RTF text is about to be bound the code runs the tr.Load(xx) but the rtf data is not put in my RichTextbox ???

 

Do I sth wrong?

Feb 26, 2011 at 9:39 PM

You cannot set the text in the converter on a background thread, because you cannot update a UI from a bg thread.

Feb 26, 2011 at 10:45 PM

right I can only update via the Dispatcher. But I have a problem doing this. When I do dispatcher.BeginInvoke( ..) I pass a delegate pointing to a method. And this one method could be the SetUIText but then again the tr.Load would be executed on the UI thread...

 

Do you understand my problem Brian?

 

Btw. Brian congratulation for the community support award. Looking what you have done you really deserve it!

Feb 28, 2011 at 3:37 AM

I think I understand your problem.  Is it possible to not load the data until your transition has completed or to redesign the UI to where not all 20 have to be loaded at once?

Feb 28, 2011 at 9:00 AM

My 20 items must be loaded at once because they are displayed in a DataGrid datatemplated with your RichTextBox.

And actually My Transition knows nothing from the tr.Load...

Hm Maybe I can do an IsTransitionFinished Property in my ViewModel and when this is true I do my datafetching which is anyway async but the rtb.settext is still on the ui thread...

... not all 20 to be loaded... Hm... maybe YIELD can help me getting my data lazily and only when the user scrolls to the next row the ObservableCollection is enumerated again for the next item.

I gotta check that.

Mar 3, 2011 at 11:07 AM

@Brian I had no luck in what I tried.

I tried to use the Dispatcher.BeginInvoke for SetText like you used it for GetText. But It did not compile... 

Afair the first param for the BeginInvoke is a delegate, BUT you pass it the Priority.Background how come this? and it even compiles... I do not understand that.

Mar 3, 2011 at 1:54 PM

Well, that code isn't used anymore, but I know what you are talking about.  Don't let intellisense fool you.  There are actually four overloads for the BeginInvoke.

http://msdn.microsoft.com/en-us/library/system.windows.threading.dispatcher.begininvoke.aspx

 

Mar 3, 2011 at 9:30 PM
Edited Mar 3, 2011 at 9:31 PM

not used anymore???

 

Anyway... seems I got fooled last time. Now I am very satisfied with the result. The UI is now responsive, nothing blocks the buttons and its loading data fine. 

Feel free to implement those 2 lines...

 

 

public void SetText(FlowDocument document, string text)
        {   
            Action<FlowDocument,string> action = SetUIText;
            Dispatcher.CurrentDispatcher.BeginInvoke(action,DispatcherPriority.Background, document, text);
        }

        private void SetUIText(FlowDocument doc, string text)
        {
            TextRange tr = new TextRange(doc.ContentStart, doc.ContentEnd);
            using (MemoryStream ms = new MemoryStream(Encoding.ASCII.GetBytes(text)))
            {
                tr.Load(ms, DataFormats.Rtf);
            }
        }
Mar 4, 2011 at 3:29 AM

Yes, I removed the Dispatcher from the GetText because there was a chance that the underyling data source would not be correctly updated when using UpdateSourceTrigger=PropertyChanged, since it occured on a seperate thread.  But I can see how it can be helpfull for setting the text.

Mar 4, 2011 at 8:32 AM

One thing I forgot to mention Brian. I use your RichTextBox stuff from 2010 maybe November. Last days I switched to the latest version.

This new version seems to be much slower as I loaded the same rtf data into my datagrid. I will investigate that case before I really use the latest version

maybe you can fix then something ;-)

Mar 14, 2011 at 10:57 PM

for those who love anonymous methods :P

 

public void SetText(FlowDocument document, string text)
        {
            Action action = delegate
            {
                TextRange tr = new TextRange(document.ContentStart,document.ContentEnd);
                using (MemoryStream ms = new MemoryStream(Encoding.ASCII.GetBytes(text)))
                {
                    tr.Load(ms, DataFormats.Rtf);
                }
            };
            Dispatcher.CurrentDispatcher.BeginInvoke(action,DispatcherPriority.Background);
        }