Workaround para Windows Phone ListBox y LongListSelector y ScrollIntoView en elementos de diferente tamaño

Rafael Ontivero

Programador sistemas embebidos

https://github.com/rfog/InsertInListboxproblem_Solution

http://geeks.ms/blogs/rfog/

Microsoft MVP

LinkedIn: http://es.linkedin.com/pub/rafael-ontivero/54/622/8aa



En Windows Phone 7.5 hay un bug bastante asqueroso que sólo se presenta cuando quieres mostrar un elemento en un ListBox o en un LongListSelector llamando a ScrollIntoView() y los elementos contenidos tienen diferentes tamaños. El bug también está presente en Windows Phone 8, pero sólo en el componente ListBox. LongListSelector está construido de otra forma y, según Microsoft, ya no presenta el problema. Tampoco existe ningún workaround oficialmente sancionado para resolverlo.

***

En el ejemplo que anexo más abajo, cuando se pulsa el botón de Insert… se genera un elemento de tres posibles mediante Code Behind. Los tres tienen como elemento base un Grid, pero el interior es diferente y por tanto su altura. Dos de ellos contienen una imagen y el tercero, un texto.

En cada elemento creado, se llama a ScrollIntoView() sobre el citado, para que se visualice adecuadamente.

Conforme vamos creando elementos, llega un momento en el que uno de ellos se queda cortado a medias como en la captura:

Dn144693.D9121849C76683706DFF5B5C6D8F6728(es-es,MSDN.10).png


El comportamiento adecuado debería ser que se mostrara completo, mostrando la parte inferior. Ese es el problema: cuando los elementos tienen diferente altura, termina ocurriendo esto mismo. Y según Microsoft, no se va a solucionar. Las apuestas suben cuando hay campos de edición y cambios de orientación: con total seguridad te quedará el elemento cortado.

El código que genera los elementos es:

        private void Button_Tap(object sender, GestureEventArgs e)
        {
            switch (s_rnd.Next(0,3))
            {
                case 0:
                    var itemText = new ListBoxItemText("Hola mundo");
                    ListBox.Items.Add(itemText); 
                    ListBox.ScrollIntoView(itemText);
                    break;
                case 1:
                    var itemImgBien = new ListBoxItemImage(StaticResources.ImageBien);
                    ListBox.Items.Add(itemImgBien);
                    ListBox.ScrollIntoView(itemImgBien);
                    break;
                case 2:
                    var itemImgMal = new ListBoxItemImage(StaticResources.ImageMal);
                    ListBox.Items.Add(itemImgMal);
                    ListBox.ScrollIntoView(itemImgMal);
                    break;
            }
        }

Fijaos en el detalle. Dependiendo de un número aleatorio creamos uno de tres. Eso debería funcionar bien, pero como podéis comprobar vosotros mismos, no lo hace en el momento en el que empiezan a aparecer elementos con el texto y con una de las dos imágenes.

***

Como ya os he dicho, la solución es bastante sencilla. Os pego el código:

        private void ButtonNoBug_Tap(object sender, GestureEventArgs e)
        {
            switch (s_rnd.Next(0, 3))
            {
                case 0:
                    var itemText = new ListBoxItemText("Hola mundo");
                    ListBox.Items.Add(itemText);
                    ForceScrollListBox();
                    break;
                case 1:
                    var itemImgBien = new ListBoxItemImage(StaticResources.ImageBien);
                    ListBox.Items.Add(itemImgBien);
                    ForceScrollListBox();
                    break;
                case 2:
                    var itemImgMal = new ListBoxItemImage(StaticResources.ImageMal);
                    ListBox.Items.Add(itemImgMal);
                    ForceScrollListBox();
                    break;
            }
        }

Y el truco del almendruco:

private void ForceScrollListBox()
        {
            var fake = new Grid();
            ListBox.Items.Add(fake);
            ListBox.ScrollIntoView(fake);
        }

Sí, señores. El truco consiste en crear un nuevo elemento del mismo tipo base pero completamente vacío, añadirlo y llamar a ScrollIntoView() sobre él. El uso de memoria es mínimo porque el componente no debe ocupar muchos bytes ya que está vacío, y tampoco se visualiza, pero en general el sistema empuja hacia arriba al elemento que queremos mostrar.

No obstante, creo haber detectado que no siempre funciona, al menos en el emulador.

En el ejemplo tenéis tres botones. En el primero hacemos inserciones con el bug, en el segundo sin él y con el tercero corregimos si tenemos un elemento a medio mostrar o nos vamos abajo del todo si estamos a medias.

Finalmente, el código fuente de todo esto está aquí:

https://github.com/rfog/InsertInListboxproblem_Solution



Mostrar: