C# find already existing object instance using a string - c#

How can I the value of a string variable as an instance name?
Here is some code:
TabItem selectedTabItem = this.mainTabControl.SelectedItem as TabItem;
Type t = selectedTabItem.DataContext.GetType();
object instance = Activator.CreateInstance(t);
MethodInfo method = t.GetMethod("SelectAll");
//Here is the problem
method.Invoke(t, null);
It exists 5 different classes (UserManager.cs, GroupManager.cs, PrinterManager.cs, FontManager.cs, ...) and everyone of these classes have the functions SelectAll(), UnselectAll, RemoveSelected. Now I dont want to create something like this:
TabItem selectedTabItem = this.mainTabControl.SelectedItem as TabItem;
if (selectedTabItem.Name == "UserManager")
{
if (btnSelectAll == pressed)
{
this.UserManager.SelectAll();
}
else if (btnUnselectAll == pressed)
{
this.UserManager.UnselectAll();
}
else if (btnRemoveSelected == pressed)
{
this.UserManager.RemoveSelected();
}
}
else if (selectedTabItem.Name == "PrinterManager")
{
if (btnSelectAll == pressed)
{
this.PrinterManager.SelectAll();
}
//...
}
else if (selectedTabItem.Name == "GroupManager")
{
}
//...
Now this line here:
method.Invoke(t, null);
It works fine, but I dont want to create a new instance. I need a existing instance. Now I could write
method.Invoke(this.UserManager, null);
But I think this way is not that good, because I will need again much if/else.
I need something like this:
string instance = "this.UserManager";
method.Invoke(instance, null);

This is something that can be solved by making use of a Custom Control that extends TabItem. This custom TabItem will need a manager assigned to it.
Firstly, you'll need an interface for your manager implementations.
public interface IManager
{
SelectAll();
UnselectAll();
RemoveSelected();
}
Then of course, you'll need some implementations:
public class UserManager : IManager
{
//IManager methods
}
You'll then need a TabItem which can hold an IManager. Here's an example:
public class ManagedTabItem : TabItem
{
public IManager Manager
{
get { return (IManager)GetValue(ManagerProperty); }
set { SetValue(ManagerProperty, value); }
}
public static readonly DependencyProperty ManagerProperty =
DependencyProperty.Register("Manager", typeof(IManager), typeof(ManagedTabItem), new PropertyMetadata(null));
static ManagedTabItem()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ManagedTabItem), new FrameworkPropertyMetadata(typeof(ManagedTabItem)));
}
}
You may want to read up on Custom Controls, you can find a nice tutorial here.
Now, to bring this all together you simply need to replace existing TabItem with the new ManagedTabItem and assign the Manager property.
<local:ManagedTabItem Manager="{Binding UserManager}" ... />
The above method assumes you are making use of DataContext and Binding. Otherwise, you can of course do this in code behind instead:
myTabItem.Manager = UserManager;
The beauty of this method is now you don't need to check which TabItem was clicked, instead you can simply call whatever method you need to on the tab's assigned manager.
ManagedTabItem selectedTabItem = this.mainTabControl.SelectedItem as ManagedTabItem;
if (btnSelectAll == pressed)
{
selectedTabItem.Manager.SelectAll();
}
...
That was quite a large workaround, however there's nothing wrong with keeping things extensible and allowing changes to be easy.

Related

ViewModel properties with multiple calls to PropertyChanged

