[Okta Webinar] Learn how to a build a cloud-first strategyRegister Now

x
?
Solved

How can I check whether a form exists from its type and if so access its properties?

Posted on 2007-07-23
38
Medium Priority
?
253 Views
Last Modified: 2008-01-09
I need to be able check all MDI children for a specific type of form, and then if I find it check its properties to see if they match a known specification - this is to be able to determine if the particular form and its data already exists - if so, bring it to the front, if not create it.  

The reason for this requirement is that I have multiple use forms, so there could be multiple instances of a single form each of which could contain information about a range of different things and this is determined by properties such as EditID which contains the ID of the current record being edited.  Others determine the table, and so on.  

In Delphi this is pretty simple, because you can use GetPropInfo^ to access the form's properties, and you can pass the form type directly to the method.  I can get the form type in C#:

Type type = Type.GetType("MyNamespace.MyForm");

I can also loop through the MDI children, but that is where my plan falls down...

Can anyone advise the best way to achieve this?

Chris Bray.
0
Comment
Question by:chrisbray
  • 19
  • 14
  • 5
38 Comments
 
LVL 96

Expert Comment

by:Bob Learned
ID: 19550823
Try this:

            foreach (Form child in this.MdiChildren)
                if (child.GetType() == typeof(MyNamespace.MyForm))
                {
                    MyNamespace.MyForm myForm = (MyNamespace.MyForm)child;

                }

Bob
0
 
LVL 6

Expert Comment

by:Bruce_1975
ID: 19563784
An other way:
foreach (Form child in this.MdiChildren)
{
   MyForm actualChild = child as MyForm;
   if (actualChild != null)
   {
      // do waht ever you want with actualChild
      actualChild.BringToFront();
   }
}
0
 
LVL 3

Author Comment

by:chrisbray
ID: 19563870
The Learned One:

Hi Bob,

Thanks for your suggestion. Unfortunately at compile time we do not know the type of the form we are searching for, nor the other tings we need to check - these will have to be passed in as parameters.

Does this make any difference to your suggested code?

Chris Bray.
0
Industry Leaders: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 
LVL 3

Author Comment

by:chrisbray
ID: 19563880
Hi Bruce_1975,

Unfortunately we do not know the type of the form at compile time.  It will have to be passed as a string and processed from there....  We would need to case the form type somehown probably using Gettype?

Chris Bray.
0
 
LVL 96

Expert Comment

by:Bob Learned
ID: 19563972
Chris,

This might be one direction:

        public static Form GetFormInstance(Form parent, string typeName)
        {
            foreach (Form child in parent.MdiChildren)
            {
                if (child.GetType() == Type.GetType(typeName))
                {
                    return child;
                }
            }
            return null;
        }

Bob
0
 
LVL 96

Expert Comment

by:Bob Learned
ID: 19563976
The problem is accessing the properties, since you don't have a "known" type.  You could use reflection to get at the property values.

Bob
0
 
LVL 3

Author Comment

by:chrisbray
ID: 19564188
Hi Bob,

I have already got the part where I can loop through and get the type of the form using reflection... the problem is to access the properties as you describe.  

In Delphi, as I said before, it is quite easy - just call PropInfo^ and away you go.  I think there is a PropertyInfo class in .Net, but I am not sure where or how to use it, hence this question.

Any ideas?

Chris Bray.
0
 
LVL 96

Expert Comment

by:Bob Learned
ID: 19565031
Something like this example (untested):

using System.Reflection;

foreach (PropertyInfo prop in child.GetType().GetProperties())
   Console.WriteLine("{0}, {1}", prop.Name, prop.GetValue(child, new object[] {}));

Bob
0
 
LVL 3

Author Comment

by:chrisbray
ID: 19565392
Hi Bob,

Looks like we could be getting closer... your untested example at least compiles :-)

Can we access the properties by name rather than as part of a loop?

Chris Bray.
0
 
LVL 3

Author Comment

by:chrisbray
ID: 19565540
Hi Bob,

