Salut,
Suite à plusieurs posts que j'ai lu récemment concernant des demandes de filtrage de liste, et surtout parce que j'en ai moi-même besoin pour un projet, j'ai décidé de faire une petite API, qui se veut un plus générique que celle de JXList (d'ailleurs elle utilise la généricité). Et elle permet d'être utilisée directement dans une JList (ou autre).
En fait il s'agit de la modélisation d'un filtrage, par forcément pour une JList, mais pour n'importe quoi, il suffit que ce n'importe quoi soit Filtrable<Type> (2 méthodes à définir : get(index) et getSize()).
Il y a la notion de Filtrate<Type>, qui correspond au résultat du filtrage (comme en physique le résultat d'un filtration s'appelle filtrat, je l'ai appelé comme ceci ), et un filtrat est également filtrable (ainsi on peut refiltrer un filtrat, etc...). Il y a également la possibilité de définir un comparator sur un filtrat, pour que le résultat soit trié.
Et donc il y a évidemment la notion de Filtre<Type>, et là il n'y a qu'une méthode à définir (si vous n'en utilisez pas un tout fait, comme StringFilter ou RegexFilter), il s'agit de boolean accept(D data) qui indique si la donnée doit être présente dans le filtrat ou non. On peut définir plusieurs filtres par filtrats...
Il y a des méthodes permettant d'obtenir un ListModel ou ComboBoxListModel à partir d'un filtrable.
Bon un petit exemple pour illustrer tout ça. Faisons une liste qui sera filtrée par un JTextField.
Et enfin, un petit truc bien pratique, le SwingAsyncFiltrate<Type>, qui a 2 gros avantages.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
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 /* * Voici notre filtrable, ce sont nos données sources qui seront * filtrées, que nous ne modifirons pas ici. Pour pouvoir les modifier * de manière totalement libre, il vaut mieux utiliser un * AbstractFiltrable et coder les méthodes abstraites, et d'appeler les * fireXXX sur une modification (exactement comme vous le feriez avec un * AbstractListModel). */ Filtrable<String> filtrable = new DefaultFiltrable<String>("Java", "C", "C++", "Lisp", "CaML", "Fortran", "Lisaac", "x86", "HTML", "XML", "PHP", "JSP", "EJB", "Français", "çhîNöÏs"); /* * On récupère un filtrat (résultat du filtrage). Pour le moment il n'y * a pas de filtre (le texte du textfield est vide). On le déclare final * pour l'utiliser directement dans la classe anonyme de * DocumentListener (c'est juste pour l'exemple). */ final DefaultFiltrate<String> filtrate = new DefaultFiltrate<String>(filtrable); /* On veut trier dans l'ordre alphabétique. */ filtrate.setSorter(String.CASE_INSENSITIVE_ORDER); /* On veut utiliser notre filtrat comme modèle de la liste. */ ListModel listModel = FiltrableAdapters.toListModel(filtrate); JList list = new JList(listModel); /* On crée un textField, qui contiendra le texte de recherche. */ final JTextField textField = new JTextField(); /* * On écoute maintenant tous les évènements du textfield, ainsi à chaque * fois que l'utilisateur entre du texte, le contenu de la liste est mis * à jour. */ textField.getDocument().addDocumentListener(new DocumentListener() { @Override public void changedUpdate(DocumentEvent arg0) { changed(); } @Override public void insertUpdate(DocumentEvent arg0) { changed(); } @Override public void removeUpdate(DocumentEvent arg0) { changed(); } private void changed() { /* On utilise un simple filtre de texte. */ Filter<String> filter = new StringFilter(textField.getText()); /* Filtre le contenu avec le nouveau filtre. */ filtrate.setFilter(filter); } }); JFrame frame = new JFrame("EModelFiltererDemo"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.add(new JScrollPane(list)); frame.add(textField, BorderLayout.SOUTH); frame.setBounds(100, 100, 150, 400); frame.setVisible(true);
Pour comprendre son utilité, voyons d'abord ce que fait un filtrat "classique". Dans l'exemple ci-dessus, le filtrage est effectué chaque fois que du texte est entré dans le textfield. Supposons que le temps de filtrage soit long, par exemple il y a 1000 données dans la liste, et le Filter utilisé met 2 millisecondes par entrée. Cela fait une attente de 2 secondes à chaque lettre tapée dans le textfield! Et dans l'EDT, donc l'interface graphique se fige. Si vous tapez vite, ça va lagguer !
Ce que permet SwingAsyncFiltrate<Type> :
- filtrer dans un thread séparé, et notifier la fin du filtrage dans l'EDT (pratique pour afficher une info "veuillez patienter..." lors d'un filtrage, et le supprimer à la fin).
- virer les demandes de filtrages inutiles dynamiquement. Reprenons notre exemple d'un filtrage qui dure 2 secondes, si en 2 secondes vous écrivez abcdefgh, il va chercher pour "a" (première lettre), mais au bout de 2 secondes, vous ne voulez pas qu'il recherche pour "ab", "abc", "abcd" ... puisque ce qui vous intéresse c'est le résultat pour "abcdefgh". Donc les filtrages inutiles ne seront pas effectués.
Voici les liens:
emodelfilter-api.zip
emodelfilter-doc.zip
emodelfilter-src.zip
Donc voilà, j'ai programmé ça ce week-end, j'en suis très content (je l'utilise pour filtrer une liste de chaînes par 2 filtrats (et non 1 filtrat avec 2 filtres, car j'ai besoin du résultat intermédiaire également), tout d'abord par le groupe de favoris, et ensuite par le texte de recherche dans un textfield.
Pour donner un exemple concret d'utilisation du Filter (on peut utiliser des filtres de base que l'on combine) :
Donc tout ça pour dire que moi ça me sert bien, si ça peut servir à d'autres...
Code : Sélectionner tout - Visualiser dans une fenêtre à part
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 /** * Filtre de chaînes. * * @author <a href="mailto:rom1v@yahoo.fr">Romain Vimont</a> (rom1v) * @param <C> * Type de chaînes. */ public class ChannelFilter<C extends Channel> implements Filter<C> { /** * Filtre de chaînes de caractères, utilisé pour filtrer le numéro et le nom * de la chaîne. */ private StringFilter filter; /** * Crée un filtre de chaîne. * * @param text * Texte recherché. * @throws NullPointerException * si le texte recherché est {@code null}. */ public ChannelFilter(String text) { if (text == null) { throw new NullPointerException(); } filter = new StringFilter(text); } @Override public boolean accept(C channel) { /* * On accepte la chaîne si le filtre de chaîne de caractères accepte le * numéro de la chaîne ou s'il accepte le nom. */ return filter.accept(String.valueOf(channel.getNum())) || filter.accept(channel.getName()); } }
Si vous avez des suggestions, n'hésitez pas...
@+
Partager