Move to C++/WinRT from C#
Tip
If you've read this topic before, and you're returning to it with a particular task in mind, then you can jump to the Find content based on the task you're performing section of this topic.
This topic comprehensively catalogs the technical details involved in porting the source code in a C# project to its equivalent in C++/WinRT.
For a case study of porting one of the Universal Windows Platform (UWP) app samples, see the companion topic Porting the Clipboard sample to C++/WinRT from C#. You can gain porting practice and experience by following along with that walkthrough, and porting the sample for yourself as you go.
How to prepare, and what to expect
The case study Porting the Clipboard sample to C++/WinRT from C# illustrates examples of the kinds of software design decisions that you'll make while porting a project to C++/WinRT. So, it's a good idea to prepare for porting by gaining a solid understanding of how the existing code works. That way, you'll get a good overview of the app's functionality, and the code's structure, and then the decisions that you make will always take you forward, and in the right direction.
In terms of what kinds of porting changes to expect, you could group them into four categories.
- Port the language projection. The Windows Runtime (WinRT) is projected into various programming languages. Each of those language projections is designed to feel idiomatic to the programming language in question. For C#, some Windows Runtime types are projected as .NET types. So for example you'll be translating System.Collections.Generic.IReadOnlyList<T> back to Windows.Foundation.Collections.IVectorView<T>. Also in C#, some Windows Runtime operations are projected as convenient C# language features. An example is that in C# you use the
+=
operator syntax to register an event-handling delegate. So you'll be translating language features such as that back to the fundamental operation that's being performed (event registration, in this example). - Port language syntax. Many of these changes are simple mechanical transforms, replacing one symbol for another. For example, changing dot (
.
) to double-colon (::
). - Port language procedure. Some of these can be simple, repetitive changes (such as
myObject.MyProperty
tomyObject.MyProperty()
). Others need deeper changes (for example, porting a procedure that involves the use of System.Text.StringBuilder to one that involves the use of std::wostringstream). - Porting-related tasks that are specific to C++/WinRT. Certain details of the Windows Runtime are taken care of implicitly by C#, behind the scenes. Those details are done explicitly in C++/WinRT. An example is that you use an
.idl
file to define your runtime classes.
After the task-based index that follows, the rest of the sections in this topic are structured according to the taxonomy above.
Find content based on the task you're performing
Task | Content |
---|---|
Author a Windows Runtime component (WRC) | Certain functionality can be achieved (or certain APIs called) only with C++. You can factor that functionality into a C++/WinRT WRC, and then consume the WRC from (for example) a C# app. See Windows Runtime components with C++/WinRT and If you're authoring a runtime class in a Windows Runtime component. |
Port an async method | It's a good idea for the first line of an asynchronous method in a C++/WinRT runtime class to be auto lifetime = get_strong(); (see Safely accessing the this pointer in a class-member coroutine).Porting from Task , see Async action.Porting from Task<T> , see Async operation.Porting from async void , see Fire-and-forget method. |
Port a class | First, determine whether the class needs to be a runtime class, or whether it can be an ordinary class. To help you decide that, see the very beginning of Author APIs with C++/WinRT. Then, see the next three rows below. |
Port a runtime class | A class that shares functionality outside of the C++ app, or a class that's used in XAML data binding. See If you're authoring a runtime class in a Windows Runtime component, or If you're authoring a runtime class to be referenced in your XAML UI. Those links describe this in more detail, but a runtime class must be declared in IDL. If your project already contains an IDL file (for example, Project.idl ), then we recommend that you declare any new runtime class in that file. In IDL, declare any methods and data members that will be used outside your app, or that will be used in XAML. After updating the IDL file, rebuild, and look at the generated stub files (.h and .cpp ) in your project's Generated Files folder (In Solution Explorer, with the project node selected, make sure Show All Files is toggled on). Compare the stub files with the files already in your project, adding files or adding/updating function signatures as necessary. Stub file syntax is always correct, so we recommend that you use it in order to minimize build errors. Once the stubs in your project match those in the stub files, you can go ahead and implement them by porting the C# code over. |
Port an ordinary class | See If you're not authoring a runtime class. |
Author IDL | Introduction to Microsoft Interface Definition Language 3.0 If you're authoring a runtime class to be referenced in your XAML UI Consuming objects from XAML markup Define your runtime classes in IDL |
Port a collection | Collections with C++/WinRT Making a data source available to XAML markup Associative container Vector member access |
Port an event | Event handler delegate as class member Revoke event handler delegate |
Port a method | From C#: private async void SampleButton_Tapped(object sender, Windows.UI.Xaml.Input.TappedRoutedEventArgs e) { ... } To the C++/WinRT .h file: fire_and_forget SampleButton_Tapped(IInspectable const&, RoutedEventArgs const&); To the C++/WinRT .cpp file: fire_and_forget OcrFileImage::SampleButton_Tapped(IInspectable const&, RoutedEventArgs const&) {...} |
Port strings | String handling in C++/WinRT ToString String-building Boxing and unboxing a string |
Type conversion (type casting) | C#: o.ToString() C++/WinRT: to_hstring(static_cast<int>(o)) Also see ToString. C#: (Value)o C++/WinRT: unbox_value<Value>(o) Throws if unboxing fails. Also see Boxing and unboxing. C#: o as Value? ?? fallback C++/WinRT: unbox_value_or<Value>(o, fallback) Returns fallback if unboxing fails. Also see Boxing and unboxing. C#: (Class)o C++/WinRT: o.as<Class>() Throws if conversion fails. C#: o as Class C++/WinRT: o.try_as<Class>() Returns null if conversion fails. |
Changes that involve the language projection
Category | C# | C++/WinRT | See also |
---|---|---|---|
Untyped object | object , or System.Object |
Windows::Foundation::IInspectable | Porting the EnableClipboardContentChangedNotifications method |
Projection namespaces | using System; |
using namespace Windows::Foundation; |
|
using System.Collections.Generic; |
using namespace Windows::Foundation::Collections; |
||
Size of a collection | collection.Count |
collection.Size() |
Porting the BuildClipboardFormatsOutputString method |
Typical collection type | IList<T>, and Add to add an element. | IVector<T>, and Append to add an element. If you use a std::vector anywhere, then push_back to add an element. | |
Read-only collection type | IReadOnlyList<T> | IVectorView<T> | Porting the BuildClipboardFormatsOutputString method |
Event handler delegate as class member | myObject.EventName += Handler; |
token = myObject.EventName({ get_weak(), &Class::Handler }); |
Porting the EnableClipboardContentChangedNotifications method |
Revoke event handler delegate | myObject.EventName -= Handler; |
myObject.EventName(token); |
Porting the EnableClipboardContentChangedNotifications method |
Associative container | IDictionary<K, V> | IMap<K, V> | |
Vector member access | x = v[i]; v[i] = x; |
x = v.GetAt(i); v.SetAt(i, x); |
Register/revoke an event handler
In C++/WinRT, you have several syntactic options to register/revoke an event handler delegate, as described in Handle events by using delegates in C++/WinRT. Also see Porting the EnableClipboardContentChangedNotifications method.
Sometimes, for example when an event recipient (an object handling an event) is about to be destroyed, you'll want to revoke an event handler so that the event source (the object raising the event) doesn't call into a destroyed object. See Revoke a registered delegate. In cases like that, create an event_token member variable for your event handlers. For an example, see Porting the EnableClipboardContentChangedNotifications method.
You can also register an event handler in XAML markup.
<Button x:Name="OpenButton" Click="OpenButton_Click" />
In C#, your OpenButton_Click method can be private, and XAML will still be able to connect it to the ButtonBase.Click event raised by OpenButton.
In C++/WinRT, your OpenButton_Click method must be public in your implementation type if you want to register it in XAML markup. If you register an event handler only in imperative code, then the event handler doesn't need to be public.
namespace winrt::MyProject::implementation
{
struct MyPage : MyPageT<MyPage>
{
void OpenButton_Click(
winrt::Windows:Foundation::IInspectable const& sender,
winrt::Windows::UI::Xaml::RoutedEventArgs const& args);
}
};
Alternatively, you can make the registering XAML page a friend of your implementation type, and OpenButton_Click private.
namespace winrt::MyProject::implementation
{
struct MyPage : MyPageT<MyPage>
{
private:
friend MyPageT;
void OpenButton_Click(
winrt::Windows:Foundation::IInspectable const& sender,
winrt::Windows::UI::Xaml::RoutedEventArgs const& args);
}
};
One final scenario is where the C# project that you're porting binds to the event handler from markup (for more background on that scenario, see Functions in x:Bind).
<Button x:Name="OpenButton" Click="{x:Bind OpenButton_Click}" />
You could just change that markup to the more simple Click="OpenButton_Click"
. Or, if you prefer, you can keep that markup as it is. All you have to do to support it is to declare the event handler in IDL.
void OpenButton_Click(Object sender, Windows.UI.Xaml.RoutedEventArgs e);
Note
Declare the function as void
even if you implement it as Fire and forget.
Changes that involve the language syntax
Category | C# | C++/WinRT | See also |
---|---|---|---|
Access modifiers | public \<member\> |
public: \<member\> |
Porting the Button_Click method |
Access a data member | this.variable |
this->variable |
|
Async action | async Task ... |
IAsyncAction ... |
IAsyncAction interface, Concurrency and asynchronous operations with C++/WinRT |
Async operation | async Task<T> ... |
IAsyncOperation<T> ... |
IAsyncOperation interface, Concurrency and asynchronous operations with C++/WinRT |
Fire-and-forget method (implies async) | async void ... |
winrt::fire_and_forget ... |
Porting the CopyButton_Click method, Fire and forget |
Access an enumerated constant | E.Value |
E::Value |
Porting the DisplayChangedFormats method |
Cooperatively wait | await ... |
co_await ... |
Porting the CopyButton_Click method |
Collection of projected types as a private field | private List<MyRuntimeClass> myRuntimeClasses = new List<MyRuntimeClass>(); |
std::vector <MyNamespace::MyRuntimeClass> m_myRuntimeClasses; |
|
GUID construction | private static readonly Guid myGuid = new Guid("C380465D-2271-428C-9B83-ECEA3B4A85C1"); |
winrt::guid myGuid{ 0xC380465D, 0x2271, 0x428C, { 0x9B, 0x83, 0xEC, 0xEA, 0x3B, 0x4A, 0x85, 0xC1} }; |
|
Namespace separator | A.B.T |
A::B::T |
|
Null | null |
nullptr |
Porting the UpdateStatus method |
Obtain a type object | typeof(MyType) |
winrt::xaml_typename<MyType>() |
Porting the Scenarios property |
Parameter declaration for a method | MyType |
MyType const& |
Parameter-passing |
Parameter declaration for an async method | MyType |
MyType |
Parameter-passing |
Call a static method | T.Method() |
T::Method() |
|
Strings | string , or System.String |
winrt::hstring | String handling in C++/WinRT |
String literal | "a string literal" |
L"a string literal" |
Porting the constructor, Current, and FEATURE_NAME |
Inferred (or deduced) type | var |
auto |
Porting the BuildClipboardFormatsOutputString method |
Using-directive | using A.B.C; |
using namespace A::B::C; |
Porting the constructor, Current, and FEATURE_NAME |
Verbatim/raw string literal | @"verbatim string literal" |
LR"(raw string literal)" |
Porting the DisplayToast method |
Note
If a header file doesn't contain a using namespace
directive for a given namespace, then you'll have to fully-qualify all type names for that namespace; or at least qualify them sufficiently for the compiler to find them. For an example, see Porting the DisplayToast method.
Porting classes and members
You'll need to decide, for each C# type, whether to port it to a Windows Runtime type, or to a regular C++ class/struct/enumeration. For more info, and detailed examples illustrating how to make those decisions, see Porting the Clipboard sample to C++/WinRT from C#.
A C# property typically becomes an accessor function, a mutator function, and a backing data member. For more info, and an example, see Porting the IsClipboardContentChangedEnabled property.
For non-static fields, make them data members of your implementation type.
A C# static field becomes a C++/WinRT static accessor and/or mutator function. For more info, and an example, see Porting the constructor, Current, and FEATURE_NAME.
For member functions, again, you'll need to decide for each one whether or not it belongs in the IDL, or whether it's a public or private member function of your implementation type. For more info, and examples of how to decide, see IDL for the MainPage type.
Porting XAML markup, and asset files
In the case of Porting the Clipboard sample to C++/WinRT from C#, we were able to use the same XAML markup (including resources) and asset files across the C# and the C++/WinRT project. In some cases, edits to markup will be necessary to achieve that. See Copy the XAML and styles necessary to finish up porting MainPage.
Changes that involve procedures within the language
Category | C# | C++/WinRT | See also |
---|---|---|---|
Lifetime management in an async method | N/A | auto lifetime{ get_strong() }; orauto lifetime = get_strong(); |
Porting the CopyButton_Click method |
Disposal | using (var t = v) |
auto t{ v }; t.Close(); // or let wrapper destructor do the work |
Porting the CopyImage method |
Construct object | new MyType(args) |
MyType{ args } orMyType(args) |
Porting the Scenarios property |
Create uninitialized reference | MyType myObject; |
MyType myObject{ nullptr }; orMyType myObject = nullptr; |
Porting the constructor, Current, and FEATURE_NAME |
Construct object into variable with args | var myObject = new MyType(args); |
auto myObject{ MyType{ args } }; or auto myObject{ MyType(args) }; or auto myObject = MyType{ args }; or auto myObject = MyType(args); or MyType myObject{ args }; or MyType myObject(args); |
Porting the Footer_Click method |
Construct object into variable without args | var myObject = new T(); |
MyType myObject; |
Porting the BuildClipboardFormatsOutputString method |
Object initialization shorthand | var p = new FileOpenPicker{ ViewMode = PickerViewMode.List }; |
FileOpenPicker p; p.ViewMode(PickerViewMode::List); |
|
Bulk vector operation | var p = new FileOpenPicker{ FileTypeFilter = { ".png", ".jpg", ".gif" } }; |
FileOpenPicker p; p.FileTypeFilter().ReplaceAll({ L".png", L".jpg", L".gif" }); |
Porting the CopyButton_Click method |
Iterate over collection | foreach (var v in c) |
for (auto&& v : c) |
Porting the BuildClipboardFormatsOutputString method |
Catch an exception | catch (Exception ex) |
catch (winrt::hresult_error const& ex) |
Porting the PasteButton_Click method |
Exception details | ex.Message |
ex.message() |
Porting the PasteButton_Click method |
Get a property value | myObject.MyProperty |
myObject.MyProperty() |
Porting the NotifyUser method |
Set a property value | myObject.MyProperty = value; |
myObject.MyProperty(value); |
|
Increment a property value | myObject.MyProperty += v; |
myObject.MyProperty(thing.Property() + v); For strings, switch to a builder |
|
ToString() | myObject.ToString() |
winrt::to_hstring(myObject) |
ToString() |
Language string to Windows Runtime string | N/A | winrt::hstring{ s } |
|
String-building | StringBuilder builder; builder.Append(...); |
std::wostringstream builder; builder << ...; |
String-building |
String interpolation | $"{i++}) {s.Title}" |
winrt::to_hstring, and/or winrt::hstring::operator+ | Porting the OnNavigatedTo method |
Empty string for comparison | System.String.Empty | winrt::hstring::empty | Porting the UpdateStatus method |
Create empty string | var myEmptyString = String.Empty; |
winrt::hstring myEmptyString{ L"" }; |
|
Dictionary operations | map[k] = v; // replaces any existing v = map[k]; // throws if not present map.ContainsKey(k) |
map.Insert(k, v); // replaces any existing v = map.Lookup(k); // throws if not present map.HasKey(k) |
|
Type conversion (throw on failure) | (MyType)v |
v.as<MyType>() |
Porting the Footer_Click method |
Type conversion (null on failure) | v as MyType |
v.try_as<MyType>() |
Porting the PasteButton_Click method |
XAML elements with x:Name are properties | MyNamedElement |
MyNamedElement() |
Porting the constructor, Current, and FEATURE_NAME |
Switch to the UI thread | CoreDispatcher.RunAsync | CoreDispatcher.RunAsync, or winrt::resume_foreground | Porting the NotifyUser method, and Porting the HistoryAndRoaming method |
UI element construction in imperative code in a XAML page | See UI element construction | See UI element construction |
The following sections go into more detail regarding some of the items in the table.
UI element construction
These code examples show the construction of a UI element in the imperative code of a XAML page.
var myTextBlock = new TextBlock()
{
Text = "Text",
Style = (Windows.UI.Xaml.Style)this.Resources["MyTextBlockStyle"]
};
TextBlock myTextBlock;
myTextBlock.Text(L"Text");
myTextBlock.Style(
winrt::unbox_value<Windows::UI::Xaml::Style>(
Resources().Lookup(
winrt::box_value(L"MyTextBlockStyle")
)
)
);
ToString()
C# types provide the Object.ToString method.
int i = 2;
var s = i.ToString(); // s is a System.String with value "2".
C++/WinRT doesn't directly provide this facility, but you can turn to alternatives.
int i{ 2 };
auto s{ std::to_wstring(i) }; // s is a std::wstring with value L"2".
C++/WinRT also supports winrt::to_hstring for a limited number of types. You'll need to add overloads for any additional types you want to stringify.
Language | Stringify int | Stringify enum |
---|---|---|
C# | string result = "hello, " + intValue.ToString(); string result = $"hello, {intValue}"; |
string result = "status: " + status.ToString(); string result = $"status: {status}"; |
C++/WinRT | hstring result = L"hello, " + to_hstring(intValue); |
// must define overload (see below) hstring result = L"status: " + to_hstring(status); |
In the case of stringifying an enum, you will need to provide the implementation of winrt::to_hstring.
namespace winrt
{
hstring to_hstring(StatusEnum status)
{
switch (status)
{
case StatusEnum::Success: return L"Success";
case StatusEnum::AccessDenied: return L"AccessDenied";
case StatusEnum::DisabledByPolicy: return L"DisabledByPolicy";
default: return to_hstring(static_cast<int>(status));
}
}
}
These stringifications are often consumed implicitly by data binding.
<TextBlock>
You have <Run Text="{Binding FlowerCount}"/> flowers.
</TextBlock>
<TextBlock>
Most recent status is <Run Text="{x:Bind LatestOperation.Status}"/>.
</TextBlock>
These bindings will perform winrt::to_hstring of the bound property. In the case of the second example (the StatusEnum), you must provide your own overload of winrt::to_hstring, otherwise you'll get a compiler error.
Also see Porting the Footer_Click method.
String-building
For string building, C# has a built-in StringBuilder type.
Category | C# | C++/WinRT |
---|---|---|
String-building | StringBuilder builder; builder.Append(...); |
std::wostringstream builder; builder << ...; |
Append a Windows Runtime string, preserving nulls | builder.Append(s); |
builder << std::wstring_view{ s }; |
Add a newline | builder.Append(Environment.NewLine); |
builder << std::endl; |
Access the result | s = builder.ToString(); |
ws = builder.str(); |
Also see Porting the BuildClipboardFormatsOutputString method, and Porting the DisplayChangedFormats method.
Running code on the main UI thread
This example is taken from the Barcode scanner sample.
When you want to do work on the main UI thread in a C# project, you typically use the CoreDispatcher.RunAsync method, like this.
private async void Watcher_Added(DeviceWatcher sender, DeviceInformation args)
{
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
// Do work on the main UI thread here.
});
}
It's much simpler to express that in C++/WinRT. Notice that we're accepting parameters by value on the assumption we'll want to access them after the first suspension point (the co_await
, in this case). For more info, see Parameter-passing.
winrt::fire_and_forget Watcher_Added(DeviceWatcher sender, winrt::DeviceInformation args)
{
co_await Dispatcher();
// Do work on the main UI thread here.
}
If you need to do the work at a priority other than the default, then see the winrt::resume_foreground function, which has an overload that takes a priority. For code examples showing how to await a call to winrt::resume_foreground, see Programming with thread affinity in mind.
Porting-related tasks that are specific to C++/WinRT
Define your runtime classes in IDL
See IDL for the MainPage type, and Consolidate your .idl
files.
Include the C++/WinRT Windows namespace header files that you need
In C++/WinRT, whenever you want to use a type from a Windows namespaces, you need to include the corresponding C++/WinRT Windows namespace header file. For an example, see Porting the NotifyUser method.
Boxing and unboxing
C# automatically boxes scalars into objects. C++/WinRT requires you to call the winrt::box_value function explicitly. Both languages require you to unbox explicitly. See Boxing and unboxing with C++/WinRT.
In the tables that follows, we'll use these definitions.
C# | C++/WinRT |
---|---|
int i; |
int i; |
string s; |
winrt::hstring s; |
object o; |
IInspectable o; |
Operation | C# | C++/WinRT |
---|---|---|
Boxing | o = 1; o = "string"; |
o = box_value(1); o = box_value(L"string"); |
Unboxing | i = (int)o; s = (string)o; |
i = unbox_value<int>(o); s = unbox_value<winrt::hstring>(o); |
C++/CX and C# raise exceptions if you try to unbox a null pointer to a value type. C++/WinRT considers this a programming error, and it crashes. In C++/WinRT, use the winrt::unbox_value_or function if you want to handle the case where the object is not of the type that you thought it was.
Scenario | C# | C++/WinRT |
---|---|---|
Unbox a known integer | i = (int)o; |
i = unbox_value<int>(o); |
If o is null | System.NullReferenceException |
Crash |
If o is not a boxed int | System.InvalidCastException |
Crash |
Unbox int, use fallback if null; crash if anything else | i = o != null ? (int)o : fallback; |
i = o ? unbox_value<int>(o) : fallback; |
Unbox int if possible; use fallback for anything else | i = as int? ?? fallback; |
i = unbox_value_or<int>(o, fallback); |
For an example, see Porting the OnNavigatedTo method, and Porting the Footer_Click method.
Boxing and unboxing a string
A string is in some ways a value type, and in other ways a reference type. C# and C++/WinRT treat strings differently.
The ABI type HSTRING is a pointer to a reference-counted string. But it doesn't derive from IInspectable, so it's not technically an object. Furthermore, a null HSTRING represents the empty string. Boxing of things not derived from IInspectable is done by wrapping them inside an IReference<T>, and the Windows Runtime provides a standard implementation in the form of the PropertyValue object (custom types are reported as PropertyType::OtherType).
C# represents a Windows Runtime string as a reference type; while C++/WinRT projects a string as a value type. This means that a boxed null string can have different representations depending how you got there.
Behavior | C# | C++/WinRT |
---|---|---|
Declarations | object o; string s; |
IInspectable o; hstring s; |
String type category | Reference type | Value type |
null HSTRING projects as | "" |
hstring{} |
Are null and "" identical? |
No | Yes |
Validity of null | s = null; s.Length raises NullReferenceException |
s = hstring{}; s.size() == 0 (valid) |
If you assign null string to object | o = (string)null; o == null |
o = box_value(hstring{}); o != nullptr |
If you assign "" to object |
o = ""; o != null |
o = box_value(hstring{L""}); o != nullptr |
Basic boxing and unboxing.
Operation | C# | C++/WinRT |
---|---|---|
Box a string | o = s; Empty string becomes non-null object. |
o = box_value(s); Empty string becomes non-null object. |
Unbox a known string | s = (string)o; Null object becomes null string. InvalidCastException if not a string. |
s = unbox_value<hstring>(o); Null object crashes. Crash if not a string. |
Unbox a possible string | s = o as string; Null object or non-string becomes null string. OR s = o as string ?? fallback; Null or non-string becomes fallback. Empty string preserved. |
s = unbox_value_or<hstring>(o, fallback); Null or non-string becomes fallback. Empty string preserved. |
Making a class available to the {Binding} markup extension
If you intend to use the {Binding} markup extension to data bind to your data type, then see Binding object declared using {Binding}.
Consuming objects from XAML markup
In a C# project, you can consume private members and named elements from XAML markup. But in C++/WinRT, all entities consumed by using the XAML {x:Bind} markup extension must be exposed publicly in IDL.
Also, binding to a Boolean displays true
or false
in C#, but it shows Windows.Foundation.IReference`1<Boolean> in C++/WinRT.
For more info, and code examples, see Consuming objects from markup.
Making a data source available to XAML markup
In C++/WinRT version 2.0.190530.8 or later, winrt::single_threaded_observable_vector creates an observable vector that supports both IObservableVector<T> and IObservableVector<IInspectable>. For an example, see Porting the Scenarios property.
You can author your Midl file (.idl) like this (also see Factoring runtime classes into Midl files (.idl)).
namespace Bookstore
{
runtimeclass BookSku { ... }
runtimeclass BookstoreViewModel
{
Windows.Foundation.Collections.IObservableVector<BookSku> BookSkus{ get; };
}
runtimeclass MainPage : Windows.UI.Xaml.Controls.Page
{
MainPage();
BookstoreViewModel MainViewModel{ get; };
}
}
And implement like this.
// BookstoreViewModel.h
...
struct BookstoreViewModel : BookstoreViewModelT<BookstoreViewModel>
{
BookstoreViewModel()
{
m_bookSkus = winrt::single_threaded_observable_vector<Bookstore::BookSku>();
m_bookSkus.Append(winrt::make<Bookstore::implementation::BookSku>(L"To Kill A Mockingbird"));
}
Windows::Foundation::Collections::IObservableVector<Bookstore::BookSku> BookSkus();
{
return m_bookSkus;
}
private:
Windows::Foundation::Collections::IObservableVector<Bookstore::BookSku> m_bookSkus;
};
...
For more info, see XAML items controls; bind to a C++/WinRT collection, and Collections with C++/WinRT.
Making a data source available to XAML markup (prior to C++/WinRT 2.0.190530.8)
XAML data binding requires that an items source implements IIterable<IInspectable>, as well as one of the following combinations of interfaces.
- IObservableVector<IInspectable>
- IBindableVector and INotifyCollectionChanged
- IBindableVector and IBindableObservableVector
- IBindableVector by itself (will not respond to changes)
- IVector<IInspectable>
- IBindableIterable (will iterate and save elements into a private collection)
A generic interface such as IVector<T> can't be detected at runtime. Each IVector<T> has a different interface identifier (IID), which is a function of T. Any developer can expand the set of T arbitrarily, so clearly the XAML binding code can never know the full set to query for. That restriction isn't a problem for C# because every CLR object that implements IEnumerable<T> automatically implements IEnumerable. At the ABI level, that means that every object that implements IObservableVector<T> automatically implements IObservableVector<IInspectable>.
C++/WinRT doesn't offer that guarantee. If a C++/WinRT runtime class implements IObservableVector<T>, then we can't assume that an implementation of IObservableVector<IInspectable> is somehow also provided.
Consequently, here's how the previous example will need to look.
...
runtimeclass BookstoreViewModel
{
// This is really an observable vector of BookSku.
Windows.Foundation.Collections.IObservableVector<Object> BookSkus{ get; };
}
And the implementation.
// BookstoreViewModel.h
...
struct BookstoreViewModel : BookstoreViewModelT<BookstoreViewModel>
{
BookstoreViewModel()
{
m_bookSkus = winrt::single_threaded_observable_vector<Windows::Foundation::IInspectable>();
m_bookSkus.Append(winrt::make<Bookstore::implementation::BookSku>(L"To Kill A Mockingbird"));
}
// This is really an observable vector of BookSku.
Windows::Foundation::Collections::IObservableVector<Windows::Foundation::IInspectable> BookSkus();
{
return m_bookSkus;
}
private:
Windows::Foundation::Collections::IObservableVector<Windows::Foundation::IInspectable> m_bookSkus;
};
...
If you need to access objects in m_bookSkus, then you'll need to QI them back to Bookstore::BookSku.
Widget MyPage::BookstoreViewModel(winrt::hstring title)
{
for (auto&& obj : m_bookSkus)
{
auto bookSku = obj.as<Bookstore::BookSku>();
if (bookSku.Title() == title) return bookSku;
}
return nullptr;
}
Derived classes
In order to derive from a runtime class, the base class must be composable. C# doesn't require that you take any special steps to make your classes composable, but C++/WinRT does. You use the unsealed keyword to indicate that you want your class to be usable as a base class.
unsealed runtimeclass BasePage : Windows.UI.Xaml.Controls.Page
{
...
}
runtimeclass DerivedPage : BasePage
{
...
}
In the header file for your implementation type, you must include the base class header file before you include the autogenerated header for the derived class. Otherwise you'll get errors such as "Illegal use of this type as an expression".
// DerivedPage.h
#include "BasePage.h" // This comes first.
#include "DerivedPage.g.h" // Otherwise this header file will produce an error.
namespace winrt::MyNamespace::implementation
{
struct DerivedPage : DerivedPageT<DerivedPage>
{
...
}
}