2017-02-13 4 views
0

[1/2] Контекст

Hello! Qt дает нам возможность создавать высоко настраиваемые графические элементы. Все, что нам нужно сделать, это наследовать от QGraphicsItem и переопределить чистую виртуальную функцию boundingRect(). Более того, мы можем опционально переопределить функцию виртуальной формы(), чтобы (среди прочих) дать деталям более точную форму ...Qt - Выбор QGraphics (Shaped) Товар

Теперь давайте посмотрим график ниже. Я рисовал с помощью программного обеспечения (личный студенческий проект). Я был разработка с Qt в C++.

a quite beautiful graph

Тогда давайте серо-выделить ограничивающий прямоугольник каждого края в пределах выше изображен график.

highlighting the edge bounding rectangle

[2/2] Вопросы & Замечания

Я хочу, чтобы элементы, чтобы быть по выбору, так что я включить флаг выбора:

setFlag(ItemIsSelectable, true); 

Он работает как сон для прямоугольных и круглые предметы. Он тоже работает для краев, но не как шарм. Действительно, ребра по-прежнему выбраны, если я щелкнул в области, заданной ограничивающим прямоугольником (серый на предыдущем графике). Есть ли способ обеспечить, чтобы событие с щелчком мыши учитывалось, только если мы нажали на кривую, определяющую форму элемента?

Я переопределил всю мышь * Событие и возвращение, если shape() не пересекается с event.scenePos(), но результат не был лучше. Есть ли способ достичь того, что я хочу сделать? Есть ли способ Qt-ish проверить, находится ли положение мыши в пределах кривой?

На самом деле я, наконец, в конечном итоге установив флаг так, чтобы края игнорировать кнопки мыши:

setAcceptedMouseButtons(Qt::NoButton); 

Но я был бы рад, если бы кто-нибудь сталкивался с аналогичной проблемой и имеет решение, чтобы разделить.

EDIT

Вот часть кода, который вы можете скомпилировать и выполнить. В качестве напоминания я хочу, чтобы ребра (кривые) выбирались только в том случае, если мы нажимаем на путь, определяющий его форму.

/** 
* It was really hard to come up with the little snippet below, 
* since the real code is more complex. 
* Hope someone'll be able to provide me with a solution. 
* 
* All you need to do is to copy and paste the code to a main.cpp file. 
*/ 

#include <QApplication> 

#include <QGraphicsItem> 
#include <QGraphicsRectItem> 
#include <QGraphicsView> 
#include <QScrollBar> 

/** 
* Nothing special about this class. 
* Note View instances handle rubber band selection (you can try it). 
*/ 
class View : public QGraphicsView { 
public: 
    View(QWidget *parent = nullptr) 
     : QGraphicsView(parent) 
    { 
     customize(); 
    } 

private: 
    void customize() // just customization 
    { 
     horizontalScrollBar()->setContextMenuPolicy(Qt::NoContextMenu); 
     verticalScrollBar()->setContextMenuPolicy(Qt::NoContextMenu); 

     setBackgroundBrush(QBrush(Qt::lightGray, Qt::CrossPattern)); 
     setRenderHint(QPainter::Antialiasing); 
     setDragMode(RubberBandDrag); 
     setRubberBandSelectionMode(Qt::ContainsItemShape); 
    } 
}; 

/** 
* Nothing special about this class, just a helper class. 
* 
* A rect item has the QGraphicsItem::ItemIsSelectable QGraphicsItem::ItemIsMovable enabled. 
* So you can select and move it around. 
*/ 
class RectItem : public QGraphicsRectItem { 
public: 
    RectItem(QGraphicsItem *parent = nullptr) 
     : QGraphicsRectItem(parent) 
    { 
     const double length = 10; 
     setFlags(ItemIsSelectable | ItemIsMovable | ItemSendsGeometryChanges); 
     setRect(-length/2.0, -length/2.0, length, length); 
    } 

    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = Q_NULLPTR) override 
    { 
     setBrush(isSelected() ? QBrush(Qt::gray) : Qt::NoBrush); 
     QGraphicsRectItem::paint(painter, option, widget); 
    } 

protected: 
    QVariant itemChange(GraphicsItemChange change, const QVariant &value) override 
    { 
     switch(change) { 
     case ItemPositionChange: case ItemSelectedChange: 
      if(scene()) { 
       scene()->update(); // just to avoid some ugly effect occuring on the scene. 
      } 
      break; 

     default: 
      break; 
     } 

     return QGraphicsRectItem::itemChange(change, value); 
    } 
}; 

/** 
* A quite simple version of what a cubic Bezier curve is: 
*  it starts at a given point "from", 
*  ends at some point "to", 
*  having two control points (let's say "ctrlPt1" and ctrlPt2"). 
* 
* A curve has the QGraphicsItem::ItemIsSelectable enabled. 
* So you can select it. 
*/ 
class Curve : public QGraphicsItem { 
protected: 
    RectItem from; 
    RectItem ctrlPt1; 
    RectItem ctrlPt2; 
    RectItem to; 

public: 
    Curve(QGraphicsItem *parent = nullptr) 
     : QGraphicsItem(parent) 
    { 
     // simple customization 

     setFlags(ItemIsSelectable); 

     // set positions 

     const qreal h = 100.; 
     const qreal d = 100.; 

     from.setPos(-150, 0); 
     ctrlPt1.setPos(from.pos() + QPointF(d, -h)); 
     ctrlPt2.setPos(ctrlPt1.pos() + QPointF(d, 0)); 
     to.setPos(ctrlPt2.x()+d, ctrlPt2.y()+h); 
    } 

