Funzionalità Trascina verso il basso

L'aggiornamento tramite trascinamento verso il basso consente a un utente di trascinare verso il basso un elenco di dati tramite tocco per recuperare altri dati. Questa funzione è ampiamente usata nei dispositivi dotati di touchscreen. Puoi usare le API mostrate qui per implementare l'aggiornamento tramite trascinamento verso il basso nella tua app.

pull-to-refresh gif

È il controllo giusto?

Usa l'aggiornamento tramite trascinamento verso il basso quando hai un elenco o una griglia di dati che l'utente potrebbe voler aggiornare regolarmente ed è probabile che l'app verrà eseguita in dispositivi usati in prevalenza con input tocco.

Puoi anche usare RefreshVisualizer per creare un'esperienza coerente per gli aggiornamenti richiamati in altri modi, ad esempio con un pulsante di aggiornamento.

Controlli di aggiornamento

L'aggiornamento tramite trascinamento verso il basso viene abilitato da 2 controlli.

  • RefreshContainer - Un controllo ContentControl che fornisce un wrapper per l'esperienza di aggiornamento tramite trascinamento verso il basso. Gestisce le interazioni tramite tocco e lo stato del visualizzatore di aggiornamento interno.
  • RefreshVisualizer - Incapsula la visualizzazione di aggiornamento illustrata nella sezione successiva.

Il controllo principale è RefreshContainer, posto come wrapper intorno al contenuto che l'utente trascina per avviare un aggiornamento. RefreshContainer funziona solo con il tocco, quindi si consiglia di avere anche un pulsante di aggiornamento disponibile per gli utenti che non hanno un'interfaccia di tocco. Puoi posizionare il pulsante di aggiornamento in una posizione appropriata nell'app, in una barra dei comandi o in prossimità della superficie da aggiornare.

Visualizzazione di aggiornamento

La visualizzazione di aggiornamento predefinita è una rotellina di stato circolare usata per comunicare quando verrà eseguito un aggiornamento e lo stato di avanzamento dell'aggiornamento dopo l'avvio. Il visualizzatore di aggiornamento ha 5 stati.

La distanza a cui l'utente deve scendere in un elenco di avviare un aggiornamento viene chiamata soglia. Il visualizzatore di stato dipende dallo stato di trascinamento in relazione a questa soglia. I valori possibili sono contenuti nell'enumerazione RefreshVisualizerState.

Idle

Lo stato predefinito del visualizzatore è inattivo. L'utente non interagisce con RefreshContainer tramite tocco e non ci sono aggiornamenti in corso.

Visivamente, non esiste alcun segnale della presenza del visualizzatore di aggiornamento.

Interazione

Quando l'utente trascina l'elenco nella direzione specificata dalla proprietà PullDirection e prima che venga raggiunta la soglia, il visualizzatore si trova in stato Interazione.

  • Se l'utente rilascia il controllo quando si trova in questo stato, il controllo torna su Inattivo.

    pull-to-refresh pre-threshold

    Visivamente, l'icona viene visualizzata come disabilitata (opacità al 60%). Inoltre, l'icona compie una rotazione completa con l'azione di scorrimento.

  • Se l'utente trascina l'elenco oltre la soglia, il visualizzatore passa da Interazione a In sospeso.

    pull-to-refresh at threshold

    Visivamente, l'icona passa a un'opacità del 100% e pulsa raggiungendo dimensioni fino al 150%, quindi torna al 100% durante la transizione.

In sospeso

Dopo aver trascinato l'elenco oltre la soglia, il visualizzatore entra in stato In sospeso.

  • Se l'utente sposta l'elenco di nuovo oltre la soglia senza rilasciarlo, viene restituito lo stato Interazione.
  • Se l'utente rilascia l'elenco, viene avviata una richiesta di aggiornamento e passa allo stato Aggiornamento.

pull-to-refresh post-threshold

Visivamente, l'icona è al 100% sia per dimensioni che opacità. In questo stato, l'icona continua a spostarsi verso il basso con l'azione di scorrimento, ma non ruota più.

Aggiornamento in corso

