SIGNAL-SLOT是Qt的一大特色,使用起来十分方便。在传统的AWT和Swing编程中,我们都是为要在

监听的对象上添加Listener监听器。被监听对象中保存有Listener的列表,当相关事件发生时,被监听
对象会通知所有Listener。而在Qt中,我们只需通过connect方法连接两个对象上的方法就可以了,非常
方便、优雅地实现了传统的观察者Observer模式。
 
Qt是如何办到的呢?对于发出SIGNAL的对象,我们需要在其头文件定义中声明Q_Object宏,之后Qt的
预处理器MOC会为我们自动添加上相应的代码来实现SIGNAL-SLOT机制。这与AspectJ自定义了Javac
编译器很类似,都是通过增强编译器来自动添加相应的代码。
 
增强编译或增加预处理太复杂,怎样能够简单的实现这种机制呢?首先我们实现一个类似的QObject类,
需要发射SIGNAL的类都要继承它。在QObject类中,我们自动为其子类提供监听器列表,查找SLOT方法,
信号发射等功能。
 
QObject.java
 
1.在连接方法中,我们将信号和新建的ReceiverSlot类保存到Map中,从而将它们关联起来。
  1. public static void connect(QObject sender, String signal, Object receiver, String slot) {
  2. if (sender.signalSlotMap == null)
  3. sender.signalSlotMap = new HashMap<String, List<ReceiverSlot>>();
  4. List<ReceiverSlot> slotList = sender.signalSlotMap.get(signal);
  5. if (slotList == null) {
  6. slotList = new LinkedList<ReceiverSlot>();
  7. sender.signalSlotMap.put(signal, slotList);
  8. }
  9. slotList.add(createReceiverSlot(receiver, slot));
  10. }
  1. static class ReceiverSlot {
  2. Object receiver;
  3. Method slot;
  4. Object[] args;
  5. }
 
2.在创建ReceiverSlot时,我们解析SLOT方法名,如将slot(String,String)解析为方法slot,参数两个String。
如果解析失败我们就认为该SLOT仍是一个信号,也就是SIGNAL-SIGNAL的连接。这种情况下,我们需要
传递调用的不是receiver的SLOT方法,而是emit方法继续发射信号。
  1. private static ReceiverSlot createReceiverSlot(Object receiver, String slot) {
  2. ReceiverSlot receiverSlot = new ReceiverSlot();
  3. receiverSlot.receiver = receiver;
  4. Pattern pattern = Pattern.compile("(\\w+)\\(([\\w+,]*)\\)");
  5. Matcher matcher = pattern.matcher(slot);
  6. if (matcher.matches() && matcher.groupCount() == 2) {
  7. // 1.Connect SIGNAL to SLOT
  8. try {
  9. String methodName = matcher.group(1);
  10. String argStr = matcher.group(2);
  11. ArrayList<String> argList = new ArrayList<String>();
  12. pattern = Pattern.compile("\\w+");
  13. matcher = pattern.matcher(argStr);
  14. while (matcher.find())
  15. argList.add(matcher.group());
  16. String[] arguments = argList.toArray(new String[0]);
  17. receiverSlot.slot = findMethod(receiver, methodName, arguments);
  18. receiverSlot.args = new Object[0];
  19. }
  20. catch (Exception e) {
  21. e.printStackTrace();
  22. }
  23. }
  24. else {
  25. // 2.Connect SIGNAL to SIGNAL
  26. if (receiver instanceof QObject) {
  27. receiverSlot.slot = emitMethod;
  28. receiverSlot.args = new Object[] { slot };
  29. }
  30. }
  31. return receiverSlot;
  32. }
  1. private static Method emitMethod;
  2. protected Map<String, List<ReceiverSlot>> signalSlotMap;
  3. static {
  4. try {
  5. emitMethod = QObject.class.getDeclaredMethod("emit", String.class, Object[].class);
  6. } catch (Exception e) {
  7. e.printStackTrace();
  8. }
  9. }
 
