Wie erstellt man eine neue Class?

MAC OSX spezifisches Forum
Beiträge, die plattformübergreifend sind, gehören ins 'Allgemein'-Forum.
Wolfram
Beiträge: 28
Registriert: 23.08.2013 14:38
Computerausstattung: OSX 10.13 | PB 5.46

Wie erstellt man eine neue Class?

Beitrag von Wolfram »

Hallo,

ich habe leider immer noch nicht verstanden wie man eine neu Class erstellt um ein verhalten eines Gadgets zu überschreiben.
Ich habe ein Xcode Beispiel auf youtube gesehen und versucht das umzusetzen, aber komme nicht weiter.
https://www.youtube.com/watch?v=7MZJxPOo_xU

Kann mir mir das jemand erklären?

Code: Alles auswählen

ImportC ""
  class_addMethod(Class.i, Selector.i, *Callback, Types.P-ASCII)
  objc_allocateClassPair(ModelClass.i, NewClassName.P-ASCII, ExtraBytes.i)
  objc_registerClassPair(NewClass.i)
  object_setClass(ObjectToModify.i, NewClass.i)
  sel_registerName(MethodName.P-ASCII)
EndImport


Global Window_0, Button_0

; Global AppDelegate.i = CocoaMessage(0, CocoaMessage(0, 0, "NSApplication sharedApplication"), "delegate")
; Global DelegateClass.i = CocoaMessage(0, AppDelegate, "class")

