PyQt5-opplæring med eksempler: Design GUI med PyQt in Python

Hva er PyQt?

PyQt er en python-binding av åpen kildekode-widget-verktøysettet Qt, som også fungerer som et rammeverk for applikasjonsutvikling på tvers av plattformer. Qt er en populær C++ rammeverk for å skrive GUI-applikasjoner for alle større stasjonære, mobile og innebygde plattformer (støtter Linux, Windows, Mac os, Android, iOS, Raspberry Pi og mer).

PyQt er en gratis programvare utviklet og vedlikeholdt av Riverbank Computing, et selskap basert i England, mens Qt er utviklet av et finsk firma kalt The Qt Company.

Funksjoner av PyQT

Her er viktige funksjoner i PyQt:

Lær PyQt som består av mer enn seks hundre klasser som dekker en rekke funksjoner som f.eks

  • Grafiske brukergrensesnitt
  • SQL Databaser
  • Nettverktøysett
  • XML-behandling
  • nettverk

Disse funksjonene kan kombineres for å lage avanserte brukergrensesnitt så vel som frittstående applikasjoner. Mange store selskaper på tvers av alle bransjer bruker Qt. Noen eksempler er LG, Mercedes, AMD, Panasonic, Harman, etc.

PyQt-versjoner

PyQt er tilgjengelig i to utgaver, PyQt4 og PyQt5. PyQt4 gir limkode for binding av 4.x- og 5.x-versjoner av Qt-rammeverket, mens PyQt5 gir en binding for kun 5.x-versjonene. Som et resultat er PyQt5 ikke bakoverkompatibel med de utdaterte modulene til den eldre versjonen. I denne Qt GUI-opplæringen vil PyQt5 bli brukt til å demonstrere eksempler. Bortsett fra disse to versjonene,

Riverbank Computing gir også PyQt3D – python-bindingene for Qt3D-rammeverket. Qt3D er et applikasjonsrammeverk som brukes til å lage sanntidssimuleringssystemer med 2D/3D-gjengivelse.

Hvordan installere PyQt5

I denne PyQt5-opplæringen vil vi se de to måtene å installere PyQt på:

  • Bruke hjulfiler
  • Bygge og installere fra kilden

Qt (uttales søt) er et komplekst system, og PyQt-kodebasen inneholder kompilerte C++ og Python kode under panseret. Som et resultat er det en komplisert prosess å bygge og installere den fra kilden sammenlignet med andre python-biblioteker. Du kan imidlertid enkelt installere PyQt5 ved hjelp av hjul.

Montering med hjul

Hjul er den nye standarden Python emballasje og distribusjonsformat. Enkelt sagt er et hjul et ZIP-arkiv med et spesielt navn og .whl filtypen. Hjul kan installeres ved hjelp av pip (Python's pakkebehandling), som er inkludert som standard i de siste versjonene av Python.

Så hvis du har Python 3.4 eller senere installert, har du allerede pip. Hvis du derimot bruker en eldre versjon av Python, må du laste ned og installere pip før du går videre. Du kan søke etter instruksjoner for det på denne lenken: https://pypi.org/project/pip/.

For å installere PyQt5,

Trinn 1) Åpne ledeteksten.
Åpne ledeteksten eller PowerShell i din Windows maskin.

installer PyQt5

Trinn 2) Skriv inn følgende.

 pip install PyQt5

Trinn 3) Installasjonen var vellykket.
Dette trinnet i denne PyQt5-opplæringen vil laste ned PyQt5 whl-pakken (ca. 50 MB) og installere den på systemet ditt.

installer PyQt5

Alternativt kan du også laste ned en Windows binær for versjonen av python installert på datamaskinen din.

Når den er fullført, fortsett til neste del i denne PyQt5-opplæringen for å skrive din første GUI-app.

Grunnleggende PyQt Concepts og programmer

Nå som du har installert PyQt5 på datamaskinen din, er du klar til å skrive Python GUI-designapplikasjoner.

La oss starte med en enkel app i denne PyQt5-opplæringen som viser et tomt vindu på skjermen din.

Fyr opp pytonslangen din IDLE og skriv inn følgende:

Program 1

import sys
from PyQt5.QtWidgets import QApplication, QWidget
if __name__ == "__main__":
    app = QApplication(sys.argv)
    w = QWidget()
    w.resize(300,300)
    w.setWindowTitle("Guru99")
    w.show()
    sys.exit(app.exec_())

Lagre den som app.py (navnet spiller ingen rolle) og trykk F5 for å kjøre programmet. Alternativt kan du bare dobbeltklikke på den lagrede filen for å starte programmet. Hvis du har gjort alt riktig, åpnes et nytt vindu med tittelen Guru99 som vist nedenfor.

Grunnleggende PyQt Concepts

Stor! Det fungerer. Det er ikke mye, men det er nok til å forstå det grunnleggende. La oss nå i denne PyQt-opplæringen se i detalj hva hver av linjene i programmet ditt gjør.

from PyQt5.QtWidgets import QApplication, QWidget

Denne setningen importerer alle modulene du trenger for å lage en GUI til det gjeldende navneområdet. QtWidgets-modulen inneholder alle de viktigste widgetene du skal bruke i denne Python Qt opplæring.

app = QApplication(sys.argv)

Her lager du et objekt av QApplication-klassen. Dette trinnet er en nødvendighet for PyQt5; hver UI-app må lage en forekomst av QApplication, som et slags inngangspunkt til appen. Hvis du ikke oppretter det, vil feil vises.

sys.argv er listen over kommandolinjeparametere som du kan sende til applikasjonen når du starter den gjennom skallet eller mens du automatiserer grensesnittet.

I dette PyQt5-eksemplet sendte du ingen argumenter til QApplications. Derfor kan du også erstatte den med koden nedenfor og ikke engang trenger å importere sys-modulen.

app = QApplication([])
w = QWidget()

Deretter lager vi et objekt av QWidget-klassen. QWidget er basisklassen for alle UI-objekter i Qt, og praktisk talt alt du ser i en app er en widget. Det inkluderer dialoger, tekster, knapper, stolper og så videre. Funksjonen som lar deg designe komplekse brukergrensesnitt er at widgetene kan nestes, dvs. du kan ha en widget inne i en widget, som er inne i enda en widget. Du vil se dette i aksjon i neste avsnitt.

w.resize(300,300)

Resize-metoden til QWidget-klassen lar deg angi dimensjonene til en hvilken som helst widget. I dette tilfellet har du endret størrelsen på vinduet til 300px med 300px.

Her bør du huske at widgets kan være nestet sammen, den ytterste widgeten (dvs. widgeten uten foreldre) kalles et vindu.

w.setWindowTitle("Guru99")

Metoden setWindowTitle() lar deg sende en streng som et argument som vil sette tittelen på vinduet til strengen du sender. I PyQt5-eksemplet vil tittellinjen vise Guru99.

w.show()

show() viser ganske enkelt widgeten på skjermen.

sys.exit(app.exec_())

App.exec_()-metoden starter Qt/C++ hendelsessløyfe. Som du vet er PyQt stort sett skrevet inn C++ og bruker hendelsesløkkemekanismen for å implementere parallell utførelse. app.exec_() overfører kontrollen til Qt som vil avslutte applikasjonen bare når brukeren lukker den fra GUI. Det er grunnen til at ctrl+c ikke avslutter programmet som i andre python-programmer. Siden Qt har kontroll over appen, behandles ikke python-hendelser med mindre vi setter dem opp inne i applikasjonen. Vær også oppmerksom på at exec-metoden har en understreking i navnet; dette er fordi exec() allerede var et nøkkelord i python og understreken løser navnekonflikter.

Utover tomme vinduer

I forrige seksjon så du hvordan du lager en grunnleggende widget i Qt. Det er nå på tide å lage noen mer involverte grensesnitt som brukerne virkelig kan samhandle med. Igjen, fyr opp IDLE og skriv følgende.

import sys
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QPushButton, QMessageBox

def dialog():
    mbox = QMessageBox()

    mbox.setText("Your allegiance has been noted")
    mbox.setDetailedText("You are now a disciple and subject of the all-knowing Guru")
    mbox.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel)
            
    mbox.exec_()

