2012-07-02 15 views
7

Ho bisogno di sapere quando un ListBox ha terminato il rendering per la prima volta in modo che possa scorrere verso l'alto per presentare all'utente il primo elemento dell'elenco.Come faccio a sapere quando un ListBox ha finito il rendering in Silverlight?

Ho un ListBox che utilizza RichTextBox nel suo 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> 

Il ListBox è associato a un ObservableCollection.

ho avuto un problema con lo scorrimento della ListBox - se l'altezza del RichTextBox era maggiore di quello del ListBox l'utente non può scorrere fino alla fine della RichTextBox. Lo ListBox passerebbe alla voce successiva nell'elenco. Anche l'altezza del cursore della barra di scorrimento cambia. Questo perché l'altezza effettiva di RichTextBox viene calcolata solo quando viene effettivamente visualizzata. Quando è fuori dallo schermo, l'altezza ritorna a un valore inferiore (penso che il codice presuppone che il testo possa adattarsi a una singola riga piuttosto che dover essere avvolto).

Ho rintracciato questi problemi fino all'utilizzo dello ListBox di un VirtualisingStackPanel per disegnare gli articoli. Quando l'ho sostituito con un semplice StackPanel, quei problemi sono andati via.

Questo quindi ha creato il problema che ho ora che è che il ListBox scorre in fondo alla lista al caricamento iniziale. Gli eventi Loaded e LayoutUpdated sullo ListBox si verificano prima che i dati siano stati caricati. Ho provato ad ascoltare fuori per l'evento PropertyChanged sul modello vista quando la ObservableCollection viene inizializzata:

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

questo spara troppo presto pure. L'elenco viene reso dopo questo evento viene generato e fa sì che lo ListBox scorra verso il basso.

risposta

0

Alla fine ho dovuto usare un ripiego:

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(); 
} 

Se qualcuno conosce un modo migliore si prega di inviare la tua risposta ora.

0

Provare a scorrere in Loaded handler, ma ritardarlo un po 'da Dispatcher. Qualcosa del genere

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

Potrebbe essere d'aiuto.

+0

Hmm. Sono diffidente nei confronti di cose come questa - specialmente perché potrebbero esserci molti articoli nella lista - ma ci provo. – ChrisF

+0

Poiché il rendering viene eseguito nel thread dell'interfaccia utente e Dispatcher.BeginInvoke trasferisce l'operazione di scorrimento nella coda del thread dell'interfaccia utente: in realtà non importa quanto sia grande l'elenco. Lo scorrimento dovrebbe essere eseguito dopo che sono state fatte altre istruzioni sul thread UI corrente. Naturalmente se la tua raccolta (o altre parti di controllo/modello) viene creata asincrona, non funzionerà.Ma è un altro caso .. Di solito per scenari semplici funziona. – Kreol

+0

Buon punto lì - anche se la lista è popolata in modo asincrono .... – 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 
    } 

quindi utilizzare in XAML come

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

su alcuni comandi si dovrebbe essere in grado di controllare e impostare SelectedItemFromModel proprietà del modello di vista.

Problemi correlati