Qt in Scala(JVM)开发梗概

移动开发 后端
本文将讲述Qt in Scala(JVM)开发梗概.本篇文章涉及的开发环境如下:windows xp sp2,JDK6u21,Scala 2.8.0 final,Qt Jambi LGPL 4.5.2_01,IDE选NetBeans吧!

本文将讲述Qt in Scala(JVM)开发梗概。

前言

一直有人问Qt的开发情况,希望有个感性的认识。一直也有整理这方面资料的冲动,但时间也很紧迫,所以长话短说。目前将目标锁定Qt Jambi版本吧,熟悉了Qt以后,我对其C++版本的兴趣也十分浓厚,所以,将来再慢慢整理吧。

本篇文章涉及的开发环境如下:windows xp sp2JDK6u21Scala 2.8.0 finalQt Jambi LGPL 4.5.2_01,IDE选NetBeans吧!

列出Qt的文档中心的几个重要资源的链接入口:

Qt Jambi 4.5.2_01的文档索引

API Javadoc

CSS样式说明(内容彼此交叉,十分详尽,你只需要有一点点的CSS基础就能明白)

官方实例

Qt Jambi 4.5.2_01Binary for Windows 32-bit(点击将自动下载)

关于Qt的点滴,我会以注释的形式写在代码里面,毕竟都是写代码的,对注释会比较敏感。

Hello Qt in Scala

  1. package qt.demo  
  2.  
  3. import com.trolltech.qt.gui._  
  4.  
  5. object HelloQt {  
  6.  
  7.   def main(args: Array[String]): Unit = {  
  8.     QApplication.initialize(args)  
  9.     (new QLabel("Hello Qt")).show  
  10.     QApplication.exec  
  11.   }  
  12. }  
  13.  

可能上述代码还能再度简化一下,比如去掉new QLabel两边的括号(但可能会很怪异了)。这个很简单吧,输出结果如下图:

去掉new QLabel两边的括号  

QApplication是Qt的一个全局单例类,就把他看作是一个总控制中心吧。他是一个static类,通过调用QApplication.instance()方法,可获得当前运行过程中的app实例。

QApplication.instance是一个全局控制实例,这里所定义的内容(可以定义的东西,详细请看手册),除非在实例具体某个对象时有具体设置,否则全局都按照instance的设置进行。当然,其实多数时候,我会用他来控制全局的样式定义。

好吧,上述的例子实在简单的有些恶心了,我们来些实际一点的东西:

  1. package qt.demo  
  2.  
  3. import com.trolltech.qt.gui._  
  4. import com.trolltech.qt.core.Qt._  
  5.  
  6. object CustomWindow {  
  7.  
  8.   val globalStyle = """  
  9. * { font-family: Mircosoft Yahei; font-size: 12px; color: #333; }  
  10. #mainWindow { border: 40px solid #ccc; border-image: url(classpath:qt/demo/resource/window.png) 40 stretch; }  
  11. """  
  12.  
  13.   def main(args: Array[String]): Unit = {  
  14.     QApplication.initialize(args)  
  15.     QApplication.instance.setStyleSheet(globalStyle)  
  16.     val frame = new QFrame() {  
  17.       this.setObjectName("mainWindow")  
  18.       // 以下为窗体展现定制,应该在show之前调用  
  19.       // show以后再调用,会令窗体crash,你需要再次show  
  20.       this.setWindowFlags(WindowType.FramelessWindowHint)  
  21.       this.setAttribute(WidgetAttribute.WA_TranslucentBackground, true)  
  22.       // 由于设定了不使用windows窗体,所以,请手动结束多余的进程  
  23.     }  
  24.     frame.show()  
  25.     QApplication.exec  
  26.   }  
  27. }  

截图效果如下:

 截图效果

怎么样,开始有点意思了吧?30行代码连样式,其实想做漂亮的界面,也不是那么难吧!

安装Qt Jambi

回到最初点,首先还是要把环境搭建起来。先去上面的地址下载Qt Jambi 4.5.2_01Binary for Windows 32-bit,随便解压吧。解开目录,里面有几个值得一看的东西:

 

qtjambi.exe 这个仅仅是运行一个Demo示例,看看吧,里面很多东西都会给你带来不错的启发。但不得不说,Qt原版的Demo,那叫一个炫啊,Java真受冷落。

designer.bat 这个是打开设计器的,实际设计器在这个目录下的bin目录里面。

qtjambi-4.5.2_01.jar

