IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Android Discussion :

SimpleAdapter avec images distantes


Sujet :

Android

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre éclairé
    Profil pro
    Inscrit en
    Mars 2002
    Messages
    92
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2002
    Messages : 92
    Par défaut SimpleAdapter avec images distantes
    Bonjour,

    J'ai créé un classe qui hérite de SimpleAdapter qui permet d'afficher une liste (ListActivity) avec des images distantes (via HTTP).

    Voici le code de ma classe :
    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
    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
    public class SimpleAdapterWithRemoteImage extends SimpleAdapter {
    	Context context = null;
    	Map<String, SoftReference<Bitmap>> bitmaps = new HashMap<String, SoftReference<Bitmap>>();
    	Map<ImageView, URL> imageviews = new HashMap<ImageView, URL>();
     
     
    	public SimpleAdapterWithRemoteImage(Context context, List<Map<String, String>> data, int resource, String[] from, int[] to) {
    		super(context, data, resource, from, to);
    		this.context = context;
    	}
     
    	private Bitmap fetchBitmap(String url) {
    		SoftReference<Bitmap> bitmapReference =  bitmaps.get(url);
     
    		if (bitmapReference!=null) {
    			Bitmap bitmap = bitmapReference.get();
    			if (bitmap!=null) {
    				return bitmap;
    			} else {
    				bitmaps.remove(url);
    			}
    		}
     
    		return null;
    	}
     
    	public View getView(int position, View convertView, ViewGroup parent) {
    		View v = null;
     
     
    		if ( super.getCount()>position) {
    			v = super.getView(position, convertView, parent);
     
    			if (v!=null) {
    				ImageView imageView = (ImageView)v.findViewById(R.id.ad_detail_image);
    				if ( imageView!=null) {
    					URL url = null;
    					String thumb = null;
    					try {
    						Map<String, String> params = (Map<String, String>)super.getItem(position);
    						if ( params!=null) {
    							thumb = params.get("image");
    							if (thumb != null) {
    								url = new URL(thumb);
    							} else {
    								imageView.setImageResource(R.drawable.blank);
    							}
    						} else {}
    					} catch (MalformedURLException e) {}
     
    					imageviews.put(imageView, url);
    					if ( thumb!=null) {
    						Bitmap bitmap = fetchBitmap(thumb);
    						if ( bitmap!=null) {
    							imageView.setImageBitmap(bitmap);
    						} else {
    							try {
    								if (url!=null) {
    									imageView.setImageResource(R.drawable.blank);
    									ImageDownloadTask idt = new ImageDownloadTask();
    									idt.execute(new RemoteImageView(imageView, url));
    								} else {}
    							} catch (RejectedExecutionException e) {}
    						}
    					} else {
    						imageView.setImageDrawable(v.getResources().getDrawable(R.drawable.blank));
    					}
    				} else {}
    			} else {}
    		}
     
    		return v;
    	}
     
    	private class RemoteImageView {
    		ImageView iv;
    		URL url;
     
     
    		public RemoteImageView(ImageView iv, URL url) {
    			this.iv = iv;
    			this.url = url;
    		}
    	}
     
    	private class ImageDownloadTask extends AsyncTask<RemoteImageView, Integer, Bitmap> {
    		private RemoteImageView riv;
     
     
    		protected Bitmap doInBackground(RemoteImageView... rivs) {
    			Bitmap bm = null;
    			riv = rivs[0];
     
     
    			if ( riv!=null && riv.url!=null) {
    				URL url = riv.url;
    				InputStream is = null;
     
    				try {
    					is = url.openConnection().getInputStream();
    				} catch (MalformedURLException e) {
    				} catch (IOException e) {
    				}
     
    				if ( is!=null) {
    					try {
    						bm = BitmapFactory.decodeStream(is);
    						bitmaps.put(url.toExternalForm(), new SoftReference<Bitmap>(bm));
    					} catch ( OutOfMemoryError e) {}
     
    					try {
    						is.close();
    					} catch (IOException e) {}
    				} else {}
    			}
     
    			return bm;
    		}
     
    		protected void onPostExecute(Bitmap result) {
    			if ( riv!=null && riv.iv!=null && riv.url!=null && riv.url.equals(imageviews.get(riv.iv))) {
    				riv.iv.setImageBitmap(result);
    			}
    		}
    	}
    }
    Dans la plupart des cas, l'application fonctionne très bien. Cependant, je reçois de temps en temps les deux rapports d'erreur suivant :
    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
    java.lang.NullPointerException
    	at android.widget.ListView.setupChild(ListView.java:1735)
    	at android.widget.ListView.makeAndAddView(ListView.java:1713)
    	at android.widget.ListView.fillUp(ListView.java:670)
    	at android.widget.ListView.fillGap(ListView.java:615)
    	at android.widget.AbsListView.trackMotionScroll(AbsListView.java:2622)
    	at android.widget.AbsListView.onTouchEvent(AbsListView.java:2048)
    	at android.widget.ListView.onTouchEvent(ListView.java:3276)
    	at android.view.View.dispatchTouchEvent(View.java:3796)
    	at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:856)
    	at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:888)
    	at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:888)
    	at com.android.internal.policy.impl.PhoneWindow$DecorView.superDispatchTouchEvent(PhoneWindow.java:1673)
    	at com.android.internal.policy.impl.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1114)
    	at android.app.Activity.dispatchTouchEvent(Activity.java:2061)
    	at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchTouchEvent(PhoneWindow.java:1657)
    	at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:888)
    	at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:888)
    	at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:888)
    	at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:888)
    	at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:888)
    	at com.android.internal.policy.impl.PhoneWindow$DecorView.superDispatchTouchEvent(PhoneWindow.java:1673)
    	at com.android.internal.policy.impl.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1114)
    	at android.app.Activity.dispatchTouchEvent(Activity.java:2061)
    	at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchTouchEvent(PhoneWindow.java:1657)
    	at android.view.ViewRoot.handleMessage(ViewRoot.java:1758)
    	at android.os.Handler.dispatchMessage(Handler.java:99)
    	at android.os.Looper.loop(Looper.java:136)
    	at android.app.ActivityThread.main(ActivityThread.java:4425)
    	at java.lang.reflect.Method.invokeNative(Native Method)
    	at java.lang.reflect.Method.invoke(Method.java:521)
    	at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:850)
    	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:608)
    	at dalvik.system.NativeStart.main(Native Method)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    java.lang.IllegalStateException: The content of the adapter has changed but ListView did not receive a notification. Make sure the content of your adapter is not modified from a background thread, but only from the UI thread. [in ListView(16908298, class android.widget.ListView) with Adapter(class com.company.project.SimpleAdapterWithRemoteImage)]
    	at android.widget.ListView.layoutChildren(ListView.java:1508)
    	at android.widget.AbsListView$CheckForTap.run(AbsListView.java:1930)
    	at android.os.Handler.handleCallback(Handler.java:587)
    	at android.os.Handler.dispatchMessage(Handler.java:92)
    	at android.os.Looper.loop(Looper.java:123)
    	at android.app.ActivityThread.main(ActivityThread.java:4627)
    	at java.lang.reflect.Method.invokeNative(Native Method)
    	at java.lang.reflect.Method.invoke(Method.java:521)
    	at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:868)
    	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:626)
    	at dalvik.system.NativeStart.main(Native Method)
    Quelqu'un sait-il d'où ça peut venir ? J'ai cherché un peu partout. Amélioré deux ou trois trucs. Mais quelques rares utilisateurs ont toujours ces erreurs.

    Merci !

  2. #2
    Expert confirmé

    Avatar de Feanorin
    Profil pro
    Inscrit en
    Avril 2004
    Messages
    4 589
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2004
    Messages : 4 589
    Par défaut
    Bonjour,

    Pour la première erreur:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    java.lang.NullPointerException
    	at android.widget.ListView.setupChild(ListView.java:1735)
    	at android.widget.ListView.makeAndAddView(ListView.java:1713)
    On dirait que les données de ta liste sont nulle . Est ce qu'a un moment donnée dans ton code tu peux avoir ce cas ?

    Pour la seconde erreur :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    java.lang.IllegalStateException: The content of the adapter has changed but ListView did not receive a notification. Make sure the content of your adapter is not modified from a background thread, but only from the UI thread. [in ListView(16908298, class android.widget.ListView) with Adapter(class com.company.project.SimpleAdapterWithRemoteImage)]
    	at android.widget.ListView.layoutChildren(ListView.java:1508)
    Les données de ta liste ont été changé, et tu préviens la liste à partir du Async Thread et non de l'UIThread .

    Donc pourrais tu nous montrer comment charges-tu ta liste avec l'adapter ?

    De même lorsque les données ont été modifié ( ajout, suppression , modification).

    Merci .

  3. #3
    Membre éclairé
    Profil pro
    Inscrit en
    Mars 2002
    Messages
    92
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2002
    Messages : 92
    Par défaut
    Merci pour ta réponse. Voici le code de mon activité :
    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
    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
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    abstract public class ResultActivity extends ListActivity {
    	SearchQuery currentQuery = null;
    	ResultSearch mainResult = null;
     
    	List<Map<String, String>> resultList = new ArrayList<Map<String, String>>();
    	List<Map<String, String>> prepareList = new ArrayList<Map<String, String>>();
     
    	boolean searching = false;
    	boolean hasFocus = false;
     
    	final Handler uiThreadCallback = new Handler();
     
    	final Runnable runInUIThreadOnScroll = new Runnable() {
    		public void run() {
    			onUpdateResultList();
    			adapter.notifyDataSetChanged();
    			onUpdateTitle();
    			getParent().setProgressBarIndeterminateVisibility(false);
    	        searching = false;
    		}
    	};
     
    	final Runnable runInUIThreadOnResultSearch = new Runnable() {
    		public void run() {
    			onUpdateResultList();
    			adapter.notifyDataSetChanged();
    			onUpdateTitle();
    			getParent().setProgressBarIndeterminateVisibility(false);
    	        searching = false;
    		}
    	};
     
    	private Context context;
    	private SimpleAdapter adapter;
     
    	public void onCreate(Bundle savedInstanceState) {
    		super.onCreate(savedInstanceState);
    		setContentView(R.layout.ad_list);
     
    		context = this;
     
    		String[] from = {"id", "name", "locality", "subAdminArea", "category", "date", "price", "image", "urgent"};
    		int[] to = {R.id.ad_detail_id, R.id.ad_detail_name, R.id.ad_detail_locality, R.id.ad_detail_subadmin_area, R.id.ad_detail_category, R.id.ad_detail_date, R.id.ad_detail_price};
    		adapter = new SimpleAdapterWithRemoteImage(this.getApplicationContext(), resultList, R.layout.ad_row, from, to);
    		setListAdapter(adapter);
     
    		ListView listView = getListView();
    		listView.setOnScrollListener(new OnScrollListener() {
    			int lastItem = 0;
     
    			public void onScrollStateChanged(AbsListView view, int scrollState) {
    				if ( adapter!=null && mainResult!=null && currentQuery!=null && !searching) {
    			    	if (lastItem == getListAdapter().getCount() && scrollState == OnScrollListener.SCROLL_STATE_IDLE && adapter.getCount()<mainResult.nbAds) {
    			    		searching = true;
    			    		getParent().setProgressBarIndeterminateVisibility(true);
     
    						new Thread() {
    							public void run() {
    								try {
    									currentQuery.page++;
    									ResultSearch rs = ProjectService.searchItems(context, currentQuery);
    									if ( mainResult==null) {
    										mainResult = rs;
    									} else if (mainResult.ads==null) {
    										mainResult.ads = rs.ads;
    									} else {
    										mainResult.ads.addAll(rs.ads);
    									}
     
    									prepare(rs);
    								} catch (ProjectException e) {
    									currentQuery.page--;
    								}
     
    								uiThreadCallback.post(runInUIThreadOnScroll);
    							}
    						}.start();
    			    	} else {}
    				} else {}
    		    }
     
    			public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
    		    	lastItem = firstVisibleItem + visibleItemCount;
    			}
    		});
     
    		final Intent queryIntent = getIntent();
    		final String queryAction = queryIntent.getAction();
    		if (Intent.ACTION_SEARCH.equals(queryAction)) {
    			doSearchQuery(queryIntent);
    		} else {}
    	}
     
    	public void onResume() {
    		super.onResume();
    		hasFocus = true;
    		if (searching==false) {
    			onUpdateTitle();
    		}
    	}
     
    	public void onPause() {
    		super.onPause();
    		hasFocus = false;
    	}
     
    	public void onNewIntent(final Intent newIntent) {
    		super.onNewIntent(newIntent);
     
    		final Intent queryIntent = getIntent();
    		final String queryAction = queryIntent.getAction();
    		if (Intent.ACTION_SEARCH.equals(queryAction)) {
    			doSearchQuery(queryIntent);
    		} else {}
    	}
     
    	private void doSearchQuery(final Intent queryIntent) {
    		SearchQuery query = queryIntent.getExtras().getParcelable(SearchResultsActivity.INTENT_EXTRA_SEARCH_QUERY);
    		query = (SearchQuery)query.clone();
    		doSearchQuery(query);
    	}
     
    	private void doSearchQuery(SearchQuery query) {
    		if (mainResult!=null) {
    			mainResult = null;
    			resultList.clear();
    			prepareList.clear();
    		}
    		currentQuery = query;
    		searching = true;
    		getParent().setProgressBarIndeterminateVisibility(true);
    		getParent().setTitle(getString(R.string.search_title_processing, currentQuery.search));
     
    		new Thread() {
    			public void run() {
    				try {
    					mainResult = ProjectService.searchItems(context, currentQuery);
    					prepare(mainResult);
    				} catch ( ProjectException e) {
    				}
     
    				uiThreadCallback.post(runInUIThreadOnResultSearch);
    			}
    		}.start();
    	}
     
    	private void onUpdateTitle() {
    		if ( currentQuery!=null/* && hasWindowFocus()*/ && hasFocus) {
    			if ( mainResult!=null && mainResult.nbAds>0) {
    				if ( currentQuery.search!=null && currentQuery.search.length()>0) {
    					getParent().setTitle(getString(R.string.search_title_result, Math.min(currentQuery.page*ProjectService.SEARCH_PAGE_SIZE, mainResult.nbAds), mainResult.nbAds, currentQuery.search));
    				} else {
    					getParent().setTitle(getString(R.string.search_title_result_for_category, Math.min(currentQuery.page*ProjectService.SEARCH_PAGE_SIZE, mainResult.nbAds), mainResult.nbAds));
    				}
    			} else {
    				getParent().setTitle(getString(R.string.search_title_no_results));
    			}
    		} else {
    			getParent().setTitle(getString(R.string.search_query_results));
    		}
    	}
     
    	private void prepare(ResultSearch rs) {
    		if ( rs!=null && rs.ads!=null) {
    			if ( !rs.ads.isEmpty()) {
    				for (ClassifiedAd classifiedAd : rs.ads) {
    					if ( classifiedAd!=null) {
    						Map<String, String> map = new HashMap<String, String>();
    						map.put("id", classifiedAd.id);
    						map.put("url", classifiedAd.url);
    						map.put("name", classifiedAd.name);
    						map.put("locality", classifiedAd.city);
    						map.put("subAdminArea", classifiedAd.department);
    						map.put("category", classifiedAd.category);
    						map.put("date", classifiedAd.createDate);
    						map.put("price", classifiedAd.price);
    						map.put("image", classifiedAd.thumb);
     
    						prepareList.add(map);
    					}
    				}
    			}
    		} else {}
    	}
     
    	private void onUpdateResultList() {
    		resultList.addAll(prepareList);
    		prepareList.clear();
    	}
    }
    Lors de l'ouverture de l'activité, la liste des N premiers éléments à afficher est récupérée (via l'appel à ProjectService.searchItems()).

    Ensuite, si l'utilisateur scrolle et arrive en fin de liste, les N élements suivants sont récupérés s'ils existent.

    La liste n'est jamais nulle à priori. Au pire, elle est vide.

    J'ai l'impression (sans pouvoir l'affirmer) que la première exception arrive lorsque l'activité s'ouvre et la seconde lorsque l'utilisateur scrolle dans la liste.

    Merci beaucoup pour ton aide.

    PS : En relisant, je me rends compte que les paramètres String[] from et int[] to n'ont pas la même taille puisque le dernier élément ("image" l'image distante) de from est positionné à la main. Je n'ai donc pas mis sa correspondance dans to.

  4. #4
    Membre éclairé
    Profil pro
    Inscrit en
    Mars 2002
    Messages
    92
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2002
    Messages : 92
    Par défaut
    Personne n'a d'idée ?

    Sinon, j'ai vu que je pouvais simplement surcharger la méthode SimpleAdapter.setImageView() pour charger mon image distante. Enfin, je me demande si l'une des exceptions ne vient pas du fait que je charge l'image avec une AsyncTask...

    En revanche, quelles sont des bonnes pratiques pour personnaliser l'affichage des éléments d'une liste ? Par exemple, j'ai un champ qui m'indique si l'utilisateur a déjà cliqué dessus. Dans ce cas, je n'utilise pas la même couleur de fond. J'ai ré-implémenté complètement la méthode SimpleAdapter.getView(). Mais il y a peut-être une façon de faire plus simple ou plus propre ?

    Merci pour votre aide !

  5. #5
    Expert confirmé

    Avatar de Feanorin
    Profil pro
    Inscrit en
    Avril 2004
    Messages
    4 589
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2004
    Messages : 4 589
    Par défaut
    Bonjour,

    Ici tu as un souci

    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
    final Runnable runInUIThreadOnScroll = new Runnable() {
    		public void run() {
    			onUpdateResultList();
    			adapter.notifyDataSetChanged();
    			onUpdateTitle();
    			getParent().setProgressBarIndeterminateVisibility(false);
    	        searching = false;
    		}
    	};
     
    	final Runnable runInUIThreadOnResultSearch = new Runnable() {
    		public void run() {
    			onUpdateResultList();
    			adapter.notifyDataSetChanged();
    			onUpdateTitle();
    			getParent().setProgressBarIndeterminateVisibility(false);
    	        searching = false;
    		}
    	};
    Tu n'es pas du tout dans l'UIThread , donc tu ne peux pas à partir d'ici modifier ton graphique .

    Tu peux utiliser cette fonction de ton Activity public final void runOnUiThread
    http://developer.android.com/referen...ng.Runnable%29

    OU sinon il faut que tu modifies les appels de changement de ton graphique dans tes runnables pour qu'ils s'exécutent dans l'UIThread .

  6. #6
    Membre éclairé
    Profil pro
    Inscrit en
    Mars 2002
    Messages
    92
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2002
    Messages : 92
    Par défaut
    Je pensais que l'Handler permettait d'exécuter du code dans UIThread. Ce n'est pas le cas ?

    Ça veut donc dire que je dois remplacer tous mes uiThreadCallback.post(monRunnableQuiMetAJourMonUI) par runOnUiThread(monRunnableQuiMetAJourMonUI) ?

Discussions similaires

  1. Problème d'utilisation des attributs width et height avec une image distante
    Par Ptit_Mouss dans le forum Général JavaScript
    Réponses: 4
    Dernier message: 21/09/2006, 15h40
  2. projet de base Interbase 7.5 avec images
    Par KRis dans le forum InterBase
    Réponses: 8
    Dernier message: 13/06/2005, 10h17
  3. alignement input avec image
    Par Shabata dans le forum Balisage (X)HTML et validation W3C
    Réponses: 5
    Dernier message: 24/02/2005, 09h45
  4. Formulaire et bouton submit avec image mapée
    Par dody dans le forum Général JavaScript
    Réponses: 7
    Dernier message: 06/12/2004, 16h00
  5. boîte de dialogue avec image de fond + texte
    Par Eugénie dans le forum MFC
    Réponses: 13
    Dernier message: 31/08/2004, 13h32

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo