Custom edit form for XML attribute values

Hi AlterNet,

I had the weird idea to enhance our xml editor usage: we have several places in our xml where we enter ids of data items in a attribute value. It would be great if I could display a popup form where a list of data items is displayed, and the user could select the items instead of entering ids. The selected items would be written to the attribute value

Here is a sample which shows the current situation:
https://www.hg-online.de/downloads/AlterNetXml_2023-10-29.zip

I want to display a custom item selection form for the attribute “terminartids” in line 3. In the form, the items corresponding to the current attribute value should be selected when it pops up. A multi select should be possible, so it is not sufficient to add all items to the standard code completion.

So first question: is the event “SyntaxEdit.NeedCodeCompletion” the correct place to handle it (Or subclassing “XmlParserWithSchema” and override “CodeCompletion”)?

Second question: I think I need several pieces of information:

  1. how can I check whether the code completion is to be shown for an attribute value or something else?
  2. how do I get the name of the current attribute, whose value is to be edited? I could check the attribute name, but I think it would be better to add a custom type to our xsd so that I don’t need to rely on the attribute name. So, I might need Xml schema information about the current element.
  3. how do I get the current attribute value, whĂ­ch should populate the current selection in the popup?
  4. after the popup is closed, how do I write the selected items to the xml attribute value in code editor?

Is all of this possible at all? Is there a sample which could be used for a start?

Best regards

Wolfgang

Hi Wolfgang,

I’ve uploaded a modified demo here:

Making XML parser to display custom code completion is relatively straightforward, please look at my changes in xsd file and code in CustomXmlRepository.

However, our code completion listbox does not support multi-select mode at this moment. We’ve tweaked it a bit to make it work. The demo project allows to select multiple items and write their IDs, but this is more like a prototype showing that this can be implemented.

We will need to do it in a proper way (implement multi-select mode for the code completion listbox and code completion providers)

Regards,
Dmitry

Whow, this sounds like a long road to go do until this goal is achieved. I have to reflect more on your sample - it is not easy to understand :wink:

Would it be possible to use a custom form instead of the standard listbox? We have several attributes/datatypes where I could add this feature, and for all we have already custom listboxes with multi selection.
It seems I could override “SyntaxEdit.ShowCodeCompletionBox” and show a custom form? But this would bring me back to the original question: how can I fetch meta information about the current xml caret location?

Best regards

Wolfgang

Hi Wolfgang,

It should be possible to use custom control instead of a built-in listbox.
This control needs to implement ICompletionListBox interface.

I’ve uploaded a modified demo to Google drive that shows how to use CheckedListBox as a custom control for code completion of the terminartids attribute.

Please let me know if this is what you’re looking for.,

Kind regards,
Dmitry

Whow, this is rather complex… Would it also be possible to use “ICodeCompletionWindow” instead of “ICompletionListBox”? We already have a set of controls (some lists, others comboboxes) for several datatypes. So I would like use this custom control as code completion popup.
I think I could even show a dialog (though this would be a bit clunky to use). Would this happen in “ICodeCompletionWindow.Popup”?

I think I understood that the value from xml is parsed in the “XmlRepository” subclass and written to a “ICodeCompletionProviderItem”. So I could create a “ICodeCompletionProviderItem” subclass, which just contains information about the xml attribute type and the current value. And in “ICodeCompletionWindow.Provider.set”, I could initialize the popup control according to the datatype and the current value.

Would all of this work?

Best regards

Wolfgang

I think this is not possible, as “SyntaxEdit.CreateCodeCompletionBox” returns a “ICodeCompletionBox” instead of a “ICodeCompletionWindow”.

Before replying to my previous question: give me a few days. I try to integrate your sample into our application and fake the “ICompletionListBox” implementation, which is a rather complex process.

Best regards

Wolfgang

Hi Wolfgang,

This should be possible to use ICodeCompletionWindow instead of ICodeCompletionBox here. I’ve uploaded a modified demo.

Kind regards,
Dmitry

Great, I can show our custom editor control :wink:


But there is still a lot of work to be done, because I want to show custom windows also for other attributes with different data types.

I noticed a bunch of problems caused by our popup being a rather complex control with several child controls. Here focus handling causes problems. I could work around most of the issues, but maybe you want to improve “CodeCompletionWindow” so that my workarounds are not necessary ;-).

Problem 1: “CodeCompletionWindow.IsFocused” and “FocusControl” just check whether the PopUpControl is focused. But if any child control is focused, “PopUpControl.Focused” returns false. So all child controls need to be checked.
Otherwise, the popup is closed automatically when it is to be shown.

Problem 2: the popup did not close for me when clicking outside of the popup in SyntaxEdit.
Reason: the CodeCompletionWindow ctor registers “LostFocus” only for the window itself and the “PopUpControl”. But if any child control has focus, the parent control “LostFocus” is not called.
I could work around it by adding those eventhandlers after creating the child and calling “CodeCompletionWindow.DoLostFocus”.