Recently I've been learning C# and WPF for work. I'm trying to use MVVM on a project I'm working on, just to keep the code organized and learn how it works.
In MVVM, controls on the View bind to properties on the ViewModel, which implements INotifyPropertyChanged. Pretty often, when a certain property is updated, I'll want a bunch of other properties to get updated as a result.
For example, I have a ListBox with a TextBox above it. You can type in the TextBox, and it filters the stuff in the ListBox. But I also need to be able to clear the TextBox from code in certain cases. The code ends up looking like this:
private Collection<string> _listOfStuff;
public Collection<string> FilteredList
{
get
{
if (String.IsNullOrWhiteSpace(SearchText))
{
return _listOfStuff;
}
else
{
return new Collection<string>(_listOfStuff.Where(x => x.Contains(SearchText)));
}
}
set
{
if (value != _listOfStuff)
{
_listOfStuff = value;
OnPropertyChanged("FilteredList");
}
}
}
private string _searchText;
public string SearchText
{
get { return _searchText; }
set
{
if (value != _searchText)
{
_searchText = value;
OnPropertyChanged("SearchText"); // Tells the view to change the value of the TextBox
OnPropertyChanged("FilteredList"); // Tells the view to update the filtered list
}
}
}
As this project gets bigger, this is starting to feel sloppy. I have one setter with 6 calls to OnPropertyChanged and it's getting hard to keep track of stuff. Is there a better way to do this?
I tried out Assisticant on a project about a year ago. It figures out which of your properties need to raise notifications and also which are related. There is a good course for it on Pluralsight and the examples on the website are pretty good. If nothing else you could check out the source code to see how he did it.
Also some good suggestions from Change Notification in MVVM Hierarchies.
They mentioned:
Use an attribute -> e.g. [DependsUpon(nameof(Size))]
and
Josh Smith's PropertyObserver
Could put the raise property change calls in a method if you just need to raise the same notifications every time.
First you shouldn't do potentially expensive operations in a command, then you'll be able to remove the OnPropertyChanged("FilteredList"); from your SearchText.
So you should move that code from the getter and into it's own command and bind it from XAML (either as Command on a button or using Blends Interactivity Trigger to call it when the text fields value changes).
public ICommand SearchCommand { get; protected set; }
// Constructor
public MyViewModel()
{
// DelegateCommand.FromAsyncHandler is from Prism Framework, but you can use
// whatever your MVVM framework offers for async commands
SearchCommand = DelegateCommand.FromAsyncHandler(DoSearch);
}
public async Task DoSearch()
{
var result = await _listOfStuff.Where(x => x.Contains(SearchText)).ToListAsync();
FilteredList = new Collection<string>(result);
}
private Collection<string> _listOfStuff;
private Collection<string> _filteredList;
public Collection<string> FilteredList
{
get
{
return _filteredList;
}
set
{
if (value != _filteredList)
{
_filteredList = value;
OnPropertyChanged("FilteredList");
}
}
}
private string _searchText;
public string SearchText
{
get
{
return _searchText;
}
set
{
if (value != _searchText)
{
_searchText = value;
OnPropertyChanged("SearchText");
}
}
}
On a side note: You can also use OnPropertyChanged(nameof(FilteredList)); to have a refactor friendly version, when you rename your property all of your OnPropertyChanged calls will be updated to. Requires C# 6.0 though, but it's compatible with older .NET Frameworks (back to 2.0), but requires Visual Studio 2015 or later
For anyone searching for a good solution to this type of problem: Check out ReactiveUI.
It is a framework based on Reactive Extensions (Rx), with the idea that you model this type of dependencies between properties explicitly, without a jungle of RaisePropertyChanged(..).
Specifically check out the ObservableAsPropertyHelper (sometimes called OAPH).
You should only raise OnPropertyChanged in the setter of the property itself.
A cleaner implementation of your ViewModel can be:
private Collection<string> _listOfStuff;
private Collection<string> _filteredList;
public Collection<string> FilteredList
{
get
{
return _filteredList;
}
set
{
if (value != _filteredList)
{
_filteredList = value;
OnPropertyChanged("FilteredList");
}
}
}
private string _searchText;
public string SearchText
{
get { return _searchText; }
set
{
if (value != _searchText)
{
_searchText = value;
OnPropertyChanged("SearchText");
FilteredList = new Collection<string>(_listOfStuff.Where(x => x.Contains(SearchText)));
}
}
}
if you just don't wanna type only other option is to fire OnPropertyChanged for all properties which can be done by passing a null or string.Empty, although it will be sloppier code!
OnPropertyChanged(Null);
or
OnPropertyChanged(String.Empty);

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>();

Properties are making trouble

