diff --git a/samples/getting-started/ReactiveDemo.sln b/samples/getting-started/ReactiveDemo.sln
new file mode 100644
index 0000000000..a26068cff4
--- /dev/null
+++ b/samples/getting-started/ReactiveDemo.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.28010.2003
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReactiveDemo", "ReactiveDemo\ReactiveDemo.csproj", "{996C151B-7AAF-4C5C-A786-52DF9C251A73}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {996C151B-7AAF-4C5C-A786-52DF9C251A73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {996C151B-7AAF-4C5C-A786-52DF9C251A73}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {996C151B-7AAF-4C5C-A786-52DF9C251A73}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {996C151B-7AAF-4C5C-A786-52DF9C251A73}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {D4A4A79B-20F4-41EC-BADF-141BA15D261F}
+ EndGlobalSection
+EndGlobal
diff --git a/samples/getting-started/ReactiveDemo/App.config b/samples/getting-started/ReactiveDemo/App.config
new file mode 100644
index 0000000000..56efbc7b5f
--- /dev/null
+++ b/samples/getting-started/ReactiveDemo/App.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/getting-started/ReactiveDemo/App.xaml b/samples/getting-started/ReactiveDemo/App.xaml
new file mode 100644
index 0000000000..25a503fd0e
--- /dev/null
+++ b/samples/getting-started/ReactiveDemo/App.xaml
@@ -0,0 +1,9 @@
+
+
+
+
+
diff --git a/samples/getting-started/ReactiveDemo/App.xaml.cs b/samples/getting-started/ReactiveDemo/App.xaml.cs
new file mode 100644
index 0000000000..3a2731400b
--- /dev/null
+++ b/samples/getting-started/ReactiveDemo/App.xaml.cs
@@ -0,0 +1,18 @@
+using ReactiveUI;
+using Splat;
+using System.Reflection;
+using System.Windows;
+
+namespace ReactiveDemo
+{
+ public partial class App : Application
+ {
+ public App()
+ {
+ // A helper method that will register all classes that derive off IViewFor
+ // into our dependency injection container. ReactiveUI uses Splat for it's
+ // dependency injection by default, but you can override this if you like.
+ Locator.CurrentMutable.RegisterViewsForViewModels(Assembly.GetCallingAssembly());
+ }
+ }
+}
diff --git a/samples/getting-started/ReactiveDemo/AppViewModel.cs b/samples/getting-started/ReactiveDemo/AppViewModel.cs
new file mode 100644
index 0000000000..3989f31c29
--- /dev/null
+++ b/samples/getting-started/ReactiveDemo/AppViewModel.cs
@@ -0,0 +1,120 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using System.Reactive.Linq;
+using System.Threading;
+using System.Linq;
+using ReactiveUI;
+using NuGet.Protocol.Core.Types;
+using NuGet.Protocol;
+using NuGet.Configuration;
+
+namespace ReactiveDemo
+{
+ // AppViewModel is where we will describe the interaction of our application.
+ // We can describe the entire application in one class since it's very small now.
+ // Most ViewModels will derive off ReactiveObject, while most Model classes will
+ // most derive off INotifyPropertyChanged
+ public class AppViewModel : ReactiveObject
+ {
+ // In ReactiveUI, this is the syntax to declare a read-write property
+ // that will notify Observers, as well as WPF, that a property has
+ // changed. If we declared this as a normal property, we couldn't tell
+ // when it has changed!
+ private string _searchTerm;
+ public string SearchTerm
+ {
+ get => _searchTerm;
+ set => this.RaiseAndSetIfChanged(ref _searchTerm, value);
+ }
+
+ // Here's the interesting part: In ReactiveUI, we can take IObservables
+ // and "pipe" them to a Property - whenever the Observable yields a new
+ // value, we will notify ReactiveObject that the property has changed.
+ //
+ // To do this, we have a class called ObservableAsPropertyHelper - this
+ // class subscribes to an Observable and stores a copy of the latest value.
+ // It also runs an action whenever the property changes, usually calling
+ // ReactiveObject's RaisePropertyChanged.
+ private readonly ObservableAsPropertyHelper> _searchResults;
+ public IEnumerable SearchResults => _searchResults.Value;
+
+ // Here, we want to create a property to represent when the application
+ // is performing a search (i.e. when to show the "spinner" control that
+ // lets the user know that the app is busy). We also declare this property
+ // to be the result of an Observable (i.e. its value is derived from
+ // some other property)
+ private readonly ObservableAsPropertyHelper _isAvailable;
+ public bool IsAvailable => _isAvailable.Value;
+
+ public AppViewModel()
+ {
+ // Creating our UI declaratively
+ //
+ // The Properties in this ViewModel are related to each other in different
+ // ways - with other frameworks, it is difficult to describe each relation
+ // succinctly; the code to implement "The UI spinner spins while the search
+ // is live" usually ends up spread out over several event handlers.
+ //
+ // However, with ReactiveUI, we can describe how properties are related in a
+ // very organized clear way. Let's describe the workflow of what the user does
+ // in this application, in the order they do it.
+
+ // We're going to take a Property and turn it into an Observable here - this
+ // Observable will yield a value every time the Search term changes, which in
+ // the XAML, is connected to the TextBox.
+ //
+ // We're going to use the Throttle operator to ignore changes that happen too
+ // quickly, since we don't want to issue a search for each key pressed! We
+ // then pull the Value of the change, then filter out changes that are identical,
+ // as well as strings that are empty.
+ //
+ // We then do a SelectMany() which starts the task by converting Task>
+ // into IObservable>. If subsequent requests are made, the
+ // CancellationToken is called. We then ObservableOn the main thread,
+ // everything up until this point has been running on a separate thread due
+ // to the Throttle().
+ //
+ // We then use a ObservableAsPropertyHelper and the ToProperty() method to allow
+ // us to have the latest results that we can expose through the property to the View.
+ _searchResults = this
+ .WhenAnyValue(x => x.SearchTerm)
+ .Throttle(TimeSpan.FromMilliseconds(800))
+ .Select(term => term?.Trim())
+ .DistinctUntilChanged()
+ .Where(term => !string.IsNullOrWhiteSpace(term))
+ .SelectMany(SearchNuGetPackages)
+ .ObserveOn(RxApp.MainThreadScheduler)
+ .ToProperty(this, x => x.SearchResults);
+
+ // We subscribe to the "ThrownExceptions" property of our OAPH, where ReactiveUI
+ // marshals any exceptions that are thrown in SearchNuGetPackages method.
+ // See the "Error Handling" section for more information about this.
+ _searchResults.ThrownExceptions.Subscribe(error => { /* Handle errors here */ });
+
+ // A helper method we can use for Visibility or Spinners to show if results are available.
+ // We get the latest value of the SearchResults and make sure it's not null.
+ _isAvailable = this
+ .WhenAnyValue(x => x.SearchResults)
+ .Select(searchResults => searchResults != null)
+ .ToProperty(this, x => x.IsAvailable);
+ }
+
+ // Here we search NuGet packages using the NuGet.Client library. Ideally, we should
+ // extract such code into a separate service, say, INuGetSearchService, but let's
+ // try to avoid overcomplicating things at this time.
+ private async Task> SearchNuGetPackages(
+ string term, CancellationToken token)
+ {
+ var providers = new List>();
+ providers.AddRange(Repository.Provider.GetCoreV3()); // Add v3 API support
+ var packageSource = new PackageSource("https://api.nuget.org/v3/index.json");
+ var sourceRepository = new SourceRepository(packageSource, providers);
+
+ var filter = new SearchFilter(false);
+ var searchResource = await sourceRepository.GetResourceAsync();
+ var searchMetadata = await searchResource.SearchAsync(term, filter, 0, 10, null, token);
+ return searchMetadata.Select(x => new NugetDetailsViewModel(x));
+ }
+ }
+}
diff --git a/samples/getting-started/ReactiveDemo/MainWindow.xaml b/samples/getting-started/ReactiveDemo/MainWindow.xaml
new file mode 100644
index 0000000000..14a1edd4fb
--- /dev/null
+++ b/samples/getting-started/ReactiveDemo/MainWindow.xaml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/getting-started/ReactiveDemo/MainWindow.xaml.cs b/samples/getting-started/ReactiveDemo/MainWindow.xaml.cs
new file mode 100644
index 0000000000..f1c1b5d6ae
--- /dev/null
+++ b/samples/getting-started/ReactiveDemo/MainWindow.xaml.cs
@@ -0,0 +1,65 @@
+using System.Reactive.Disposables;
+using System.Windows;
+using ReactiveUI;
+
+namespace ReactiveDemo
+{
+ public partial class MainWindow : IViewFor
+ {
+ // Using a DependencyProperty as the backing store for ViewModel.
+ // This enables animation, styling, binding, etc.
+ public static readonly DependencyProperty ViewModelProperty =
+ DependencyProperty.Register("ViewModel",
+ typeof(AppViewModel), typeof(MainWindow),
+ new PropertyMetadata(null));
+
+ public MainWindow()
+ {
+ InitializeComponent();
+ ViewModel = new AppViewModel();
+
+ // We create our bindings here. These are the code behind bindings which allow
+ // type safety. The bindings will only become active when the Window is being shown.
+ // We register our subscription in our disposableRegistration, this will cause
+ // the binding subscription to become inactive when the Window is closed.
+ // The disposableRegistration is a CompositeDisposable which is a container of
+ // other Disposables. We use the DisposeWith() extension method which simply adds
+ // the subscription disposable to the CompositeDisposable.
+ this.WhenActivated(disposableRegistration =>
+ {
+ // Notice we don't have to provide a converter, on WPF a global converter is
+ // registered which knows how to convert a boolean into visibility.
+ this.OneWayBind(ViewModel,
+ viewModel => viewModel.IsAvailable,
+ view => view.searchResultsListBox.Visibility)
+ .DisposeWith(disposableRegistration);
+
+ this.OneWayBind(ViewModel,
+ viewModel => viewModel.SearchResults,
+ view => view.searchResultsListBox.ItemsSource)
+ .DisposeWith(disposableRegistration);
+
+ this.Bind(ViewModel,
+ viewModel => viewModel.SearchTerm,
+ view => view.searchTextBox.Text)
+ .DisposeWith(disposableRegistration);
+ });
+ }
+
+ // Our main view model instance.
+ public AppViewModel ViewModel
+ {
+ get => (AppViewModel)GetValue(ViewModelProperty);
+ set => SetValue(ViewModelProperty, value);
+ }
+
+ // This is required by the interface IViewFor, you always just set it to use the
+ // main ViewModel property. Note on XAML based platforms we have a control called
+ // ReactiveUserControl that abstracts this.
+ object IViewFor.ViewModel
+ {
+ get => ViewModel;
+ set => ViewModel = (AppViewModel)value;
+ }
+ }
+}
diff --git a/samples/getting-started/ReactiveDemo/NugetDetailsView.xaml b/samples/getting-started/ReactiveDemo/NugetDetailsView.xaml
new file mode 100644
index 0000000000..e6146c003d
--- /dev/null
+++ b/samples/getting-started/ReactiveDemo/NugetDetailsView.xaml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ Open
+
+
+
\ No newline at end of file
diff --git a/samples/getting-started/ReactiveDemo/NugetDetailsView.xaml.cs b/samples/getting-started/ReactiveDemo/NugetDetailsView.xaml.cs
new file mode 100644
index 0000000000..70a3e7a644
--- /dev/null
+++ b/samples/getting-started/ReactiveDemo/NugetDetailsView.xaml.cs
@@ -0,0 +1,43 @@
+using ReactiveUI;
+using System.Reactive.Disposables;
+using System.Windows.Media.Imaging;
+
+namespace ReactiveDemo
+{
+ // The class derives off ReactiveUserControl which contains the ViewModel property.
+ // In our MainWindow when we register the ListBox with the collection of
+ // NugetDetailsViewModels if no ItemTemplate has been declared it will search for
+ // a class derived off IViewFor and show that for the item.
+ public partial class NugetDetailsView : ReactiveUserControl
+ {
+ public NugetDetailsView()
+ {
+ InitializeComponent();
+ this.WhenActivated(disposableRegistration =>
+ {
+ // Our 4th parameter we convert from Url into a BitmapImage.
+ // This is an easy way of doing value conversion using ReactiveUI binding.
+ this.OneWayBind(ViewModel,
+ viewModel => viewModel.IconUrl,
+ view => view.iconImage.Source,
+ url => url == null ? null : new BitmapImage(url))
+ .DisposeWith(disposableRegistration);
+
+ this.OneWayBind(ViewModel,
+ viewModel => viewModel.Title,
+ view => view.titleRun.Text)
+ .DisposeWith(disposableRegistration);
+
+ this.OneWayBind(ViewModel,
+ viewModel => viewModel.Description,
+ view => view.descriptionRun.Text)
+ .DisposeWith(disposableRegistration);
+
+ this.BindCommand(ViewModel,
+ viewModel => viewModel.OpenPage,
+ view => view.openButton)
+ .DisposeWith(disposableRegistration);
+ });
+ }
+ }
+}
diff --git a/samples/getting-started/ReactiveDemo/NugetDetailsViewModel.cs b/samples/getting-started/ReactiveDemo/NugetDetailsViewModel.cs
new file mode 100644
index 0000000000..b87b9bb7b2
--- /dev/null
+++ b/samples/getting-started/ReactiveDemo/NugetDetailsViewModel.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Diagnostics;
+using System.Reactive;
+using NuGet.Protocol.Core.Types;
+using ReactiveUI;
+
+namespace ReactiveDemo
+{
+ // This class wraps out NuGet model object into a ViewModel and allows
+ // us to have a ReactiveCommand to open the NuGet package URL.
+ public class NugetDetailsViewModel : ReactiveObject
+ {
+ private readonly IPackageSearchMetadata _metadata;
+ private readonly Uri _defaultUrl;
+
+ public NugetDetailsViewModel(IPackageSearchMetadata metadata)
+ {
+ _metadata = metadata;
+ _defaultUrl = new Uri("https://git.io/fAlfh");
+ OpenPage = ReactiveCommand.Create(() => { Process.Start(ProjectUrl.ToString()); });
+ }
+
+ public Uri IconUrl => _metadata.IconUrl ?? _defaultUrl;
+ public string Description => _metadata.Description;
+ public Uri ProjectUrl => _metadata.ProjectUrl;
+ public string Title => _metadata.Title;
+
+ // ReactiveCommand allows us to execute logic without exposing any of the
+ // implementation details with the View. The generic parameters are the
+ // input into the command and it's output. In our case we don't have any
+ // input or output so we use Unit which in Reactive speak means a void type.
+ public ReactiveCommand OpenPage { get; }
+ }
+}
diff --git a/samples/getting-started/ReactiveDemo/Properties/AssemblyInfo.cs b/samples/getting-started/ReactiveDemo/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..9b8288b7c9
--- /dev/null
+++ b/samples/getting-started/ReactiveDemo/Properties/AssemblyInfo.cs
@@ -0,0 +1,24 @@
+using System.Reflection;
+using System.Resources;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Windows;
+
+[assembly: AssemblyTitle("ReactiveDemo")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("ReactiveDemo")]
+[assembly: AssemblyCopyright("")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+[assembly: ComVisible(false)]
+
+[assembly: ThemeInfo(
+ ResourceDictionaryLocation.None,
+ ResourceDictionaryLocation.SourceAssembly
+)]
+
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/samples/getting-started/ReactiveDemo/ReactiveDemo.csproj b/samples/getting-started/ReactiveDemo/ReactiveDemo.csproj
new file mode 100644
index 0000000000..f6bc442c29
--- /dev/null
+++ b/samples/getting-started/ReactiveDemo/ReactiveDemo.csproj
@@ -0,0 +1,78 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {996C151B-7AAF-4C5C-A786-52DF9C251A73}
+ WinExe
+ ReactiveDemo
+ ReactiveDemo
+ v4.7.2
+ 512
+ {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ 4
+ true
+ true
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+ App.xaml
+ Code
+
+
+
+ MainWindow.xaml
+ Code
+
+
+ Designer
+ MSBuild:Compile
+
+
+
+
+ NugetDetailsView.xaml
+
+
+
+ Code
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file