views:

261

answers:

4

Collecting usage statistics per web-page on sites is common practice, I'm interested in a similar thing, but for GUI:s. You see Google Chrome (and others) collect usage statistics so Google can find out what features people use, to data-mine what seems to "work".

A straight-forward way to do this is to explicitly log the interaction with every GUI element, but that is both tedious and prone to mistakes in missing parts of the GUI.

So what I wonder, is this a solved problem? Is there anything existing that can provide a summary similar to code-profiling, metrics (number of visits, clicks, etc) broken down on per component? Automatically added to all components in the whole tree of AWT/Swing components?

This information would need to be summarized to a file so it can be sent to "us" for aggregation and data mining, to drive decisions etc.

I don't really know exactly what I want, so I am also asking to find out good ideas and what other people have done that have faced this problem.

A: 

Netbeans and Eclipse both have mechanisms for collecting UI statistics but I have no idea how easy it is to use these outwith applications based on their platforms.

Mark
A: 

Well, as far as I know, I've never seen any kind of usage statistics automatic gathering for Swing applications.

To my mind, the easiest way to implement such a feature would be to use look'n'feel : this way, each component will transparently be associated to the best fitting loggers (a JCheckBox will be listened for checks, while a JSCrollBar would have its scroll logged).

You may think about asking Kirill Grouchnikov about that feature, but I fear even Substance does not implement it.

Riduidel
A: 

Well, you would first expect to receive the usage statistics somehow. So are you going to have your app connect to a database? Are you going to have another app that process the usage statistics? In short, I would create a function like this

void LogUsage(this object id or handle name/caption text etc)

and let that function handle all the processing of handling statistical usage. Of course you will need to do /some/ kind of work such as add this function to onClicks, edits changes etc, but that should be fairly simple. Especially if your LogUsage function just takes something unique (like the name) and uses that for the statistics.

The LogUsage function can also then manage connecting remotely and clearing any cache it may have stored since its last transmit.

Simply stated, if you created a LogUsage function that accepts the object, and you always grab the name. You could just easily copy/paste this line throughout your program

LogUsage(this);

Edit-

I also noticed you are looking for suggestions: I would do what I said above; a simple LogUsage function that accepts a param such as the object, and grab the name, eg - btnLogin, and then processes that name into some kind of file. You would obviously first load this file into some kind of map or array, check to see if it exists first. If not it adds it to the list with 1 click (or usage). If it exists, it increments its usage point. You will obviously not want to call LogUsage on the OnChange method in a textbox etc, but probably all onFocus, Clicks, or whatever you are really wanting to keep track of.

In the end, you might end up with something like btnLogin (5) that is sent to you, indicating that the user clicked on btnLogin 5 times.

Handling all this received data is another endeavor, which is why I would definitely have it sent to a database rather than receiving, say an email or a server directory full of files of usage statistics. Either way, you will need something to process it and turn it into graphs or sortable usage statistics etc..

Liam
+3  A: 

Whilst this isn't a complete solution, it might help guide you closer to something workable. I agree with the previous poster, that it is possible to add hooks into your code, this becomes unmanageable on a large project. Also if you miss a part of the application and come to examine the data, you'll have a blank space each time the user uses that component.

Instead you can listen directly to AWTEvents which are generated by every component in the UI. This could easily be the source information for your data mining. The following code shows you how this is done:

package awteventlistenerexample;

import java.awt.AWTEvent;
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.FlowLayout;
import java.awt.Toolkit;
import java.awt.event.AWTEventListener;
import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;

public class Test {
    private static final String ACTION_CLOSE = "Close";
    private JFrame frame;
    public Test() {
        frame = new JFrame();
        initActions();
        frame.setLayout(new BorderLayout());
        frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
        frame.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                frame.getRootPane().getActionMap().get(ACTION_CLOSE).actionPerformed(null);
            }
        });

        JPanel content = new JPanel(new FlowLayout());
        content.add(new JLabel("Creature"));
        JButton badger = new JButton("Badger");
        badger.setName("badger");
        JButton ferret = new JButton("Ferret");
        ferret.setName("ferret");
        JButton stoat = new JButton("Stoat");
        stoat.setName("stoat");
        content.add(badger);
        content.add(ferret);
        content.add(stoat);
        frame.add(content, BorderLayout.CENTER);

        JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
        JButton close = new JButton(frame.getRootPane().getActionMap().get(ACTION_CLOSE));
        buttonPanel.add(close);

        frame.add(buttonPanel, BorderLayout.SOUTH);
        frame.setSize(200, 150);
        frame.pack();
        frame.setLocationByPlatform(true);
        frame.setVisible(true);
    }

    private void initActions() {
        Action close = new AbstractAction("Close") {
            public void actionPerformed(ActionEvent e) {
                frame.dispose();
            }
        };
        frame.getRootPane().getActionMap().put(ACTION_CLOSE, close);
    }

    public static void main(String args[]) {
        // Attach listener to AWTEvents (Mouse Events)
        Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() {
            public void eventDispatched(AWTEvent event) {
                if (event instanceof MouseEvent) {
                    MouseEvent m = (MouseEvent) event;
                    if (m.getID() == MouseEvent.MOUSE_CLICKED) {
                        System.out.println(m.toString());
                    }
                }
            }
        }, AWTEvent.MOUSE_EVENT_MASK);

        EventQueue.invokeLater(new Runnable() {
            public void run() {
                new Test();
            }
        });
    }
}

In this case I have listened to Mouse Events. These seem to be the most useful as they can tell you what the user clicked on. From here, you'll need to work out what you need to collect in order to build up a picture of what the user clicked on. I would also be interested in what the user didn't click on as well.

There is lots of work surrounding automated UI testing which uses this technique. Abbot and FEST are examples I have used. You might want to look at how they process AWTEvents in case there is anything helpful there.

gencoreoperative
Excellent answer.
Pindatjuh