This project has moved. For the latest updates, please go here.

Thank you for the RTF in the RichTextBox

Aug 20, 2010 at 9:36 PM
Edited Aug 20, 2010 at 10:50 PM

Hello Brian,

you did a great favor to me!

A hint: UpdateText() method in RichTextBox.cs

private void UpdateText()    
{
     if (!_textHasLoaded && string.IsNullOrEmpty(Text))          
     	_textHasLoaded = true;

     if (_textHasLoaded) 
     	Text = TextFormatter.GetText(Document);

            isInvokePending = false;  
 }

if(_textHasLoad is == true and above you set it explicitly = true you do not need to ask one line below for this: if(_textHasLoaded)...
I would change the code to this:

 if (!_textHasLoaded && string.IsNullOrEmpty(Text))          
 {
      _textHasLoaded = true;
     Text = TextFormatter.GetText(Document);
}    	
isInvokePending = false;

One time less to check the value because its redundant, just in my opinion, maybe I miss something...
:)

 

Aug 20, 2010 at 9:46 PM

Thanks for the suggestion, but you are missing something.  The reason that is there is because when data binding to the Text property, the OnTextPropertyChanged callback occurs before the constructor is called.  Also, only if the Text is empty and the text wasn't loaded by the TextPropertyChanged callback should I consider the text loaded, meaning the text is initially null or empty, otherwise the text hasn't been loaded so no need to get any text.

I do appreciate the feedback and if you notice something else that look suspisous, or you have a better approach, please let me know.

Aug 20, 2010 at 10:49 PM
Edited Aug 20, 2010 at 10:50 PM

just some little contribution :) I just coded it because I did not want to execute a database update everytime I leave the RichTextBox in a DataGrids cell.

 

 

 public static readonly DependencyProperty HasTextChangedProperty = DependencyProperty.Register(
           "HasTextChanged",
           typeof(bool),
           typeof(RichTextBox));

        public bool HasTextChanged
        {
            get { return (bool)GetValue(HasTextChangedProperty); }
            set { SetValue(HasTextChangedProperty, value); }
        }

 private void InvokeUpdateText()
        {
            if (!isInvokePending)
            {
                Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(UpdateText));
                isInvokePending = true;
                HasTextChanged = true;
            }
        }

private void RTFBox_LostFocus(object sender, System.Windows.RoutedEventArgs e)
        {
            var rtb = (TBM.Helper.RichTextBox)e.Source;
            var lessonday = (LessonDay)rtb.DataContext;
            var vm = (LessonPlannerDailyViewModel)this.DataContext;

            if (rtb.HasTextChanged)
            {
                vm.UpdateHomeWork(lessonday.LessonNumber, lessonday.LessonDayDate, lessonday.Homework);
            }                        
        } 

 

But know this works only when: Text="{Binding xxx, UpdateSourceTrigger=PropertyChanged}" > not with UpdateSourceTrigger=LostFocus

 

:)

Actually I need only LostFocus and then do my updates to database but that does not work with my HasTextChanged DepProperty.

You have idea how to make it work?

I have set AllowDrop to TRUE and IsDocumentEnable to TRUE

 

You have any idea why copy/pase a .gif image does not work? Actually I do not really need it its even better when the user can´t use that feature hihi would probably only cause trouble.

Aug 20, 2010 at 11:09 PM

For your scenario, I would place your HasTextChanged in the OnTextProprtyChanged callback that way the only process that would update the HasTextChanged property is when the TextProperty changes and not during the inital load of the control.

For your LostFocus issue I would look at actually handling your logic in the Grid events such as the CellEndEditing, CurrentCellChanged or RowEditEnding.

As for the image issue:

While RichTextBox supports copying and pasting of images, certain scenarios are currently unsupported and may not work properly.

Image Copy Source

Image Paste Result

Image source is a graphic metafile

No image is pasted

Image source uses relative path or link

Garbled or blank image is pasted

Image source link does not end with an expected image format (.PNG, .JPG, .GIF)

Garbled or blank image is pasted

Image source copied from malformed RichText (RTF)

Link to image source is pasted (rather than image)

Aug 21, 2010 at 2:09 PM

About my first question and the code rant... you were right. I got problems with saving my data although I changed it in the RichTextBox :)

Now I were able to paste a jpg file but having an image in a RTBox and try typing is slooooowwwww. Good I do not need that feature :P

At The moment I do this binding: 

  <Helper:RichTextBox VerticalScrollBarVisibility="Auto" x:Name="RTFBox" LostFocus="RTFBox_LostFocus" Text="{Binding Homework, UpdateSourceTrigger=PropertyChanged,IsAsync=True}" >

Watch the last property IsAsync=True. I did this because from database I get max 10-15 entities. Each entity has 5 columns. 2 columns support RTF text. When I put in each RTF Textbox about 10-15 times this:

