Archiv der Kategorie: Qt

Design Update

Ich habe nun die Software und das Design upgedated, jetzt ist es nicht mehr dunkel und nicht mehr so bedrückend. Die Farben mögen dem einen oder anderen vielleicht ungewöhnlich Vorkommen, dennoch finde ich es hat etwas.
Anfang letzten Monats bin ich von Visual Studio 2010 aus Visual Studio 2012 umgestiegen sowie das neue Qt5 damit ausprobiert, jedoch waren die ersten Eindrücke relativ enttäuschend. Das neue VS macht einen guten Eindruck und Funktioniert im Grunde sehr gut jedoch ist es mit Qt nicht wirklich kompatibel. Das Webkit funktioniert zB. gänzlich gar nicht und alle abhängigen Anwendungen stürzten einfach ab. Desweiteren wurden in Qt5 vieles geändert und sehr viele neue Abhängigkeiten sind dadurch entstanden sodass man die erstellte Projekte nur sehr umständlich weiter geben kann.
Nach dieser Erkenntnis habe ich versucht wieder Qt4 mit dem neuen VS zu verwenden was letztlich auch nicht zufriedenstellend Lauffähig war und ich seit dem die Lust an dem ganzen verloren habe.

Euer mage-dev Admin

Sokoslide Game – Contest

sokoslide

Im Forum spieleprogrammierer.de läuft zur Zeit ein Programmier-Contest in dem es gilt ein kleines Spiel Namens Sokoslide mit diversen Algorithmen so schnell wie möglich lösen zu lassen. Genaueres findet ihr in dem dazugehörigen Thread hier.

Ich persönlich werde mich wahrscheinlich nicht mehr mit einer Implementierung, unter anderem da die Veranstaltung nur noch bis zum 3.3 läuft, eines Löse-System beschäftigen. Was ich jedoch mit diesem Beitrag vorstellen möchte, ist ein Programm welches ich bezüglich obigen Anlasses erstellt habe, mit welchem man Maps für das erwähnte Spiel erstellen und auch spielen kann.

Ziel des Spieles ist es die runden Spielsteine in die farblich dazugehörigen Rechteckfelder mit so wenigen Zügen wie möglich zu bewegen. Dabei können die Steine in vier Richtungen bewegt werden, wobei diese in die ausgewählte Richtung solange „rutschen“ bis sie auf ein Hindernis, entweder ein anderer Spielstein oder eine Mauer, treffen.

Die compilierte Binary könnt ihr hier herunterladen und den dazugehörigen Source-Code auf meinem github-Account hier einsehen.

Viel Spaß beim Spielen von Sokoslide

Qt Custom Widget

customwidget

Wer mit Qt arbeitet der hat sich früher oder später schon mal gefragt wie erstelle ich ein eigenes Steuerelement und/oder wie zeige ich meine eigene Grafik in meiner GUI an.
Eins vorweg ich arbeit mit Visual Studio und dem Plugin dafür inklusive des Qt-Designers. Mit dem Qt-Creator sollten die hier Beschriebenen Schritte jedoch ähnlich ablaufen.

In diesem Artikel möchte ich euch zeigen wie einfach es sein kann sowas zu realisieren und wie man das Steuerelement direkt im Qt Designer platziert. Im Bild rechts sieht man einen Schalter der ein Volumenmeter, welches auch von Windows verwendet wird um die Sound- ausgabe zu visualisieren, steuert. Die Programmierung von diesem wird im folgendem Erklärt.

Zunächst erstellen wir uns eine von QWidget abgeleitete Klasse namens QVolumeMeter und deklarieren uns die virtuelle Funktion paintEvent.
Diese Funktion wird von der Elternklasse immer dann aufgerufen wenn das Steuerelement gezeichnet werden soll. Die Header-Datei sollte dann in etwa so aussehen:

#ifndef QVOLUMEMETER_H
#define QVOLUMEMETER_H

#include <QWidget>

class QVolumeMeter : public QWidget
{
	Q_OBJECT

public:
	QVolumeMeter(QWidget *parent);
	~QVolumeMeter();

protected:
	virtual void paintEvent(QPaintEvent *event);
};

