agronholm / jython-swingutils (http://pypi.python.org/pypi/jython-swingutils/)
An assortment of utility classes and functions that ease the task of writing GUI applications with the Java Swing toolkit.
Clone this repository (size: 401.9 KB): HTTPS / SSH
$ hg clone http://bitbucket.org/agronholm/jython-swingutils/
| commit 67: | c6f4e2cfec82 |
| parent 66: | 4a42cd901944 |
| branch: | default |
* got rid of weak references
* added property names in PropertyAdapter
* prevent property event loopback in PropertyAdapter
* require value holders as sources when binding components
* automatically disable components when the value holder is empty
Changed (Δ1.0 KB):
raw changeset »
swingutils/binding.py (101 lines added, 74 lines removed)
Up to file-list swingutils/binding.py:
| … | … | @@ -6,7 +6,7 @@ using weak references, and automatically |
6 |
6 |
garbage collected. |
7 |
7 |
|
8 |
8 |
""" |
9 |
import weakref |
|
9 |
from types import MethodType |
|
10 |
10 |
|
11 |
11 |
from java.beans import PropertyChangeListener |
12 |
12 |
from java.awt.event import ItemListener, FocusListener |
| … | … | @@ -16,7 +16,7 @@ from swingutils.beans import JavaBeanSup |
16 |
16 |
from swingutils.events import addPropertyListener |
17 |
17 |
|
18 |
18 |
__all__ = ('ValueHolder', 'bindProperty', 'bindCheckbox', 'bindComboBox', |
19 |
'bindTextComponent' |
|
19 |
'bindTextComponent') |
|
20 |
20 |
|
21 |
21 |
|
22 |
22 |
class ValueHolder(JavaBeanSupport): |
| … | … | @@ -41,6 +41,9 @@ class ValueHolder(JavaBeanSupport): |
41 |
41 |
object.__setattr__(self, name, value) |
42 |
42 |
else: |
43 |
43 |
setattr(self._value, name, value) |
44 |
||
45 |
def __nonzero__(self): |
|
46 |
return self._value is not None |
|
44 |
47 |
|
45 |
48 |
def _getvalue(self): |
46 |
49 |
return self._value |
| … | … | @@ -49,6 +52,9 @@ class ValueHolder(JavaBeanSupport): |
49 |
52 |
self.firePropertyChange(event.propertyName, event.oldValue, |
50 |
53 |
event.newValue) |
51 |
54 |
|
55 |
def _getProperties(self, value): |
|
56 |
return [p for p in dir(value) if not p.startswith('_')] |
|
57 |
||
52 |
58 |
def _setvalue(self, newValue): |
53 |
59 |
oldValue = self._value |
54 |
60 |
if oldValue and self.__wrapper: |
| … | … | @@ -63,14 +69,16 @@ class ValueHolder(JavaBeanSupport): |
63 |
69 |
|
64 |
70 |
# Notify listeners of changes in all properties, including properties |
65 |
71 |
# from both the old and new values |
66 |
oldProps = set([p for p in dir(oldValue) if not p.startswith('_')]) |
|
67 |
newProps = set([p for p in dir(newValue) if not p.startswith('_')]) |
|
72 |
oldProps = set(self._getProperties(oldValue)) |
|
73 |
newProps = set(self._getProperties(newValue)) |
|
68 |
74 |
properties = oldProps.union(newProps) |
69 |
75 |
properties.discard('value') |
70 |
76 |
for property in properties: |
71 |
77 |
oldVal = getattr(oldValue, property, None) |
72 |
78 |
newVal = getattr(newValue, property, None) |
73 |
|
|
79 |
if not isinstance(oldVal, MethodType) and not \ |
|
80 |
isinstance(newVal, MethodType): |
|
81 |
self.firePropertyChange(property, oldVal, newVal) |
|
74 |
82 |
|
75 |
83 |
value = property(_getvalue, _setvalue) |
76 |
84 |
|
| … | … | @@ -82,25 +90,42 @@ class ValueHolder(JavaBeanSupport): |
82 |
90 |
|
83 |
91 |
|
84 |
92 |
class PropertyAdapter(PropertyChangeListener): |
85 |
def __init__(self, source, destination, converter, backConverter): |
|
86 |
self.source = weakref.ref(source, self.disconnect) |
|
87 |
|
|
93 |
def __init__(self, source, srcProperty, destination, dstProperty, |
|
94 |
converter, backConverter): |
|
95 |
self.source = source |
|
96 |
self.srcProperty = srcProperty |
|
97 |
self.destination = destination |
|
98 |
self.dstProperty = dstProperty |
|
88 |
99 |
self.converter = converter |
89 |
100 |
self.backConverter = backConverter |
101 |
self._propertyChangeInProgress = False |
|
90 |
102 |
|
91 |
103 |
def propertyChange(self, event): |
92 |
src = self.source() |
|
93 |
dst = self.destination() |
|
104 |
if self._propertyChangeInProgress: |
|
105 |
return |
|
106 |
self._propertyChangeInProgress = True |
|
107 |
||
108 |
src = self.source |
|
109 |
srcProperty = self.srcProperty |
|
110 |
dst = self.destination |
|
111 |
dstProperty = self.dstProperty |
|
94 |
112 |
converter = self.converter |
95 |
113 |
if event.source is dst: |
96 |
114 |
dst = src |
97 |
src = self.destination |
|
115 |
src = self.destination |
|
116 |
srcProperty = self.dstProperty |
|
117 |
dstProperty = self.srcProperty |
|
98 |
118 |
converter = self.backConverter |
99 |
value = getattr(src, event.propertyName) |
|
100 |
value = converter(value) |
|
101 |
|
|
119 |
||
120 |
try: |
|
121 |
value = getattr(src, srcProperty) |
|
122 |
if converter: |
|
123 |
value = converter(value) |
|
124 |
setattr(dst, dstProperty, value) |
|
125 |
finally: |
|
126 |
self._propertyChangeInProgress = False |
|
102 |
127 |
|
103 |
def disconnect(self |
|
128 |
def disconnect(self): |
|
104 |
129 |
src = self.source() |
105 |
130 |
if src: |
106 |
131 |
src.removePropertyChangeListener(self) |
| … | … | @@ -111,88 +136,88 @@ class PropertyAdapter(PropertyChangeList |
111 |
136 |
|
112 |
137 |
|
113 |
138 |
class CheckBoxAdapter(PropertyChangeListener, ItemListener): |
114 |
def __init__(self, |
|
139 |
def __init__(self, holder, srcProperty, checkBox, converter, |
|
115 |
140 |
backConverter): |
116 |
self. |
|
141 |
self.holder = holder |
|
117 |
142 |
self.srcProperty = srcProperty |
118 |
self.checkBox = |
|
143 |
self.checkBox = checkBox |
|
119 |
144 |
self.converter = converter |
120 |
145 |
self.backConverter = backConverter |
121 |
146 |
|
122 |
147 |
def propertyChange(self, event): |
123 |
src = self. |
|
148 |
src = self.holder() |
|
124 |
149 |
checkBox = self.checkBox() |
125 |
150 |
value = getattr(src, self.srcProperty) |
126 |
151 |
value = self.converter(value) |
127 |
152 |
checkBox.selected = value |
153 |
self.checkBox.enabled = self.holder.value is not None |
|
128 |
154 |
|
129 |
155 |
def stateChanged(self, event): |
130 |
src = self. |
|
156 |
src = self.holder() |
|
131 |
157 |
checkBox = self.checkBox() |
132 |
158 |
value = self.backConverter(checkBox.selected) |
133 |
159 |
setattr(src, self.srcProperty, value) |
134 |
160 |
|
135 |
def disconnect(self, ref=None): |
|
136 |
src = self.source() |
|
137 |
if src: |
|
138 |
src.removePropertyChangeListener(self) |
|
139 |
||
140 |
checkBox = self.checkBox() |
|
141 |
if checkBox: |
|
142 |
checkBox.removeItemListener(self) |
|
161 |
def disconnect(self): |
|
162 |
self.holder.removePropertyChangeListener(self) |
|
163 |
self.checkBox.removeItemListener(self) |
|
143 |
164 |
|
144 |
165 |
|
145 |
166 |
class ComboBoxAdapter(PropertyChangeListener, ItemListener): |
146 |
def __init__(self, |
|
167 |
def __init__(self, holder, srcProperty, comboBox, converter, |
|
147 |
168 |
backConverter): |
148 |
self. |
|
169 |
self.holder = holder |
|
149 |
170 |
self.srcProperty = srcProperty |
150 |
self.comboBox = |
|
171 |
self.comboBox = comboBox |
|
151 |
172 |
self.converter = converter |
152 |
173 |
self.backConverter = backConverter |
153 |
174 |
|
154 |
175 |
def propertyChange(self, event): |
155 |
src = self.source() |
|
156 |
combo = self.comboBox() |
|
157 |
value = getattr(s |
|
176 |
value = getattr(self.holder, self.srcProperty) |
|
158 |
177 |
value = self.converter(value) |
159 |
|
|
178 |
self.comboBox.selectedItem = value |
|
179 |
self.comboBox.enabled = self.holder.value is not None |
|
160 |
180 |
|
161 |
181 |
def stateChanged(self, event): |
162 |
182 |
value = self.backConverter(event.item) |
163 |
setattr(self. |
|
183 |
setattr(self.holder, self.srcProperty, value) |
|
164 |
184 |
|
165 |
def disconnect(self, ref=None): |
|
166 |
src = self.source() |
|
167 |
if src: |
|
168 |
src.removePropertyChangeListener(self) |
|
169 |
||
170 |
combo = self.comboBox() |
|
171 |
if combo: |
|
172 |
combo.removeItemListener(self) |
|
185 |
def disconnect(self): |
|
186 |
self.holder.removePropertyChangeListener(self) |
|
187 |
self.comboBox.removeItemListener(self) |
|
173 |
188 |
|
174 |
189 |
|
175 |
190 |
class TextComponentAdapter(PropertyChangeListener, FocusListener): |
176 |
def __init__(self, source, srcProperty, textComponent): |
|
177 |
self.source = weakref.ref(source, self.disconnect) |
|
191 |
def __init__(self, holder, srcProperty, textComponent): |
|
192 |
self.holder = holder |
|
178 |
193 |
self.srcProperty = srcProperty |
179 |
self.textComponent = |
|
194 |
self.textComponent = textComponent |
|
180 |
195 |
|
181 |
196 |
def propertyChange(self, event): |
182 |
src = self.source() |
|
183 |
textComponent = self.textComponent() |
|
184 |
value = getattr(src, self.srcProperty) |
|
185 |
textComponent.text = value |
|
197 |
self.textComponent.text = event.newValue |
|
198 |
self.textComponent.enabled = self.holder.value is not None |
|
186 |
199 |
|
187 |
200 |
def focusLost(self, event): |
188 |
src = self.source() |
|
189 |
textComponent = self.textField() |
|
190 |
setattr(s |
|
201 |
setattr(self.holder, self.srcProperty, self.textComponent.text) |
|
202 |
||
203 |
def disconnect(self): |
|
204 |
self.holder.removePropertyChangeListener(self) |
|
205 |
self.textComponent.removeFocusListener(self) |
|
206 |
||
207 |
||
208 |
class FormattedTextFieldAdapter(PropertyAdapter): |
|
209 |
def __init__(self, holder, srcProperty, textField): |
|
210 |
PropertyAdapter.__init__(self, holder, srcProperty, textField, 'value', |
|
211 |
None, None) |
|
212 |
||
213 |
def propertyChange(self, event): |
|
214 |
PropertyAdapter.propertyChange(self, event) |
|
215 |
self.destination.enabled = self.source.value is not None |
|
191 |
216 |
|
192 |
217 |
|
193 |
218 |
def bindProperty(source, srcProperty, destination, dstProperty, |
194 |
twoway=False, syncnow=False, converter=lambda v: v, |
|
195 |
backConverter=lambda v: v): |
|
219 |
twoway=False, syncnow=False, converter=None, |
|
220 |
backConverter=None): |
|
196 |
221 |
""" |
197 |
222 |
Connects a property in the source object to a property in the destination |
198 |
223 |
object. When the source property changes, the destination property is set |
| … | … | @@ -227,7 +252,8 @@ def bindProperty(source, srcProperty, de |
227 |
252 |
assert hasattr(destination, 'addPropertyChangeListener'), \ |
228 |
253 |
'destination object has no addPropertyChangeListener method' |
229 |
254 |
|
230 |
adapter = PropertyAdapter(source, |
|
255 |
adapter = PropertyAdapter(source, srcProperty, destination, dstProperty, |
|
256 |
converter, backConverter) |
|
231 |
257 |
source.addPropertyChangeListener(srcProperty, adapter) |
232 |
258 |
if twoway: |
233 |
259 |
destination.addPropertyChangeListener(dstProperty, adapter) |
| … | … | @@ -239,41 +265,42 @@ def bindProperty(source, srcProperty, de |
239 |
265 |
return adapter |
240 |
266 |
|
241 |
267 |
|
242 |
def bindCheckbox( |
|
268 |
def bindCheckbox(holder, srcProperty, checkBox, twoway=True): |
|
243 |
269 |
""" |
244 |
270 |
Binds an object to a check box. |
245 |
271 |
|
246 |
272 |
:param checkBox: the check box to bind to |
247 |
273 |
:param twoway: `True` if changes in the check box state should also reflect |
248 |
in ` |
|
274 |
in `holder` |
|
249 |
275 |
:type checkBox: :class:`javax.swing.JCheckBox` |
250 |
276 |
|
251 |
277 |
""" |
252 |
adapter = CheckBoxAdapter(source, srcProperty, checkBox) |
|
253 |
||
254 |
|
|
278 |
adapter = CheckBoxAdapter(holder, srcProperty, checkBox) |
|
279 |
holder.addPropertyChangeListener(srcProperty, adapter) |
|
255 |
280 |
if twoway: |
256 |
281 |
checkBox.addItemListener(adapter) |
282 |
checkBox.enabled = holder.value is not None |
|
257 |
283 |
|
258 |
284 |
|
259 |
def bindComboBox(source, srcProperty, comboBox, twoway=True, |
|
260 |
converter=lambda v: v, backConverter=lambda v: v): |
|
261 |
|
|
285 |
def bindComboBox(holder, srcProperty, comboBox, twoway=True, |
|
286 |
converter=None, backConverter=None): |
|
287 |
adapter = ComboBoxAdapter(holder, srcProperty, comboBox, converter, |
|
262 |
288 |
backConverter) |
263 |
|
|
289 |
holder.addPropertyChangeListener(srcProperty, adapter) |
|
264 |
290 |
if twoway: |
265 |
291 |
comboBox.addItemListener(adapter) |
292 |
comboBox.enabled = holder.value is not None |
|
266 |
293 |
|
267 |
294 |
|
268 |
def bindTextComponent( |
|
295 |
def bindTextComponent(holder, srcProperty, textComponent, twoway=True): |
|
269 |
296 |
if isinstance(textComponent, JFormattedTextField): |
270 |
|
|
297 |
adapter = FormattedTextFieldAdapter(holder, srcProperty, textComponent) |
|
298 |
holder.addPropertyChangeListener(srcProperty, adapter) |
|
299 |
if twoway: |
|
300 |
textComponent.addPropertyChangeListener('value', adapter) |
|
271 |
301 |
else: |
272 |
adapter = TextComponentAdapter(source, srcProperty, textComponent) |
|
273 |
source.addPropertyChangeListener(srcProperty, adapter) |
|
302 |
adapter = TextComponentAdapter(holder, srcProperty, textComponent) |
|
303 |
holder.addPropertyChangeListener(srcProperty, adapter) |
|
274 |
304 |
if twoway: |
275 |
305 |
textComponent.addFocusListener(adapter) |
276 |
||
277 |
||
278 |
def bindFormattedTextField(source, srcProperty, textField, twoway=True): |
|
279 |
bindProperty(source, srcProperty, textField, 'value', twoway) |
|
306 |
textComponent.enabled = holder.value is not None |
