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机制(保存到Map中,从而将它们关联起来,收到信号进行解析,最后反射调用)的更多相关文章

  1. kafka flumn sparkstreaming java实现监听文件夹内容保存到Phoenix中

    ps:具体Kafka Flumn SparkStreaming的使用  参考前几篇博客 2.4.6.4.1 配置启动Kafka (1) 在slave机器上配置broker 1) 点击CDH上的kafk ...

  2. 【redis,1】java操作redis: 将string、list、map、自己定义的对象保存到redis中

    一.操作string .list .map 对象 1.引入jar: jedis-2.1.0.jar   2.代码 /**      * @param args      */     public s ...

  3. Redis使用场景一,查询出的数据保存到Redis中,下次查询的时候直接从Redis中拿到数据。不用和数据库进行交互。

    maven使用: <!--redis jar包--> <dependency> <groupId>redis.clients</groupId> < ...

  4. ffmpeg从AVFrame取出yuv数据到保存到char*中

    ffmpeg从AVFrame取出yuv数据到保存到char*中   很多人一直不知道怎么利用ffmpeg从AVFrame取出yuv数据到保存到char*中,下面代码将yuv420p和yuv422p的数 ...

  5. 将数字n转换为字符串并保存到s中

    将数字n转换为字符串并保存到s中 参考 C程序设计语言 #include <stdio.h> #include <string.h> //reverse函数: 倒置字符串s中各 ...

  6. Android把图片保存到SQLite中

    1.bitmap保存到SQLite 中 数据格式:Blob db.execSQL("Create table " + TABLE_NAME + "( _id INTEGE ...

  7. c# 抓取和解析网页,并将table数据保存到datatable中(其他格式也可以,自己去修改)

    使用HtmlAgilityPack 基础请参考这篇博客:https://www.cnblogs.com/fishyues/p/10232822.html 下面是根据抓取的页面string 来解析并保存 ...

  8. Flask实战第43天:把图片验证码和短信验证码保存到memcached中

    前面我们已经获取到图片验证码和短信验证码,但是我们还没有把它们保存起来.同样的,我们和之前的邮箱验证码一样,保存到memcached中 编辑commom.vews.py .. from utils i ...

  9. 1.scrapy爬取的数据保存到es中

    先建立es的mapping,也就是建立在es中建立一个空的Index,代码如下:执行后就会在es建lagou 这个index.     from datetime import datetime fr ...

随机推荐

  1. Xamarin.Forms开发APP

    Xamarin.Forms+Prism(1)—— 开发准备 准备: 1.VS2017(推荐)或VS2015: 2.JDK 1.8以上: 3.Xamarin.Forms 最新版: 4.Prism 扩展, ...

  2. 【87.65%】【codeforces 731A】Night at the Museum

    time limit per test1 second memory limit per test256 megabytes inputstandard input outputstandard ou ...

  3. linux下仅仅有rman备份集的异机不同文件夹恢复

    昨天在客户那里做了一次rman异机的恢复,把生产库弄一份给測试库用,总库大概80G,总共花费了2个小时,当时客户的环境是windows 11.2.0.3,今天早晨在linux下又一次測试了一下,记录下 ...

  4. HSQL一个简短的引论

    前言     在对dao层写測试类的时候,我们须要一个測试数据库,一般我们会是专门建立一个真实的測试数据库,可是有了HSQLDB事情就变得简单了起来. 正题 一.简单介绍: hsql数据库是一款纯Ja ...

  5. 贝叶斯方法(Bayesian approach) —— 一种概率解释(probabilistic interpretation)

    1. Bayesian approach 对于多项式拟合问题,我们可通过最小二乘(least squares)的方式计算得到模型的参数,最小二乘法又可视为最大似然(maximum likelihood ...

  6. 百度地图 JavaScript API 极速版 开发体会

    前段时间百度地图API推出了 JavaScript API 极速版 1.0 简单看了一下,从产品定位来说真是挺好. 把开发人员细分成普通web开发人员和移动web开发人员.正好用到了手机地图这块决定尝 ...

  7. Windows DPI Awareness for WPF

    原文 Windows DPI Awareness for WPF 对于 WPF 程序,要控制程序的 DPI 感知程度,可在 App.manifest 中添加如下代码. 本文知识已经陈旧,你可以阅读这两 ...

  8. Customize Acrylic Brush in UWP Applications(在UWP中自定义亚克力笔刷)

    原文 Customize Acrylic Brush in UWP Applications(在UWP中自定义亚克力笔刷) Windows 10 Fall Creators Update(Build ...

  9. go语言刷leetcode - 53 Maximum Subarray

    package main import ( "fmt" "math" ) func maxSubArray(nums []int) int { var larg ...

  10. 至HDFS附加内容

    在最近的项目开发中遇到的问题: 需要产生良好hdfs文件的其他内容.但使用在线版1.0.3.见发现官方文件,于1.0.4支持的文件的版本号之后append 一下是向hdfs中追加信息的操作方法 假设你 ...