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. char* 和char[]的差别

    下面内容均来自互联网,系笔者汇总并总结. 1. 问题介绍 问题引入: 在实习过程中发现了一个曾经一直默认的错误,相同char *c = "abc"和char c[]="a ...

  2. 『零行代码』解决键盘遮挡问题(iOS)

    关注仓库,及时获得更新:iOS-Source-Code-Analyze https://github.com/draveness/iOS-Source-Code-Analyze Follow: Dra ...

  3. 安装Visual Studio 2013 中文社区版

    Visual Studio 2013 免费了,我收到邮件后,立即从邮件的下载连接安装了 Visual Studio Community 2013 with Update 4 . 安装后几天没打开,今天 ...

  4. WPF Binding值转换器ValueConverter使用简介(二)-IMultiValueConverter

    注: 需要继承IMultiValueConverter接口,接口使用和IValueConverter逻辑相同. 一.MultiBinding+Converter 多值绑定及多值转换实例 当纵向流量大于 ...

  5. 自定义Window 服务

    自定义window 服务 开发到使用的流程: 1.完成对应的代码之后(代码在底下),右键MyService.cs 添加安装程序 2.添加window服务安装程序打开Service1.cs[设计]页面, ...

  6. 关于SQL配置管理器的服务无法启动的解决办法!

    由于各种问题的因素,导致SQL服务无法启动,然后去事件查看器里看了下,有两个关于SQL 的错误.分别是实例中master.mdf和master.ldf的文件系统拒绝访问! 为了赶作业,带着焦急的心情去 ...

  7. this的一个作用 当前对象

    class Person{ String name="小花"; int age=19; void eat(){  System.out.println("在吃饭" ...

  8. 【转】 wpf系列-入门

    转自:http://www.cnblogs.com/huangxincheng/category/388852.html   8天入门wpf—— 第八天 最后的补充 摘要: 从这一篇往前看,其实wpf ...

  9. 优秀Android开源项目

    开源项目汇总: Trinea/android-open-project · GitHub 包含个性化控件.工具库.优秀项目.开发及测试工具等 优秀完整项目: 1.Google I/O Android ...

  10. C宏系统缺陷

    这两天稍稍看了一下boost的preprocessor库,发觉boost那帮疯子竟然利用各种奇技淫巧定义出各种数据类型和结构,还在上面定义出加减乘除等等各种运算,在快速浏览的过程中,还瞄到了很眼熟的各 ...