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. HDU 1025 Constructing Roads In JGShining&#39;s Kingdom (DP)

    Problem Description JGShining's kingdom consists of 2n(n is no more than 500,000) small cities which ...

  2. Javascript --扩展String实现替换字符串中index处字符

    String.prototype.replaceCharAt = function(n,c){ return this.substr(0, n)+ c + this.substr(n+1,this.l ...

  3. android 43 SQLite数据库

    SQLite数据库很小,占用内存只有几百K,安卓和IOS都是用的SQLite数据库. 页面: <LinearLayout xmlns:android="http://schemas.a ...

  4. Struts2和Struts1的不同

    转载(没看懂) Action 类 ◆Struts1要求Action类继承一个抽象基类org.apache.struts.action.Action.Struts1的一个普遍问题是使用抽象类编程而不是接 ...

  5. ubuntu下的openfire安装、配置、运行

    openfire服务器              Openfire 采用Java开发,开源的实时协作(RTC)服务器基于XMPP(Jabber)协议.您可以使用它轻易的构建高效率的即时通信服务器.Op ...

  6. java InputStream

    java InputStream 当为网络数据流是,不能以read为-1作为数据结束的尾. 而用下列案例获取数据. Log.v(TAG, "==========start========== ...

  7. 9.22 noip模拟试题

    水灾(sliker.cpp/c/pas) 1000MS  64MB 大雨应经下了几天雨,却还是没有停的样子.土豪CCY刚从外地赚完1e元回来,知道不久除了自己别墅,其他的地方都将会被洪水淹没. CCY ...

  8. table转list

    DataTable数据集转换为List非泛型以及泛型方式 前言 DataTable是断开式的数据集合,所以一旦从数据库获取,就会在内存中创建一个数据的副本,以便使用.由于在实际项目中,经常会将 Dat ...

  9. sql根据'/'截取最后的字符串

    filpath字段值:/DataFile/UpLoad/Logo/NoPhoto.jpg select filpath,REVERSE((SUBSTRING(REVERSE(FilPath),0,CH ...

  10. Swift 中 Selector 方法的访问权限控制问题

    今天用Swift写了个视图,在视图上加个手势,如下所示: panGestureRecognizer = UIPanGestureRecognizer(target: self, action: &qu ...