Gambas 3 v 3.8.90.svn.7791

Om één of andere reden startte de gambas 3 IDE niet meer en na wat pogingen om een recentere versie af te halen en een de-installatie + installatie ging het nog steeds niet

Enkele van de foutmeldingen die ik zag:
~> gambas3
ERROR: #2: Cannot load class ‘Action!’: Unable to load class file
~> gambas3
gbx3: unable to find startup file
~> gambas3
bash: /usr/bin/gambas3: No such file or directory

Daarna gekeken in de repositories (via software.opensuse.org):

Education

3.8.4
32 Bit
64 Bit
Source

home:munix9

3.8.4
32 Bit
64 Bit
Source

home:munix9:unstable

3.8.90.svn.7791
32 Bit
64 Bit
Source

En dus de laatste versie gekozen, die heel wat Qt5 spullen afhaalt.

Nu draait 3.8.90!

Gambas3 voorbeelden, WaveGenerator 3.6.3

Op openSUSE 13.1 kan het WaveGenerator voorbeeld een foutmelding geven:

Unable to create media control

en dit op de lijn met “autoaudisink”:

$hOutput = New MediaControl($hPlayer, "autoaudiosink")

Dat heeft te maken met het openen van een audio-uitvoer. Er zijn nogal wat verschillende audiosystemen, in verschillende lagen, en meestal lukt het met die autoaudiosink; hier dus niet.

Ik heb* “autoaudiosink” vervangen door “alsasink”, en daarna loopt het voorbeeld normaal.

$hOutput = New MediaControl($hPlayer, "alsasink")

(*) Op basis van een bericht op de mailinglist waar alsasink wordt vervangen door autoaudiosink.
Ik vond enkele meldingen van het probleem, met verwijzingen naar het audiosysteem, maar nergens een lijst met mogelijke waarden om uit te proberen om het snel te doen werken voor mezelf.

Objecten die een array bevatten

Array in een Klasse

Een objectklasse kan gegevens en methoden bevatten, maar ook bv een array. Die array kan bestaan uit getallen of tekst, maar kan ook weer uit objecten bestaan.

Ik maak een object met twee arrays;

  • 1 met string elementen
  • 1 met ‘object2’ elementen.

Object2 bevat enkel twee eigenschappen ter demonstratie: een tekstveld en een getal.

Een testprogramma maakt eerst de objecten en vult de arrays.

Daarna kan elk van de arrays weergegeven worden in een tabel.

(upd) Code testprogramma zie verder, maar eerst een opmerkig:

embedded arrays
Er wordt op online Gambas documentatie gewezen op het probleem van “embedded arrays” (lees meer).

Het testprogramma ziet er zo uit; het aantal elementen wordt bepaald door het getal, de tekst die opgeslagen wordt door het tekst-invulveld:

ArraysInObjectsFMainEdit

Als het draait:

ArraysInObjectsFMainRun

Hieronder de broncode … Continue reading

Klik en slepen beheren met een object? (Drag’n drop object)

In een bepaalde toepassing wilde ik meer controle krijgen over de klik en sleep akties in een form, omdat er verschillende mogelijke bronnen en bestemmingen waren. Ik weet niet of het een goed idee is, maar ik maakte er een apart object voor; zeg maar door een Class CDragDrop.

Daarin definieerde ik de nodige constanten.
Ik hield bij waar de klik en sleep vandaan kwam, en in geval van een tabel bv ook de coordinaten dmv rij en kolom in die tabel (row, col).

Ik definieerde een aantal mogelijke ontvangers in een array.
(deze zijn redelijk specifiek voor het scherm (Form) waarin ik werk)
bv:


' Gambas class file
' CDragDrop - to manage drag n drop
'
PUBLIC CONST Employee AS String = "employee"
PUBLIC CONST DestinationOne AS String = "destination1"
PUBLIC CONST DestinationTwo AS String = "destination2"
'
PRIVATE arrReceiver AS String[] = [Employee, DestinationOne, DestinationTwo]

Klik en sleep toestanden kan je bijhouden door methodes als:
Door procedures als

  • setOrigin
  • setReceiver
  • getOrigin
  • getReceiver

(deze zijn op zich redelijk universeel, invulling verschilt).
Ook kan je de bron en bestemmeling checken in de klasse.

Ik hergebruik het object van deze klasse:

PUBLIC SUB clear()

waarin ik alles leeg maak; maar je kan ook een nieuwe instantie maken en de gebruikte bijhouden als referentie (fifo lijstje?), als je nadien moet weten wat je vorige klik-en sleep aktie was.