test testtesttest testtest testtest testtest testtest testtest testtest 

est testtesttest testtest testtest testtest testtest testtest testtest 
est testtesttest testtest testtest testtest testtest testtest testtest 
Aug 21, 2010 at 3:04 PM

Ups it seems some text in the last post was swallowed the most important part , my last question: The databiding needs about 3 seconds until I see all RTF TExt in the datagrid cells.

Do you have any idea how to speed up that? 

Aug 21, 2010 at 5:14 PM

This is funny I tried this: In the OnPropertyChanged callback: Only set to true when its Property is changed and initialize the application. Oddly my rtb.HasTextChanged code is also run at startup of the app, is my logic run? 

I just thought this is no help forum as my question is not very related to your user control now;-) But If you don`t mind, would be nice from you finishing this matter because I think you know where to put the code.

I have read DepProps are faster than CLR Props. Then I regarded these properties:

 private bool _textHasLoaded ; 

 bool isInvokePending;

Well In my case using the RTB as DataGrid`s cell editor do you think they loading of RTF could speed up a bit transforming those clr props to depProps ?

 

 

if (!rtb._textHasLoaded)
            {
                rtb.TextFormatter.SetText(rtb.Document, (string)e.NewValue);
                rtb._textHasLoaded = true;
            }
            else // _textHasLoaded
            {
                rtb.HasTextChanged = true; 
            }

 

 

Aug 21, 2010 at 5:44 PM

I am not sure what you are trying to achieve, so I am unable to really assist you beyond the scope of the RichTextBox control as it is written.  I did notice that you are using UpdateSourceTrigger=PropertyChanged, this causes the text to be updated each time a character is typed or  formatting is applied.  So if you do not need that to occur, remove the updateSourceTrigger and the text will only be updated on LostFocus, which would result in better performance while typing in the RichTextBox.

Also, dependency properties are not faster than normal properties.  Dependency properties are properties that can be data bound to dynamically, therefore there is more overhead.  Technically _textHasLoaded is not a property, having a getter and setter, it is a private member/variable.

Good luck.

Aug 21, 2010 at 5:49 PM

One more note.  The Windows RichTextBox, the control that my RichTextBox inherits from, has performance problems, and a lot of the issues you are experiencing are from the base RichTextBox control.

As an example, try using a Windows RichtextBox and paste an image in and start typing, the same performance problem will occur.  You may also want to try disabling the SpellCheck, that also slows things down.

Aug 21, 2010 at 7:44 PM
Edited Aug 21, 2010 at 7:51 PM

Thank for all your help for the rest I search a forum :)

Wish you much fun with your project. Cant wait to see some really interesting controls like a Office 2010 Format Bar aka AdornerLayer ;-)

Aug 24, 2010 at 7:10 PM
Edited Aug 24, 2010 at 7:10 PM

The Office 2010 Format Bar functionality is acutally in the works now.

Aug 24, 2010 at 9:25 PM

You kidding? because I mentioned it like sort of request ? That would definetely rock :P