This inefficient test code in my FormExists method runs, but does not find a matching child even though I have deliberately opened a child window of that type:

           Type type = Type.GetType("MyNamespace." + formType);
           string foundproperties = string.Empty;
           foreach (Form child in this.MdiChildren)
                if (child.GetType() == Type.GetType(formType))
                {
                    foreach (PropertyInfo prop in child.GetType().GetProperties())
                        foundproperties += string.Format("{0}, {1}", prop.Name, prop.GetValue(child, new object[] { }));
                }

            MessageBox.Show(foundproperties);

The calling code is as follows:

            FormExists("ListForm", "", -1);

"ListForm" is the string representation of the form type, you can ignore the other parameters which will eventually represent the property values we want to check for...

Does this shed any light on a way forward?

Chris Bray.
0
 
LVL 3

Author Comment

by:chrisbray
ID: 19565729
Hi Bob,

Thanks again for your help so far.  A few modifications and the test code now produces a list of properties in a message box:

           Type type = Type.GetType("MyNamespace." + formType);
           
            string foundproperties = string.Empty;

           foreach (Form child in this.MdiChildren)
           {
                if (child.GetType() == type)
                {
                    foundproperties += type.ToString()+" =";
                    foreach (PropertyInfo prop in child.GetType().GetProperties())
                        foundproperties += string.Format(" {0}, {1};", prop.Name, prop.GetValue(child, new object[] { }));
                }
           }

            MessageBox.Show(foundproperties);

That just leaves accessing the properties by name instead of looping through them to produce a working solution...

Chris Bray
0
 
LVL 3

Author Comment

by:chrisbray
ID: 19565766
Hi Bob,