English summary:
I created my own class to manage drag’n drop in my app. I can keep code away from my Form, and have all drag’n drop information at hand in one place. I could even keep the last action archived by keeping the previous instance(s) (in a FIFO array?).

Tekst van runtime knop onbereikbaar (button Text property)

In bepaalde omstandigheden kwam ik op een foutmelding bij het gebruiken van de tekst die ingesteld staat bij een knop. De foutmelding:

Unknown symbol ‘Text’ in class ‘Control’

Unknown Symbol Text In Class Control

De klasse van het object waarvan ik de tekst eigenschap wil gebruiken is niet “Control”, maar wel “Button”, maar dat ziet Gambas niet. Toch niet ‘at runtime’, maar… :

Unknown Symbol Text In Class Control Debugged

Bij het stilleggen van het programma, en het selecteren van de betreffende code, toont hij de juiste tekst in de tooltip.

Daarom gebruik ik nu deze kleine omweg:

In plaats van de children van hboxItems aan te spreken met een index, en daarvan ineens de tekst op te vragen, wijs ik nu eerst het i-de item van de children toe aan een voorgedefinieerde button, en gebruik ik daarvan de tekst eigenschap:

Unknown Symbol Text In Class Control Solved

ps: Dit is een stukje code uit een complexer stuk programma, en om de fout te isoleren heb ik me hier beperkt tot het weergeven met DEBUG van de Text eigenschap; in werkelijkheid gebeurt daar iets anders mee, meer bepaald een drag’n’drop bewerking.

De buttons werden niet gemaakt in de Form designer, maar vanuit het programma, met als parent de hboxItems container. Daarom kan ik ze enkel onrechtstreeks aanspreken.

English summaray:
To use the Text attribute of a button (in this case), you have to make a button object first, then allocate the child object (with index i) to this button object. Then you are able to use the button text property.
The buttons in the hBoxItems were made under program control (at runtime), that’s why I have to call them in this inderect way, as children of the parent container.

openSUSE Leap 42.1 en Gambas2, Gambas3

Update 03/2016:
Ondertussen draait Gambas2 IDE (2.24) perfect op LEap 42.1!

* Een update van openSUSE 13.1 naar openSUSE Leap:
– Gambas2 programma’s compileren en draaien via runtime ok.
– Gambas IDE’s opstarten: werkt niet (meer), te checken.

Opgelet; Trek geen andere conclusie; dit is dus geen “verse installatie” van Leap; kan een verschil maken!
(inderdaad, na nieuwe installatie of updates was het in orde, zie boven)

Klik en sleep van TableView naar GridView

Klik en sleep
De eenvoudige voorbeelden van klik en sleep met de muis, zoals die waarbij een tekst van een TextBox naar een TextLabel worden gesleept, schieten te kort als je een TableView of een GridView gebruikt (beiden even roosters genoemd). Immers: je kan een heel rooster slepen naar een ander rooster en daar allerlei leuke dingen mee doen, maar meestal is dat niet de bedoeling.

Cel in plaats van rooster
Je wil een bepaald vakje, één cel, of eerder nog de inhoud daarvan, naar een andere plaats slepen. De bestemming is dan niet een element als TextBox of GridView, maar ook weer een bepaalde cel van een TableView of GridView rooster.

Drag..
De basis blijft hetzelfde:
Voor de vertrekplaats moet een _MouseDrag() gemaakt worden:

PUBLIC SUB TableView1_MouseDrag()
  IF Mouse.Left
    Drag.Icon = PictureBox1.Picture
    TableView1.Drag(TableView1[TableView1.Row, TableView1.Column].Text)
  ENDIF
END

Ik heb in de IDE op voorhand een PictureBox1 op FMain gezet, er in de IDE een beeldje voor gekozen uit de verzameling (stock), en die PictureBox1.Visible op FALSE gezet.

.. en Drop
De bestemming moet als eigenschap Drop = TRUE hebben:

GridView1.Drop = TRUE

en er moet drop code gemaakt worden:

PUBLIC SUB GridView1_Drop()
  GridView1[GridView1.RowAt(Drag.y), GridView1.ColumnAt(Drag.X)].Text = Drag.Data
END

Om de data in de Text te krijgen van de gewenste cel op rij, kolom

GridView1[row, column].Text = Drag.Data

moet je die coördinaten eerst detecteren:

GridView1.RowAt(Drag.y)
GridView1.ColumnAt(Drag.X)

Dit is de kleine “truuk” om het klikken en slepen tussen TableViews en GridViews mogelijk te maken.
Er zit nog wel een addertje onder het gras: als je een beetje buiten de bestaande cellen sleept en daar in het gridview gebied onder de laatste rij cellen bv loslaat, zijn de coördinaten niet gedefineerd. Crash!

