Russian Qt Forum

Qt => Model-View (MV) => Тема начата: Sky от Июль 07, 2010, 13:16



Название: [РЕШЕНО] QTableView - видимые ячейки
Отправлено: Sky от Июль 07, 2010, 13:16
Доброго времени суток!

Мне необходимо выводить большой объем данных в таблице. Для этого унаследовал модель от QAbstractTableModel. Для отображения использую QTableView.
Так вот сам вопрос: как мне определить какой диапазон строк сейчас виден пользователю и как отловить событие, при котором этот диапазон меняется?
Поиск результатов не дал.  :(

Заранее большое спасибо.




Название: Re: QTableView - видимые ячейки
Отправлено: SABROG от Июль 07, 2010, 15:18
Посмотри этот пример

http://doc.qt.nokia.com/latest/tools-contiguouscache.html

Логика тут такая, если ячейка становится видимой, то у нее идет запрос Qt::DisplayRole.
Код
C++ (Qt)
QVariant RandomListModel::data(const QModelIndex &index, int role) const
{
    if (role != Qt::DisplayRole)
        return QVariant();
...
 


Название: Re: QTableView - видимые ячейки
Отправлено: Sky от Июль 07, 2010, 16:20
Т.е. только вручную сохранять в data() максимальное/минимальное значения для строк?
И событие изменения диапазона видимых ячеек собирать из события прокрутки и изменения размера области просмотра?
К этому уже пришел, но такое чувство, что неправильно это. Неужто нет более элегантного способа?

upd:
Тут еще дело в том, что данные у меня извлекаются из базы в отдельном потоке, т. е. внешне это выглядит так, что пользователь прокрутил (без тормозов) до нужного места, а с небольшим запаздыванием в ячейках появляются данные.
При этом не хотелось бы читать данные из базы по строчке (нечто подобное в Вашем примере), а хотелось бы по событию изменения диапазона видимых строк прочитать недостающий блок данных сразу целиком.
Видимо придется по событиям прокрутки/изменения размера вычислять диапазон строк, используя номер первой строки, высоту строк и высоту области просмотра...


Название: Re: QTableView - видимые ячейки
Отправлено: DmP от Июль 07, 2010, 16:49
Так вот сам вопрос: как мне определить какой диапазон строк сейчас виден пользователю и как отловить событие, при котором этот диапазон меняется?
Как вариант унаследовать QTableView и переопределить функции updateGeometries() и scrollContentsBy(), в них можно определять первую и последнюю видимую строку через ф-ции verticalHeader()->logicalIndexAt() и viewport()->geometry().


Название: Re: QTableView - видимые ячейки
Отправлено: SABROG от Июль 08, 2010, 00:50
При этом не хотелось бы читать данные из базы по строчке (нечто подобное в Вашем примере), а хотелось бы по событию изменения диапазона видимых строк прочитать недостающий блок данных сразу целиком.
data() c ролью Qt::DisplayRole вызывается тогда, когда элемент становится видимым на вьюпорте. Если у тебя размер вьюпорта вмещает 2 итема, а в модели 100000 итемов, то data() будет вызван 2 раза. Когда пользователь начинает прокручивать таблицу, то на каждый появляющийся итем вызывается data() c Qt::DisplayRole.

Предположим, что у тебя в базе 1 миллион записей. Перед тем как заполнить представление ты прочитал записи с Primary Key 0-100 (100 записей):

Код
SQL
SELECT * FROM mytable WHERE KEY BETWEEN 0 AND 100 ORDER BY KEY ASC

и поместил их в QContiguousCache. Модель забирает эти 100 записей из кэша и отображает на представлении. Собственно массовый (не построчный, как тебе и нужно) забор данных идет в этих строках:

Код
C++ (Qt)
else while (row > m_rows.lastIndex())
            m_rows.append(fetchRow(m_rows.lastIndex()+1));
 

Только у троллей while, а у тебя обычный SELECT к базе должен быть.


Название: Re: QTableView - видимые ячейки
Отправлено: daimon от Июль 20, 2010, 09:59
А в модели так и возвращать, что строк где-то миллионы или тысячи - много (rowCount)?


Название: Re: QTableView - видимые ячейки
Отправлено: Pink_Panter от Июль 22, 2010, 13:20
Пытаюсь сделать так, как описано в этой теме.

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

Как бы мне отучить потомка QTableView запрашивать сразу все строки по каждому чиху?


Название: Re: QTableView - видимые ячейки
Отправлено: Pink_Panter от Июль 23, 2010, 20:55
Разобрался я со своей проблемой.
Сдуру у QTableView включил установку размеров строк и столбцов по содержимому.
Вот оно и гоняло data модели в хвост и в гриву. Причем сволочь запрашивало не только HintSize, но и сами данные.


Название: Re: QTableView - видимые ячейки
Отправлено: SABROG от Июль 24, 2010, 12:31
Разобрался я со своей проблемой.
Сдуру у QTableView включил установку размеров строк и столбцов по содержимому.
Вот оно и гоняло data модели в хвост и в гриву. Причем сволочь запрашивало не только HintSize, но и сами данные.

Логично, чтобы подогнать размер итема под размер данных их сначала нужно получить. Хотя если у тебя есть готовый пример я бы отписал троллям предложение о добавлении какого-нибудь аттрибута, который бы позволял подгонять итемы под размер только для видимой части (на лету).


Название: Re: QTableView - видимые ячейки
Отправлено: Pink_Panter от Июль 25, 2010, 13:19
Тут про готовый пример тяжело сказать. Если очень понадобится - сделаю. если есть возможность донести до троллей...
Насколько я понимаю MVC: функция data у модели вызывается вьювером с разными ролями для разных задач.
Для запроса размеров есть специальная роль HintSize или что-то похожее. (Пишу из дома ассистент не под рукой).
а для запроса значения - вызывается DisplayRole  или EditRole.
С чем я столкнулся:
Для быстрой работы с весьма большой таблицей БД и для реализации нужного мне доп.функционала
я как в этой теме тобой рекомендовано (Кстати спасибо за очень полезный совет) реализовал потомка QAbstractTableModel в котором использовал QContiguousCache. Функцию data реализовал более полно чем в примере у троллей. С разбором ролей и возвратом соответствующих значений. Сейчас все работает просто превосходно. (если не включать авторазмер по содержимому у QTableView)
Желаемые размеры колонок я легко получаю одним запросом и храня их в внутренних переменных могу не напрягаясь отдавать их хоть миллионы раз.
А вот запрос некешированных данных занимает довольно много времени. (Собственно кэш для того и делал, что бы не тащить сразу всю таблицу в память, а только по мере необходимости).
Собственно вопрос к троллям:
Зачем? запрашивать сами данные? если нужный мне размер под эти данные модель может сказать, или даже уже сказала?


Название: Re: QTableView - видимые ячейки
Отправлено: lit-uriy от Июль 25, 2010, 13:49
если речь идёт о геометрическом размере ячейки, то этот размер определяется делегатом, функцией sizeHint(), как и у всех виджетов. Представление не обращается за размером к модели, оно обращается к делегату.

Qt::SizeHintRole используется только в функции Model::headerData(...) для определения размеров заголовков


Название: Re: QTableView - видимые ячейки
Отправлено: Pink_Panter от Июль 25, 2010, 21:30
Возможно. Но вроде отладчик у меня регистрировал Qt::SizeHintRole в функции data.
Хотя это уже не важно. Моя задача успешно решена. А о методологии пусть у троллей голова болит. Я на хайлевел не претендую, пользуюсь тем, что есть.


Название: Re: QTableView - видимые ячейки
Отправлено: Sky от Август 02, 2010, 11:09
Меня тут временно на другую задачу перебрасывали, так что не мог отписаться.

DmP

Код:
else while (row > m_rows.lastIndex())
             m_rows.append(fetchRow(m_rows.lastIndex()+1));
Это ведь вызывается в data() модели? Тогда при прокрутке будет притормаживать, пока не загрузится часть таблицы из базы. Или нет?

Сейчас сделал так:
При изменении размера или при прокрутке QTableView стартует в отдельном потоке чтение необходимых строк из БД (при этом уже загруженные строки не грузятся). При этом передается первая строка (получена по сигналу valueChanged(int) QTableModel::verticalScrollBar()) и последняя (QTableView::rowAt(height()-1)).
В принципе все работает, кроме того, что при прокрутке в самый низ таблицы rowAt(height()-1) возвращает -1. При прокрутке вверх на 3 строки возвращается нормальное значение (при любом размере таблицы).
Ну и само по себе rowAt(height()-1) как-то криво выглядит.


Название: Re: QTableView - видимые ячейки
Отправлено: DmP от Август 02, 2010, 12:09
Сейчас сделал так:
В принципе все работает, кроме того, что при прокрутке в самый низ таблицы rowAt(height()-1) возвращает -1.
Это нормально, у тролей при определении последней строки в таблице если получается -1, то берется максимальный номер строки, типа model()->rowCount() - 1. Только у них от высоты не отнимается 1, просто rowAt(viewport()->height())


Название: Re: QTableView - видимые ячейки
Отправлено: Sky от Август 02, 2010, 12:16
Это нормально, у тролей при определении последней строки в таблице если получается -1, то берется максимальный номер строки, типа model()->rowCount() - 1. Только у них от высоты не отнимается 1, просто rowAt(viewport()->height())

Спасибо!
Т. е. такой способ определения последней строки является правильным?


Название: Re: QTableView - видимые ячейки
Отправлено: DmP от Август 02, 2010, 12:22
Это нормально, у тролей при определении последней строки в таблице если получается -1, то берется максимальный номер строки, типа model()->rowCount() - 1. Только у них от высоты не отнимается 1, просто rowAt(viewport()->height())

Спасибо!
Т. е. такой способ определения последней строки является правильным?
Да, даже бы сказал официальным :). Пожалуйста.


Название: Re: QTableView - видимые ячейки
Отправлено: Sky от Август 02, 2010, 13:12
Отлично! Большое спасибо!


Название: QTableView - видимые ячейки
Отправлено: daimon от Август 06, 2010, 22:32
Можете кинуть пример с базой (посмотреть). Я пытаюсь делать простую таблицу (данные в файле-считывание из файла)


Название: Re: [РЕШЕНО] QTableView - видимые ячейки
Отправлено: Sky от Август 09, 2010, 09:09
Вот классы, работы с БД и класс модели. С отображением уж сами, у меня весьма запутанно все выглядит, там под свои задачи переколбашено много.

db_data.py
Код:
# -*- coding: utf-8 -*-

from PyQt4.QtCore import *
from PyQt4.QtSql import *
import threading as trd

class DbThread(QThread):
    """
    DbThread(QSqlDatabase db)
    Экземпляр класса в отдельном потоке загружает данные из БД.   
    """
    def __init__(self, db):
        QThread.__init__(self)
        self.db = db
        self.data = {}
        self.begin = 0
        self.end = 0  # Эта переменная указывает на последний индекс + 1
        self.cacheSize = 0
        self.queryInfo = {} # В словаре хранятся списки таблиц, полей и сортировки
        self.queryPrepared = {} # В словаре хранятся таблицы, поля и сортировки, перечисленные через запятую

    def setBounds(self, begin, end):
        """
        setBound(begin, end)
        Установка первой и "последней + 1" строки необходимого блока данных.
        """
        if begin > end or begin < 0 or end < 0:
            return       
        self.begin = begin
        self.end = end

    def run(self):
        """
        Не использовать, автоматически запускается функцией start().
        Здесь находится основной код получения данных от БД.
        """
        blockBegin = -1
        qryPrepare = self.queryInfo
        qryText = 'SELECT %(fields)s FROM %(tables)s ORDER BY %(orders)s' % self.queryPrepared
        for i in xrange(self.begin, self.end):
            if blockBegin == -1 and (not self.data.has_key(i)):
                blockBegin = i
            if blockBegin != -1 and (self.data.has_key(i) or i == self.end - 1):
                if i == self.end - 1:
                    blockEnd = self.end
                else:
                    blockEnd = i
                query = QSqlQuery(self.db)
                #print '%s OFFSET %d LIMIT %d' % (qryText, blockBegin, blockEnd - blockBegin)
                query.exec_('%s OFFSET %d LIMIT %d' % (qryText, blockBegin, blockEnd - blockBegin))
                #print query.executedQuery()
                currentRow = blockBegin
                while query.next():
                    fld = []
                    for i in xrange(query.record().count()):
                        fld.append(query.record().value(i).toString())
                    self.data[currentRow] = fld
                    currentRow += 1
                blockBegin = -1
       
        for i in self.data.copy():        # сборка мусора
            if i < self.begin - self.cacheSize or i >= self.end + self.cacheSize:
                del(self.data[i])

class DbData(QObject):
    """
    DbData(QSqlDatabase db)
    С классом можно обращаться как с обычным списком,
    любой индекс будет корректен. Если строка не загружена,
    то возвращается None. Элементы списка - словари {field:data}"""


    def __init__(self, db):
        QObject.__init__(self)
        self.thread = DbThread(db)
        self.connect(self.thread, SIGNAL('finished()'), self.threadFinished)
        self.lastBegin = -1
        self.lastEnd = -1
        self.fin = True

    def __getitem__(self, index):
        """
        Получение строки данных по индексу.
        dbData.__getitem__(i) <==> dbData[i]
        """
        if self.thread.data.has_key(index):
            return dict(zip(self.thread.queryInfo['fields'], self.thread.data[index]))
        else:
            return None

    def __setitem__(self, index, item):
        """
        Присваивание строки данных по индексу.
        dbData.__setitem__(i, data) <==> dbData[i] = data
        """
        pass  # Не реализовано и не надо

    def start(self, newBegin, newEnd):
        """
        start(begin, end)
        Запуск процесса получения данных.
        begin = первая строка
        end = последняя строка + 1
        """
        if not self.fin:                # Здесь мы ожидаем окончания потока, хотя лучше бы его убивать,
            self.lastBegin = newBegin   # но это почему-то глючит. :( А сейчас мы немного теряем в производительности,
            self.lastEnd = newEnd       # но есть мнение, что выигрываем в нагрузке на сервер.
        else:
            self.thread.setBounds(newBegin, newEnd) # Устанавливаем границы, которые были запрошены последними
            self.fin = False                        # и запускаем поток
            self.thread.start()
            self.lastBegin = -1

    def threadFinished(self):
        """
        Слот окончания работы потока. Если других запросов
        во время работы не было, генерирует сигнал finished()
        """
        if self.lastBegin > -1:
            self.thread.setBounds(self.lastBegin, self.lastEnd)
            self.fin = False           
            self.thread.start()
            self.lastBegin = -1
        else:
            self.emit(SIGNAL('finished()'))
            self.fin = True
           
    def getCacheSize(self):
        return self.thread.cacheSize
    def setCacheSize(self, cacheSize):
        self.thread.cacheSize = cacheSize
    cacheSize = property(getCacheSize, setCacheSize)

db_model.py
Код:
# -*- coding: utf-8 -*-

from PyQt4.QtCore import *
from PyQt4.QtSql import *
from db_data import DbData

class DbAbstractModel(QAbstractTableModel):
   
    def __init__(self, database, parent=None):
        self.rCnt = None
        self.database = database
        self.dbData = DbData(database)
        self.connect(self.dbData, SIGNAL('finished()'), self.dbFinished)
        self.queryInfo = {}
        self.queryPrepared = {}
        self.colList = []
        QAbstractTableModel.__init__(self, parent)

    def getRowCount(self):
        qry = QSqlQuery(self.database)
        qry.exec_('SELECT COUNT(*) FROM %(tables)s' % (self.queryPrepared))
        qry.first()
        self.rCnt = qry.record().value('count').toInt()[0]

    def viewportChanged(self, begin, end):
        self.dbData.start(begin, end)

    def dbFinished(self):
        self.emit(SIGNAL('layoutChanged()'))

    def rowCount(self, parent=QModelIndex()):
        if self.rCnt == None:
            self.getRowCount()
        return self.rCnt

    def columnCount(self, parent=QModelIndex()):
        return len(self.colList)

    def data(self, index, role = Qt.DisplayRole):
        if role == Qt.DisplayRole:
            if self.dbData[index.row()]:
                return self.dbData[index.row()][self.colList[index.column()]]
        return QVariant()
   
    def setQueryInfo(self, queryInfo):
        """
        setQueryInfo(dict('tables':[], 'fields':[], 'orders':[]))
        Параметр передает в словаре списки таблиц, полей и сортировки
        """
        self.queryInfo = queryInfo
        for key in queryInfo:
            self.queryPrepared[key] = ','.join(queryInfo[key])

        if len(queryInfo['pkey']):
            self.queryPrepared['orders'] += ',' + self.queryPrepared['pkey']

        self.dbData.thread.queryInfo = queryInfo
        self.dbData.thread.queryPrepared = self.queryPrepared
        self.dataReset()

    def setCacheSize(self, cacheSize):
        self.dbData.cacheSize = cacheSize
       
    def headerData(self, section, orientation, role = Qt.DisplayRole):
        if role == Qt.DisplayRole and orientation == Qt.Horizontal:
            return self.colList[section]
        return QAbstractTableModel.headerData(self, section, orientation, role)
           
    def dataReset(self):
        self.dbData.thread.terminate()
        self.dbData.thread.data.clear()
        self.getRowCount()

Здесь тоже много чего лишнего, но уж разбирайтесь. ;)