qtjambi-win32-msvc2005-4.5.2_01.jar 这两个jar包是你在实际开发中需要使用的,你需要将这两个库引入到你的项目中。并且从文件名我们可以发现,他是使用vc2005(vc80),如果你没安装vs2005补丁,快去装一个吧。

好了,准备功夫就这么点,我们可以开始进一步的工作了!

填充基础界面

好了,我们该开始往这样一个界面里面加东西了,首先,他要有个标题栏,中间是他的视图展示部分,当然了,我们还可以加一个底部的状态条。实际上,使用设计器和标准的QMainWidget,我们能做得很好。但一方面我是代码控,而另一方面,我觉得通过代码,能展示更多感性方面的东西(透过Qt Jambi的Eclipse的插件,一个界面设计完成,它会自动帮你转换为一个Java的类,所以,你无需过分担心后续实际开发的复杂度。)。而且说实在的,他的设计器和Vs比,就差很多很多了。

既然有那么多想法,我们可以考虑给它添加一个Layout,哦,忘记说了,Qt遵循较为严格的对象机制,所有界面构造元素都继承自QWidget,而Layout,则继承自QLayout,而QLayout和QWidget则都来自QObject,当然,细分还有很多,但Layout和QWidget毕竟是大头。

刚才展示的两个例子中,QFrame和QLabel都继承自QWidget,所以你可以对他们对Show,而不用考虑窗口,父容器,你对哪个QWidget的实例调用了show方法,它就会是一个独立窗体,逻辑十分清晰。

Layout有两种声明方式(当然,new实例只有一种方式),所谓声明是指和QWidget产生关联。new QHBoxLayout(anyQWidget),或者anyQWidget.setLayout(anyQLayout)。一个QWidget实例不能进行(关联)两次以上布局,当你第二次对它进行布局关联时,系统会给你一个警告,但不会令程序或者窗体Crash。

  1. package qt.demo  
  2.  
  3. import com.trolltech.qt.gui._  
  4. import com.trolltech.qt.core.Qt._  
  5.  
  6. object CustomWindow {  
  7.  
  8.   //=======================// 全局样式 //=======================//  
  9.  
  10.   val globalStyle = """  
  11. * { font-family: Microsoft Yahei; font-size: 12px; color: #333; }  
  12. #mainWindow { border: 32px solid #ccc; border-image: url(classpath:qt/demo/resource/window.png) 32 stretch; }  
  13. #title { font-size: 13px; font-weight: bold; color: #000; }  
  14. #body { border: 1px solid #ccc; }  
  15. """  
  16.  
  17.   //=======================// 主窗体 //=======================//  
  18.   //  
  19.   // 我们将frame变量转移到这里  
  20.   // lazy关键字,表示调用时才实现该变量值  
  21.   lazy val frame = new QFrame() {  
  22.     this.setObjectName("mainWindow")  
  23.     // 以下为窗体展现定制,应该在show之前调用  
  24.     // show以后再调用,会令窗体crash,你需要再次show  
  25.     this.setWindowFlags(WindowType.FramelessWindowHint)  
  26.     this.setAttribute(WidgetAttribute.WA_TranslucentBackground, true)  
  27.     // 由于设定了不使用windows窗体,所以,请手动结束多余的进程  
  28.   }  
  29.  
  30.   // 为了进一步分工明确,我又把layout也挪出来了  
  31.   // 这里采用声明关联  
  32.   // 一个layout会默认撑满整个容器  
  33.   lazy val frameLayout = new QVBoxLayout(frame) {  
  34.     this.setMargin(0)  // Layout四边的margin,当然你也可以用setContentsMargin来设定四边的具体值  
  35.     this.setSpacing(0) // 设定元件之间的间距  
  36.   }  
  37.  
  38.   //=======================// 标题栏 //=======================//  
  39.   //  
  40.   // 要构造界面,未必需要全部都是widget  
  41.   // layout也可以插入到layout中  
  42.   lazy val titleLayout = {  
  43.     val _layout = new QHBoxLayout // 这是一个内部变量,不在类变量中  
  44.     // 让他垂直居中,左对齐  
  45.     _layout.setSpacing(5)  
  46.     _layout.setAlignment(new Alignment(AlignmentFlag.AlignLeft, AlignmentFlag.AlignVCenter))  
  47.     _layout.addWidget(titleIcon)  
  48.     _layout.addWidget(titleText)  
  49.     _layout // 这里等同于这个block return _layout  
  50.   }  
  51.  
  52.   // 标题的元素构成  
  53.   lazy val titleIcon = new QLabel {  
  54.     this.setPixmap(getIcon.pixmap(18))  
  55.     // 指定他的具体宽高  
  56.     this.setFixedSize(20, 20)  
  57.     // 让该容器按照该宽高占位  
  58.     this.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)  
  59.   }  
  60.   lazy val titleText = new QLabel("这是一个标题栏") {  
  61.     this.setObjectName("title")  
  62.     // 只指定具体高度  
  63.     this.setFixedHeight(20)  
  64.     // 他的水平方向会100%的撑开,垂直方向按指定的高度占位  
  65.     this.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)  
  66.   }  
  67.  
  68.   //=======================// body部分 //=======================//  
  69.  
  70.   lazy val body = {  
  71.     val widget = new QWidget  
  72.     widget.setObjectName("body")  
  73.     widget.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)  
  74.     widget  
  75.   }  
  76.  
  77.   //=======================// foot部分 //=======================//  
  78.  
  79.   lazy val foot = new QLabel("状态栏") {  
  80.     this.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)  
  81.     this.setAlignment(new Alignment(AlignmentFlag.AlignRight, AlignmentFlag.AlignBottom))  
  82.   }  
  83.  
  84.   //=======================// 快捷函数 //=======================//  
  85.  
  86.   def app = QApplication.instance  
  87.  
  88.   def getIcon = frame.style.standardIcon(QStyle.StandardPixmap.SP_MessageBoxInformation)  
  89.  
  90.   def main(args: Array[String]): Unit = {  
  91.     QApplication.initialize(args)  
  92.     app.setStyleSheet(globalStyle)  
  93.     frameLayout.addLayout(titleLayout)  
  94.     frameLayout.addWidget(body)  
  95.     frameLayout.addWidget(foot)  
  96.     frame.resize(370, 270) // 这里要计算样式中border-width的宽度,实际上我们期望这个窗体的内容大小在300, 200  
  97.     frame.show()  
  98.     QApplication.exec  
  99.   }  
  100. }  
  101.  