Nun sollte man sich zunächst Gedanken machen wie man das was Dargestellt werden soll am besten intern Beschreibt. Ich habe wie es für die meisten Fortschrittsanzeigen gemacht wird mir drei Variablen angelegte mit Start, Stop und Position, jeweils mit dem Typ float. An dieser Stelle ist es relativ egal welchen Typ ihr nehmt ihr solltet nur beachten dass ihr die Verwendete Typen richtig für eure Funktion interpretiert. Nun definieren wir für diese Variablen wie es sich gehört jeweils drei sogenannte Getter und Setter Funktionen mit denen man die Werte auslesen und beschreiben kann. Die Getter Funktionen sollten in diesem Fall klar sein, sie geben einfach den aktuell eingestellten Wert wieder zurück. Die Setter Funktionen führen jedoch in meinem Fall noch die Prüfung der übergebenen Werte durch, zb. damit der Stop-Wert sich nicht vor dem Start-Wert befindet und umgekehrt. Eine mögliche Implementierung könnte dann so aussehen (ich habe für stop und start hier min un max verwendet):

void QVolumeMeter::setLevelMin(float min)
{ 
	this->pLevelMin=min; 
	if (this->pLevelMax<this->pLevelMin)
		qSwap(this->pLevelMax, this->pLevelMin);
	this->update(); 
};

void QVolumeMeter::setLevelMax(float max)
{ 
	this->pLevelMax=max; 
	if (this->pLevelMax<this->pLevelMin)
		qSwap(this->pLevelMax, this->pLevelMin);
	this->update(); 
};

void QVolumeMeter::setLevel(float lvl)
{ 
	this->pLevel=lvl; 
	if (this->pLevel>this->pLevelMax)
		this->pLevel=this->pLevelMax;
	else
	if (this->pLevel<this->pLevelMin)
		this->pLevel=this->pLevelMin;
	this->update(); 
};

Man beachte bei jeder Funktion den letzten Befehl update. Was macht nun dieser Befehl? Es gibt neben update noch den Befehl repaint. Der unterschied zwischen beiden ist, dass repaint das Steuerelement sofort neu zeichnet, update hingegen das Neuzeichnen in „Auftrag“ gibt, sodass wenn mehrere update Befehle eingehen das Steuerelement nur einmal neu gezeichnet wird. Warum benutz ich hier nun update anstelle von repaint? Ganz einfach, wenn man mehrmals diese Funktionen innerhalb einer Aktion aufruft, soll sich das Steuerelement möglichst erst nach dem letzten update Aufruf neu zeichnen um alle neuen Informationen anzuzeigen. Würde man den Befehl repaint verwenden würde dies das Steuerelement jedesmal zum Neuzeichnen veranlassen und das Programm zum stocken bringen, da das Neuzeichnen eines Steuerelementes im vergleich viel Rechenleistung beansprucht. Von daher sollte man darauf achten, dass man das Steuerelement auch nur dann neue Zeichnet wenn es nötig ist.

Kommen wir endlich zum Zeichnen des Steuerelements. Da ich an dieser Stelle ein fertiges Bild auf das Steuerelement male ist die Zeichenroutine auch relativ simple. Ich habe das Bild als Qt-Ressource eingebunden und lade dieses im Konstruktor des eigenen Steuerelements:

pTemplate = new QImage(":/QTestExampleWidget/Resources/volumeMeter.png");

Ich möchte an diese Stelle hinweisen, dass wenn man mehrere Steuerelemente der selben eigenen Klasse verwendet sich dann eine Hilfsklasse schreiben sollte um die Bilder besser zu verwalten, da sonst für jedes Steuerelement das selbe Bild immer wieder geladen wird.

Schließlich wird das Bild in der Zeichenfunktion mit Hilfe eines QPainter Objekts auf das Steuerelement gezeichnet:

