问题
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!
回答1:
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:
- Create a
ControlDesigner
and register it as designer of your control. Then in itsOnCreateHandle
find the property which you are going to edit. For exampleBackgroundImage
. - Find the
UITypeEditor
of that property. The editor is of type ofResourceEditorSwitch<T>
which uses an instance ofResourcePickerDialog
. Get the instance forResourcePickerDialog
. Get the
resourcePickerUI
field and create an instance ofResourcePickerUI
dialog. It is the dialog that you should change. The dialog contains someTableLayoutPanel
. You should insert aTextBox
in a suitable place and handle itsTextChanged
event and filter the values which is showing in theListBox
. 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:
r-aghaei/CustomizeSelectResourceDialog
Download zip
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();
}
}
来源:https://stackoverflow.com/questions/14952181/visual-studio-select-resource-dialog-replacement