DataBinding editor document text

SyntaxEditor for WPF Forum

Posted 15 years ago by Scott Currie
Avatar
Just getting started with the alpha, so I may just be missing something. I don't see a settable dependency property anywhere for the Text of an editor, document, or snapshot. This would force the app developer to write a wrapper that calls Document.SetText and reads Document.CurrentSnapshot.Text in order to do databinding on the contents of an SE. Is that by design?

Thanks,
-Scott

[Modified at 02/14/2009 02:39 PM]

Comments (18)

Posted 15 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar
Hi Scott,

Good point, we'll have to think about this a bit more. Right now, EditorDocument does have a Text property that we have as not browsable. But it's used as the ContentProperty for XAML setting. You could probably bind to that, however we'd need to add support for INotifyPropertyChanged.

Any other thoughts or would that be the best approach?


Actipro Software Support

Posted 15 years ago by Scott Currie
Avatar
That should work. I don't have enough experience with the control yet to have a strong opinion. I do have a few thoughts though:

  1. Binding text is likely to be common, so it should be exposed as a property on the SE itself, not just member objects. I think it's reasonable that this will delegate to the underlying document and potentially affect other things that are bound to the document.

  2. Get and set functionality should be exposed in the same place in the object hierarchy. That is, I shouldn't have to go to document for the setter but only find the getter in the snapshot.

  3. There should be a way to determine if the last text change was programmatic or not. This might be exposed as a IsLastChangeProgrammatic property. That way, I could easily create a multi-binding that did the right things based on text change and mode of change.

  4. There should be a better way to preserve viewport and caret position than there was in the WinForms SE. (i.e. suspend, caching 3-5 properties, change text, load 3-5 properties from cache, resume)
Posted 15 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar
1) That may be a better idea, put the Text property on SyntaxEditor instead.

2) Here's the issue with this. SyntaxEditor is unique in that now we have immutable snapshots. So every text change you make to a document creates a new snapshot. You are able to fully work with the snapshot and parse it without having to worry about end user edits changing things while you parse. No other editor we know of has this feature other than VS. That is why the read methods are all on the snapshot while the write methods are on document, since the write methods create a new snapshot based on whatever is the current snapshot in the document at the time. Make better sense why things are that way?

3) Could you provide more detail on this, what you need it for and what you would do with it?

4) Noted.


Actipro Software Support

Posted 15 years ago by Scott Currie
Avatar
I noticed the snapshot feature, and the lack of a setter on the snapshot does make sense. You could still have a text getter on the document or SE to provide parallelism. For user who don't use snapshots, they may find it confusing or frustrating to have to drop down to that level to get text out of the control.

WRT #3, I am building an IDE. I have a proprietary XML-based language that describes a solution. I also have graphical designers for the solution. Think of the XAML editor in VS and the split view that keeps the graphical designer and XML editor in sync dynamically (except our syncing works a lot better :-)).

OK, so with that context, we do all of my synching with WPF data binding against a backend engine. That is, both the XML editor and the designers bind to the engine, not directly to each other. This makes everything run very smoothly (integrated undo/redo stacks, background compilation and recompile, and a bunch of other stuff).

Now consider that we have to keep things in sync, but we have to do somewhat different things depending on whether a change came from the designer or the SE. If it came from the designer, we need to emit changes to the engine object model and then render the appropriate XML fragment into the SE. If it came from the SE, we need to parse the text and then merge object model changes into the engine.

See how this can get into an infinite loop. You change the designer which renders text, which triggers a text update, which triggers a parse, which updates the OM, which updates the designer, etc. The IsLastChangeProgrammatic property is our guard to break the recursion.

Of course, I'm totally open to doing it differently if there is something else that has a similar effect.
Posted 15 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar
Hi Scott,

I was thinking about this over the weekend... we probably won't be adding a flag that says whether the last change was programmatic to the SyntaxEditor or EditorDocument classes, however what you could do is inherit those and in the document's TextChanged event, the ITextChange there has a Source property. Generally that is an IEditorView instance if it came from a user edit. So what you can do is look for that scenario and update a custom property you add to your inherited class.


Actipro Software Support

Posted 15 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar
We have added a SyntaxEditor.Text property for the upcoming build, along with a QuickStart showing it bound to a WPF TextBox.


Actipro Software Support

Posted 15 years ago by Jon von Gillern
Avatar
Have you thought about possibly making the SyntaxEditor.Text a dependency property? I've got a regular property on a plain domain object (that implements INotifyPropertyChanged) that I'd like to bind to the syntax editor's text, but binding will only work if at least one of the properties is a DependencyProperty, but the SyntaxEditor.Text is just a regular property, so the binding fails.

The reason the quick start sample works is because you're binding to the TextBox.Text property which is a dependency property. Since my domain objects are used can also be used in a web application, I don't want to make them derive from DependencyObject in order to use DependencyProperty.

Thanks!
Posted 15 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar
Hi Jon,

We considered that however the main problem is that the text is not stored on the SyntaxEditor at all, and is completely stored in the Document. So by making the SyntaxEditor.Text property a dependency property, there would be two copies of the text. This introduces two issues:

1) The SyntaxEditor text would have to be updated with every change. This means additional delay between keystrokes and for very large documents, would make a huge performance difference.

2) Twice the memory would be used for storing text data. Also for large documents, that could be significant.

For those main reasons, the property is a wrapper around the document/snapshot properties instead.


Actipro Software Support

Posted 15 years ago by Cameron MacFarland - Senior Software Engineer, Orelogy Geotechnical
Avatar
Hi,

I've just hit this problem (trying to bind a POCO to a Syntax Editor). I've checked the Data Binding example but that only seems useful when binding another control to the Syntax Editor.

