Russian Qt Forum

Qt => Общие вопросы => Тема начата: Odyssey от Июль 20, 2011, 12:50



Название: Запуск программы только один раз (QtSingleApplication)
Отправлено: Odyssey от Июль 20, 2011, 12:50
В похожей теме для запуска одного и только одного экземпляра программы предлагается использовать класс QtSingleApplication, который нужно предварительно скачать и установить. Вопросы:

1) Где его скачать? Искал в сети, но, похоже, этот класс теперь бесплатно не доступен. (Или плохо смотрел?)
2) Можно ли просто сбросить в каталог проекта файлы класса .cpp и .h, а потом написать #include "qtsingleapplication.h" ?


Название: Re: Запуск программы только один раз (QtSingleApplication)
Отправлено: GreatSnake от Июль 20, 2011, 12:57
http://doc.qt.nokia.com/solutions/4/qtsingleapplication/qtsingleapplication.html


Название: Re: Запуск программы только один раз (QtSingleApplication)
Отправлено: Fess от Июль 20, 2011, 13:16
Там около 10 файлов с исходниками, можете закинуть к себе и пробовать скомпилировать это все вместе. Но лучше все же собрать ее отдельно.
configure скрипт в наличии, в INSTALL & README все подробно описано.
Можно собирать как длл (опция -library), так и сделать статическую сборку.
Для подключения есть готовые pri файлы


Название: Re: Запуск программы только один раз (QtSingleApplication)
Отправлено: Odyssey от Июль 20, 2011, 13:44
Извините, что туплю, но ведь на странице по ссылке GreatSnake имеется только описание класса, а не файлы самого класса?


Название: Re: Запуск программы только один раз (QtSingleApplication)
Отправлено: Igore от Июль 20, 2011, 14:13
На сайте nokia есть поиск
http://qt.nokia.com/products/qt-addons/solutions-archive/
http://qt.gitorious.org/qt-solutions/qt-solutions/trees/master


Название: Re: Запуск программы только один раз (QtSingleApplication)
Отправлено: Fess от Июль 20, 2011, 16:03
Как вариант:
ftp://ftp.qt.nokia.com/qt/solutions/lgpl/qtsingleapplication-2.6_1-opensource.zip (ftp://ftp.qt.nokia.com/qt/solutions/lgpl/qtsingleapplication-2.6_1-opensource.zip)


Название: Re: Запуск программы только один раз (QtSingleApplication)
Отправлено: GreatSnake от Июль 20, 2011, 19:05
Извините, что туплю, но ведь на странице по ссылке GreatSnake имеется только описание класса, а не файлы самого класса?
Сорри, это я не ту ссылку дал :(


Название: Re: Запуск программы только один раз (QtSingleApplication)
Отправлено: Odyssey от Июль 21, 2011, 10:51
Благодарю! Буду пробовать :)


Название: Re: Запуск программы только один раз (QtSingleApplication)
Отправлено: SeverusSnape от Август 12, 2011, 14:14
Так и нужно использовать


Название: Re: Запуск программы только один раз (QtSingleApplication)
Отправлено: kambala от Март 23, 2012, 15:13
чтобы новую тему не создавать, спрошу тут.

Я не могу собрать QtSingleApplication в студии 2010. Делаю как и написано: добавляю исходники в свой проект, подключаю QtNetwork и использую QtSingleApplication вместо QApplication. При построении сыпется туча ошибок о том, что класс QtLockedFile не существует (указывает на qtlockedfile.cpp), хотя он точно в проекте и #include "qtlockedfile.h" в нём есть.

Теперь более интересное. При построении самого QtSingleApplication из креатора тем же компилятором всё проходит ок. Далее я взял один из примеров, засунул его исходники (без файла .pro) в другую папку, кинул туда исходники QtSingleApplication (только .h/.cpp), добавил всё это добро в чистый .pro файл, подключил нужные библиотеки - собралось. Дальше решил импортировать этот рабочий проект в студию - всё прошло гладко и всё работает, но зачем-то были добавлены зависимости от ActiveQt Server/Container Library (без них тоже собирается).

И последний тест: создал дефолтный Qt проект в студии и сбросил в него исходники из предыдущего рабочего проекта, подключив нужные библиотеки - и фэил (те же ошибки про QtLockedFile). Пытался сравнить файлы .vcxproj рабочего и нерабочего проекта, но там слишком огромная разница чтобы понять что не так. Помогите пожалуйста разобраться какой настройки не хватает. Прикрепляю оба проекта (рабочий и нерабочий).

P.S. Мой проект собирается в креаторе.


Название: Re: Запуск программы только один раз (QtSingleApplication)
Отправлено: Alex Custov от Март 23, 2012, 15:29
А каталог qtsingleapplication есть в списке каталогов, где искать заголовки? (аналог -I в gcc или INCLUDEPATH в qmake)


Название: Re: Запуск программы только один раз (QtSingleApplication)
Отправлено: kambala от Март 23, 2012, 15:44
не было, дописал (см. скрин) - не помогло. также пробовал писать $(ProjectDir) вместо .


