Raccolte con C++/WinRT

Internamente, una raccolta di Windows Runtime dispone di molte parti mobili complicate. Tuttavia, quando vuoi passare un oggetto raccolta a una funzione di Windows Runtime o implementare proprietà e tipi di raccolta personalizzati, sono disponibili nuove funzioni e classi di base C++/WinRT che possono aiutarti. Queste funzionalità eliminano le complessità e consentono di risparmiare molto tempo e fatica.

IVector è l'interfaccia di Windows Runtime implementata da qualsiasi raccolta di elementi ad accesso casuale. Se implementassi IVector tu stesso, dovresti implementare anche IIterable, IVectorView e IIterator. Anche se avessi bisogno di un tipo di raccolta personalizzato avresti molto lavoro da svolgere. Se invece disponi di dati in uno std::vector (oppure uno std::map o uno std::unordered_map) e vuoi solo passarli a un'API di Windows Runtime, preferirai, se è possibile, evitare tutto quel lavoro. In effetti è possibile, perché C++/WinRT ti aiuta a creare raccolte in modo efficiente e con un minimo sforzo.

Vedi anche Controlli di elementi XAML, binding a una raccolta C++/WinRT.

Funzioni di supporto per le raccolte

Raccolta per uso generico, vuota

Questa sezione descrive lo scenario in cui si desidera creare una raccolta che è inizialmente vuota e quindi popolarla dopo la creazione.

Per recuperare un nuovo oggetto di un tipo che implementa una raccolta per uso generico, è possibile chiamare il modello di funzione winrt::single_threaded_vector. L'oggetto viene restituito come un'IVector e si tratta dell'interfaccia tramite cui vengono chiamate funzioni e proprietà dell'oggetto restituito.

Se vuoi copiare e incollare i seguenti esempi di codice direttamente nel file di codice sorgente principale di un progetto Applicazione console Windows (C++/WinRT), imposta prima di tutto Senza intestazioni precompilate nelle proprietà del progetto.

// main.cpp
#include <winrt/Windows.Foundation.Collections.h>
#include <iostream>
using namespace winrt;

int main()
{
    winrt::init_apartment();

    Windows::Foundation::Collections::IVector<int> coll{ winrt::single_threaded_vector<int>() };
    coll.Append(1);
    coll.Append(2);
    coll.Append(3);

    for (auto const& el : coll)
    {
        std::cout << el << std::endl;
    }

    Windows::Foundation::Collections::IVectorView<int> view{ coll.GetView() };
}

Come puoi vedere nell'esempio di codice precedente, dopo aver creato la raccolta è possibile aggiungere elementi, eseguire iterazioni su essi e in genere trattare l'oggetto come si farebbe con qualsiasi oggetto raccolta di Windows Runtime ricevuto da un'API. Se hai bisogno di una visualizzazione immutabile sulla raccolta, puoi chiamare IVector::GetView, come mostrato. Il modello illustrato in precedenza, di creazione e uso di una raccolta, è appropriato per scenari semplici in cui si vuole passare dati o recuperare dati da un'API. Puoi passare un'IVector, o un'IVectorView, ovunque sia prevista un'IIterable.

La chiamata a winrt::init_apartment nell'esempio di codice riportato sopra inizializza il thread in Windows Runtime in un apartment a thread multipli per impostazione predefinita. La chiamata inizializza anche COM.

Raccolta per uso generico, preparata con i dati

Questa sezione descrive lo scenario in cui si desidera creare una raccolta e popolarla contemporaneamente.

È possibile evitare il sovraccarico delle chiamate ad Append nell'esempio di codice precedente. I dati di origine possono essere già disponibili oppure si più preferire popolarli prima di creare l'oggetto raccolta di Windows Runtime. Ecco come farlo.

auto coll1{ winrt::single_threaded_vector<int>({ 1,2,3 }) };

std::vector<int> values{ 1,2,3 };
auto coll2{ winrt::single_threaded_vector<int>(std::move(values)) };

for (auto const& el : coll2)
{
    std::cout << el << std::endl;
}

Puoi passare un oggetto temporaneo che contiene i dati a winrt::single_threaded_vector, come con coll1 sopra. Oppure puoi spostare uno std::Vector, se non vi accederai più, nella funzione. In entrambi i casi si passa un rvalue alla funzione. In questo modo il compilatore diventa più efficiente ed evita di copiare i dati. Se vuoi saperne di più sugli rvalue, vedi Categorie di valore e riferimenti.

