Archiwum miesiąca: kwiecień 2017

Layout dla każdego (QWidgetu) czyli co zrobić, gdy kontrolki wewnątrz layoutu nie skalują się wraz z oknem

Dziś trochę o layoutach i definiowaniu interfejsu graficznego. Po dodaniu kilku kontrolek do layoutu zauważyłem, że podczas skalowania okna znajdujące się w jego wnętrzu kontrolki nie zmieniają swojego rozmiaru i kształtu. Efekt, który chciałem osiągnąć to skalowanie kontrolek wewnątrz okna wraz ze zmianą rozmiaru okna. Chwila w kwakania w Duck Duck Go zaprowadziła mnie do rozwiązania problemu. Mianowicie każde okno (QMainWindow) i każdy obszar typu QScrollArea powinien mieć ustawiony layout. W edytorze interfejsu po prawej stronie mamy okno z drzewem widżetów. Dla nowo stworzonego projektu zaraz pod elementem MainWindow znajduje się centralWidget. Jest to serce całego interfejsu, najważniejszy widget w oknie. Po jego lewej stronie znajduje się ikonka layoutu z czerwonym znacznikiem informującym, że layout nie został ustawiony. Aby ustawić layout wystarczy kliknąć prawym przyciskiem myszy na MainWindow → Lay Out → Lay Out Horizontaly (bądź inny wybrany przez nas layout). Można też tego dokonać klikając na MainWindow a następnie ikonkę Lay Out Horizontaly na pasku ponad oknem edycji GUI albo używając skrótu klawiszowego CTRL + H.

Po ustawieniu layoutu zawartość okna powinna zmieniać rozmiar razem z nim:

Simple Drawing Application – prosta aplikacja QT5 znaleziona w Internecie

Znalazłem w Internecie całkiem schludnie napisaną a jednocześnie w miarę prostą aplikację w QT 5 z użyciem widgetów. Aplikacja ta korzysta z frameworka testów jednostkowych Catch. Autor inspirował się książką Roberta C. Martina – Agile Software Development, Principles, Patterns, and Practices. Osobiście czytałem inną książkę Roberta C. Martina – Czysty kod, którą przy okazji mogę polecić gdyż w prosty sposób przedstawia jak pisać czytelny i łatwy do zrozumienia kod.
Link do aplikacji na GitHubie:
https://github.com/bruceoutdoors/DrawingApp

Migracja na nowy serwer i nowa domena.

31 marca otrzymałem wiadomość, że darmowy serwer hostingowy elektroda.eu kończy swoją działalność. Domena została wyłączona praktycznie z dnia na dzień. Zmusiło mnie to do poszukiwań nowego hostingu i domeny. Postanowiłem wykupić domenę, aby uniezależnić się od humorów darmowych dostawców. Sytuacja ta ma także swoje zalety, gdyż dzięki temu dowiedziałem się jak skonfigurować ustawienia DNS, jak podpiąć domenę pod hosting (w panelu rejestratora domeny edycja wpisu typu A z adresem IP serwera hostingowego) oraz że na współdzielone konto hostingowe można dostać się poprzez adres: ip serwera/~nazwa_użytkownika.

Testy jednostkowe w Qt Creatorze

W tym poście zostanie przedstawiona struktura projektu zawierającego aplikację oraz testy jednostkowe. Przedstawię dwa przykłady. Pierwszy z nich będzie oparty o wbudowaną bibliotekę QtTest, natomiast w drugim przykładzie zostanie wykorzystana biblioteka Catch, która jest lekka i składa się tylko z jednego pliku nagłówkowego.

Struktura projektu

TemplateProj struktura.png

Projekt (TemplateProj) składa się z dwóch podprojektów: standardowego projektu aplikacji (app) oraz projektu zawierającego testy jednostkowe (tests). Główny projekt (TemplateProj) to tak naprawdę tylko kontener na pozostałe projekty. Korzysta z szablonu TEMPLATE = subdirs i definiuje jedynie nazwy podkatalogów projektu. CONFIG += ordered informuje kompilator, że wymienione podkatalogi powinny być przetwarzane w takiej kolejności w jakiej są wymienione. Zapewnia to poprawną kompilację, ponieważ projekt tests korzysta z elementów projektu app. Zawartość pliku TemplateProj:

TEMPLATE = subdirs

CONFIG += ordered

SUBDIRS = \
    app \
    tests

