Visual Studio Design Time Property - Form List Drop Down - c#

[EDIT] To be clear, I know how to get a list of forms via reflection. I'm more concerned with the design time property grid.
I have a user control with a public property of the type Form.
I want to be able to select a form at design time from a dropdown.
I want to populate the form dropdown list from a set namespace: UI.Foo.Forms
This would work like if you have a public property of Control. At design time, the property will automatically populate a dropdown with all the controls on the form, for you to select from. I just want to populate it with all the forms in a namespace.
How do I go about doing this? I hope I'm being clear enough so there is no confusion. I'm looking for some code examples if at all possible. I'm trying to avoid having to spend too much time on this when I have other deadlines to meet.
Thanks for your help in advance.

You can easily get the classes via Reflection:
var formNames = this.GetType().Assembly.GetTypes().Where(x => x.Namespace == "UI.Foo.Forms").Select(x => x.Name);
Assuming you're calling this from code in the same assembly as your forms, you'll get the names of all the types that are in the "UI.Foo.Forms" namespace. You can then present this in the dropdown and, eventually, instantiate whichever is selected by the user via reflection once more:
Activator.CreateInstance(this.GetType("UI.Form.Forms.FormClassName"));
[Edit] Adding code for the design time stuff:
On your control you can create a Form property as such:
[Browsable(true)]
[Editor(typeof(TestDesignProperty), typeof(UITypeEditor))]
[DefaultValue(null)]
public Type FormType { get; set; }
Which references the Editor type that must be defined. The code is pretty self-explanatory, with a minimal amount of tweaking, you'll likely be able to get it to produce exactly what you want.
public class TestDesignProperty : UITypeEditor
{
public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
{
return UITypeEditorEditStyle.DropDown;
}
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
{
var edSvc = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
ListBox lb = new ListBox();
foreach(var type in this.GetType().Assembly.GetTypes())
{
lb.Items.Add(type);
}
if (value != null)
{
lb.SelectedItem = value;
}
edSvc.DropDownControl(lb);
value = (Type)lb.SelectedItem;
return value;
}
}

The dropdown does not close when item gets selected by clicking it, so this could be useful:
assign the click event handler for the listbox and add the event handler function
public class TestDesignProperty : UITypeEditor
{
// ...
IWindowsFormsEditorService editorService;
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
{
// ...
editorService = edSvc ; // so can be referenced in the click event handler
ListBox lb = new ListBox();
lb.Click += new EventHandler(lb_Click);
// ...
}
void lb_Click(object sender, EventArgs e)
{
editorService.CloseDropDown();
}
}

Related

How to inject view when selected item property changes in another view