Quando l'utente rilascia il visualizzatore oltre la soglia, si trova in stato Aggiornamento.

Quando si entra in questo stato, viene generato l'evento RefreshRequested. Questo è il segnale per avviare l'aggiornamento del contenuto dell'app. Gli argomenti dell'evento, RefreshRequestedEventArgs, contengono un oggetto Deferral, di cui è necessario eseguire un handle nel gestore eventi. Quindi, è necessario contrassegnare il rinvio come completato quando il codice usato per eseguire l'aggiornamento viene completato.

Una volta completato l'aggiornamento, il visualizzatore torna sullo stato Inattivo.

Visivamente, l'icona torna sulla posizione di soglia e ruota per la durata dell'aggiornamento. Questa rotazione viene usata per mostrare lo stato dell'aggiornamento e viene sostituita dall'animazione del contenuto in ingresso.

Visualizzazione

Quando l'utente trascina verso la direzione dell'aggiornamento da una posizione di partenza in cui l'aggiornamento non è consentito, il visualizzatore entra in stato Visualizzazione. Ciò si verifica in genere quando ScrollViewer non si trova nella posizione 0 quando l'utente inizia a trascinare.

  • Se l'utente rilascia il controllo quando si trova in questo stato, il controllo torna su Inattivo.

Direzione del trascinamento

Per impostazione predefinita, l'utente trascina un elenco dall'alto verso il basso per avviare un aggiornamento. Se hai un elenco o una griglia con un orientamento diverso, devi modificare la direzione di trascinamento del contenitore di aggiornamento in modo che corrisponda.

La proprietà PullDirection accetta uno dei valori RefreshPullDirection seguenti: BottomToTop, TopToBottom, RightToLeft o LeftToRight.

Quando modifichi la direzione di trascinamento, la posizione iniziale della rotellina di stato del visualizzatore ruota automaticamente in modo che la freccia venga avviata nella posizione appropriata per la direzione di trascinamento. Se necessario, puoi modificare la proprietà RefreshVisualizer.Orientation per ignorare il comportamento automatico. Nella maggior parte dei casi si consiglia di lasciare il valore predefinito Automatico.

Piattaforma UWP e WinUI 2

Importante

Le informazioni e gli esempi in questo articolo sono ottimizzati per le app che usano Windows App SDK e WinUI 3, ma sono generalmente applicabili alle app UWP che usano WinUI 2. Per informazioni ed esempi specifici della piattaforma, consultare le indicazioni di riferimento sulle API UWP.

Questa sezione contiene informazioni necessarie per usare il controllo in un'app UWP o WinUI 2.

I controlli di aggiornamento per le app UWP sono inclusi come parte della libreria dell'interfaccia utente di Windows 2. Per altre informazioni, incluse le istruzioni per l'installazione, vedi Libreria dell'interfaccia utente di Windows. Le API per questo controllo sono presenti negli spazi dei nomi Windows.UI.Xaml.Controls (UWP) e Microsoft.UI.Xaml.Controls (WinUI).

È consigliabile usare la versione più recente di WinUI 2 per ottenere gli stili, i modelli e le funzionalità più recenti per tutti i controlli.

Per usare il codice in questo articolo con WinUI 2, usare un alias in XAML (si usa muxc) per rappresentare le API della libreria dell'interfaccia utente di Windows incluse nel progetto. Per altre informazioni, vedere Attività iniziali di WinUI 2.

xmlns:muxc="using:Microsoft.UI.Xaml.Controls"

<muxc:RefreshContainer />

Implementare l'aggiornamento tramite trascinamento verso il basso

L'app Raccolta WinUI 3 include esempi interattivi della maggior parte dei controlli e delle funzionalità di WinUI 3. Scaricare l'app da Microsoft Store od ottenere il codice sorgente su GitHub

L'aggiunta di funzionalità di aggiornamento tramite trascinamento verso il basso a un elenco richiede solo pochi passaggi.

  1. Eseguire il wrapping dell'elenco in un controllo RefreshContainer.
  2. Gestire l'evento RefreshRequested per aggiornare il contenuto.
  3. Facoltativamente, avviare un aggiornamento chiamando RequestRefresh (ad esempio, con il clic di un pulsante).