В представлении надо реализовать вызов viewportChanged(begin, end) модели по событиям изменения размера вьюпорта и скроллингу.


Название: Re: [РЕШЕНО] QTableView - видимые ячейки
Отправлено: daimon от Август 09, 2010, 15:47
Вот классы, работы с БД и класс модели. С отображением уж сами, у меня весьма запутанно все выглядит, там под свои задачи переколбашено много.

db_data.py
Код:
# -*- coding: utf-8 -*-

from PyQt4.QtCore import *
from PyQt4.QtSql import *
import threading as trd

class DbThread(QThread):
    """
    DbThread(QSqlDatabase db)
    Экземпляр класса в отдельном потоке загружает данные из БД.   
    """
    def __init__(self, db):
        QThread.__init__(self)
        self.db = db
        self.data = {}
        self.begin = 0
        self.end = 0  # Эта переменная указывает на последний индекс + 1
        self.cacheSize = 0
        self.queryInfo = {} # В словаре хранятся списки таблиц, полей и сортировки
        self.queryPrepared = {} # В словаре хранятся таблицы, поля и сортировки, перечисленные через запятую

    def setBounds(self, begin, end):
        """
        setBound(begin, end)
        Установка первой и "последней + 1" строки необходимого блока данных.
        """
        if begin > end or begin < 0 or end < 0:
            return       
        self.begin = begin
        self.end = end

    def run(self):
        """
        Не использовать, автоматически запускается функцией start().
        Здесь находится основной код получения данных от БД.
        """
        blockBegin = -1
        qryPrepare = self.queryInfo
        qryText = 'SELECT %(fields)s FROM %(tables)s ORDER BY %(orders)s' % self.queryPrepared
        for i in xrange(self.begin, self.end):
            if blockBegin == -1 and (not self.data.has_key(i)):
                blockBegin = i
            if blockBegin != -1 and (self.data.has_key(i) or i == self.end - 1):
                if i == self.end - 1:
                    blockEnd = self.end
                else:
                    blockEnd = i
                query = QSqlQuery(self.db)
                #print '%s OFFSET %d LIMIT %d' % (qryText, blockBegin, blockEnd - blockBegin)
                query.exec_('%s OFFSET %d LIMIT %d' % (qryText, blockBegin, blockEnd - blockBegin))
                #print query.executedQuery()
                currentRow = blockBegin
                while query.next():
                    fld = []
                    for i in xrange(query.record().count()):
                        fld.append(query.record().value(i).toString())
                    self.data[currentRow] = fld
                    currentRow += 1
                blockBegin = -1
       
        for i in self.data.copy():        # сборка мусора
            if i < self.begin - self.cacheSize or i >= self.end + self.cacheSize:
                del(self.data[i])