Is there a solution to this? Or a different way of achieving this affect? Basically I want the editor to display and edit the value of a string property in a basic object.

Thanks
Cam
Posted 15 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar
Hi Cam,

The problem is that the SyntaxEditor.Text property cannot be a dependency property. If it was, that would mean that it would have to be updated with the full text of the document any time the document text changed. This would cause performance issues and would really increase memory usage in larger documents.

So for now we have it as a standard property that wraps a call to the Document to retrieve the text.

We're open to any suggestions to improve this though without the negative impacts I mentioned.


Actipro Software Support

Posted 15 years ago by RS
Avatar
We hit a similar dilemma recently with an editor control and we decided to add 2 properties in addition to the standard Text property. TextDependencyProperty is a dependency property and a related second IsTextDependencyProperty indicates whether the TextDependency property is being used.

The logic being that if the scenario in which the control is being used dictates that the text is updated each time a change fires then the developer is just going to code for that (listening for the editors change event handler and listening for the POCOs property to change). So the performance issue is still going to be there in that scenario and it's better to have the native Wpf binding support for it whilst adding the IsTextDependencyProperty property allows the dependency property overhead to be selectively enabled rather than enabled all the time.

RS

Posted 15 years ago by Jon von Gillern
Avatar
Cam,

I ended up wrapping the syntax editor in my own custom control and was then able to bind to a poco.

The performance for my app was not a concern because my syntax editor will only ever be editing a very small amount of code (10-20 lines max)

Hope this helps

    public partial class MyEditor : UserControl
    {
        public MyEditor()
        {
            InitializeComponent();

            MyEditorDocument.SetText(Code);

            MyEditorDocument.Language = LoadDynamicLanguageFromFile("CSharp.xml");
            MyEditorDocument.TextChanged += Document_TextChanged;

            
        }


        void Document_TextChanged(object sender, TextDocumentChangeEventArgs e)
        {
            if (Code != e.NewSnapshot.Text)
                Code = e.NewSnapshot.Text;
        }

        public static DynamicSyntaxLanguage LoadDynamicLanguageFromFile(string filename)
        {
            // Load from stream
            string path = filename;
            Stream stream = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream(path);
            if (stream == null)
                return null;
            string xml = new StreamReader(stream).ReadToEnd();
            stream.Close();

            return new DynamicSyntaxLanguage(filename, xml);
        }

        

        #region Code

        public static readonly DependencyProperty CodeProperty = DependencyProperty.Register("Code", typeof(string), typeof(NitriqEditor), new UIPropertyMetadata(null, new PropertyChangedCallback(OnCodeChanged), new CoerceValueCallback(OnCoerceCode)));

        private static object OnCoerceCode(DependencyObject o, object value)
        {
            NitriqEditor nitriqEditor = o as NitriqEditor;
            if (nitriqEditor != null)
                return nitriqEditor.OnCoerceCode((string)value);
            else
                return value;
        }

        private static void OnCodeChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            NitriqEditor nitriqEditor = o as NitriqEditor;
            if (nitriqEditor != null)
                nitriqEditor.OnCodeChanged((string)e.OldValue, (string)e.NewValue);
        }

        protected virtual string OnCoerceCode(string value)
        {
            // TODO: Keep the proposed value within the desired range.
            return value;
        }

        protected virtual void OnCodeChanged(string oldValue, string newValue)
        {
            // TODO: Add your property changed side-effects. Descendants can override as well.
            if (CodeBox.Text != newValue)
                CodeBox.Text = newValue;
        }

        public string Code
        {
            // IMPORTANT: To maintain parity between setting a property in XAML and procedural code, do not touch the getter and setter inside this dependency property!
            get
            {
                return (string)GetValue(CodeProperty);
            }
            set
            {
                SetValue(CodeProperty, value);
            }
        }
        
        #endregion

        
    }

<!-- guts of xaml usercontrol -->
    <Grid>        
        <editor:SyntaxEditor x:Name="CodeBox" 
                             FontSize="11pt" FontFamily="Consolas" 
                             >
            <editor:EditorDocument x:Name="MyEditorDocument" />
        </editor:SyntaxEditor>
    </Grid>

<!-- usage of usercontrol -->
<local:MyEditor Code="{Binding Code, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="0,7"/>
Posted 15 years ago by Cameron MacFarland - Senior Software Engineer, Orelogy Geotechnical
Avatar
Awesome, Thanks Jon.

With a few tweaks I got it to work. I tried using a wrapper with it's own dependency property before, but I don't think I handled the documents TextChanged event so it didn't work.
Posted 15 years ago by Eli Gazit - CEO, Softwear Suit Ltd
Avatar
Have someone fixed this code to work with the new beta?
did someone found a better way?
syntax Editor with no Databinding is almost pointless in wPF?
I do understand the performance issue but there has to be a workaround.
Why not update the Source only on LostFocus rather than text changed?
Posted 15 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar
Based on all the requests for proper two-way binding, we have implemented this for the next maintenance release. It will be disabled by default because of the performance reasons described in a previous post (memory/performance issues) when data binding is eanbled due to copying full document text around.

To enable it you'd set the new IsTextDataBindingEnabled property to true and then bind to the Text property and all is good.


Actipro Software Support

Posted 15 years ago by Eli Gazit - CEO, Softwear Suit Ltd
Avatar
When Should we expect this?
Thanks again,
Eli
Posted 15 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar
Probably in the next couple weeks sometime.


Actipro Software Support

Posted 15 years ago by Eli Gazit - CEO, Softwear Suit Ltd
Avatar
Cool, keep up the good job guys.
The latest build of this product (v24.1.2) was released 2 days ago, which was after the last post in this thread.

Add Comment

Please log in to a validated account to post comments.