2012-07-02 766 views
7

Necesito saber cuando un ListBox ha terminado de renderizar por primera vez para que pueda desplazarlo hacia arriba para presentar al usuario el primer elemento en la lista.¿Cómo puedo saber cuándo un ListBox ha terminado de renderizarse en Silverlight?

Tengo un ListBox que utiliza RichTextBox en que es DataTemplate:

<DataTemplate x:Key="HelpTextTemplate"> 
    <Grid> 
     <Grid.ColumnDefinitions> 
      <ColumnDefinition Width="*"/> 
      <ColumnDefinition Width="Auto"/> 
     </Grid.ColumnDefinitions> 
     ... 
     <ContentControl> 
      ... 
      <RichTextBox x:Name="HelpTextContent" Grid.Row="1" 
         Tag="{Binding Path=HelpObject.Text, Mode=TwoWay}" 
         TextWrapping="Wrap" 
         HorizontalAlignment="Stretch" 
         Margin="0,0,20,0" 
         Loaded="RichTextBox_Loaded" 
         ContentChanged="RichTextBox_ContentChanged" 
         SelectionChanged="RichTextBox_SelectionChanged"/> 
      ... 
     </ContentControl> 
     ... 
    </Grid> 
</DataTemplate> 

El ListBox se une a un ObservableCollection.

que tenía un problema con el desplazamiento de la ListBox - si la altura de la RichTextBox fue mayor que el de la ListBox el usuario no podía desplazarse a la parte inferior de la RichTextBox. El ListBox pasaría al siguiente elemento de la lista. La altura del control deslizante de la barra de desplazamiento también cambiará. Esto se debe a que la altura real del RichTextBox solo se calcula cuando se representa en realidad. Cuando está fuera de la pantalla, la altura vuelve a un valor menor (creo que el código supone que el texto puede caber en una sola línea en lugar de tener que ser envuelto).

Seguí estos problemas hasta el uso de VirtualisingStackPanel para dibujar los elementos. Cuando reemplacé eso con un simple StackPanel esos problemas desaparecieron.

Esto creó el problema que tengo ahora que es que el ListBox se desplaza al final de la lista en la carga inicial. Los eventos Loaded y LayoutUpdated en ListBox se producen antes de que se carguen los datos. He intentado escuchar a cabo para el evento PropertyChanged en el modelo de vista cuando el ObservableCollection se inicializa:

void editViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e) 
{ 
    switch (e.PropertyName) 
    { 
     case "ListDataSource": 
      // Try to scroll to the top of the ListBox 
      break; 
    } 
} 

este dispara demasiado pronto también. La lista se representa después de este evento se dispara y hace que el ListBox se desplace hacia la parte inferior.

Respuesta

0

Al final tuve que usar una chapuza:

private System.Threading.Timer timer; 
void editViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e) 
{ 
    switch (e.PropertyName) 
    { 
     case "ListDataSource": 
      TimerCallback callback = TimerResult; 
      timer = new Timer(callback, null, 750, 0); 
      break; 
    } 
} 

private void TimerResult(Object stateInfo) 
{ 
    Dispatcher.BeginInvoke(() => 
    { 
     if (this.ItemsList.Items.Count > 0) 
     { 
      this.ItemsList.UpdateLayout(); 
      this.ItemsList.SelectedIndex = 0; 
      this.ItemsList.ScrollIntoView(this.ItemsList.Items[0]); 
     } 
    }); 

    timer.Dispose(); 
} 

Si alguien sabe una mejor manera por favor enviar su respuesta ahora.

0

Intenta desplazarte en el controlador Cargado, pero diferirlo un poco por Dispatcher. Algo así

void OnLoaded(...) 
{ 
    Dispatcher.BeginInvoke(() => {/*Scroll your ListBox here*/}); 
} 

Podría ayudar.

+0

Hmm. Soy cauteloso con este tipo de cosas, especialmente porque podría haber muchos elementos en la lista, pero lo intentaré. – ChrisF

+0

Dado que el procesamiento tiene lugar en el hilo de la interfaz de usuario, y Dispatcher.BeginInvoke empuja la operación de desplazamiento a la cola del hilo de la interfaz de usuario, en realidad no importa qué tan grande sea su lista. El desplazamiento se debe realizar después de que se realicen otras instrucciones actuales de la secuencia de la interfaz de usuario. Por supuesto, si su colección (u otras partes de control/plantilla) se crea de manera asíncrona, no funcionará.Pero es otro caso ... Por lo general, para escenarios simples, funciona. – Kreol

+0

Buen punto allí, aunque la lista se rellena de forma asíncrona ... – ChrisF

0
public class AutoScrollBehavior : Behavior<ListBox> 
    { 
     #region Properties 

     public object ItemToScroll 
     { 
      get { return GetValue(ItemToScrollProperty); } 
      set { SetValue(ItemToScrollProperty, value); } 
     } 

     public static readonly DependencyProperty ItemToScrollProperty = DependencyProperty.Register("ItemToScroll", typeof(object), typeof(AutoScrollBehavior), new PropertyMetadata(ItemToScrollPropertyChanged)); 

     private static void ItemToScrollPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
     { 
      ListBox lb = ((AutoScrollBehavior)d).AssociatedObject; 
      lb.UpdateLayout(); 
      ((AutoScrollBehavior)d).AssociatedObject.ScrollIntoView(e.NewValue); 
     } 
     #endregion 
    } 

luego usar en el XAML como

<ListBox SelectedItem="{Binding SelectedItemFromModel, Mode=TwoWay}"> 
<i:Interaction.Behaviors> 
                   <Behaviors:AutoScrollBehavior ItemToScroll="{Binding SelectedItemFromModel}"/> 
                  </i:Interaction.Behaviors> 
</ListBox> 

en algún comando que debe ser capaz de controlar y establecer SelectedItemFromModel propiedad de su modelo de vista.

Cuestiones relacionadas