if __name__ == "__main__":
    app = QApplication(sys.argv)
    w = QWidget()
    w.resize(300,300)
    w.setWindowTitle("Guru99")
    
    label = QLabel(w)
    label.setText("Behold the Guru, Guru99")
    label.move(100,130)
    label.show()

    btn = QPushButton(w)
    btn.setText('Beheld')
    btn.move(110,150)
    btn.show()
    btn.clicked.connect(dialog)

    
    w.show()
    sys.exit(app.exec_())

Lagre filen som appone.py eller noe du liker og trykk F5 for å kjøre programmet. Hvis du ikke har gjort noen feil, IDLE vil åpne et nytt vindu med litt tekst og en knapp som vist nedenfor.

Utover tomt Windows

  1. Når du klikker på knappen i det første vinduet, åpnes en ny meldingsboks med teksten du har skrevet.
  2. Du kan nå klikke på Skjul detaljer/Vis detaljer-knappen for å veksle mellom synligheten av tilleggstekst.

Som du kan se, siden vi ikke hadde satt noen vindustittel i meldingsboksen, ble en standardtittel gitt av python selv.

Nå som det fungerer, la oss ta en titt på ekstrakoden du har lagt til i det forrige PyQt5-eksemplet.

from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QPushButton, QMessageBox

Dette importerer noen flere widgets som du har brukt i PyQt5-eksempler, nemlig QLabel, QPushButton og QMessageBox.

def dialog():
    mbox = QMessageBox()

    mbox.setText("Your allegiance has been noted")
    mbox.setDetailedText("You are now a disciple and subject of the all-knowing Guru")
    mbox.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel)
            
    mbox.exec_()

Her har du definert en metode kalt dialog som lager en meldingsboks-widget og setter litt tekst til knappene og andre felt.

Dialogmetoden kalles fra hovedblokken til programmet når en knapp trykkes i en spesifikk widget (i dette tilfellet btn-trykkknappen). Klikkhendelsen som utløses på den knappen får denne funksjonen til å utføres. En slik funksjon kalles slot i Qt, og du vil lære mer om signaler og spilleautomater i de kommende avsnittene.

if __name__ == "__main__":
    app = QApplication(sys.argv)
    w = QWidget()
    w.resize(300,300)
    w.setWindowTitle("Guru99")

Dette er hoveddelen av appen og som i forrige eksempel starter du med å lage en forekomst av QApplication etterfulgt av en enkel widget, altså en forekomst av QWidget.

label = QLabel(w)
    btn = QPushButton(w)

Du har lagt til to nye widgets i denne applikasjonen: QLabel og QPushButton. QLabel brukes til å skrive ut ikke-redigerbar tekst eller plassholdere inne i en widget, mens QPushButton brukes til å lage en klikkbar knapp.

Det kritiske å legge merke til her er at når du oppretter etikett- og btn-objektene, sender du vindusobjektet (w) til konstruktørene til QLabel og QPushButton. Dette er hvordan nesting fungerer i PyQt5. For å lage en widget i en annen widget, sender du referansen til den overordnede widgeten til barnets konstruktør.

label.move(100,130)
btn.move(110,150)

move() brukes til å angi posisjonen til en widget i forhold til dens overordnede widget. I det første tilfellet flyttes etiketten 100 piksler fra venstre og 130 piksler fra toppen av vinduet.

På samme måte vil knappen være plassert 110px fra venstre og 150px fra toppen av vinduet. Dette eksemplet er en grov måte å oppnå oppsett og brukes vanligvis ikke i produksjon; den er kun inkludert her for læringsformål. Qt støtter forskjellige oppsett som du vil se i detalj i de kommende delene av denne PyQt-opplæringen.

btn.clicked.connect(dialog)

Til slutt er dette et eksempel på signaler og spor i Qt. I GUI-baserte applikasjoner utføres funksjoner basert på handlingene som utføres av brukeren, som å holde musepekeren over et element eller klikke på en knapp. Disse handlingene kalles hendelser. Husk at app.exec_()-metoden overfører kontroll til Qt begivenhet-løkke. Dette er hva hendelsessløyfen er der for: å lytte etter hendelser og utføre handlinger som svar.

Når en hendelse inntreffer, som at en bruker klikker på en knapp, vil den tilsvarende Qt-widgeten heve en signalisere. Disse signalene kan kobles til python funksjoner (som dialogfunksjonen i dette eksemplet) slik at funksjonen utføres når et signal utløses. Disse funksjonene kalles spilleautomater på Qt-språk.