Dus:
TRY GridView1[GridView1.RowAt(Drag.y), GridView1.ColumnAt(Drag.X)].Text = Drag.Data

Verder moet je de code aanvullen met alles wat moet gebeuren, zoals opslaan in een databank, berekeningen enz.

Rechter muisklik

Als je wil reageren op de rechtermuisklik kan dat in volgende twee stappen:

PUBLIC SUB SchermObject_MouseDown() 
  '
  IF Mouse.Right
    ' Doe iets
  ENDIF 
  '
END

Veelgebruikt is extra informatie of een extra optie geven voor het met de muis aangewezen object.

Om de gebruiker in te lichten vul je de “tooltip” in van het aangewezen object, zodat de extra mogelijkheid uitgelegd wordt alvorens geklikt is.

Voorbeeld: een rooster met gegevens van werknemers (Tableview met Employee data):

Twee andere procedures moeten bestaan;

  • newDataAvailable() AS BOOLEAN ‘ checkt of er nieuwe data is
  • doReloadData() ‘ haalt nieuwe data af en vult ze in de tabel
PUBLIC SUB tbvwEmpl_MouseDown() 'tbvwEmpl_DblClick()
  '
  IF Mouse.Right
    IF newDataAvailable()
      tbvwEmpl.Clear()
      doLoadData()
    ELSE 
      Message.Info("Up to date already")
    ENDIF  
  ENDIF 
  '
END

Data module met universele procedure om record toe te voegen

Gambas Modules of minstens stukken ervan kan je zo universeel maken dat je ze gemakkelijk kan hergebruiken.
Bij gebruik van een databank (bv MySQL/MariaDB) moet je altijd code schrijven om een record aan te maken. Het gemeenschappelijke daarin is minstens het toevoegen van een record met een sleutel (record id).

Je kan de aanmaak van een nieuw record (dus met nieuwe gegevens) opsplitsen in twee delen:

  • Aanmaken van een leeg record met een nieuw bekomen record nummer.
  • Het aanvullen van dat nieuwe record met alle andere gegevens, een update dus.

De update procedure moet je waarschijnlijk toch ook maken om gegevens te kunnen wijzigen aan een record waarvan je de id kent.

Het aanroepend programma kan gewoon een “ModData.saveRecord(id, data)” gebruiken, de module splitst het op in twee delen.
Als de id gegevens is, wordt de update procedure aangeroepen.
Als de id = 0 moet er een nieuw record gemaakt worden, daarna de update procudure met de ondertussen verkregen id.

Een datamodule ModData bevat:

PRIVATE sLastError AS String
' ...
PUBLIC SUB addRecord(sTablename AS String, sKeyname AS String) AS Long
' adds a record to a given database, get the record id of the empty record 
  DIM sSql AS String  
  DIM myResult AS Result
 ' 
  sSql = "INSERT INTO " & db.Quote(sTablename) & " (" & sKeyname & ") VALUES(0)" 
  DEBUG sSql
  '
  TRY $hConData.Exec(sSql)
  IF ERROR 
    sLastError &= "insert; " & Error.Text
    RETURN 0
  ELSE 
    sSql = "SELECT LAST_INSERT_ID() AS myId "
    'myId" ' FROM " & db.Quote(sTablename)
    TRY myResult = $hConData.Exec(sSql)
    IF ERROR 
      sLastError &= "last id ?" & Error.Text
      RETURN 0
    ELSE 
      RETURN myResult!myId 
    ENDIF 
  ENDIF 
  DEBUG sLastError
END

De procedure geeft het aangemaakte recordnummer terug. Daarna kunnen de gegevens ingevuld worden, wat minder universeel is. Veel kans dat dit stuk code enkel intern vanuit de module aangeroepen wordt, je zou ze dus ook PRIVATE kunnen maken.
Fouten vraagt het aanroepend programma/aanroepende procedure op met getLastError(), wat geimplementeerd is als

PUBLIC SUB getLastError() AS String
  '
  RETURN sLastError
  '
END

Alle procedures in de datamodule die een record moeten toevoegen in een database, kunnen deze zelfde functie gebruiken, mits aangeroepen met de juiste parameters voor de betreffende tabel.

Voorbeeld:

# Name Type Null Default Extra
1 emp_id bigint(20) No None AUTO_INCREMENT
2 emp_data1 int(11) No 0  
3 emp_data2 int(11) No 0  
4 emp_data3 char(1) Yes NULL  
5 emp_data4 varchar(11) Yes NULL  
6 emp_data5 datetime Yes NULL  
7 emp_data6 datetime Yes NULL  
8 emp_cre timestamp Yes CURRENT_TIMESTAMP  
9 emp_creby char(24) Yes NULL  
10 emp_upd datetime Yes NULL  
11 emp_updby char(24) Yes NULL  

