How to ensure, the entire area in QScrollArea was displayed? - qt

I'm displaying some information to the user in QScrollArea.
The user should have seen all contents, before she can proceed (at least the content should have been scrolled to the end)
How could I detect this in an easily?
Is the reimplementing of virtual void scrollContentsBy (int dx,int dy) the only way?
EDIT
I was able to solve it, but had to use some workarounds:
Scroll-action value sent by the signal actionTriggered(int) had never the value QAbstractSlider::SliderToMaximum (Qt4.8, Windows 7). So I've checked manually, if the slider value is close to maximum.
Even if scroll-bar widget was dragged by mouse till the bottom, the value of the scroll-bar is never the maximum. Only if the scroll-bar widget is moved by any other event such as arrow-down or mouse wheel, the value may become maximum. I've work-arounded it with recheckPosition()
I hope, there are better solutions.
void NegativeConfirmation::recheckPosition()
{
processScrollAction(1);
}
void NegativeConfirmation::processScrollAction( int evt)
{
if ( evt == QAbstractSlider::SliderToMaximum) // Have not managed to receive this action
{
ui->bConfirm->setEnabled(true);
}
//Another approach
QWidget * sw = ui->scrollArea->widget();
if ( sw ) //any content at all ?
{
QScrollBar * sb = ui->scrollArea->verticalScrollBar();
if ( sb )
{
int sbv = sb->value();
int sbm = sb->maximum()-10;
if ( sbm>0 && sbv >= sbm )
{
ui->bConfirm->setEnabled(true);
}
else
{
QTimer::singleShot(1000, this, SLOT(recheckPosition()));
}
}
}
}

QScrollArea inherits QAbstractSlider which provides this signal: -
void QAbstractSlider::actionTriggered(int action)
Where action can be QAbstractSlider::SliderToMaximum.
I expect you can connect to the this signal and test when the action is QAbstractSlider::SliderToMaximum, representing that the user has scrolled to the bottom.

Related

Is there a way to detect whether CollectionView scrolling has been initiated by the user or ScrollTo?

I have a page that hosts a CollectionView and Map.
there is a list of items, the ItemsSource of the CollectionView is set to this list and the map pins are drawn based on this list, there's a requirement that when the user scrolls the CollectionView the opposite map pin is highlighted, and when the pin is clicked the CollectionView is scrolled to that item.
I use ScrollTo and Scrolled event. but the problem is that when the ScrollTo is called the Scrolled event is fired too, and that causes a lag or some unexpected behavior.
I tried to set a flag:
private int centerIndex = -1;
bool userScroll;
private void CollectionView_Scrolled(object sender, ItemsViewScrolledEventArgs args)
{
if (centerIndex != args.CenterItemIndex)
{
if (userScroll)
MessagingCenter.Send<object, int>(this, Keys.GoToLocation, args.CenterItemIndex);
userScroll = true;
centerIndex = args.CenterItemIndex;
}
}
private void ScrollToVehicle(object arg1, Item item)
{
if (collectionView.ItemsSource != null && collectionView.ItemsSource.Cast<Item>().Contains(item))
{
userScroll = false;
collectionView.ScrollTo(item, position: ScrollToPosition.Center, animate: false);
}
}
but the problem is that Scrolled event is called multiple times (inside the if statement)
Try to unsubscribe from the CollectionView_Scrolled before call the ScrollTo
collection.Scrolled -= CollectionView_Scrolled;

graphicsview receives mouse event before the item