Not quite as good as I thought, since the list of properties includes those of the MDI parent as well as those of the child form :-(

Chris Bray.
0
 
LVL 96

Expert Comment

by:Bob Learned
ID: 19565896
Chris,

You can use the GetProperty method to get a property by name:

     child.GetType().GetProperty(name)

Bob
0
 
LVL 3

Author Comment

by:chrisbray
ID: 19566336
HI Bob,

Neraly there now.. I find that the property I need to access is in fact a property of a property of the form.  Can we go down one step to access the property of a property?   Not a problem if we can't, I can just make sure that every form has the necessary properties and create a readonly property linked to the property's property.

Having done that, how can I access the property value?  The GetValue method demands an Object or Index, and I don't seem to have either...

Chris Bray.

0
 
LVL 96

Expert Comment

by:Bob Learned
ID: 19566373
Can you explain the "property of a property" part in a little more detail please?

Bob
0
 
LVL 3

Author Comment

by:chrisbray
ID: 19566972
Hi Bob,

I think this may be a bit of a red herring, but each form has a property called ListSchema.  This property is of type ListSchema and contains a number of properties itself.  The one of these that I am interested in for my purposes is FormID (string, contains the from identity string).  I also need to access EditID (int64, contains a value to indicate the record currently being edited by the form) but that is a direct access property.

I have a large application with many forms many of which are identical and all of which are based on a known ancestor and so have certain properties we can rely on being there.  When we wish to deal with (for example) Countries we pass to the form CountryListSchema (inherits from ListSchema) which tells the list form how to obtain its data, how to edit its contents, and so on.  When it is editing a record, it passes its ListSchema to the edit form so that it too knows how to save itself, update itself, and so on.

In the situation I am trying to resolve I need to know if a copy of a particular form is currently displayed (for example a ListForm or a ListItemEditForm).  If I am looking for a ListIemEditForm or one of its ancestors I need to know if the ID and Type are the same so that I don't allow the user to edit the same record twice in two separate MDI child windows.  If we are editing a Country and the ID is different it is OK to create a new form and edit the new ID.  If the ID is the same we need to bring it to the front so that the user can continue to edit it.

Chris Bray.

0
 
LVL 96

Expert Comment

by:Bob Learned
ID: 19567558
Chris,

If each form had the same base class, where the ListSchema was defined, then you could just cast the form to the base class, and get access to the ListSchema property.

Bob
0
 
LVL 3

Author Comment

by:chrisbray
ID: 19568843
Hi Bob,

They don't necessarily have the same base class.  All the edit forms inherit from ListEditDialog, but the list forms inherit from Form.  Because this is effectively generic I am looking to pick it all up from the parameter passed to the search routine.  In Delphi, as I have said before, this is almost a no-brainer because I can use the 'as' clause to generate the necessary access but I am struggling in C#...

In Delphi I just do:

          // frm: TFormClass, passed as parameter to the method
          f := MainForm.MDIChildren[i] as frm;

I can then do:

              TagMatch := (f.Tag = TagID);

here accessing the Tag directly.  Alternatively I can do:

              PropInfo := GetPropInfo(f.ClassInfo, 'FormIDString');
              if Assigned(PropInfo) then
                IDMatch := GetStrProp(f, PropInfo) = FormIDString;

This compares the value of the property with passed FormIDString parameter to determine whether it matches.  Can we do the same in C# somehow?

Chris Bray.
0
 
LVL 96

Expert Comment

by:Bob Learned
ID: 19568914
You can use the 'as' operator in C# also to attempt to cast an object to a different class.  If it doesn't work, a null will be returned.  

You can have multiple classes in the inheritance chain for the form:

Form
   FormBase - ListSchema
      ListEditDialog

FormBase fb = child as FormBase;
if (fb.ListSchema != null)
{
}

Bob
0
 
LVL 3

Author Comment

by:chrisbray
ID: 19569209
Hi Bob,

OK. But I need to pick FormBase from the string parameter passed to the method...  or from the type retrieved using that string.

I need to do something like:

  Type type = Type.GetType("MyNamespace." + formType);
  (type) fb = child as (type);
  if (fb.ListSchema != null)
   {
   }

That, of course, wouldn't work... but it demonstrates what I think I need to achieve, and what the Delphi code does easily.

Chris Bray.
0
 
LVL 96

Expert Comment

by:Bob Learned
ID: 19569335
Chris,

Maybe I am not getting my point across very well.  If all of your forms share the same base somewhere within its inheritance chain, you can cast the Form instance to get at the members of the base class.  Then you wouldn't need all that "type" stuff.

Bob
0
 
LVL 3

Author Comment

by:chrisbray
ID: 19569776
Hi Bob,

More likely me just being thick...  Anyway, as I said before, I cannot guarantee that ALL forms whill share the same base class, except of course for Form.  Even if I enforce a common ancestor in the current application, I can't enforce it personally on plugins etc., and what about forms that don't have any need or use for the ListSchema?

Some forms are of type Form, other forms are of type ListEditDialog, others ListItemDialog, and so on.   The first two have ListSchema properties, and the latter inherits from ListEditDialog and therefore picks up the property from its ancestor.

Chris Bray.
0
 
LVL 96

Expert Comment

by:Bob Learned
ID: 19571206
Well, then, there is a way to guarantee that ALL forms implement a common interface that you can interrogate.

Bob
0
 
LVL 3

Author Comment

by:chrisbray
ID: 19572741
Hi Bob,

I am sorry but I don't understand, please can you clarify? Having re-read my message I think the problem may be that I have not been as clear as I should have been as  the words 'the first two' are easy to wrongly interpret - type Form does NOT have a ListSchema property by default, it is the standard Windows Form.  However I have added the property to specific forms that require it.

Since I cannot say who will actually implement a plugin or access the system through its API, and I have no way of knowing what sort of form they will implement, how can I guarantee that all forms implement this common interface?  

Many forms may not require a ListSchema at all, so where is the benefit in insisting that they have one that will never be used?  Why can we not simply do as I did in Delphi and determine the type of form prior to interacting with it?  Is this yet another thing that C# simply cannot do?

Chris Bray.
0
 
LVL 6

Expert Comment

by:Bruce_1975
ID: 19573494
Hi Chris,
Bob showed you the way to implement a plugin concept this c# (baseclass and interface). I don't think, that there is any other (simple) way to handle this problem.

Maybe Delphi would be a much better choise to solve the problem the way, you want to do it. Or you have to think about a different plugin concept (if possible).
0
 
LVL 3

Author Comment

by:chrisbray
ID: 19574365
Hi Bruce_1975,

Thank you for your input.  However, you seem to have missed the point.  It is not the plugins that are the issue, I was merely providing Bob with the additional clarification that he had requested so that he could understand the issue.  The simple thing I need to do is access a property (or a property of a property) based in its name.

Delphi is not an option I can use when I am part of a small team coding in C#.net!!  In Delphi I can already do what is required with ease and so would not need to ask the question, but the application is not being written in that language hence my need for assistance from experts.  

Consequently, to restate the problem in its simplest terms, I need to access directly properties of a form of unknown type based only on the name of its type and the name of its property.  This has been covered by the answers so far for specific cases, but not for accessing a property of a property or for as yet unknown form types.  It is this latter part that is critical.

The pseudocode I provided earlier clearly shows what I need to achieve, here is an updated version:

  Type type = Type.GetType("MyNamespace." + formType);
  (type) fb = child as (type);
  if (fb.SomeProperty == SomeVariable)
    return true;

I am fully aware that this won't actually work (it is after all pseudocode), but how can it be changed so that it does??

Chris Bray.
0
 
LVL 6

Expert Comment

by:Bruce_1975
ID: 19576054
Ok, if I understand you right:
You want to get a specific object from a form through a property of a form.
The only thing you know: the form is a form and may have a property of a given name
This "known" property returns an object you hopefully know the type of.
If so, trys:
   PropertyInfo info = myForm.GetType().GetProperty( myPropertyName );
   MyKnownObject myObject = (MyKnownObject)info.GetValue(myForm, null);  

   than you can access the "properties property" through myObject.
If not, go through the PropertyInfo loop again. Hope that helps!
0
 
LVL 3

Author Comment

by:chrisbray
ID: 19576647
Hi Bruce,

Your restatement of the issue appears to be correct.  Unless I misunderstand your code

   PropertyInfo info = myForm.GetType().GetProperty( myPropertyName );

myForm is presumably the form name which is not known.  We only know the type and that is not known except as a string parameter.

Could we combine one of your earlier suggestions with this to get to something like:

foreach (Form child in this.MdiChildren)
{
   MyForm actualChild = child as MyForm;
   if (actualChild != null)
   {
    PropertyInfo info = actualChild.GetType().GetProperty( myPropertyName );  
    if (info.toString() == passed parameter)
      actualChild.BringToFront();
   }
}

I can't test it as I am nowhere near my development machine, but am I on the right lines?

Chris Bray.
0
 
LVL 6

Expert Comment

by:Bruce_1975
ID: 19576899
sure...
to find the right form just use:
foreach (Form child in this.MdiChildren)
{
   if (child.Name == mTheFormNameString)
   {
      ...
   }
}
0
 
LVL 96

Expert Comment

by:Bob Learned
ID: 19577133
Bruce,

You have got the read the entire question history to figure out that all that you have described has already been covered.

Bob
0
 
LVL 6

Expert Comment

by:Bruce_1975
ID: 19580569
Hi Bob,
there is a (very) smal difference in my foreach loop; I only use the Form class and not a Base class of the form.
I think, Chris has the problem, that he only knows taht the MDI childs are a form of some kind but doesn't anything about it except the base class Form.
So he has to step through all childs to find the one with the same name given by a string from somewhere else.
From this child he has to get a property given also by a string from somewhere else.
The Property returns a known object or returns an unknown object with a known property he wants to access.

I know, all I've written in this post is somewhere in the thread ;o)

Regards,
Bruce
0
 
LVL 3

Author Comment

by:chrisbray
ID: 19582708
Hi Bob and Bruce,

Thank you very much for all your help and advice so far.  I now have a fully working method of the main form which is fine as far as it goes:

        public bool FormExists(string formType, string formID, Int64 editId, bool bringToFront)
        {
            bool result = false;

            Type type = Type.GetType("MyNamespace." + formType);          

            foreach (Form child in this.MdiChildren)
            {
                if (child.GetType() == type)
                {
                    bool editIdMatch;
                    bool formIdMatch;
                    bool typeMatch = true;

                    // Check if the form ID matches
                    if (formID == string.Empty) // check not required
                        formIdMatch = true;
                    else  
                    {
                        PropertyInfo info = child.GetType().GetProperty("ListSchema");
                        ListSchema listSchema = (ListSchema)info.GetValue(child, new object[] { });
                        if (listSchema != null)
                        {
                            formIdMatch = (listSchema.FormId == formID);
                        }
                        else
                        {
                            formIdMatch = false;
                        }
                    }

                    // Check if the EditID matches
                    if (editId == -1) // check not required
                        editIdMatch = true;
                    else
                    {
                        PropertyInfo info = child.GetType().GetProperty("EditID");
                        Int64 id = (Int64)info.GetValue(child, new object[] { });
                        editIdMatch = (id == editId);
                    }

                    // set the result
                    result = (typeMatch && formIdMatch && editIdMatch);

                    // If required, bring the window to the front
                    if (result)
                    {
                        if (bringToFront)
                            child.BringToFront();
                        return result;
                    }
                }
            }
            return result;
        }

Criticism of the code and improvements will be welcome, but at least it works :-)