I am trying to determine the best method to populate a region when the selected item of a data grid view changes.
I am using Prism.DryIoc in my WPF desktop application. It has several regions, each with corresponding loosely coupled views and viewmodels. When the selected item of this data grid changes, I want a different view to populate a different region.
These views do not have references to each other even though they are contained in the same module project file.
So far, I have tried using the IEventAggregator to publish that the selected item has changed.
public class MyObjectViewModel : BindableBase
{
private IEventAggregator _ea;
public MyObjectViewModel(IEventAggregator ea )
{
_ea = ea;
//Do stuff here …
Grid_SelectionChanged = new DelegateCommand(SelectObject, CanSelectObject);
}
private MyData _selectedObject;
public MyData SelectedObject
{
get { return _selectedObject; }
set
{
SetProperty(ref _selectedObject, value);
_ea.GetEvent<MyObjectSelectionChangedEvent>().Publish(SelectedObject);
}
} …
In my MyAppModule.cs file I have the following constructor where I subscribe to the event:
public MyAppModule(IRegionManager regionManager, IEventAggregator ea)
{
_regionManager = regionManager;
ea.GetEvent<MyObjectSelectionChangedEvent>().Subscribe(MyObjectSelectionChanged);
}
I have a corresponding method that reads a property from SelectedObject.ObjectName.
private void MyObjectSelectionChanged(MyObject myObject)
{
Type myObjectType = null;
switch (myObject.ObjectName)
{
case "objectA":
myObjectType = typeof(Views.MyObjectA);
break;
case "objectB":
myObjectType = typeof(Views.MyObjectB);
break;
...
default:
myObjectType = typeof(Views.NoObjects);
break;
}
_regionManager.AddToRegion("TestDataRegion", deviceType);
}
When I debug this, the publish event is called but the above method is not. The subscribe method is called when the object is created.
Am I pursuing the correct solution in order to dynamically load a view based on a selected item in the datagrid object?
What am I missing to get my method called after publishing that the event changed?
Is there a better way to handle this in Prism so that I don't have to have a dirty setter?
EDIT: I have switched to use RegisterForNavigation and RequestNavigation. Thank you for the tip

Make ComboBox accept only specific type

I currently have a ComboBox in my Windows Forms Application. In order to specify which values the ComboBox will contain, I set DataSource property of the ComboBox to some array so that ComboBox contains values from that array. I could also use Items.Add() to add new values to ComboBox. However, I want to make sure that ComboBox can be populated with objects of some specific type. So, if I have a class called X, then I want to make it so that only an array of type X can be used as a data source for the ComboBox. Right now, ComboBox accepts objects of type System.Object. How can I achieve it? Is there a property of ComboBox that I need to set to be equal to my data type's name? Or is there an event that will check whether an object added to my ComboBox is of the needed type and will throw an exception if not?
I was thinking of creating a new class as a subtype of ComboBox, and overriding the Add method of Items property so that Add checks whether its argument is of the needed type (not sure if and how I can do it). Even if I do that, there are still other ways to add new values into ComboBox (AddRange, CopyTo, etc.), so I think there should be a more elegant solution to this problem.
If you want to control the type of item that the ComboBox can contain, you could try creating a new class derived form ComboBox, but you'd run into the problem that it still has the ComboBox.ObjectCollection Items property which would still accept any type! And (unfortunately for your idea of overriding) the Add method isn't virtual.
The only practical solution that I could think of would be to abstract the ComboBox somehow. If this isn't shared code, I would recommend just creating a method that you would use to add items to the ComboBox. Something like:
// NOTE: All items that are added to comboBox1 need to be of type `SomeType`.
private void AddItemToComboBox(SomeType item)
{
comboBox1.Items.Add(item);
}
Any attempt to add a non-SomeType object to the ComboBox would be met with a compiler error. Unfortunately, there's no easy way to prevent someone from still adding a non-SomeType item to ComboBox.Items directly.
Again, if this isn't shared code, it shouldn't really be an issue.
You can hide Items property by your
own Items property of custom type which taking as parameter original ItemsCollection
Example class for testing
public class Order
{
public Int32 ID { get; set; }
public string Reference { get; set; }
public Order() { }
public Order(Int32 inID, string inReference)
{
this.ID = inID;
this.Reference = (inReference == null) ? string.Empty : inReference;
}
//Very important
//Because ComboBox using .ToString method for showing Items in the list
public override string ToString()
{
return this.Reference;
}
}
With next class I tried wrap ComboBox's items collection in own type.
Where adding items must be concrete type
Here you can add other methods/properties you need (Remove)
public class ComboBoxList<TCustomType>
{
private System.Windows.Forms.ComboBox.ObjectCollection _baseList;
public ComboBoxList(System.Windows.Forms.ComboBox.ObjectCollection baseItems)
{
_baseList = baseItems;
}
public TCustomType this[Int32 index]
{
get { return (TCustomType)_baseList[index]; }
set { _baseList[index] = value; }
}
public void Add(TCustomType item)
{
_baseList.Add(item);
}
public Int32 Count { get { return _baseList.Count; } }
}
Here custom combobox class derived from ComboBox
Added: generic type
public class ComboBoxCustomType<TCustomType> : System.Windows.Forms.ComboBox
{
//Hide base.Items property by our wrapping class
public new ComboBoxList<TCustomType> Items;
public ComboBoxCustomType() : base()
{
this.Items = new ComboBoxList<TCustomType>(base.Items);
}
public new TCustomType SelectedItem
{
get { return (TCustomType)base.SelectedItem; }
}
}
Next code used in the Form
private ComboBoxCustomType<Order> _cmbCustom;
//this method used in constructor of the Form
private void ComboBoxCustomType_Initialize()
{
_cmbCustom = new ComboBoxCustomType<Order>();
_cmbCustom.Location = new Point(100, 20);
_cmbCustom.Visible = true;
_cmbCustom.DropDownStyle = ComboBoxStyle.DropDownList;
_cmbCustom.Items.Add(new Order(0, " - nothing - "));
_cmbCustom.Items.Add(new Order(1, "One"));
_cmbCustom.Items.Add(new Order(2, "Three"));
_cmbCustom.Items.Add(new Order(3, "Four"));
_cmbCustom.SelectedIndex = 0;
this.Controls.Add(_cmbCustom);
}
Instead of overriding ComboBox (which wont work as stated in itsme86's answer) you could override usercontrol, add a combobox to this, and then only expose the elements that you wish to work with. Something similar to
public partial class MyComboBox<T> : UserControl where T: class
{
public MyComboBox()
{
InitializeComponent();
}
public void Add(T item)
{
comboBox1.Items.Add(item);
}
public IEnumerable<T> Items
{
get { return comboBox1.Items.Cast<T>(); }
}
}
Please note however that some pieces of automated software rely on access the the underlying controls however so this may cause some issues.
This approach never changes the Items of the combobox so they will still store as objects but when you access them, you are casting them to the correct type and only allowing them to be added of that type. You can create a new combobox via
var myCB = new MyComboBox<ItemClass>();

Binding ListBox.SelectedItem to Property

This might be a duplicate question, but I'm unable to find a good answer. All the answers like Binding WinForms ListBox to object properties don't work on my WinForm. I'll explain.
I have a list of Firms that I show in a ListBox. I would like when the SelectedItem changes, that it updates a property on my model. So that I can read the Firms properties.
// the classes
public class Firm
{
public string Name { get; set; }
public int Id { get; set; }
// more properties ...
}
public class MyModel : INotifyPropertyChanged
{
private Firm _firm = new Firm();
public Firm Firm
{
get { return _firm; }
set
{
if (Equals(value, _firm)) return;
_firm = value;
OnPropertyChanged();
}
}
// more properties and OnPropertyChanged() ...
}
// the form
private MyModel Model;
public void MyForm(List<Firm> firms)
{
lstFirm.DataBindings.Add("SelectedItem", Model, "Firm",
true, DataSourceUpdateMode.OnPropertyChanged);
lstFirm.DisplayMember = "Name";
lstFirm.ValueMember = "Id";
lstFirm.DataSource = firms;
}
public void lstFirm_SelectedIndexChanged(object sender, EventArgs e)
{
// Do something with Model.Firm
}
The problem is that Model.Firm null is. Does anybody have an idea what I need to do to make a databinding between the ListBox and the Model? I bind other stuff on my WinForm (such as TextBoxes to String properties) and those work nicely.
From what I can see, your code never sets Model.Firm... Where's the constructor for MyModel? If you don't provide one, Model.Firm will stay null unless you explicitly set it. Here's an example constructor:
public MyModel(Firm firm)
{
_firm = firm;
}
Also, Equals() doesn't do what you think it does. Instead of if (Equals(value, _firm)) return;, use this: if (value == _firm) return;
Ok, so after a weekend of testing, I figured it out.
I was debuging in the SelectedIndexChanged event and didn't see the change in my Model.Firm just yet. But as the SelectedItemChanged event is only internal, I couldn't use that and that's where the databinding on SelectedItem applies the values to databound items.
Now the reason why the change isn't visible yet, is because the SelectedItemChanged is only fired after the SelectedIndexChanged is executed. So internally in the ListBox control, it probably looks like
this.SelectedIndex = value;
this.SelectedItem = FindItem(value);
this.SelectedIndexChanged(/*values*/);
this.SelectedItemChanged(/*values*/); // Apply databinding changes
So it's quite normal that you don't see the changes, before the change has occured. And I didn't know this, so I was kinda stumped why the SelectedItem (who was displaying the changed value) wasn't copied over to the databound model property.
So I didn't have to change anything major to get it all working. :)

Dynamic Auto updating (to UI, Grid) binding list in C# Winform?

I'm not even sure if I'm doing this correctly. But basically I have a list of objects that are built out of a class/interface. From there, I am binding the list to a DataGridView that is on a Windows Form (C#)
Here the list is a Sync list which will auto update the UI, in this case DataGridView.
Every thing works fine now, but now i would like to have the List should have an dynamic object, that is the object will have by default two static property (ID, Name), and at run time user will select remaining properties. These should be bind to the data grid. Any update on the list should be auto reflected in the grid.
I am aware that, we can use dynamic objects, but i would like to know , how to approach for solution,
datagridview.DataSource = myData; // myData is AutoUpdateList<IPersonInfo>
Now IPersonInfo is the type of object, need to add dynamic properties for this type at runtime.
public class AutoUpdateList<T> : BindingList<T>
{
private ISynchronizeInvoke _SyncObject;
private Action<ListChangedEventArgs> _FireEventAction;
public AutoUpdateList()
: this(null)
{
}
public AutoUpdateList(ISynchronizeInvoke syncObject)
{
_SyncObject = syncObject;
_FireEventAction = FireEvent;
}
protected override void OnListChanged(ListChangedEventArgs args)
{
try
{
if (_SyncObject == null)
{
FireEvent(args);
}
else
{
_SyncObject.Invoke(_FireEventAction, new object[] { args });
}
}
catch (Exception)
{
// TODO: Log Here
}
}
private void FireEvent(ListChangedEventArgs args)
{
base.OnListChanged(args);
}
}
Could you help out on this?
I guess the best way for you is 'to simulate' the properties. I guess the best way would be the ITypedList implementing, the great example is here.
Once I faced similar issue. For my case I've taken this approach. This just might be helpful for you.
Also, there is a way (it's not about anything 'dynamic') to have a base class with fulls set of properties you gonna use. But it won't work if you don't have all properties before the runtime.

How do you create a custom collection editor form for use with the property grid?

I am trying to incorporate a property grid control with a class that has a list/collection of another class as one of the properties. Lets call them class A and the list would be containing class B for reference.
I was wanting to incorporate a form that had two list boxes. The list box on the left would contain a list of all of class B's in my program that are not currently in the list on the right. The list on the right would contain all of the class B's that are currently associated with class A. I want buttons in between to move items between the two lists.
This would be easy to design, but I'm not sure exactly how to set up the form to be used as the collection editor.
Can anyone point me in the right direction?
And also, how can I go about having setting up a drop down for a property that contains a list of id's to select from if anyone could point me in the direction for accomplishing this as well.
Okay, I was finally able to track down how to accomplish this.
I was attempting to create a custom CollectionEditor.CollectionForm which was close to what I needed to do, but it wasn't quite the right direction.
First of all, create a regular Windows Form which includes your GUI for editing your collection. Then just include button/buttons which return a DialogResult in the Form.
Now the key to accomplishing what I was looking for is not a CollectionEditor.CollectionForm as I had thought would be the correct approach, but rather a UITypeEditor.
So, I created a class that inherited from the UITypeEditor. Then you simply flesh it out as such:
public class CustomCollectionModalEditor: UITypeEditor
{
public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
{
if (context ==null || context.Instance == null)
return base.GetEditStyle(context);
return UITypeEditorEditStyle.Modal;
}
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
{
IWindowsFormsEditorService editorService;
if (context == null || context.Instance == null || provider == null)
return value;
editorService = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
CForm CollectionEditor = new CForm();
if (editorService.ShowDialog(CollectionEditor) == System.Windows.Forms.DialogResult.OK)
return CollectionEditor.Programmed;
return value;
//return base.EditValue(context, provider, value);
}
}
The key parts to take note of, are the functions GetEditStyle and EditValue. The part responsible for firing-off the Form you created to edit your collection, is in the EditValue override function.
CForm is the custom collection editor form I designed in this test to edit the collection. You need to fetch the IWindowsFormsEditorService associated with the IServiceProvider and simply call the .ShowDialog(formVariable) of the IWindowsFormsEditorService in order to show the form you designed to edit the collection. You can then catch the returned DialogResult value from your Form and perform any custom handling that you require.
I hope this helps someone out as it took me quite a bit of digging to determine the right way to incorporate this.
This answers Brandon's question. I too searched long and hard on how to actually replace the default propertygrid collection editor. Nathan's answer was the final solution. Brandon here is how I was able to use Nathan's solution and use my own collection editor.
using Swfd = System.Windows.Forms.Design;
using Scm = System.ComponentModel;
using Sdd = System.Drawing.Design;
public class CustomCollectionModalEditor : Sdd.UITypeEditor
{
public override Sdd.UITypeEditorEditStyle GetEditStyle(Scm.ITypeDescriptorContext context)
{
if (context == null || context.Instance == null)
return base.GetEditStyle(context);
return Sdd.UITypeEditorEditStyle.Modal;
}
public override object EditValue(Scm.ITypeDescriptorContext context, IServiceProvider provider, object value)
{
Swfd.IWindowsFormsEditorService editorService;
if (context == null || context.Instance == null || provider == null)
return value;
editorService = (Swfd.IWindowsFormsEditorService)provider.GetService(typeof(Swfd.IWindowsFormsEditorService));
//CForm CollectionEditor = new CForm();
//--- Replaced the Collection from this post with mine which requires an argument that passes the collection
Ccne.CustomCollection editgcp = new Ccne.CustomCollection(); // Ccne.CustomCollection is my collection
editgcp = MYGCPS; // MYGCPS is the actual instance to be edited
Gcp_Editor.GcpEditorMain CollectionEditor = new Gcp_Editor.GcpEditorMain(editgcp); // call my editor
if (editorService.ShowDialog(CollectionEditor) == System.Windows.Forms.DialogResult.OK)
{
MYGCPS = CollectionEditor.ReturnValue1; // update current instance of the collection with the returned edited collection
THISPG.Refresh(); // calls a method which refreshes the property grid
return value; // the replaces the statment in the post >> CollectionEditor.Programmed;
}
//---
return value;
//return base.EditValue(context, provider, value);
}
}
//---------- The propertygrid entry
private Ccne.CustomCollection gCPs;
[Scm.Category("3 - Optional inputs to run gdal_translate")]
[PropertyOrder(133)]
[Scm.TypeConverter(typeof(Ccne.CustomCollectionConverter))]
[Scm.Editor(typeof(CustomCollectionModalEditor), typeof(Sdd.UITypeEditor))]
[Scm.Description("The Collection of the single or multiple Ground Control Points (Gcp)" +
" entered. \n Each gcp requires a Name, pixel, line, easting, " +
"northing, and optionally an elevation")]
[Scm.RefreshProperties(Scm.RefreshProperties.All)] // http://stackoverflow.com/questions/3120496/updating-a-propertygrid
[Scm.DisplayName("23 Collection of Gcp's")]
[Scm.ReadOnly(true)] // prevents values being changed without validation provided by form
public Ccne.CustomCollection GCPs
{
get { return gCPs; }
set { gCPs = value; }
}
//-------- important code from CollectionEditor i.e. > Gcp_Editor.GcpEditorMain(editgcp)
using Swf = System.Windows.Forms;
namespace Gcp_Editor
{
public partial class GcpEditorMain : Swf.Form
{
public Ccne.CustomCollection ReturnValue1 { get; set; }
...
public GcpEditorMain(Ccne.CustomCollection input1)
{
InitializeComponent();
formcollection = input1;
}
...
private void OkayBtn_Click(object sender, EventArgs e)
{
this.DialogResult = Swf.DialogResult.OK;
ReturnValue1 = formcollection;
return;
}

Categories