Laden...

Generisches WinForm, Typ T Abfragen

Erstellt von elTorito vor 8 Jahren Letzter Beitrag vor 8 Jahren 2.914 Views
elTorito Themenstarter:in
177 Beiträge seit 2009
vor 8 Jahren
Generisches WinForm, Typ T Abfragen

Hi,

ich hab ein Winform dem ich ein Typ übergebe.


public partial class MyGenericForm<T>  : Form where T: new()
{
}

MyGenericForm<Auto> mgf = new MyGenericForm<Auto>();
mgf.Show();


Ich möchte nun von diesem Form ein Event abonniern. Bevor diese Form Generisch war habe ich das wie folgt gemacht:


 var mf = this.ActiveMdiChild as MyForm;
if (mf != null)
{
    mf.bindingNavigatorAddNewItem_Click(sender, e);
}
else
{
    Form f = this.ActiveMdiChild;
}

Wie mache ich das nun mit dem Generischen Form?
Ich müsste ja erst einmal den Typ ermitteln.


System.Type constructed = this.ActiveMdiChild.GetType();

Gibt mir:
Type ist: MyGenericForm`1[Auto] zurück.

Hatte gedacht ich könnte das so machen:


if (constructed.IsGenericType)
{
    System.Type[] typeArguments = constructed.GetGenericArguments();
    var tParam = typeArguments[0];
    MyGenericForm<tParam> mfg = this.ActiveMdiChild as MyGenericForm<tParam>;
   mfg.bindingNavigatorAddNewItem_Click(sender, e);
}

Das funktioniert aber nicht.

Wie kann komme ich an den Typ und wie übergebe ich den richtig?

Danke
gruß
Peter

301 Beiträge seit 2009
vor 8 Jahren

Einfacher wäre es mit einem Interface


public interface IHasSpecialMethod
    {
        void SpecialMethod();
    }

Und dann Interface implementieren


public class MyForm : Form, IHasSpecialMethod
{ 
 // ... 
}

Dann brauchst du später nicht auf einen bestimmten generischen Typen casten sondern kannst einfach auf das Interface gehen.


var myFormWithInterface = this.ActiveMdiChild as IHasSpecialEvent;
myFormWithInterface.SpecialMethod();

elTorito Themenstarter:in
177 Beiträge seit 2009
vor 8 Jahren

Hallo KroaX,

danke für die Antwort.

gefällt mir die Lösung mit dem Interface.
Sieht viel sauberer aus 👍

was man nicht so alles mit Interfaces anstellen kann ...

2.298 Beiträge seit 2010
vor 8 Jahren

Ich möchte nun von diesem Form ein Event abonniern. Bevor diese Form Generisch war habe ich das wie folgt gemacht:

  
 var mf = this.ActiveMdiChild as MyForm;  
if (mf != null)  
{  
    mf.bindingNavigatorAddNewItem_Click(sender, e);  
}  
else  
{  
    Form f = this.ActiveMdiChild;  
}  
  

Tust du aber nicht, du rufst nur einen EventHandler direkt auf.

Wie mache ich das nun mit dem Generischen Form?
Ich müsste ja erst einmal den Typ ermitteln.

Naja, eigentlich brauchst du den exakten Typ nicht wissen. - Das Event wird ja von der Form-Klasse bereitgestellt (MyForm<T>). Insofern solltest du eigentlich alle Events etc. aufrufen können ohne den konkreten Typ von <T> zu wissen.

Ich habe das auch gerade mal probiert und es funktioniert super. Einziges was anschließend nicht so einfach ist, wäre der Rückschluss auf den sender.

Der Weg über das Interface ist natürlich noch einen Schritt besser. 👍

Wissen ist nicht alles. Man muss es auch anwenden können.

PS Fritz!Box API - TR-064 Schnittstelle | PS EventLogManager |

elTorito Themenstarter:in
177 Beiträge seit 2009
vor 8 Jahren

Hi,

vielleicht könnt Ihr mir noch einmal helfen...

Ich hatte vor der "Genericsalisierung" ein Form so aufgerufen:


public ProjectForm :MyForm {}

string frmName = "Namespace.irgendwas.Projectform"
MyForm frm = (MyForm)(Assembly.GetExecutingAssembly().CreateInstance(frmName));
frm.Show();


Nun möchte ich anhand eines String mein Generisches Form aufrufen, aber ich verzweifle dran 😦
Wahrscheinlich ist es ganz einfach, oder mann kann es sogar wieder über ein interface lösen? 😃

Ich hatte da an soetwas in die richtung gedacht:


System.Type myType = FindType("Project");
MyGenericForm<myType>() = new MyGenericForm<myType>()  ;

Das funktioniert (natürlich) nicht ...

Hab dann auch über Interface versucht, aber irgendwie scheitere ich ständig daran dass ich T nicht "dynamisch" erzeugen kann.

Vielleicht hat noch einer eine Idee und ich hab Tomaten auf den Augen.

Danke

5.657 Beiträge seit 2006
vor 8 Jahren
  
MyForm frm = (MyForm)(Assembly.GetExecutingAssembly().CreateInstance(frmName));  

Wenn der Typ sowieso in der aktuellen Assembly liegt, wozu benötigst du dann Reflection?
Was sind denn überhaupt deine Anforderungen? Willst du ein Plugin-System entwickeln? Warum liegt der Name des Typs überhaupt als Zeichenkette vor?

Christian

Weeks of programming can save you hours of planning

elTorito Themenstarter:in
177 Beiträge seit 2009
vor 8 Jahren

Hi Christian,

die Anforderung ist:

Ich hab eine TreeNode Liste, ein TreeNode beherbergt den Name (string, Liste wird aus einer DB eingelesen) eines Form, dass bei Click aufgerufen werden soll. Da ich bisher für jeden Node ein eigenes Form hatte, welches von MyForm abgeleitet wurde. Habe ich dann anhand des String das Form geholt und es gecastet...

Der Node string sah vorher so aus: "namespace.elTorito.WindowsGUI.frmProject"

Hatte erst gedacht ich könnte einfach den String ändern in :

"namespace.elTorito.WindowsGUI.MyGenericForm<Project>"

Aber da bekam ich nur null zurück von :


Type newType = System.Type.GetType(frmName, true, true);

Also Click auf ein treeNode soll das GenerischeForm öffnen mit dem passenden Typ/Objekt (welches dem TreeNode in welcher Art und weise auch imemr hinterlegt ist)

Hinweis von MrSparkle vor 8 Jahren

Fullquote entfernt.

Bitte beachte [Hinweis] Wie poste ich richtig?, Punkt 2.3

5.657 Beiträge seit 2006
vor 8 Jahren

ein TreeNode beherbergt den Name (string, Liste wird aus einer DB eingelesen) eines Form, dass bei Click aufgerufen werden soll

Wenn der Name des Typs einer View in der Datenbank steht, erscheint mir das als eine schwere Mißachtung der Schichtentrennung, mit der du dir erst solche Probleme überhaupt erst einhandelst. Siehe dazu [Artikel] Drei-Schichten-Architektur.

Um eine bestimmte View für einen bestimmten Typ auswählen zu können, gibt es unter WPF das DataTemplate. Unter WinForms gibt es sicherlich auch eine Lösung, die besser ist, als dein bisheriger Ansatz. Zur Not kann man das ja in ein paar Zeilen manuell mappen:


Form form;
if (node is ClassA)
  form = new ClassAForm(node as ClassA);
else if (node is ClassB)
  form = new ClassBForm(node as ClassB);
// etc.

Christian

Weeks of programming can save you hours of planning

elTorito Themenstarter:in
177 Beiträge seit 2009
vor 8 Jahren

hi Christian,

also der Ablauf ist wie folgt:

Am Anfang ist der TreeList leer, der Benutzer kann sich neue Nodes anlegen, beim anlegen eines Nodes, kann der Benutzer ein Name wählen, und aus einer DropDown Box ein WinForm. In Dieser DropDown Box wurden alle vorhandenen Formulare gelistet welche von MyForms geerbt hatten.

Gespeichert wurde dann der Name der Form.


private static List<string> GetFormsInCurrentAssemly()
        {
            List<string> formList = new List<string>(new string[] { "" });
            // Current Assembly
            Console.WriteLine("========== Forms in Current Assembly ===================================");
            Assembly SampleAssembly = Assembly.GetExecutingAssembly();
            System.Type[] ts = SampleAssembly.GetTypes();
            string text = "";
            for (int i = 0; i < ts.Length; i++)
            {
                if (ts[i].IsSubclassOf(typeof(MyForm)) || (ts[i]).GetType() == typeof(MyForm))
                {
                    formList.Add(ts[i].FullName);
                }
            }
            return formList;
        }

Das hat ganz gut funktioniert, ich hatte viele Forms:


public class ProjectForm:MyForm{}
public class CountryForm:MyForm{}
public class IgrendwasForm:MyForm{}
...

Mit Click auf ein Node:


System.Diagnostics.Debug.WriteLine("Node Mouse Click ");
Node node = (Node)(e.Node.Tag);
if ((node.Form != null) && (e.Node.GetNodeCount(true) == 0))
{
this.BeginInvoke(new Action(() => openOrActivateForm(node.Form))); //Node.Form is string
}

Also ich hab in der Datenbank Nodes gespeichert, die Liste lese ich ein, und erstelle anhand derer ein TreeView.

Da alle Forms das gleiche machen, aber nur Unterschiedliche Objekte behandeln, habe ich diese nun reduziert auf ein MyGenericForm<T>.

Der Benutzer soll halt frei wählen können welches Form er mit dem Click auf den Node öffnen kann. Und diese Angabe soll in der DB gespeichert werden.

Aber ich weiß halt nicht wie ich nun das MyGenericsForm<T> initialisiere bzw. öffne, weil T ist ja nicht bekannt, ich habe nur T als string vorliegen.

4.930 Beiträge seit 2008
vor 8 Jahren

Hallo elTorito,

lies dir mal Gewusst wie: Untersuchen und Instanziieren von generischen Typen mit Reflektion (besonders den 2. Teil) durch - Stichworte: MakeGenericType und Activator.CreateInstance.

Wobei es noch besser wäre, du würdest gleich in der Liste die Typen (anstatt den Namen) speichern, d.h. List<Type> - zur Anzeige und Speicherung in der DB kannst du ja dessen FullName verwenden.

5.657 Beiträge seit 2006
vor 8 Jahren

Hi elTorito,

deine ganze Herangehensweise ist reines Gefrickel, das dir auf Dauer nur neue Probleme bringen wird. Warum das keine gute Idee ist, sollte eigentlich aus dem von mir verlinkten Artikel hervorgehen. Wenn du den Namen eines Typs in die DB schreibst, ist das einfach nur ein Zeichen für schlechte Software-Architektur. Anstatt mit Reflection und sowas zu arbeiten, solltest du lieber erstmal damit beginnen, ein geeignetes Datenmodell zu entwerfen, das deine Anforderungen abbildet. Da kannst du dir ruhig auch erstmal anschauen, wie andere soetwas gelöst haben, du bist ja schließlich nicht der erste auf der Welt, der solche Anforderungen umsetzt.

Christian

Weeks of programming can save you hours of planning

elTorito Themenstarter:in
177 Beiträge seit 2009
vor 8 Jahren

Hi Christian,

okay, ist ein Gefrickel, dem stimme ich irgendwo zu, sonst hätte ich wohl schon was ich möchte...

ich hab nun den gestrigen Tag damit verbracht zu suchen wie es andere wohl machen würde, entweder suche ich falsch, oder ich habe eine falsche Vorstellung wie es umgesetzt werden müsste bzw. könnte.

Über Reflection aber bin ich immer und immer wieder gestolpert.

Ich versuche nochmal die Anforderung zu spezifizieren, evtl. kann ich einen anderen Weg einschlagen.

In der Datenbank gibt es eine Tabelle TreeNodes, welche die Knoten für den TreeView speichert.
Ich habe ein Object MyNode, es wird eine Liste von myNodes eingelesen, und der TreeView anhand derer erstellt, wobei

TreeView.Node.Tag = MyNode ist

MyNode soll irgendeine Eigenschaft besitzten anhand derer ich ein bestimmtes Form identifizieren kann, so dass das Form beim MausKlick auf den TreeView Node instanziiert /aufgerufen, irgendwas mit gemacht wird.

Jetzt habe ich ein Form erstellt welches BasisFunktionen enthält für die Behandlung diverser Objekte, eine Liste von Objekten wird in einer BindingList eingelesen, und in einem DGV angezeigt, mögliche Funktionen sind: CRUD , so wie Übergabe von Feld Inhalten/Texte in eine Status Anzeige...

Die Funktionen sind für alle Objekte gleich, der Unterschied liegt immer nur im Objekt. Deswegen kam ich auf die Idee ein MyGenricForm<T> zu erstellen, wo T mein zu behandelnes Objekt sein soll.

Evtl. ist dass auch nicht der richtige Weg, und es würde reichen wenn ich ein normales Form habe, wo ich ein Object übergebe statt das Form mit ein unbekannten Typ initialisiere (ähnlich hier (aber auch mit Reflection) .

Weitere suche ergab auch "winform via object factory" (auch mit reflection)

Was die Schichten Trennung angeht, so denke ich, habe ich meine Schichten/Daten Sauber getrennt. Aufbau ist bei mir derzeit:

Client (GUi, SharedBL, ServiceAccess)
Server/Service (BL, UOW, EF)
Sql DB

Also, ich seh irgendwie kein Weg der an Reflection vorbeigeht, bevor ich nun weiter versuche würde ich aber gerne ein Gedankenstoß bekommen der in einer andere Richtung geht. Evtl. auch ein Hinweis/Verweis wie andere so etwas gelöst haben.

Danke

gruß
Peter

H
114 Beiträge seit 2007
vor 8 Jahren

MyNode soll irgendeine Eigenschaft besitzten anhand derer ich ein bestimmtes Form identifizieren kann, so dass das Form beim MausKlick auf den TreeView Node instanziiert /aufgerufen, irgendwas mit gemacht wird.

Ich würde an dieser Stelle einfach auf DependencyInjection bzw. das MEF zurückgreifen. Alle Elemente, die angezeigt werden müssen, werden mittels gemeinsamer generischer Schnittstelle abgebildet, im einfachsten Fall sowas wie

public interface INode
{
	void Show();
}

Die Implementierungen können nun relativ einfach von deiner Basisklasse ableiten

[NodeMetadata("NodeA")]
public class NodeAForm : MyGenericForm<AObject>, INode
{
	public void Show()
	{
		//...
	}
}

und via Attributen bzw. Metadaten kann deklariert werden, für welchen Node sie zuständig sind.

Nun kann man sich via MEF alle Implementierungen einer Schnittstelle (z.B. INode) holen, die Metadaten auswerten und so die zu einem Node passende Implementierung ermitteln.
Nun einfach Show() an dieser Implementierung bzw. Instanz aufrufen und voila...Ohne Reflection, switch case oder sonstigem bekommt man die zu einem Node passende Form angezeigt. Und erweiterbar ist das ganze auch sehr einfach, da man für jeden neuen eindeutigen Wert einfach eine neue Implementierung der Schnittstelle erstellt.

Mir ist durchaus bewusst, dass meine Erklärung sehr komprimiert ist und evtl. nicht alle angesprochenen Technologien und Vorgehen bekannt sind, aber mit etwas Eigeninitiative sollte sich zu allem was finden lassen und bei konkreten Fragen einfach wieder hier im Forum nachfragen 😉

Grüße, HiGHteK

5.657 Beiträge seit 2006
vor 8 Jahren

Hi elTorito,

MyNode soll irgendeine Eigenschaft besitzten anhand derer ich ein bestimmtes Form identifizieren kann

Eine Enum würde dafür völlig ausreichen.

Christian

Weeks of programming can save you hours of planning

elTorito Themenstarter:in
177 Beiträge seit 2009
vor 8 Jahren

Hi,

danke für eure Antworten. 👍

Nun kann man sich via MEF alle Implementierungen einer Schnittstelle (z.B. INode) holen, die Metadaten auswerten und so die zu einem Node passende Implementierung ermitteln.

MEF hatte ich mal gehört, wusste nicht genau was man damit anstellt, habe hier ein ganz Verständlichen Einstieg gefunden.

Finde ich interessant, vielleicht nehme ich damit doch wieder meine UrsprungsIdee Plugins mit ins Boot.

Ich hab mein problem nun auch mit dem MEF gelöst. Irgendwie ne tolle Sache 🙂


public interface INodeContract
{
    void Show();
}

public interface INodeMetadata
{
    [DefaultValue("NoName")]
    string Name { get; }
}

[MetadataAttribute]
public class NodeMetadataAttribute : Attribute
{
    public string Name { get; set; }
}

[NodeMetadata(Name = "Project"]
[Export(typeof(INodeContract))]
public partial class ProjectForm : MyGenericForm<Project>, INodeContract, IHasSpecialMethod
}
[NodeMetadata(Name = "Country"]
[Export(typeof(INodeContract))]
public partial class CountryForm : MyGenericForm<Country>, INodeContract, IHasSpecialMethod
}

public partial class MyGenericForm<T> : Form, INodeContract, IHasSpecialMethod where T : new()
{
    InitializeComponent();    
    System.Diagnostics.Debug.WriteLine(" T in MyGenericForm is : " + typeof(T));
}

Im TreeNode Click dann so was wie:


[ImportMany(typeof(INodeContract))]
private IEnumerable<Lazy<INodeContract, INodeMetadata>> MyNodes{ get; set; }

void OpenOrActivateForm(string objectMetaName)
{
    var container = new CompositionContainer(catalog);
    container.ComposeParts(this);

    var formList = from x in MyNodes
                              let metadata = x.Metadata
                              where metadata.Name == objectName
                              select x.Value;

    foreach (INodeContract nc in formList )
    {
        nc.Show();
    }
}

Ausbaufähig, aber denke kann ich was mit anfangen.

Vielen Dank nochmal.