But I know that my requirements are very, very specific. So it would be OK for me if I have to keep the workarounds on my side :wink:

Question: when clicking outside of the popup, the changes are discarded (also in your sample). Is there any chance to commit them? It seems this happens in “CodeCompletionWindow.DoLostFocus”, where “Close (accept:false, …)” is called.

That’s all for today.

Best regards

Wolfgang

Hi Wolfgang,

Problem1 :
It looks quite sophisticated :slight_smile:

IsFocused is a virtual property that is implemented like this:

   public virtual bool IsFocused
        {
            get
            {
                return Focused || (popupControl != null && popupControl.Focused);
            }
        }

You can override it and check if any other control on your popup form is focused.

Problem2:

Similarly, if your form has more controls, you can register the same events for them as well:

myControl.LostFocus += new EventHandler(DoLostFocus);

We will make DoLostFocus a virtual method so you can override it to Close with accept = true;

Right now, you can use the following workaround to accept changes when the window is closed.

         public SimpleCodeCompletionBox(Control owner)
             : base(owner)
        {
...
            LostFocus -= base.DoLostFocus;
            PopupControl.LostFocus -= base.DoLostFocus;
            LostFocus += DoLostFocus;
            PopupControl.LostFocus += DoLostFocus;
        }

        protected new void DoLostFocus(object sender, EventArgs e)
        {
            if (!Showing && NeedCloseOnLostFocus())
            {
                Close(true, false);
            }

            base.DoLostFocus(sender, e);
        }

Regards,
Dmitry

OK, so I will keep my custom “Focused” workarounds.

About the “clicking outside does not commit changes” question: as I don’t want to copy too much internal AlterNet code (which might change in the original implementation for good reasons, and then my own copy might not work any more), is there any chance that you add a boolean property “AcceptOnLostFocus” to CodeCompletionWindow? Or add a flag “CodeCompletionFlags.AcceptOnLostFocus”? This way, “DoLostFocus” could check this property/flag.

Best regards

Wolfgang

One more small question:

In “CustomXmlRepository.IsIdentifierChar”, I added the space char, because the ID lists in attribute values of existing xml texts can contain spaces, which were previously entered by users (and which were ignored by our parsing of the id list):

    protected override bool IsIdentifierChar(char ch)
    {
      return base.IsIdentifierChar(ch) || ch == ',' || ch == ' ';
    }

Is it safe to do so? I did not notice any side effects up to now.

If I don’t do so, after closing the popup, the attribute value is only replaced up to the first space char.

Best regards

Wolfgang

Hi Wolfgang,

We will add AcceptOnLostFocus flag and publish changes alongside the coming update (which we are going to push to NuGet next week).

IsIdentChar is used to determine where the parameter value ends, as the whole parameter is replaced once the code completion window is committed. In case a parameter value does not have a closing quote mark and space is included in the identifier characters, this may lead to code completion eating the next parameter name once committed. Below is the picture illustrating the problem:

If your parameter values, in this particular case, accept only digits, numbers, and spaces, you might need to override a different method (CodeCompletion) and calculate e.StartPosition / e.EndPosition differently to other parameter values, something along these lines:

        public override void CodeCompletion(string text, Point position, CodeCompletionArgs e, ISyntaxNode node)
        {
            base.CodeCompletion(text, position, e, node);
            var simple = (e.Provider != null && e.Provider.Count > 0 && e.Provider[0] is XmlListMember);
            if (simple)
            {
                var attr = node.FindAttribute(XmlNodeType.XmlParameter.ToString());
                if (attr != null)
                {
                    string value = attr.Value.ToString();
                    Point startPos = attr.Position;
                    Point endPos = attr.EndPosition;

                    int start = 0;
                    if (value.StartsWith("\""))
                    {
                        startPos.X++;
                        start = 1;
                    }
                    
                    int len = 0;

                    for (int i = start; i < value.Length; i++)
                    {
                        var ch = value[i];
                        if (char.IsDigit(ch) || ch == ',')
                            len++;
                        else
                        if (ch == ' ' && i < value.Length - 1 && char.IsDigit(value[i + 1]))
                            len++;
                        else
                            break;
                    }

                    endPos.X = startPos.X + len;

                    e.StartPosition = startPos;
                    e.DisplayPosition = startPos;
                    e.EndPosition = endPos;
                }
            }
        }

Kind regards,
Dmitry

Hi Dmitry,

thanks for the improved space parsing snippet, I applied it and it works. Now awaiting the next Nuget update :wink:

Best regards

1 Like

Hi Wolfgang,

We’ve added AcceptOnLostFocus flag in the latest version (9.1.2)

Please let me know if it works for you.

Kind regards,
Dmitry

Perfect!

Many thanks for the great support on this issue! You probably invested a lot of hours of work to create and modify the sample. And though it is a big improvement in our report configuration, I will probably be the only one of your customers who ever uses this approach of a custom code completion window ;-).

Best regards

Wolfgang

Hi Wolfgang,

Thank you for your kind words, much appreciated!

Always great to see happy customers:)

Dmitry