void QVolumeMeter::paintEvent(QPaintEvent *event)
{
	if (!pTemplate)
		return;

	QPainter painter(this);
	painter.drawImage(0, 0, *pTemplate, width(), 0);

	float range = (this->pLevelMax - this->pLevelMin);
	float pos = (range-(this->pLevel - this->pLevelMin)/range)* float(this->height()) ;
	QRect rect(0, pos, this->width(), this->height());
	painter.drawImage(rect, *pTemplate, rect);

	painter.end();
}

Mein Bild besteht aus zwei Hälften, die eine Hälfte zeigt den gefüllten Status und die andere den Ungefühlten Status. Die Berechnung oben in der Zeichenfunktion mixt die zwei Hälften jeweils zu einem zusamen sodass man eine Trennung der Hellen und dunklen Flächen erhält und damit eine Art Höhenanzeige.

Um nun das Steuerelement im Qt Designer verwenden zu können deklariere ich noch ein paar Metaeigenschaften und Slotfunktionen:

	Q_PROPERTY(float levelMin READ levelMin WRITE setLevelMin)
	Q_PROPERTY(float levelMax READ levelMax WRITE setLevelMax)
	Q_PROPERTY(float level READ level WRITE setLevel)
...
	void setPercent(int value);

Mit diesen Eigenschaften kann nun im Qt Designer die Eigenschafts-Werte vor der Laufzeit festgelegt werden. Dafür erstellen wir zunächst im Qt Designer ein einfaches QWidget Objekt und klicken mit der rechten Maustaste darauf und dann auf Promote to … im nächsten Fenster geben wir unter new die Daten von unserem eigene Steuerelement an (Bassisklasse, Unser neuer Klassenname sowie den Headerdateinamen) und fügen diese hinzu. Anschließend können wir mit einem klick auf Promote dem Designer sagen dass das ausgewählte Widget das von uns erstellte QVolumeMeter-Widget ist. Es wird jedoch leer angezeigt, das ist normal da der Qt Designer ohne ein Plugin nicht wissen kann wie wir dieses programmiert haben. Im Propertyeditor sehen wir dann auch nichts können jedoch die Eigenschaften die wir für den Metainterpreter hinterlegt haben selbst hinzufügen und verwenden.
Die Zusätzliche setPercent-Funktion hab ich noch hinzugefügt um ein Signal eines QSlider-Objekts direkt mit meinem zu Verknüpfen.

Die fertige Klasse sieht letzten endlich dann so aus:
Header:

#ifndef QVOLUMEMETER_H
#define QVOLUMEMETER_H

#include <QWidget>
#include <QImage>

class QVolumeMeter : public QWidget
{
	Q_OBJECT

	Q_PROPERTY(float levelMin READ levelMin WRITE setLevelMin)
	Q_PROPERTY(float levelMax READ levelMax WRITE setLevelMax)
	Q_PROPERTY(float level READ level WRITE setLevel)
public:
	QVolumeMeter(QWidget *parent);
	~QVolumeMeter();

	float levelMin() const;
	float levelMax() const;
	float level() const;

public slots:
	void setLevelMin(float min);
	void setLevelMax(float max);
	void setLevel(float lvl);
	void setPercent(int value);

protected:
	virtual void paintEvent(QPaintEvent *event);

private:
	QImage *pTemplate;
	float   pLevel;
	float   pLevelMin;
	float   pLevelMax;
};

inline float QVolumeMeter::levelMin() const
{ return this->pLevelMin; };

inline float QVolumeMeter::levelMax() const
{ return this->pLevelMax; };

inline float QVolumeMeter::level() const
{ return this->pLevel; };

inline void QVolumeMeter::setPercent(int value) {
  setLevel(this->pLevelMin + (this->pLevelMax - this->pLevelMin) * (float(value) / 100.f));	
}

#endif // QVOLUMEMETER_H

Und der Sourcecode der Klasse:

#include "qvolumemeter.h"
#include <QPainter>

QVolumeMeter::QVolumeMeter(QWidget *parent)
	: QWidget(parent), pTemplate(0), pLevel(0.5f), pLevelMin(0.f), pLevelMax(1.f)
{
	this->pTemplate = new QImage(":/QTestExampleWidget/Resources/volumeMeter.png");
	if (pTemplate)
	{
		QSize nsize(pTemplate->width()/2, pTemplate->height());
		setMinimumSize(nsize);
		setMaximumSize(nsize);
	}
}

