Listening for events from Windows in electron – Tutorial

Electron fires a number of events around window actions. You can use these events to tell if a
window is minmized, maximized, and its position on screen. This is handy when you need to tell if
a window needs to be restored, or minimized for a sepcific applaction event.

Here are the basics, API can be found here.

With electron installed. Create a basic package.json

1
2
3
4
5
{
    "name":"window_events",
    "main":"./main.js",
    "version":"0.0.1"
}

index.html

1
2
3
4
5
<html>
    <body>
        <p>Electron window events</p>
    </body>
</html>

Electron window events

main.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
const electron = require('electron')
const app = electron.app
 
const BrowserWindow = electron.BrowserWindow
 
var mainWindow = null;
 
app.on('ready', function(){
    mainWindow = new BrowserWindow({
        width:800,
        height:600
    });
 
    mainWindow.loadURL('file://' + __dirname + '/index.html');
    console.log("Ready");
    mainWindow.on('move',(e) =>{
        console.log('electron move');
    });
    mainWindow.on('minimize',(e) =>{
        console.log('electron minimize');
    });
    mainWindow.on('maximize',(e) =>{
        console.log('electron maximize');
    });
    mainWindow.on('restore',(e) =>{
        console.log('electron restore');
    });
});

Running the above application

C:\yourfolder\electron .

you see the following output when you:

1. Minimize the window:

electron move
electron move
electron minimize

2. Restore the window

electron move
electron move
electron restore

3. Maximize the window

electron move
electron move
electron maximize

The confusing thing about these events is that move is called when the window has not moved. Also the minimize / restore / and maximize states are the last events called.

So if we where listening to the move event we would have to check to see if the window really did move.

Windows work around.

hookWindowsMessage allows electron to directly listen to window events.

The WM_SYSCOMMAND (0x0112) message is sent for some windows events.

Add the following listener

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
mainWindow.hookWindowMessage(Number.parseInt('0x0112'),(wParam,lParam)=>{// Hook WM_SYSCOMMAND
    let eventName = null;
    if (wParam.readUInt32LE(0) == 0xF060) { //SC_CLOSE
        eventName = 'close';
    } else if (wParam.readUInt32LE(0) == 0xF030) { //SC_MAXIMIZE
        eventName = 'maximize'
    }else if (wParam.readUInt32LE(0) == 0xF020) { //SC_MINIMIZE
        eventName = 'minimize'
    }else if (wParam.readUInt32LE(0) == 0xF120) { //SC_RESTORE
        eventName = 'restored'
    }
    if(eventName != null)
    {
        console.log("WINDOWS " + (eventName));
    }
});

For the moving and resizing of the window we only want to know when the move or resize is done.

1
2
3
4
// WM_EXITSIZEMOVE 0x0232
mainWindow.hookWindowMessage(Number.parseInt('0x0232'),(wParam,lParam)=>{
    console.log("Winodws move or resize complete");
});

The windows event fired first. In this last example we only see move(resize) / maximize / minimize and restore events when they are actually fired.

Final main.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
const electron = require('electron')
const app = electron.app
 
const BrowserWindow = electron.BrowserWindow
 
var mainWindow = null;
 
app.on('ready', function(){
    mainWindow = new BrowserWindow({
        width:800,
        height:600
    });
 
    mainWindow.loadURL('file://' + __dirname + '/index.html');
    console.log("Ready");
 
    // Hook WM_SYSCOMMAND
    mainWindow.hookWindowMessage(Number.parseInt('0x0112'),(wParam,lParam)=>{
        let eventName = null;
        if (wParam.readUInt32LE(0) == 0xF060) { //SC_CLOSE
            eventName = 'close';
        } else if (wParam.readUInt32LE(0) == 0xF030) { //SC_MAXIMIZE
            eventName = 'maximize'
        }else if (wParam.readUInt32LE(0) == 0xF020) { //SC_MINIMIZE
            eventName = 'minimize'
        }else if (wParam.readUInt32LE(0) == 0xF120) { //SC_RESTORE
            eventName = 'restored'
        }
        if(eventName != null)
        {
            console.log("WINDOWS " + (eventName));
        }
    });
 
    // WM_EXITSIZEMOVE
    mainWindow.hookWindowMessage(Number.parseInt('0x0232'),(wParam,lParam)=>{
        console.log("Winodws move or resize complete");
    });
 
});
Posted in Uncategorized | Leave a comment

Very Basic JavaFX 2.0 Component