Deretter er den grunnleggende syntaksen for å utløse en sporfunksjon som svar på signalet fra en hendelse som følger

 widget.signal.connect(slot)

Hvilket betyr at når en signalisere utløses av en widget, den tilkoblede slot funksjonen vil bli utført. Oppsummert brukes signaler og spor av Qt for å kommunisere mellom objekter og lette gjenbruk av komponenter og interaktivitet.

Nå som du vet hvordan du nester widgets og implementerer interaksjoner ved hjelp av signaler og spor, her er en liste over nyttige widgets og andre klasser som du kan bruke i PyQt-appene dine.

Komponenter og widgets

Det er et stort antall widgets tilgjengelig i PyQt for å lage GUI-apper. Men med PyQt5 har det vært en omstokking av klasser til forskjellige moduler og revisjoner i lisensene.

Derfor er det avgjørende å ha et overblikk over strukturen til PyQt5. I denne delen vil du se hvordan PyQt5 er organisert internt og lære om de forskjellige modulene, bibliotekene og API-klassene som tilbys av PyQt5.

PyQt5 katalogstruktur

PyQt5 katalogstruktur

Dette er de grunnleggende modulene som brukes av Pythonsin Qt-binding, spesifikt PyQt5.

  • Qt: Den kombinerer alle klassene/modulene nevnt nedenfor til en enkelt modul. Det øker minnet som brukes av applikasjonen betraktelig. Det er imidlertid enklere å administrere rammeverket ved å bare importere én modul.
  • QtCore: Inneholder de ikke-grafiske kjerneklassene som brukes av andre moduler. Det er her Qt-hendelsesløkken, signaler og spor-tilkobling osv. implementeres.
  • QtWidgets: Inneholder de fleste widgetene som er tilgjengelige i Pyqt5.
  • QtGui: Inneholder GUI-komponenter og utvider QtCore-modulen.
  • QtNetwork: Inneholder klasser som brukes til å implementere nettverksprogrammering gjennom Qt. Den støtter TCP-servere, TCP-sockets, UDP-sockets, SSL-håndtering, nettverksøkter og DNS-oppslag.
  • QtMultimedia gir multimediefunksjonalitet på lavt nivå.
  • QtSql: implementerer databaseintegrasjon for SQL-databaser. Støtter ODBC, MySQL, Oracle, SQLiteog PostgreSQL.

PyQt5-widgets

Her er en liste over de mest brukte widgetene i PyQt5

  • QLineEdit: Dette er et inndatafelt som lar brukeren skrive inn én tekstlinje.
    line = QLineEdit()
  • QRadio-knapp: Dette er et inndatafelt med en valgbar knapp, lik radioknappene i html.
    rad = QRadioButton("button title")
    rad.setChecked(True)  #to select the button by default.
  • QComboBox: Den brukes til å vise en rullegardinmeny med en liste over valgbare elementer.
    drop = QComboBox(w)
    drop.addItems(["item one", "item two", "item three"])
  • QSjekkBox: Viser en valgbar firkantet boks foran etiketten som er krysset av hvis den er valgt, lik radioknapper.
    check = QCheckBox("button title")
  • QMenuBar: den viser en horisontal menylinje øverst i et vindu. Du kan bare legge til objekter av QMenu-klassen til denne linjen. Disse QMenu-objektene kan videre inneholde strenger, QAction-objekter eller andre QMenu-objekter.
  • QToolBar: Det er en horisontal stang eller rute som kan flyttes innenfor vinduet. Den kan inneholde knapper og andre widgets.
  • QTab: den brukes til å dele opp innholdet i et vindu i flere sider som kan nås via forskjellige faner på toppen av widgeten. Den består av to deler: fanelinjen og fanesiden.
  • QScrollBar: Den brukes til å lage rullefelt som lar brukeren rulle opp og ned i et vindu. Den består av en bevegelig glidebryter, et skyvespor og to knapper for å rulle skyveknappen opp eller ned.
    scroll = QScrollBar()
  • QSplitter: Splittere brukes til å skille innholdet i et vindu slik at widgetene er riktig gruppert og ikke ser rotete ut. QSplitter er en av de primære layoutbehandlerne som er tilgjengelige i PyQt5 og brukes til å dele innholdet både horisontalt og vertikalt.
  • QDock: En dock-widget er et undervindu med to egenskaper:
  • Den kan flyttes innenfor hovedvinduet og
  • Den kan forankres utenfor foreldrevinduet til et annet sted på skjermen.