Procedure buttonCallback(object.i, selector.i, dummy.i)
  Debug "Callback"
  
  view = CocoaMessage(0, WindowID(Window_0), "contentView")
  layer = CocoaMessage(0, view, "layer")
  CocoaMessage(0, view, "updateLayer", #YES)
EndProcedure

Procedure OpenWindow_0(x = 0, y = 0, width = 450, height = 160)
  Window_0 = OpenWindow(#PB_Any, x, y, width, height, "", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  Button_0 = ButtonGadget(#PB_Any, 160, 50, 130, 25, "OK")
  
  buttonCell = CocoaMessage(0, GadgetID(Button_0), "cell")

  customButtonClass = objc_allocateClassPair( CocoaMessage(0, buttonCell, "class"), "MyCustomButton", 0)
  objc_registerClassPair(customButtonClass)
  object_setClass(buttonCell, customButtonClass)
  selector = sel_registerName("isHighLighted:")
  If class_addMethod(customButtonClass, selector, @buttonCallback(), "v@:@") = #True
    Debug "successful"
  EndIf
  

EndProcedure

Procedure Window_0_Events(event)
  Select event
    Case #PB_Event_CloseWindow
      ProcedureReturn #False

    Case #PB_Event_Menu
      Select EventMenu()
	Case #PB_Menu_Quit
          ProcedureReturn #False
      EndSelect

    Case #PB_Event_Gadget
      Select EventGadget()
        Case Button_0
          
      EndSelect
  EndSelect
  ProcedureReturn #True
EndProcedure

OpenWindow_0()

Repeat
  event = WaitWindowEvent()
Until Window_0_Events(event) = #False

End
OSX 10.13 | PB 5.46
Benutzeravatar
Shardik
Beiträge: 738
Registriert: 25.01.2005 12:19

Re: Wie erstellt man eine neue Class?

Beitrag von Shardik »

Es geht darum ein Subclassing einer bereits vorhandenen Klasse vorzunehmen, um dann bestimmte Methodenaufrufe abfangen und manipulieren zu können. Im englischen Forum hat empty an diesem einfachen Beispiel gezeigt, wie man bei einem StringGadget das Subclassing durchführt, um das Drücken der Escape-Taste zu entdecken und darauf zu reagieren. Ich habe sein Beispiel, das auch eine ungenutzte Prozedur zum Subclassing eines Fensters enthält, weiter versucht zu vereinfachen. Zum Beispiel ist der komplette ImportC..EndImport-Block nicht mehr notwendig, da Wilbert viele wichtige API-Aufrufe in die PureBasic-Ressourcen eingepflegt hat.

Dies ist die Vorgehensweise beim Subclassing in meinem weiter vereinfachten Beispiel:
- Die Prozedur SubclassGadget() mit der Gadget-Nummer aufrufen, für die das Subclassing durchgeführt werden soll
- Die Funktion class_addMethod_() aufrufen und folgende Parameter übergeben:
1. Den Rückgabewert des Aufrufs von SubclassGadget() (die neue abgeleitete Gadget-Klasse)
2. Die Methode sel_registerName_() mit dem gewünschten Selektor übergeben, der abgefangen werden soll (Entdeckung der Escape-Taste)
3. Den Callback, der bei Auftreten der Aktion (Drücken der Escape-Taste) aufgerufen werden soll
4. Eine Zeichenkette mit Symbolen für die Parametertypen, die im Callback ankommen

Code: Alles auswählen

EnableExplicit

Procedure SubclassGadget(GadgetID.I, NewClassName.S)
  Protected GadgetClass.I = CocoaMessage(0, GadgetID(GadgetID), "class")
  Protected NewGadgetClass.I

  NewGadgetClass = objc_allocateClassPair_(GadgetClass, NewClassName, 0)
  objc_registerClassPair_(NewGadgetClass)
  object_setClass_(GadgetID(GadgetID), NewGadgetClass)

  ProcedureReturn NewGadgetClass
EndProcedure

ProcedureC SubclassedStringGadgetEventCallback(Object.I, Selector.I, Sender.I)
  Debug "<Esc> wurde gedrückt"
EndProcedure

Define SubclassedStringGadget.I

OpenWindow(0, 270, 100, 350, 80, "Subclassed StringGadget")
StringGadget(0, 10, 10, 330, 25, "Standard StringGadget")
StringGadget(1, 10, 45, 330, 25,
  "Subclassed StringGadget (Callback bei <Esc>-Taste)")
SetActiveGadget(1)

SubclassedStringGadget = SubclassGadget(1, "SubclassedStringGadget")
class_addMethod_(SubclassedStringGadget, sel_registerName_("cancelOperation:"),
  @SubclassedStringGadgetEventCallback(), "v@:@")

Repeat
Until WaitWindowEvent() = #PB_Event_CloseWindow
Bitte gib immer die von Dir verwendete MacOS-Version an (Du hattest ja längere Zeit Lion verwendet) und Deine PureBasic-Version (am besten beides in Deiner Signatur). Dies ist besonders wichtig, wenn Du speziell an dem verlinkten Beispiel in dem YouTube-Video interessiert bist, da sich in dem Bereich laut den Kommentaren viel geändert hat und bei neueren MacOS-Versionen vieles einfacher geworden ist.
Wolfram
Beiträge: 28
Registriert: 23.08.2013 14:38
Computerausstattung: OSX 10.13 | PB 5.46

Re: Wie erstellt man eine neue Class?

Beitrag von Wolfram »

Hallo Shardik,

ich glaube das habe ich soweit verstanden. Nur woher weis ich wann ich das Gadget selber und wann die Cell des Gadgets?
Und wie finde ich eine Liste mit möglichen Methoden wie z.B. das drücken einer Bestimmten Taste? Sind das alles NSResponder oder gibt es noch mehr?

Wenn ich das nun auf das Youtube Beispiel anwenden möchte passiert nichts.
Was habe ich da missverstanden?


Mir geht's darum ein eigenen Butten mit Hilfe des CGRectMark() zu erstellen. Das ist ideal um flexibel in Größe und Design zu bleiben.
Ich bin unter OSX 10.13 und PB 5.46 und XCode 8.3.3 am arbeiten.

Code: Alles auswählen

Global Window_0, Button_0


Procedure SubclassGadget(gadgetID, NewClassName.s)
;   Protected buttonCell.i = CocoaMessage(0, gadgetID, "cell")

  Protected GadgetClass.i = CocoaMessage(0, gadgetID, "class")
  
  
  Protected NewGadgetClass.i

  NewGadgetClass = objc_allocateClassPair_(GadgetClass, NewClassName, 0)
  objc_registerClassPair_(NewGadgetClass)
  object_setClass_(gadgetID, NewGadgetClass)

  ProcedureReturn NewGadgetClass
EndProcedure


ProcedureC subclassedGadgetEventCallback(object.i, selector.i, sender.i)
  Debug "callBack"
EndProcedure



  Window_0 = OpenWindow(#PB_Any, 0, 0, 450, 160, "", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  Button_0 = ButtonGadget(#PB_Any, 160, 50, 130, 25, "OK")
  
  
  Define mySubclassedGadget.i
  
  mySubclassedGadget = SubclassGadget( GadgetID(Button_0) , "mySubclassedGadget")
  class_addMethod_(mySubclassedGadget, sel_registerName_("isHighLighted:"),
                   @subclassedGadgetEventCallback(), "v@:@")




Procedure Window_0_Events(event)
  Select event
    Case #PB_Event_CloseWindow
      ProcedureReturn #False

    Case #PB_Event_Menu
      Select EventMenu()
	Case #PB_Menu_Quit
	  ProcedureReturn #False
      EndSelect

    Case #PB_Event_Gadget
      Select EventGadget()
        Case Button_0
          
      EndSelect
  EndSelect
  ProcedureReturn #True
EndProcedure

Repeat
  event = WaitWindowEvent()
Until Window_0_Events(event) = #False

End
OSX 10.13 | PB 5.46
Benutzeravatar
Shardik
Beiträge: 738
Registriert: 25.01.2005 12:19

Re: Wie erstellt man eine neue Class?

Beitrag von Shardik »

Wolfram hat geschrieben:Und wie finde ich eine Liste mit möglichen Methoden wie z.B. das drücken einer Bestimmten Taste? Sind das alles NSResponder oder gibt es noch mehr?
Die Methoden der gewünschten Klasse und die Klassen aller Elternklassen ("superclass"), von denen eine Klasse abgeleitet ist, können verwendet werden. Wilbert hat im englischen Forum gezeigt, wie man die Elternklassen ermittelt. Für das ButtonGadget erhält man die Elternklassen folgendermaßen:

Code: Alles auswählen

Procedure.S GetInheritedObjects(Object)
  Protected Result.I
  Protected MutableArray.I = CocoaMessage(0, 0, "NSMutableArray arrayWithCapacity:", 10)
 
  Repeat
    CocoaMessage(0, MutableArray, "addObject:", CocoaMessage(0, Object, "className"))
    CocoaMessage(@Object, Object, "superclass")
  Until Object = 0
 
  CocoaMessage(@Result, MutableArray, "componentsJoinedByString:$", @"  -->  ")
  CocoaMessage(@Result, Result, "UTF8String")
 
  ProcedureReturn PeekS(Result, -1, #PB_UTF8)
 EndProcedure

OpenWindow(0, 270, 100, 150, 80, "Test")
ButtonGadget(0, 10, 30, 130, 25, "Click me!")

MessageRequester("Elternklassen:",
  "ButtonGadget  -->  " + GetInheritedObjects(GadgetID(0)))

Repeat
Until WaitWindowEvent() = #PB_Event_CloseWindow
Wolfram hat geschrieben:Wenn ich das nun auf das Youtube Beispiel anwenden möchte passiert nichts.
Was habe ich da missverstanden?
Folgende Schritte sind nötig:
1. Einen Layer für das ButtonGadget anfordern:

Code: Alles auswählen

CocoaMessage(0, GadgetID(0), "setWantsLayer:", #YES)
2. Das Subclassing des ButtonGadget durchführen:

Code: Alles auswählen

SubclassedButtonGadget = SubclassGadget(0, "SubclassedButtonGadget")
3. Abschließend muss der Callback definiert werden, der das Bild des Button im normalen und gedrückten Zustand zeichnet:

Code: Alles auswählen

class_addMethod_(SubclassedButtonGadget, sel_registerName_("updateLayer"),
  @UpdateLayer(), "v@")
4. Als Images habe ich die Original-Images aus dem YouTube-Video von GitHub heruntergeladen. Ich habe aus button.imageset die Datei button.png und aus button_pressed.imageset die Datei button_pressed.png heruntergeladen und im gleichen Verzeichnis wie den untenstehenden Sourcecode gespeichert.

5. Ich habe in meiner IDE unter "Werkzeuge..." das plist-Tool (von Joe Baker und Wilbert) installiert und als Auslöser "Nach dem Kompilieren/Starten" eingestellt. Außerdem habe ich die beiden Grafiken aus GitHub im Sourcecode dann so eingebunden:

Code: Alles auswählen

;@@ DisableDebugWindow
;@R button.png
;@R button_pressed.png
Beim Kompilieren des untenstehenden Sourcecodes erhalte ich dann das gleiche Ergebnis wie im YouTube-Video: die blaue Farbe des Button verdunkelt sich beim Klicken darauf.
Bild
Ich habe den Beispiel-Code erfolgreich mit MacOS 10.9.5 'Mavericks' mit PB 5.46 x86 und x64 im ASCII- und Unicode-Modus getestet.

Code: Alles auswählen

EnableExplicit

;@@ DisableDebugWindow
;@R button.png
;@R button_pressed.png

Define SubclassedButtonGadget.I

Procedure SubclassGadget(GadgetID.I, NewClassName.S)
  Protected GadgetClass.I = CocoaMessage(0, GadgetID(GadgetID), "class")
  Protected NewGadgetClass.I

  NewGadgetClass = objc_allocateClassPair_(GadgetClass, NewClassName, 0)
  objc_registerClassPair_(NewGadgetClass)
  object_setClass_(GadgetID(GadgetID), NewGadgetClass)

  ProcedureReturn NewGadgetClass
EndProcedure

ProcedureC UpdateLayer(SubclassedButton.I, Selector.I)
  Protected ButtonImage.S = "button.png"
  Protected ButtonPressedImage.S = "button_pressed.png"
  Protected Cell.I

  Cell = CocoaMessage(0, SubclassedButton, "cell")

  If CocoaMessage(0, Cell, "isHighlighted")
    CocoaMessage(0, CocoaMessage(0, SubclassedButton, "layer"), "setContents:",
      CocoaMessage(0, 0, "NSImage imageNamed:$", @ButtonPressedImage))
  Else
    CocoaMessage(0, CocoaMessage(0, SubclassedButton, "layer"), "setContents:",
      CocoaMessage(0, 0, "NSImage imageNamed:$", @ButtonImage))
  EndIf
EndProcedure

OpenWindow(0, 270, 100, 240, 90, "Subclassed ButtonGadget")
ButtonGadget(0, 50, 30, 140, 30, "Click me!")
SetGadgetFont(0, LoadFont(0, "Apple Chancery", 18))

CocoaMessage(0, GadgetID(0), "setWantsLayer:", #YES)
SubclassedButtonGadget = SubclassGadget(0, "SubclassedButtonGadget")
class_addMethod_(SubclassedButtonGadget, sel_registerName_("updateLayer"),
  @UpdateLayer(), "v@")

Repeat
Until WaitWindowEvent() = #PB_Event_CloseWindow
Wolfram
Beiträge: 28
Registriert: 23.08.2013 14:38
Computerausstattung: OSX 10.13 | PB 5.46

Re: Wie erstellt man eine neue Class?

Beitrag von Wolfram »

Hi Shardik,

vielen Dank!
Ich habe dann in den callback noch das mit dem CGRect eingebaut und nun sind, egal bei welcher Größe, die Ecken immer schön rund.
Bild

Letzte Frage:
Warum wird denn der callback immer zweimal pro klick aufgerufen?

Code: Alles auswählen

ProcedureC UpdateLayer(SubclassedButton.I, Selector.I)
  Protected cell.i, layer.i, image.i
  Protected Rect.CGRect
  
  Debug "UpdateLayer"

  cell = CocoaMessage(0, SubclassedButton, "cell")
  layer = CocoaMessage(0, SubclassedButton, "layer", 0 )
  
  If CocoaMessage(0, cell, "isHighlighted")
    image =CocoaMessage(0, 0, "NSImage imageNamed:$", @"button_pressed.png")
    CocoaMessage(0, layer, "setContents:", image)
    
  Else
    image =CocoaMessage(0, 0, "NSImage imageNamed:$", @"button.png")
    CocoaMessage(0, layer, "setContents:", image)
    
    CocoaMessage(@Rect, layer, "contentsCenter")
    Rect\origin\x  =0.5
    Rect\origin\y =0.5
    Rect\size\width =0
    Rect\size\height =0
    CocoaMessage(0, layer, "setContentsCenter:@", @Rect)
    
  EndIf
  
EndProcedure
OSX 10.13 | PB 5.46
Benutzeravatar
mk-soft
Beiträge: 3695
Registriert: 24.11.2004 13:12
Wohnort: Germany

Re: Wie erstellt man eine neue Class?

Beitrag von mk-soft »

An beide... :allright:
Alles ist möglich, fragt sich nur wie...
Projekte ThreadToGUI / EventDesigner V3 / OOP-BaseClass-Modul
Downloads auf MyWebspace / OneDrive
Antworten