Название: Re: Запуск программы только один раз (QtSingleApplication)
Отправлено: kambala от Март 23, 2012, 17:48
добился некого прогресса: закомментировав все упоминания QtLP_Private, а также не совсем понятные мне инклуды цпп-файлов в qtlocalpeer.cpp, проект начал собираться в студии, зато перестал линковаться в креаторе:
Цитировать
qtlocalpeer.obj:-1: error: LNK2019: unresolved external symbol "public: virtual __thiscall QtLockedFile::~QtLockedFile(void)" (??1QtLockedFile@@UAE@XZ) referenced in function __unwindfunclet$??0QtLocalPeer@@QAE@PAVQObject@@ABVQString@@@Z$0
qtlocalpeer.obj:-1: error: LNK2019: unresolved external symbol "public: __thiscall QtLockedFile::QtLockedFile(void)" (??0QtLockedFile@@QAE@XZ) referenced in function "public: __thiscall QtLocalPeer::QtLocalPeer(class QObject *,class QString const &)" (??0QtLocalPeer@@QAE@PAVQObject@@ABVQString@@@Z)
qtlocalpeer.obj:-1: error: LNK2019: unresolved external symbol "public: bool __thiscall QtLockedFile::lock(enum QtLockedFile::LockMode,bool)" (?lock@QtLockedFile@@QAE_NW4LockMode@1@_N@Z) referenced in function "public: bool __thiscall QtLocalPeer::isClient(void)" (?isClient@QtLocalPeer@@QAE_NXZ)
qtlocalpeer.obj:-1: error: LNK2019: unresolved external symbol "public: bool __thiscall QtLockedFile::isLocked(void)const " (?isLocked@QtLockedFile@@QBE_NXZ) referenced in function "public: bool __thiscall QtLocalPeer::isClient(void)" (?isClient@QtLocalPeer@@QAE_NXZ)
если раскомментировать инклуды, ситуация получается с точностью до наоборот, только в студии 10 неразрешённых символов


Название: Re: Запуск программы только один раз (QtSingleApplication)
Отправлено: Igors от Март 23, 2012, 18:48
Побеждать MSVC - довольно нудное дело, может соскочить в нативняк? (там несложно)


Название: Re: Запуск программы только один раз (QtSingleApplication)
Отправлено: kambala от Март 23, 2012, 18:52
если ничего не получится, то придётся.

для винды я видел, что несложно - помню это обсуждалось совсем недавно. а как с этим обстоят дела в мак ос?


Название: Re: Запуск программы только один раз (QtSingleApplication)
Отправлено: Igors от Март 23, 2012, 19:18
для винды я видел, что несложно - помню это обсуждалось совсем недавно. а как с этим обстоят дела в мак ос?
Там нет "instance" - второй раз копию пользователь не запустит (во всяком случае из Finder). Если же надо пресекать запуск приложения с тем же именем, то я делал через

::GetCurrentProcess
::GetNextProcess
::GetProcessInformation

Может есть и лучший способ, но и это работает


Название: Re: Запуск программы только один раз (QtSingleApplication)
Отправлено: kambala от Март 23, 2012, 19:41
спасибо за информацию, может когда-нибудь пригодится :)

а со студией всё оказалось просто: надо было просто не добавлять файлы qtlockedfile*.cpp в проект.

но кто-то может объяснить зачем используется такая магия в qtlocalpeer.cpp?
Код
C++ (Qt)
namespace QtLP_Private {
#include "qtlockedfile.cpp"
#if defined(Q_OS_WIN)
#include "qtlockedfile_win.cpp"
#else
#include "qtlockedfile_unix.cpp"
#endif
}


Название: Re: Запуск программы только один раз (QtSingleApplication)
Отправлено: kambala от Март 24, 2012, 16:06
QtSingleApplication использует следующий код для активации существующей копии приложения:
Код
C++ (Qt)
void QtSingleApplication::activateWindow()
{
   if (actWin) {
       actWin->setWindowState(actWin->windowState() & ~Qt::WindowMinimized);
       actWin->raise();
       actWin->activateWindow();
   }
}
но ассистент нам ясно даёт понять, что под виндой такой трюк плохо работает:
Цитировать
On Windows, if you are calling this when the application is not currently the active one then it will not make it the active window. It will change the color of the taskbar entry to indicate that the window has changed in some way. This is because Microsoft does not allow an application to interrupt what the user is currently doing in another application.
поэтому я добавил WinAPI код для явного показа окна (идея взята из исходников Notepad++, за что автору большое спасибо):
Код
C++ (Qt)
if (sendMessage("hello world"))
{
#ifdef Q_WS_WIN32
   extern const QString qt_getRegisteredWndClass();
 
   const QString windowClassName = qt_getRegisteredWndClass(); // usually returns "QWidget"
   HWND existingWindowHandle = NULL;
   for (int i = 0; i < 10; ++i) // assume there're 10 different Qt windows maximum at the moment
   {
       HWND qtWindowHandle = ::FindWindow(windowClassName.utf16(), NULL);
       if (qtWindowHandle)
       {
           WCHAR captionWstr[100];
           if (::GetWindowText(qtWindowHandle, captionWstr, 100))
           {
               QString caption = QString::fromWCharArray(captionWstr);
               qDebug("found Qt window with caption %s", qPrintable(caption));
               if (caption.endsWith(qApp->applicationName())) // this condition is for my application
               {
                   qDebug("this is our window");
                   existingWindowHandle = qtWindowHandle;
                   break;
               }
           }
           else
               qDebug("failed to get caption of window %#x", qtWindowHandle);
       }
       else
       {
           qDebug("WTF?!?!?!");
           break;
       }
       Sleep(100);
   }
 
   if (existingWindowHandle)
   {
       ::ShowWindow(existingWindowHandle, ::IsIconic(existingWindowHandle) ? SW_RESTORE : SW_SHOW);
       ::SetForegroundWindow(existingWindowHandle);
   }
#endif
   return;
}
этот код вызывается из конструктора класса-наследника QtSingleApplication, но его можно вынести и в main(). правда тут есть одно ограничение: сравниваются заголовки окон, что теоретически не всегда может быть приемлемым. по идее можно перерегистрировать класс окна со своим именем вместо стандартного "QWidget", но я решил с этим не заморачиваться.