效果截图:

  效果截图

从30行激增到100行,可能需要点耐性去读。但整个窗体的初步效果已经看出来了,而且,很显然,类似这样的窗体,我们完全可以将他封装成一个单独的类。当然模拟一个完整的窗口,也许需要更多更多的代码,但通过良性的封装,能很好的解决代码冗余的问题。

Qt的信号槽和事件

信号槽,是Qt中一个有趣的设定,这个也体现了Qt在Ui方面的一种经验的积累。

首先给出最最简单的信号槽的代码样例。

  1. class ClickEvent(widget: QWidget) {  
  2.    
  3.   def doClicked(checked: Int) {  
  4.     // 若干操作  
  5.   }  
  6. }  
  7.  
  8. val btn = new QPushButton()  
  9. btn.clicked.connect(anyInstance, "doClicked(int)")  

好了,当你点击了btn以后,他会自动执行doClicked方法。也许你会说,切,这有什么了不起的呢?如果要细说,恐怕不是一时半会能说的清楚的,大家也许还焦急着进一步完善刚才做出来的窗口。OK,我长话短说。

在Qt所有类的上层,Event接口,是由QObject去定义的,而QWidget实际上是继承自QObject的。QObject::installEventFilter(QObject),实际上是注册事件的总入口。当然,在你每次实例一个QObject的时候,他已经默认的帮你为当前实例installEventFilter。这个方法实际上就是指派给具体哪个实例作为该对象的事件观察者。

从installEventFileter以后,事件首先经过evnetFilter进行分发,event应该是同级的事件分发。在这两个方法里,需要指定返回Boolean类型,实际上是对事件的拦截。

再其次,到各种种类繁多,因应具体类而产生的event入口,比如showEvent等等。在这个层面,事件经由上层的分发,已经不会再等待你返回结果(他是返回无类型的),虽然对于QEvent实例,有accept或者igroe,但实际上这个过程里,你已无法保证绝对控制事件的整体了,你只能控制经由上层分到你这一级以后的事情,可是上层做了什么,你不知道,也管不了。

在这个事件机制的基础之下,信号槽似乎是处于整个事件机制的***端,但信号槽机制又有其主要特点。他是单向的,不管理该实例的全局状态,他只关心他最关心(当然也是指派给他)的对象,这个(或若干个)对象往往是整个事件机制中最关键的一个状态,他提供给你一个既不用单独注册一个QObject以从头接管实例的全部事件,也不用利用override的方式重载事件声明,即可利用信号槽,去做其他关联操作的触发。而无论他有或者无,不会对实例本身的状态进行改变,所以他是一个松耦合、无歧义的接口。

