Samlingar med C++/WinRT

Internt sett har en Windows Runtime-samling många komplexa delar som samverkar. Men när du vill skicka ett samlingsobjekt till en Windows Runtime funktion eller implementera egna samlingsegenskaper och samlingstyper finns det funktioner och basklasser i C++/WinRT som stöd för dig. De här funktionerna tar komplexiteten ur händerna och sparar mycket tid och arbete.

IVector är det Windows Runtime-gränssnitt som implementeras av alla slumpmässigt tillgängliga samlingar av element. Om du skulle implementera IVector själv skulle du också behöva implementera IIterable, IVectorView och IIterator. Även om du behöver en anpassad samlingstyp är det mycket arbete. Men om du har data i en std::vector (eller en std::map, eller en std::unordered_map) och allt du vill göra är att skicka det till ett Windows Runtime API, så vill du undvika att göra den arbetsnivån, om möjligt. Och att undvika det är möjligt, eftersom C++/WinRT hjälper dig att skapa samlingar effektivt och med liten ansträngning.

Se även XAML-objektkontroller; binda till en C++/WinRT-samling.

Hjälpfunktioner för samlingar

Samling för generell användning, tom

Det här avsnittet beskriver scenariot där du vill skapa en samling som ursprungligen är tom. och fyll sedan i den när du har skapat den.

Om du vill hämta ett nytt objekt av en typ som implementerar en samling för generell användning kan du anropa funktionsmallen winrt::single_threaded_vector . Objektet returneras som en IVector och det är gränssnittet som du anropar det returnerade objektets funktioner och egenskaper via.

Om du vill kopiera och klistra in följande kodexempel direkt i huvudkällkodsfilen för ett Windows konsolprogramprojekt (C++/WinRT) anger du först Inte använda förkompilerade rubriker i projektegenskaper.

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

Som du ser i kodexemplet ovan, när du har skapat samlingen kan du lägga till element, iterera över dem och i allmänhet behandla objektet som du skulle göra med alla Windows Runtime samlingsobjekt som du kan ha fått från ett API. Om du behöver en oföränderlig vy över samlingen kan du anropa IVector::GetView, som du ser. Mönstret ovan – att skapa och använda en samling – är lämpligt för enkla scenarier där du vill skicka data till eller få ut data från ett API. Du kan skicka vidare en IVector eller en IVectorView överallt där ett IIterable förväntas.

I kodexemplet ovan initierar anropet till winrt::init_apartment tråden i Windows Runtime, som standard i en flertrådad lägenhet. Anropet initierar också COM.

Samling för generell användning, förberedd från data

Det här avsnittet beskriver scenariot där du vill skapa en samling och fylla i den samtidigt.

Du kan undvika omkostnaderna för anrop till Append i föregående kodexempel. Du kanske redan har källdata, eller så kanske du föredrar att fylla i källdata innan du skapar Windows Runtime samlingsobjektet. Så här gör du det.

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

Du kan skicka ett tillfälligt objekt som innehåller dina data till winrt::single_threaded_vector, som med coll1, ovan. Eller så kan du flytta en std::vector (förutsatt att du inte kommer åt den igen) till funktionen. I båda fallen skickar du ett rvalue till funktionen. Det gör att kompilatorn kan vara effektiv och undvika att kopiera data. Om du vill veta mer om rvalues kan du läsa Värdekategorier och referenser till dem.

Om du vill binda en XAML-objektkontroll till din samling kan du göra det. Men tänk på att om du vill ställa in egenskapen ItemsControl.ItemsSource på rätt sätt måste du ange det till ett värde av typen IVector av IInspectable (eller av en samverkanstyp som IBindableObservableVector).

Här är ett kodexempel som genererar en samling av en typ som är lämplig för bindning och lägger till ett element i den. Du hittar kontexten för det här kodexemplet i XAML-objektkontroller. Bind till en C++/WinRT-samling.

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

Du kan skapa en Windows Runtime samling från data och få en vy över den redo att skickas till ett API, allt utan att kopiera något.

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

I exemplen ovan kan samlingen vi skapar bindas till en XAML-objektkontroll. men samlingen kan inte observeras.

Observerbar samling

Om du vill hämta ett nytt objekt av en typ som implementerar en observerbar samling anropar du funktionsmallen winrt::single_threaded_observable_vector med valfri elementtyp. Men för att göra en observerbar samling lämplig för bindning till en XAML-objektkontroll använder du IInspectable som elementtyp.

Objektet returneras som en IObservableVector, och det är det gränssnitt som du (eller kontrollen som det är bundet till) anropar det returnerade objektets funktioner och egenskaper.

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

Mer information och kodexempel om att binda användargränssnittskontroller (UI) till en observerbar samling finns i XAML-objektkontroller; binda till en C++/WinRT-samling.

Associativ samling (karta)

Det finns associativa samlingsversioner av de två funktionerna som vi har tittat på.

Du kan också primera dessa samlingar med data genom att skicka ett rvalue av typen std::map eller std::unordered_map till funktionen.

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

Enkeltrådad

Den "enkeltrådade" i namnen på dessa funktioner anger att de inte ger någon samtidighet, med andra ord är de inte trådsäkra. Omnämnandet av trådar är inte relaterat till lägenheter, eftersom objekten som returneras från dessa funktioner är alla flexibla (se Agile-objekt i C++/WinRT). Det är bara det att objekten är enkeltrådiga. Och det är helt lämpligt om du bara vill skicka data på ett eller annat sätt över programmets binära gränssnitt (ABI).

Basklasser för samlingar

Om du vill implementera en egen anpassad samling för fullständig flexibilitet vill du undvika att göra det på det svåra sättet. Det är till exempel så här en anpassad vektorvy skulle se ut utan hjälp av C++/WinRT:s basklasser.

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

I stället är det mycket enklare att härleda din anpassade vektorvy från mallen winrt::vector_view_base struct och bara implementera funktionen get_container för att exponera containern som innehåller dina data.

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

Containern som returneras av get_container måste ange det start - och slutgränssnitt som winrt::vector_view_base förväntar sig. Som du ser i exemplet ovan tillhandahåller std::vector det. Men du kan returnera alla containrar som uppfyller samma kontrakt, inklusive din egen anpassade container.

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

Det här är de basklasser som C++/WinRT tillhandahåller för att hjälpa dig att implementera anpassade samlingar.

winrt::vector_view_base

Se kodexemplen ovan.

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

Viktiga API:er