1

This relates to this Java question.

Here's my problem. I've written an app that allows people to do a lot of data entry, typing into a lot of separate fields. To confirm the change in each field they can often hit Return (for a single line field) or control-S (for multi-line fields where Return would be valid input), but that's cumbersome, so I also allowed fields to save their content when they lose focus. So users can type-tab-type and it all goes smoothly.

Except if they change a field and then click on the application window exit X in the corner. They expect that this counts as losing focus and will save that last change. But the lost focus event doesn't happen and the change is lost.

I could add a Done button, which would have the side effect of moving focus and saving the last field, and then exiting. But I shouldn't have to. There's a X in the corner and it should do the right thing.

My first thought was

frame.addWindowListener(new java.awt.event.WindowAdapter() {
      @Override
      public void windowClosing(.....

because I thought from there I could publish() something to my SwingWorker to tell it call loseFocus on everything. No such luck; publish() is protected.

Basically I need to do one last operation on my various widgets when X is clicked. How do I?

Edit: I should note that each editable widget (dropdown, JTextPane, etc) has been extended to hold the actual relevant data. All the data for that widget, e.g. whether the value the user typed is valid, what it was before he edited it, etc. is in those extended class instances. There's no other place values are held; this isn't model-view-controller.

The reason for this is that widgets can get changed either by user actions or network messages; a message can come in that throws out an existing widget entirely and replaces it with one with new content. In other words, doInBackground is in a permanent read-loop, reading network update messages and publish()ing those update requests to process(). User action happens as usual, between calls to process().

Bottom line,there's no global data structure to go to at exit time to get values. They're all in dozens to hundreds of data structures managed by the swing worker thread.The app itself, outside that swing worker thread, doesn't even know what sort of values and widgets exist - all widgets are created, placed and destroyed by network messages from the server. The rest of the app (what little there is) couldn't safely get to the data if it wanted to, unless I implemented a whole lot of shared data and locking.

It all works flawlessly, and I'd rather not redesign it all for this one tiny shutdown case. It just never occurred to me that I couldn't publish an extra "shut down" message into the work queue for process() from outside that thread. (I mean thread safe queues are trivial to implement; why didn't they?)

If the answer is "you can't talk to swing at shut down", I'll live with it. I do have a potentially evil workaround - I could have x do nothing but send a message to the server, which could write back a "you should shut down message" which could do the rest. But that seems ungainly.

Scott M
  • 684
  • 7
  • 14
  • 1
    What about calling the "save" code directly from `windowClosing()` rather than trying to fire an event that calls it? – Code-Apprentice Feb 07 '20 at 17:14
  • Put the logic of the `publish` method into a separate, `public` method, which both the SwingWorker and the WindowListener can call. – VGR Feb 07 '20 at 17:16
  • 2
    *There's a X in the corner and it should do the right thing.* - the right thing usually means to close the window without processing. That is most people think of it as a "Cancel" button. Typically applications (think Word, Excel) would check if the data has been changed and would then prompt whether you want to "Save" or "Cancel". So I would add logic to the WindowListener to do the prompt and then invoke the "Save" method as required. Not sure what a SwingWorker has to do with this. – camickr Feb 07 '20 at 17:24
  • I don't see what this had to do with shutdown. You need to process, or maybe even disable, the window closing event. Better still, you need to educate your users that they have to specifically commit their changes. I've never used an app that would do that for me if I just closed the window, in thirty years. – user207421 Feb 07 '20 at 22:47
  • @user207421: yes, this is unusual. Every field saves itself on focus-lost because save is done by sending the changes to a server - nothing is kept locally - and some users have network connections that can drop spontaneously. I'd be lynched if they did 45 minutes of editing and then lost it all. There's another reason for saving nothing locally, and wanting to save every individual change - multiple users can edit the same collection of data simultaneously, so changes need to go to the server and get distributed more or less in real time. – Scott M Feb 08 '20 at 01:25

1 Answers1

0

The short answer is, there isn't a good solution. I tried installing a shutdown hook and publishing a message to the swing thread to tell it to finish up, and then gave the shutdown thread a 500ms sleep to give process() time to happen. process() wasn't called. publish() alone apparently isn't enough, once shutdown starts.

Bottom line, don't put data you need to get at in swing threads. Global data and synchronized functions is the only way to go.

Scott M
  • 684
  • 7
  • 14