Oppsett og temaer

I de forrige PyQt5-eksemplene har du bare brukt metodene move() og resize() for å angi posisjonene til widgets i GUI-en.

PyQt har imidlertid en robust layoutstyringsmotor som kan brukes til å lage avanserte brukergrensesnitt for applikasjoner. I denne delen vil du lære om to viktige klasser som brukes i Qt for å lage og administrere oppsett.

  1. QBoxLayout
  2. QGridLayout

QBoxLayout

QBoxLayout brukes til å justere de underordnede widgetene til oppsettet i en horisontal eller vertikal rad. De to interesseklassene som arver fra QBoxLayouten er:

  • QHBoxLayout: brukes til å sette underordnede widgets horisontalt.
  • QVBoxLayout: Brukes til å sette underordnede widgeter vertikalt.

For eksempel er dette hvordan tre knapper er justert med QHBoxLayout vil se ut.

QBoxLayout

import sys
from PyQt5.QtWidgets import *

if __name__ == "__main__":

    app = QApplication([])
    w = QWidget()
    w.setWindowTitle("Musketeers")

    btn1 = QPushButton("Athos")
    btn2 = QPushButton("Porthos")
    btn3 = QPushButton("Aramis")

    hbox = QHBoxLayout(w)

    hbox.addWidget(btn1)
    hbox.addWidget(btn2)
    hbox.addWidget(btn3)

    w.show()

    sys.exit(app.exec_())

Og slik vil de se ut i QVBoxLayout.

QBoxLayout

import sys
from PyQt5.QtWidgets import *

if __name__ == "__main__":

    app = QApplication([])
    w = QWidget()
    w.setWindowTitle("Musketeers")

    btn1 = QPushButton("Athos")
    btn2 = QPushButton("Porthos")
    btn3 = QPushButton("Aramis")

    vb = QVBoxLayout(w)

    vb.addWidget(btn1)
    vb.addWidget(btn2)
    vb.addWidget(btn3)

    w.show()

    sys.exit(app.exec_())

Den eneste funksjonen som trenger noen forklaring på dette tidspunktet er addWidget()-metoden. Den brukes til å sette inn widgets i HBox eller VBox layout. Den brukes også i andre oppsett der den tar et annet antall parametere som du vil se i neste avsnitt. Widgetene vises i oppsettet i den rekkefølgen du setter dem inn.

QGridLayout

QGridLayout brukes til å lage grensesnitt der widgetene er lagt ut i form av et rutenett (som en matrise eller 2D-array). For å sette inn elementer i et rutenettoppsett, kan du bruke matriserepresentasjonen til å definere antall rader og kolonner i rutenettet samt plasseringen av disse elementene.

For eksempel, for å lage et 3*3 rutenett (dvs. et rutenett med tre rader og tre kolonner), skriver du følgende kode:

Import sys
from PyQt5.QtWidgets import *

if __name__ == "__main__":
    app = QApplication([])

    w = QWidget()

    grid = QGridLayout(w)

    for i in range(3):
        for j in range(3):
            grid.addWidget(QPushButton("Button"),i,j)


    w.show()
    sys.exit(app.exec_())

Dette vil være utgangen:

QGridLayout

AddWidget()-metoden I rutenettoppsettet tar disse argumentene:

  • Widgetobjektet du vil legge til i rutenettet
  • x-koordinaten til objektet
  • Y-koordinaten til objektet
  • Radspennet (standard =0)
  • Kol-spennet (standard=0)

For å forstå det bedre, kan du manuelt sette inn hver widget som vist nedenfor

import sys
from PyQt5.QtWidgets import *

if __name__ == "__main__":
    app = QApplication([])

    w = QWidget()

    grid = QGridLayout(w)
    grid.addWidget(QPushButton("Button one"),0,0)
    grid.addWidget(QPushButton("Button two"),0,1)
    grid.addWidget(QPushButton("Button three"),1,0)
    grid.addWidget(QPushButton("Button four"),1,1)


    w.show()
    sys.exit(app.exec_())