    // Should be called after scene is defined for this item. 
    void addPoints() { 
     QList<QGraphicsRectItem*> list; 
     list << &from << &ctrlPt1 << &ctrlPt2 << &to; 
     for(auto *item : list) { 
      scene()->addItem(item); 
     } 
    } 

    QRectF boundingRect() const override 
    { 
     QPolygonF poly; 
     poly << from.pos() << ctrlPt1.pos() << ctrlPt2.pos() << to.pos(); 

     return poly.boundingRect() 
       .normalized(); 
    } 

    QPainterPath shape() const override 
    { 
     QPainterPath path; 
     path.moveTo(from.pos()); 
     path.cubicTo(ctrlPt1.pos(), ctrlPt2.pos(), to.pos()); 

     return path; 
    } 

    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = Q_NULLPTR) override 
    { 
     Q_UNUSED(option) 
     Q_UNUSED(widget) 

     // Draw curve 

     QPen pen = QPen(Qt::darkBlue); 
     pen.setWidthF(isSelected() ? 3. : 1.); 
     painter->setPen(pen); // curve pen 
     painter->setBrush(Qt::green); // curve brush 

     painter->drawPath(shape()); 

     // Tie ctrl points 

     const bool tieCtrlPoints = from.isSelected() || ctrlPt1.isSelected() || ctrlPt2.isSelected() || to.isSelected(); 
     if(tieCtrlPoints) { 
      painter->setPen(Qt::black); 
      painter->setBrush(Qt::black); 

      painter->drawLine(from.pos(), ctrlPt1.pos()); 
      painter->drawLine(ctrlPt1.pos(), ctrlPt2.pos()); 
      painter->drawLine(ctrlPt2.pos(), to.pos()); 
     } 
    } 
}; 

int main(int argc, char *argv[]) 
{ 
    QApplication a(argc, argv); 

    QGraphicsScene scene; 
    scene.setSceneRect(-300, -300, 600, 600); 

    View view; 
    view.setScene(&scene); 
    Curve curve; 
    scene.addItem(&curve); 
    curve.addPoints(); 

    view.show(); 

    return a.exec(); 
} 

ответ

2

Вам не нужно делать ничего особенного ожидать возвращения соответствующего QPainterPath от метода shape. Если путь, который вы возвращаете, является простым, незамкнутым путем, тогда вы должны щелкнуть именно по этому пути, чтобы выбрать. Вы не указали код для своих методов shape, но вот в чем проблема. Там не должно быть необходимости играть в игры с событиями мыши.

Сложение:

документация не говорит, но механизм выбора, как представляется, рассматривать возвращаемый путь от shape в виде замкнутого пути, действительно ли он или нет.Я смог исправить это, используя строчку, чтобы вернуть контур:

QPainterPath shape() const override 
{ 
    QPainterPath path; 
    path.moveTo(from.pos()); 
    path.cubicTo(ctrlPt1.pos(), ctrlPt2.pos(), to.pos()); 
    QPainterPathStroker stroker; 
    return stroker.createStroke (path).simplified(); 
} 

Это дает вам выбор поведения. Однако это создает новую проблему, потому что вы в настоящее время рисуете возвращаемое значение shape. С помощью этого нового кода кривая не заполняется. Я бы рекомендовал, чтобы вы создали отдельный метод, который строил путь, а затем получите как shape, так и paint путь от этого нового метода. Например:

QPainterPath buildPath() const 
{ 
    QPainterPath path; 
    path.moveTo(from.pos()); 
    path.cubicTo(ctrlPt1.pos(), ctrlPt2.pos(), to.pos()); 

    return path; 
} 

QPainterPath shape() const override 
{ 
    QPainterPath path = buildPath(); 
    QPainterPathStroker stroker; 
    return stroker.createStroke (path).simplified(); 
} 

А потом в краске, у его называют buildPath вместо shape. Этот подход более согласован с целью метода shape. Он используется для обнаружения и выбора столкновений, а не для рисования. На самом деле, если у вас очень тонкие линии, вашим пользователям может быть сложно точно на них нажимать, поэтому с помощью строка вы можете расширить ширину выделенного пути, чтобы разрешить несколько пикселей буфера вокруг кривой. Это отлично работает, но вы не хотите нарисовать этот результат.

+0

Действительно, вы правы: моя функция фигуры была неправильной. Я меняю его так, чтобы он возвращал тот же путь, что и тот, который был обращен к экрану. ** Но это ничего не меняет **. Может быть, мне что-то не хватает ... Затем я редактирую исходное сообщение, чтобы добавить фрагмент, чтобы вы могли компилировать, выполнять и видеть, о чем я говорю сам. Было очень сложно придумать этот маленький фрагмент, поскольку реальный код более сложный. Надеюсь, кто-то сможет предоставить мне решение. – misterFad

+0

Ответ отредактирован выше для обеспечения исправления. И спасибо за отличный пример кода. Я не сомневаюсь, что это было сложно создать, но то, что вы предоставили, было совершенным. – goug

+0

Wow https://translate.google.fr/#ja/en/subarashi! Он работает как сон. Большое спасибо. Но (да, есть, но), скажем, перо, используемое для рисования пути, получает нестандартный стиль, например, в 'pen.setStyle (Qt :: DashLine);' например. Затем выберите кривую (так, чтобы ширина пера художника увеличивалась). Теперь выберите одну из контрольных точек и передвиньте ее. Вы заметите, что пройденный путь немного странный: он отличается от того, который был бы нарисован, если бы функция 'shape' просто« возвращала buildPath() », не используя' QPainterPathStroker'. Любой совет, чтобы обойти это? – misterFad