This enables me to do what I need to do for any form launched from the main form.  However, the list forms described earlier create their own Edit dialogs based on what has been set in the ListSchema property.  Consequently I need to move the method into a class on its own within the same project and allow all forms (or indeed other code) to call it if they need to check whether a specific form exists as a child of the MDI parent at any time.

However, when I attempt this although MainForm is visible in intellisense it will not let me access its MDIChildren property...

Can you help me with this?  I did not make this requirement entirely clear in the original question, so if you feel it warrants a new question I am happy to create one.

Chris Bray.
0
 
LVL 96

Accepted Solution

by:
Bob Learned earned 1500 total points
ID: 19583357
MDIChildren is an instance property of MainForm, so if you are trying to reference it through a static reference, it won't allow that, without an instance of MainForm.

Bob
0
 
LVL 3

Author Comment

by:chrisbray
ID: 19583762
Hi Bob,

Somehow, I have to be able to access the MDIChildren.  Forgive me going back to Delphi but there is where my experience lies...

In the original Delphi mockup of the app I have a unit (class) called Common which contains various routines which are commonly used.   In that unit it adds MainForm to the uses clause (basically the same as using in C#) and then I can simply call Mainform.whatever from within the unit.  To avoid circular references MainForm cannot have Common in its uses clause, but all the other forms can as long as they don't reference MainForm.  Therefore any interaction with MainForm by another form is done via the Common unit (class) to ensure that rule is not broken - methods are provided to access MainForm whenever necessary, for example for this FormExists method, or to tell it to reset its caption or re-evaluate the status of its menu items and disable any that should not currently be available following a modification on a child form.  

In the current C#' situation I now have a method that can actively check for the sort of form I have and optionally bring it to the front if it already exists, but I MUST be able to access it.  Let me give you an example:

MainForm instantiates a form of type ListForm, and passes it a ListSchema that tells it that it is to handle a list of Currencies.  From the ListSchema the ListForm knows that to edit any Currency it needs to use a form of type CurrencyDialog, and the CurrencyDialog knows how to select its data, edit, update etc.  However, it also needs to be able to prevent one currency from being edited twice in the current application instance.

Assuming we are going to edit the Euro currency, the process is this: In the MainForm the user clicks the Currencies menu item, and MainForm checks to see if that list is already open.  If so it brings it to the front, if not it creates and displays the list form with the relevant information loaded.  The user then selects the Euro currency from the list and clicks the Edit button.  The ListForm now needs to check to see if the Euro currency is already being displayed, but cannot do so because it cannot access the MDIChildren...

Whilst we could probably use the Parent property of the ListForm to get to an instance, unless we rewite the checking code in every form we would appear to have a problem and in any case we may need to access that check in code without actually having a form instantiated - for example if we are proposing to delete a record it would make sense to ensure that it is not currently being displayed otherwise we have orphan data that could be written back to the database (we are operating a disconnected architecture, and I would have data validation in place, but the point is that the user may have multiple windows open and not realise that they already have the relevant record on display and so may keep editing it after it has been deleted - you know what users are like!!).

So, this is absolutely critical stuff, and I have to find a way through it...  any suggestions?  Can we pass the MainForm or the MDIChildren by reference somehow so that it is available anywhere? If we need an instance can we pass an instance somehow?

Chris Bray
0
 
LVL 96

Expert Comment

by:Bob Learned
ID: 19591981
Well, if you can't enforce a base class, can you allow the developer to specify an interface when defining the form.  This interface would ensure that the form has the ListSchema property:

public interface IListForm
{
    ListSchema ListSchema;
}

public Form formChild : IListForm
{
}

IListForm test = (child as IListForm);

if (test != null)
{
   test.ListSchema...
}

Bob
0
 
LVL 3

Author Comment

by:chrisbray
ID: 19592185
Hi Bob,

That is no longer the problem.  With the code supplied we know not to send a query to the ListSchema if it doesn't exist, and have a check built in to resolve the problem if it is called in error.  That has resolved that issue, and as reported .before the code now works perfectly in all tested scenarios.

The only issue remaining (which is critical but does not necessarily need an answer to win the points) is to access the MDIChildren from outside the main form.  

Can you possibly help with that one at all?  I know you said that it is an instance property so can we pass that instance somewhere or create a static access point or otherwise ensure that I can make the check we have built from anywhere within the application?  My last response gives more detail if needed.

Chris Bray.
0
 
LVL 96

Expert Comment

by:Bob Learned
ID: 19592209
Can you show me where you are trying to access MdiChildren?

Bob
0
 
LVL 3

Author Comment

by:chrisbray
ID: 19637140
Hi Bob,

I have now resolved this issue, and it was dead easy once I got a handle on it - in fact one of my colleagues sorted it out in about five minutes once he was shown the issue!

In the class concerned, which I have named FormControl, add a property to hold a MainForm reference:

    class FormControl
    {
        private static Form mainForm;

        public static Form MainForm
        {
            get { return mainForm; }
            set { mainForm = value; }
        }
    }

In the main form constructor, create a reference to the property:

        public MainForm()
        {
            InitializeComponent();
            FormControl.MainForm = this;
        }

From there, all that was needed was to add the FormExists method I detailed above to the FormControl class, changing the references to this.MDIChildren to mainForm.MDIChildren. That then allows me to call the method from anywhere within the project like this:

            if (FormControl.FormExists("MyForm", "Form1", -1, true))
            {
                // do something if it exists
            }
            else
            {
                // do something else if it doesn't exist...
            }

I am very grateful for your efforts to help, and I propose to award you the points for all your efforts - even though your proposed solution was not exact it led to the current functioning solution.

Chris Bray.
0

Featured Post

[Webinar] Cloud and Mobile-First Strategy

Maybe you’ve fully adopted the cloud since the beginning. Or maybe you started with on-prem resources but are pursuing a “cloud and mobile first” strategy. Getting to that end state has its challenges. Discover how to build out a 100% cloud and mobile IT strategy in this webinar.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Calculating holidays and working days is a function that is often needed yet it is not one found within the Framework. This article presents one approach to building a working-day calculator for use in .NET.
Real-time is more about the business, not the technology. In day-to-day life, to make real-time decisions like buying or investing, business needs the latest information(e.g. Gold Rate/Stock Rate). Unlike traditional days, you need not wait for a fe…
This video shows how to quickly and easily deploy an email signature for all users in Office 365 and prevent it from being added to replies and forwards. (the resulting signature is applied on the server level in Exchange Online) The email signat…
Is your data getting by on basic protection measures? In today’s climate of debilitating malware and ransomware—like WannaCry—that may not be enough. You need to establish more than basics, like a recovery plan that protects both data and endpoints.…
Suggested Courses

872 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question