I have implemented the panning view on the QGraphicsView, using the mouse move event using
void View::mouseMoveEvent(QMouseEvent* event) {
pan();
QGraphicsView::mouseMoveEvent(event);
}
and in the scene of this view I have added few items where some of the items are resizable, so I implemented
void Item::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
if(m_resizeMode)
{
resize();
e->accept();
}
}
I tried to filter the mouse move not to propagate to any further using e->accept()
but my View mouseMove event has been called first , so when ever I tried to resize the item, the view started to pan all the way.
How can I avoid this event propagation from view to scene.
You can call the base class implementation of the QGraphicsView and check if the event is accepted. However I would do this in the mousePressEvent instead of the mouseMoveEvent. After all this is where you determine if you should resize an item or do some panning. This is how I do something similar in my project:
void View::mousePressEvent(QMouseEvent *event)
{
...
QGraphicsView::mousePressEvent(event);
if(event->isAccepted())
move = false;
else
move = true;
}
void View::mouseMoveEvent(QMouseEvent *event)
{
if(!move)
{
QGraphicsView::mouseMoveEvent(event);
return;
}
... // you would do the panning here
QGraphicsView::mouseMoveEvent(event);
}
void View::mouseReleaseEvent(QMouseEvent *event)
{
if(!move)
{
QGraphicsView::mouseReleaseEvent(event);
return;
}
else
{
...
move = false;
}
QGraphicsView::mouseReleaseEvent(event);
}
The view will always receive the mouse event first.
So, in the view, check to see if the mouse is over an item before allowing it to pan by getting the mouse pos in scene coordinates and retrieving the items at that position with QGraphicsScene::items( )

How to let QComboBox have context menu?

I have a Qt combo box. When it pops, items are listed down. When right clicking an item, I hope a context menu to pop up. Any way to implement it? I find a function onContextMenuEvent under QComboBox. Does it help? Thanks.
You can obtain the list widget using QComboBox::view. You can add a context menu to the list as usual. But also you should install event filter on the view's viewport and block right click events because such events cause popup list to close.
In the initialization:
QAbstractItemView* view = ui->comboBox->view();
view->viewport()->installEventFilter(this);
view->setContextMenuPolicy(Qt::CustomContextMenu);
connect(view, SIGNAL(customContextMenuRequested(QPoint)),
this, SLOT(list_context_menu(QPoint)));
Event filter:
bool MainWindow::eventFilter(QObject *o, QEvent *e) {
if (e->type() == QEvent::MouseButtonRelease) {
if (static_cast<QMouseEvent*>(e)->button() == Qt::RightButton) {
return true;
}
}
return false;
}
In the slot:
void MainWindow::list_context_menu(QPoint pos) {
QAbstractItemView* view = ui->comboBox->view();
QModelIndex index = view->indexAt(pos);
if (!index.isValid()) { return; }
QMenu menu;
QString item = ui->comboBox->model()->data(index, Qt::DisplayRole).toString();
menu.addAction(QString("test menu for item: %1").arg(item));
menu.exec(view->mapToGlobal(pos));
}
In this example items are identified by their displayed texts. But you also can attach additional data to items using QComboBox::setItemData. You can retrieve this data using ui->comboBox->model()->data(...) with the role that was used in setItemData.

Qt custom context menu

