Russian Qt Forum

Qt => Пользовательский интерфейс (GUI) => Тема начата: IUnknown от Апрель 17, 2007, 20:00



Название: Проводник файлов
Отправлено: IUnknown от Апрель 17, 2007, 20:00
В программе нужно сделать проводник для файлов, что бы иметь возможность их перетаскивать в проэкт только для Windows (как в Nero). Основные требования - быстрый и красивый. QDirModel не подходит - очень убогий. Делаю свой класс QFileListModel : public QAbstractTableModel. Информация о каждом файле имеет вид
Код:

class FileInfo
{
public:
FileInfo()
{}

FileInfo(const FileInfo& ci)
{
//
m_bSysIcon = ci.m_bSysIcon;
m_icon = ci.m_icon;
m_pathName = ci.m_pathName;
m_bIsUpDir = ci.m_bIsUpDir;
m_FileInfo = ci.m_FileInfo;
//
}

const FileInfo& operator=(const FileInfo& stringSrc)
{
//
m_bSysIcon = stringSrc.m_bSysIcon;
m_icon = stringSrc.m_icon;
m_pathName = stringSrc.m_pathName;
m_bIsUpDir = stringSrc.m_bIsUpDir;
m_FileInfo = stringSrc.m_FileInfo;
//
return *this;
}

virtual ~FileInfo()
{}
//
QString m_pathName;
bool m_bSysIcon;
QIcon m_icon;
bool m_bIsUpDir;
QFileInfo m_FileInfo;
//
};

Получаю список файлов и сохраняю его QList<FileInfo*> m_FileList. В QVariant QFileListModel::data(const QModelIndex &index, int role) const отображаю информацию о файле. В функции которая получает список файлов запускаю поток, который должен подгружать системные иконки. Все вродебы нормально, пока не попробовать перейти в другую директорию, пока в текущей еще не закончилась подгрузка иконок. Вот класс потока:
Код:

QIconsLoaderThread::QIconsLoaderThread(QObject *parent)
: QThread(parent)
{
m_bAbort = false;
}

QIconsLoaderThread::~QIconsLoaderThread()
{
QMutexLocker locker(&m_mutex);
    m_bAbort = true;
}

void QIconsLoaderThread::Load()
{
qDebug()<<"Call Load()";
    QMutexLocker locker(&m_mutex);
    if (!isRunning()) {
m_bAbort = false;
        start();
    }
}

void QIconsLoaderThread::run()
{
qDebug()<<"Call run()";
QFileListModel* pModel = qobject_cast<QFileListModel*>(this->parent());
QMutexLocker locker(&m_mutex);
for(int i=0; i<=pModel->m_FileList.count()-1; i++)
{
if (m_bAbort)
{
pModel = 0;
qDebug()<<"Thread stoped!";
break;
}
FileInfo* pFileInfo = const_cast<FileInfo*>(pModel->m_FileList[i]);
if((!pFileInfo->m_bSysIcon) && (!pFileInfo->m_FileInfo.isDir()))
{
pFileInfo->m_icon = GetIcon(pFileInfo->m_pathName);
pFileInfo->m_bSysIcon = true;
}
pFileInfo = 0;
emit pModel->reset();
}
}

void QIconsLoaderThread::stop()
{
qDebug()<<"Call stop()";
    QMutexLocker locker(&m_mutex);
if(isRunning())
{
m_bAbort = true;
wait();
}
}

Я догадываюсь что они m_FileList не могут поделить, но что не пробую не могу никак синхронизовать. Буду благодарен за помощь.  :)

добавлено спустя 2 минуты:

 А да в начале функции которая заполняет список файлами вызываю    m_pLoaderThread->stop(), а в конце m_pLoaderThread->Load().