class DbData(QObject):
    """
    DbData(QSqlDatabase db)
    С классом можно обращаться как с обычным списком,
    любой индекс будет корректен. Если строка не загружена,
    то возвращается None. Элементы списка - словари {field:data}"""


    def __init__(self, db):
        QObject.__init__(self)
        self.thread = DbThread(db)
        self.connect(self.thread, SIGNAL('finished()'), self.threadFinished)
        self.lastBegin = -1
        self.lastEnd = -1
        self.fin = True

    def __getitem__(self, index):
        """
        Получение строки данных по индексу.
        dbData.__getitem__(i) <==> dbData[i]
        """
        if self.thread.data.has_key(index):
            return dict(zip(self.thread.queryInfo['fields'], self.thread.data[index]))
        else:
            return None

    def __setitem__(self, index, item):
        """
        Присваивание строки данных по индексу.
        dbData.__setitem__(i, data) <==> dbData[i] = data
        """
        pass  # Не реализовано и не надо

    def start(self, newBegin, newEnd):
        """
        start(begin, end)
        Запуск процесса получения данных.
        begin = первая строка
        end = последняя строка + 1
        """
        if not self.fin:                # Здесь мы ожидаем окончания потока, хотя лучше бы его убивать,
            self.lastBegin = newBegin   # но это почему-то глючит. :( А сейчас мы немного теряем в производительности,
            self.lastEnd = newEnd       # но есть мнение, что выигрываем в нагрузке на сервер.
        else:
            self.thread.setBounds(newBegin, newEnd) # Устанавливаем границы, которые были запрошены последними
            self.fin = False                        # и запускаем поток
            self.thread.start()
            self.lastBegin = -1

    def threadFinished(self):
        """
        Слот окончания работы потока. Если других запросов
        во время работы не было, генерирует сигнал finished()
        """
        if self.lastBegin > -1:
            self.thread.setBounds(self.lastBegin, self.lastEnd)
            self.fin = False           
            self.thread.start()
            self.lastBegin = -1
        else:
            self.emit(SIGNAL('finished()'))
            self.fin = True
           
    def getCacheSize(self):
        return self.thread.cacheSize
    def setCacheSize(self, cacheSize):
        self.thread.cacheSize = cacheSize
    cacheSize = property(getCacheSize, setCacheSize)