Dit werkt op voorwaarde dat

  • de key record_id een getal is, bv van het type bigint(20)
  • de waarde van het veld record_id altijd automatisch wordt toegekend door de database (Auto Increment)
  • er standaardwaarden voorzien zijn in de struktuur van de tabel (0, NULL, ..).

Als geen standaardwaarden voorzien zijn krijg je een foutmelding; de database krijgt enkel een opdracht om de key in te vullen en weet niet wat er met de andere velden moet gebeuren.

Andere dingen die je kan standaardiseren:

  • Een foutmelding bijhouden (zie ook hierboven sLastError) en op laten vragen.
  • Een record verwijderen dat een bepaalde keywaarde heeft.
  • Opbouwen, openen en sluiten van de databaseverbinding
  • Een keuze tussen verschillende datasets voorzien (bv laptop/netwerk, ontwikkeling/productie)
  • enz.

Metadata
Dikwijls is het nuttig om te weten wanneer een record ontstaan is (creation date-time), en wie het gemaakt heeft (user). Als je daarvoor standaard velden opneemt in de database kan je die laten invullen vanuit deze record toevoegen-procedure. Het eenvoudigste voor het “createdon” veld is in de database als standaardwaarde “CURRENT_TIMESTAMP” in te stellen. Dan blijft nog het “createdby” veld, waar we de gebruikerslogin naam kunnen invullen (gb.user.name). Ofwel laat je een waarde opslaan die als parameter van de oproepende procedure komt, en kan het een andere vorm van gebruikers-id zijn, bv die waarmee in de applicatie ingelogd wordt.

  • Database: employee
  • Velden: id, naam, address, …, createdon, createdby
  • Database: department
  • Velden: id, building, floor, …, createdon, createdby

Dit is universeel; je kan employee.createdby of department.createdby enz. gebruiken; bij de AddRecord is het steeds dezelfde naam. Je moet dat dan voor alle tabellen zo doen!

Een andere mogelijkheid is de createdby optioneel te maken, zodat je ook nog minimale tabellen met enkel id kan houden.
En: als je niet de standaardnaam gebruikt, maar velden met een prefix voor de tabel gebruikt, moet je de veldnaam ergens vandaan halen.

  • Database: employee
  • Velden: emp_id, emp_naam, emp_address, …, emp_createdon, emp_createdby
  • Database: department
  • Velden: dep_id, dep_building, dep_floor, …, dep_createdon, dep_createdby

Gecombineerd:

PUBLIC SUB addRecord(sTablename AS String, sKeyname AS String, OPTIONAL sCreatedByField AS String) AS Long 
' adds a record to a given database, get the record id of the empty record 
  DIM sSql AS String  
  DIM myResult AS Result
 '
  sSql = "INSERT INTO " & db.Quote(sTablename) & " (" & sKeyname 
  IF sCreatedByField
    sSql &= "," & sCreatedByField & ") VALUES(0,&1)" ' & db.Quote(User.Name) & ")"
  ELSE 
    sSql &= ") VALUE(0)" 
  ENDIF 
  '
  DEBUG sSql
  '
  TRY $hconData.Exec(sSql, User.Name)
  IF ERROR 
    sLastError &= "insert; " & Error.Text
    RETURN 0
  ELSE 
    sSql = "SELECT LAST_INSERT_ID() AS myId "
    '
    TRY myResult = $hConData.Exec(sSql)
    IF ERROR 
      sLastError &= "last id ?" & Error.Text
      RETURN 0
    ELSE 
      RETURN myResult!myId 
    ENDIF 
  ENDIF 
  DEBUG sLastError
END 

De procedure AddRecord krijgt een optionele parameter “sCreatedByField”.

Als die ingevuld is, wordt dat veld gebruikt om de gebruikersnaam in te bewaren; ‘User.Name’ kan je in Gambas altijd gebruiken en geeft de loginnaam van de gebruiker die het programma draait. Hier wordt die ingevuld vanuit AddRecord, zodat er van het oproepend programma niet aan gedacht moet worden, of ook de waarde niet vergeten of verkeerd ingevuld kan worden; zelfs als de update nadien niet werkt heb je al meta-informatie.

Als geen veldnaam wordt doorgegeven om de createdby informatie in op te slaan, wordt enkel de record_id ingevuld.

Upd: Ps: misschien gebruik ik ook beter geen tabelnaam-PREFIX in de veldnamen … (http://gambas.copyleft.be/blog/archives/1453)