Reputation: 345
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.
Do you have any idea how to replace this dialog?
Is there any extension that can do that?
If there is no such extension I would create an extension/add-in whatever I need to do. Do you have any real experience if it can be done at all?
I thought I will find the appropriate dll and extend its beaviour, but unfortunately I cannot find which dll contains this tragedy
Any help would be appreciated, thank you!
Upvotes: 4
Views: 678
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:
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:
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
.UITypeEditor
of that property. The editor is of type of ResourceEditorSwitch<T>
which uses an instance of ResourcePickerDialog
. Get the instance for ResourcePickerDialog
.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.
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