Zsolt
Zsolt

Reputation: 345

Visual Studio "Select Resource" dialog replacement

In my project I have more than 750 images in resource. Using VS built in "Select Resource" dialog is a nightmare to find and select one image - let's say - for a button in winforms designer.

It would be much more usable if it was some explorer like dialog and it is lack of search functionality.

Any help would be appreciated, thank you!

Upvotes: 4

Views: 678

Answers (1)

Reza Aghaei
Reza Aghaei

Reputation: 125277

The Resource Select dialog is a UITypeEditor. It is the internal class ResourceEditorSwitch<T> which internally uses the internal class ResourcePickerDialog and both of them are in Microsoft.VisualStudio.Windows.Forms.dll assembly which is one of Visual Studio's assemblies.

Since the implementation of the class is tightly coupled with some other internal classes of Visual Studio's assemblies, so it's hard to extract the class source code and customize it, but you having those information about the class will help us to take a look at its source code and let us to have more information about the class.

To customize the Resource Select dialogInstead you can get an instance of the class at design time, and before showing the dialog, manipulate the dialog using code to have a filtering feature like following gif, pay attention to the TextBox that I've added to the dialog:

enter image description here

You can filter the ListBox by typing in TextBox and using and keys, without changing the focus from TextBox you can select filtered results.

To do so, you should:

  1. Create a ControlDesigner and register it as designer of your control. Then in its OnCreateHandle find the property which you are going to edit. For example BackgroundImage.
  2. Find the UITypeEditor of that property. The editor is of type of ResourceEditorSwitch<T> which uses an instance of ResourcePickerDialog. Get the instance for ResourcePickerDialog.
  3. Get the resourcePickerUI field and create an instance of ResourcePickerUI dialog. It is the dialog that you should change. The dialog contains some TableLayoutPanel. You should insert a TextBox in a suitable place and handle its TextChanged event and filter the values which is showing in the ListBox. All controls have names and you can simply access to them and change their properties and values.

  4. After changing the form, assign it resourcePickerUI. This way, the editor will use the changed form and will show what you need.

Implementation

You can find the full working example in the following repository:

Here is the code for the designer:

public class MyControlDesigner : ControlDesigner
{
    protected override void OnCreateHandle()
    {
        base.OnCreateHandle();
        var property = TypeDescriptor.GetProperties(this.Control)["BackgroundImage"];
        var resourceEditorSwitch = property.GetEditor(typeof(UITypeEditor)) as UITypeEditor;
        var editorToUseField = resourceEditorSwitch.GetType().GetProperty("EditorToUse",
            BindingFlags.NonPublic | BindingFlags.Instance);
        var editorToUse = editorToUseField.GetValue(resourceEditorSwitch);
        var resourcePickerUIField = editorToUse.GetType().GetField("resourcePickerUI",
            System.Reflection.BindingFlags.Instance |
            System.Reflection.BindingFlags.NonPublic);
        var resourcePickerUI = (Form)Activator.CreateInstance(resourcePickerUIField.FieldType);
        ModifyForm(resourcePickerUI);
        resourcePickerUIField.SetValue(editorToUse, resourcePickerUI);
    }
    void ModifyForm(Form f)
    {
        var resourceContextTableLayoutPanel = GetControl<TableLayoutPanel>(f, "resourceContextTableLayoutPanel");
        var resourceList = GetControl<ListBox>(f, "resourceList");
        resourceContextTableLayoutPanel.Controls.Remove(resourceList);
        var tableLayoutPanel = new TableLayoutPanel();
        tableLayoutPanel.Dock = DockStyle.Fill;
        tableLayoutPanel.Margin = new Padding(0);
        tableLayoutPanel.ColumnCount = 1;
        tableLayoutPanel.RowCount = 2;
        tableLayoutPanel.RowStyles.Add(new RowStyle(SizeType.AutoSize));
        tableLayoutPanel.RowStyles.Add(new RowStyle(SizeType.Percent, 100));

        List<string> list = new List<string>();
        var textBox = new TextBox() { Dock = DockStyle.Fill, Margin = resourceList.Margin };
        Action<string> applyFilter = (s) =>
        {
            if (string.IsNullOrEmpty(s))
            {
                resourceList.BeginUpdate();
                resourceList.Items.Clear();
                resourceList.Items.AddRange(list.ToArray());
                resourceList.EndUpdate();
            }
            else
            {
                var list2 = list.Where(x => x.ToLower().StartsWith(s.ToLower())).ToList();
                resourceList.BeginUpdate();
                resourceList.Items.Clear();
                resourceList.Items.Add("(none)");
                resourceList.Items.AddRange(list2.ToArray());
                resourceList.EndUpdate();
            }
            if (resourceList.Items.Count > 1)
                resourceList.SelectedIndex = 1;
            else
                resourceList.SelectedIndex = 0;
        };
        var resxCombo = GetControl<ComboBox>(f, "resxCombo");
        resxCombo.SelectedValueChanged += (s, e) =>
        {
            resxCombo.BeginInvoke(new Action(() =>
            {
                if (resourceList.Items.Count > 0)
                {
                    list = resourceList.Items.Cast<string>().ToList();
                    textBox.Text = string.Empty;
                }
            }));
        };
        textBox.TextChanged += (s, e) => applyFilter(textBox.Text);
        textBox.KeyDown += (s, e) =>
        {
            if (e.KeyCode == Keys.Up)
            {
                e.Handled = true;
                if (resourceList.SelectedIndex >= 1)
                    resourceList.SelectedIndex--;
            }
            if (e.KeyCode == Keys.Down)
            {
                e.Handled = true;
                if (resourceList.SelectedIndex < resourceList.Items.Count - 1)
                    resourceList.SelectedIndex++;
            }
        };
        tableLayoutPanel.Controls.Add(textBox, 0, 0);

        resourceList.EnabledChanged += (s, e) =>
        {
            textBox.Enabled = resourceList.Enabled;

        };
        tableLayoutPanel.Controls.Add(resourceList, 0, 1);
        resourceContextTableLayoutPanel.Controls.Add(tableLayoutPanel, 0, 4);
    }
    T GetControl<T>(Control c, string name)
        where T : Control
    {
        return (T)c.Controls.Find(name, true).FirstOrDefault();
    }
}

Upvotes: 1

Related Questions