QVolumeMeter::~QVolumeMeter()
{
	delete this->pTemplate;
}

void QVolumeMeter::setLevelMin(float min)
{ 
	this->pLevelMin=min; 
	if (this->pLevelMax<this->pLevelMin)
		qSwap(this->pLevelMax, this->pLevelMin);
	this->update(); 
};

void QVolumeMeter::setLevelMax(float max)
{ 
	this->pLevelMax=max; 
	if (this->pLevelMax<this->pLevelMin)
		qSwap(this->pLevelMax, this->pLevelMin);
	this->update(); 
};

void QVolumeMeter::setLevel(float lvl)
{ 
	this->pLevel=lvl; 
	if (this->pLevel>this->pLevelMax)
		this->pLevel=this->pLevelMax;
	else
	if (this->pLevel<this->pLevelMin)
		this->pLevel=this->pLevelMin;
	this->update(); 
};

void QVolumeMeter::paintEvent(QPaintEvent *event)
{
	if (!pTemplate)
		return;

	QPainter painter(this);
	painter.drawImage(0, 0, *pTemplate, width(), 0);

	float range = (this->pLevelMax - this->pLevelMin);
	float pos = (range-(this->pLevel - this->pLevelMin)/range)* float(this->height()) ;
	QRect rect(0, pos, this->width(), this->height());
	painter.drawImage(rect, *pTemplate, rect);

	painter.end();
}

Im Prinzip ist es also nicht sonderlich schwer sein eigenes Steuerelement zu erstellen. Wer möchte kann nun hingehen und für sich weitere event-Funktinen implementieren um zB. auf Mausklicks etc zu reagieren, siehe dazu auch QWidget Reference. Das Projekt bzw. den Source findet ihr wieder unter meinem github Account: QTestExampleWidget

Schreibt mir doch für was Ihr euch ein eigenes Steuerelement geschrieben habt.

Qt JSON Translator

sht15

JSON steht für JavaScript Object Notation und ist ein Datenformat in lesbarer Textform, welches zum Abspeichern und einlesen von Eigenschaften bzw. Einstellungen sehr gut geeignet ist. Ich habe dieses Format erst vor kurzem kennengelernt und hab mir gedacht, dass man dieses als Sprachdatei mit dem Qt-Framework kombinieren kann. So hab ich mich mal ran gemacht und ein kleines Beispiel-Projekt erstellt. Um den Overhead der Webkit Bibliothek aus dem Weg zu gehen habe ich ein Standalone – Implementierung eines JSON Interpreters für Qt verwendet. Zu finden ist diese hier.
Das Programm selbst ist ein kleiner Rechner mit den Standardfunktionen wie Addition, Subtraktion, Multiplikation und Division; zusätzlich mit einer Einstellbaren Sprache. Verzeiht mir an dieser Stelle, wenn in den Sprachen irgendwelche Fehler zu finden sind, diese habe ich einfach von google Übersetzen lassen.

Wenn man sich das JSON Format mal näher anschaut sieht man relativ schnell wie das ganz Aufbaut ist.

{
  "QJsonTranslatorExampleForm": {
    "QJsonTranslatorExample": "JsonTranslator Beispiel",
	"&Exit": "B&eenden",
	"&File": "&Datei",
	"&Language": "&Sprache",
	"Number One:": "Zahl Eins:",
	"Number Two:": "Zahl Zwei:", 
	"Add": "Addieren",
	"Subtract": "Subtrahieren",
	"Multiply": "Multiplizieren",
	"Divide": "Dividieren",
  },
  "QJsonTranslatorExample": {
  	"Error": "Fehler",
	"Invalid Input": "Falsche Eingabe",
  	"Result": "Ergebnis",
	"The result value is: %1": "Das Ergebnis lautet: %1",
  },
}