Some Observations from me about the Office 2010 FormtBar: fadein/out when cursor moves out to the right side of the selected text (consider multiple selected words on different paragraphs... btw. imho the office 2010 FormatBar behaves sometimes buggy its not a feature so do not copy that behavior :P

 

This you write on your site about the Gotcha of RichTextBox: " ...you will notice that the Text is not updated until the control losses focus..."

In my opinion it should be this: "...you will notice that the Text is not updated until the control HAS LOST its focus..."

Even my description is not quite correct... because when I have set up everything with RTB_FocusLost event the Text property is updated AFTER the FocusLost event, what is very frustrating to me, as I want to retrieve the new value within

the FocusLost event. The stupid solution is to remove the Dispatcher:

public void InvokeUpdateText()                {            if (!isInvokePending)            {                UpdateText(); // Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(UpdateText));                isInvokePending = true;                            }        }

and do this in my FocusLost event:

 

private void RTFBox_LostFocus(object sender, System.Windows.RoutedEventArgs e)
        {
            var rtb = (TBM.Helper.RichTextBox)e.Source;
            var lessonday = (LessonDay)rtb.DataContext;
            var vm = (LessonPlannerDailyViewModel)this.DataContext;
             
            rtb.InvokeUpdateText();

            if (!rtb.Text.Equals(lessonday.Homework))           
                vm.UpdateHomeWork(lessonday.LessonNumber, lessonday.LessonDayDate, rtb.Text);                                        
        }

I call the (now public method former private...) InvokeUpdateText() manually and compare then the new value with the old value wether I need to update my database or not.

 

Do you have a better solution, because its not 100% working ?

Aug 24, 2010 at 9:43 PM

Yes, I am already working on the format toolbar similar to Office 2010.  Currently I only have bold, italic, and underline working.  I am finding that working with the Microsoft's RichTextBox is a major pain in the butt.  I am also looking into allowing a user to specify their own Text adorner.  Meaning that they could replace the default format toolbar with a user control of their own.

I have already made a change regarding the issue you are having.  Although, I haven't checked it in yet.  I would not change the behavior of InvokeUpdateText as you have, but instead in the LostFocus delegate event change InvokeUpdateText to UpdateText.  That would make it a synchronous operation instead of an async on the LostFocus event.

I also believe you may have better luck updating if you hook into the PropertyChanged event on your object instead of the LostFocus event of the RichTextBox.  That way you won't have to worry about what the RichTextBox is doing, as soon as your object property changes you can perform the update.

Aug 24, 2010 at 10:10 PM
Edited Aug 24, 2010 at 10:11 PM

haha your so right. The WPF RichTextBox is pain in the butt... worse it couldn`t be. What did MS? They created a FlowDocument nobody wants. All want a WinForms RTBox just displaying RTF string with easy save/load...

office bar: Of course this is a must feature that I/we can use our own buttons with editingscommands. I do now want predefined stuff, thats antique winforms timeage ;-)

 

quote:"That way you won't have to worry about what the RichTextBox is doing, as soon as your object property changes you can perform the update."

This is in my case right and wrong the same time ;-) I can use PropertyChanged then I have always the actual value. But what do think is my update method calling? Surely not a PropertyChanged event this will flood my database ;-)

I can only make a database call when the cell/richtextbox is left. There exist no other possibility. Do you agree? Well there exist sometimes cases they do not fit in our usual way of thinking or we have special situations but actually I think my scenario is very common.

Therefore exist RichTextBox solutions like the one from David Veeneman you surely know. He offers a "Force Update" button but thats not practially at all in real life applications which must be user-friendly. The user wants to write stuff and do not want to think about "have I already

saved or not..., damn forgot to press this little button..."

:-)

I made it now 100% working in removeing the Dispatcher and all that check variable isPending etc.. with FocusLost... I see no other chance.

 

Aug 24, 2010 at 10:19 PM

Let me explain further.  You hook into your objects property changed event like so

MyObject.PropertyChanged += (o,e) =>
{
     if (e.PropertyName == "RichTextBoxText")
          //do database update
}

This code would be in your ViewModel.  Where would you hook into this, in the setter of the object

public MyObjectClass MyObject
{
     set
     {
          if (_myObject !+ value)
          {
                _myObject = value;
                _myObject.PropertyChanged += ......
          }
     }
}

Of course on your Homework property setter you want to only change the value if it isn't the same so make sure you check in the setter if _homework != value thenn set.

Aug 25, 2010 at 8:10 AM

yes I know this code:

 

private string _homework;
        public string Homework
        {
            get { return _homework; }
            set
            {
                if (_homework == value)
                    return;

                _homework = value;
                this.NotifyPropertyChanged("Homework");
                // Here I could do repository.UpdateHome(_homework,lessonNumber,lessondate);
                // But I do not like to get database updates when my app starts and loads the data because then also property changes happen
                // And I do not yet use a ViewMOdel, LessonDay is my model because I do not yet see an advantage in copy the model to have a viewmodel ;-)
            }
        }

Aug 25, 2010 at 2:30 PM

Of course you would not want to update the database on your object's Homework propery setter.  My suggestion would only work if you were using a ViewModel, which in my opinion, you should never build a WPF application without using the MVVM pattern.  I guess this situation would be one advantage of using a ViewModel.

Good luck.

Aug 26, 2010 at 8:09 PM

quote:"My suggestion would only work if you were using a ViewModel, which in my opinion, you should never build a WPF application without using the MVVM pattern. 

I guess this situation would be one advantage of using a ViewModel."

 

With a ViewModel the same problem exists. For every propertychange like typing a char "A" or "123"...

 

this code gets executed: But why should I fire a DatabaseUpdate if a single char changes? makes no sense, really Brian it makes no sense. But thank you for your help and now GO and FOCUS the office 2010 format bar :P

public LessonDay()
        {
            Attachments = new ObservableCollection<Attachment>();

            this.PropertyChanged += (o, e) =>
            {
                if (e.PropertyName == "Homework")
                {

                   //do database update

                }
                
            };
        }

 

Aug 26, 2010 at 8:18 PM

Obviously you would have to remove the UpdateSourceTriger in your binding and rely on the LostFocus, then it would make perfect sense.

Aug 26, 2010 at 9:14 PM

I have done what you said. Yes when the focus is lost (leaving cell) the InvokeupdateText method is run in the RichTextBox, BUT when this LOC is run:

 

  Text = TextFormatter.GetText(Document);

Here the Text changed but nothing happend in my ViewModel... Assigning the new Text to the RichTextbox.Text property did not induce  a PropertyChange in my ViewModel.

 

My Binding:  Text="{Binding Homework,Mode=TwoWay,IsAsync=True}"

It looks like the Text Property seems to be dead ??? Did I forgot something? Actually not.

Aug 26, 2010 at 9:32 PM

I am sorry you are having so much trouble with your implementation.  It is apparent that without knowing your application I am really of no use.  Good luck to you.

Sep 21, 2010 at 9:02 PM
Edited Sep 21, 2010 at 9:15 PM

Hello Brian, just wanted to let you know the update of the property via LostFocus and ViewModel now works! :)

Actually I did not do anything different from last time just used your latest files, set the LostFocus trigger explicitly and my property got raised and bam!

the subscription when a property changes worked.

 

EDIT: :/ some odd thing happens... The PropertyChange from Lost Focus works only when I leave the Cell the 2nd time LOL actually it should work the first time. This must be a bug but not on my side...

I try to repro a simple sample project next days so you can check it maybe its your code ;-)

Sep 23, 2010 at 9:29 PM

Ok now I nailed the bug down. Personally I think thats not my bug. Please check wether this could be your bug. If you think its Microsofts bug, then I will ask on a public forum for help :)

Bug scenario:

- When you click from the left Content Cell to the right Homework cell staying on the SAME ROW => LostFocus does not work!

- When you click from the left Content Cell to the right Homework cell of ANOTHER ROW => LostFocus works!

- Clicking on all 4 cells from the Content immediately raises a property changed event.

And now the CRAZY stuff...:

- Clicking on all 4 cells from the Homework column starting for example at the first cell entering "test" clicking in 2nd cell entering "test" clicking in 3rd cell entering "test" clicking in 4th cell entering "test" NOTHING happend.

When I go now the same way in reverse order from cell 4 to cell 1 sometimes I get property changes ROFL ...

Can you explain that?

 

- The Content Column is a DataGridTextColumnT

- The Homework Column is a DataGridTemplateColumn with your DataTemplated RichTextBox

I have not touched the c# code of your RichTextBox, its the latest version. I would be pleased and grateful to hear from you :)

 

File zipped with no compression as Visual Studio 2010 project: http://www.sendspace.com/file/xtn1mc  (500kb)


Sep 23, 2010 at 9:50 PM

The RichTextBox in the toolkit does not do anythign special with LostFocus, it just hooks into the Windows LostFocus event. 
I believe it has something to do with the grid and the templated column.  Try placing the Windows RichTextBox in the templated column and see if the lostfocus has the same behavior.

Sep 24, 2010 at 7:04 PM
Edited Sep 24, 2010 at 9:38 PM

I have implemented the MS RichTextbox the way a MSFT guy advised me on MSDN forum:

And it is the same tragedy... The LostFocus does not work leaving cells/RichTextBox. In the next days I will post that file on MSDN forum as bug. :/ in the meantime I use my code-behind version good `ol style... (erhm... I forgot the code-behind method is also

depending on the lostfocus... thanks Brian.

 

                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <RichTextBox>
                                <FlowDocument>
                                    <Paragraph>
                                        <Run Text="{Binding Homework, Mode=OneWay}"/>
                                    </Paragraph>
                                </FlowDocument>
                            </RichTextBox>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                    
                    <DataGridTemplateColumn.CellEditingTemplate>
                        <DataTemplate>
                            <TextBox Text="{Binding Homework}" TextWrapping="Wrap"/>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellEditingTemplate>

It seems I am  not the only one having trouble with wpf datagridtemplatecolumn lostfocus => www.eggheadcafe.com/community/aspnet/14/10187312/textbox-is-not-receiving-lostfocus-event-wpfvbnet.aspx

 

Oct 27, 2010 at 9:51 PM

Hello Brian,

JFYI => http://social.msdn.microsoft.com/Forums/en/wpf/thread/c07cbb43-3662-463c-923a-412842ac22db

Its about the lostfocus problem which got solved by PreviewLostKeyboardFocus

:)

Oct 27, 2010 at 10:45 PM

Nice.  I will change the control to use the PreviewLostKeyboardFocus event instead.  But I will leave the rest as is.  The solution author is mistaken in some of his comments.

Oct 29, 2010 at 12:58 AM

Just wanted to let you know I added the office 2010 style contextual formatting toolbar.  Set the AllowFormatting property top true.  Please give a try and provide feedback in a new discussion item.

Oct 29, 2010 at 7:41 PM

Hey Brian you won`t believe it but I just wanted to login here to ask about the state of the formatting bar HAHAHA :P and now you wrote that...

I will test it now and let you know in a new Thread of course.