none
OCX dynamisch erstellen, Properties setzen vor Darstellung

    Frage

  • Folgendes Problem:

    In einem C#-Programm erzeuge ich dynamisch Instanzen von OCX-Controls (C++). 

    Was ich nun gern möchte ist, daß ich nach Erstellung der Instanz, aber noch VOR der ersten Darstellung auf einem Form einige Properties setzen kann. Mein Problem daran ist folgendes:

    nach dem von

    AxPointerInstrumentLib.AxPointerInstrument newControl = new AxPointerInstrumentLib.AxPointerInstrument();
    

    habe ich eine Instanz des Controls erstellt. Sämtliche Aufrufe zu Properties werfen dann eine InvalidActiveXStateException:

    Message = "Eine Ausnahme vom Typ \"System.Windows.Forms.AxHost+InvalidActiveXStateException\" wurde ausgelöst."

    was soweit noch in der MSDN entsprechend dokumentiert ist. Es gibt eben noch kein Fenster und kein Handle auf das Control usw.

    Abhilfe soll dann CreateControl schaffen.

    Nach/bei Aufruf von 

    newControl.CreateControl();
    

    passiert allerdings folgendes

    Beim Aufruf fängt der Debugger eine System.NullReferenceException. Stacktrace:

      bei System.Windows.Forms.AxHost.OleInterfaces.System.Windows.Forms.UnsafeNativeMethods.IOleInPlaceSite.GetWindowContext(IOleInPlaceFrame& ppFrame, IOleInPlaceUIWindow& ppDoc, COMRECT lprcPosRect, COMRECT lprcClipRect, tagOIFI lpFrameInfo)

    ich kann nach er Exception das Programm aber problemlos mit F5 oder F10 fortsetzen und das setzen der Properties funktioniert dann tadellos. Was habe ich also mit der Exception anzufangen und was ist dagegen zu tun bzw. wie macht mans denn richtig?

    weitere Anmerkungen:

    - selbst, wenn ich CreateControl in einen try-catch-block packe, fängt sie der debugger, der catch-block wird nicht angesprungen

    - beim Ausführen ohne debugger (die exe starten oder strg-f5) stürzt nichts ab und alles funktioniert wie erwartet

    - beim Ausrühren von einem c++-Projekt aus (Debuggen des native-OCX-Controls) stürzt zwar nichts ab, als Debug-Ausgabe erhalte ich allerdings

    First-chance exception at 0x6a06ac62 in PointerInstrumentTestNet.exe: 0xC0000005: Access violation reading location 0x00000000.
    First-chance exception at 0x7746fc16 (kernel32.dll) in PointerInstrumentTestNet.exe: 0xE0434352: 0xe0434352.

    fängt man die Exception, gibts als Stacktrace dann

    clr.dll!_COMToCLRDispatchHelper@28() + 0x28 bytes
    clr.dll!BaseWrapper<Stub *,FunctionBase<Stub *,&DoNothing<Stub *>,&StubRelease<Stub>,2>,0,&CompareDefault<Stub *>,2>::~BaseWrapper<Stub *,FunctionBase<Stub *,&DoNothing<Stub *>,&StubRelease<Stub>,2>,0,&CompareDefault<Stub *>,2>() + 0xe40e3 bytes
    clr.dll!COMToCLRWorkerBody() + 0x80 bytes
    clr.dll!COMToCLRWorkerDebuggerWrapper() + 0x34 bytes
    clr.dll!_COMToCLRWorker@8() + 0x12b bytes

    - wenn ich auf der c#-Seite das Control per Parent-Property oder per Controls.Add irgendwo in ein Form tue, kann ich danach auch auf alle Properties zugreifen, aber wie gesagt: ich möchte ZUERST die Properties setzen und das Control dann darstellen

    micha

    Donnerstag, 10. Oktober 2013 12:26