3.解析后,如果是SIGNAL-SLOT的连接,那我我们根据方法名和参数找到该方法,准备反射调用。
  1. private static Method findMethod(Object receiver, String methodName, String[] arguments)
  2. throws NoSuchMethodException {
  3. Method slotMethod = null;
  4. if (arguments.length == 0)
  5. slotMethod = receiver.getClass().getMethod(methodName, new Class[0]);
  6. else {
  7. for (Method method : receiver.getClass().getMethods()) {
  8. // 1.Check method name
  9. if (!method.getName().equals(methodName))
  10. continue;
  11. // 2.Check parameter number
  12. Class<?>[] paramTypes = method.getParameterTypes();
  13. if (paramTypes.length != arguments.length)
  14. continue;
  15. // 3.Check parameter type
  16. boolean isMatch = true;
  17. for (int i = 0; i < paramTypes.length; i++) {
  18. if (!paramTypes[i].getSimpleName().equals(arguments[i])) {
  19. isMatch = false;
  20. break;
  21. }
  22. }
  23. if (isMatch) {
  24. slotMethod = method;
  25. break;
  26. }
  27. }
  28. if (slotMethod == null)
  29. throw new NoSuchMethodException("Cannot find method[" + methodName +
  30. "] with parameters: " + Arrays.toString(arguments));
  31. }
  32. return slotMethod;
  33. }
 
4.发射信号时,我们取到所有与该SIGNAL关联的ReceiverSlot类,逐个发射信号。
  1. protected void emit(String signal, Object... args) {
  2. System.out.println(getClass().getSimpleName() + " emit signal " + signal);
  3. if (signalSlotMap == null)
  4. return;
  5. List<ReceiverSlot> slotList = signalSlotMap.get(signal);
  6. if (slotList == null || slotList.isEmpty())
  7. return;
  8. for (ReceiverSlot objSlot : slotList) {
  9. try {
  10. if (objSlot.slot == emitMethod)
  11. objSlot.slot.invoke(objSlot.receiver, objSlot.args[0], args);
  12. else
  13. objSlot.slot.invoke(objSlot.receiver, args);
  14. }
  15. catch (Exception e) {
  16. e.printStackTrace();
  17. }
  18. }
  19. }
 
之后,我们实现一个它的子类QWidget,将常用的Swing控件都封装在QWidget的子类中,为这些控件提供
常见的预定义的SIGNAL,像Qt中的clicked和returnPressed。
 
QWidget.java
  1. public class QWidget<T extends JComponent> extends QObject implements QSwing<T> {
  2. protected T widget;
  3. public QWidget(Class<T> clazz) {
  4. try {
  5. widget = clazz.newInstance();
  6. }
  7. catch (Exception e) {
  8. e.printStackTrace();
  9. }
  10. }
  11. @Override
  12. public T getSwingWidget() {
  13. return this.widget;
  14. }
  15. }
 
以下是封装了JButton和JTextField的QWidget子类。
 
QPushButton.java
  1. public class QPushButton extends QWidget<JButton> {
  2. public static final String CLICKED = "clicked";
  3. public QPushButton(String text) {
  4. super(JButton.class);
  5. widget.setText(text);
  6. widget.addActionListener(new ActionListener() {
  7. @Override
  8. public void actionPerformed(ActionEvent e) {
  9. emit(CLICKED);
  10. }
  11. });
  12. }
  13. }
 
QLineEdit.java
  1. public class QLineEdit extends QWidget<JTextField> {
  2. public static final String RETURN_PRESSED = "returnPressed";
  3. public QLineEdit() {
  4. super(JTextField.class);
  5. widget.addActionListener(new ActionListener() {
  6. @Override
  7. public void actionPerformed(ActionEvent e) {
  8. emit(RETURN_PRESSED);
  9. }
  10. });
  11. }
  12. }
 
下面我们来写个测试类实验下Java版的SIGNAL-SLOT机制,依旧是之前的浏览器的例子。
 
AddressBar.java
  1. public class AddressBar extends QWidget<JPanel> {
  2. /**
  3. * SIGNAL
  4. */
  5. public static final String NEW_BUTTON_CLICKED = "newButtonClicked";
  6. public static final String GO_TO_ADDRESS = "goToAddress(String,String)";
  7. /**
  8. * SLOT
  9. */
  10. public static final String HANDLE_GO_TO_ADDRESS = "handleGoToAddress()";
  11. private QPushButton newButton;
  12. private QLineEdit addressEdit;
  13. private QPushButton goButton;
  14. public AddressBar() {
  15. super(JPanel.class);
  16. // 1.Create widget
  17. newButton = new QPushButton("New");
  18. addressEdit = new QLineEdit();
  19. goButton = new QPushButton("Go");
  20. // 2.Set property
  21. addressEdit.getSwingWidget().setColumns(10);
  22. // 3.Connect signal-slot
  23. connect(newButton, QPushButton.CLICKED, this, NEW_BUTTON_CLICKED);
  24. connect(addressEdit, QLineEdit.RETURN_PRESSED, this, HANDLE_GO_TO_ADDRESS);
  25. connect(goButton, QPushButton.CLICKED, this, HANDLE_GO_TO_ADDRESS);
  26. // 4.Add to layout
  27. getSwingWidget().add(newButton.getSwingWidget());
  28. getSwingWidget().add(addressEdit.getSwingWidget());
  29. getSwingWidget().add(goButton.getSwingWidget());
  30. }
  31. public void handleGoToAddress() {
  32. emit(GO_TO_ADDRESS, addressEdit.getSwingWidget().getText(), "test string");
  33. }
  34. }
 