Название: Проводник файлов
Отправлено: SABROG от Апрель 17, 2007, 21:11
Можно создать QList с типом QPair, где первый элемент - указатель на модель, второй указатель на нить.
Создадим новую модель и установим в нужный item view.
Перед созданием новой нити пробегаемся по QList'у выхватывая указатель на нити, делаем проверку на ее активность, если нить уже не работает - берем из первого параметра указатель на модель и удаляем указатели на нить и модель, затем удаляем элемент из QList'a.
Создаем новую нить и передаем эту модель ей, добавляем указатели в QList.

Плюс - быстрое переключение между директориями.
Минус - чем быстрее навигация по директориям тем больше кушается память и процессорное время на нитях.


Название: Проводник файлов
Отправлено: IUnknown от Апрель 17, 2007, 22:16
А может можно както мютексами все сделать?


Название: Проводник файлов
Отправлено: SABROG от Апрель 18, 2007, 07:26
Посмотри в сторону QWaitCondition и QSemaphore. Я так понимаю, там метод wait есть, который будет выполняться так долго, пока ресурс не освободит другая нить.
Только вот может быть ситуация, что одна нить подгрузит и расставит одни иконки, а другая дождавшись пока ресурс освободиться - расставит уже другие, в некоторых случаях может произойти перезапись, что нам и нужно, а если в предыдущей директории файлов было больше, то вылезет хвост в виде дополнительных иконок на файлы, которых нет в этой директории.


Название: Проводник файлов
Отправлено: Gryz от Апрель 18, 2007, 16:04
m_bAbort - статический член?

 и m_mutex тоже?

 или какие они?

 пока что, как я понимаю, модель одна для всех директорий?


Название: Проводник файлов
Отправлено: IUnknown от Апрель 18, 2007, 20:09
Не статические, нужно иметь несколько файловых списков (дерево, список). И они должны работать автономно ( не так как в проводнике виндовс). А нить не подгрузит другие иконки. Мне нужно при двойном клике на директорию войти в нее, тоесть старое содержимое m_FileList очищается и заполняется файлами из новой директории. Тоесть мне нужно при клике на директорию завершить процесс подгрузки иконок для текущей(если он сам не завершился - не для всех файлов были вытянуты иконки) и начать новый для новой директории. Хотелось бы при переходе в другую директорию остановить процесс для текущей, дождатся пока он перестанет использовать m_FileList, очистить m_FileList и запустить процесс для директорию в которую я вхожу.


Название: Проводник файлов
Отправлено: SABROG от Апрель 18, 2007, 21:01
Значит перед входом в новую директорию взываешь QThread::terminate() или QThread:exit()/QThread::quit() , потом вызываешь QThread::wait(), чтобы дождаться, пока она *корректно* завершиться.

В первом случае нить прервется в любой точке выполнения, если нет данных которые при этом могут потеряться или записаться неверно или крашнуть прогу, то это лучший выбор, надо не забыть поставить QThread::setTerminationEnabled ( bool enabled = true ). Если надо дождаться выполнения определенных действий, то quit или exit.


Название: Проводник файлов
Отправлено: Gryz от Апрель 19, 2007, 13:08
Т.е. я понимаю, схема такая.
Для навигации в одном View используется один экземпляр класса QFileListModel. Этот экземпляр, т.е. модель, владеет одним экземпляром класса QIconsLoaderThread. После одновления списка файлов, т.е. списка FileInfo, модель запускает поток для чтения иконок.

При переходе в другую директорию возможен, вариант, что поток не успеет доработать цикл до конца. Необходимо его прервать.

Тогда
QThread:exit()/QThread::quit()  использовать не получится. Т.к. не запущен event loop потока. Возможно только прерывание  QThread::terminate() . Или установкой m_bAbort в true, как сделано у тебя.

Непонятно, зачем в десрукторе это:  m_bAbort = true;

добавлено спустя 43 минуты:

 
Код:
void QIconsLoaderThread::Load() 
{
   qDebug()<<"Call Load()";
    QMutexLocker locker(&m_mutex);
    if (!isRunning()) {
      m_bAbort = false;
        start();
    }
}


Вот здесь locker не сможен захватить мутекс, пока поток этого объекта выполняется (в методе run захват мутекс происходит практически сразу). Так что проверка !isRunning() необходима только для