I work a lot with Swing and one of the more common tasks is creating a custom component. This usually involves some custom drawing.

Here is a quick example of a component that displays a black circle. Very basic, but it will re-size if the panels bounds change.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class SwingClock extends JPanel{
 
  @Override
  public void paintComponent(Graphics g)
  {
    super.paintComponent(g);
    Graphics2D g2d = (Graphics2D)g.create();
    Dimension size = getSize();
    int w = size.width;
    int h = size.height;
    int r = w;
    if(h < r)
      r = h;
    int x = 0;
    int y = 0;
    g2d.setColor(Color.BLACK);
    g2d.fillOval(x, y, r, r);
    g2d.dispose();
  }
 
  public static void main(String[] args) throws Exception
  {
    JFrame frame = new JFrame();
    frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    SwingClock clock = new SwingClock();
    frame.getContentPane().setLayout(new BorderLayout());
    frame.getContentPane().add(clock,BorderLayout.CENTER);
    frame.setSize(200,200);
    frame.setVisible(true);
  }
}

Now how is this done in JavaFX 2..0? Well it’s a bit more work.

You will need three classes 1. The Control, 2. The Control Skin, and 3. The stage.

First up is the Control, this will contain the model for our control. Because it does not really do anything the we only need to set the skin and define the max width and height. This is so the control will grow along with the frame.

1
2
3
4
5
6
7
8
9
public class Clock extends Control implements Skinnable
{
  public Clock()
  {
    setSkin(new ClockSkin(this));
    setMaxHeight(Double.MAX_VALUE);
    setMaxWidth(Double.MAX_VALUE);
  }
}

Next up is the Skin. The skin provides the Node used to display this control. It’s important to use the StackPane as the returned node as it will allow you to add overlapping nodes if you want.

getNode is only called when the parent had changed. So it’s ok to do some adjustments here.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class ClockSkin implements Skin
{
  private Clock skinable;
  private StackPane root;
  private Circle cirlce;
 
  public ClockSkin(Clock clock)
  {
    skinable = clock;
    root = new StackPane();
    cirlce = new Circle();
    cirlce.setFill(Color.BLACK);
    root.getChildren().add(cirlce);
  }
 
  @Override
  public void dispose()
  {
  }
 
  @Override
  public Node getNode() {
    double w = skinable.getWidth();
    double h = skinable.getHeight();
    cirlce.setCenterX(w/2);
    cirlce.setCenterY(h/2);
    double r = w;
    if(h < r)
      r = h;
    cirlce.setRadius(r/3);
    return root;
  }
 
  @Override
  public Clock getSkinnable() {
    return skinable;
  }}

This is just a basic JavaFX startup code that every applications needs.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class FirstComponent extends Application {
 
  public static void main(String[] args) {
    launch(args);
  }
 
  @Override
  public void start(Stage primaryStage) {
    primaryStage.setTitle("First Component");
    Clock clock = new Clock();
    StackPane root = new StackPane();
    root.getChildren().add(clock);
    primaryStage.setScene(new Scene(root, 300, 250));
    primaryStage.show();
  }
}

That should be it! Now you have a very basic control on which to build something more advanced.

https://github.com/robmayhew/learning-javafx-2.0

Posted in Uncategorized | Leave a comment

Java Quick Data Store

I’ve written a small library to quickly save and load data in a Java app. It’s a simple persistent data store. I plan to use it in some upcoming demos.

Example:


QDS.save("Testing", "A String");
String s = (String)QDS.load("Testing");

Downlaod src

Posted in Uncategorized | Leave a comment

JavaFX 2.0 Fading Status Message

When a user completes an action it’s nice to display a message to show that the action was successful. With JavaFX animations this becomes very easy to do.

The code below displays a message when a button is clicked, and quickly fades away.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
 
package statusflash;
 
import javafx.animation.FadeTransition;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Bounds;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.util.Duration;
 
 
public class StatusFlash extends Application {
 
 
    public static void main(String[] args) {
        Application.launch(args);
    }
 
    @Override
    public void start(Stage primaryStage) {
        primaryStage.setTitle("Status Flash");
        BorderPane layout = new BorderPane();
        Scene scene = new Scene(layout, 300, 250);
 
        final TextField text = new TextField("Added 1 Record");        
        final Button btn = new Button();
 
        btn.setText("Show Message");
        layout.setCenter(text);
        layout.setBottom(btn);
 
        btn.setOnAction(new EventHandler<ActionEvent>() {
 
            public void handle(ActionEvent event) {
                flash(btn, text.getText());
            }
        });
 
        primaryStage.setScene(scene);
        primaryStage.show();
    }
 
