Dictionary übergeben ByRef Problem

  • Hi,
    ich hab mal einen Auszug aus meinem Projekt gemacht um das Problem anschaulich zu machen:

    [autoit]

    #include <GUIConstantsEx.au3>
    #include <WindowsConstants.au3>
    #include <GuiListView.au3>
    #include <String.au3>

    [/autoit][autoit][/autoit][autoit]

    Opt("GUIOnEventMode", 1) ; enable OnEvent functions notifications
    Global $oOnEventDict = ObjCreate("Scripting.Dictionary")

    [/autoit][autoit][/autoit][autoit][/autoit][autoit]

    Example()

    [/autoit][autoit][/autoit][autoit]

    Func Example()
    Local $WimData = ObjCreate("Scripting.Dictionary")
    Local $comDict = ObjCreate("Scripting.Dictionary")
    Local $aArray = [$comDict, $WimData]

    [/autoit][autoit][/autoit][autoit]

    ; Create a GUI with various controls.
    Local $hGUI = GUICreate("Example")
    Local $iOK = GUICtrlCreateButton("OK", 310, 370, 85, 25)
    Local $iset = GUICtrlCreateButton("Set", 210, 370, 85, 25)
    Local $ishow = GUICtrlCreateButton("Show", 110, 370, 85, 25)

    [/autoit][autoit][/autoit][autoit]

    ; set on event
    GUICtrlSetOnEventEx($iOK, "SetComDict", $comDict)
    GUICtrlSetOnEventEx($iset, "SetWimData", $aArray, 1)
    GUICtrlSetOnEventEx($ishow, "_ArrayListDisplay", $WimData)

    [/autoit][autoit][/autoit][autoit]

    ; Display the GUI.
    GUISetState(@SW_SHOW, $hGUI)

    [/autoit][autoit][/autoit][autoit]

    ; Loop until the user exits.
    While 1
    Switch GUIGetMsg()
    Case $GUI_EVENT_CLOSE
    ExitLoop

    [/autoit][autoit][/autoit][autoit]

    EndSwitch
    WEnd

    [/autoit][autoit][/autoit][autoit]

    ; Delete the previous GUI and all controls.
    GUIDelete($hGUI)
    EndFunc ;==>Example

    [/autoit][autoit][/autoit][autoit][/autoit][autoit]

    Func SetWimData(ByRef $comDict, ByRef $WimData)
    _ArrayListDisplay($comDict)
    _ArrayListDisplay($WimData)
    ;~ $WimData = $comDict ; Warum funktioniert das so nicht?

    [/autoit][autoit][/autoit][autoit][/autoit][autoit]

    For $k in $comDict.Keys
    $WimData.add($k, $comDict($k)) ; Warum funktioniert es so?
    Next

    [/autoit][autoit][/autoit][autoit]

    _ArrayListDisplay($WimData)
    EndFunc

    [/autoit][autoit][/autoit][autoit][/autoit][autoit]

    Func SetComDict(ByRef $comDict)
    $comDict.add("1", "Hier1")
    $comDict.add("2", "Hier2")

    [/autoit][autoit][/autoit][autoit]

    Local $newDict = ObjCreate("Scripting.Dictionary")
    $newDict.add("10", "test10")
    $newDict.add("11", "test11")

    [/autoit][autoit][/autoit][autoit]

    $comDict.add("Dict", $newDict)
    EndFunc

    [/autoit][autoit][/autoit][autoit][/autoit][autoit]

    ; helper function for debug
    Func _ArrayListDisplay(ByRef $ArrayList, $isDict = 0, $hParent = 0)
    If Not IsObj($ArrayList) Then Return MsgBox(64, "Object Error", "No object to process.") ; verify object
    If $isDict = 1 And Not IsArray($ArrayList.Keys) Then Return MsgBox(64, "Dictionary Error", "Dictionary is empty.") ; verify keys
    Local $Opt_OnEventMode, $counter = 1
    If $hParent Then
    $Opt_OnEventMode = Opt("GUIOnEventMode", 1)
    Else
    $Opt_OnEventMode = Opt("GUIOnEventMode", 0)
    EndIf
    Opt("GUICoordMode", 1) ; absolute position
    Local $GUI = GUICreate("ArrayList", 572, 536, -1, -1, -1, -1, $hParent)
    Local $ListView = GUICtrlCreateListView("ID|Element", 8, 8, 553, 536-8*2)

    [/autoit][autoit][/autoit][autoit]

    ; if object is a dict, read elements and values
    If $isDict = 1 And $hParent Then
    _GUICtrlListView_AddColumn($ListView, "Value")
    ControlMove($GUI, "", $ListView, 8, 8, 553, 481)
    Local $showBtn = GUICtrlCreateButton("Display", 8, 536-30-8, 60, 30)
    Local $test = [$ArrayList, $ListView, $GUI]
    GUICtrlSetOnEventEx($showBtn, "_DataDisplay", $test, 1)
    For $key In $ArrayList.Keys
    Local $val = $ArrayList($key) ; value
    GUICtrlCreateListViewItem($counter & '|' & $key & '|' & $val, $ListView)
    $counter += 1
    Next
    ; if object is list, read elements only
    Else
    For $e In $ArrayList
    GUICtrlCreateListViewItem($counter & '|' & $e, $ListView)
    $counter += 1
    Next
    EndIf
    GUISetState(@SW_SHOW)
    If Not $hParent Then
    Do
    Switch GUIGetMsg()
    Case $GUI_EVENT_CLOSE
    ExitLoop
    EndSwitch
    Until 0
    GUIDelete($GUI)
    EndIf
    Opt("GUIOnEventMode", $Opt_OnEventMode) ; undo changes
    EndFunc

    [/autoit][autoit][/autoit][autoit][/autoit][autoit]

    ; #FUNCTION# ====================================================================================================================
    ; Name...........: GUICtrlSetOnEventEx
    ; Description ...: Ruft eine benutzerdefinierte Funktion mit Parametern auf, wenn das GUI-Control angeklickt wurde.
    ; Syntax.........: GUICtrlSetOnEventEx($controlID,[$Function,[$aParameter]])
    ; Parameters ....: $controlID - ID des Controls
    ; $Function - Der Name der Funktion als String
    ; $aParameter - Die Parameter als eindimensionales Array
    ; Author ........: Zeitriss
    ; ===============================================================================================================================
    Func GUICtrlSetOnEventEx($controlID, $Function = Default, $aParameter = Default, $AssignFlag = 0)
    If $Function <> Default Then
    Local $Data = ObjCreate("Scripting.Dictionary")
    If $aParameter = Default Then
    GUICtrlSetOnEvent($controlID, $Function)
    Else
    $Data.add("Parameter", $aParameter)
    EndIf
    $Data.add("Function", $Function)
    $Data.add("AssignFlag", $AssignFlag)
    If $oOnEventDict.Exists(String($controlID)) Then $oOnEventDict.remove(String($controlID))
    $oOnEventDict.add(String($controlID), $Data)
    Local $DataDict = $oOnEventDict(String($controlID))
    GUICtrlSetOnEvent($controlID, "GUICtrlSetOnEventBuffer")
    Else
    If $oOnEventDict.Exists(String($controlID)) Then $oOnEventDict.remove(String($controlID))
    EndIf
    EndFunc

    [/autoit][autoit][/autoit][autoit][/autoit][autoit]

    Func GUICtrlSetOnEventBuffer()
    If Not $oOnEventDict.Exists(String(@GUI_CtrlId)) Then Return
    Local $Data = $oOnEventDict(String(@GUI_CtrlId))
    Local $params = $Data("Parameter")
    If $Data("AssignFlag") = 1 Then
    If IsArray($params) Then _ArrayInsert($params, 0, "CallArgArray")
    If IsObj($params) Then $params.Insert(0, "CallArgArray")
    EndIf
    Call($Data("Function"), $params)
    EndFunc

    [/autoit]

    Die Funktion _ArrayListDisplay könnt ihr ignorieren, die funktioniert wie _ArrayDisplay nur mit Objekten!


    Ich verknüpfe mit GUICtrlSetOnEventEx (Wie GUICtrlSetOnEvent, nur das ich hier mehrere Parameter übergeben kann) die Buttons mit bestimmten Funktionen und übergebe bei der Funktion "SetWimData" das leere Dictionary $WimData. Das möchte ich dann in einer anderen Funktion die ich auch mit einem Button verknüpft habe mit Daten füllen um es dann bei der letzten Funktion (GUICtrlSetOnEventEx($ishow, "_ArrayListDisplay", $WimData)) darzustellen.

    Hat jemand eine Idee warum ich in der "SetWimData" Funktion nicht einfach

    [autoit]

    $WimData = $comDict

    [/autoit]

    schreiben kann sondern das machen muss damit es klappt??

    [autoit]

    For $k in $comDict.Keys
    $WimData.add($k, $comDict($k)) ; Warum funktioniert es so?
    Next

    [/autoit]

    Danke schonmal!

    Einmal editiert, zuletzt von Trolleule1337 (16. Mai 2014 um 11:37)

    • Offizieller Beitrag

    Ich kann das Problem nicht reproduzieren. Ein Kopieren eines Dictionary in eine andere Variable funktioniert tadellos:

    [autoit]


    $oDict = ObjCreate("Scripting.Dictionary")

    [/autoit][autoit][/autoit][autoit]

    $oDict.Add('A',1)
    $oDict.Add('B',2)
    $oDict.Add('C',3)
    $oDict.Add('D',4)

    [/autoit][autoit][/autoit][autoit]

    $oDictCopy = $oDict

    [/autoit][autoit][/autoit][autoit]

    For $k in $oDictCopy.Keys
    ConsoleWrite($k & @TAB & $oDictCopy.Item($k) & @LF)
    Next

    [/autoit][autoit][/autoit][autoit]

    #cs -- OUTPUT
    A 1
    B 2
    C 3
    D 4
    #ce

    [/autoit]
  • Ja habe ich auch getestet. Dein Beispiel funktioniert bei mir auch...

    In meinem Szenario funktioniert es nicht, die Frage ist wieso?

    Der Code kann so ausgeführt werden, könnt ihr mal drüber schauen, muss ja einen Grund haben warum ich es nicht einfach in eine Variable kopieren kann ?(

  • Bei mir funktioniert dein Beispiel so wie ich es mir denke wie es funktionieren soll.
    Wenn man in deinem Skript Zeile 48 enkommentiert und das _ArrayListDisplay aus Zeile 47 in Zeile 49 schreibt wird bei Aufruf dieses ArrayListDisplays der Inhalt von $comdict angezeigt (wie in Zeile 46).
    Ich denke das war es was du möchtest - oder?

  • Danke für die Mühe AspirinJunkie!
    Leider war es anders gedacht.

    In Zeile 48 kopiere ich ja $comdict in $WimData. Damit möchte ich $WimData setzen um es dann mit dem Button "Show" ($ishow) aufzurufen, aber es ist beim Aufruf (GUICtrlSetOnEventEx($ishow, "_ArrayListDisplay", $WimData)) leer wenn ich es mache wie in Zeile 48. Mache ich es wie in Zeile 52 klappt es komischerweise. ?(?(

    Einmal editiert, zuletzt von Trolleule1337 (14. Mai 2014 um 12:11)

  • Ich glaub das ist ein Bug:

    Wenn ich nach dem
    $WimData.add ...
    $WimData = 0

    schreibe, wird mir das Dictionary trotzdem angezeigt:

    [autoit]

    Func SetWimData(ByRef $comDict, ByRef $WimData)
    ;~ $WimData = $comDict ; Warum funktioniert das so nicht?

    [/autoit][autoit][/autoit][autoit]

    For $k in $comDict.Keys
    $WimData.add($k, $comDict($k)) ; Warum funktioniert es so?
    Next

    [/autoit][autoit][/autoit][autoit]

    $WimData = 0
    EndFunc

    [/autoit]
  • Nein ein Bug ist es wohl nicht.
    Allerdings ist das Skript durch die GUICtrlSetOnEventEx und dem Verhalten einer Dictionary-Variable ziemlich verworren.
    Fangen wir mal an:

    Was steht in einer Variable der ein Dictionary zugewiesen wird?
    Antwort: Ein Handle auf eben dieses Dictionary und nicht das Dictionary selbst.
    Das führt wiederrum dazu, dass es so scheint als wäre eine Übergabe per ByRef überflüssig.
    Denn egal ob ein Dictionary mit oder ohne ByRef übergeben wird - es wird immer der Inhalt des globalen Dictionaries verändert:

    [autoit]

    $o_Dic = ObjCreate("Scripting.Dictionary")
    Test1($o_Dic)
    MsgBox(0,"mit ByRef", $o_Dic("Test"))
    Test2($o_Dic)
    MsgBox(0,"ohne ByRef", $o_Dic("Test"))

    [/autoit][autoit][/autoit][autoit]

    Func Test1(ByRef $o_T)
    $o_T("Test") = "funktioniert"
    EndFunc

    [/autoit][autoit][/autoit][autoit]

    Func Test2($o_T)
    $o_T("Test") = "funktioniert ebenfalls"
    EndFunc

    [/autoit]

    Wenn man nun aber nicht den Inhalt des Dictionaries ändern möchte, sondern das Handle auf das Dictionary (in deinem Fall also das setzen auf 0) dann macht es sehr wohl einen Unterschied ob man ByRef verwendet oder nicht.
    Denn verwendet man kein ByRef enthält der Parameter nur eine Kopie des Wertes des Handles. Also nichts weiter als eine Zahl.
    Die Variable außerhalb enthält weiterhin den alten Wert des Handles:

    [autoit]


    $o_Dic = ObjCreate("Scripting.Dictionary")
    Test1($o_Dic)
    MsgBox(0,"ohne ByRef", IsObj($o_Dic))
    Test2($o_Dic)
    MsgBox(0,"mit ByRef", IsObj($o_Dic))

    [/autoit][autoit][/autoit][autoit]

    Func Test1($o_T)
    $o_T = 0
    EndFunc

    [/autoit][autoit][/autoit][autoit]

    Func Test2(ByRef $o_T)
    $o_T = 0
    EndFunc

    [/autoit]

    In deinem Fall schleifst du die Variable $WimData bevor sie aufgerufen wird noch durch die Funktion GUICtrlSetOnEventEx.
    Dort wird sie nicht per ByRef übergeben.
    Es wird also dort eine neue lokale Variable $WimData erstellt. Diese hat zwar den selben Namen, hat auch den selben Wert und verweist somit auf das selbe Objekt, ist aber von der in Example() erstellten $WimData unabhängig.

    Nun kommt es zum Aufruf von SetWimData().
    Was passiert?:
    Das Dictionary auf das das Handle in $WimData verweist bekommt Werte zugewiesen.
    Es handelt sich bei $WimData um das $WimData welches in der GUICtrlSetOnEventEx erstellt wurde.
    Es zeigt auf das selbe Dictionary wie auch die $WimData aus Example().

    Nun setzt du das Handle auf 0.
    Es ändert sich also der Wert des $WimData aus GUICtrlSetOnEventEx - nicht aber aus Example - denn dieses ist ja weiterhin unabhängig.

    Es handelt sich demnach also nicht um einen Bug.

  • Vielen Dank für die ausführliche Erklärung.

    Was mach ich denn wenn ich nun in SetWimData(...) die $WimData mit der $comdict gleichsetzen möchte. Also:

    [autoit]

    Func SetWimData(ByRef $comDict, ByRef $WimData)
    $WimData = $comDict ; Warum funktioniert das so nicht?

    [/autoit][autoit][/autoit][autoit]

    ; For $k in $comDict.Keys
    ; $WimData.add($k, $comDict($k)) ; Warum funktioniert es so?
    ; Next

    [/autoit][autoit][/autoit][autoit]

    EndFunc

    [/autoit]
  • Wie gesagt - nur durch Änderungen in der SetWimData gar nicht da du von dort aus keinen Einfluss auf die $WimData aus der Funktion Example() hast.
    Du kannst den Inhalt des Dictionaries verändern da beide $WimData auf das selbe Dictionary zeigen aber den Wert des Handles auf das Dictionary $comdict zu setzen geht nur für die $WimData aus GUICtrlSetOnEventEx.
    Deswegen klappt die Variante mit der einzelnen Wertzuweisung während der einfache Zuweisungsoperator versagt.

    Zum einen könntest du nun versuchen die GUICtrlSetOnEventEx auf ByRef umzustellen.
    Das wird aber auch nur mäßigen Erfolg bringen, da du Parameter der SetWimData nicht direkt übergibst sondern als Array.
    Das hat dann wieder den Effekt, das das Handle im Array nur eine Kopie des Wertes darstellt:

    [autoit]

    $o_Dic = ObjCreate("Scripting.Dictionary")
    Local $aArray = [$o_Dic]

    [/autoit][autoit][/autoit][autoit]

    Test1($aArray)
    MsgBox(0,"ohne ByRef", IsObj($o_Dic))
    Test2($aArray)
    MsgBox(0,"mit ByRef", IsObj($o_Dic))

    [/autoit][autoit][/autoit][autoit]

    Func Test1($o_T)
    $o_T[0] = 0
    EndFunc

    [/autoit][autoit][/autoit][autoit]

    Func Test2(ByRef $o_T)
    $o_T[0] = 0
    EndFunc

    [/autoit]


    Du müsstest also ziemlich umstrukturieren damit das irgendwann mal klappt.

    Viel viel einfacher ist es jedoch die beiden Dictionaries einfach als globale Variablen zu deklarieren.
    Dann vereinfacht sich dein Skript extrem weil du dir das ganze Durchgeschleife sparen kannst.
    Warum versuchst du um jeden Preis zu die globale Deklaration dieser Variablen zu vermeiden?

  • Super versteh ich, hast mir sehr geholfen.

    Mein gesamtes Projekt läuft so gut wie ohne globale Variablen, jetzt gibt es kein zurück. Ich versuch ein bisschen objekt orientierung reinzubringen, weil ich keine OO Sprache drauf hab, aber das nächste Projekt wird ohne AutoIt ...wahrscheinlich :D

  • Wo wir gerade bei Dictionaries sind, das hab ich von progandi gefunden (2012)

    [autoit]

    $oINI = ObjCreate('Scripting.Dictionary')
    $sSection = "test"
    $oINI($sSection) = ObjCreate('Scripting.Dictionary')
    $oINI($sSection).Add("key", "value")
    $oINI($sSection).Item("key2") = "value2"

    [/autoit][autoit][/autoit][autoit]

    [autoit]$sSection2 = "section"
    $oINI.Item($sSection) = ObjCreate('Scripting.Dictionary')
    $oINI($sSection2).Add("key2") = "value2"
    $oINI.Item($sSection2).Item("key2") = "value2"

    [/autoit][autoit][/autoit][autoit]

    $sSection2 = "section"
    $oINI.Add($sSection, ObjCreate('Scripting.Dictionary'))

    [/autoit][autoit][/autoit][autoit]

    MsgBox(0, "test", IsObj($oINI)) ; 1
    MsgBox(0, "test", IsObj($oINI($sSection))) ; 0

    [/autoit][autoit][/autoit][autoit]

    MsgBox(0, "test", $oINI($sSection).Item("key2")) ; leer
    MsgBox(0, "test", $oINI($sSection).Item("key")) ; leer

    [/autoit]

    Ich bin verwundert wieso ich weder
    $oINI($sSection).Item("key") noch
    $oINI($sSection).Item("key2")
    abrufen kann, da progandy immer gute arbeit macht.

    Mir gefällt die kurze Schreibweise, kann jemand mal eine funktionierende kurz version aufzeigen bzw. erklären wieso sein Beispiel nicht klappt ?!

    Einmal editiert, zuletzt von Trolleule1337 (16. Mai 2014 um 02:43)