Подготовка рабочего места
Весь код я писал под Gentoo-linux в Qt Creator. Если вы решите делать так же, то для начала придётся подготовить окружение под наши нужды.
Первое, что надо сделать — это создать проект. Я использовал «Простой проект на С++» без поддержки Qt. Хотя Wt можно использовать совместно с Qt, в данном случае нам это не понадобится.
Я назвал проект MyApp. Далее откроем файл MyApp.pro и немного его отредактируем. А именно, в моём случае, надо добавить такие строки:
LIBS += -L/usr/lib -lwt -lwthttp -lwtdbo -lwtdbosqlite3 -I/usr/local/include
LIBS += -L/usr/local/lib -lwthttp -lwt -lboost_regex -lboost_signals
LIBS += -lboost_system -lboost_thread -lboost_filesystem -lboost_date_time -lpthread -lcrypt
Ещё нам надо будет слинковать папку resources в то место, откуда мы будем запускать наш сайт. Например, так:
ln -s /usr/share/Wt/resources/ ~/build-MyApp-Qt5-Отладка/
Подготовку на этом можно считать законченной.
Итак, начнём…
Чтобы запустить пустое приложение, достаточно будет написать такой код:
#include <iostream> #include <Wt/WServer> #include <Wt/WApplication> using namespace Wt; WApplication *createWidget(const WEnvironment& env) { WApplication *app=new WApplication(env); //Создаём пустое веб-приложение app->setTitle("My Site"); //Устанавливаем заголовок страницы (то же, что в html <title></title>) app->setTheme(new Wt::WBootstrapTheme()); //Будем использовать стили bootstrap return app; } int main(int argc, char **argv) { Wt::WServer server(argv[0]); //Создаём сервер server.setServerConfiguration(argc, argv, WTHTTP_CONFIGURATION); //Конфигурируем его server.addEntryPoint(Wt::Application,createWidget); //Добавляем "точку входа" if (server.start()) { int sig = Wt::WServer::waitForShutdown(); //Если заработало, ждём завершения std::cerr << "Shutting down: (signal = " << sig << ")" << std::endl; server.stop(); } }
Мы можем запустить всё это на localhost на порту 8080 командой:
./myapp --docroot . --http-address 0.0.0.0 --http-port 8080
Процесс запуска можно автоматизировать, для этого параметры запуска нужно прописать в строке «Проекты->Запуск->Параметры»
Таким образом, мы получаем веб-сервер с пустой страничкой, доступ к которой можно получить по адресу http://localhost:8080/
Добавим меню.
Приступим к заполнению. Для начала сделаем меню для нашего сайта. Для этого создадим новый класс, я назвал его «Page».
page.h:
#ifndef PAGE_H #define PAGE_H #include <Wt/WContainerWidget> #include <Wt/WText> #include <Wt/WString> #include <Wt/WNavigationBar> #include <Wt/WMenu> #include <Wt/WStackedWidget> class Page : public Wt::WContainerWidget { public: Page(WContainerWidget *parent = 0); private: Wt::WStackedWidget *contentsStack; Wt::WNavigationBar *NavBar; Wt::WMenu *LeftMenu; }; #endif // PAGE_H
page.cpp:
#include "page.h" using namespace Wt; Page::Page(WContainerWidget *parent):WContainerWidget(parent) { setStyleClass("container"); contentsStack = new Wt::WStackedWidget(); //В этом "контейнере" будет появляться виджет, соответствующий текущему пункту меню contentsStack->setStyleClass("container"); contentsStack->setPadding(48,Wt::Top); //Потому, что меню будет фиксированным сверху и закроет собой часть этого "контейнера" NavBar = new WNavigationBar(this); //Сама полосочка меню NavBar->setTitle("MyApp","http://localhost:8080"); //Красивый заголовок перед пунктами меню, а заодно ссылка NavBar->setResponsive(true); //Наша полоска будет адаптироваться под размер экрана NavBar->addStyleClass("container"); //Стиль bootstrap NavBar->setPositionScheme(Wt::Fixed); //Делаем полоску меню фиксированной LeftMenu=new WMenu(contentsStack,this); //Создаём новое меню. Заметьте, это именно пункты меню. NavBar->addMenu(LeftMenu); //Вставляем пункты меню в полоску LeftMenu->addItem(WString::fromUTF8("Новости"),new WText(WString::fromUTF8("Здесь будут новости"))); //Добавляем в меню пункт "Новости". addWidget(contentsStack); //Мы создавали контейнер без родителя, и он не будет отображаться, пока мы его куда-нибудь не добавим. }
Заодно надо добавить строчку в main.cpp
... WApplication *createWidget(const WEnvironment& env) { WApplication *app=new WApplication(env); app->setTitle("My Site"); app->setTheme(new Wt::WBootstrapTheme()); new Page(app->root()); //Вот эту! return app; } ...
Проверяем:
Допустим. А где же новости?
Сейчас и до них дойдём. Для начала нам надо научиться работать с БД.
В Wt работа с БД, как по мне, реализована довольно странно, но, когда я разобрался с принципом, особых проблем и сложностей у меня больше не возникало.
Первое, что необходимо — это создать шаблон нашей новости в виде класса.
news.h:
#ifndef NEWS_H #define NEWS_H #include <Wt/WDateTime> #include <Wt/Dbo/Types> #include <Wt/Dbo/WtSqlTraits> class News { public: News(); std::string title; //Заголовок новости std::string text; //Текст новости std::string author; //Автор новости Wt::WDateTime created; //Дата создания template<class Action> //А это наш шаблон для записей БД void persist(Action& a) { Wt::Dbo::field(a, title, "title"); Wt::Dbo::field(a, text, "text"); Wt::Dbo::field(a, author, "aurhor"); Wt::Dbo::field(a, created, "created"); } }; DBO_EXTERN_TEMPLATES(News); #endif // NEWS_H
news.cpp:
#include "news.h" #include <Wt/Dbo/Impl> DBO_INSTANTIATE_TEMPLATES(News); News::News(): created(Wt::WDateTime::currentDateTime()) //Здесь можно указать поля, заполняемые автоматически, например дата и время создания новости. {}
«Хм, и как этим пользоваться?» — спросите вы. Всё очень просто. Я использовал sqlite3 по некоторым причинам (переносимость вместе с проектом и потому, что поддержка MySQL в Wt у меня по неизвестным причинам не собралась), но аналогично должно работать с любой поддерживаемой БД.
Переписываем page.cpp:
#include "page.h" #include "news.h" #include <Wt/Dbo/backend/Sqlite3> #include <Wt/WApplication> #include <Wt/WPanel> using namespace Wt; Page::Page(WContainerWidget *parent):WContainerWidget(parent) { Wt::Dbo::backend::Sqlite3 DataBase=WApplication::instance()->appRoot() + "myapp.db"; //Задаём файл БД Wt::Dbo::Session DBSession; //Создаём сессию DBSession.setConnection(DataBase); //Подключаемся DBSession.mapClass<News>("news"); //Ассоциируем наш класс с таблицей news в БД Wt::Dbo::Transaction transaction(DBSession); //Создаём транзакцию try { DBSession.createTables(); //Если таблицы в БД пока нет, создаём её таким вот простым образом. Все поля будут созданы автоматически. Wt::log("info") << "Database created"; } catch (...) { Wt::log("info") << "Using existing database"; } transaction.commit();//Записываем изменения Wt::WTable *NewsTable=new Wt::WTable();//Создаём таблицу для вывода новостей NewsTable->addStyleClass("container");//Делаем таблицу нормальной ширины Wt::Dbo::Transaction transaction1(DBSession);//Ещё одна транзакция. Их нужно по одной на каждый запрос, насколько я понял. Wt::Dbo::collection<Wt::Dbo::ptr<News> > a=DBSession.find<News>().orderBy("created desc");//Забираем наши новости из БД, отсортированные по дате и времени создания. int j=0; for (Wt::Dbo::collection<Wt::Dbo::ptr<News> >::const_iterator i = a.begin(); i != a.end(); ++i) {//Заполняем таблицу новостями. Элементы в таблице заранее создавать не надо, они создаются автоматически при первом обращении к ним. Почти бэйсик =) Wt::Dbo::ptr<News> Article = *i; Wt::WPanel *panel = new Wt::WPanel(); panel->setTitle(Article.get()->title); panel->setCentralWidget(new Wt::WText(WString::fromUTF8("<p>Автор:")+Article.get()->author+"</p><p>"+Article.get()->text+"</p>")); NewsTable->elementAt(j,0)->addWidget(panel); j++; } transaction1.commit();//Закрыли транзакцию. Кстати, повторно транзакции использовать нельзя. setStyleClass("container"); contentsStack = new Wt::WStackedWidget(); contentsStack->setStyleClass("container"); contentsStack->setPadding(48,Wt::Top); NavBar = new WNavigationBar(this); NavBar->setTitle("MyApp","http://cursed.redegrade.net"); NavBar->setResponsive(true); NavBar->addStyleClass("container"); NavBar->setPositionScheme(Wt::Fixed); LeftMenu=new WMenu(contentsStack,this); NavBar->addMenu(LeftMenu); LeftMenu->addItem(WString::fromUTF8("Новости"),NewsTable); //Прикручиваем нашу табличку с новостями к пункту меню. this->addWidget(contentsStack); }
Ну, по поводу вывода новостей всё понятно, а как же создание?
Хм, и правда. Куда без создания. Сейчас прикрутим. Придётся переписать класс Page ещё раз, заключительный в пределах статьи.
page.h:
#ifndef PAGE_H #define PAGE_H #include <Wt/WContainerWidget> #include <Wt/WText> #include <Wt/WString> #include <Wt/WNavigationBar> #include <Wt/WMenu> #include <Wt/WTable> #include <Wt/WStackedWidget> #include <Wt/Dbo/Session> class Page : public Wt::WContainerWidget { public: Page(WContainerWidget *parent = 0); private: Wt::WStackedWidget *contentsStack; Wt::WNavigationBar *NavBar; Wt::WMenu *LeftMenu; Wt::WTable *CreateArticle; Wt::WLineEdit *Title; Wt::WLineEdit *Author; Wt::WTextArea *Text; Wt::WPushButton *AddNews; Wt::Dbo::Session DBSession; void AddArticle(); }; #endif // PAGE_H
page.cpp
#include "page.h" #include "news.h" #include <Wt/WApplication> #include <Wt/WPanel> #include <Wt/WLineEdit> #include <Wt/WTextEdit> #include <Wt/WPushButton> #include <Wt/Dbo/backend/Sqlite3> using namespace Wt; Page::Page(WContainerWidget *parent):WContainerWidget(parent) { Wt::Dbo::backend::Sqlite3 DataBase=WApplication::instance()->appRoot() + "myapp.db"; DBSession.setConnection(DataBase); DBSession.mapClass<News>("news"); Wt::Dbo::Transaction transaction(DBSession); try { DBSession.createTables(); Wt::log("info") << "Database created"; } catch (...) { Wt::log("info") << "Using existing database"; } transaction.commit(); Wt::WTable *NewsTable=new Wt::WTable(); NewsTable->addStyleClass("container"); Wt::Dbo::Transaction transaction1(DBSession); Wt::Dbo::collection<Wt::Dbo::ptr<News> > a=DBSession.find<News>().orderBy("created desc"); int j=0; for (Wt::Dbo::collection<Wt::Dbo::ptr<News> >::const_iterator i = a.begin(); i != a.end(); ++i) { Wt::Dbo::ptr<News> Article = *i; Wt::WPanel *panel = new Wt::WPanel(); panel->setTitle(WString::fromUTF8(Article.get()->title)); panel->setCentralWidget(new Wt::WText(WString::fromUTF8("<p>Автор:")+WString::fromUTF8(Article.get()->author)+"</p><p>"+WString::fromUTF8(Article.get()->text)+"</p>"));//Немного исправил вывод. Из коробки норовит выводить русский текст знаками вопроса. NewsTable->elementAt(j,0)->addWidget(panel); NewsTable->elementAt(j,0)->setLoadLaterWhenInvisible(true); j++; } transaction1.commit(); setStyleClass("container"); contentsStack = new Wt::WStackedWidget(); contentsStack->setStyleClass("container"); contentsStack->setPadding(48,Wt::Top); NavBar = new WNavigationBar(this); NavBar->setTitle("MyApp","http://localhost:8080"); NavBar->setResponsive(true); NavBar->addStyleClass("container"); NavBar->setPositionScheme(Wt::Fixed); LeftMenu=new WMenu(contentsStack,this); NavBar->addMenu(LeftMenu); LeftMenu->addItem(WString::fromUTF8("Новости"),NewsTable); CreateArticle=new Wt::WTable(); //Табличка для формы создания новости Title=new Wt::WLineEdit(WString::fromUTF8("Заголовок новости")); //Всякие поля Author=new Wt::WLineEdit(WString::fromUTF8("Автор")); Text=new Wt::WTextArea(WString::fromUTF8("Текст новости")); AddNews=new Wt::WPushButton(WString::fromUTF8("Добавить новость")); CreateArticle->addStyleClass("container"); CreateArticle->elementAt(0,0)->addWidget(Title); //Добавляем поля в табличку CreateArticle->elementAt(1,0)->addWidget(Author); CreateArticle->elementAt(2,0)->addWidget(Text); CreateArticle->elementAt(3,0)->addWidget(AddNews); LeftMenu->addItem(WString::fromUTF8("Создать новость"),CreateArticle); //Создаём кнопку и привязываем метод Page::AddArticle к её нажатию AddNews->clicked().connect(this,&Page::AddArticle); this->addWidget(contentsStack); } void Page::AddArticle() { Wt::Dbo::backend::Sqlite3 DataBase=WApplication::instance()->appRoot() + "myapp.db"; DBSession.setConnection(DataBase); //Переподключаемся (долго с этим бился, возможно, стоило сделать всё переменными класса, а не локальными) Wt::Dbo::Transaction transaction(DBSession); //Повторюсь: для каждой операции с БД нужна живая транзакция. News *a=new News(); //Создаём новость a->title=Title->text().toUTF8(); a->author=Author->text().toUTF8(); a->text=Text->text().toUTF8(); Wt::Dbo::ptr<News> Article=DBSession.add(a); //Заносим новость в БД }
Результат:
P.S. Если необходимы дополнительные пояснения, пишите в комментариях, постараюсь рассказать подробней. Я только начал изучать Wt, поэтому, вполне возможно, где-то неправ. Буду рад, если поправите.
P.P.S. Если вам интересны статьи по Wt, пишите в комментариях, что ещё вы хотели бы о нём узнать, и я постараюсь написать об этом!
ссылка на оригинал статьи http://habrahabr.ru/post/209754/
Добавить комментарий