Module zur Vereinfachung des Arbeitens mit XML Dialogen

Anfängerfragen zum Programmieren mit PureBasic.
puretom
Beiträge: 109
Registriert: 06.09.2013 22:02

Module zur Vereinfachung des Arbeitens mit XML Dialogen

Beitrag von puretom »

Ich arbeite fast nur mehr mit XML Dialogen, weil das die Arbeit mit Windows enorm vereinfacht.
Was mich aber stört, ist, dass aufgrund von Dialog-Nummer, Window-Nummer usw. irgendwie ein Chaos entsteht, so habe ich mir ein Modul gemacht, das das alles vereinfacht.

Das Modul ist außerdem ein Objekt/eine Klasse, das heißt, man kann mehrere Instanzen eines Dialogs öffnen, und alle Nummern usw. sind sozusagen in einer Hand, gesammelt im Objekt.
Ein bisschen musste ich mit den öffentlichen Instanz-Variablen tricksen, da ist ein 2. Pointer notwendig, aber das ist für mich verschmerzbar.

Die Klasse habe ich nicht von Hand geschrieben, ich habe mir dazu einen kleinen (naiv implementierten) Objekt-Precompiler geschrieben, der das alles in ein Objekt gießt.
Ich denke, ich werde den Precompiler so erweitern, dass er auch die Erstellung reiner Module (also ohne die OOP-Komponente) automatisieren kann, die ganzen Declares sind schon lästig, wäre toll, ginge das automatisch. Egal, ich komme vom Thema ab :D

Zunächst einmal die Source-Klasse vor der Precompiler-Verwendung:

Code: Alles auswählen