db_model.py
Код:
# -*- coding: utf-8 -*-

from PyQt4.QtCore import *
from PyQt4.QtSql import *
from db_data import DbData

class DbAbstractModel(QAbstractTableModel):
   
    def __init__(self, database, parent=None):
        self.rCnt = None
        self.database = database
        self.dbData = DbData(database)
        self.connect(self.dbData, SIGNAL('finished()'), self.dbFinished)
        self.queryInfo = {}
        self.queryPrepared = {}
        self.colList = []
        QAbstractTableModel.__init__(self, parent)

    def getRowCount(self):
        qry = QSqlQuery(self.database)
        qry.exec_('SELECT COUNT(*) FROM %(tables)s' % (self.queryPrepared))
        qry.first()
        self.rCnt = qry.record().value('count').toInt()[0]

    def viewportChanged(self, begin, end):
        self.dbData.start(begin, end)

    def dbFinished(self):
        self.emit(SIGNAL('layoutChanged()'))

    def rowCount(self, parent=QModelIndex()):
        if self.rCnt == None:
            self.getRowCount()
        return self.rCnt

    def columnCount(self, parent=QModelIndex()):
        return len(self.colList)

    def data(self, index, role = Qt.DisplayRole):
        if role == Qt.DisplayRole:
            if self.dbData[index.row()]:
                return self.dbData[index.row()][self.colList[index.column()]]
        return QVariant()
   
    def setQueryInfo(self, queryInfo):
        """
        setQueryInfo(dict('tables':[], 'fields':[], 'orders':[]))
        Параметр передает в словаре списки таблиц, полей и сортировки
        """
        self.queryInfo = queryInfo
        for key in queryInfo:
            self.queryPrepared[key] = ','.join(queryInfo[key])

        if len(queryInfo['pkey']):
            self.queryPrepared['orders'] += ',' + self.queryPrepared['pkey']

        self.dbData.thread.queryInfo = queryInfo
        self.dbData.thread.queryPrepared = self.queryPrepared
        self.dataReset()

    def setCacheSize(self, cacheSize):
        self.dbData.cacheSize = cacheSize
       
    def headerData(self, section, orientation, role = Qt.DisplayRole):
        if role == Qt.DisplayRole and orientation == Qt.Horizontal:
            return self.colList[section]
        return QAbstractTableModel.headerData(self, section, orientation, role)
           
    def dataReset(self):
        self.dbData.thread.terminate()
        self.dbData.thread.data.clear()
        self.getRowCount()

Здесь тоже много чего лишнего, но уж разбирайтесь. ;)

В представлении надо реализовать вызов viewportChanged(begin, end) модели по событиям изменения размера вьюпорта и скроллингу.

жаль что на питоне, я С/С++ шарю - буду дуплить