3

I'm following this C# article to learn how to create an ActionList and Action Items, however the article is only focused to action items of type DesignerActionPropertyItem...

I would like to create an item of type DesignerActionMethodItem to call a method that must open a MultilineStringEditor to edit the text lines of the control, just the same action item that we can see in a default RichTextBox control:

enter image description here

Someone could explain me how to do this in C# or VB.NET?.

I'm stuck on the values to pass to the UITypeEditor.EditValue() method, I think that is the method that invokes/displays the editor, but I'm not sure which value I must pass to the first parameter (it accepts an IServiceProvider or ITypeDescriptorContext). I seen this related answer but I think there should exist a more direct/easier way than creating a class that implements IServiceProvider and ITypeDescriptorContext... since I will to run a specific UITypeEditor (MultilineStringEditor).


This is what I got so far; when I click on the "Edit Text Lines..." action item nothing happens, any exception, just nothing of nothing; I'm not sure whether that is a good or bad signal, because if I try to pass other kind of values to the first parameter of UITypeEditor.EditValue() method then I got exceptions of invalid type casting when I click on my custom action item.

C# code version:

public class MyControlActionList : DesignerActionList {

    private DesignerActionUIService designerActionUISvc;

    public new MyControl Component {
        get { return (MyControl)base.Component; }
    }

    public MyControlActionList(MyControl component) : base(component) {
        // Cache a reference to DesignerActionUIService, so the DesigneractionList can be refreshed.
        this.designerActionUISvc = (DesignerActionUIService)GetService(typeof(DesignerActionUIService));
    }

    public override DesignerActionItemCollection GetSortedActionItems() {
        DesignerActionItemCollection items = new DesignerActionItemCollection();
        items.Add(new DesignerActionMethodItem(this, "EditTextLines", "Edit Text Lines...", "Behavior", "Opens the Lines collection editor", false));
        return items;
    }

    public void EditTextLines(){
        PropertyDescriptor pd = TypeDescriptor.GetProperties(this.Component)("Text");
        MultilineStringEditor editor = (MultilineStringEditor)pd.GetEditor(typeof(UITypeEditor));

        editor.EditValue((IServiceProvider)this.GetService(typeof(MultilineStringEditor)), this.Component.Text);

    }

}

VB.NET code version:

Public Class MyControlActionList : Inherits DesignerActionList

    Private designerActionUISvc As DesignerActionUIService

    Public Shadows ReadOnly Property Component As MyControl
        Get
            Return DirectCast(MyBase.Component, MyControl)
        End Get
    End Property

    Public Sub New(ByVal component As MyControl)
        MyBase.New(component)
        ' Cache a reference to DesignerActionUIService, so the DesigneractionList can be refreshed.
        Me.designerActionUISvc = DirectCast(GetService(GetType(DesignerActionUIService)), DesignerActionUIService)
    End Sub

    Public Overrides Function GetSortedActionItems() As DesignerActionItemCollection
        Dim items As New DesignerActionItemCollection()
        items.Add(New DesignerActionMethodItem(Me, "EditTextLines", "Edit Text Lines...", "Behavior", "Opens the Lines collection editor", False))
        Return items
    End Function

    Public Sub EditTextLines()

        Dim pd As PropertyDescriptor = TypeDescriptor.GetProperties(Me.Component)("Text")
        Dim editor As MultilineStringEditor = DirectCast(pd.GetEditor(GetType(UITypeEditor)), MultilineStringEditor)

        editor.EditValue(CType(Me.GetService(GetType(MultilineStringEditor)), IServiceProvider), Me.Component.Text)

    End Sub

End Class
Community
  • 1
  • 1
