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!
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 its OnCreateHandle find the property which you are going to edit. For example BackgroundImage.
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.
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:
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();
}
}
Related
I'm working on an inventory program and have finished the main functionality as a command line console app. I am now working on a version for winforms. I want to enable it to dynamically generate a Groupbox that holds some textboxes. I'd rather not design 50+ lines of multiple textboxes. Keep in mind I'm rather new to programming, having started with C# a year ago. I know next to nothing on Winforms.
I've tried to use dynamic item = new Groupbox();as a similar method allowed generation of objects at runtime. In the command line app, the way it works is that based on information given, a certain amount of objects are passed into the list _AllItems. I was thinking of generating the Groupboxes by using:
private void InitializeGroupBox()
{
foreach (Product product in Product._AllItems)
{
dynamic Item = new GroupBox();
}
}
But I have the feeling I'm nowhere near the correct method. Thanks to anybody who helps.
You will need to learn a bit more, but here is what I usually do to achieve what you asked.
internal class DynamicForm : Form
{
private FlowLayoutPanel mFlowLayoutPanel;
public DynamicForm()
{
mFlowLayoutPanel = new FlowLayoutPanel();
mFlowLayoutPanel.Dock = DockStyle.Fill;
// Add to this Form
this.Controls.Add(mFlowLayoutPanel);
InitializeGroupBox();
}
private void InitializeGroupBox()
{
mFlowLayoutPanel.SuspendLayout(); // Performance
for (int i = 1; i <= 20; i++) {
var groupBox = new GroupBox();
groupBox.Text = "GroupBox #" + i;
groupBox.Size = new Size(200, 50);
var textBox = new TextBox();
textBox.Dock = DockStyle.Fill;
// Add the TextBox to GroupBox
groupBox.Controls.Add(textBox);
// Add to this Form
mFlowLayoutPanel.Controls.Add(groupBox);
}
mFlowLayoutPanel.ResumeLayout(); // after suspend, resume!
}
}
I have a form which contains a dynamically added TableLayoutPanel, which contains some dynamically added Labels, TextBox, CheckBox. I am obtaining exactly the visualization I would like to have, but I am struggling to get the "tab key" to work for moving from one control to the other.
I have tried to add a:
control.TabIndex = tabIndex++;
control.TabStop = true;
But this doesn't seem to have any impact...
This is the (tested) stub code:
class MyForm : Form
{
public MyForm()
{
InitializeComponent();
string[] titles = {"first","second"};
var myLayout = new TableLayoutPanel();
myLayout.AutoSize = true;
int myTabIndex = 1; //Not really necessary
int rowNumber = 0;
foreach (var title in titles)
{
var label = new Label();
label.Text = title;
myLayout.Controls.Add(label, 0, rowNumber);
var control = new TextBox();
control.TabIndex = myTabIndex++; //Not really necessary
myLayout.Controls.Add(control, 1, rowNumber);
rowNumber++;
}
this.Controls.Add(myLayout);
}
}
This is the window I get, and I am not able to navigate from first to second field using the tab key.
Update:
Applying Visual Studio 2013 Update 5 did not help.
Something must have been corrupted in my project. The best I could do is to move on with a new clean Windows Form project, and everything now is working.
I use this solution(in code below) to add multiply buttons on panel. It works ok but it takes too long, when it tries to add a lot of buttons (for an example 40). I want to ask, if any one knows of a better solution for this situation? I was thinking of creating all possible buttons at program start-up, but in this case will start-up take too long, especially if there will be really lot of buttons (this scenario is possible)?
while (data.Read())
{
btnName = Convert.ToString(data["Name"]);
btnColor = (color == string.Empty) ? Convert.ToString(data["Color"]) : color;
categoryId = Convert.ToInt16(data["CategoryId"]);
//both category and article table's contains this data!
if (categoryId == articleCatId || cl == typeof(Category))
{
Button newbtn = new Button();
newbtn.TextAlign = ContentAlignment.MiddleCenter;
newbtn.Click += (sender, e) => method(sender, e);
newbtn.Text = btnName;
newbtn.Name = "button-" + btnName;
newbtn.Height = size;
newbtn.Width = size;
newbtn.Font = new Font("Microsoft Sans Serif", fontH);
newbtn.Location = new Point(paddingL, paddingT);
newbtn.BackColor = ColorTranslator.FromHtml(btnColor);
location.Controls.Add(newbtn);
num += 1;
if ((num - 1) / inline == 1) { paddingT += size; paddingL = 2; num = 1; }
else { paddingL = paddingL + size; }
}
}
You probably cannot reduce the number of buttons you need to create, so you then have some options to speed it up a little bit:
Add the buttons to an object that is not visible. Only when you're done adding buttons, you make the object visible.
Call SuspendLayout on the parent control to stop it from trying to layout itself. Then call ResumeLayout when you're done adding buttons.
Use a more lightweight control than a button, that is more appropriate for the task. For example a Listbox, Combobox, or several checkboxes or option buttons styled as normal buttons.
Write your own lightweight Button control that does exactly what you want but no more.
I'm trying to create a DevEx drop down button. Unfortunately, I'm running into two problems I can't figure out:
1) I can't get the popup menu to skin correctly, i.e. it doesn't skin as "Office 2010 Blue". The code I'm using is shown below:
private void InitializeSendToPricingSheetButton()
{
var barManager = new BarManager();
if (barManager.Controller == null) barManager.Controller = new BarAndDockingController();
barManager.Controller.PaintStyleName = "Skin";
barManager.Controller.LookAndFeel.UseDefaultLookAndFeel = false;
barManager.Controller.LookAndFeel.SkinName = "Office 2010 Blue";
barManager.ItemClick += HandleSendToPricingSheetClick;
barManager.Items.AddRange(new[] { new BarButtonItem(barManager, "Foo"), new BarButtonItem(barManager, "Bar"), new BarButtonItem(barManager, "Baz") });
var popupMenu = new PopupMenu { Manager = barManager };
foreach (var barItem in barManager.Items) popupMenu.ItemLinks.Add((BarItem)barItem);
popupMenu.ItemLinks[1].BeginGroup = true;
dropDownButtonSendToPricingSheet.DropDownControl = popupMenu;
}
2) This button is on a form. If the form loses focus (e.g. I click on Firefox), the pop-up menu still remains on-top. It won't go away until clicked.
Any suggestions would be much appreciated. Thanks for helping me deal with DevEx insanity.
I have solution to your second question.
You should add drop down button event handler as below:
dropDownButton1.LostFocus += new EventHandler(HidePopUp);
Handler method should be as below:
private void HidePopUp(object sender,object e)
{
dropDownButton1.HideDropDown();
}
For your second question, you should assign value to the bar manager property as:
BarManager manager = new BarManager();
manager.Form = this; // refers to current form
Find below link for reference
https://www.devexpress.com/Support/Center/Question/Details/Q274641
It is probably simpler to use DefaultLookAndFeel
Add this comp to your form and set the theme you'd like to use.
There is no need to set the theme for individual components.
defaultLookAndFeel1.LookAndFeel.SetSkinStyle("Office 2010 Blue");
I am trying to add an activeX control in an user control in a C# windows form based project.
Now if I add that activeX component from the tools menu then by simply using drag and drop I am able use the activeX control.
But when I try to add that one at run time using C# code then it throw following exception:
"Exception of Type
'System.Windows.Forms.AxHost=InvalidActiveXStateException' was
thrown".
Using CreateControl() I am able to get rid of this exception but now the activeX control does not appear on the form.
When are you adding the control and where are you adding it on the form?
You would normally load the control in the constructor just after the component is initialized:
public FormRecalculation()
{
InitializeComponent();
loadDataSelector();
}
If there are any associated license keys you will need to set them and add them to the appropriate container on the form:
private void loadDataSelector()
{
//Initialize the DataSelector
DataSelector = new AXQDataSelector(getClsidFromProgId("QDataSelLib.QDataSel"));
if (DataSelector != null)
{
System.Reflection.FieldInfo f =
typeof(AxHost).GetField("licenseKey",
System.Reflection.BindingFlags.NonPublic |
System.Reflection.BindingFlags.Instance);
f.SetValue(DataSelector, "license-here");
splitContainer1.Panel2.Controls.Add(DataSelector);
((System.ComponentModel.ISupportInitialize)(DataSelector)).BeginInit();
this.DataSelector.Dock = System.Windows.Forms.DockStyle.Fill;
this.DataSelector.Enabled = true;
this.DataSelector.Location = new System.Drawing.Point(0, 0);
this.DataSelector.Name = "DataSelector";
this.DataSelector.Size = new System.Drawing.Size(324, 773);
this.DataSelector.TabIndex = 0;
splitContainer1.Panel2.ResumeLayout();
((System.ComponentModel.ISupportInitialize)(DataSelector)).EndInit();
this.ResumeLayout(false);
this.PerformLayout();
}
else
{
return;
}
}
This is actually for a wrapped OCX but you get the idea...
ok, after some changes the code looks like this. Here at runtime four tabs are created. Initially, on first tab the control is displayed. When user clicks on other tabs page activex control added on those pages dynamically. (This code is written for a .net usercontrol. On run time this usercontrol is added to the form)
private void Populate()
{
int position;
int i = 0;
//here children in list of string type
foreach (string child in children)
{
this.productLineTabs.TabPages.Add(child);
AxSftTree treeadd = loadtree(this.productLineTabs.TabPages[i]);
this.tree.Add(treeadd);
this.tree[i].Columns = 2;
this.tree[i].set_ColumnText(0, "Col1");
this.tree[i].set_ColumnText(1, "Col2");
position = this.tree[i].AddItem(child);
i++;
}
form plv = new form();
plv.Controls.Add(this);
plv.Show();
}
private AxSftTree loadtree(TabPage tab)
{
AxSftTree treeobject = new AxSftTree();
((System.ComponentModel.ISupportInitialize)(treeobject)).BeginInit();
SuspendLayout();
tab.Controls.Add(treeobject);
treeobject.Dock = DockStyle.Fill;
ResumeLayout();
((System.ComponentModel.ISupportInitialize)(treeobject)).EndInit();
return treeobject;
}
You can find some details about this implementation on this page:
http://newapputil.blogspot.in/2013/11/how-to-add-activex-control-at-run-time.html