TabBar.java
  1. public class TabBar extends JTabbedPane {
  2. /**
  3. * SLOT
  4. */
  5. public static final String HANDLE_NEW_TAB = "handleNewTab()";
  6. public static final String HANDLE_GO_TO_SITE = "goToSite(String,String)";
  7. public TabBar() {
  8. handleNewTab();
  9. }
  10. public void handleNewTab() {
  11. WebView tab = new WebView();
  12. add("blank", tab);
  13. }
  14. public void goToSite(String url, String testStr) {
  15. System.out.println("Receive url: " + url + ", " + testStr);
  16. WebView tab = (WebView) getSelectedComponent();
  17. tab.load(url);
  18. }
  19. }
 
MainWindow.java
  1. public class MainWindow extends JFrame {
  2. public static void main(String[] args) {
  3. JFrame window = new MainWindow();
  4. window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  5. window.setSize(320, 340);
  6. window.setVisible(true);
  7. }
  8. public MainWindow() {
  9. // 1.Create widget
  10. AddressBar addressBar = new AddressBar();
  11. TabBar tabBar = new TabBar();
  12. // 2.Set property
  13. // 3.Connect signal-slot
  14. QObject.connect(addressBar, AddressBar.NEW_BUTTON_CLICKED, tabBar, TabBar.HANDLE_NEW_TAB);
  15. QObject.connect(addressBar, AddressBar.GO_TO_ADDRESS, tabBar, TabBar.HANDLE_GO_TO_SITE);
  16. // 4.Add to layout
  17. GridBagLayout layout = new GridBagLayout();
  18. setLayout(layout);
  19. GridBagConstraints grid = new GridBagConstraints();
  20. grid.fill = GridBagConstraints.BOTH;
  21. grid.gridx = grid.gridy = 0;
  22. grid.weightx = 1.0;
  23. grid.weighty = 0.1;
  24. add(addressBar.getSwingWidget(), grid);
  25. grid.fill = GridBagConstraints.BOTH;
  26. grid.gridx = 0;
  27. grid.gridy = 1;
  28. grid.weightx = 1.0;
  29. grid.weighty = 0.9;
  30. add(tabBar, grid);
  31. }
  32. }
  33. @SuppressWarnings("serial")
  34. class WebView extends JEditorPane {
  35. public WebView() {
  36. setEditable(false);
  37. }
  38. public void load(final String url) {
  39. SwingUtilities.invokeLater(new Runnable() {
  40. @Override
  41. public void run() {
  42. try {
  43. WebView.this.setPage(url);
  44. } catch (IOException e) {
  45. e.printStackTrace();
  46. }
  47. }
  48. });
  49. }
  50. }
 
测试一下吧,运行起来的效果就是这样。
 
新建Tab页和前往该地址事件都可以成功地从AddressBar传递到TabBar。怎么样,这种Java版的
SIGNAL-SLOT是不是很方便。多开拓自己的视野,借鉴优秀的思想,我们才能做出更好的设计!
希望你喜欢本文。
 
参考:http://blog.csdn.net/dc_726/article/details/7632430