Projekt app jest generalnie standardowym projektem aplikacji, takim jaki można stworzyć w Qt Creatorze z szablonu nowy projekt. Wpis CONFIG += console umożliwia prawidłowe działanie programu, gdy odpalamy program w terminalu zamiast domyślnego okna application output. Brak tego wpisu powoduje, że po odpaleniu aplikacji okno konsoli jest puste i wyświetla tylko standardowe „Press Return to close this window”. Zmiana opcji „Run in terminal” jest dostępna w karcie Projects → Run Settings osobno dla każdej aplikacji. DESTDIR = $$PWD/../build ustawia katalog z plikami wynikowymi na build w katalogu głównym projektu. Można tę linię pominąć, wtedy pliki wynikowe pojawią się w domyślnych katalogach debug / release.

QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

CONFIG += console

TARGET = TemplateProj
TEMPLATE = app

DESTDIR = $$PWD/../build

HEADERS += \
    Calculator.h

SOURCES += \
    main.cpp \
    Calculator.cpp

W głównym projekcie app dodajmy plik main.cpp oraz przykładową klasę Calculator:

#include 
#include 

int main(int argc, char *argv[])
{
	std::cout << "Example test project" << std::endl;
	Calculator calc;
	std::cout << "2 + 6 = " << calc.add(2,6) << std::endl;
	
	return 0;
}
#ifndef CALCULATOR_H
#define CALCULATOR_H

class Calculator
{
public:
	int add(int a, int b);
};

#endif // CALCULATOR_H
#include "Calculator.h"

int Calculator::add(int a, int b)
{
	return a + b;
}

Do tego miejsca wszystkie elementy są niezależne od użytej technologii testów. Natomiast projekt z testami jest już dostosowywany do danej biblioteki testowej.

QtTest

QtTest to biblioteka dostarczana wraz z całym pakietem Qt, więc stworzenie projektu zawierającego testy jednostkowe jest możliwe bez wprowadzania dodatkowych zależności od zewnętrznych bibliotek.
Najprostszy plik projektu tests dla QtTest wygląda następująco:

QT += testlib

TARGET = TemplateProjUnitTests

SOURCES += TestMain.cpp \
    ../app/Calculator.cpp
    
INCLUDEPATH += \
    ../app

Plik TestMain.cpp (omówienie):

#include 
#include "Calculator.h"

class TestMain: public QObject
{
	Q_OBJECT
private slots:
	void adder();
};

void TestMain::adder()
{
	Calculator c;
	QCOMPARE(c.add(3,5), 8);
	QCOMPARE(c.add(3,10), 13);
}

QTEST_MAIN(TestMain)
#include "TestMain.moc"

Link do kompletnego projektu: Qt-simple-examples/ProjectStructureQTest

Catch

Catch to biblioteka testów dla C++. Jej zaletami są liberalna licencja, brak zależności od innych bibliotek oraz dystrybucja w postaci jednego pliku nagłówkowego.
Najprostszy plik projektu tests dla Catch wygląda następująco (chociaż działa także bez linijki TEMPLATE = app, ale została ona pozostawiona dla formalności):

QT += core

TARGET = TemplateProjUnitTests
TEMPLATE = app

CONFIG += console

SOURCES += TestMain.cpp \
    CalculatorTest.cpp \
    ../app/Calculator.cpp

INCLUDEPATH += \
    ../app \
    ../lib

Plik TestMain.cpp - odpowiada za prawidłowe wywołanie pozostałych plików źródłowych z testami. Zawiera w sobie definicje jednego makra i include biblioteki Catch. Makro CATCH_CONFIG_MAIN powinno zostać wywołane tylko w jednym miejscu projektu.

#define CATCH_CONFIG_MAIN
#include "catch.hpp"

Właściwy plik z testami:

#include "catch.hpp"
#include "Calculator.h"

TEST_CASE("add test") {
	Calculator c;

	REQUIRE(c.add(3,5) == 8);
	REQUIRE(c.add(3,10) == 13);
}

Link do kompletnego projektu: Qt-simple-examples/ProjectStructureCatch
O strukturze projektu: link
Przykład aplikacji wykorzystującej bibliotekę Catch: link

Potencjalne problemy

  • qmake exited with code 2 - prawdopodobnie ścieżka projektu jest zbyt długa - wystarczy przenieść projekt do katalogu o krótszej ścieżce
  • inne błędy przy kompilacji - kliknąć prawym przyciskiem na projekcie → Run qmake a następnie Rebuild