Nota

Puoi creare un'istanza di RefreshVisualizer autonomamente. Tuttavia, si consiglia di eseguire il wrapping del contenuto in RefreshContainer e usare RefreshVisualizer fornito dalla proprietà RefreshContainer.Visualizer, anche per scenari non di tocco. In questo articolo si presuppone che il visualizzatore sia sempre scaricato dal contenitore di aggiornamento.

Inoltre, per motivi di praticità vengono usati i membri di RequestRefresh e RefreshRequested del contenitore di aggiornamento. refreshContainer.RequestRefresh() equivale a refreshContainer.Visualizer.RequestRefresh() e genera gli eventi RefreshContainer.RefreshRequested e RefreshVisualizer.RefreshRequested.

Richiedere un aggiornamento

Il contenitore di aggiornamento gestisce le interazioni tramite tocco per consentire all'utente di aggiornare il contenuto tramite tocco. Ti consigliamo di fornire altre soluzioni per le interfacce senza tocco, ad esempio un pulsante di aggiornamento o un controllo vocale.

Per avviare un aggiornamento, chiama il metodo RequestRefresh.

// See the Examples section for the full code.
private void RefreshButtonClick(object sender, RoutedEventArgs e)
{
    RefreshContainer.RequestRefresh();
}

Quando chiami RequestRefresh, lo stato del visualizzatore passa direttamente da Inattivo ad Aggiornamento.

Gestire una richiesta di aggiornamento

Per ottenere nuovi contenuti quando necessario, gestisci l'evento RefreshRequested. Nel gestore eventi è necessario un codice specifico per la tua app per ottenere nuovi contenuti.

Gli argomenti dell'evento, RefreshRequestedEventArgs, contengono un oggetto Deferral. Recupera un handle per il rinvio nel gestore eventi. Quindi, contrassegna il rinvio come completato quando il codice usato per eseguire l'aggiornamento viene completato.

// See the Examples section for the full code.
private async void RefreshContainer_RefreshRequested(RefreshContainer sender, RefreshRequestedEventArgs args)
{
    // Respond to a request by performing a refresh and using the deferral object.
    using (var RefreshCompletionDeferral = args.GetDeferral())
    {
        // Do some async operation to refresh the content

         await FetchAndInsertItemsAsync(3);

        // The 'using' statement ensures the deferral is marked as complete.
        // Otherwise, you'd call
        // RefreshCompletionDeferral.Complete();
        // RefreshCompletionDeferral.Dispose();
    }
}

Rispondere alle modifiche dello stato

Se vuoi, puoi rispondere alle modifiche dello stato del visualizzatore. Ad esempio, per evitare più richieste di aggiornamento, puoi disabilitare un pulsante di aggiornamento mentre il visualizzatore è in fase di aggiornamento.

// See the Examples section for the full code.
private void Visualizer_RefreshStateChanged(RefreshVisualizer sender, RefreshStateChangedEventArgs args)
{
    // Respond to visualizer state changes.
    // Disable the refresh button if the visualizer is refreshing.
    if (args.NewState == RefreshVisualizerState.Refreshing)
    {
        RefreshButton.IsEnabled = false;
    }
    else
    {
        RefreshButton.IsEnabled = true;
    }
}

Uso di un controllo ScrollViewer in RefreshContainer

Nota

Il contenuto di un oggetto RefreshContainer deve essere un controllo scorrevole, ad esempio ScrollViewer, GridView, ListView e così via. L'impostazione del contenuto su un controllo come Grid comporterà un comportamento indefinito.

Questo esempio mostra come usare l'aggiornamento tramite trascinamento verso il basso con un visualizzatore a scorrimento.

<RefreshContainer>
    <ScrollViewer VerticalScrollMode="Enabled"
                  VerticalScrollBarVisibility="Auto"
                  HorizontalScrollBarVisibility="Auto">
 
        <!-- Scrollviewer content -->

    </ScrollViewer>
</RefreshContainer>

Aggiunta di un aggiornamento tramite trascinamento verso il basso a un controllo ListView