Am creating a Qt Application which consists of a tree view and a webview. when a item from a tree view is clicked it should load the corresponding url. It Works Fine. when am right clicking on the item a custom context menu will appear n it will open it in a new webview. This is also working. But my problem is when am Right clicking on the treeview item my context menu comes and if am clicking it outside the pop up menu the url of that item gets loaded. how to solve this.. Help me friends..
Here's my coding:
QStandardItem *rootItem = new QStandardItem("Google");
QStandardItem *stackItem = new QStandardItem("Stack Overflow");
QStandardItem *yahooItem = new QStandardItem("Yahoo");
rootItem->appendRow(stackItem);
standardModel->appendRow(rootItem);
standardModel->appendRow(yahooItem);
***// private slot for loading the url if a treeview item is clicked:***
void MainWindow::treeViewClicked(const QModelIndex &index)
{
str = index.data().toString();
if(!(str.isEmpty()) && str=="Google")
{
url = "http://www.google.com";
}
else if (!(str.isEmpty()) && str == "stack Overflow")
{
url = "http://www.stackoverflow.com";
}
else if (!(str.isEmpty()) && str == "Yahoo")
{
url = "http://www.yahoo.com";
}
WebView *wv = dynamic_cast<WebView *>(ui->tabWidget->currentWidget());
wv->load(QUrl(url));
ui->tabWidget->setTabText(ui->tabWidget->currentIndex(),str);
treeView->setModel(standardModel);
**//Creating custom context menu for QtreeView:**
void MainWindow::showContextMenu(const QPoint& point)
{
QList<QAction *> actions;
if(treeView->indexAt(point).isValid())
{
actions.append(m_treeViewAction);
}
else if(actions.count() > 0)
{
QMenu::exec(actions, MainWindow::treeView->mapToGlobal(point));
QModelIndex index = treeView->indexAt(point);
QStandardItem *item = standardModel->itemFromIndex(index);
treeView->setCurrentIndex(index);
treeViewClicked(index);
}
}
For what I know, the situation you describe is standard with context menus in views: When you right click, the item is also selected.
If you want another behaviour, you must implement the mousePressEvent and implement the behaviour you want to achieve.
Here is a hint:
void MyTreeView::mousePressEvent ( QMouseEvent * event )
{
if (event->button() == Qt::LeftButton) {
// set the current item based on event->pos() / deselect if no item
}
else if (event->button() == Qt::RightButton) {
// show context menu for the item / different context menu if no item
}
}
Yes, you must derive the QTreeView class and make one of your own.
I've done this long time ago, and I remember this as the starting point. I don't remember now if I had to reimplement all four basic mouse events: press, release, move and doubleclick, as they are internally related.

Qt painted content goes lost

I am writing an info-screen program. I created a full-screen widget and draw contents onto it.
In order to extend the life cycle of the TFT-display device, I want to implement a pixel-shifting feature. With other words, in every X minutes, I shift the screen to left/right/top/down for Y pixels.
My approach is as follows:
I use two layers (two QWidget).
I paint contents on the top layer.
When a pixel-shifting is performed, I just move the top layer for specified offset.
And then fill a background color to the bottom layer.
However, I found a problem:
If I move up the top layer for 10 pixels, the 10-pixel-content goes out of the screen. But when I move this layer down for 10 pixels. The 10-pixel-content will not be updated, it is gone.
How can I keep these 10-pixel-content? Is there any magic widget flag to solve this problem?
UPDATE 1:
The code is written in language D, but it is easy to understand:
class Canvas: QWidget
{
private QPixmap content;
this(QWidget parent)
{
super(parent);
setAttribute(Qt.WA_OpaquePaintEvent, true);
}
public void requestForPaint(QPixmap content, QRegion region)
{
this.content = content;
update(region);
}
protected override void paintEvent(QPaintEvent event)
{
if (this.content !is null)
{
QPainter painter = new QPainter(this);
painter.setClipping(event.region);
painter.fillRect(event.region.boundingRect, new QColor(0, 0, 0));
painter.drawPixmap(event.region.rect, this.content);
this.content = null;
painter.setClipping(false);
}
}
}
class Screen: QWidget
{
private Canvas canvas;
this()
{
super(); // Top-Level widget
setAutoFillBackground(True);
this.canvas = new Canvas(this);
showFullScreen();
}
public void requestForPaint(QPixmap content, QRegion region)
{
this.canvas.requestForPaint(content, region);
}
private updateBackgroundColor(QColor backgroundColor)
{
QPalette newPalette = palette();
newPalette.setColor(backgroundRole(), backgroundColor);
setPalette(newPalette);
}
public shiftPixels(int dx, int dy)
{
this.canvas.move(dx, dy);
updateBackgroundColor(new QColor(0, 0, 0)); // Just a demo background color
}
}
Screen screen = new Screen;
screen.requestForPaint(some_content, some_region);
screen.shiftPixels(0, -10);
screen.shiftPixels(0, 10);
Looking at the code, my first guess is that your region might be wrong. Try repainting the whole widget each time, and see if that solves the missing 10 pixel problem. If it does, then try working out why your region isn't covering the newly exposed portion.
One possibility along those lines: I notice in your Screen::requestForPaint method that you directly call the Canvas::requestForPaint without doing anything with the region. In Qt, the coordinates for anything like that are often assumed to be local, so if you don't account for the current position of the canvas widget, you might get an incorrect region.
Why not setting the position of the widget directly...? Another options might be using QPainter::translate(-1,-1) or something similar.

Resources