Se vuoi associare un controllo di elementi XAML alla raccolta, puoi farlo. Tieni presente però che per impostare correttamente la proprietà ItemsControl.ItemsSource devi impostarla su un valore di tipo IVector di IInspectable o di un tipo di interoperabilità, ad esempio IBindableObservableVector.

Ecco un esempio di codice che produce una raccolta di un tipo appropriato per il binding e accoda un elemento a essa. Puoi trovare il contesto per questo esempio di codice in Controlli di elementi XAML, binding a una raccolta C++/WinRT.

auto bookSkus{ winrt::single_threaded_vector<Windows::Foundation::IInspectable>() };
bookSkus.Append(winrt::make<Bookstore::implementation::BookSku>(L"Moby Dick"));

Puoi creare una raccolta di Windows Runtime dai dati e ottenere una visualizzazione di essa pronta per il passaggio a un'API, il tutto senza copiare nulla.

std::vector<float> values{ 0.1f, 0.2f, 0.3f };
Windows::Foundation::Collections::IVectorView<float> view{ winrt::single_threaded_vector(std::move(values)).GetView() };

La raccolta creata negli esempi riportati sopra può essere associata a un controllo di elementi XAML, ma non è osservabile.

Raccolta osservabile

Per recuperare un nuovo oggetto di un tipo che implementa una raccolta osservabile, chiama il modello di funzione winrt::single_threaded_observable_vector con qualsiasi tipo di elemento. Per rendere una raccolta osservabile adatta per il binding a un controllo di elementi XAML, usa IInspectable come tipo di elemento.

L'oggetto viene restituito come un'IObservableVector e si tratta dell'interfaccia tramite cui (o del controllo a cui è associata) vengono chiamate funzioni e proprietà dell'oggetto restituito.

auto bookSkus{ winrt::single_threaded_observable_vector<Windows::Foundation::IInspectable>() };

Per altre informazioni dettagliate ed esempi di codice sul binding dei controlli dell'interfaccia utente a una raccolta osservabile, vedi Controlli di elementi XAML, binding a una raccolta C++/WinRT.

Raccolta associativa (mappa)

Per le due funzioni che abbiamo esaminato sono disponibili versioni per raccolte associative.

È facoltativamente possibile preparare queste raccolte con i dati passando alla funzione una rvalue di tipo std::map o std::unordered_map.

auto coll1{
    winrt::single_threaded_map<winrt::hstring, int>(std::map<winrt::hstring, int>{
        { L"AliceBlue", 0xfff0f8ff }, { L"AntiqueWhite", 0xfffaebd7 }
    })
};

std::map<winrt::hstring, int> values{
    { L"AliceBlue", 0xfff0f8ff }, { L"AntiqueWhite", 0xfffaebd7 }
};
auto coll2{ winrt::single_threaded_map<winrt::hstring, int>(std::move(values)) };

A thread singolo

L'espressione "a thread singolo" nei nomi di queste funzioni indica che esse non forniscono concorrenza, in altre parole non sono thread-safe. La menzione dei thread non è correlata agli apartment perché gli oggetti restituiti da queste funzioni sono tutti Agile (vedi Oggetti Agile in C++/WinRT). Indica solo che gli oggetti sono a thread singolo. E questo è del tutto appropriato se si vuole semplicemente passare dati in una sola direzione o nell'altra attraverso l'interfaccia applicativa binaria (ABI).

Classi di base per le raccolte

Se, per una flessibilità completa, si desidera implementare una raccolta personalizzata, è opportuno evitare di farlo nel modo difficile. Ad esempio, ecco come si presenterebbe una visualizzazione vettoriale personalizzata senza l'assistenza delle classi di base di C++/WinRT.

...
using namespace winrt;
using namespace Windows::Foundation::Collections;
...
struct MyVectorView :
    implements<MyVectorView, IVectorView<float>, IIterable<float>>
{
    // IVectorView
    float GetAt(uint32_t const) { ... };
    uint32_t GetMany(uint32_t, winrt::array_view<float>) const { ... };
    bool IndexOf(float, uint32_t&) { ... };
    uint32_t Size() { ... };

    // IIterable
    IIterator<float> First() const { ... };
};
...
IVectorView<float> view{ winrt::make<MyVectorView>() };