而且,Qt提供给你自己扩展信号槽的机会,除了常规的Qt类可获得信号槽以外,在你自定义的类中,也可以通过继承自QSignalEmitter,来获得信号槽机制的使用。

好了废话一大堆,让我们继续干活,这次,我们添加一个关闭按钮,并且,让这个窗口可以被自由的拖动,而这也将是***将要完成的工作:

  1. package qt.demo  
  2.  
  3. import com.trolltech.qt.gui._  
  4. import com.trolltech.qt.core._  
  5. import com.trolltech.qt.core.Qt._  
  6.  
  7. object CustomWindow {  
  8.  
  9.   //=======================// 全局样式 //=======================//  
  10.  
  11.   val globalStyle = """  
  12. * { font-family: Microsoft Yahei; font-size: 12px; color: #333; }  
  13. #mainWindow { border: 32px solid #ccc; border-image: url(classpath:qt/demo/resource/window.png) 32 stretch; }  
  14. #title { font-size: 13px; font-weight: bold; color: #000; }  
  15. #body { border: 1px solid #ccc; }  
  16. #closeBtn { border: 0; background: none; font-weight: bold; color: red; }  
  17. """  
  18.  
  19.   //=======================// 主窗体 //=======================//  
  20.   // 其实我们可以将这个Frame作为一个单独类封装一下  
  21.   class DemoFrame extends QFrame {  
  22.  
  23.     this.setObjectName("mainWindow")  
  24.     // 以下为窗体展现定制,应该在show之前调用  
  25.     // show以后再调用,会令窗体crash,你需要再次show  
  26.     this.setWindowFlags(WindowType.FramelessWindowHint)  
  27.     this.setAttribute(WidgetAttribute.WA_TranslucentBackground, true)  
  28.  
  29.     val frameLayout = new QVBoxLayout(this) {  
  30.       this.setMargin(0)  // Layout四边的margin,当然你也可以用setContentsMargin来设定四边的具体值  
  31.       this.setSpacing(0) // 设定元件之间的间距  
  32.     }  
  33.  
  34.     private def frame = this 
  35.  
  36.     //=======================// 标题栏 //=======================//  
  37.  
  38.     val titleLayout = new QHBoxLayout {  
  39.       setSpacing(5)  
  40.       setAlignment(new Alignment(AlignmentFlag.AlignLeft, AlignmentFlag.AlignVCenter))  
  41.     }  
  42.  
  43.     // 标题的元素构成  
  44.     val titleIcon = new QLabel {  
  45.       this.setPixmap(getIcon.pixmap(18))  
  46.       // 指定他的具体宽高  
  47.       this.setFixedSize(20, 20)  
  48.       // 让该容器按照该宽高占位  
  49.       this.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)  
  50.     }  
  51.     val titleText = new QLabel("这是一个标题栏") {  
  52.       this.setObjectName("title")  
  53.       // 只指定具体高度  
  54.       this.setFixedHeight(20)  
  55.       // 他的水平方向会100%的撑开,垂直方向按指定的高度占位  
  56.       this.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)  
  57.       // 我们从这里注册title拖动  
  58.       // 其实***是title首先是个QWidget,那么我们可以对整个title进行拖动,请原谅我的懒惰  
  59.       val dragPosition = new QPoint()  
  60.       // 鼠标单击放下  
  61.       override def mousePressEvent(event: QMouseEvent) {  
  62.         if (event.button() == MouseButton.LeftButton) {  
  63.           val topLeft = frame.frameGeometry.topLeft  
  64.           dragPosition.setX(event.globalPos.x - topLeft.x)  
  65.           dragPosition.setY(event.globalPos.y - topLeft.y)  
  66.           event.accept  
  67.         }  
  68.       }  
  69.  
  70.       override def mouseMoveEvent(event: QMouseEvent) {  
  71.         if (event.buttons().isSet(MouseButton.LeftButton)) {  
  72.           val topLeft = frame.frameGeometry.topLeft  
  73.           val p = new QPoint(event.globalPos().x() - dragPosition.x,  
  74.                              event.globalPos().y() - dragPosition.y)  
  75.           frame.move(p)  
  76.           event.accept  
  77.         }  
  78.       }  
  79.     }  
  80.  
  81.     val closeBtn = new QPushButton("X")  
  82.     closeBtn.setObjectName("closeBtn")  
  83.     closeBtn.setFixedSize(15, 15)  
  84.     // 其实这里可以写成 this.clicked.connect(QApplication.instance, "quit()")  
  85.     // close表示关闭frame窗口,quit则是整个程序退出  
  86.     // qt内部会做调整,当你close窗口,又没有其他窗口在show或者准备show,他也会自动进行quit  
  87.     closeBtn.clicked.connect(this, "close()")  
  88.  
  89.     override def showEvent(event: QShowEvent) {  
  90.       val g = this.frameGeometry  
  91.       val (w, h) = (g.width + 70, g.height + 70)  
  92.       this.setFixedSize(w, h) // 根据样式重设大小  
  93.       closeBtn.move(w - 45, 30)  
  94.     }  
  95.  
  96.     //=======================// body部分 //=======================//  
  97.  
  98.     val body = {  
  99.       val widget = new QWidget  
  100.       widget.setObjectName("body")  
  101.       widget.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)  
  102.       widget  
  103.     }  
  104.  
  105.     //=======================// foot部分 //=======================//  
  106.  
  107.     val foot = new QLabel("状态栏") {  
  108.       this.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)  
  109.       this.setAlignment(new Alignment(AlignmentFlag.AlignRight, AlignmentFlag.AlignBottom))  
  110.     }  
  111.  
  112.     this.init()  
  113.  
  114.     def init() {  
  115.       titleLayout.addWidget(titleIcon)  
  116.       titleLayout.addWidget(titleText)  
  117.       frameLayout.addLayout(titleLayout)  
  118.       frameLayout.addWidget(body)  
  119.       frameLayout.addWidget(foot)  
  120.       closeBtn.setParent(this) // closeBtn***填充,因为他不在布局模式中。  
  121.     }  
  122.  
  123.     def getIcon = frame.style.standardIcon(QStyle.StandardPixmap.SP_MessageBoxInformation)  
  124.   }  
  125.  
  126.   def app = QApplication.instance  
  127.  
  128.   lazy val frame = new DemoFrame  
  129.  
  130.   def main(args: Array[String]): Unit = {  
  131.     QApplication.initialize(args)  
  132.     app.setStyleSheet(globalStyle)  
  133.     frame.resize(300, 200) // 我们可以自由的设定这个部件的大小了,调整元件的大小被我们送去showEvent里面去了  
  134.     frame.show()  
  135.     QApplication.exec  
  136.   }  
  137. }  

好了,弄了这么半天,这个窗口也终于算是做成了,这是最终的效果,虽然和之前的版本没有什么差别,可是能拖动,效果却很不一样了。***来个留影合照哈!

 

后记

Qt由于是基于C++的,所以除了UI界面方面,在很多细节都进行封装,比如有QString,QDevice,QByteArray等。而在HTTP、图形渲染、opengl、数据库驱动等方面类库,Qt也都一丝不苟的移植到了Qt Jambi上。事实上,Qt似乎包揽了作为客户端开放的方方面面,当然,其实Java自身也包含了JDBC、Swing等,权当是做一个比较吧。

【编辑推荐】

  1. 用Scala实现Qt QWidget对象的Eventable接口
  2. 用Qt实现类似QQ截图的工具
  3. Qt 4使用MySQL的中文问题解决方法
  4. QML教程:构建和安装QtComponents
  5. QML教程:Qt-Quick六大开源组件
责任编辑:佚名 来源: oschina
相关推荐

2018-04-17 14:41:41

Java堆内存溢出

2014-05-20 16:27:35

JVMScala

2011-03-18 19:37:38

Eventable接口QtWidget

2011-06-28 11:05:19

Qt QWidget Eventable

2011-06-13 14:29:40

Qt Designer

2009-09-07 18:14:55

Scala开发环境

2009-10-30 10:45:45

ScalaEclipseAndroid

2011-06-20 13:05:53

Qt 4.7 Qt Quick

2010-04-13 14:20:32

2011-06-08 14:24:20

JVM Qt QtJambi

2011-06-15 17:28:23

Qt 多视图 架构

2011-06-15 18:38:17

Linux Qt Symbian

2011-06-14 11:48:38

Webkit QT

2012-06-08 09:28:15

EclipseScalaAndroid

2014-01-07 10:12:15

Spark

2011-06-16 16:21:06

Qt Symbian FAQ

2011-08-30 16:08:24

Qt4.7Qt Quick

2020-11-24 09:50:22

大数据语言go

2011-06-09 18:24:36

QT Wince

2011-06-08 17:32:18

QT VS2008 开发环境
点赞
收藏

51CTO技术栈公众号