Java实现Qt的SIGNAL-SLOT机制的更多相关文章

  1. qt信号signal和槽slot机制

    内容: 一.概述 二.信号 三.槽 四.信号与槽的关联 五.元对象工具 六.程序样例 七.应注意的问题 信号与槽作为QT的核心机制在QT编程中有着广泛的应用,本文介绍了信号与槽的一些基本概念.元对象工 ...

  2. QT窗体间传值总结之Signal&Slot

    在写程序时,难免会碰到多窗体之间进行传值的问题.依照自己的理解,我把多窗体传值的可以使用的方法归纳如下: 1.使用QT中的Signal&Slot机制进行传值: 2.使用全局变量: 3.使用pu ...

  3. Qt的内存管理机制

    当我们在使用Qt时不可避免得需要接触到内存的分配和使用,即使是在使用Python,Golang这种带有自动垃圾回收器(GC)的语言时我们仍然需要对Qt的内存管理机制有所了解,以更加清楚的认识Qt对象的 ...

  4. qt的signal和slot机制

    signal和slot是QT中的一大特点 signal/slot是Qt对象以及其派生类对象之间的一种高效通信接口 用户可以将N多个信号和单个槽相连接, 或者将将N个槽和单个信号连接, 甚至是一个信号和 ...

  5. 详解 Qt 线程间共享数据(使用signal/slot传递数据,线程间传递信号会立刻返回,但也可通过connect改变)

    使用共享内存.即使用一个两个线程都能够共享的变量(如全局变量),这样两个线程都能够访问和修改该变量,从而达到共享数据的目的. Qt 线程间共享数据是本文介绍的内容,多的不说,先来啃内容.Qt线程间共享 ...

  6. 【golang-GUI开发】qt之signal和slot(二)

    上一篇文章里我们详细介绍了signal的用法. 今天我们将介绍slot的使用.在qt中slot和signal十分相像,这次我们将实现一个能显示16进制数字的SpinBox,它继承自QSpinbox并重 ...

  7. 【golang-GUI开发】qt之signal和slot(一)

    想了很久,我决定还是先从signal和slot(信号槽)开始讲起. signal和slot大家一定不陌生,先看一段示例(选自文档): class Counter : public QObject { ...

  8. C++11实现Qt的信号槽机制

    概述 Qt的信号槽机制是Qt的核心机制,按钮点击的响应.线程间通信等都是通过信号槽来实现的,boost里也有信号槽,但和Qt提供的使用接口很不一样,本文主要是用C++11来实现一个简单的信号槽,该信号 ...

  9. VJGUI消息设计-兼谈MFC、QT和信号/槽机制

    星期六下午4点,还在公司加班.终于写完了下周要交工的一个程序. 郁闷,今天这几个小时写了有上千行代码吧?虽然大部分都是Ctrl-C+Ctrl-V,但还是郁闷. 作为一个有10年经验的MFC程序员,郁闷 ...

  10. Qt学习记录--02 Qt的信号槽机制介绍(含Qt5与Qt4的差异对比)

    一 闲谈: 熟悉Window下编程的小伙伴们,对其消息机制并不陌生, 话说:一切皆消息.它可以很方便实现不同窗体之间的通信,然而MFC库将很多底层的消息都屏蔽了,尽管使用户更加方便.简易地处理消息,但 ...

随机推荐

  1. [置顶] Java Web开发教程来袭

    Java Web,是用Java技术来解决相关web互联网领域的技术总和.web包括:web服务器和web客户端两部分.Java在客户端的应用有java applet不过现在使用的很少,Java在服务器 ...

  2. Netty源代码学习——Included transports(变速箱)

    Transport API核心: Channel介面 类图表示Channel含有Pipeline和Config接口,pipeline上一节有所介绍. Channel是线程安全的,这表示在多线环境下操作 ...

  3. ||和 && 符号的赋值运用(转)

    javascript “||”.“&&”的灵活运用 博客分类: Jquery javascript javascript中运用“||”.“&&”javascript 真 ...

  4. 1/8=1/a+1/b,a,b为自然数

    #include "stdio.h" int main(){ int a; int b; for(a=1;a<1000;a++)  {  for(b=1;b<1000; ...

  5. enter 默认搜索

    onkeydown=" if(event.keyCode==13) Search(); "

  6. jquery ajax异步调用

    写程序的第一步都要知其然,至于知其所以然就要看个人的爱好了.下面说一下web开发中经常用的ajax. 这里是用的jquery框架实现的ajax异步调用.废话少说先上代码.(asp.net开发) var ...

  7. vs在winform中不给力哈-错误不提示

    我的操作系统是windows Server 2008 x64,运行winform的时候,对Dictionary累加值.运行的时候,项目一闪而过,于是我在Project的Properties上选择运行的 ...

  8. IDEA SVN1.8 问题解决

    转自 http://blog.jetbrains.com/idea/2013/12/subversion-1-8-and-intellij-idea-13/

  9. 怎样在官网上下载xcode7.2

    其实我觉得还是有必要就这个写一篇论文的  以证明自己真的是个菜鸟 首先进入苹果开发者官网 https://developer.apple.com/ 选择 resource 然后 点击加号  然后下载就 ...

  10. corejava-chap01

    <java是什么:>Programming language 程序语言Development environment 开发环境Application environment 应用环境Dep ...