目录
QwtLegend的继承关系图
QwtLegend简介
QwtAbstractLegend简介
QwtAbstractLegend的源码
实战代码(详解insertLegend( new QwtLegend() );)
(1)为什么它是在右上侧,不是在左上侧呢?
(2)它怎么直接就在界面上了,在qt中不是得先对其布局吗?
(3)还有最重要的是右上侧显示的bar0,bar1,bar2以及对应的icon从哪来的,代码中没有设置啊
要充分的了解一个类,继承关系是很重要的,子类中有很多接口是来源于基类,包括一些可重写的virtual函数,我们使用别人的库,增加一些新功能时一般需要重写库的类。所以:首先先贴个QwtLegend的继承关系图。
class QWT_EXPORT QwtAbstractLegend : public QFrame
{
Q_OBJECT
public:
explicit QwtAbstractLegend( QWidget *parent = NULL );
virtual ~QwtAbstractLegend();
/*!
Render the legend into a given rectangle.
\param painter Painter
\param rect Bounding rectangle
\param fillBackground When true, fill rect with the widget background
\sa renderLegend() is used by QwtPlotRenderer
*/
virtual void renderLegend( QPainter *painter,
const QRectF &rect, bool fillBackground ) const = 0;
//! \return True, when no plot item is inserted
virtual bool isEmpty() const = 0;
virtual int scrollExtent( Qt::Orientation ) const;
public Q_SLOTS:
/*!
\brief Update the entries for a plot item
\param itemInfo Info about an item
\param data List of legend entry attributes for the item
*/
virtual void updateLegend( const QVariant &itemInfo,
const QList &data ) = 0;
};
紧接着介绍QwtLegend:
class QScrollBar;
class QWT_EXPORT QwtLegend : public QwtAbstractLegend
{
Q_OBJECT
public:
explicit QwtLegend( QWidget *parent = NULL );
virtual ~QwtLegend();
//此处省略
............
............
............
private:
class PrivateData;
PrivateData *d_data;
};
看到QwtLegend的private成员变量中有一个class PrivateData了吧,qwt的库中凡是带有class PrivateData的类,该类的数据成员全在privateData中。这种写法的解释可以看链接:
class QwtLegend::PrivateData
{
public:
PrivateData():
itemMode( QwtLegendData::ReadOnly ),
view( NULL )
{
}
QwtLegendData::Mode itemMode;
QwtLegendMap itemMap;
class LegendView;
LegendView *view;
};
class QwtLegend::PrivateData::LegendView: public QScrollArea
{
public:
LegendView( QWidget *parent ):
QScrollArea( parent )
{
contentsWidget = new QWidget( this );
contentsWidget->setObjectName( "QwtLegendViewContents" );
setWidget( contentsWidget );
setWidgetResizable( false );
viewport()->setObjectName( "QwtLegendViewport" );
// QScrollArea::setWidget internally sets autoFillBackground to true
// But we don't want a background.
contentsWidget->setAutoFillBackground( false );
viewport()->setAutoFillBackground( false );
}
//此处省略
..............
..............
}
由上面片段代码可以看出QwtLegend的私有成员LegendView中放了一个widget。
再看下面这段代码将LegendView布局到了QwtLegend中。
QwtLegend::QwtLegend( QWidget *parent ):
QwtAbstractLegend( parent )
{
setFrameStyle( NoFrame );
d_data = new QwtLegend::PrivateData;
d_data->view = new QwtLegend::PrivateData::LegendView( this );
d_data->view->setObjectName( "QwtLegendView" );
d_data->view->setFrameStyle( NoFrame );
QwtDynGridLayout *gridLayout = new QwtDynGridLayout(
d_data->view->contentsWidget );
gridLayout->setAlignment( Qt::AlignHCenter | Qt::AlignTop );
d_data->view->contentsWidget->installEventFilter( this );
QVBoxLayout *layout = new QVBoxLayout( this );
layout->setContentsMargins( 0, 0, 0, 0 );
layout->addWidget( d_data->view );
}
综上所述:QwtLegend就只是放了一个继承自QScrollarea的legendView,然后在legendView中放了一个widget。
QwtLegend的简介完毕。我们来看看实战代码。例子是barchart。
BarChart::BarChart( QWidget *parent ):
QwtPlot( parent )
{
...
...
insertLegend( new QwtLegend() );
...
...
}
在该例中:与QwtLegend相关的只有insertLegend( new QwtLegend() );这句代码。
我们把这句话注释掉再运行,可以得出结论这句话的作用是显示右上侧的legend。这个时候我想大家应该会提出这么三个问题。
QwtLegend legend;
legend.setValue(...);
legend.setIcon(...);
没有,我们倒是看到了下面这段代码
QList titles;
for ( int i = 0; i < numBars; i++ )
{
QString title("Bar %1");
titles += title.arg( i );
}
d_barChartItem->setBarTitles( titles );
d_barChartItem->setLegendIconSize( QSize( 10, 14 ) );
但d_barChartItem是QwtPlotMultiBarChart的对象,不是QwtLegend对象啊。
我想有经验的同志们应该想到了,
第一个是默认设置在右上侧,源码如下:
在qwt_plot.h中:
void insertLegend( QwtAbstractLegend *,
LegendPosition = QwtPlot::RightLegend, double ratio = -1.0 );
LegendPosition = QwtPlot::RightLegend 看到了吧,默认在右侧。
如果你想改动到左上侧,可以写为insertLegend(legend,QwtPlot::LeftLegend);
顺便提下,在QwtLegend的简介中,有一句是需要连接 QwtPlot::legendDataChanged() 这个信号就可以进入legends对象的槽中,去执行它的代码。上面那个信号槽就是。
第二个问题的答案是:它也是用布局的方式,只是把布局隐藏到內部执行了。
在qwt_plot.cpp中:
void QwtPlot::insertLegend( QwtAbstractLegend *legend,
QwtPlot::LegendPosition pos, double ratio )
{
d_data->layout->setLegendPosition( pos, ratio );
......
......
}
void QwtPlotLayout::setLegendPosition( QwtPlot::LegendPosition pos, double ratio )
{
if ( ratio > 1.0 )
ratio = 1.0;
switch ( pos )
{
case QwtPlot::TopLegend:
case QwtPlot::BottomLegend:
if ( ratio <= 0.0 )
ratio = 0.33;
d_data->legendRatio = ratio;
d_data->legendPos = pos;
break;
case QwtPlot::LeftLegend:
case QwtPlot::RightLegend:
if ( ratio <= 0.0 )
ratio = 0.5;
d_data->legendRatio = ratio;
d_data->legendPos = pos;
break;
default:
break;
}
}
对于第三个问题,其实隐藏的是比较深的。
确实是下面的两行代码绘制了右上角的QwtLegend。
d_barChartItem->setBarTitles( titles );
d_barChartItem->setLegendIconSize( QSize( 10, 14 ) );
具体解释下:
void QwtPlotMultiBarChart::setBarTitles( const QList &titles )
{
d_data->barTitles = titles;
itemChanged();
}
d_data 指的是QwtPlotMultiBarChart的私有成员指针,具体介绍可看QwtLegend的介绍中。
第一步存储数据,第二部的itemChanged()最终执行的是qt的replot()函数,大家可以跟着断点进去看,最终是重绘。
下面代码中有关于QwtLegendData,可看我的博客关于QwtLegendData的简介。
void QwtPlotItem::setLegendIconSize( const QSize &size )
{
if ( d_data->legendIconSize != size )
{
//存储图标数据
d_data->legendIconSize = size;
legendChanged();
}
}
void QwtPlotItem::legendChanged()
{
if ( testItemAttribute( QwtPlotItem::Legend ) && d_data->plot )
d_data->plot->updateLegend( this );
}
void QwtPlot::updateLegend( const QwtPlotItem *plotItem )
{
if ( plotItem == NULL )
return;
QList legendData;
if ( plotItem->testItemAttribute( QwtPlotItem::Legend ) )
//获取QwtPlotMultiBarChart对象的LegendData数据
legendData = plotItem->legendData();
const QVariant itemInfo = itemToInfo( const_cast< QwtPlotItem *>( plotItem) );
Q_EMIT legendDataChanged( itemInfo, legendData );
}
QList QwtPlotItem::legendData() const
{
QwtLegendData data;
QwtText label = title();
label.setRenderFlags( label.renderFlags() & Qt::AlignLeft );
QVariant titleValue;
qVariantSetValue( titleValue, label );
data.setValue( QwtLegendData::TitleRole, titleValue );
const QwtGraphic graphic = legendIcon( 0, legendIconSize() );
if ( !graphic.isNull() )
{
QVariant iconValue;
qVariantSetValue( iconValue, graphic );
data.setValue( QwtLegendData::IconRole, iconValue );
}
QList list;
list += data;
return list;
}
void QwtPlot::insertLegend( QwtAbstractLegend *legend,
QwtPlot::LegendPosition pos, double ratio )
{
.......
.......
connect( this,
SIGNAL( legendDataChanged(
const QVariant &, const QList & ) ),
d_data->legend,
SLOT( updateLegend(
const QVariant &, const QList & ) )
);
.......
.......
}
void QwtLegend::updateLegend( const QVariant &itemInfo,
const QList &data )
{
//获取所有的右上角的widget,如果有的话,按照barchart例子来将总共有三个widget。
QList widgetList = legendWidgets( itemInfo );
if ( widgetList.size() != data.size() )
{
//可以看QwtLegend的简介,获取到的contentsLayout是QwtLegend中legendView的layout。
QLayout *contentsLayout = d_data->view->contentsWidget->layout();
while ( widgetList.size() > data.size() )
{
QWidget *w = widgetList.takeLast();
contentsLayout->removeWidget( w );
// updates might be triggered by signals from the legend widget
// itself. So we better don't delete it here.
w->hide();
w->deleteLater();
}
for ( int i = widgetList.size(); i < data.size(); i++ )
{
//创建右上角的widget,总共三个
QWidget *widget = createWidget( data[i] );
if ( contentsLayout )
//添加widget到布局中
contentsLayout->addWidget( widget );
//让其可见
if ( isVisible() )
{
// QLayout does a delayed show, with the effect, that
// the size hint will be wrong, when applications
// call replot() right after changing the list
// of plot items. So we better do the show now.
widget->setVisible( true );
}
widgetList += widget;
}
if ( widgetList.isEmpty() )
{
d_data->itemMap.remove( itemInfo );
}
else
{
d_data->itemMap.insert( itemInfo, widgetList );
}
//设置几个widget的tab键顺序,按tab键的时候会有顺序。
updateTabOrder();
}
for ( int i = 0; i < data.size(); i++ )
updateWidget( widgetList[i], data[i] );
}
//设置右上角QLegend中label的数据和模式
void QwtLegend::updateWidget( QWidget *widget, const QwtLegendData &data )
{
QwtLegendLabel *label = qobject_cast( widget );
if ( label )
{
label->setData( data );
if ( !data.value( QwtLegendData::ModeRole ).isValid() )
{
// use the default mode, when there is no specific
// hint from the legend data
label->setItemMode( defaultItemMode() );
}
}
}
总而言之,setLegendIconSize()的意思是存储icon数据,并绘制了QwtLegend。
将insertLegend(legend,QwtPlot::LeftLegend)
改为:
QwtLegend *legend = new QwtLegend();
QWidget* widget = legend->contentsWidget();
widget->setBackgroundRole(QPalette::Background);
widget->setStyleSheet("background-color:#37474F");
经测试可以看出QwtLegend所占的位置是右侧一大块,而不是只有显示右上侧的那么点。这也正符合了qt的布局之后的样子。
还有就是QwtLegend中有legendView继承自QScrollarea。右侧的国家如果够多就会超出QwtPlot,就会有滚动条出现。
QScrollarea这个类具有滚动条的属性。
将insertLegend(legend);改为
QwtLegend *legend = new QwtLegend();
legend->setMaxColumns(3);
效果图为: