diff --git a/_docs/avalonia-docs-theme.yml b/_docs/avalonia-docs-theme.yml
index 0d189dd1..fc1a82d5 100644
--- a/_docs/avalonia-docs-theme.yml
+++ b/_docs/avalonia-docs-theme.yml
@@ -1,4 +1,4 @@
-extends: default
+extends: default-with-font-fallbacks
base:
font-color: #222222
background-color: #ffffff
diff --git a/scripts/Ascii-doc-pdf_creator.ps1 b/scripts/Ascii-doc-pdf_creator.ps1
index 2f2f115c..049a1997 100644
--- a/scripts/Ascii-doc-pdf_creator.ps1
+++ b/scripts/Ascii-doc-pdf_creator.ps1
@@ -19,5 +19,5 @@ foreach ($file in $adocFiles) {
$pdfPath = [System.IO.Path]::ChangeExtension($file.FullName, ".pdf")
Write-Host "Compiling $($file.FullName) to $pdfPath"
- asciidoctor-pdf "$($file.FullName)" -r asciidoctor-diagram -a pdf-theme="$themePath" -a allow-uri-read=true -a source-highlighter=rouge -o "$pdfPath" --trace
+ asciidoctor-pdf "$($file.FullName)" -r asciidoctor-diagram -a pdf-theme="$themePath" -a allow-uri-read=true -a source-highlighter=rouge -a compress=screen -o "$pdfPath" --trace
}
\ No newline at end of file
diff --git a/src/Avalonia.Samples/CompleteApps/Avalonia.MusicStore/README.adoc b/src/Avalonia.Samples/CompleteApps/Avalonia.MusicStore/README.adoc
index 463449b3..a3470c54 100644
--- a/src/Avalonia.Samples/CompleteApps/Avalonia.MusicStore/README.adoc
+++ b/src/Avalonia.Samples/CompleteApps/Avalonia.MusicStore/README.adoc
@@ -1,5 +1,6 @@
= Music Store App
// --- D O N ' T T O U C H T H I S S E C T I O N ---
+ifdef::env-github[]
:toc:
:toc-placement!:
:tip-caption: :bulb:
@@ -7,26 +8,36 @@
:important-caption: :heavy_exclamation_mark:
:caution-caption: :fire:
:warning-caption: :warning:
+endif::[]
+
+ifndef::env-github[]
+:icons: font
+:icon-set: fas
+endif::[]
// ----------------------------------------------------------
// Write a short summary here what this examples does
-This example will show you how to create a simple music store using Avalonia and MVVM community toolkit. You'll learn the MVVM pattern with the MVVM community toolkit to manage multiple application windows. Also you will use advanced asynchronous techniques to implement the album search and other features, so that application responsiveness is maintained.
+In this tutorial you will create a desktop app based on the idea of a music store. The app is highly graphical - it presents images of album covers, and uses semi-transparent 'acrylic' blurred window backgrounds to give a very up-to-date look. By the end of the tutorial, you will be able search the iTunes online list of albums, and select albums for your own list.
+
+[[final_result,finished app]]
+.This is how the final App should look like
+image::_docs/initial_preview.png[This is how the final App should look like]
// --- D O N ' T T O U C H T H I S S E C T I O N ---
toc::[]
// ---------------------------------------------------------
-
+[discrete]
=== Difficulty
// Choose one of the below difficulties. You can just delete the ones you don't need.
đ„ Easy đ„
-
+[discrete]
=== Buzz-Words
// Write some buzz-words here. You can separate them by ", "
@@ -35,7 +46,12 @@ Music Store, Complete App, CommunityToolkit.MVVM, Mvvm.Messaging, Styles, Observ
== Before we start
-This is a more advanced tutorial. The 'To Do List App' is a recommended prerequisite if you have limited experience with the MVVM pattern. Find 'To Do List App' tutorial link:../../CompleteApps/SimpleToDoList[here].
+
+In this tutorial you will learn how to use the MVVM pattern with the https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/[[MVVM community toolkit\]] to manage multiple application windows. Also you will use advanced asynchronous techniques to implement the album search and other features, so that application responsiveness is maintained.
+
+TIP: This is a more advanced tutorial. The 'To Do List App' is a recommended prerequisite if you have limited experience with the MVVM pattern. Read about the 'To Do List App' tutorial link:../../CompleteApps/SimpleToDoList[[here\]].
+
+NOTE: For information and background on the concept of the MVVM pattern, see https://docs.avaloniaui.net/docs/concepts/the-mvvm-pattern/[[here\]].
This sample assumes that you have a basic knowledge about the following topics:
@@ -47,18 +63,1824 @@ This sample assumes that you have a basic knowledge about the following topics:
TIP: Some sections are optional. You can skip these if you want to.
=== MVVM pattern
-For information and background on the concept of the MVVM pattern, refer to the official documentation link:https://docs.avaloniaui.net/docs/concepts/the-mvvm-pattern/[here].
+For information and background on the concept of the MVVM pattern, refer to the official documentation link:https://docs.avaloniaui.net/docs/concepts/the-mvvm-pattern/[[here\]].
+=== Messengers
+This tutorial uses the messaging features of the CommunityToolkit.Mvvm to manage communication between view models and views. This is one approach to share data between different classes, without "knowing each other". If you are not familiar with it, you can read about it in the official documentation link:https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/messenger[[here\]].
== The Solution
-You can follow the complete step-by-step guide for building the Music Store App in the official tutorial link:https://docs.avaloniaui.net/docs/tutorials/music-store-app/[here].
-== Related
+=== 1, Create a New Project
+
+Before you can start programming, you will have to create a new project for the app. Please see the https://docs.avaloniaui.net/docs/get-started/[[getting started guide\]] if you need help installing Avalonia and setting up your IDE.
+
+Most IDEs have templates for creating new Avalonia projects. In this tutorial you will use the 'Avalonia MVVM Application' template, which is available in both _Visual Studio_ and _JetBrains Rider_. Make sure you have selected the _CommunityToolkit.Mvvm_ option when creating the project.
+
+TIP: If you have _ReactiveUI_ installed, you can uninstall this and install _CommunityToolkit.Mvvm_ instead. Make sure to also update your usages.
+
+A new project will be created with the following solution folders and files:
+
+.The basic structure of the created project
+image::_docs/2_rider_proj_structure.png[This is the structure of the creates project]
+
+NOTE: The file names may differ a little. For example "MainViewModel.cs" may be named "MainViewModel.cs" depending on the IDE you are using.
+
+==== Update Project Dependencies if needed
+
+Let's make sure you use correct version of CommunityToolkit.Mvvm:
+
+ - Locate project file Avalonia.MusicStore.csproj
+ - Right-click on the project name in the Solution Explorer.
+ - Select 'Edit' -> 'Edit Avalonia.MusicStore.csproj'
+
+[[prepare-project-for-partial-properties, Setup the project]]
+In the opened .csproj file, ensure you have the correct CommunityToolkit.Mvvm package version no older than 8.4.0 and
+Avalonia version no older than 11.3.0.
+
+.Update nuget packages
+```xml
+
+
+
+
+```
+In the same file enable preview C# language features:
+
+.Within the _PropertyGroup_ section, add the following line:
+```xml
+preview
+```
+
+This setting enables support for the latest C# features required by this tutorial, including partial properties introduced in C# 13.
+
+Now take some time to review the files and folders that the solution template created. You will see that the following the MVVM pattern, these folders were created:
+
+[cols="20h,~"]
+|===
+| Folder Name |Description
+
+|Assets
+|Contains any embedded assets that are compiled into the program. `Images`, `Icons`, `Fonts` etc, anything that the UI
+Folder Name Description
+might need to display,
+
+|Models
+|This is an empty folder for code that is the 'model' part of the MVVM pattern. This often contains everything else the app needs that is not part of the UI. For example: interaction with a database, Web API, or interfaces with a hardware device.
+
+|View Models
+|This is a folder for all the view models in the project, and it already contains an example. View models contain the application logic in the MVVM pattern. For example: a button is enabled only when the user has typed something; or open a dialog when the user clicks here; or show an error if the user enters too high a number type of logic in this input.
+
+|Views
+|This is a folder for all the views in the project, and it already contains the view for the application main window. Views in the MVVM pattern contain only the presentation for the application; that is layout and form, fonts, colors, icons and images. In MVVM they have only enough code to link them to the view model layer. In _Avalonia UI_ there is only enough code to manage windows and dialogs here.
+|===
+
+
+NOTE: To explore the concepts behind the MVVM pattern, and when is appropriate to use it, see https://docs.avaloniaui.net/docs/concepts/the-mvvm-pattern/[[Avalonia-docs\]]
+
+The solution template has created enough files for the application to run. You will meet all of these during the rest of this tutorial.
+
+==== Run the Project
+
+Press the debug button in your IDE to compile and run the project.
+
+This will show a window that looks like:
+
+.first run of the created project
+image::_docs/5_first_run.png[First run]
+
+It is a little plain - but you now have a running application, and a blank canvas to start developing with.
+
+=== Window Styling
+
+Now, you will make the main window look modern by applying a dark theme, and an acrylic blur to the window background.
+
+==== Dark Mode
+
+Follow this procedure to style the main window in 'dark' mode:
+
+- Stop the app if it is still running.
+- Locate and open the file **App.axaml**.
+- In the XAML, change the `RequestedThemeVariant` attribute in the `` element from "Default" to "Dark"
++
+```xml
+
+```
+
+- Now locate and open the **MainWindow.axaml** file in the **/Views** folder.
++
+NOTE: Notice that the preview pane is still showing the window in 'light' mode. The application will require a rebuild for the new mode to show in the preview pane.
+
+- Click **Build Startup Project** on the **Build** menu.
++
+The preview pane now changes to the dark mode.
++
+image:_docs/6_DarkMode.png[Previewer showing the dark mode]
+
+==== Acrylic Blur
+
+Follow this procedure to style the background of the main window with an acrylic blur:
+
+- Locate and open the **MainWindow.axaml** file in the **/Views** folder.
+- Find the end of the opening tag of the `` element.
+- After the `Title="Avalonia.MusicStore"` attribute, add two new attributes as follows:
++
+```xml
+
+```
+
+- To apply the acrylic effect to the whole window, replace the `` element in the content zone of the main window with the following XAML for a panel:
++
+```xml
+
+
+
+
+
+
+
+
+
+```
+
+- Click **Debug** to compile and run the project.
++
+.Acrylic materia applied
+image::_docs/7_AcrylicBlur.png[Acrylic materia applied]
+
+
+Notice that, as expected, the acrylic window effect covers the content zone of the main window. However the effect does not yet extend to the title bar.
+
+WARNING: Note that _Linux_ users can not yet take advantage of the following code due to limitations of the X11 version. The tutorial code will run and the window will still work on _Linux_, but the full effect will not be realized.
+
+Follow this procedure to extend the acrylic blur effect onto the title bar:
+
+- Stop the app if is still running.
+- Find the end of the opening tag of the `` element again.
+- Add the `ExtendClientAreaToDecorationsHint` attribute as shown:
++
+```xml
+
+```
+
+- Click **Debug** to compile and run the project.
+
+.Fully acrylic window
+image::_docs/8_FullAcrylicWindow.png[Fully acrylic window]
+
+Now you have the acrylic blur effect extending into the title bar.
+
+
+=== Add and Layout Controls
+
+The main window of the app will eventually show a list of album covers in the user's collection, with a button at its top-right corner to allow the user to add a new album. The button will open a search dialog window to find new albums to add.
+
+On this page you will learn how to layout the main window so that the button appears at its top-right corner, as required.
+
+==== Button Layout
+
+To display a button in the content zone of the main window, follow this procedure:
+
+- Stop the app if it is still running.
+- Locate and open the **MainWindow.axaml** file.
+- Inside the panel element, add the following XAML for a button. The panel XAML should look like this:
++
+```xml
+
+
+
+
+
+
+
+
+
+```
+
+- Click **Debug** to compile and run the project.
++
+.Added button but wrong location
+image::_docs/9_Button_added_but_wrong_location.png[Added the button to buy new music.]
+
+You will see the button, but it is in the default position and not at the top-right of the window as required.
+
+Follow this procedure to position the button correctly:
+
+- Stop the app if it is still running
+- Wrap the button element in a new panel element.
+- Add a margin attribute to the new panel element, with a value of 40.
+- Add horizontal and vertical alignment attributes to the button element, as shown:
+
+```xml
+
+
+
+```
+
+You should see all these changes reflected in the preview pane as you add them.
+
+==== Button Icon
+
+Have a look back at the image of the <>. You will see that the button shows an icon, and not text (as it currently does). This is actually the Microsoft Store icon from the Fluent Icons collection, and _Avalonia UI_ has definitions for all these for you to use.
+
+To use the Microsoft Store icon, follow this procedure:
+
+- Navigate to the _Avalonia UI_ _GitHub_ to find the list of Fluent Icons at https://avaloniaui.github.io/icons.html
+- Use your browser's text search to locate the name of the icon 'store\_microsoft\_regular'. There should be some code similar to:
++
+```xml
+M11.5 9.5V13H8V9.5H11.5Z M11.5 17.5V14H8V17.5H11.5Z M16 9.5V13H12.5V9.5H16Z M16 17.5V14H12.5V17.5H16Z M8 6V3.75C8 2.7835 8.7835 2 9.75 2H14.25C15.2165 2 16 2.7835 16 3.75V6H21.25C21.6642 6 22 6.33579 22 6.75V18.25C22 19.7688 20.7688 21 19.25 21H4.75C3.23122 21 2 19.7688 2 18.25V6.75C2 6.33579 2.33579 6 2.75 6H8ZM9.5 3.75V6H14.5V3.75C14.5 3.61193 14.3881 3.5 14.25 3.5H9.75C9.61193 3.5 9.5 3.61193 9.5 3.75ZM3.5 18.25C3.5 18.9404 4.05964 19.5 4.75 19.5H19.25C19.9404 19.5 20.5 18.9404 20.5 18.25V7.5H3.5V18.25Z
+```
+
+- Copy all of the code for the icon.
+- In the solution explorer, right-click the project.
+- Click **Add**, then click **Avalonia Resources**
+- Enter the **Name** 'Icons', press enter.
+- Locate and open the new **Icons.axaml** file that is created. The XAML will look like this:
++
+```xml
+
+
+
+
+
+
+
+
+```
+
+- Paste your icon code inside the ``.
++
+TIP: Remember that each node needs the `x:Key` provided.
+
+The icons file now looks like this:
+
+```xml
+
+
+
+
+
+
+
+
+
+
+ [ ... Add the path data here ... ]
+ [ ... Add the path data here ... ]
+
+
+```
+
+TIP: Most of the time the path can be also extracted from any svg-path.
+
+With a new icons file prepared, you must now include it in your app.
+
+Follow this procedure to include the icons file:
+
+- Locate and open the **App.axaml** file.
+- Add a `` element with a `` as shown:
++
+```xml
+
+
+
+
+
+
+
+```
+
+You need to build the application so that the icons become available to the preview pane.
+
+To change the button from text to icon content, follow this procedure:
+
+- Locate and open the **MainWindow.axaml** file.
+- Alter the XAML for the button, as shown:
++
+```xml
+
+```
+
+- Click **Debug** to compile and run the project.
++
+.The button now has an icon
+image::_docs/10_Button_with_icon.png[Button with icon]
+
+=== Button Command
+
+So far in this tutorial, you have altered only files from the view part of the MVVM pattern (for the main window and app). In this section you will learn how to link the button in the view for the main window, to a command in the view model. This will cause user interaction with the view (in this case a button click) to have an effect in the application logic of the view model.
+
+When you develop with _Avalonia UI_ and the MVVM pattern, the solution template will give you a choice of MVVM toolkits. This tutorial now uses _CommunityToolkit.Mvvm_, and the solution template has already added the necessary packages.
+
+==== RelayCommand
+
+The first step in linking the view and view model is to make the view model able to accept a command. You will achieve this by adding a method to the main window view model and decorating it with the `[RelayCommand]` attribute, which will generate a bindable `ICommand` property, which can be referenced from your view.
+Follow this procedure:
+
+- Stop the app if it is still running.
+- Locate and open the **MainViewModel.cs** file in the **/ViewModels** folder.
+- Delete the existing content of the class, and add the code shown:
++
+```csharp
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using System.Threading.Tasks;
+
+namespace Avalonia.MusicStore.ViewModels
+{
+ public partial class MainViewModel : ObservableObject
+ {
+ public MainViewModel()
+ {
+ // ViewModel initialization logic.
+ }
+
+ [RelayCommand]
+ private async Task AddAlbumAsync()
+ {
+ // Code here will be executed when the button is clicked.
+ }
+ }
+}
+```
+
+==== How it works
+The `[RelayCommand]` attribute generates a public property for you at compile time named `AddAlbumCommand`, which implements `ICommand`.
+
+This means that even though you only wrote a method named `AddAlbumAsync`, Avalonia's data-binding system can bind directly to `AddAlbumCommand` in your AXAML â without you writing any boilerplate command logic.
+
+TIP: If you want to see how this method is executes, you can place a debug breakpoint at the opening curly brace inside the `AddAlbumAsync()` method.
+
+To complete the link from the view to your new `AddAlbumAsync` view model property, you will add a data binding to the button.
+
+NOTE: For more information about the concept of data binding, see https://docs.avaloniaui.net/docs/basics/data/data-binding[[here\]].
+
+To add the button data binding, follow this procedure:
+
+- Locate and open the **MainWindow.axaml** file.
+- Find the XAML for the button and add the command attribute and binding, as shown:
++
+```xml
+
+```
+
+==== Why it is `AddAlbumCommand`?
+The `[RelayCommand]` attribute automatically generates command properties based on your method names. If your method name ends with _Async_, the generator removes the _Async_ suffix and appends _Command_ to form the property name.
+If the method returns a Task, `[RelayCommand]` automatically generates an `IAsyncRelayCommand` instead of a regular `IRelayCommand`, giving you full support for asynchronous execution.
+This means:
+- If your method is named `AddAlbumAsync`, the generated property will be called `AddAlbumCommand`.
+- If your method is named `AddAlbum`, it also becomes `AddAlbumCommand`.
+
+NOTE: Learn more about asynchronous `RelayCommand` generation in https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/generators/relaycommand#asynchronous-commands[[the official docs\]].
+
+The `Command` property of an _Avalonia UI_ button determines what happens when the button is clicked. In this case it binds to the `AddAlbumCommand` generated in your view model, causing the `AddAlbumAsync` method to run.
+
+- Click **Debug** to compile and run the project.
+- Click the icon button.
+
+You will see the app stop executing at the breakpoint you previously set in the view model.
+
+
+=== Open a Dialog
+
+On this page you will learn how to open dialog window in your app and exchange data between windows using Mvvm.Messaging. The dialog will be used to search for and select an album to add to a list in the main window.
+
+Several messages will be used in your app:
+
+PurchaseAlbumMessage:: sent by the main view model to request the dialog window be shown and await a result.
+MusicStoreClosedMessage:: sent by the dialog's view model when the user selects an album, to return the result and close the dialog.
+CheckAlbumAlreadyExistsMessage:: sent by the dialog's view model before sending the `MusicStoreClosedMessage` to the main view model in order to make sure the album is not yet present. This part is optional
+NotificationMessage:: sent by the main view model to display a notification, for example when an album was bought successfully. This part is optional.
+
+Below is a stripped down diagram showing the message flow between the components that you are going to implement in the next steps:
+
+```mermaid
+graph TD;
+ A[MainViewModel] -->|Send PurchaseAlbumMessage| B(MainWindow)
+ B -->|Show MusicStoreWindow await AlbumViewModel| C[MusicStoreWindow]
+ C -->|BuyMusic| D[MusicStoreViewModel]
+ D -->|Send MusicStoreClosedMessage with SelectedAlbum| C
+ C -->|Close dialog return SelectedAlbum| B
+ B -->|Reply with AlbumViewModel| A
+```
+NOTE: The diagram above is simplified to show the basic message flow. In the actual implementation, there are additional message exchanges for checking if an album already exists and for displaying notifications.
+
+=== Add a New Dialog Window
+
+There is nothing special about a window view file that makes it into a dialog; that is up to the way in which the window is controlled by the app. You will use Avalonia UI features and _CommunityToolkit.Mvvm_ to manage this. So the first step is to create a new window for the app.
+
+To create a new window, follow this procedure:
+
+- Stop the app if it is still running.
+- In the solution explorer, right-click the **/Views** folder and then click **Add**.
+- Click **Avalonia Window**.
+- When prompted for the name, type 'MusicStoreWindow'
+- Press enter.
+
+==== Dialog Window Styling
+
+To style the new dialog window so that it matches the main window, follow the same procedure as explain in the section "<>" for the main window.
+
+==== Dialog Input and Output
+
+The application logic for the dialog will be controlled by its own view model. This will be created and linked to the dialog window view whenever the dialog is to be shown.
+
+Similarly, the result of the users interaction with the dialog will eventually have to be passed back to the application logic for the main window for processing.
+
+At this stage you will create two empty view model classes to act as placeholders for the dialog view model, and the dialog return (selected album) object. To create these view models, follow this procedure:
+
+- In the solution explorer, right-click the **/ViewModels** folder and then click **Add**.
+- Click **Class**.
+- Name the class 'MusicStoreViewModel' and click **Add**.
+- Right-click again the **/ViewModels** folder and then click **Add** a second time.
+- Click **Class**.
+- Name the class 'AlbumViewModel' and click **Add**.
+
+=== Show Dialog
+
+Now that you have a new window `MusicStoreWindow` and the corresponding view models `MusicStoreViewModel` and `AlbumViewModel`.
+You are going to complete the logic so that:
+
+* The main window view model sends a message requesting the dialog to be shown.
+* The main window view receives that message, opens the dialog, and returns the result.
+
+Below is how this works step-by-step using the CommunityToolkit.Mvvm messaging API.
+
+==== Define the PurchaseAlbumMessage
+- In the project root directory create new folder **/Messages**
+- In the newly created **/Messages** folder add a class **PurchaseAlbumMessage**.
+
+First, you are going to define a message class called `PurchaseAlbumMessage` that carries an `AlbumViewModel` response.
+This message will be sent by the view model when it needs to show the dialog.
+
+Open **PurchaseAlbumMessage.cs** and add the following code there:
+
+```csharp
+using Avalonia.MusicStore.ViewModels;
+using CommunityToolkit.Mvvm.Messaging.Messages;
+
+namespace Avalonia.MusicStore.Messages;
+
+public class PurchaseAlbumMessage : AsyncRequestMessage;
+```
+
+_`AsyncRequestMessage`_ lets you send a request and await a reply of type T (in our case, AlbumViewModel?).
+
+==== Register the Message Handler in MainWindow
+In _MainWindow.axaml.cs_ register a handler for `PurchaseAlbumMessage`. This handler runs whenever the view model sends that message. Its job is to:
+
+- Create the dialog window.
+- Assign `MusicStoreViewModel` as its DataContext.
+- Call `ShowDialog` and pass the result back via m.Reply(...).
+
+
+Open _MainWindow.axaml.cs_ and add the following code into MainWindow constructor:
+```csharp
+public MainWindow()
+{
+ InitializeComponent();
+
+ if (Design.IsDesignMode)
+ return;
+
+ // Whenever 'Send(new PurchaseAlbumMessage())' is called, invoke this callback on the MainWindow instance:
+ WeakReferenceMessenger.Default.Register(this, static (w, m) =>
+ {
+ // Create an instance of MusicStoreWindow and set MusicStoreViewModel as its DataContext.
+ var dialog = new MusicStoreWindow
+ {
+ DataContext = new MusicStoreViewModel()
+ };
+ // Show dialog window and reply with returned AlbumViewModel or null when the dialog is closed.
+ m.Reply(dialog.ShowDialog(w));
+ });
+}
+```
+
+==== Send the Message from the ViewModel
+Now, update the `AddAlbumAsync()` method inside `MainViewModel` to send `PurchaseAlbumMessage` when the user clicks on the store button.
+- Open **MainViewModel.cs**
+- Locate the `AddAlbumAsync()` method that we added in the previous steps.
+- Edit `AddAlbumAsync()` as shown:
+```csharp
+[RelayCommand]
+private async Task AddAlbumAsync()
+{
+ // Send the message to the previously registered handler and await the selected album
+ var album = await WeakReferenceMessenger.Default.Send(new PurchaseAlbumMessage());
+}
+```
+Now:
+- Click **Debug** to compile and run the project.
+- Click the icon button.
+
+It all works - but the dialog window opens at the same size as the main window, and offset from it.
+
+==== Dialog Position and Size
+
+For the final tweak, you will make the dialog smaller than the main window, and open centered on it. You will also make the main window open in the center of the user's screen.
+
+Follow this procedure:
+
+- Stop the app if it is still running.
+- Locate and open the **MainWindow.axaml** file.
+- Add an attribute to the `` element to set the start-up position:
+
+```xml
+
+```
+
+- Locate and open the **MusicStoreWindow.axaml** file.
+- Add attributes for the width and height of the dialog, set at 1000 and 550 respectively.
+- Add the start-up position attribute set to `CenterOwner`, as shown:
+
+```xml
+
+```
+
+- Click **Debug** to compile and run the project.
+- Click the icon button.
+
+.Dialog opened centered
+image::_docs/12_opened_dialog.png[dialog window open centered]
+
+The dialog window is now opened centered inside the main window.
+
+
+=== Add Dialog Content
+
+Now you will learn how to add some content to the dialog window. This will be some controls for the search and a dialog close button; together with a list of placeholders for the album covers - these will eventually be loaded as the results of the search.
+
+To arrange the dialog controls, you will use the dock panel layout control, that is part of the _Avalonia UI_ built-in controls. This will keep the search controls at the top of the dialog, and the button at the bottom, whatever the height. The list will be the 'fill' area of the dock panel, so it will always take up all the remaining content zone.
+
+.A sketch of the dialog layout
+image::_docs/13_search_album_dialog_sketch.png[A sketch showing how the dialog window will be laid out]
+
+NOTE: For full information on the dock panel control, see the reference https://docs.avaloniaui.net/docs/reference/controls/dockpanel[[here\]].
+
+The dock panel itself will be located on an _Avalonia UI_ user control. This is so the code that shows the dialog can be separated from the code that operates the controls within the dialog.
+
+NOTE: This is a common pattern of UI Composition, to read about this concept, see https://docs.avaloniaui.net/docs/concepts/ui-composition[[here\]].
+
+Follow this procedure to add the user control and constituent controls for the dialog:
+
+- Stop the app if it is still running.
+- In the solution explorer, right-click the **/Views** folder and then click **Add**.
+- Click **Avalonia User Control**.
+- When prompted for the name, type 'MusicStoreView'.
+- Press enter.
+- Alter the XAML for the user control's content zone as follows:
+
+```xml
+
+
+
+
+
+
+
+
+
+
+```
+
+Inside the dialog the user will be able to search for albums, but this will use a Web API, and may take some time to return. It is for this reason that you have added a progress bar. The progress bar will be active during the search - to provide visual feedback to the user.
+
+Also, to ensure that the app remains responsive during the search, you will implement the operation itself as both asynchronous and cancellable. You will add this functionality later in the tutorial.
+
+Now the next step is for you to add the new user control to the content zone of the dialog window.
+
+To add the user control, follow this procedure:
+
+- Locate and open the **MusicStoreWindow.axaml** file.
+- Add the namespace for the views to the `` element:
++
+```xml
+
+```
+
+- Inside the panel element, add an element for new user control and a https://api-docs.avaloniaui.net/docs/T_Avalonia_Controls_Notifications_WindowNotificationManager[[NotificationManager\]] to show notifications to the user:
++
+```xml
+
+
+
+
+```
+
+NOTE: We will use the notification manager later in the tutorial to show messages to the user. To access it from code behind, give it the name `NotificationManager`.
+
+You will see the controls appear in the preview pane.
+
+
+=== Mock Search
+
+In this section you will create the view model for the album search feature, and then bind it to the controls on the new user control. At this stage you will use a mock of the search itself, so that you can concentrate on the view model.
+
+==== MVVM Toolkit View Model
+
+The _CommunityToolkit.Mvvm_ framework provides _Avalonia UI_ with support for its data binding system. You add this support by deriving your view model from the `ObservableObject` class, via the `ViewModelBase` class that was added to your project at the start, by the solution template.
+
+Follow this procedure to derive from the `ObservableObject` class:
+
+- Locate and open the **MusicStoreViewModel.cs** file.
+- Add the code to derive the class from `ViewModelBase` and make the class `partial`.
++
+```csharp
+namespace Avalonia.MusicStore.ViewModels
+{
+ public partial class MusicStoreViewModel : ViewModelBase
+ {
+ }
+}
+```
+This setup allows you to use attributes like `[ObservableProperty]`, which automatically generate backing fields and property change notifications needed for UI binding.
+
+NOTE: You can learn more about `[ObservableProperty]` and `INotifyPropertyChanged` https://docs.avaloniaui.net/docs/guides/data-binding/inotifypropertychanged[[here\]].
+
+At this stage, you will create two properties for the search application logic:
+
+* A text string that is the search criteria,
+* A Boolean that indicates whether the search is busy.
+
+Add the following properties using the `[ObservableProperty]` attribute:
+
+```csharp
+using CommunityToolkit.Mvvm.ComponentModel;
+
+namespace Avalonia.MusicStore.ViewModels
+{
+ public partial class MusicStoreViewModel : ViewModelBase
+ {
+ [ObservableProperty] public partial string? SearchText { get; set; }
+
+ [ObservableProperty] public partial bool IsBusy { get; private set; }
+ }
+}
+```
+NOTE: Note that the partial property syntax was introduced in C# 13 Community Toolkit 8.4, visit <> for correct setup.
+
+==== Data Binding
+
+Next you will add a data binding to link the view to the view model. The text box will be bound to the search text, and whether the progress bar is visible to the user will be bound to the Boolean.
+
+Follow this procedure to add data binding to the view:
+
+- Locate and open the **MusicStoreView.axaml** file.
+- Add the binding expressions shown:
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+==== Album Search and Selection
+
+Your next step is to create the music store view model properties needed to process albums. These are:
+
+* a collection of album view models to represent the albums that the search might find,
+* and a property to hold an album if the user selects one.
+
+Here you will use the `ObservableCollection` - this is a collection is capable of notification, and it is provided by the .NET framework.
+
+Follow this procedure to add the above properties:
+
+- Locate and open the **MusicStoreViewModel.cs** file.
+- Add the following code to the class:
++
+```csharp
+[ObservableProperty] public partial AlbumViewModel? SelectedAlbum { get; set; }
+
+public ObservableCollection SearchResults { get; } = new();
+```
+
+Next to bind these properties to the list box in the view, follow this procedure:
+
+- Locate and open the **MusicStoreView.axaml** file.
+- Add the binding expressions shown to the `` element:
++
+```xml
+
+```
+
+==== Mock Data
+
+Now, to test the app at this stage, you will add some mock data directly to the view model.
+
+Follow this procedure:
+
+- Locate and open the **MusicStoreViewModel.cs** file again.
+- Add a constructor to the class, as shown:
++
+```csharp
+public MusicStoreViewModel()
+{
+ SearchResults.Add(new AlbumViewModel());
+ SearchResults.Add(new AlbumViewModel());
+ SearchResults.Add(new AlbumViewModel());
+}
+```
+
+- Click **Debug** to compile and run the project.
++
+.Preview of the mock-up search results
+image::_docs/14_mock_search_preview.png[Mock search preview]
+
+This shows that the data binding from the list to the album collection in the view model is working, but the view is not graphical yet.
+
+
+=== Album View
+
+In this paragraph you will continue developing the search results list for the app by replacing the text currently shown with graphical album tiles.
+
+==== Icon Resource
+
+The first step here is to add a resource for the 'music note' icon. You will use this to act as a placeholder icon for the album covers in the app - they will eventually be replaced by the downloaded album cover artwork.
+
+To add the music note icon resource, follow this procedure:
+
+- Stop the app if it is still running.
+- Navigate to the _Avalonia UI_ _GitHub_ to find the list of Fluent Icons at https://avaloniaui.github.io/icons.html
+- Use your browser's text search to locate the name of the icon 'music_regular'. There should be some code similar to:
+
+```xml
+M11.5,2.75 C11.5,2.22634895 12.0230228,1.86388952 12.5133347,2.04775015 L18.8913911,4.43943933 C20.1598961,4.91511241 21.0002742,6.1277638 21.0002742,7.48252202 L21.0002742,10.7513533 C21.0002742,11.2750044 20.4772513,11.6374638 19.9869395,11.4536032 L13,8.83332147 L13,17.5 C13,17.5545945 12.9941667,17.6078265 12.9830895,17.6591069 C12.9940859,17.7709636 13,17.884807 13,18 C13,20.2596863 10.7242052,22 8,22 C5.27579485,22 3,20.2596863 3,18 C3,15.7403137 5.27579485,14 8,14 C9.3521238,14 10.5937815,14.428727 11.5015337,15.1368931 L11.5,2.75 Z M8,15.5 C6.02978478,15.5 4.5,16.6698354 4.5,18 C4.5,19.3301646 6.02978478,20.5 8,20.5 C9.97021522,20.5 11.5,19.3301646 11.5,18 C11.5,16.6698354 9.97021522,15.5 8,15.5 Z M13,3.83223733 L13,7.23159672 L19.5002742,9.669116 L19.5002742,7.48252202 C19.5002742,6.75303682 19.0477629,6.10007069 18.3647217,5.84393903 L13,3.83223733 Z
+```
+
+- Copy all of the code for the icon.
+- Locate and open the **Icons.axaml** file that you created earlier (see: <