Die Struktur besteht aus zwei Teilen, links auf der Seite steht die Eigenschaft und rechts der Wert, jeweils mit zwei Anführungszeichen umhüllt. Des Weiteren kann der Wert selbst auch eine Struktur sein womit man eine Verschachtelung erreicht. In der Sprachdatei wird nun für jede Klasse welche irgendwelche Sprachelemente enthält so eine verschaltete Struktur erstellt.

Das Projekt enthält eine Klasse QJsonTranslator welche sich um das Laden von Sprachdateien und um das Übersetzen von Texten zuständig ist. Mit einem einfachen Aufruf von QJsonTranslator::loadLanguage() wird die Standardsprache geladen/verwendet. Der Aufruf diser sollte geschehen bevor irgendwelche Steuerelemente initialisiert werden. Optional kann man der Funktion einen Sprach Parameter vom Typ QLocale mitgeben. Der Pfad zu den Sprachdateien ist in der konstanten LANGUAGE_STD_PATH gespeichert und ist Standardmäßig auf ‚./lang‘ gesetzt. Die Dateiendung ist in der konstanten LANGUAGE_STD_SUFFIX hinterlegt und hat den Wert, wer hätte’s gedacht, ‚.json‘.

Am Anfang des Programms wird mit der Funktion QJsonTranslatorExample::getAvailableLanguages() alle Verfügbaren Sprachdateien im Sprachordner gesucht und daraus ein Auswahlmenü generiert.

void QJsonTranslatorExample::getAvailableLanguages()
{
	QLocale::Language cur_lang = QJsonTranslator::instance()->locale().language();
	// Auswahlgruppe erstellen
	QActionGroup *language_group = new QActionGroup(this);

	// Alle json-Dateien im Sprachordner ./lang suchen
	QDir dir(LANGUAGE_STD_PATH);
	QStringList list = dir.entryList(QStringList() << QString("*%1").arg(LANGUAGE_STD_SUFFIX));
	list.prepend("en.json");
	// TODO: Eventuelle Duplikate entfernen.

	// Für alle Sprachdateien ein Eintrag im Menü erstellen
	for (int i = 0; i<list.count(); ++i)
	{
		// Den Namen der Sprache extrahieren
		QString name = list[i].left(list[i].length()-QString(LANGUAGE_STD_SUFFIX).length());
		QLocale locale(name);
		QString nativ = locale.nativeLanguageName();

		if (!nativ.isEmpty())
		{
			// Und falls gültig den Eintrag erstellen
			QAction *action = new QAction(this);
			action->setText(nativ);
			// Benutzerdaten setzen, anhand diesen wird naher bei Auswahl die Sprachdatei geladen
			action->setData(name);
			action->setCheckable(true);
			language_group->addAction(action);

			// Wenn die Standartsprache geladen wurde, diese selektieren
			if (cur_lang == locale.language())
				action->setChecked(true);
		}
	}

	// Liste dem Sprachmenü hinzufügen
	ui.menu_Language->addActions(language_group->actions());
}

Wird nun einer dieser Spracheinträge per Menü vom Benutzer ausgewählt dann wird die entsprechende Datei geladen:

if (action->data().type() == QVariant::String)
{
	QLocale newlanguage(action->data().toString());
	QJsonTranslator::loadLanguage(&newlanguage);
}

Wenn die Sprache sich ändert wird an jedes Fenster ein changeEvent mit dem Parameter QEvent::LanguageChange gesendet. In diesem sollte man alle Steuerelemente mit dem neuen Text laden. Dies geschieht üblicherweise mit einem erneut zuweisen eines Textes. Zu Vereinfachung wird von QObject die Funktion QObject::tr(const char * sourceText) zur Verfügung gestellt, welche den sourceText an den Übersetzer, in meinem Fall die Klasse QJsonTranslator, übergibt und den Übersetzen Text zurückgibt.

Es gibt sicherlich noch ein paar Fehlerfälle, die es Abzufangen gilt, jedoch denke ich, dass das Prinzip klar sein sollte. Ansonsten schaut euch das Projekt bzw. den Source, bei Interesse, einfach hier an: QJsonTranslatorExample