ElektroStudios
  • 19,105
  • 33
  • 200
  • 417
  • ElektroStudios, Just some advice based on my own adventures in this area. You are struggling trying to find good examples and those are hard to find on the internet Some info on the .Net classes is available on [Reference Source](http://referencesource.microsoft.com/), but the designer code is mostly incomplete. However if you get yourself a decompiler like [ILSpy](http://ilspy.net/) you can easily look at how MS has done it. The stuff you need is in the System.Design.dll. Give a man a burger and he has meal; teach him how to butcher a cow and he will have plenty of meat. – TnTinMn Apr 13 '17 at 19:45
  • Plutonix I think you confused the editor class names or maybe I'm not understanding you well, the default editor I get for RichTextbox text is **MultilineStringEditor** class; then answering to your question: I'm trying to use that editor because it is the default editor for RichTextBox. The **StringCollectionEditor** class it is used for edit a collection of strings (eg. **ListBox.Items**), not for "raw" text. @TnTinMn Thanks for the advice! – ElektroStudios Apr 13 '17 at 19:53

1 Answers1

1

The editor which you are looking for is StringArrayEditor which is used to edit string[] properties. It also is internal to System.Design.

To show StringArrayEditor at design-time using a designer verb you need to:

  1. Define a string[] Lines property in your control which gets or sets different lines of texts in Text property. In fact we will edit this property which cause the Text property getting edited.
  2. To be able to show the default editor for Lines property, create a class implementing ITypeDescriptorContext, IServiceProvider and IWindowsFormsEditorService interfaces. This way, after getting the editor of Lines property, you can call its EditValue method which shows the desired editor.
  3. Create a custom ActionList containing a method to show the editor. In this method, you should use the class which you created in previous step to show the editor for the property and after editing the property, set the edited value for property.
  4. Create a custom designer for your control and override its ActionLists to return your custom action list.
  5. Decorate the control with Designer attribute, registering your custom designer for the control.

Example

To make the example working, it's enough to copy and paste following code to a file in your Windows Forms application. Don't forget to add reference to System.Design assembly. After you build the project, you can drop an instance of MyControl on a form and using the smart tag window, you can click on Edit Text Lines... link to show StringArrayEditor dialog to edit the Line (and consequently Text) property. Here is the code:

using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Drawing.Design;
using System.Linq;
using System.Windows.Forms;
using System.Windows.Forms.Design;

[Designer(typeof(MyControlDesigner))]
public class MyControl : Control
{
    public string[] Lines
    {
        get
        {
            return this.Text.Split(new string[] { Environment.NewLine },
                StringSplitOptions.None);
        }
        set
        {
            if (value != null)
                this.Text = string.Join(Environment.NewLine, value);
        }
    }
}
public class MyControlDesigner : ControlDesigner
{
    public override DesignerActionListCollection ActionLists
    {
        get
        {
            var list = new DesignerActionListCollection();
            list.Add(new MyControlActionList(this));
            return list;
        }
    }

}
public class MyControlActionList : DesignerActionList
{
    MyControlDesigner designer;
    MyControl Control { get { return (MyControl)designer.Control; } }
    public MyControlActionList(MyControlDesigner designer) : base(designer.Component)
    {
        this.designer = designer;
    }
    public override DesignerActionItemCollection GetSortedActionItems()
    {
        DesignerActionItemCollection items = new DesignerActionItemCollection();
        items.Add(new DesignerActionMethodItem(this, "EditTextLines",
           "Edit Text Lines...", "Behavior", "Opens the Lines collection editor", false));
        return items;
    }
    public void EditTextLines()
    {
        var linesPropertyDescriptor = TypeDescriptor.GetProperties(this.Control)["Lines"];
        var context = new TypeDescriptionContext(this.Control, linesPropertyDescriptor);
        var editor =(UITypeEditor)linesPropertyDescriptor.GetEditor(typeof(UITypeEditor));
        var lines = (this.Control).Lines;
        var result = (string[])editor.EditValue(context, context, lines);
        if (!result.SequenceEqual(lines))
            linesPropertyDescriptor.SetValue(this.Control, result);
    }
}
public class TypeDescriptionContext : ITypeDescriptorContext, IServiceProvider,
    IWindowsFormsEditorService
{
    private Control component;
    private PropertyDescriptor editingProperty;
    public TypeDescriptionContext(Control component, PropertyDescriptor property)
    {
        this.component = component;
        editingProperty = property;
    }
    public IContainer Container { get { return component.Container; } }
    public object Instance { get { return component; } }
    public void OnComponentChanged()
    {
        var svc = (IComponentChangeService)this.GetService(
            typeof(IComponentChangeService));
        svc.OnComponentChanged(component, editingProperty, null, null);
    }
    public bool OnComponentChanging() { return true; }
    public PropertyDescriptor PropertyDescriptor { get { return editingProperty; } }
    public object GetService(Type serviceType)
    {
        if ((serviceType == typeof(ITypeDescriptorContext)) ||
            (serviceType == typeof(IWindowsFormsEditorService)))
            return this;
        return component.Site.GetService(serviceType);
    }
    public void CloseDropDown() { }
    public void DropDownControl(Control control) { }
    DialogResult IWindowsFormsEditorService.ShowDialog(Form dialog)
    {
        IUIService service = (IUIService)(this.GetService(typeof(IUIService)));
        return service.ShowDialog(dialog);
    }
}

Note

You can find a more robust implementation of ITypeDescriptorContext, IServiceProvider and IWindowsFormsEditorService interfaces which I posed in response to this question:

Then in your custom ActionList class, you can show Lines property editor this way:

EditorServiceContext.EditValue(this.designer, base.Component, "Lines");

That's exactly the same way which is used in RichTextBoxDesigner.

Community
  • 1
  • 1
Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
  • 1
    Also take a look at this post: [How to obtain ITypeDescriptorContext and IServiceProvider in inherited ControlDesigner class](http://stackoverflow.com/q/4811446/3110834). Since I copied the `EditorServiceContext` class from .Net Framework source code, it's more stable. – Reza Aghaei May 07 '17 at 08:12