I have been searching for almost a week now, but I can't seem to solve my simple problem. I want to get all the name and text properties of all the forms in my project.
Here is my code:
using System.Reflection;
Type Myforms = typeof(Form);
foreach (Type formtype in Assembly.GetExecutingAssembly().GetTypes())
{
if (Myforms.IsAssignableFrom(formtype))
{
MessageBox.Show(formtype.Name.ToString()); // shows all name of form
MessageBox.Show(formtype.GetProperty("Text").GetValue(type,null).ToString()); // it shows null exception
}
}
I need the name and the .Text of the form to store it in the database to control user privileges.
MessageBox.Show(formtype.GetProperty("Text").GetValue(type,null).ToString()); shows exception because you need an instance of Form to get its Text property as Form does not static Text property.
To get the default Text property create a instance
var frm = (Form)Activator.CreateInstance(formtype);
MessageBox.Show(formtype.GetProperty("Text").GetValue(frm, null).ToString());
The properties .Text and .Name are not static. So, you can't get the value of that property without calling a Constructor of that form. You must create an object of that form to read the property.
List<String> formList = new List<String>();
Assembly myAssembly = Assembly.GetExecutingAssembly();
foreach (Type t in myAssembly.GetTypes())
{
if (t.BaseType == typeof(Form))
{
ConstructorInfo ctor = t.GetConstructor(Type.EmptyTypes);
if (ctor != null)
{
Form f = (Form)ctor.Invoke(new object[] { });
formList.Add("Text: " + f.Text + ";Name: " + f.Name);
}
}
}
To read properties, you need to make new instance of the form. Above you're browsing all the types that are inheriting from the Form-class. You can read the different Form-class names, but that's it.
To read the Text-property, you need to browse instances of your Forms. You can use Application.OpenForms to read the Text and Name properties of your open forms.
You can try this to read the properties:
List<KeyValuePair<string, string>> formDetails = new List<KeyValuePair<string, string>>();
Type formType = typeof(Form);
foreach (Type type in Assembly.GetExecutingAssembly().GetTypes())
{
if (formType.IsAssignableFrom(type))
{
using (var frm = (Form)Activator.CreateInstance(type))
{
formDetails.Add(new KeyValuePair<string, string>(frm.Name, frm.Text));
}
}
}
I fixed the code and it should work now.
Related
I am using a PXSmartPanel to display a dialog allowing a user to enter a string. I would like to use a 'Non-persisted field', but that means (I think) that I would have to get the field value by calling the field on the Panel and extracting its value.
The text field's ID is cstFieldSSN and the non-persisted field's ID is UsrSSN
My method looks like this:
(I'm calling the dialog upon clicking a menu item)
// Initialize 'myPanel'
public PXFilter<PX.Objects.CR.Contact> myPanel;
// Make the 'Letters' menu available to 'Automation Steps'
public PXAction<PX.Objects.CR.Contact> letters;
[PXUIField(DisplayName = "Letters", MapEnableRights = PXCacheRights.Select)]
[PXButton(SpecialType = PXSpecialButtonType.Report)]
protected virtual IEnumerable Letters(PXAdapter adapter, string reportID)
{
if (myPanel.AskExt(true) != WebDialogResult.OK) return;
PXReportRequiredException ex = null;
Contact contact = Base.Caches[typeof(Contact)].Current as Contact;
Dictionary<string, string> parameters = new Dictionary<string, string>();
parameters["ContactID"] = contact.ContactID.ToString();
/** Here's the issue **/
parameters["SSN"] = myPanel.Current.UsrSSN;
throw new PXReportRequiredException(parameters, reportID, "");
if (ex != null) throw ex;
return adapter.Get();
}
I'm getting
'PX.Objects.CR.Contact' does not contain a definition for 'UsrSSN' and no extension method 'UsrSSN' accepting a first argument of type 'PX.Objects.CR.Contact' could be found (are you missing a using directive or an assembly reference?)
Could someone help me out or point me to a resource?
Thanks to #Brendan, my final code looks like this:
// Initialize 'myPanel'
public PXFilter<PX.Objects.CR.Contact> myPanel;
// Make the 'Letters' menu available to 'Automation Steps'
public PXAction<PX.Objects.CR.Contact> letters;
[PXUIField(DisplayName = "Letters", MapEnableRights = PXCacheRights.Select)]
[PXButton(SpecialType = PXSpecialButtonType.Report)]
protected virtual IEnumerable Letters(PXAdapter adapter, string reportID)
{
// Launch the PXSmartPanel dialog and test result
if (myPanel.AskExt(true) == WebDialogResult.OK)
{
PXReportRequiredException ex = null;
Contact contact = Base.Caches[typeof(Contact)].Current as Contact;
Dictionary<string, string> parameters = new Dictionary<string, string>();
//*** Get the extended class
var myExt = myPanel.Current.GetExtension<ContactExt>();
parameters["ContactID"] = contact.ContactID.ToString();
//*** Get the extended class's custom field value
parameters["SSN"] = myExt.UsrSSN;
throw new PXReportRequiredException(parameters, reportID, "");
if (ex != null) throw ex;
}
return adapter.Get();
}
But I also had to set the CommitChanges property on the text field to True so that the value would be pushed back to the cached Contact, allowing me to use it.
I'm currently looking for a way to dynamically create a FormDialog from values predefined in the database. In other words, my field types, prompts and settings are all stored in a database, and what I'm trying to achieve is reading those settings and building the appropriate form dynamically.
What I tried so far is something similar to the following. Suppose I have a form with a Name (string) and an Age (int) field (FieldDefinition is a class I created to store the parameters of a field, assuming they are fetched from the database) (The code is stripped just to illustrate the idea):
public static IForm<dynamic> BuildForm()
{
string FormMessage = "Welcome to demo contact form!";
string CompletionMessage = "Thank your for your info. Our team will contact you as soon as possible.";
var fields = new List<FieldDefinition>()
{
new FieldDefinition()
{
Name = "Name",
FieldType = typeof(string),
Prompts = new string[] { "What's your name?", "Please input your name" }
},
new FieldDefinition()
{
Name = "Age",
FieldType = typeof(int),
Prompts = new string[] { "What's your age?", "How old are you?" }
}
};
var builder = new FormBuilder<dynamic>();
builder.Message(FormMessage);
foreach (var f in fields)
{
builder.Field(
new FieldReflector<dynamic>(f.Name)
.SetType(f.FieldType)
);
}
builder.AddRemainingFields()
.OnCompletion(async (context, order) => {
var message = context.MakeMessage();
message.Text = CompletionMessage;
await context.PostAsync(message);
});
return builder.Build();
}
So here's the problems:
I thought I could use a dynamic type. But a method cannot return a dynamic object as it is determined at run-time. Therefore, I got an error when I tried building the form using the following:
dynamic values; var form = new FormDialog<dynamic>(values, ContactForm.BuildForm, FormOptions.PromptInStart, null);`
I need to create the properties of the object dynamically, therefore I looked for a way to create a Type on runtime. I ended up with something called TypeBuilder but I was a bit skeptical if it could solve my problem or not.
Therefore, I guess the ultimate start is by using the FieldReflector but I have no idea how to achieve this. I'm looking for something similar to the above but that does actually work.
Have you looked at FormBuilderJson? You could dynamically construct the .json string, and build the form at runtime:
public static IForm<JObject> BuildJsonForm()
{
string fromFlowJson = GetFormFlowJson();
return new FormBuilderJson(schema)
.AddRemainingFields()
.Build();
}
See here for more information: https://learn.microsoft.com/en-us/azure/bot-service/dotnet/bot-builder-dotnet-formflow-json-schema?view=azure-bot-service-3.0
How could I get a list of all user-defined controls that were added to current namespace?
I am using C# 2010.
You can use reflection. Try this code:
public static class ControlsFinder
{
public static List<Type> FindControls(Assembly controlsLibrary,
string rootNamespace, bool includeNestedTypes = true)
{
var parent = typeof(UserControl);
return controlsLibrary.GetTypes()
.Where(t => (includeNestedTypes
? t.FullName.StartsWith(rootNamespace)
: t.FullName.Equals($"{rootNamespace}.{t.Name}"))
&& parent.IsAssignableFrom(t))
.ToList();
}
}
Usage example:
var controls = ControlsFinder.FindControls(Assembly.GetExecutingAssembly(), "WinFrm");
If you only want names you can select them from controls:
var names = controls.Select(t => t.Name).ToArray();
With these method i can extract list of all user-defined controls in my project and create an instance of the User-defined Control by it's name on my form.
var assemblies = AppDomain.CurrentDomain.GetAssemblies(); // Get my CurrentDomain Object
Assembly myType = Assembly.GetExecutingAssembly(); // Extract list of all references in my project
foreach (var assembly in assemblies) // Search for the library that contains namespace that have needed controls
{
if (assembly.GetName().ToString().ToUpper().IndexOf("FIBACONTROLS") > -1)
{
myType = assembly; // Get All types in the library
List<Type> myTps = myType.GetTypes().ToList();
Type mT = null;
foreach (Type selType in myTps) // Find the type that refer to needed user-defined control
{
if (selType.Name.ToUpper() == "FIBACOLORPICKER")
{
mT = selType;
break;
}
}
if (mT == null)
return;
object myInstance = Activator.CreateInstance(mT); // Created an instance on the type
Control mFib = (Control)myInstance; // create the control's object
mFib.Name = "Hahah"; // add the control to my form
mFib.Left = 100;
mFib.Top = 200;
mFib.Visible = true;
this.Controls.Add(mFib);
break;
}
}
I try add some comment to code to describe it.
It work and sure there are some better way to do it, but I'm new one in C# and I am sure that the solution that I found is not the best.
I've been trying to create a new base class for a Windows Forms form. I want to have this base class go through all the tableadapters it has on it and update their connection strings without anyone adding any code to the form. They just put the tableadapters on the form and don't worry about the connection string settings as it's all handled in the base class.
The problem is my reflection code can find the property fine, but it can't set it. How can I fix it?
Below is the code:
public class cFormWS : Form
{
public string ConnectionStringToUse { get; set; }
public cFormWS()
{
Load += cFormWS_Load;
}
void cFormWS_Load(object sender, EventArgs e)
{
InitiliseTableAdapters();
}
private void InitiliseTableAdapters()
{
var ListOfComponents = EnumerateComponents();
foreach (var ItemComp in ListOfComponents)
{
if (ItemComp.ToString().ToLower().EndsWith("tableadapter"))
{
var ItemCompProps = ItemComp.GetType().GetRuntimeProperties();
var TASQLConnection =
ItemCompProps.FirstOrDefault(
w => w.PropertyType == typeof(System.Data.SqlClient.SqlConnection));
if (TASQLConnection != null)
{
var property = typeof(System.Data.SqlClient.SqlConnection).GetProperty("ConnectionString");
// How do I set the value?
string value = "some new connection string";
var ConvertedProperty = Convert.ChangeType(value, property.PropertyType);
// I tried seting a value. It is not working:
// "object does not match target type"
property.SetValue(TASQLConnection, ConvertedProperty, null);
//// I tried using a method. It is not working:
//// "object does not match target type"
//var m = property.SetMethod;
//ParameterInfo[] parameters = m.GetParameters();
//m.Invoke(m, parameters); // m.Invoke(this, parameters); // m.Invoke(ItemComp, parameters);
}
}
}
}
private IEnumerable<Component> EnumerateComponents()
{
return from field in GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
where typeof(Component).IsAssignableFrom(field.FieldType)
let component = (Component)field.GetValue(this)
where component != null
select component;
}
When you do SetValue, you need to pass in the object that you wish to set the property on.
In your first example code, you passed in ItemComp: This is incorrect, since the ConnectionString is a property of the SqlConnection which is a property of ItemComp
In your edited question (and my original answer) you pass in the TASqlConnection. However, this is not the object, but a PropertyInfobased of the object
The correct way is to get the value from the ItemComp object and pass that in:
property.SetValue(TASQLConnection.GetValue(ItemComp), ConvertedProperty, null);
ORIGINAL (INCORRECT) ANSWER:
You're trying to set a ConnectionString property of ItemComp. The ConnectionString is not a property of the TableAdapter, but of the SqlConnection (which is a property of the TableAdapter).
The correct way of setting the property would be this:
property.SetValue(TASQLConnection, ConvertedProperty, null);
I'm having trouble while getting the value of the text property in a non-executing assembly; I read an assembly from disk via reflection, then i get all classes in the assembly to search for the Text property in a windows form class which is initialized by win forms designer. So far i have the following code:
static void Main(string[] args)
{
Assembly asm = Assembly.LoadFrom(Path.Combine(path, "Assembly.exe"));
PropertyInfo[] props;
foreach (Type t in asm.GetTypes())
{
var value = t.GetProperty("Text").GetValue(/*Not sure what to put here*/)
}
}
And this is how the designer generated the form
Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None
Me.BackColor = System.Drawing.Color.FromArgb(CType(CType(0, Byte), Integer), CType(CType(128, Byte), Integer), CType(CType(128, Byte), Integer))
Me.ClientSize = New System.Drawing.Size(234, 181)
Me.Cursor = System.Windows.Forms.Cursors.Default
Me.Font = New System.Drawing.Font("Arial", 8.25!, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, CType(0, Byte))
Me.ForeColor = System.Drawing.SystemColors.WindowText
Me.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog
Me.Location = New System.Drawing.Point(581, 222)
Me.MaximizeBox = False
Me.MinimizeBox = False
Me.Name = "winform"
Me.RightToLeft = System.Windows.Forms.RightToLeft.No
Me.StartPosition = System.Windows.Forms.FormStartPosition.Manual
Me.Text = "Title"
Me.fraDÃas.ResumeLayout(False)
Me.ResumeLayout(False)
Keep in mind that the assembly is on disk and non-executing and that I want to retrieve the value of the Text property of every winform (I guess it should be somewhere hardcoded in the assembly since it was generated by the winforms designer)
Please tell me if this is possible, thanks!
Your requirements are contradictory, when you load an aseembly via reflection, and instantiate an object or try to get a property value, what happens is that some code begins to run, there is no way around that.
Remember that properties are just "syntax sugar" for a pair of methods, the getter and setter. Their current value is nothing but the value returned by the getter method, and when you change its value, you're in fact calling its setter method. So, to retrieve property values, you must make some code to run, even if it's a trivial get method.
I think maybe your confusion comes from the fact that you're using a designer to create the form. Particularly with the WinForms designer (WPF for instance is substantially different), all it does is to autogenerate some code for you. Setting properties, placing and moving controls around, what's happening under the hood is that it writes code that replicate your actions at runtime, specifically, it codes the InitializeComponent method. The real property value is set when the constructor is called (that in turn calls InitializeComponent), and then you may read/change using many properties.
What you would need to read those designer attributes is that those were hardcoded in some form of metadata, so that it's simply read as data and not as the result of code execution. That's not the case with WinForms, as it "saves" the form as code.
You cannot read a property of a class that has not been instantiated! The parameter you are missing is an instance of your type.
You must create an instance of the type with object o = Activator.CreateInstance(type); before accessing its members (unless they are static).
Your problem is related to how add-ins (plug-ins) can be loaded at runtime.
Here is how I made an Add-In Loader. Below, I will explain how you can adapt it to your problem. Add-Ins have to implement the IAddIn interface in my example. You are totally free in the definition of IAddIn. You could define it like this:
public interface IAddIn
{
bool OnLoad();
string Version { get; set; }
string Text { get; set; }
}
This allows you to access members without reflection.
public class AddInLoader
{
// Loads all non abstract types implementing IAddIn in all DLLs found in a folder.
public IList<IAddIn> Load(string folder)
{
var addIns = new List<IAddIn>();
string[] files = Directory.GetFiles(folder, "*.dll");
foreach (string file in files) {
addIns.AddRange(LoadFromAssembly(file));
}
return addIns;
}
// Loads all non abstract types implementing IAddIn found in a file.
private static IEnumerable<IAddIn> LoadFromAssembly(string fileName)
{
Assembly asm = Assembly.LoadFrom(fileName);
string addInInterfaceName = typeof(IAddIn).FullName;
foreach (Type type in asm.GetExportedTypes()) {
Type interfaceType = type.GetInterface(addInInterfaceName);
if (interfaceType != null &&
(type.Attributes & TypeAttributes.Abstract) != TypeAttributes.Abstract){
IAddIn addIn = (IAddIn)Activator.CreateInstance(type);
addIn.Version = asm.GetName().Version.ToString();
yield return addIn;
}
}
}
}
Now you can load and access the add-ins like this:
var loader = new AddInLoader();
IList<IAddIn> addIns = loader.Load(folderPath);
foreach (IAddIn addIn in addIns) {
if (addIn.OnLoad()) {
Console.WriteLine("Version = {0}, Text = {1}", addIn.Version, addIn.Text);
}
}
Reading the titles of Forms at runtime:
You can easily adapt this example. Instead of searching for types implementing an interface, search for types deriving from System.Windows.Forms.Form.
private static IEnumerable<Form> LoadFormsFromAssembly(string fileName)
{
Assembly asm = Assembly.LoadFrom(fileName);
foreach (Type type in asm.GetExportedTypes()) {
if (typeof(Form).IsAssignableFrom(type) &&
(type.Attributes & TypeAttributes.Abstract) != TypeAttributes.Abstract) {
Form form = (Form)Activator.CreateInstance(type);
yield return form;
}
}
}
Now you can get the texts of the forms like this:
var forms = LoadFormsFromAssembly(path);
foreach (Form frm in forms) {
Console.WriteLine(frm.Text);
}
Note: You must instantiate the forms, however you do not need to open (show) them. The code works only if the forms have a default constructor, i.e. a constructor without parameters.
You need an instance object for that type to get the value of a property.
It looks like you just want to check if a type has a "Text" property or not. You can to it by checking
bool hasTextProperty = t.GetProperty("Text") !=null;