In alternativa è molto più semplice derivare la visualizzazione vettoriale personalizzata dal modello di struct winrt::vector_view_base e implementare semplicemente la funzione get_container per esporre il contenitore che include i dati.

struct MyVectorView2 :
    implements<MyVectorView2, IVectorView<float>, IIterable<float>>,
    winrt::vector_view_base<MyVectorView2, float>
{
    auto& get_container() const noexcept
    {
        return m_values;
    }

private:
    std::vector<float> m_values{ 0.1f, 0.2f, 0.3f };
};

Il contenitore restituito da get_container deve fornire l'interfaccia begin e end che winrt::vector_view_base si aspetta. Come illustrato nell'esempio precedente, std:: Vector offre questo. Ma puoi restituire qualsiasi contenitore che rispetti lo stesso contratto, tra cui un contenitore personalizzato.

struct MyVectorView3 :
    implements<MyVectorView3, IVectorView<float>, IIterable<float>>,
    winrt::vector_view_base<MyVectorView3, float>
{
    auto get_container() const noexcept
    {
        struct container
        {
            float const* const first;
            float const* const last;

            auto begin() const noexcept
            {
                return first;
            }

            auto end() const noexcept
            {
                return last;
            }
        };

        return container{ m_values.data(), m_values.data() + m_values.size() };
    }

private:
    std::array<float, 3> m_values{ 0.2f, 0.3f, 0.4f };
};

Queste sono le classi di base fornite da C++/WinRT per agevolare l'implementazione delle raccolte personalizzate.

winrt::vector_view_base

Vedi gli esempi di codice riportati sopra.

winrt::vector_base

struct MyVector :
    implements<MyVector, IVector<float>, IVectorView<float>, IIterable<float>>,
    winrt::vector_base<MyVector, float>
{
    auto& get_container() const noexcept
    {
        return m_values;
    }

    auto& get_container() noexcept
    {
        return m_values;
    }

private:
    std::vector<float> m_values{ 0.1f, 0.2f, 0.3f };
};

winrt::observable_vector_base

struct MyObservableVector :
    implements<MyObservableVector, IObservableVector<float>, IVector<float>, IVectorView<float>, IIterable<float>>,
    winrt::observable_vector_base<MyObservableVector, float>
{
    auto& get_container() const noexcept
    {
        return m_values;
    }

    auto& get_container() noexcept
    {
        return m_values;
    }

private:
    std::vector<float> m_values{ 0.1f, 0.2f, 0.3f };
};

winrt::map_view_base

struct MyMapView :
    implements<MyMapView, IMapView<winrt::hstring, int>, IIterable<IKeyValuePair<winrt::hstring, int>>>,
    winrt::map_view_base<MyMapView, winrt::hstring, int>
{
    auto& get_container() const noexcept
    {
        return m_values;
    }

private:
    std::map<winrt::hstring, int> m_values{
        { L"AliceBlue", 0xfff0f8ff }, { L"AntiqueWhite", 0xfffaebd7 }
    };
};

winrt::map_base

struct MyMap :
    implements<MyMap, IMap<winrt::hstring, int>, IMapView<winrt::hstring, int>, IIterable<IKeyValuePair<winrt::hstring, int>>>,
    winrt::map_base<MyMap, winrt::hstring, int>
{
    auto& get_container() const noexcept
    {
        return m_values;
    }

    auto& get_container() noexcept
    {
        return m_values;
    }

private:
    std::map<winrt::hstring, int> m_values{
        { L"AliceBlue", 0xfff0f8ff }, { L"AntiqueWhite", 0xfffaebd7 }
    };
};

winrt::observable_map_base

struct MyObservableMap :
    implements<MyObservableMap, IObservableMap<winrt::hstring, int>, IMap<winrt::hstring, int>, IMapView<winrt::hstring, int>, IIterable<IKeyValuePair<winrt::hstring, int>>>,
    winrt::observable_map_base<MyObservableMap, winrt::hstring, int>
{
    auto& get_container() const noexcept
    {
        return m_values;
    }

    auto& get_container() noexcept
    {
        return m_values;
    }

private:
    std::map<winrt::hstring, int> m_values{
        { L"AliceBlue", 0xfff0f8ff }, { L"AntiqueWhite", 0xfffaebd7 }
    };
};

API importanti