Код:
  qDebug()<<"Call run()"; 
   QFileListModel* pModel = qobject_cast<QFileListModel*>(this->parent());


эти две строки в принципе тоже можно выполнять после захвата мутекса.

В методе stop аналогичная ситуация.

чтение и установку m_bAbort лучше реализовать через методы и защитить отдельным мутексом.

добавлено спустя 27 минут:

 также советую в методе run()  использовать не список FileInfo* не тот, что в модели, а создавать его копию.

не вижу никакой защиты от одновременного использования полей FileInfo запущенным потоком QIconsLoaderThread и gui-потоком


Название: Проводник файлов
Отправлено: IUnknown от Апрель 19, 2007, 20:53
Не очень понял как защитить одновременное использования полей FileInfo запущенным потоком QIconsLoaderThread и gui-потоком? Но есть прогресс - после защиты m_bAbort мютексами я наконец увидел qDebug()<<"Thread stoped!". Но ошибка все-равно вылетает.


Название: Проводник файлов
Отправлено: IUnknown от Апрель 22, 2007, 18:46
QIconsLoaderThread::QIconsLoaderThread(QObject *parent)
   : QThread(parent)
{
   SetStop(false);
}

QIconsLoaderThread::~QIconsLoaderThread()
{
}

void QIconsLoaderThread::Load()
{
   qDebug()<<"Call Load()";
    if (!isRunning()) {
      SetStop(false);
        start();
    }
}

void QIconsLoaderThread::run()
{
   qDebug()<<"Call run()";
   QMutexLocker locker(&m_mutex1);
   QFileListModel* pModel = qobject_cast<QFileListModel*>(this->parent());
   for(int i=0; i<=pModel->m_FileList.count()-1; i++)
   {
      if (IsStoped())
      {
         pModel = 0;
         qDebug()<<"Thread stoped!";
         return;
      }
      FileInfo* pFileInfo = const_cast<FileInfo*>(pModel->m_FileList);
      
      if((!pFileInfo->m_bSysIcon) && (!pFileInfo->m_FileInfo.isDir()))
      {
         pFileInfo->m_icon = GetIcon(pFileInfo->m_pathName);
         pFileInfo->m_bSysIcon = true;
      }
      pFileInfo = 0;
      emit pModel->reset();
   }
}

void QIconsLoaderThread::stop()
{
   qDebug()<<"Call stop()";

      SetStop(true);
}

QPixmap QIconsLoaderThread::convertHIconToPixmap( const HICON icon) const
{
}

QIcon QIconsLoaderThread::GetIcon(const QString & fileName) const
{}

void QIconsLoaderThread::SetStop(bool bStop)
{
   m_mutex.lock();
   m_bAbort = bStop;
   m_mutex.unlock();
}
bool QIconsLoaderThread::IsStoped() const
{
   m_mutex.lock();
   bool tmp = m_bAbort;
   m_mutex.unlock();
   return tmp;
}
Так переделал класс потока.
В классе модели все обращения к списку с файлами m_FileList обставил мютексами. Все-равно возникает та же ошибка.


Название: Проводник файлов
Отправлено: Gryz от Апрель 23, 2007, 09:47
Думаю, класс FileInfo стоит переделать так:
Код:

class FileInfo
{
private:
    class IconLocker
    {
    public:
        IconLocker(QIcon * icon, QMutex * lock):m_icon(icon), m_lock(lock)
        {
            m_lock->lock();
        }
        ~IconLocker()
        {
            m_lock->unlock();
        }
        QIcon * operator->()
        {
            return m_icon;
        }
    private:
        QIcon * m_icon;
        QMutex * m_lock;
    };
public:
    FileInfo()
    {}

    FileInfo(const FileInfo& ci)
    {
        //
        m_bSysIcon = ci.m_bSysIcon;
        m_icon = ci.m_icon;
        m_pathName = ci.m_pathName;
        m_bIsUpDir = ci.m_bIsUpDir;
        m_FileInfo = ci.m_FileInfo;
        //
    }