Questo esempio mostra come usare l'aggiornamento tramite trascinamento verso il basso con una visualizzazione elenco.

<StackPanel Margin="0,40" Width="280">
    <CommandBar OverflowButtonVisibility="Collapsed">
        <AppBarButton x:Name="RefreshButton" Click="RefreshButtonClick"
                      Icon="Refresh" Label="Refresh"/>
        <CommandBar.Content>
            <TextBlock Text="List of items" 
                       Style="{StaticResource TitleTextBlockStyle}"
                       Margin="12,8"/>
        </CommandBar.Content>
    </CommandBar>

    <RefreshContainer x:Name="RefreshContainer">
        <ListView x:Name="ListView1" Height="400">
            <ListView.ItemTemplate>
                <DataTemplate x:DataType="local:ListItemData">
                    <Grid Height="80">
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto" />
                            <RowDefinition Height="Auto" />
                            <RowDefinition Height="*" />
                        </Grid.RowDefinitions>
                        <TextBlock Text="{x:Bind Path=Header}"
                                   Style="{StaticResource SubtitleTextBlockStyle}"
                                   Grid.Row="0"/>
                        <TextBlock Text="{x:Bind Path=Date}"
                                   Style="{StaticResource CaptionTextBlockStyle}"
                                   Grid.Row="1"/>
                        <TextBlock Text="{x:Bind Path=Body}"
                                   Style="{StaticResource BodyTextBlockStyle}"
                                   Grid.Row="2"
                                   Margin="0,4,0,0" />
                    </Grid>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </RefreshContainer>
</StackPanel>
public sealed partial class MainPage : Page
{
    public ObservableCollection<ListItemData> Items { get; set; } 
        = new ObservableCollection<ListItemData>();

    public MainPage()
    {
        this.InitializeComponent();

        Loaded += MainPage_Loaded;
        ListView1.ItemsSource = Items;
    }

    private async void MainPage_Loaded(object sender, RoutedEventArgs e)
    {
        Loaded -= MainPage_Loaded;
        RefreshContainer.RefreshRequested += RefreshContainer_RefreshRequested;
        RefreshContainer.Visualizer.RefreshStateChanged += Visualizer_RefreshStateChanged;

        // Add some initial content to the list.
        await FetchAndInsertItemsAsync(2);
    }

    private void RefreshButtonClick(object sender, RoutedEventArgs e)
    {
        RefreshContainer.RequestRefresh();
    }

    private async void RefreshContainer_RefreshRequested(RefreshContainer sender, RefreshRequestedEventArgs args)
    {
        // Respond to a request by performing a refresh and using the deferral object.
        using (var RefreshCompletionDeferral = args.GetDeferral())
        {
            // Do some async operation to refresh the content

            await FetchAndInsertItemsAsync(3);

            // The 'using' statement ensures the deferral is marked as complete.
            // Otherwise, you'd call
            // RefreshCompletionDeferral.Complete();
            // RefreshCompletionDeferral.Dispose();
        }
    }

    private void Visualizer_RefreshStateChanged(RefreshVisualizer sender, RefreshStateChangedEventArgs args)
    {
        // Respond to visualizer state changes.
        // Disable the refresh button if the visualizer is refreshing.
        if (args.NewState == RefreshVisualizerState.Refreshing)
        {
            RefreshButton.IsEnabled = false;
        }
        else
        {
            RefreshButton.IsEnabled = true;
        }
    }

    // App specific code to get fresh data.
    private async Task FetchAndInsertItemsAsync(int updateCount)
    {
        for (int i = 0; i < updateCount; ++i)
        {
            // Simulate delay while we go fetch new items.
            await Task.Delay(1000);
            Items.Insert(0, GetNextItem());
        }
    }

    private ListItemData GetNextItem()
    {
        return new ListItemData()
        {
            Header = "Header " + DateTime.Now.Second.ToString(),
            Date = DateTime.Now.ToLongDateString(),
            Body = DateTime.Now.ToLongTimeString()
        };
    }
}

public class ListItemData
{
    public string Header { get; set; }
    public string Date { get; set; }
    public string Body { get; set; }
}

Scaricare il codice di esempio