    public static void flash(Node node, String message) {
        Font font = Font.font("Verdana", FontWeight.NORMAL, 20);
        Color boxColor = Color.GREY;
        Color textColor = Color.WHITE;
        double duration = 800;
        double arcH = 5;
        double arcW = 5;
 
        final Rectangle rectangle = new Rectangle();
        final Text text = new Text(message);
 
 
        double x = 0;
        double y = 0;
        text.setLayoutX(x);
        text.setLayoutY(y);
        text.setFont(font);
        text.setFill(textColor);
 
        Scene scene = node.getScene();
        final Parent p = scene.getRoot();
 
        if (p instanceof Group) 
        {
            Group group = (Group) p;
            group.getChildren().add(rectangle);
            group.getChildren().add(text);
        }
        if (p instanceof Pane) 
        {
            Pane group = (Pane) p;
            group.getChildren().add(rectangle);
            group.getChildren().add(text);
        }
 
        Bounds bounds = text.getBoundsInParent();
 
        double sWidth = scene.getWidth();
        double sHeight = scene.getHeight();
 
        x = sWidth / 2 - (bounds.getWidth() / 2);
        y = sHeight / 2 - (bounds.getHeight() / 2);
        text.setLayoutX(x);
        text.setLayoutY(y);
        bounds = text.getBoundsInParent();
        double baseLineOffset = text.getBaselineOffset();
 
 
        rectangle.setFill(boxColor);
        rectangle.setLayoutX(x - arcW);
        rectangle.setLayoutY(y - baseLineOffset - arcH);
        rectangle.setArcHeight(arcH);
        rectangle.setArcWidth(arcW);
        rectangle.setWidth(bounds.getWidth() + arcW * 2);
        rectangle.setHeight(bounds.getHeight() + arcH * 2);
 
        FadeTransition ft = new FadeTransition(
                Duration.millis(duration), rectangle);
        ft.setFromValue(1.0);
        ft.setToValue(0.0);
        ft.play();
        ft.setOnFinished(new EventHandler<ActionEvent>() {
 
            public void handle(ActionEvent event) {
                if (p instanceof Group) {
                    Group group = (Group) p;
                    group.getChildren().remove(rectangle);
                    group.getChildren().remove(text);
                }
                 if (p instanceof Pane) {
                    Pane group = (Pane) p;
                    group.getChildren().remove(rectangle);
                    group.getChildren().remove(text);
                }
            }
        });
        FadeTransition ft2 = new FadeTransition(
                Duration.millis(duration + (duration * .1)), text);
        ft2.setFromValue(1.0);
        ft2.setToValue(0.0);
        ft2.play();
    }
}
Posted in Uncategorized | 1 Comment

JavaFX 2.0 Layout with MigPane

I have been starting to look at JavaFX 2.0 since I have been doing a lot of GUI work in swing.

The first challenge was layout since it is different from swing. JavaFX comes with a number of Panes (or panels) to layout your display. They are all pretty basic except for GridPane. GridPane reminds me of GridBagLayout, which is difficult to work with.

Fortunately there is a port of MiGLayout called MigPane. Below is a simple example of using MigPane to build a screen that will grow with a window.

You will need to download MigLayout and the MigPane wrapper.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package miglayoutexample;
 
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.stage.Stage;
import org.tbee.javafx.scene.layout.MigPane;
 
public class MigLayoutExample extends Application 
{
    public static void main(String[] args) {
        Application.launch(args);
    }
 
    private Button newBtn;
    private Button prevBtn;
    private Button nextBtn;
    private TextArea textArea;
 
    @Override
    public void start(Stage primaryStage) 
    {
        primaryStage.setTitle("MigPane example");
        newBtn = new Button("New");
        prevBtn = new Button("<<<<");
        nextBtn = new Button(">>>>");
 
        textArea = new TextArea();
 
        MigPane layout = new MigPane(
            "",                         // Layout Constraints
            "[grow]10[shrink 0]4[shrink 0]",  // Column constraints
            "[][200,grow]");            // Row constraint
 
        layout.add(newBtn);
        layout.add(prevBtn);
        layout.add(nextBtn, "wrap");
 
        layout.add(textArea, "grow,span");
 
        Scene scene = new Scene(layout);        
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}
Posted in Uncategorized | 2 Comments