    const FileInfo& operator=(const FileInfo& stringSrc)
    {
        //
        m_bSysIcon = stringSrc.m_bSysIcon;
        m_icon = stringSrc.m_icon;
        m_pathName = stringSrc.m_pathName;
        m_bIsUpDir = stringSrc.m_bIsUpDir;
        m_FileInfo = stringSrc.m_FileInfo;
        //
        return *this;
    }

    virtual ~FileInfo()
    {}
    IconLocker getIcon()
    {
        return IconLocker(&m_icon, &m_memberLock);
    }

    void setIcon(const QIcon & icn)
    {
        QMutexLocker locker(&m_memberLock);
        m_icon = icn;
    }
    bool isSysIcon()
    {
        QMutexLocker locker(&m_memberLock);
        return m_bSysIcon;
    }
    bool isDir()
    {
        QMutexLocker locker(&m_memberLock);
        return m_FileInfo.isDir();
    };
    //

    QString m_pathName;
    bool m_bSysIcon;
    QIcon m_icon;
    bool m_bIsUpDir;
    QFileInfo m_FileInfo;
    QMutex m_memberLock;
    //
};


В методе run делать копию списка из модели:

QList<FileInfo*> locList = pModel->getFileList()

причем, в модели оставить защиту мутексом только в getFileList().

Сообщение о сбросе модели посылать не напрямую, а через postEvent или через вызов ее спец слота при соединении с сигналом потока с типом соединения Qt::QueuedConnection. И вообще, что-то я не вижу в базовом классе модели сигнала reset();


Название: Проводник файлов
Отправлено: IUnknown от Апрель 24, 2007, 16:16
Переделал класс FileInfo. В классе модели создал:
Код:

QFileListModel::QFileListModel(QObject *parent)
: QAbstractTableModel(parent)
{
connect(this, SIGNAL(reset()),this, SLOT(reset()));
m_pLoaderThread = new QIconsLoaderThread(this);
//m_iCount = GetLocalFiles(QString("c:\\"));
SetCurrentPath(QString("c:\\"));
}

QList<FileInfo*> QFileListModel::GetFileList() const
{
QMutexLocker locker(&m_mutex);
return m_FileList;
}

void QFileListModel::reset()
{
QMutexLocker locker(&m_mutex);
QAbstractTableModel::reset();
}

В функции потока run вызываю:
Код:

QList<FileInfo*> locList = pModel->GetFileList();
и
QMetaObject::invokeMethod(pModel, "reset", Qt::QueuedConnection);

Все-равно вылетает та же ошибка. Если не использовать reset - она не возникает, но мне как-то же нужно перерисовать список.  :shock:


Название: Проводник файлов
Отправлено: Gryz от Апрель 24, 2007, 17:50
Код:
connect(this, SIGNAL(reset()),this, SLOT(reset())); 

не пойму, как это вообще работает  :? .

а если так попробовать (это слот):

Код:
void QFileListModel::notifyAboutChanges(int fileNumber) 
{
    emit dataChanged(index(fileNumber, m_colNumber), index(fileNumber, m_colNumber));    
}

где fileNumber - номер строки с описанием файла в модели (предполагаю, что совпадает с номером в списке); m_colNumber - номер столбца, где рисуется иконка.

а в методе run:

 
Код:
QMetaObject::invokeMethod(pModel, "notifyAboutChanges", Qt::QueuedConnection,
                           Q_ARG(int, i));


и что за ошибка вылетает?


Название: Проводник файлов
Отправлено: IUnknown от Апрель 24, 2007, 23:14
Спасибо большое! Все работает замечательно.


Название: Проводник файлов
Отправлено: Gryz от Апрель 25, 2007, 09:23
Пожалуйста :)

Смотрю только, доступ к списку файлов в модели все-таки остается несинхронизированным. Это можно исправить используя "опимизированный мутекс" QReadWriteLock в классе модели.