Slik vil rutenettet se ut:

QGridLayout

Du kan også sende parameterne rowspan og colspan til addWidget() for å dekke mer enn én rad eller kolonne.

For eksempel,

grid.addWidget(QPushButton("Button five"),2,0,1,0)

Dette vil lage en knapp som strekker seg over begge kolonnene.

QGridLayout

temaer

PyQt5 kommer med noen innebygde temaer som du kan bruke i appene dine. De setStyle() metode kalt på QApplication-forekomsten brukes til å sette et bestemt tema til applikasjonen din.

Hvis du for eksempel legger til følgende kodelinje, vil temaet for applikasjonen endres fra standard til Fusion

app.setStyle("Fusion")

Slik vil det forrige eksemplet se ut i Fusion Theme

temaer

En annen nyttig funksjon for å tematisere appene dine er setPalette()-metoden. Her er koden for å endre fargen på forskjellige widgets ved å bruke setPalette().

import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import *
from PyQt5.QtGui import QPalette

if __name__ == "__main__":
    app = QApplication([])
    app.setStyle("Fusion")
    
    qp = QPalette()
    qp.setColor(QPalette.ButtonText, Qt.black)
    qp.setColor(QPalette.Window, Qt.black)
    qp.setColor(QPalette.Button, Qt.gray)
    app.setPalette(qp)

    w = QWidget()

    grid = QGridLayout(w)
    grid.addWidget(QPushButton("Button one"),0,0)
    grid.addWidget(QPushButton("Button two"),0,1)
    grid.addWidget(QPushButton("Button three"),1,0)
    grid.addWidget(QPushButton("Button four"),1,1)


    w.show()
    sys.exit(app.exec_())

Her er resultatet.

temaer

For å bruke metoden setPalette() må du først definere en palett. Dette gjøres ved å lage et objekt av QPalette-klassen.

 qp = QPalette()

Legg merke til at QPalette-klassen tilhører QtGui-modulen, og du må importere den for at dette skal fungere. Når du har opprettet QPalette-objektet, bruk setColor()-metoden for å sende navnet på en widget hvis farge du vil endre og fargen du vil angi.

 qp.setColor(QPalette.Window, Qt.black)

Dette vil endre fargen på vinduet til svart. Etter at du har definert fargeskjemaet ditt, bruk setPalette()-funksjonen for å bruke paletten på applikasjonen din.

app.setPalette(qp)

Det er alt du trenger å gjøre hvis du vil lage noen grunnleggende temaer for appen din. PyQt lar deg også bruke stilark for å definere utseendet til widgetene dine. Hvis du er kjent med CSS, kan du enkelt definere avanserte stiler for appen din ved å bruke Qt Style Sheets.

Sammendrag

  • PyQt er python-bindingen for C++ UI-rammeverk, Qt.
  • PyQt4 og PyQt5 er de to hovedversjonene utviklet av Riverbank Computing.
  • Hovedmodulene i PyQt-rammeverket er:
    1. Qt
    2. QtCore
    3. QtWidgets
    4. QtGui
    5. QtSql
    6. QtNetwork
  • PyQt støtter forskjellige widgets som:
    1. knapper
    2. Tekstetiketter
    3. Tekstfelt
    4. Radioknapper og avmerkingsbokser
    5. Verktøylinjer og menylinjer
    6. nettsett
    7. Tabs
    8. Docks
  • I PyQt implementeres interaktivitet vha signaler og spilleautomater.
  • An hendelse er en handling utført av en bruker i GUI (som å klikke på en knapp).
  • A signalisere heves av den tilsvarende widgeten når en hendelse inntreffer på den.
  • A slot er en funksjon som er koblet til signalet og utføres når signalet heves.
  • PyQt har en robust layoutmotor og støtter avansert layoutdesign og administrasjon. Dette er to ofte brukte layoutskjemaer i PyQt:
    1. Box Layout
    2. Grid Layout
  • PyQt-designeren lar deg lage tilpassede temaer for GUI-applikasjoner og har innebygd støtte for stilark.
  • qtcreator Python kan brukes til å lage brukergrensesnitt så vel som frittstående applikasjoner.