Antworten

  • Hi,

    Das Herzstück von Control.CreateControl() ist der Aufruf von Control.CreateHandle() - eine recht einfache Sache wenn's nicht um ein ActiveX-Control geht.

    Der verwaltete Wrapper des ActiveX-Controls (z.B. AxWindowsMediaPlayer1) wird von der Klasse AxHost abgeleitet, die wiederum von Control abgeleitet ist (und ISupportInitialize, ICustomTypeDescriptor implementiert). Dieser verwaltete Wrapper hat ein override auf Control.CreateHandle(). In diesem Override steht dann als letzte Zeile:

    this.GetParentContainer().ControlCreated(this);

    Ich frage dich: Wie soll GetParentContainer() Erfolg haben, wenn das Wrapperobjekt noch kein Parent hat? Das kann so nicht funktionieren. Solange kein Parent da ist, befindet sich das ActiveX-Steuerelement in einem ungültigen Zustand.

    Die Sequenz die tut, sieht so aus (Pseudocode):

    ax.BeginInit();
    ax.Parent=this;
    //ax.Visible=false;
    //ax.CustomProperty=value;
    //ax.Visible = true;

    Sonst musst Du dein Glück mit OcxState versuchen, wie oben verlinkt. Sorry, was anderes fällt mir im Moment nicht mehr ein.

    Gruß
    Marcel

    • Als Antwort markiert suriel6666 Montag, 14. Oktober 2013 07:59
    Freitag, 11. Oktober 2013 20:17