и ещё я не уверен насчёт недокументированной функции qt_getRegisteredWndClass() - вдруг в будущем она исчезнет. по-хорошему надо пользоваться ::GetClassName() :)


Название: Re: Запуск программы только один раз (QtSingleApplication)
Отправлено: Alex Custov от Март 25, 2012, 18:42
У меня в main() сделано так:

Код
C++ (Qt)
AllowSetForegroundWindow(ASFW_ANY);
 
if(app.sendMessage("wake up"))
   return 0;
 
// normal startup
...
 

Через AllowSetForegroundWindow разрешается другим процессам ставить свои окна в foreground. Это будет работать при условии, что вторая запущенная копия вашей программы будет сама в foreground (насколько я понимаю, в винде это всегда так). Таким образом, в первой копии программы эта функция ни на что не влияет, я в во второй копии разрешает первой копии поставить своё окно на передний план. Проверял в XP и в 7, работает.


Название: Re: Запуск программы только один раз (QtSingleApplication)
Отправлено: kambala от Март 25, 2012, 19:11
спасибо, действительно работает!

но я заметил один недостаток у данного подхода: если первая копия покажет месседж бокс сразу после активации, то ни окно, ни месседж бокс, на первый план не выходят (в 7, в ХР ещё не смотрел). думаю надо ещё добавить вызов ShowWindow() и/или SetForegroundWindow().


Название: Re: Запуск программы только один раз (QtSingleApplication)
Отправлено: kambala от Март 25, 2012, 19:52
желаемого эффекта добился двумя путями:
Код
C++ (Qt)
void Application::activateWindow()
{
   QtSingleApplication::activateWindow();
   ::ShowWindow(_mainWindow->winId(), SW_SHOW);
   ::SetForegroundWindow(_mainWindow->winId());
}
Код
C++ (Qt)
void Application::activateWindow()
{
   ::ShowWindow(_mainWindow->winId(), ::IsIconic(_mainWindow->winId()) ? SW_RESTORE : SW_SHOW);
   ::SetForegroundWindow(_mainWindow->winId());
}


Название: Re: Запуск программы только один раз (QtSingleApplication)
Отправлено: kambala от Март 25, 2012, 21:09
дело оказалось даже не в этом, т.к. предыдущий код тоже на самом деле не помог. при более пристальном изучении внутренностей QtSingleApplication я заметил, что
Код
C++ (Qt)
connect(peer, SIGNAL(messageReceived(const QString&)), SIGNAL(messageReceived(const QString&)));
совершается в конструкторе, а
Код
C++ (Qt)
connect(peer, SIGNAL(messageReceived(const QString&)), this, SLOT(activateWindow()));
только в setActivationWindow(), т.е. позже. у меня в приложении было
Код
C++ (Qt)
setActivationWindow(_mainWindow);
connect(this, SIGNAL(messageReceived(const QString &)), _mainWindow, SLOT(loadFile(const QString &)));
значит когда отправлялся сигнал messageReceived(), то сначала грузился файл, вылазил месседжбокс, а потом уже только шёл вызов activateWindow() - видно тут собака и зарылась. также иногда наблюдал артефакты с появлением окна (на мгновение появлялось и исчезало, или месседжбокс 2 раза подряд вылезал), но я это списывал на особенности винды, а теперь понял что к чему :)

в итоге пришлось чуть схитрить, чтобы загружать файл после показа окна:
Код
C++ (Qt)
setActivationWindow(_mainWindow);
connect(this, SIGNAL(messageReceived(const QString &)), SLOT(setParam(const QString &))); // просто сохраняем параметр
...
void Application::activateWindow()
{
   QtSingleApplication::activateWindow();
   _mainWindow->loadFile(_param);
}
и теперь всё отрабатывает идеально.