In my application I have added a Properties.cs file which contains properties that I am would use through out the application. I am getting NullReferenceException => Object reference not set to an instance of an object.
Here is the code for Properties.cs
public class Properties
{
private static string type1;
public static string Type1
{
get
{
return type1;
}
set
{
type1= value;
}
}
}
And when I access this property in one of my form I am getting error. e.g.
if (Properties.Type1.Equals(string.Empty) || Properties.Type1.Equals(null))
{
// Do something
}
Firstly, you're making life hard for yourself. This is fine (or at least, just as fine, but a lot easier; whether or not static members is a good idea is a separate question, and depends a lot on the context):
public class Properties
{
public static string Type1 { get;set; }
}
Secondly, this has nothing to do with properties, and everything to do with calling a method on a null instance. You can just use == which avoids this issue, i.e.
if (Properties.Type1 == "" || Properties.Type1 == null)
{
// Do something
}
However, for convenience there is also string.IsNullOrEmpty:
if (string.IsNullOrEmpty(Properties.Type1))
{
// Do something
}
Use this instead:
if (string.IsNullOrEmpty(Properties.Type1))
{
// Do something
}
You are doing the null and empty check in the wrong way.
The correct way is:
if (string.IsNullOrEmpty(Properties.Type1))
{
....
}
You can do this
if (String.IsNullOrEmpty(Properties.Type1))
{
// Do something
}
but if you want a default value for this then I suggest you set it in a static constructor.

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;
}

How to pass control to a method

I want to create a method that changes enabled property. How do I pass the contorl name and property to a method.
If the following were my original method:
public void ChangeProperties()
{
btnAcesScore.Enabled = true;
}
I want to be able to change the "btnAcesScore" each time I call this method. How do I pass this to the method. I tried passing it as a string but that doesn't work.
Here is what I tried:
public void ChangeProperties(string category)
{
category = true;
}
ChangeProperties("btnAcesScore.Enabled");
Susan
Try this :
public void ChangeProperties(Control ctrl)
{
ctrl.Enabled = true;
}
and call it like that :
ChangeProperties(btnAcesScore);
What exactly is the purpose of this? Is it to reuse the method to arbitrarily change the Enabled property of any given control? If so, there is an easier way to accomplish it, as outlined by Canavar.
Or is the point of this method to toggle the setting? In which case, your method would look either like:
public void ChangeProperties()
{
btnAcesScore.Enabled = !btnAcesScore.Enabled;
}
or
public void ChangeProperties(Control ctrl)
{
ctrl.Enabled = !ctrl.Enabled;
}
depending on whether you wanted to hit just the one control, or provide access to many. In any event, I personally don't see much point to encapsulating a single property access within a method, and if you were insistent (and this method didn't adjust other properties), I'd at least rename it to something like ToggleEnabled.
Since the original question had a reflection tag I think she wanted a reflection answer (whether or not that is good design) so here is a Reflection answer.
the form has a controls collection and with this you can search for it and use reflection to set the property:
public void ChangeProperties(Form form, string category)
{
string[] parts = category.Split(".");
int index = form.Controls.IndexOfKey(parts[0]);
Control control = null;
if (index >= 0)
{
control = form.Controls[index].;
}
if (control != null)
{
PropertyInfo propertyInfo = control.GetType().GetProperty(parts[1]);
if (propertyInfo != null)
{
propertyInfo.SetValue(control, true);
}
}
}
if you call it from the form the control lives on
ChangeProperties(this, "btnAcesScore.Enabled");
How about also
void ChangeProperty(ref bool output)
{
output = true;
}
ChangeProperty(ref btnAcesScore.Enabled);
Not sure I totally understand your intent, but you could pass a delegate to some code that changed your property...
public void ChangeProperties(Action<bool> setprop)
{
...
setprop(true);
}
then call it:
ChangeProperties(b => btnAcesScore.Enabled = b);
I'd use reflection - use the GetType() Method on the object you send through to your method and then use the GetProperties to match against the property you send through. You can then set the values at that point.
Try this:
public void ChangeProperties(string category, object value)
{
var categoryConcat = category.Split('.');
var control = this.Controls.Cast<Control>()
.Where(x => x.Name == categoryConcat[0]).First();
control.GetType().GetProperty(categoryConcat[1])
.SetValue(control, value);
}
The example probably needs some checks on the existance of the control and the property.
Main()
{
ChangeProperties(ref category,True); //Where Category is the ID of the Textbox control i.e <asp:textbox ID="Category "></textbox>
}
public void ChangeProperties(ref TextBox category,bool val)
{
category.Enabled = val;
}

Categories