Module Dialog
  
  Structure object
    window_no.i As Public
    dialog_no.i As Public
    xml_no.i As Public
  EndStructure
  
  Public Class xml_number ; Xml Number after NewCatch
  
  ; Helpers
  Class Procedure CheckXMLTree(xml_no)
    
    If xml_no=0 Or XMLStatus(xml_no)<>#PB_XML_Success
      MessageRequester("XML error",
                       "XML error: " + XMLError(xml_no) + " (Line: " + XMLErrorLine(xml_no) + ")")
      End
    EndIf
    
  EndProcedure
  Class Procedure OpenXMLDialog_(xml_no, window_name.s, x, y)
    
    Protected dialog_no = CreateDialog(#PB_Any)
    
    If OpenXMLDialog(dialog_no, xml_no, window_name, x, y)=0
      MessageRequester("Dialog error",
                       "Dialog error: " + DialogError(dialog_no))
      End
    EndIf
    
    ProcedureReturn dialog_no
    
  EndProcedure
  
  ; Constructors, Open the window
  Public Class Procedure NewCatchXML(window_name.s, adress, size, free_xml=#False, x=0, y=0)
    
    ; new instance
    Protected *this.object = _New_()
    
    ; create xml tree & check it
    *this\xml_no = CatchXML(#PB_Any, adress, size)
    xml_number   = *this\xml_no
    CheckXMLTree(*this\xml_no)
    
    ; create dialog window
    *this\dialog_no = OpenXMLDialog_(*this\xml_no, window_name, x, y)
    If free_xml
      FreeXML(*this\xml_no)
      *this\xml_no = 0
      xml_number = 0
    EndIf
    
    ; get window number (e.g. EventWindow() = window_no)
    *this\window_no = DialogWindow(*this\dialog_no)
    
    ; returns instance pointer
    ProcedureReturn *this
    
  EndProcedure
  Public Class Procedure NewParseXML(window_name.s, string_name.s, free_xml=#False, x=0, y=0)
    
    ; new instance
    Protected *this.object = _New_() 
    
    ; create xml tree & check it
    *this\xml_no = ParseXML(#PB_Any, string_name)
    xml_number   = *this\xml_no
    CheckXMLTree(*this\xml_no)
    
    ; create dialog dialog
    *this\dialog_no = OpenXMLDialog_(*this\xml_no, window_name, x, y)
    If free_xml
      FreeXML(*this\xml_no)
      *this\xml_no = 0
      xml_number = 0
    EndIf
    
    ; get window number (for e.g. EventWindow() = window_no)
    *this\window_no = DialogWindow(*this\dialog_no) 
    
    ; returns instance pointer
    ProcedureReturn *this                           
    
  EndProcedure
  Public Class Procedure New(window_name.s, xml_no, x=0, y=0, free_xml=#False)
    
    ; new instance
    Protected *this.object = _New_() 
    
    ; check xml tree
    *this\xml_no = xml_no 
    CheckXMLTree(xml_no)
    
    ; create dialog dialog
    *this\dialog_no = OpenXMLDialog_(*this\xml_no, window_name, x, y)
    
    ; get window number (for e.g. EventWindow() = window_no)
    *this\window_no = DialogWindow(*this\dialog_no) 
    
    ; returns instance pointer
    ProcedureReturn *this                           
    
  EndProcedure
  
  ; Destructor  
  Public Procedure Free(*this.object)
    FreeDialog(*this\dialog_no)
    _Free_(*this)
  EndProcedure
  
EndModule

; Create a dialog Object with:
;     Global Window.Dialog
;     Window = Dialog::NewCatch(..)

; Create pointer to private instance variables (if needed)
; dont use _method_table_:
;     Global *Window.Dialog::Variables
;     *Window = Window  <--- important

; Free:
;     Window = Dialog::Free()
;     -> Window will be 0, and can be tested

Das macht der Precompiler daraus:

Code: Alles auswählen


; Instance Interface of Object with Public Instance Procedures (Methods)
Interface Dialog
  Free.i()
EndInterface
DeclareModule Dialog
  EnableExplicit

  ; Public Enumeration

  ; Public Structure

  ; Structure of Public Instance Variables (Attributes)
  Structure Variables
    *_method_table_
    window_no.i
    dialog_no.i
    xml_no.i
  EndStructure

  ; Declare Public Class Variables (Attributes)
  Global xml_number.i

  ; Declare Public Class Procedures (Methods)
  Declare.i New(window_name.s, xml_no, x=0, y=0, free_xml=#False)
  Declare.i NewCatchXML(window_name.s, adress, size, free_xml=#False, x=0, y=0)
  Declare.i NewParseXML(window_name.s, string_name.s, free_xml=#False, x=0, y=0)

EndDeclareModule
Module Dialog
  EnableExplicit

  ; Private Enumeration

  ; Private Structure

  ; Structure of Private Instance Variables (Attributes)
  Structure object Extends Variables
  EndStructure

  ; Declare Private Class Variables (Attributes)

  ; Declare all Instance & Private Class Procedures (Methods)
  Declare.i OpenXMLDialog_(xml_no, window_name.s, x, y)
  Declare.i CheckXMLTree(xml_no)
  Declare.i _Free_(*this.object)
  Declare.i _New_()
  Declare.i Free(*this.object)

  ; Procedures (Methods) Implementation
  Procedure.i OpenXMLDialog_(xml_no, window_name.s, x, y)
    Protected dialog_no = CreateDialog(#PB_Any)
    If OpenXMLDialog(dialog_no, xml_no, window_name, x, y)=0
    MessageRequester("Dialog error",
    "Dialog error: " + DialogError(dialog_no))
    End
    EndIf
    ProcedureReturn dialog_no
  EndProcedure
  Procedure.i CheckXMLTree(xml_no)
    If xml_no=0 Or XMLStatus(xml_no)<>#PB_XML_Success
    MessageRequester("XML error",
    "XML error: " + XMLError(xml_no) + " (Line: " + XMLErrorLine(xml_no) + ")")
    End
    EndIf
  EndProcedure
  Procedure.i New(window_name.s, xml_no, x=0, y=0, free_xml=#False)
    Protected *this.object = _New_()
    *this\xml_no = xml_no
    CheckXMLTree(xml_no)
    *this\dialog_no = OpenXMLDialog_(*this\xml_no, window_name, x, y)
    *this\window_no = DialogWindow(*this\dialog_no)
    ProcedureReturn *this
  EndProcedure
  Procedure.i _Free_(*this.object)
    FreeStructure(*this)
  EndProcedure
  Procedure.i Free(*this.object)
    FreeDialog(*this\dialog_no)
    _Free_(*this)
  EndProcedure
  Procedure.i _New_()
    Protected *new.object = AllocateStructure(object)
    If *new: *new\_method_table_ = ?method_table
    Else: MessageRequester("Runtime Class Allocation Error","Allocation of Object 'Dialog' failed."): End
    EndIf
    ProcedureReturn *new
  EndProcedure
  Procedure.i NewCatchXML(window_name.s, adress, size, free_xml=#False, x=0, y=0)
    Protected *this.object = _New_()
    *this\xml_no = CatchXML(#PB_Any, adress, size)
    xml_number   = *this\xml_no
    CheckXMLTree(*this\xml_no)
    *this\dialog_no = OpenXMLDialog_(*this\xml_no, window_name, x, y)
    If free_xml
    FreeXML(*this\xml_no)
    *this\xml_no = 0
    EndIf
    *this\window_no = DialogWindow(*this\dialog_no)
    ProcedureReturn *this
  EndProcedure
  Procedure.i NewParseXML(window_name.s, string_name.s, free_xml=#False, x=0, y=0)
    Protected *this.object = _New_()
    *this\xml_no = ParseXML(#PB_Any, string_name)
    xml_number   = *this\xml_no
    CheckXMLTree(*this\xml_no)
    *this\dialog_no = OpenXMLDialog_(*this\xml_no, window_name, x, y)
    If free_xml
    FreeXML(*this\xml_no)
    *this\xml_no = 0
    EndIf
    *this\window_no = DialogWindow(*this\dialog_no)
    ProcedureReturn *this
  EndProcedure

  ; DataSection of Public Instance Procedures (Methods)
  DataSection
    method_table:
    Data.i @Free()
  EndDataSection

EndModule

Bitte ausgiebigst testen, falls das Zeug wer brauchen kann, ich habe gerade erst angefangen, Objekte und Basis-OOP zu üben und zu verstehen. Das ist alles noch nicht 100% durchgetestet.

LG Tom und Danke :-)

Testcode im nächsten Post


Ich habe das auch im englischen Forum gepostet: http://www.purebasic.fr/english/viewtop ... 13&t=68207
Zuletzt geändert von puretom am 28.03.2017 21:20, insgesamt 2-mal geändert.
Windows 7 und Windows 10 (Laptop), PB 5.62 | Projekt: Tutorial - Compiler und Virtual Machine | vielleicht einmal ein Old School Text-Adventure Tutorial | Neu: Spielereien, Üben rund um OOP in PB
puretom
Beiträge: 109
Registriert: 06.09.2013 22:02

Re: Module zur Vereinfachung des Arbeitens mit XML Dialogen

Beitrag von puretom »

Hier der Testcode.
Man sieht hier, wie man eine Instanz instantiiert (sagt man das so?) und auch wie man sie zerstört.
Die EventLoop() zeigt, dass man in der If-Abfrage auf jeden Fall abfragen sollte, ob der Instanz-Pointer nicht null ist, sonst gibt es nach dem Schließen eines Fensters Probleme.

Auch die ein wenig getrickste Zugangsweise zu Instanzvariablen (public) sieht man, über einen *pointer, der gleich heißt, wie der Instanzpointer.
Man könnte mit Gettern arbeiten, dann erspart man sich diesen Kunstgriff, aber aus Performancegründen und auch aufgrund von Overhead habe ich mich mal für die Public Instanz-Variable entschieden, ist aber flott geändert, wenn man das will.

Ansonsten, scheint auf den ersten Blick zu funktionieren, wie gesagt, wer es braucht, bitte ausgiebig testen und Rückmeldung.

Lg Tom <)

Der Testcode:

Code: Alles auswählen

XIncludeFile "Class Dialog.pb"
; DataSection ; Dialog XML
;   begin_dialog: :IncludeBinary "_dialogs.xml":end_dialog:
; EndDataSection

Global xml$
xml$+"<Dialogs>"
xml$+"  <Window name='wndMain' text='Dialog from the string' width='800' height='650' flags='#PB_Window_SystemMenu|#PB_Window_MinimizeGadget|#PB_Window_MaximizeGadget|#PB_Window_SizeGadget|#PB_Window_ScreenCentered'>"
xml$+"    <vbox>"
xml$+"      <button text='Load' onevent='buttonLoad()' />"
xml$+"      <button text='Save' />"
xml$+"      <button text='Credits' />"
xml$+"    </vbox>"
xml$+"  </Window>"
xml$+"</Dialogs>"
  
Global MainWindow.Dialog
Global *MainWindow.Dialog::Variables

Global Window2.Dialog
Global *Window2.Dialog::Variables

Global Window3.Dialog
Global *Window3.Dialog::Variables

Procedure EventLoop()
  
  Protected event
  
  Repeat
    event = WaitWindowEvent()
    
    If MainWindow And EventWindow() = *MainWindow\window_no 
      If event = #PB_Event_CloseWindow
        MainWindow = MainWindow\Free()
        End
        ;End ; or not End if it was not the Main Window
      EndIf
      
    ElseIf Window2 And EventWindow() = *Window2\window_no
      If event = #PB_Event_CloseWindow
        Window2 = Window2\Free()
      EndIf
      
    ElseIf Window3 And EventWindow() = *Window3\window_no
      If event = #PB_Event_CloseWindow
        Window3 = Window3\Free()
      EndIf
    EndIf
    
  ForEver
  
EndProcedure
Procedure Main()
  
  ;MainWindow = Dialog::NewCatchXML("wndMain", ?begin_dialog, ?end_dialog-?begin_dialog) ; <--- use with datasection
  MainWindow = Dialog::NewParseXML("wndMain", xml$) ; <--- use with string
  *MainWindow = MainWindow
  SetWindowTitle(*MainWindow\window_no, "Main Window - closing me means end")
  
  Debug "*MainWindow\window_no = "+*MainWindow\window_no
  Debug "*MainWindow\dialog_no = "+*MainWindow\dialog_no
  Debug "*MainWindow\xml_no    = "+*MainWindow\xml_no
  Debug ""
  Debug "Dialog::xml_number    = "+Dialog::xml_number
  
  
  Debug ""
  Window2  = Dialog::New("wndMain", Dialog::xml_number) ; <--- use to open with existing xml tree in memory
  *Window2 = Window2
  SetWindowTitle(*Window2\window_no, "Window 2")
  
  Debug "*Window2\window_no    = "+*Window2\window_no
  Debug "*Window2\dialog_no    = "+*Window2\dialog_no
  Debug "*Window2\xml_no       = "+*Window2\xml_no
  Debug ""
  Debug "Dialog::xml_number    = "+Dialog::xml_number
  
  Window3  = Dialog::New("wndMain", Dialog::xml_number)
  *Window3 = Window3
  SetWindowTitle(*Window3\window_no, "Window 3")
  
  Debug "*Window3\window_no    = "+*Window3\window_no
  Debug "*Window3\dialog_no    = "+*Window3\dialog_no
  Debug "*Window3\xml_no       = "+*Window3\xml_no
  Debug ""
  Debug "Dialog::xml_number    = "+Dialog::xml_number
  Debug "------------------------------------------------------"
  
EventLoop()
  
EndProcedure

Runtime Procedure buttonLoad()
  
  Protected window_no = EventWindow()
  
  Select window_no
    Case *MainWindow\window_no
      Debug "buttonLoad -> *MainWindow: "+window_no
    Case *Window2\window_no
      Debug "buttonLoad -> *Window2: "+window_no      
    Case *Window3\window_no
      Debug "buttonLoad -> *Window3: "+window_no      
  EndSelect
  
EndProcedure

Main()

Edit: noch schnell einen Button-Test eingebaut
Windows 7 und Windows 10 (Laptop), PB 5.62 | Projekt: Tutorial - Compiler und Virtual Machine | vielleicht einmal ein Old School Text-Adventure Tutorial | Neu: Spielereien, Üben rund um OOP in PB
puretom
Beiträge: 109
Registriert: 06.09.2013 22:02

Re: Module zur Vereinfachung des Arbeitens mit XML Dialogen

Beitrag von puretom »

Da ich keinen neuen Thread machen wollte, hier eine nur Module-Lösung (ohne Objekt-Zeug) für XML Files, die ich in eine DataSection include.
Das hier ist also ein vollständig eigenständiges Code-Snippet und hat mit dem Vorigen nichts zu tun!

Vielleicht kann's ja wer brauchen.

Code: Alles auswählen

DeclareModule XMLDialogs
  EnableExplicit
  
  Declare CatchDataSectionLabels(begin_label, end_label)
  Declare OpenDialogWindow(xml_no, wnd_name.s, close_button_callback=0)
  Declare EventLoopForever()
  
  Macro CatchDataSectionFile(file_path)
    
    ; returns xml_no (with this trick here)
    XMLDialogs::CatchDataSectionLabels(?begin_xmlcatchlabel#MacroExpandedCount, ?end_xmlcatchlabel#MacroExpandedCount)
    
    DataSection
      begin_xmlcatchlabel#MacroExpandedCount:
      IncludeBinary file_path
      end_xmlcatchlabel#MacroExpandedCount:
    EndDataSection
    
  EndMacro
  Macro BindCloseEvent(callback, dialog_no)
    BindEvent(#PB_Event_CloseWindow, callback, DialogWindow(dialog_no))
  EndMacro
  
EndDeclareModule
Module XMLDialogs
  EnableExplicit
  
  #errortitle$ = ~"Error in Module \"XMLDialogs\""
  
  Procedure CatchDataSectionLabels(begin_label, end_label)
    
    ; returns xml number 
    Protected xml_no = CatchXML(#PB_Any, begin_label, end_label-begin_label)
    
    If xml_no And XMLStatus(xml_no)=#PB_XML_Success
    Else
      MessageRequester(#errortitle$, "XML error: " + XMLError(xml_no) + " (Line: " + XMLErrorLine(xml_no) + ")")
      End
    EndIf    
    
    ; returns xml number 
    ProcedureReturn xml_no
    
  EndProcedure
  Procedure OpenDialogWindow(xml_no, wnd_name.s, close_button_callback=0)
    
    ; returns dialog number
    
    Protected dialogno = CreateDialog(#PB_Any)
    
    If dialogno And OpenXMLDialog(dialogno, xml_no, wnd_name)
    Else  
      MessageRequester(#errortitle$, "Dialog error: " + DialogError(dialogno))
      End
    EndIf        
    
    If close_button_callback
      BindCloseEvent(close_button_callback, dialogno) 
    EndIf
    
    ; returns dialog number
    ProcedureReturn dialogno
    
  EndProcedure
  Procedure EventLoopForever()
    Repeat:WaitWindowEvent():ForEver
  EndProcedure
  
EndModule

; ---- DIALOGS.xml: open a text file and save the lines below at "dialogs.xml" ----------

; <?xml version="1.0" encoding="utf-8"?>
; <Dialogs>
;   <Window name="wndMain" width="1280" height="650" flags="#PB_Window_SystemMenu|#PB_Window_MinimizeGadget|#PB_Window_MaximizeGadget|#PB_Window_SizeGadget|#PB_Window_ScreenCentered">
;   </Window>
; </Dialogs>


; ---- EXAMPLE CODE ---------------------------------------------------------------------

;   Global xmlno_wndMain, dialogno_wndMain
;   
;   Procedure Close_wndMain()
;     End
;   EndProcedure
;   Procedure wndMain()
;     
;     xmlno_wndMain    = XMLDialogs::CatchDataSectionFile("dialogs.xml")
;     dialogno_wndMain = XMLDialogs::OpenDialogWindow(xmlno_wndMain, "wndMain", @Close_wndMain())
;     
;     ; loop forever
;     XMLDialogs::EventLoopForever()
;     
;   EndProcedure

;   wndMain()

Windows 7 und Windows 10 (Laptop), PB 5.62 | Projekt: Tutorial - Compiler und Virtual Machine | vielleicht einmal ein Old School Text-Adventure Tutorial | Neu: Spielereien, Üben rund um OOP in PB
Antworten