├── .gitattributes ├── .gitignore ├── Animation.gif ├── HelloWorldBasic ├── App.xaml ├── App.xaml.cs ├── AssemblyInfo.cs ├── HelloWorldBasic.csproj ├── MainWindow.xaml └── MainWindow.xaml.cs ├── HelloWorldRUI.sln ├── HelloWorldRUI ├── Animation.gif ├── App.xaml ├── App.xaml.cs ├── AssemblyInfo.cs ├── FodyWeavers.xml ├── FodyWeavers.xsd ├── HelloWorldRUI.csproj ├── MainWindow.xaml └── MainWindow.xaml.cs ├── LICENSE └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/[Pp]ackages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/[Pp]ackages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/[Pp]ackages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc -------------------------------------------------------------------------------- /Animation.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richk1/HelloWorldRUI/8d4aa42f9b6977e763117de5a7cce26ea6115460/Animation.gif -------------------------------------------------------------------------------- /HelloWorldBasic/App.xaml: -------------------------------------------------------------------------------- 1 |  6 | 7 | 8 | -------------------------------------------------------------------------------- /HelloWorldBasic/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | namespace HelloWorldBasic; 4 | /// 5 | /// Interaction logic for App.xaml 6 | /// 7 | public partial class App : Application 8 | { 9 | } 10 | -------------------------------------------------------------------------------- /HelloWorldBasic/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | [assembly: ThemeInfo( 4 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 5 | //(used if a resource is not found in the page, 6 | // or application resource dictionaries) 7 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 8 | //(used if a resource is not found in the page, 9 | // app, or any theme specific resource dictionaries) 10 | )] 11 | -------------------------------------------------------------------------------- /HelloWorldBasic/HelloWorldBasic.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | WinExe 5 | net8.0-windows10.0.22621.0 6 | enable 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /HelloWorldBasic/MainWindow.xaml: -------------------------------------------------------------------------------- 1 |  13 | 14 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /HelloWorldBasic/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Windows; 5 | using System.Windows.Controls; 6 | using System.Windows.Data; 7 | using System.Windows.Threading; 8 | 9 | using CommunityToolkit.Mvvm.ComponentModel; 10 | 11 | namespace HelloWorldBasic; 12 | public partial class MainWindow : Window 13 | { 14 | private readonly AppViewModel viewModel; 15 | public MainWindow() { 16 | InitializeComponent(); 17 | viewModel = new AppViewModel(); 18 | 19 | GreetingTextBlock.SetBinding(TextBlock.TextProperty, new Binding("Greeting") { Source = viewModel }); 20 | LangTextBlock.SetBinding(TextBlock.TextProperty, new Binding("Language") { Source = viewModel }); 21 | } 22 | } 23 | 24 | public partial class AppViewModel : ObservableObject 25 | { 26 | [ObservableProperty] private string greeting = "Greeting"; 27 | [ObservableProperty] private string language = "Language"; 28 | private readonly Dictionary greetingsDict; 29 | private readonly string[] keys; 30 | private readonly int maxCount = 100; 31 | private readonly int dwellDuration = 2; 32 | 33 | private readonly DispatcherTimer timer; 34 | private readonly EventHandler th; 35 | private int tickCount = 0; 36 | 37 | public AppViewModel() { 38 | greetingsDict = new Dictionary() { 39 | { "English", "Hello World!" }, 40 | { "French", "Bonjour le monde!" }, 41 | { "German", "Hallo Welt!" }, 42 | { "Japanese", "Kon'nichiwa sekai!" }, 43 | { "Spanish", "¡Hola Mundo!" }, 44 | }; 45 | keys = [.. greetingsDict.Keys]; 46 | 47 | // Create a timer and associated event handler to rotate through the languages 48 | timer = new() { Interval = TimeSpan.FromSeconds(dwellDuration) }; 49 | timer.Tick += (th = new EventHandler(TickHandler)); 50 | timer.Start(); 51 | } 52 | 53 | // update Greeting when language changes 54 | partial void OnLanguageChanged(string value) { 55 | Greeting = keys.Contains(value) ? greetingsDict[value] : string.Empty; 56 | } 57 | 58 | // select next language every 2 seconds (100 times) 59 | private void TickHandler(object? sender, EventArgs e) { 60 | Language = keys[tickCount % keys.Length]; 61 | 62 | tickCount++; 63 | if (tickCount >= maxCount) { 64 | timer.Stop(); 65 | timer.Tick -= th; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /HelloWorldRUI.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.7.33920.267 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HelloWorldRUI", "HelloWorldRUI\HelloWorldRUI.csproj", "{2290CCD9-1A1F-4475-A1A5-1FA8BC392BDC}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HelloWorldBasic", "HelloWorldBasic\HelloWorldBasic.csproj", "{8910E851-D2B3-4FEB-ADC3-7FEA3BBCC4B3}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {2290CCD9-1A1F-4475-A1A5-1FA8BC392BDC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {2290CCD9-1A1F-4475-A1A5-1FA8BC392BDC}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {2290CCD9-1A1F-4475-A1A5-1FA8BC392BDC}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {2290CCD9-1A1F-4475-A1A5-1FA8BC392BDC}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {8910E851-D2B3-4FEB-ADC3-7FEA3BBCC4B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {8910E851-D2B3-4FEB-ADC3-7FEA3BBCC4B3}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {8910E851-D2B3-4FEB-ADC3-7FEA3BBCC4B3}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {8910E851-D2B3-4FEB-ADC3-7FEA3BBCC4B3}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {2ECD8EEB-D41D-444B-8DE4-1E63C2E5CE9D} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /HelloWorldRUI/Animation.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richk1/HelloWorldRUI/8d4aa42f9b6977e763117de5a7cce26ea6115460/HelloWorldRUI/Animation.gif -------------------------------------------------------------------------------- /HelloWorldRUI/App.xaml: -------------------------------------------------------------------------------- 1 |  6 | 7 | 8 | -------------------------------------------------------------------------------- /HelloWorldRUI/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using ReactiveUI; 2 | using Splat; 3 | using System.Reflection; 4 | using System.Windows; 5 | 6 | namespace HelloWorldRUI 7 | { 8 | /// 9 | /// Interaction logic for App.xaml 10 | /// 11 | public partial class App : Application 12 | { 13 | public App() 14 | { 15 | Locator.CurrentMutable.RegisterViewsForViewModels(Assembly.GetCallingAssembly()); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /HelloWorldRUI/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | [assembly:ThemeInfo( 4 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 5 | //(used if a resource is not found in the page, 6 | // or application resource dictionaries) 7 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 8 | //(used if a resource is not found in the page, 9 | // app, or any theme specific resource dictionaries) 10 | )] 11 | -------------------------------------------------------------------------------- /HelloWorldRUI/FodyWeavers.xml: -------------------------------------------------------------------------------- 1 |  2 | 4 | 5 | -------------------------------------------------------------------------------- /HelloWorldRUI/FodyWeavers.xsd: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. 12 | 13 | 14 | 15 | 16 | A comma-separated list of error codes that can be safely ignored in assembly verification. 17 | 18 | 19 | 20 | 21 | 'false' to turn off automatic generation of the XML Schema file. 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /HelloWorldRUI/HelloWorldRUI.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net8.0-windows10.0.22621.0 4 | WinExe 5 | true 6 | True 7 | True 8 | 10.0.17763.0 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /HelloWorldRUI/MainWindow.xaml: -------------------------------------------------------------------------------- 1 |  16 | 17 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /HelloWorldRUI/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reactive.Disposables; 5 | using System.Reactive.Linq; 6 | 7 | using ReactiveUI; 8 | using ReactiveUI.Fody.Helpers; 9 | 10 | namespace HelloWorldRUI 11 | { 12 | public partial class MainWindow : ReactiveWindow 13 | { 14 | public MainWindow() { 15 | InitializeComponent(); 16 | ViewModel = new AppViewModel(); 17 | 18 | this.WhenActivated(d => { 19 | this.OneWayBind(ViewModel, viewModel => viewModel.Lang, 20 | view => view.LangTextBlock.Text).DisposeWith(d); 21 | this.OneWayBind(ViewModel, viewModel => viewModel.Greeting, 22 | view => view.GreetingTextBlock.Text).DisposeWith(d); 23 | }); 24 | } 25 | } 26 | } 27 | 28 | // Normally the ViewModel goes in a separate file 29 | namespace HelloWorldRUI 30 | { 31 | public class AppViewModel : ReactiveObject 32 | { 33 | [Reactive] public string Greeting { get; set; } = "Greeting"; 34 | [ObservableAsProperty] public string Lang { get; } 35 | 36 | private readonly int maxCount = 100; 37 | private readonly int dwellDuration = 2; 38 | 39 | public AppViewModel() { 40 | Dictionary Greetings = new() { 41 | { "English", "Hello World!" }, 42 | { "French", "Bonjour le monde!" }, 43 | { "German", "Hallo Welt!" }, 44 | { "Japanese", "Kon'nichiwa sekai!" }, 45 | { "Spanish", "¡Hola Mundo!" }, 46 | }; 47 | string[] keys = [.. Greetings.Keys]; 48 | 49 | // select next language every 2 seconds (100 times) 50 | Observable.Interval(TimeSpan.FromSeconds(dwellDuration)) 51 | .Take(maxCount) 52 | .Select(_ => keys[(Array.IndexOf(keys, Lang) + 1) % keys.Length]) 53 | .ObserveOn(RxApp.MainThreadScheduler) 54 | .ToPropertyEx(this, x => x.Lang, "Language"); 55 | 56 | // update Greeting when language changes 57 | this.WhenAnyValue(x => x.Lang) 58 | .Where(lang => keys.Contains(lang)) 59 | .Select(x => Greetings[x]) 60 | .ObserveOn(RxApp.MainThreadScheduler) 61 | .Subscribe(x => Greeting = x); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 richk1 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HelloWorldRUI 2 | An example WPF HelloWorld program written using the ReactiveUI framework. 3 | 4 | 1. Original: 3 February 2019
5 | 2. Latest Update: Dec 2023 6 | * Now using DotNet 8 / C# 12 7 | * Mostly minor tweaks to syntax 8 | * My level of proficiency with ReactiveUI hasn't increased much since I originally posted this (I've been doing other things), so I've left most of my original commentary unchanged. My observations may well be outdated. 9 | * Added a copy of the same project written without using ReactiveUI for comparison. 10 | 11 | ## Motivation 12 | I'm just a hobbiest programmer these days, and thought I'd give ReactiveUI a spin. I had an unexpectedly difficult time figuring out how to get a simple starter program working using ReactiveUI. I found some documentation sources useful, and others confusing, or lacking SIMPLE examples for the current software versions. So, once I figured out some of the basics, I thought I'd post my working example here on github. It's just a slightly fancy Hello World that makes use of a few ReactiveUI components. 13 | 14 | (My biggest problem was getting the bindings to work. I think that was because I was using a version of the Splat service locater code in my app.xaml.cs file that was incompatible with the rest of my code. I had tried using different versions of that line that I copied from various documentation sources/examples, but not evidently ones that were in sync with the rest of my code. The sample that finally solved my problem was one of the two buried in the ReactvieUI source code itself - ReactiveDemo. This is the code the ReactiveUI 'Getting Started' page currently walks you through.) 15 | 16 | ---- 17 | ## This Example 18 | What it does:
19 | Opens a Window that shows the greeting "Hello World", cycling between a few 20 | different languages. 21 | 22 |
23 | 24 | ![](https://cdn.jsdelivr.net/gh/richk1/HelloWorldRUI@master/Animation.gif) 25 | 26 |
27 | 28 | ---- 29 | ### Development Environment 30 | I'm currently using Visual Studio 2022 Community Edition preview. 31 | 32 | ### Dependencies 33 | You'll need to install the following NuGet Packages into your environment. They'll 34 | in-turn load others that they require. 35 | 36 | * NuGet Packges 37 | * ReactiveUI.WPF 38 | * ReactiveUI.Fody 39 | 40 | (Note: I perhaps complicated the example a bit by using Fody, but its pretty 41 | straitforward to use, has given me very little trouble, and makes the code look 42 | cleaner.) 43 | 44 | ---- 45 | ### Source Files 46 | #### File: App.xaml 47 | Nothing really notable here. Standard WPF layout created by the Visual Studio 48 | project creater. 49 | 50 | #### File: App.xaml.cs 51 | Additions to the base template are: 52 | ##### Namespaces 53 | These namespaces are used by the Locator statement below. 54 | ````csharp 55 | using ReactiveUI; 56 | using Splat; 57 | using System.Reflection; 58 | ```` 59 | ##### App Class Constructor 60 | Add the following constructor code to the boilerplate produced by VS: 61 | ````csharp 62 | public App() 63 | { 64 | Locator.CurrentMutable.RegisterViewsForViewModels(Assembly.GetCallingAssembly()); 65 | } 66 | ```` 67 | Note 68 | * The Splat "Locater..." line magically connects the Views and ViewModels. I've seen various other incarnations of this line, but this is the one that worked for me. 69 | * Splat is ReactiveUI's built-in dependency inversion container. When I first tried ReactiveUI I was unfamiliar with the concepts of Dependency Injection/Inversion of Control and the various frameworks that help implement them. Apparently ReactiveUI supports overriding Splat with other popular DI implementations, but I myself haven't tried that yet. 70 | 71 | #### File: MainWindow.xaml 72 | 73 | ##### Window properties 74 | * The top level XAML object is ReactiveWindow rather than a vanilla WPF Window. 75 | * x:TypeArguments ties the specific ViewModel class to this ReactiveWindow. 76 | * Namespaces aliases are also defined for the project and for reactiveui 77 | 78 | ````xml 79 | 86 | ... 87 | 88 | ```` 89 | 90 | ##### Window Layout 91 | 92 | In this example we are only going to display two strings, each in its own TextBlock. 93 | In a normal WPF project you'd bind the UI elements to ViewModel properties explicitly 94 | within the XAML. Here, we simply ensure that each TextBlock has a unique name. 95 | The binding is done in the MainWindow's constructor code, which references 96 | the x:Name values. 97 | 98 | ````xml 99 | 100 | 101 | 102 | 103 | ```` 104 | 105 | 106 | #### File: MainWindow.xaml.cs 107 | In the HelloWorldRUI project, I've left both the View and ViewModel code in the 108 | same file. This is not normally done, but it doesn't hurt anything, and I think 109 | makes it a little easier to read as an example. 110 | 111 | In a WPF MVVM program, the View class constructor is normally responsible for calling 112 | InitializeComponent() and creating an instance of the ViewModel. In this ReactiveUI 113 | code the constructor also binds the View and ViewModel properties, as shown below. 114 | 115 | ##### View Code 116 | ````csharp 117 | using ReactiveUI; 118 | using System.Reactive.Disposables; 119 | 120 | namespace HelloWorldRUI 121 | { 122 | public partial class MainWindow : ReactiveWindow 123 | { 124 | public MainWindow() 125 | { 126 | InitializeComponent(); 127 | ViewModel = new AppViewModel(); 128 | 129 | this.WhenActivated(d => { 130 | this.OneWayBind(ViewModel, viewModel => viewModel.Lang, 131 | view => view.LangTextBlock.Text).DisposeWith(d); 132 | this.OneWayBind(ViewModel, viewModel => viewModel.Greeting, 133 | view => view.GreetingTextBlock.Text).DisposeWith(d); 134 | }); 135 | } 136 | } 137 | } 138 | ```` 139 | 140 | ##### ViewModel Code 141 | Assuming you understand Reactive Observable pipelines, this code is 142 | reasonably straightforward. 143 | 144 | Using Fody slightly changes the declarations of the Reactive properties and 145 | ObservableAsPropertyHelpers compared to how you've probably seen it described in 146 | ReactiveUI documentation. Setting ObservableAsPropertyHelper values using ToProperty() 147 | changes slightly as well. 148 | See the Fody readme (referenced farther below) for a more detailed explanation. 149 | 150 | The Observable.Interval pipeline creates an observable that fires every 2 seconds, 151 | eventually resulting in changes to the Language displayed on the UI. The take(100) 152 | function terminates the pipeline after 100 iterations. The Select() function 153 | specified will increment the Lang to the next one in the Greetings dictionary. 154 | ObserveOn(RxApp.MainThreadScheduler) needs to be used when changing any property 155 | bound to the View. ToPropertyEx() will set the value of the ObservableAsProperty 156 | Lang to the language output in the Select() function. Since Lang is bound to the 157 | LangTextBlock in the View, the UI will automatically reflect this change. The 158 | initial value of the Observable Lang is passed as an argument to ToPropertyEx(), 159 | as it cant be set otherwise. 160 | 161 | The final observable pipeline will take care of updating the Greeting. This pipeline 162 | is set to 'tick' whenever the ViewModel's Lang property changes (as specified by 163 | the WhenAnyValue function). The Where() clause ignores any values that are not 164 | contained in the Keys array. The Select() function looks up the Greeting associated 165 | with the Lang. Again, ObserveOn makes sure our change is visible to the UI, and 166 | the Subscribe() function simply sets the Reactive property Greeting with the 167 | result created by Select. Since Greeting is a 'simple' Reactive property (not an 168 | Observable) you can just set its value. Its initial value is specified on its 169 | declaration line. 170 | 171 | (Experiment: try removing one of the ObserveOn lines and see what happens. 172 | It's useful for future reference to see for yourself how that error manifests.) 173 | 174 | ````csharp 175 | using ReactiveUI; 176 | using ReactiveUI.Fody.Helpers; 177 | using System; 178 | using System.Collections.Generic; 179 | using System.Linq; 180 | using System.Reactive.Linq; 181 | 182 | namespace HelloWorldRUI 183 | { 184 | public class AppViewModel : ReactiveObject 185 | { 186 | [Reactive] public string Greeting { get; set; } = "Greeting"; 187 | [ObservableAsProperty] public string Lang { get; } 188 | private readonly int maxCount = 100; 189 | private readonly int dwellDuration = 2; 190 | 191 | public AppViewModel() 192 | { 193 | Dictionary Greetings = new Dictionary() { 194 | { "English", "Hello World!" }, 195 | { "French", "Bonjour le monde!" }, 196 | { "German", "Hallo Welt!" }, 197 | { "Japanese", "Kon'nichiwa sekai!" }, 198 | { "Spanish", "¡Hola Mundo!" }, 199 | }; 200 | string[] keys = Greetings.Keys.ToArray(); 201 | 202 | // select next language every 2 seconds (100 times) 203 | Observable.Interval(TimeSpan.FromSeconds(dwellDuration)) 204 | .Take(maxCount) 205 | .Select(_ => keys[(Array.IndexOf(keys, Lang) + 1) % keys.Length]) 206 | .ObserveOn(RxApp.MainThreadScheduler) 207 | .ToPropertyEx(this, x => x.Lang, "Language"); 208 | 209 | // update Greeting when language changes 210 | this.WhenAnyValue(x => x.Lang) 211 | .Where(lang => keys.Contains(lang)) 212 | .Select(x => Greetings[x]) 213 | .ObserveOn(RxApp.MainThreadScheduler) 214 | .Subscribe(x => Greeting = x); 215 | } 216 | } 217 | } 218 | ```` 219 | ---- 220 | 221 | ## Misc Notes 222 | ### Fody 223 | If you create these source files by hand, you may get an error when building the 224 | project the first time indicating that the FodyWeavers.xml file couldn't be found 225 | and had to be generated for you. In that case simply rebuild the project and the 226 | error should resolve itself. (Rebuilding after the FodyWeavers file was created 227 | also resolved an Intellisense error I had in the MainWindow.xaml file which claimed 228 | it couldn't find the AppViewModel.) 229 | 230 | ### Intellisense 231 | Integration of ReactiveUI and Fody with Intellisense seems to be incomplete or 232 | slow at times. Occasionally I think I found Intellisense reported errors which 233 | resolved themselves by rebuilding the project. 234 | 235 | ## Useful References 236 |

237 | 239 | ReactiveUI Project 240 |

241 | 242 | Links: 243 | * Main Website: https://reactiveui.net/ 244 | * API: https://reactiveui.net/api/ 245 | * GitHub: https://github.com/reactiveui/ReactiveUI 246 | * Getting Started example - ReactiveDemo.sln: 247 | * master: https://github.com/reactiveui/ReactiveUI.Samples/tree/main/wpf/getting-started 248 | * snapshot of the version I looked at (in case it changes): [0a8d8fb4afa90fc839026a66d1193fccdfb44938](https://github.com/reactiveui/ReactiveUI/tree/0a8d8fb4afa90fc839026a66d1193fccdfb44938/samples/getting-started) 249 | 250 | ### ReactiveUI.Fody: 251 | This was originally an independant project, but has since been absorbed into the ReactiveUI GitHub repo. 252 | 253 | Links: 254 | * ReactiveUI Page discussing Fody: https://www.reactiveui.net/docs/handbook/view-models/boilerplate-code 255 | * Current Source in ReactiveUI GitHub: 256 | https://github.com/reactiveui/ReactiveUI/tree/master/src/ReactiveUI.Fody 257 | * Old GitHub project: https://github.com/kswoll/ReactiveUI.Fody 258 | 259 | ### ReactiveX (C#) 260 | Reactive Extensions (ReactiveX) is a project which provides Reactive libraries for a number of programming languages and platforms. One such supported combination is C#/DotNet (aka the System.Reactive package). The base form of Reactive programming has no direct support for Graphical User Interfaces (that's where ReactiveUI comes in.) Its been around for quite a while, and there's some pretty good documentation for it, though not necessarily all up to date for C#/DotNet. However the older stuff is still VERY useful. 261 | (You may notice that over time there has been some significant overlap of the maintainers of dotnet/reactive and ReactiveUI) 262 | 263 | Links: 264 | * ReactiveX Website: http://reactivex.io/ 265 | * Rx.NET aka dotnet/Reactive aka System.Reactive GitHub repo: 266 | https://github.com/dotnet/reactive 267 | * Introduction to Rx online Book 268 | http://introtorx.com/Content/v1.0.10621.0/00_Foreword.html 269 | The above link now redirects to https://introrx.com, which is most probably an updated version, or at least an updated presentation of the material originally written in 2012. (I haven't had time to look through the new info.) 270 | 271 | 272 | ### You, I and ReactiveUI (Book by Kent Boogaart, April 2018) 273 | At the time I'm writing this section (~2019), this book was (and perhaps still is) the only organized, well written, reasonably complete reference/tutorial for ReactiveUI that I could find. I'm still working my way through it, and I'm finding it very useful. (Thanks to the author for making the effort to write and publish it!) 274 | The biggest shortcoming of the book, for me as a newcomer, was the way the sample code was organized. I found that it made it very difficult for me to observe and emulate each example as a standalone application. 275 | I offer the following observations for my fellow noobs:
276 | * The sample code is organized as one big project. I found this a bit awkward when trying to browse the source code, as the files for each example (app files, MainWindow and View files, and ViewModel files), are spread out far apart from one another, making for a bit of work when trying to view all the files that belong to a single example. 277 | * The project makes use of an external WPF UI toolkit (MahApps.metro) that I haven't found discussed in the text (as far as I've read). 278 | * The service locator code used in the project's app.xaml.cs doesn't match what I'm using in my HelloWorld (which I copied from ReactiveUI's 'getting started' sample). In fact, I dont see a description of RegisterViewsForModels() in the book. (Note: the book doesn't claim to describe all the APIs.) Probably the way the book does it would work for me if I understood the service locator API better. 279 | * (I haven't finished reading it all yet. I'm skipping around reading the parts I think I need to get my code working.) 280 | 281 | Links 282 | * Book GitHub repo: https://github.com/kentcb/YouIandReactiveUI 283 | 284 | [ReactiveUILogo]:https://d33wubrfki0l68.cloudfront.net/1b88ade41838e8036700f92c2fb945455fb45fb3/97288/assets/img/logo.png 285 | --------------------------------------------------------------------------------