Revision | 209 (tree) |
---|---|
Zeit | 2019-12-20 10:58:27 |
Autor | hirukawa_ryo |
* fx-util 0.3.3
コンテキストメニューからマウスカーソルが外れたときの振る舞い修正のバグを修正しました。(メニューアイテムを動的に追加した場合にリスナーが登録されず正しく動作しませんでした。)
@@ -1,8 +1,12 @@ | ||
1 | 1 | package net.osdn.util.javafx.scene.control; |
2 | 2 | |
3 | +import javafx.beans.property.ReadOnlyObjectProperty; | |
4 | +import javafx.beans.value.ChangeListener; | |
5 | +import javafx.beans.value.ObservableValue; | |
6 | +import javafx.collections.ListChangeListener; | |
7 | +import javafx.collections.ObservableList; | |
3 | 8 | import javafx.event.EventHandler; |
4 | 9 | import javafx.scene.Node; |
5 | -import javafx.scene.Parent; | |
6 | 10 | import javafx.scene.control.ContextMenu; |
7 | 11 | import javafx.scene.control.Menu; |
8 | 12 | import javafx.scene.control.MenuBar; |
@@ -15,7 +19,12 @@ | ||
15 | 19 | |
16 | 20 | public class ContextMenuUtil { |
17 | 21 | |
18 | - private static Map<ContextMenu, EventHandler<WindowEvent>> fixEventHandlers = new WeakHashMap<ContextMenu, EventHandler<WindowEvent>>(); | |
22 | + private static Map<ObservableList<Menu>, ListChangeListener<Menu>> menuListChangeListeners = new WeakHashMap<>(); | |
23 | + private static Map<ObservableList<MenuItem>, ListChangeListener<MenuItem>> menuItemListChangeListeners = new WeakHashMap<>(); | |
24 | + private static Map<ReadOnlyObjectProperty<ContextMenu>, ChangeListener<ContextMenu>> parentPopupChangeListeners = new WeakHashMap<>(); | |
25 | + private static Map<ContextMenu, EventHandler<WindowEvent>> contextMenuWindowShowingEventHandlers = new WeakHashMap<>(); | |
26 | + private static Map<Node, EventHandler<MouseEvent>> menuItemNodeMouseExitedEventHandlers = new WeakHashMap<>(); | |
27 | + private static Map<Node, EventHandler<MouseEvent>> menuItemNodeMouseReleasedEventFilters = new WeakHashMap<>(); | |
19 | 28 | |
20 | 29 | /** |
21 | 30 | * 指定したメニューバーに関連するコンテキスト・メニューの振る舞いを修正します。 |
@@ -27,7 +36,28 @@ | ||
27 | 36 | if(menuBar == null) { |
28 | 37 | return null; |
29 | 38 | } |
30 | - for(Menu menu : menuBar.getMenus()) { | |
39 | + ObservableList<Menu> menus = menuBar.getMenus(); | |
40 | + ListChangeListener<Menu> oldListChangeListener = menuListChangeListeners.get(menus); | |
41 | + if(oldListChangeListener != null) { | |
42 | + menus.removeListener(oldListChangeListener); | |
43 | + menuListChangeListeners.remove(menus); | |
44 | + } | |
45 | + ListChangeListener<Menu> newListChangeListener = new ListChangeListener<Menu>() { | |
46 | + @Override | |
47 | + public void onChanged(Change<? extends Menu> c) { | |
48 | + while(c.next()) { | |
49 | + if(c.wasAdded()) { | |
50 | + for(Menu menu : c.getAddedSubList()) { | |
51 | + fix(menu); | |
52 | + } | |
53 | + } | |
54 | + } | |
55 | + } | |
56 | + }; | |
57 | + menus.addListener(newListChangeListener); | |
58 | + menuListChangeListeners.put(menus, newListChangeListener); | |
59 | + | |
60 | + for(Menu menu : menus) { | |
31 | 61 | fix(menu); |
32 | 62 | } |
33 | 63 | return menuBar; |
@@ -43,7 +73,28 @@ | ||
43 | 73 | if(menu == null) { |
44 | 74 | return null; |
45 | 75 | } |
46 | - for(MenuItem menuItem : menu.getItems()) { | |
76 | + ObservableList<MenuItem> menuItems = menu.getItems(); | |
77 | + ListChangeListener<MenuItem> oldListChangeListener = menuItemListChangeListeners.get(menuItems); | |
78 | + if(oldListChangeListener != null) { | |
79 | + menuItems.removeListener(oldListChangeListener); | |
80 | + menuItemListChangeListeners.remove(menuItems); | |
81 | + } | |
82 | + ListChangeListener<MenuItem> newListChangeListener = new ListChangeListener<MenuItem>() { | |
83 | + @Override | |
84 | + public void onChanged(Change<? extends MenuItem> c) { | |
85 | + while(c.next()) { | |
86 | + if(c.wasAdded()) { | |
87 | + for(MenuItem menuItem : c.getAddedSubList()) { | |
88 | + fix(menuItem); | |
89 | + } | |
90 | + } | |
91 | + } | |
92 | + } | |
93 | + }; | |
94 | + menuItems.addListener(newListChangeListener); | |
95 | + menuItemListChangeListeners.put(menuItems, newListChangeListener); | |
96 | + | |
97 | + for(MenuItem menuItem : menuItems) { | |
47 | 98 | fix(menuItem); |
48 | 99 | } |
49 | 100 | return menu; |
@@ -59,17 +110,26 @@ | ||
59 | 110 | if(menuItem == null) { |
60 | 111 | return null; |
61 | 112 | } |
62 | - menuItem.parentPopupProperty().addListener((observable, oldValue, newValue) -> { | |
63 | - if(oldValue != null) { | |
64 | - EventHandler<WindowEvent> oldEventHandler = fixEventHandlers.get(oldValue); | |
65 | - if(oldEventHandler != null) { | |
66 | - oldValue.removeEventHandler(WindowEvent.WINDOW_SHOWING, oldEventHandler); | |
113 | + ReadOnlyObjectProperty<ContextMenu> parentPopup = menuItem.parentPopupProperty(); | |
114 | + ChangeListener<ContextMenu> oldChangeListener = parentPopupChangeListeners.get(parentPopup); | |
115 | + if(oldChangeListener != null) { | |
116 | + parentPopup.removeListener(oldChangeListener); | |
117 | + parentPopupChangeListeners.remove(parentPopup); | |
118 | + } | |
119 | + ChangeListener<ContextMenu> newChangeListener = new ChangeListener<ContextMenu>() { | |
120 | + @Override | |
121 | + public void changed(ObservableValue<? extends ContextMenu> observable, ContextMenu oldValue, ContextMenu newValue) { | |
122 | + if(oldValue != null) { | |
123 | + removeListeners(oldValue); | |
67 | 124 | } |
125 | + if(newValue != null) { | |
126 | + fix(newValue, false); | |
127 | + } | |
68 | 128 | } |
69 | - if(newValue != null) { | |
70 | - fix(newValue, false); | |
71 | - } | |
72 | - }); | |
129 | + }; | |
130 | + parentPopup.addListener(newChangeListener); | |
131 | + parentPopupChangeListeners.put(parentPopup, newChangeListener); | |
132 | + | |
73 | 133 | return menuItem; |
74 | 134 | } |
75 | 135 |
@@ -108,9 +168,10 @@ | ||
108 | 168 | if(contextMenu == null) { |
109 | 169 | return null; |
110 | 170 | } |
111 | - EventHandler<WindowEvent> oldEventHandler = fixEventHandlers.get(contextMenu); | |
171 | + EventHandler<WindowEvent> oldEventHandler = contextMenuWindowShowingEventHandlers.get(contextMenu); | |
112 | 172 | if(oldEventHandler != null) { |
113 | 173 | contextMenu.removeEventHandler(WindowEvent.WINDOW_SHOWING, oldEventHandler); |
174 | + contextMenuWindowShowingEventHandlers.remove(contextMenu); | |
114 | 175 | } |
115 | 176 | EventHandler<WindowEvent> newEventHandler = new EventHandler<WindowEvent>() { |
116 | 177 | @Override |
@@ -118,29 +179,88 @@ | ||
118 | 179 | Node contextMenuNode = contextMenu.getStyleableNode(); |
119 | 180 | for (MenuItem menuItem : contextMenu.getItems()) { |
120 | 181 | Node menuItemNode = menuItem.getStyleableNode(); |
121 | - if(menuItemNode == null) { | |
122 | - continue; | |
123 | - } | |
124 | - menuItemNode.setOnMouseExited(me -> { | |
125 | - contextMenuNode.requestFocus(); | |
126 | - }); | |
127 | - menuItemNode.addEventFilter(MouseEvent.MOUSE_RELEASED, e -> { | |
128 | - if(!menuItemNode.isFocused()) { | |
129 | - e.consume(); | |
130 | - if(hideOnMouseReleased) { | |
131 | - contextMenu.hide(); | |
132 | - } | |
133 | - } | |
134 | - }); | |
182 | + fixImpl(contextMenu, contextMenuNode, menuItem, menuItemNode, hideOnMouseReleased); | |
135 | 183 | } |
136 | 184 | // コンテキスト・メニュー表示時になぜか先頭のアイテムが選択状態になることがあるようなので、 |
137 | 185 | // コンテキスト・メニュー自体にフォーカス要求を出して、先頭アイテムが初期選択状態にならないようにします。 |
138 | 186 | contextMenuNode.requestFocus(); |
139 | 187 | contextMenu.removeEventHandler(WindowEvent.WINDOW_SHOWING, this); |
188 | + contextMenuWindowShowingEventHandlers.remove(contextMenu); | |
140 | 189 | } |
141 | 190 | }; |
142 | 191 | contextMenu.addEventHandler(WindowEvent.WINDOW_SHOWING, newEventHandler); |
143 | - fixEventHandlers.put(contextMenu, newEventHandler); | |
192 | + contextMenuWindowShowingEventHandlers.put(contextMenu, newEventHandler); | |
144 | 193 | return contextMenu; |
145 | 194 | } |
195 | + | |
196 | + private static void fixImpl(ContextMenu contextMenu, Node contextMenuNode, MenuItem menuItem, Node menuItemNode, boolean hideOnMouseReleased) { | |
197 | + if(contextMenu == null) { | |
198 | + return; | |
199 | + } | |
200 | + if(contextMenuNode == null) { | |
201 | + return; | |
202 | + } | |
203 | + if(menuItem == null) { | |
204 | + return; | |
205 | + } | |
206 | + if(menuItemNode == null) { | |
207 | + return; | |
208 | + } | |
209 | + | |
210 | + removeListeners(menuItemNode); | |
211 | + | |
212 | + EventHandler<MouseEvent> newEventHandler = new EventHandler<MouseEvent>() { | |
213 | + @Override | |
214 | + public void handle(MouseEvent event) { | |
215 | + contextMenuNode.requestFocus(); | |
216 | + } | |
217 | + }; | |
218 | + menuItemNode.addEventHandler(MouseEvent.MOUSE_EXITED, newEventHandler); | |
219 | + menuItemNodeMouseExitedEventHandlers.put(menuItemNode, newEventHandler); | |
220 | + | |
221 | + EventHandler<MouseEvent> newEventFilter = new EventHandler<MouseEvent>() { | |
222 | + @Override | |
223 | + public void handle(MouseEvent event) { | |
224 | + if(!menuItemNode.isFocused()) { | |
225 | + event.consume(); | |
226 | + if(hideOnMouseReleased) { | |
227 | + contextMenu.hide(); | |
228 | + } | |
229 | + } | |
230 | + } | |
231 | + }; | |
232 | + menuItemNode.addEventFilter(MouseEvent.MOUSE_RELEASED, newEventFilter); | |
233 | + menuItemNodeMouseReleasedEventFilters.put(menuItemNode, newEventFilter); | |
234 | + } | |
235 | + | |
236 | + private static void removeListeners(ContextMenu contextMenu) { | |
237 | + if(contextMenu == null) { | |
238 | + return; | |
239 | + } | |
240 | + for(MenuItem menuItem : contextMenu.getItems()) { | |
241 | + Node menuItemNode = menuItem.getStyleableNode(); | |
242 | + removeListeners(menuItemNode); | |
243 | + } | |
244 | + EventHandler<WindowEvent> oldEventHandler = contextMenuWindowShowingEventHandlers.get(contextMenu); | |
245 | + if(oldEventHandler != null) { | |
246 | + contextMenu.removeEventHandler(WindowEvent.WINDOW_SHOWING, oldEventHandler); | |
247 | + contextMenuWindowShowingEventHandlers.remove(contextMenu); | |
248 | + } | |
249 | + } | |
250 | + | |
251 | + private static void removeListeners(Node menuItemNode) { | |
252 | + if(menuItemNode == null) { | |
253 | + return; | |
254 | + } | |
255 | + EventHandler<MouseEvent> oldEventHandler = menuItemNodeMouseExitedEventHandlers.get(menuItemNode); | |
256 | + if(oldEventHandler != null) { | |
257 | + menuItemNode.removeEventHandler(MouseEvent.MOUSE_EXITED, oldEventHandler); | |
258 | + menuItemNodeMouseExitedEventHandlers.remove(menuItemNode); | |
259 | + } | |
260 | + EventHandler<MouseEvent> oldEventFilter = menuItemNodeMouseReleasedEventFilters.get(menuItemNode); | |
261 | + if(oldEventFilter != null) { | |
262 | + menuItemNode.removeEventFilter(MouseEvent.MOUSE_RELEASED, oldEventFilter); | |
263 | + menuItemNodeMouseReleasedEventFilters.remove(menuItemNode); | |
264 | + } | |
265 | + } | |
146 | 266 | } |