Alle Antworten

  • Hi Micha,

    Mach mal folgendes Experiment: Was passiert denn, wenn Du das OCX-Steuerelement auf den Windows Forms Designer ziehst und anschließend in dem vom Designer in InitializeComponent() geschriebenen Code die Eigenschaften des Controls in der von dir gewünschten Reihenfolge setzt?

    Wenn das problemlos funktioniert, dann liegt dein Problem wahrscheinlich darin dass Du nicht zuerst - wie der Designer - ISupportInitialize.BeginInit() aufrufst.

    Schau dir genau an, was der Designer macht. Beim Aufruf von BeginInit() erst wird - vereinfacht gesprochen - die Verbindung zwischen dem generierten verwalteten Wrapperobjekt und dem nativen ActiveX-Objekt hergestellt, vorher kann das Setzen von Properties nicht erfolgreich geschehen. 

    Hier ist ein Beispiel von einem dynamisch erstellten AxWindowsMediaPlayer-Control:

    using System;
    using System.Windows.Forms;
    
    namespace OcxSpike
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
            }
    
            AxWMPLib.AxWindowsMediaPlayer axWindowsMediaPlayer1; 
    
            private void Form1_Load(object sender, EventArgs e)
            {
                System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Form1));
                this.axWindowsMediaPlayer1 = new AxWMPLib.AxWindowsMediaPlayer();
                ((System.ComponentModel.ISupportInitialize)(this.axWindowsMediaPlayer1)).BeginInit();
                this.SuspendLayout();
                this.axWindowsMediaPlayer1.Enabled = true;
                this.axWindowsMediaPlayer1.Location = new System.Drawing.Point(49, 40);
                this.axWindowsMediaPlayer1.Name = "axWindowsMediaPlayer1";
                this.axWindowsMediaPlayer1.Size = new System.Drawing.Size(185, 148);
                this.axWindowsMediaPlayer1.TabIndex = 0;
    
                this.Controls.Add(this.axWindowsMediaPlayer1);
    ((System.ComponentModel.ISupportInitialize)(this.axWindowsMediaPlayer1)).EndInit(); this.ResumeLayout(false); } } }

    Gruß

    Marcel

    Donnerstag, 10. Oktober 2013 17:37
  • > Mach mal folgendes Experiment: Was passiert denn, wenn Du das OCX-Steuerelement auf den Windows Forms Designer ziehst und anschließend in dem vom Designer in InitializeComponent() geschriebenen Code die Eigenschaften des Controls in der von dir gewünschten Reihenfolge setzt?

    Dann passiert genau das gleiche. bis zu  this.Controls.Add(meinControl) werfen sämtliche Getter eine Exception.

    Der Designer selbst ist da sehr viel gerissener unterwegs: der setzt garnicht direkt irgendwelche Properties sondern nur das Feld OcxState, das wiederum sämtliche Propertywerte in einer serialisierten Form enthält. beim tatsächlichen erzeugen des native Controls (wenn es per Controls.Add dem Formular zugeordnet wird) werden die dann wohl zurückgelesen und am Control gesetzt.

    > Wenn das problemlos funktioniert, dann liegt dein Problem wahrscheinlich darin dass Du nicht zuerst - wie der Designer - ISupportInitialize.BeginInit() aufrufst.

    Nein, daran liegt es nicht. auch nach BeginInit und EndInit sind die Properties nicht zugreifbar. das geschieht erst, nach Controls.Add

     > Schau dir genau an, was der Designer macht. Beim Aufruf von BeginInit() erst wird - vereinfacht gesprochen - die Verbindung zwischen dem generierten verwalteten Wrapperobjekt und dem  

    Nein, eben nicht. Ctrols. Add tut die Zauberei, nicht das BeginInit. Das sorgt m.A. lediglich dafür, daß zwischen Begin und End nichts gezeichnet wird.

    micha

    PS: Sorry für die Optik meines Postings. ich bin schon froh, wenn ich hier in diesem Webcontrol überhaupt irgendwas geschrieben bekomme. hier ist leider fast jeder Tastendruck ein Glücksspiel, da versuche ich, möglichst auf Formatierungen zu verzichten.

    Freitag, 11. Oktober 2013 10:51
  • Hi Micha,

    das mit BeginInit() ist wirklich nötig (wenn Du willst kann ich die Dokumentationsstelle heraussuchen), es kann aber sein, dass Du noch this.axWindowsMediaPlayer1.Parent = this setzen musst, bevor Du auf Properties zugreifen kannst. Am besten noch in OnLoad oder HandleCreated, wo ein Fensterhandle für die Form bereits existiert.

    using System;
    using System.Windows.Forms;
    
    namespace OcxSpike
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
            }
    
            AxWMPLib.AxWindowsMediaPlayer axWindowsMediaPlayer1;
    
            private void Form1_Load(object sender, EventArgs e)
            {
                System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Form1));
                this.axWindowsMediaPlayer1 = new AxWMPLib.AxWindowsMediaPlayer();
                ((System.ComponentModel.ISupportInitialize)(this.axWindowsMediaPlayer1)).BeginInit();
                this.SuspendLayout();
                this.axWindowsMediaPlayer1.Enabled = true;
                this.axWindowsMediaPlayer1.Location = new System.Drawing.Point(49, 40);
                this.axWindowsMediaPlayer1.Name = "axWindowsMediaPlayer1";
                this.axWindowsMediaPlayer1.OcxState = ((System.Windows.Forms.AxHost.State)(resources.GetObject("axWindowsMediaPlayer1.OcxState")));
                this.axWindowsMediaPlayer1.Size = new System.Drawing.Size(185, 148);
                this.axWindowsMediaPlayer1.TabIndex = 0;
    
                ((System.ComponentModel.ISupportInitialize)(this.axWindowsMediaPlayer1)).EndInit();
                this.ResumeLayout(false);
    
                // Property setzen (N.B. funktioniert vor Aufruf von Controls.Add())
                this.axWindowsMediaPlayer1.Parent = this;
                this.axWindowsMediaPlayer1.settings.autoStart = false;
    
                // Zur Controlsauflistung hinzufügen
                this.Controls.Add(this.axWindowsMediaPlayer1);
            }
        }
    }

    Gruß
    Marcel



    Freitag, 11. Oktober 2013 12:33
  • > heraussuchen), es kann aber sein, dass Du noch this.axWindowsMediaPlayer1.Parent = this setzen musst,

    Ja, aber genau DAS war es ja, was ich nicht möchte. Ich möchte das Control gern erzeugen und die benötigten Properties zur Darstellung setzen, BEVOR es irgendeinem Parent zugeordnet oder dargestellt wird.

    Der Plan ist eben, das Control mit seinen initialen Werten zu setzen, unabhängig davon, wo es mal dargestellt werden soll. Das ist da noch gar nicht relevant.


    Freitag, 11. Oktober 2013 13:14
  • Du musst ja das OCX-Control nicht anzeigen. Du musst nur die Parent-Property setzen, damit Du der InvalidActiveXStateException aus dem Weg gehst. Wenn Du einen Wrapper um das Control erzeugst, dürfte das keine große Sache sein. Aber warum verwendest Du nicht einfach ein UserControl in dem Du das OCX-Control über den Designer ziehen kannst und auch entsprechend initialisieren kannst, egal wo das UserControl dann verwendet wird? Wenn Du auch das nicht möchtest, dann könntest Du den OcxState manipulieren, Code dazu findest Du hier.

    Freitag, 11. Oktober 2013 13:28
  • > Du musst ja das OCX-Control nicht anzeigen. Du musst nur die Parent-Property setzen

    Und dadurch würde es nicht auf dem Parent angezeigt werden? Davon abgesehen ist es eben so, daß ich zur Initialisierungszeit den Parent selbst noch gar nicht kenne. Ich will dem aufrufer ein vorkonfiguriertes Control geben und der tut das dann... wo auch immer hin...

    Klar kann ich einen Wrapper schreiben, nur braucht der Aufrufer, um an das eigentlich Control zu kommen, noch jedesmal einen zusätzlich Propertyaufruf, der das Control zurückliefert (die über 60 Methoden selbst zu wrappen, ist sicher keine gute Idee)

    > Aber warum verwendest Du nicht einfach ein UserControl i

    Sicher ginge das technisch auch. Hat nur den Haken, daß typeof mir dann UserControl zurückliefert... oder wieder das oben Erwähnte get-property für das gewrappte Control her muß, auf das der Aufrufer ständig zugreift

    Was ist eigentlich mit CreateControl? Die Dokumentation klingt für mich so, als wäre die Methode eigentlich genau für meine Zweck da (das native Ocx und alle Handles erzeugen), oder nicht?


    Freitag, 11. Oktober 2013 16:58
  • Hi,

    Das Herzstück von Control.CreateControl() ist der Aufruf von Control.CreateHandle() - eine recht einfache Sache wenn's nicht um ein ActiveX-Control geht.

    Der verwaltete Wrapper des ActiveX-Controls (z.B. AxWindowsMediaPlayer1) wird von der Klasse AxHost abgeleitet, die wiederum von Control abgeleitet ist (und ISupportInitialize, ICustomTypeDescriptor implementiert). Dieser verwaltete Wrapper hat ein override auf Control.CreateHandle(). In diesem Override steht dann als letzte Zeile:

    this.GetParentContainer().ControlCreated(this);

    Ich frage dich: Wie soll GetParentContainer() Erfolg haben, wenn das Wrapperobjekt noch kein Parent hat? Das kann so nicht funktionieren. Solange kein Parent da ist, befindet sich das ActiveX-Steuerelement in einem ungültigen Zustand.

    Die Sequenz die tut, sieht so aus (Pseudocode):

    ax.BeginInit();
    ax.Parent=this;
    //ax.Visible=false;
    //ax.CustomProperty=value;
    //ax.Visible = true;

    Sonst musst Du dein Glück mit OcxState versuchen, wie oben verlinkt. Sorry, was anderes fällt mir im Moment nicht mehr ein.

    Gruß
    Marcel

    • Als Antwort markiert suriel6666 Montag, 14. Oktober 2013 07:59
    Freitag, 11. Oktober 2013 20:17
  • > Solange kein Parent da ist, befindet sich das ActiveX-Steuerelement in einem ungültigen Zustand

    das ist dann wohl die antwort. schade eigentlich

    Montag, 14. Oktober 2013 07:59