diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
new file mode 100644
index 00000000..476dd7c3
--- /dev/null
+++ b/.config/dotnet-tools.json
@@ -0,0 +1,13 @@
+{
+ "version": 1,
+ "isRoot": true,
+ "tools": {
+ "fantomas": {
+ "version": "7.0.3",
+ "commands": [
+ "fantomas"
+ ],
+ "rollForward": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/.editorconfig b/.editorconfig
index de329fa9..1cc6785c 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -4,4 +4,6 @@ root = true
insert_final_newline = false
trim_trailing_whitespace = true
indent_style = space
+
+[*.{xml,slnx,fsproj,csproj,xaml}]
indent_size = 2
\ No newline at end of file
diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml
index 2bd7c2e4..52372b8f 100644
--- a/.github/workflows/continuous_integration.yml
+++ b/.github/workflows/continuous_integration.yml
@@ -21,13 +21,13 @@ jobs:
6.0.x
- name: Restore
- run: dotnet restore src/Elmish.WPF.sln
+ run: dotnet restore Elmish.WPF.sln
- name: Build
- run: dotnet build --no-restore --configuration Release src/Elmish.WPF.sln
+ run: dotnet build --no-restore --configuration Release Elmish.WPF.sln
- name: Test
- run: dotnet test --no-build --configuration Release src/Elmish.WPF.Tests/Elmish.WPF.Tests.fsproj
+ run: dotnet test --no-build --configuration Release tests/Elmish.WPF.Tests/Elmish.WPF.Tests.fsproj
- name: Publish NuGet
uses: alirezanet/publish-nuget@v3.0.4
diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml
index 28bddc2a..fc8187b3 100644
--- a/.github/workflows/pull_request.yml
+++ b/.github/workflows/pull_request.yml
@@ -23,10 +23,10 @@ jobs:
6.0.x
- name: Restore
- run: dotnet restore src/Elmish.WPF.sln
+ run: dotnet restore Elmish.WPF.sln
- name: Build
- run: dotnet build --no-restore --configuration Release src/Elmish.WPF.sln
+ run: dotnet build --no-restore --configuration Release Elmish.WPF.sln
- name: Test
- run: dotnet test --no-build --configuration Release src/Elmish.WPF.Tests/Elmish.WPF.Tests.fsproj
+ run: dotnet test --no-build --configuration Release tests/Elmish.WPF.Tests/Elmish.WPF.Tests.fsproj
diff --git a/.gitignore b/.gitignore
index 9f0c5275..d0581198 100644
--- a/.gitignore
+++ b/.gitignore
@@ -45,6 +45,9 @@ BenchmarkDotNet.Artifacts/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
+# Claude AI local settings
+.claude/
+
### VS Code ###
.vscode/*
!.vscode/settings.json
diff --git a/Elmish.WPF.sln b/Elmish.WPF.sln
new file mode 100644
index 00000000..1e82b5d2
--- /dev/null
+++ b/Elmish.WPF.sln
@@ -0,0 +1,1100 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31903.59
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}"
+EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Elmish.WPF", "src\Elmish.WPF\Elmish.WPF.fsproj", "{ED67A1EA-E467-4ABC-B627-8D8A184E20E9}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{0AB3BF05-4346-4AA6-1389-037BE0695223}"
+EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Elmish.WPF.Benchmarks", "tests\Elmish.WPF.Benchmarks\Elmish.WPF.Benchmarks.fsproj", "{06B21109-54A2-47FD-B701-C5B572059F88}"
+EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Elmish.WPF.Tests", "tests\Elmish.WPF.Tests\Elmish.WPF.Tests.fsproj", "{603D8145-0AD4-44BE-A730-678B9BE015E1}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{D1B4CF99-7947-DD6C-81D0-5B5B3989814C}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Dynamic", "Dynamic", "{D93CAD63-3252-5A7F-9C37-FCA27F08B606}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Capabilities", "src\Samples\Dynamic\Capabilities\Capabilities.csproj", "{F905EB30-20A0-42F6-9035-98C27D1CCF81}"
+EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Capabilities.Core", "src\Samples\Dynamic\Capabilities.Core\Capabilities.Core.fsproj", "{EBC55A44-EF7F-4D3E-8E94-8848C00C3059}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventBindingsAndBehaviors", "src\Samples\Dynamic\EventBindingsAndBehaviors\EventBindingsAndBehaviors.csproj", "{CE4B62D4-26FD-4C6A-B1C4-6871A057CE3F}"
+EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "EventBindingsAndBehaviors.Core", "src\Samples\Dynamic\EventBindingsAndBehaviors.Core\EventBindingsAndBehaviors.Core.fsproj", "{C1E54278-5FE4-4370-AA98-AB20F037AD6A}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileDialogs", "src\Samples\Dynamic\FileDialogs\FileDialogs.csproj", "{1982CF30-6D02-45F6-808A-C35A75623343}"
+EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FileDialogs.Core", "src\Samples\Dynamic\FileDialogs.Core\FileDialogs.Core.fsproj", "{ABCDCFAB-666F-40FA-906F-AFC3916E25D3}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileDialogsCmdMsg", "src\Samples\Dynamic\FileDialogsCmdMsg\FileDialogsCmdMsg.csproj", "{69C45190-79DC-4F2F-B0D2-0FD84B77F8A9}"
+EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FileDialogsCmdMsg.Core", "src\Samples\Dynamic\FileDialogsCmdMsg.Core\FileDialogsCmdMsg.Core.fsproj", "{FAD8476C-AE4E-4298-9F08-044ED3C0BA08}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Multiselect", "src\Samples\Dynamic\Multiselect\Multiselect.csproj", "{02668954-D273-4140-9FD2-1BDE615A401E}"
+EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Multiselect.Core", "src\Samples\Dynamic\Multiselect.Core\Multiselect.Core.fsproj", "{8565CE50-713C-4336-88AD-EAC732DC921A}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NewWindow", "src\Samples\Dynamic\NewWindow\NewWindow.csproj", "{5D83CDEA-106C-4002-BCC9-1F2BBB63EBE5}"
+EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "NewWindow.Core", "src\Samples\Dynamic\NewWindow.Core\NewWindow.Core.fsproj", "{F884FA9A-2A79-48AB-938C-A34E8A8BCF3E}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OneWaySeq", "src\Samples\Dynamic\OneWaySeq\OneWaySeq.csproj", "{239809C7-5679-4A85-AC76-254AB83C1DF0}"
+EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "OneWaySeq.Core", "src\Samples\Dynamic\OneWaySeq.Core\OneWaySeq.Core.fsproj", "{7B1997D7-7E82-4094-97FF-F267AB1E628E}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SingleCounter", "src\Samples\Dynamic\SingleCounter\SingleCounter.csproj", "{464B25D1-BF6F-4F52-B8C1-2F783A4865AE}"
+EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "SingleCounter.Core", "src\Samples\Dynamic\SingleCounter.Core\SingleCounter.Core.fsproj", "{74BE5736-B441-4371-AB5B-F1C7A61341C2}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sticky", "src\Samples\Dynamic\Sticky\Sticky.csproj", "{54B7ACB9-F2CD-4B30-ABA6-191C37E336A0}"
+EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Sticky.Core", "src\Samples\Dynamic\Sticky.Core\Sticky.Core.fsproj", "{F30FD499-8F88-4613-8B15-C06AA649881F}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SubModel", "src\Samples\Dynamic\SubModel\SubModel.csproj", "{220CFDCE-5B3B-49C7-A340-C19B7C28BF79}"
+EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "SubModel.Core", "src\Samples\Dynamic\SubModel.Core\SubModel.Core.fsproj", "{D17190BB-8C0E-4CC3-8644-F0B120EDBD55}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SubModelOpt", "src\Samples\Dynamic\SubModelOpt\SubModelOpt.csproj", "{6A489E6B-CD18-442D-A856-89DDE710B009}"
+EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "SubModelOpt.Core", "src\Samples\Dynamic\SubModelOpt.Core\SubModelOpt.Core.fsproj", "{E060A98E-50F0-45B0-AB5C-D33A90ADC1E6}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SubModelSelectedItem", "src\Samples\Dynamic\SubModelSelectedItem\SubModelSelectedItem.csproj", "{86B65707-7652-4ED0-8B0A-6C4907E8FF86}"
+EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "SubModelSelectedItem.Core", "src\Samples\Dynamic\SubModelSelectedItem.Core\SubModelSelectedItem.Core.fsproj", "{686BA896-BD38-490A-88C1-A8D0B48D05E6}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SubModelSeq", "src\Samples\Dynamic\SubModelSeq\SubModelSeq.csproj", "{A737F617-3916-4AC1-AEEE-69585FF89838}"
+EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "SubModelSeq.Core", "src\Samples\Dynamic\SubModelSeq.Core\SubModelSeq.Core.fsproj", "{A1B60C73-0FAC-43EF-93F9-4E6AD121F9D4}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Threading", "src\Samples\Dynamic\Threading\Threading.csproj", "{335C2372-CB51-4569-B04D-CE8AFC8EC755}"
+EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Threading.Core", "src\Samples\Dynamic\Threading.Core\Threading.Core.fsproj", "{59976F36-A8F0-425E-9620-FA78E68E145F}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UiBoundCmdParam", "src\Samples\Dynamic\UiBoundCmdParam\UiBoundCmdParam.csproj", "{3D4E6246-667E-4243-B2F3-B9CE12743B8D}"
+EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "UiBoundCmdParam.Core", "src\Samples\Dynamic\UiBoundCmdParam.Core\UiBoundCmdParam.Core.fsproj", "{BBC09072-9D08-454F-90BF-FD754CD60B93}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Validation", "src\Samples\Dynamic\Validation\Validation.csproj", "{43CF5F69-38A8-493E-960A-0877E248996F}"
+EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Validation.Core", "src\Samples\Dynamic\Validation.Core\Validation.Core.fsproj", "{C7D0416E-9785-40A1-921D-C92A56A9848F}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Typed", "Typed", "{1740D47D-DA94-6415-017E-48203BA1C52C}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SubModelStatic", "src\Samples\Typed\SubModelStatic\SubModelStatic.csproj", "{2A113076-21F0-4F11-A1CF-D0E4A1D14503}"
+EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "SubModelStatic.Core", "src\Samples\Typed\SubModelStatic.Core\SubModelStatic.Core.fsproj", "{A7BA896C-1D7E-4336-BE50-032BF8E62D81}"
+EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "SingleCounter.Core", "src\Samples\Typed\SingleCounter.Core\SingleCounter.Core.fsproj", "{F964D761-6542-4A3C-8ADC-41F5B8F77529}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SingleCounter", "src\Samples\Typed\SingleCounter\SingleCounter.csproj", "{148B26DF-05B6-44D6-890D-8AFBFAD9F7D9}"
+EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Validation.Core", "src\Samples\Typed\Validation.Core\Validation.Core.fsproj", "{6455B74A-6CB8-4DC3-9329-3D9CBB56173A}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Validation", "src\Samples\Typed\Validation\Validation.csproj", "{84AF37C1-52A8-4F3F-8F2C-E7394E789523}"
+EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "SubModelSeq.Core", "src\Samples\Typed\SubModelSeq.Core\SubModelSeq.Core.fsproj", "{75110CC0-5A94-4491-BA7E-234ACC39106D}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SubModelSeq", "src\Samples\Typed\SubModelSeq\SubModelSeq.csproj", "{8F1471D6-CEFA-4B4F-84C5-FB4B5CB035BD}"
+EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "SubModelOpt.Core", "src\Samples\Typed\SubModelOpt.Core\SubModelOpt.Core.fsproj", "{A57D4155-3F27-4BDA-900E-1A39B022BE00}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SubModelOpt", "src\Samples\Typed\SubModelOpt\SubModelOpt.csproj", "{86E04393-86E7-4BBF-A9EF-9433B91E4988}"
+EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Sticky.Core", "src\Samples\Typed\Sticky.Core\Sticky.Core.fsproj", "{B9C9B2A4-562B-45BB-BD91-CE5027F41C3F}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sticky", "src\Samples\Typed\Sticky\Sticky.csproj", "{435B3E59-C5B2-412A-86DC-795F1E52C223}"
+EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "OneWaySeq.Core", "src\Samples\Typed\OneWaySeq.Core\OneWaySeq.Core.fsproj", "{6E4E4453-36C8-42FC-9B8A-C8FEBCC6A643}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OneWaySeq", "src\Samples\Typed\OneWaySeq\OneWaySeq.csproj", "{CAD2DE5B-A359-4E51-8138-A02FDCA8E2A6}"
+EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "UiBoundCmdParam.Core", "src\Samples\Typed\UiBoundCmdParam.Core\UiBoundCmdParam.Core.fsproj", "{A3D2D2D1-413E-4367-B1D5-0AEEB1551240}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UiBoundCmdParam", "src\Samples\Typed\UiBoundCmdParam\UiBoundCmdParam.csproj", "{03FA4F94-962D-4324-B71E-D36E638D13D7}"
+EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "EventBindingsAndBehaviors.Core", "src\Samples\Typed\EventBindingsAndBehaviors.Core\EventBindingsAndBehaviors.Core.fsproj", "{197B01F2-1974-46A1-9C40-7303E405BFA7}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventBindingsAndBehaviors", "src\Samples\Typed\EventBindingsAndBehaviors\EventBindingsAndBehaviors.csproj", "{0C0BBD9A-1AC7-4B54-B725-03535AE033F1}"
+EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FileDialogs.Core", "src\Samples\Typed\FileDialogs.Core\FileDialogs.Core.fsproj", "{1093E8A9-77C3-43A0-ACA2-E0857EC19AED}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileDialogs", "src\Samples\Typed\FileDialogs\FileDialogs.csproj", "{FC2045E2-A48F-4389-9F72-90EDE8918FCC}"
+EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FileDialogsCmdMsg.Core", "src\Samples\Typed\FileDialogsCmdMsg.Core\FileDialogsCmdMsg.Core.fsproj", "{9B3D5A8F-16CD-4644-A239-EC2519195C80}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileDialogsCmdMsg", "src\Samples\Typed\FileDialogsCmdMsg\FileDialogsCmdMsg.csproj", "{BB30719A-3461-493E-972F-F6CEF534163E}"
+EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Threading.Core", "src\Samples\Typed\Threading.Core\Threading.Core.fsproj", "{9ECB1F3B-3076-4C98-9ECA-6ACE8C8CAB2A}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Threading", "src\Samples\Typed\Threading\Threading.csproj", "{E6476EE0-71CA-4E5D-BE05-E57825E8AECB}"
+EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Multiselect.Core", "src\Samples\Typed\Multiselect.Core\Multiselect.Core.fsproj", "{984CD20C-0F0A-4458-A3ED-6CC9BE2A1DC8}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Multiselect", "src\Samples\Typed\Multiselect\Multiselect.csproj", "{20D9470B-F39E-4E67-A00D-9714D6E857DE}"
+EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "SubModelSelectedItem.Core", "src\Samples\Typed\SubModelSelectedItem.Core\SubModelSelectedItem.Core.fsproj", "{5D54DD79-59AA-4C85-8ECA-CAC59D86F251}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SubModelSelectedItem", "src\Samples\Typed\SubModelSelectedItem\SubModelSelectedItem.csproj", "{F10A1DEA-545F-4C9F-AFA1-A115726BD415}"
+EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "NewWindow.Core", "src\Samples\Typed\NewWindow.Core\NewWindow.Core.fsproj", "{0330D3D5-5A97-4F6D-AE22-993256ABDFEE}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NewWindow", "src\Samples\Typed\NewWindow\NewWindow.csproj", "{565FC442-5E49-42AD-AC33-14AD14E11961}"
+EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Capabilities.Core", "src\Samples\Typed\Capabilities.Core\Capabilities.Core.fsproj", "{F6BB74C6-971A-44D7-BC10-3B37B74DE264}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Capabilities", "src\Samples\Typed\Capabilities\Capabilities.csproj", "{8B54C3E2-5F8B-471B-83DF-26239DF1C5C8}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Navigation", "src\Samples\Typed\Navigation\Navigation.csproj", "{1C8C9F58-A63E-4ED6-A185-F26F672EEDE0}"
+EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Navigation.Core", "src\Samples\Typed\Navigation.Core\Navigation.Core.fsproj", "{46397A4D-287C-410E-BDE9-1F09D7E64EF0}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Navigation", "src\Samples\Dynamic\Navigation\Navigation.csproj", "{12E352F9-40EE-4C12-BD9D-5B4ABDEB5487}"
+EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Navigation.Core", "src\Samples\Dynamic\Navigation.Core\Navigation.Core.fsproj", "{D6BDA325-9615-42F5-8D42-EEF27F9E7339}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {ED67A1EA-E467-4ABC-B627-8D8A184E20E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {ED67A1EA-E467-4ABC-B627-8D8A184E20E9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {ED67A1EA-E467-4ABC-B627-8D8A184E20E9}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {ED67A1EA-E467-4ABC-B627-8D8A184E20E9}.Debug|x64.Build.0 = Debug|Any CPU
+ {ED67A1EA-E467-4ABC-B627-8D8A184E20E9}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {ED67A1EA-E467-4ABC-B627-8D8A184E20E9}.Debug|x86.Build.0 = Debug|Any CPU
+ {ED67A1EA-E467-4ABC-B627-8D8A184E20E9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {ED67A1EA-E467-4ABC-B627-8D8A184E20E9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {ED67A1EA-E467-4ABC-B627-8D8A184E20E9}.Release|x64.ActiveCfg = Release|Any CPU
+ {ED67A1EA-E467-4ABC-B627-8D8A184E20E9}.Release|x64.Build.0 = Release|Any CPU
+ {ED67A1EA-E467-4ABC-B627-8D8A184E20E9}.Release|x86.ActiveCfg = Release|Any CPU
+ {ED67A1EA-E467-4ABC-B627-8D8A184E20E9}.Release|x86.Build.0 = Release|Any CPU
+ {06B21109-54A2-47FD-B701-C5B572059F88}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {06B21109-54A2-47FD-B701-C5B572059F88}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {06B21109-54A2-47FD-B701-C5B572059F88}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {06B21109-54A2-47FD-B701-C5B572059F88}.Debug|x64.Build.0 = Debug|Any CPU
+ {06B21109-54A2-47FD-B701-C5B572059F88}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {06B21109-54A2-47FD-B701-C5B572059F88}.Debug|x86.Build.0 = Debug|Any CPU
+ {06B21109-54A2-47FD-B701-C5B572059F88}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {06B21109-54A2-47FD-B701-C5B572059F88}.Release|Any CPU.Build.0 = Release|Any CPU
+ {06B21109-54A2-47FD-B701-C5B572059F88}.Release|x64.ActiveCfg = Release|Any CPU
+ {06B21109-54A2-47FD-B701-C5B572059F88}.Release|x64.Build.0 = Release|Any CPU
+ {06B21109-54A2-47FD-B701-C5B572059F88}.Release|x86.ActiveCfg = Release|Any CPU
+ {06B21109-54A2-47FD-B701-C5B572059F88}.Release|x86.Build.0 = Release|Any CPU
+ {603D8145-0AD4-44BE-A730-678B9BE015E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {603D8145-0AD4-44BE-A730-678B9BE015E1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {603D8145-0AD4-44BE-A730-678B9BE015E1}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {603D8145-0AD4-44BE-A730-678B9BE015E1}.Debug|x64.Build.0 = Debug|Any CPU
+ {603D8145-0AD4-44BE-A730-678B9BE015E1}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {603D8145-0AD4-44BE-A730-678B9BE015E1}.Debug|x86.Build.0 = Debug|Any CPU
+ {603D8145-0AD4-44BE-A730-678B9BE015E1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {603D8145-0AD4-44BE-A730-678B9BE015E1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {603D8145-0AD4-44BE-A730-678B9BE015E1}.Release|x64.ActiveCfg = Release|Any CPU
+ {603D8145-0AD4-44BE-A730-678B9BE015E1}.Release|x64.Build.0 = Release|Any CPU
+ {603D8145-0AD4-44BE-A730-678B9BE015E1}.Release|x86.ActiveCfg = Release|Any CPU
+ {603D8145-0AD4-44BE-A730-678B9BE015E1}.Release|x86.Build.0 = Release|Any CPU
+ {F905EB30-20A0-42F6-9035-98C27D1CCF81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F905EB30-20A0-42F6-9035-98C27D1CCF81}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F905EB30-20A0-42F6-9035-98C27D1CCF81}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {F905EB30-20A0-42F6-9035-98C27D1CCF81}.Debug|x64.Build.0 = Debug|Any CPU
+ {F905EB30-20A0-42F6-9035-98C27D1CCF81}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {F905EB30-20A0-42F6-9035-98C27D1CCF81}.Debug|x86.Build.0 = Debug|Any CPU
+ {F905EB30-20A0-42F6-9035-98C27D1CCF81}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F905EB30-20A0-42F6-9035-98C27D1CCF81}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F905EB30-20A0-42F6-9035-98C27D1CCF81}.Release|x64.ActiveCfg = Release|Any CPU
+ {F905EB30-20A0-42F6-9035-98C27D1CCF81}.Release|x64.Build.0 = Release|Any CPU
+ {F905EB30-20A0-42F6-9035-98C27D1CCF81}.Release|x86.ActiveCfg = Release|Any CPU
+ {F905EB30-20A0-42F6-9035-98C27D1CCF81}.Release|x86.Build.0 = Release|Any CPU
+ {EBC55A44-EF7F-4D3E-8E94-8848C00C3059}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {EBC55A44-EF7F-4D3E-8E94-8848C00C3059}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EBC55A44-EF7F-4D3E-8E94-8848C00C3059}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {EBC55A44-EF7F-4D3E-8E94-8848C00C3059}.Debug|x64.Build.0 = Debug|Any CPU
+ {EBC55A44-EF7F-4D3E-8E94-8848C00C3059}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {EBC55A44-EF7F-4D3E-8E94-8848C00C3059}.Debug|x86.Build.0 = Debug|Any CPU
+ {EBC55A44-EF7F-4D3E-8E94-8848C00C3059}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {EBC55A44-EF7F-4D3E-8E94-8848C00C3059}.Release|Any CPU.Build.0 = Release|Any CPU
+ {EBC55A44-EF7F-4D3E-8E94-8848C00C3059}.Release|x64.ActiveCfg = Release|Any CPU
+ {EBC55A44-EF7F-4D3E-8E94-8848C00C3059}.Release|x64.Build.0 = Release|Any CPU
+ {EBC55A44-EF7F-4D3E-8E94-8848C00C3059}.Release|x86.ActiveCfg = Release|Any CPU
+ {EBC55A44-EF7F-4D3E-8E94-8848C00C3059}.Release|x86.Build.0 = Release|Any CPU
+ {CE4B62D4-26FD-4C6A-B1C4-6871A057CE3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CE4B62D4-26FD-4C6A-B1C4-6871A057CE3F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CE4B62D4-26FD-4C6A-B1C4-6871A057CE3F}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {CE4B62D4-26FD-4C6A-B1C4-6871A057CE3F}.Debug|x64.Build.0 = Debug|Any CPU
+ {CE4B62D4-26FD-4C6A-B1C4-6871A057CE3F}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {CE4B62D4-26FD-4C6A-B1C4-6871A057CE3F}.Debug|x86.Build.0 = Debug|Any CPU
+ {CE4B62D4-26FD-4C6A-B1C4-6871A057CE3F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CE4B62D4-26FD-4C6A-B1C4-6871A057CE3F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CE4B62D4-26FD-4C6A-B1C4-6871A057CE3F}.Release|x64.ActiveCfg = Release|Any CPU
+ {CE4B62D4-26FD-4C6A-B1C4-6871A057CE3F}.Release|x64.Build.0 = Release|Any CPU
+ {CE4B62D4-26FD-4C6A-B1C4-6871A057CE3F}.Release|x86.ActiveCfg = Release|Any CPU
+ {CE4B62D4-26FD-4C6A-B1C4-6871A057CE3F}.Release|x86.Build.0 = Release|Any CPU
+ {C1E54278-5FE4-4370-AA98-AB20F037AD6A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C1E54278-5FE4-4370-AA98-AB20F037AD6A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C1E54278-5FE4-4370-AA98-AB20F037AD6A}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {C1E54278-5FE4-4370-AA98-AB20F037AD6A}.Debug|x64.Build.0 = Debug|Any CPU
+ {C1E54278-5FE4-4370-AA98-AB20F037AD6A}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {C1E54278-5FE4-4370-AA98-AB20F037AD6A}.Debug|x86.Build.0 = Debug|Any CPU
+ {C1E54278-5FE4-4370-AA98-AB20F037AD6A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C1E54278-5FE4-4370-AA98-AB20F037AD6A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C1E54278-5FE4-4370-AA98-AB20F037AD6A}.Release|x64.ActiveCfg = Release|Any CPU
+ {C1E54278-5FE4-4370-AA98-AB20F037AD6A}.Release|x64.Build.0 = Release|Any CPU
+ {C1E54278-5FE4-4370-AA98-AB20F037AD6A}.Release|x86.ActiveCfg = Release|Any CPU
+ {C1E54278-5FE4-4370-AA98-AB20F037AD6A}.Release|x86.Build.0 = Release|Any CPU
+ {1982CF30-6D02-45F6-808A-C35A75623343}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1982CF30-6D02-45F6-808A-C35A75623343}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1982CF30-6D02-45F6-808A-C35A75623343}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {1982CF30-6D02-45F6-808A-C35A75623343}.Debug|x64.Build.0 = Debug|Any CPU
+ {1982CF30-6D02-45F6-808A-C35A75623343}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {1982CF30-6D02-45F6-808A-C35A75623343}.Debug|x86.Build.0 = Debug|Any CPU
+ {1982CF30-6D02-45F6-808A-C35A75623343}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1982CF30-6D02-45F6-808A-C35A75623343}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1982CF30-6D02-45F6-808A-C35A75623343}.Release|x64.ActiveCfg = Release|Any CPU
+ {1982CF30-6D02-45F6-808A-C35A75623343}.Release|x64.Build.0 = Release|Any CPU
+ {1982CF30-6D02-45F6-808A-C35A75623343}.Release|x86.ActiveCfg = Release|Any CPU
+ {1982CF30-6D02-45F6-808A-C35A75623343}.Release|x86.Build.0 = Release|Any CPU
+ {ABCDCFAB-666F-40FA-906F-AFC3916E25D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {ABCDCFAB-666F-40FA-906F-AFC3916E25D3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {ABCDCFAB-666F-40FA-906F-AFC3916E25D3}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {ABCDCFAB-666F-40FA-906F-AFC3916E25D3}.Debug|x64.Build.0 = Debug|Any CPU
+ {ABCDCFAB-666F-40FA-906F-AFC3916E25D3}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {ABCDCFAB-666F-40FA-906F-AFC3916E25D3}.Debug|x86.Build.0 = Debug|Any CPU
+ {ABCDCFAB-666F-40FA-906F-AFC3916E25D3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {ABCDCFAB-666F-40FA-906F-AFC3916E25D3}.Release|Any CPU.Build.0 = Release|Any CPU
+ {ABCDCFAB-666F-40FA-906F-AFC3916E25D3}.Release|x64.ActiveCfg = Release|Any CPU
+ {ABCDCFAB-666F-40FA-906F-AFC3916E25D3}.Release|x64.Build.0 = Release|Any CPU
+ {ABCDCFAB-666F-40FA-906F-AFC3916E25D3}.Release|x86.ActiveCfg = Release|Any CPU
+ {ABCDCFAB-666F-40FA-906F-AFC3916E25D3}.Release|x86.Build.0 = Release|Any CPU
+ {69C45190-79DC-4F2F-B0D2-0FD84B77F8A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {69C45190-79DC-4F2F-B0D2-0FD84B77F8A9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {69C45190-79DC-4F2F-B0D2-0FD84B77F8A9}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {69C45190-79DC-4F2F-B0D2-0FD84B77F8A9}.Debug|x64.Build.0 = Debug|Any CPU
+ {69C45190-79DC-4F2F-B0D2-0FD84B77F8A9}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {69C45190-79DC-4F2F-B0D2-0FD84B77F8A9}.Debug|x86.Build.0 = Debug|Any CPU
+ {69C45190-79DC-4F2F-B0D2-0FD84B77F8A9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {69C45190-79DC-4F2F-B0D2-0FD84B77F8A9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {69C45190-79DC-4F2F-B0D2-0FD84B77F8A9}.Release|x64.ActiveCfg = Release|Any CPU
+ {69C45190-79DC-4F2F-B0D2-0FD84B77F8A9}.Release|x64.Build.0 = Release|Any CPU
+ {69C45190-79DC-4F2F-B0D2-0FD84B77F8A9}.Release|x86.ActiveCfg = Release|Any CPU
+ {69C45190-79DC-4F2F-B0D2-0FD84B77F8A9}.Release|x86.Build.0 = Release|Any CPU
+ {FAD8476C-AE4E-4298-9F08-044ED3C0BA08}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {FAD8476C-AE4E-4298-9F08-044ED3C0BA08}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {FAD8476C-AE4E-4298-9F08-044ED3C0BA08}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {FAD8476C-AE4E-4298-9F08-044ED3C0BA08}.Debug|x64.Build.0 = Debug|Any CPU
+ {FAD8476C-AE4E-4298-9F08-044ED3C0BA08}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {FAD8476C-AE4E-4298-9F08-044ED3C0BA08}.Debug|x86.Build.0 = Debug|Any CPU
+ {FAD8476C-AE4E-4298-9F08-044ED3C0BA08}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {FAD8476C-AE4E-4298-9F08-044ED3C0BA08}.Release|Any CPU.Build.0 = Release|Any CPU
+ {FAD8476C-AE4E-4298-9F08-044ED3C0BA08}.Release|x64.ActiveCfg = Release|Any CPU
+ {FAD8476C-AE4E-4298-9F08-044ED3C0BA08}.Release|x64.Build.0 = Release|Any CPU
+ {FAD8476C-AE4E-4298-9F08-044ED3C0BA08}.Release|x86.ActiveCfg = Release|Any CPU
+ {FAD8476C-AE4E-4298-9F08-044ED3C0BA08}.Release|x86.Build.0 = Release|Any CPU
+ {02668954-D273-4140-9FD2-1BDE615A401E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {02668954-D273-4140-9FD2-1BDE615A401E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {02668954-D273-4140-9FD2-1BDE615A401E}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {02668954-D273-4140-9FD2-1BDE615A401E}.Debug|x64.Build.0 = Debug|Any CPU
+ {02668954-D273-4140-9FD2-1BDE615A401E}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {02668954-D273-4140-9FD2-1BDE615A401E}.Debug|x86.Build.0 = Debug|Any CPU
+ {02668954-D273-4140-9FD2-1BDE615A401E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {02668954-D273-4140-9FD2-1BDE615A401E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {02668954-D273-4140-9FD2-1BDE615A401E}.Release|x64.ActiveCfg = Release|Any CPU
+ {02668954-D273-4140-9FD2-1BDE615A401E}.Release|x64.Build.0 = Release|Any CPU
+ {02668954-D273-4140-9FD2-1BDE615A401E}.Release|x86.ActiveCfg = Release|Any CPU
+ {02668954-D273-4140-9FD2-1BDE615A401E}.Release|x86.Build.0 = Release|Any CPU
+ {8565CE50-713C-4336-88AD-EAC732DC921A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8565CE50-713C-4336-88AD-EAC732DC921A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8565CE50-713C-4336-88AD-EAC732DC921A}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {8565CE50-713C-4336-88AD-EAC732DC921A}.Debug|x64.Build.0 = Debug|Any CPU
+ {8565CE50-713C-4336-88AD-EAC732DC921A}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {8565CE50-713C-4336-88AD-EAC732DC921A}.Debug|x86.Build.0 = Debug|Any CPU
+ {8565CE50-713C-4336-88AD-EAC732DC921A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8565CE50-713C-4336-88AD-EAC732DC921A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8565CE50-713C-4336-88AD-EAC732DC921A}.Release|x64.ActiveCfg = Release|Any CPU
+ {8565CE50-713C-4336-88AD-EAC732DC921A}.Release|x64.Build.0 = Release|Any CPU
+ {8565CE50-713C-4336-88AD-EAC732DC921A}.Release|x86.ActiveCfg = Release|Any CPU
+ {8565CE50-713C-4336-88AD-EAC732DC921A}.Release|x86.Build.0 = Release|Any CPU
+ {5D83CDEA-106C-4002-BCC9-1F2BBB63EBE5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5D83CDEA-106C-4002-BCC9-1F2BBB63EBE5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5D83CDEA-106C-4002-BCC9-1F2BBB63EBE5}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {5D83CDEA-106C-4002-BCC9-1F2BBB63EBE5}.Debug|x64.Build.0 = Debug|Any CPU
+ {5D83CDEA-106C-4002-BCC9-1F2BBB63EBE5}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {5D83CDEA-106C-4002-BCC9-1F2BBB63EBE5}.Debug|x86.Build.0 = Debug|Any CPU
+ {5D83CDEA-106C-4002-BCC9-1F2BBB63EBE5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5D83CDEA-106C-4002-BCC9-1F2BBB63EBE5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5D83CDEA-106C-4002-BCC9-1F2BBB63EBE5}.Release|x64.ActiveCfg = Release|Any CPU
+ {5D83CDEA-106C-4002-BCC9-1F2BBB63EBE5}.Release|x64.Build.0 = Release|Any CPU
+ {5D83CDEA-106C-4002-BCC9-1F2BBB63EBE5}.Release|x86.ActiveCfg = Release|Any CPU
+ {5D83CDEA-106C-4002-BCC9-1F2BBB63EBE5}.Release|x86.Build.0 = Release|Any CPU
+ {F884FA9A-2A79-48AB-938C-A34E8A8BCF3E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F884FA9A-2A79-48AB-938C-A34E8A8BCF3E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F884FA9A-2A79-48AB-938C-A34E8A8BCF3E}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {F884FA9A-2A79-48AB-938C-A34E8A8BCF3E}.Debug|x64.Build.0 = Debug|Any CPU
+ {F884FA9A-2A79-48AB-938C-A34E8A8BCF3E}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {F884FA9A-2A79-48AB-938C-A34E8A8BCF3E}.Debug|x86.Build.0 = Debug|Any CPU
+ {F884FA9A-2A79-48AB-938C-A34E8A8BCF3E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F884FA9A-2A79-48AB-938C-A34E8A8BCF3E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F884FA9A-2A79-48AB-938C-A34E8A8BCF3E}.Release|x64.ActiveCfg = Release|Any CPU
+ {F884FA9A-2A79-48AB-938C-A34E8A8BCF3E}.Release|x64.Build.0 = Release|Any CPU
+ {F884FA9A-2A79-48AB-938C-A34E8A8BCF3E}.Release|x86.ActiveCfg = Release|Any CPU
+ {F884FA9A-2A79-48AB-938C-A34E8A8BCF3E}.Release|x86.Build.0 = Release|Any CPU
+ {239809C7-5679-4A85-AC76-254AB83C1DF0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {239809C7-5679-4A85-AC76-254AB83C1DF0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {239809C7-5679-4A85-AC76-254AB83C1DF0}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {239809C7-5679-4A85-AC76-254AB83C1DF0}.Debug|x64.Build.0 = Debug|Any CPU
+ {239809C7-5679-4A85-AC76-254AB83C1DF0}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {239809C7-5679-4A85-AC76-254AB83C1DF0}.Debug|x86.Build.0 = Debug|Any CPU
+ {239809C7-5679-4A85-AC76-254AB83C1DF0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {239809C7-5679-4A85-AC76-254AB83C1DF0}.Release|Any CPU.Build.0 = Release|Any CPU
+ {239809C7-5679-4A85-AC76-254AB83C1DF0}.Release|x64.ActiveCfg = Release|Any CPU
+ {239809C7-5679-4A85-AC76-254AB83C1DF0}.Release|x64.Build.0 = Release|Any CPU
+ {239809C7-5679-4A85-AC76-254AB83C1DF0}.Release|x86.ActiveCfg = Release|Any CPU
+ {239809C7-5679-4A85-AC76-254AB83C1DF0}.Release|x86.Build.0 = Release|Any CPU
+ {7B1997D7-7E82-4094-97FF-F267AB1E628E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7B1997D7-7E82-4094-97FF-F267AB1E628E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7B1997D7-7E82-4094-97FF-F267AB1E628E}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {7B1997D7-7E82-4094-97FF-F267AB1E628E}.Debug|x64.Build.0 = Debug|Any CPU
+ {7B1997D7-7E82-4094-97FF-F267AB1E628E}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {7B1997D7-7E82-4094-97FF-F267AB1E628E}.Debug|x86.Build.0 = Debug|Any CPU
+ {7B1997D7-7E82-4094-97FF-F267AB1E628E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7B1997D7-7E82-4094-97FF-F267AB1E628E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7B1997D7-7E82-4094-97FF-F267AB1E628E}.Release|x64.ActiveCfg = Release|Any CPU
+ {7B1997D7-7E82-4094-97FF-F267AB1E628E}.Release|x64.Build.0 = Release|Any CPU
+ {7B1997D7-7E82-4094-97FF-F267AB1E628E}.Release|x86.ActiveCfg = Release|Any CPU
+ {7B1997D7-7E82-4094-97FF-F267AB1E628E}.Release|x86.Build.0 = Release|Any CPU
+ {464B25D1-BF6F-4F52-B8C1-2F783A4865AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {464B25D1-BF6F-4F52-B8C1-2F783A4865AE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {464B25D1-BF6F-4F52-B8C1-2F783A4865AE}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {464B25D1-BF6F-4F52-B8C1-2F783A4865AE}.Debug|x64.Build.0 = Debug|Any CPU
+ {464B25D1-BF6F-4F52-B8C1-2F783A4865AE}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {464B25D1-BF6F-4F52-B8C1-2F783A4865AE}.Debug|x86.Build.0 = Debug|Any CPU
+ {464B25D1-BF6F-4F52-B8C1-2F783A4865AE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {464B25D1-BF6F-4F52-B8C1-2F783A4865AE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {464B25D1-BF6F-4F52-B8C1-2F783A4865AE}.Release|x64.ActiveCfg = Release|Any CPU
+ {464B25D1-BF6F-4F52-B8C1-2F783A4865AE}.Release|x64.Build.0 = Release|Any CPU
+ {464B25D1-BF6F-4F52-B8C1-2F783A4865AE}.Release|x86.ActiveCfg = Release|Any CPU
+ {464B25D1-BF6F-4F52-B8C1-2F783A4865AE}.Release|x86.Build.0 = Release|Any CPU
+ {74BE5736-B441-4371-AB5B-F1C7A61341C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {74BE5736-B441-4371-AB5B-F1C7A61341C2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {74BE5736-B441-4371-AB5B-F1C7A61341C2}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {74BE5736-B441-4371-AB5B-F1C7A61341C2}.Debug|x64.Build.0 = Debug|Any CPU
+ {74BE5736-B441-4371-AB5B-F1C7A61341C2}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {74BE5736-B441-4371-AB5B-F1C7A61341C2}.Debug|x86.Build.0 = Debug|Any CPU
+ {74BE5736-B441-4371-AB5B-F1C7A61341C2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {74BE5736-B441-4371-AB5B-F1C7A61341C2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {74BE5736-B441-4371-AB5B-F1C7A61341C2}.Release|x64.ActiveCfg = Release|Any CPU
+ {74BE5736-B441-4371-AB5B-F1C7A61341C2}.Release|x64.Build.0 = Release|Any CPU
+ {74BE5736-B441-4371-AB5B-F1C7A61341C2}.Release|x86.ActiveCfg = Release|Any CPU
+ {74BE5736-B441-4371-AB5B-F1C7A61341C2}.Release|x86.Build.0 = Release|Any CPU
+ {54B7ACB9-F2CD-4B30-ABA6-191C37E336A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {54B7ACB9-F2CD-4B30-ABA6-191C37E336A0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {54B7ACB9-F2CD-4B30-ABA6-191C37E336A0}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {54B7ACB9-F2CD-4B30-ABA6-191C37E336A0}.Debug|x64.Build.0 = Debug|Any CPU
+ {54B7ACB9-F2CD-4B30-ABA6-191C37E336A0}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {54B7ACB9-F2CD-4B30-ABA6-191C37E336A0}.Debug|x86.Build.0 = Debug|Any CPU
+ {54B7ACB9-F2CD-4B30-ABA6-191C37E336A0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {54B7ACB9-F2CD-4B30-ABA6-191C37E336A0}.Release|Any CPU.Build.0 = Release|Any CPU
+ {54B7ACB9-F2CD-4B30-ABA6-191C37E336A0}.Release|x64.ActiveCfg = Release|Any CPU
+ {54B7ACB9-F2CD-4B30-ABA6-191C37E336A0}.Release|x64.Build.0 = Release|Any CPU
+ {54B7ACB9-F2CD-4B30-ABA6-191C37E336A0}.Release|x86.ActiveCfg = Release|Any CPU
+ {54B7ACB9-F2CD-4B30-ABA6-191C37E336A0}.Release|x86.Build.0 = Release|Any CPU
+ {F30FD499-8F88-4613-8B15-C06AA649881F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F30FD499-8F88-4613-8B15-C06AA649881F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F30FD499-8F88-4613-8B15-C06AA649881F}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {F30FD499-8F88-4613-8B15-C06AA649881F}.Debug|x64.Build.0 = Debug|Any CPU
+ {F30FD499-8F88-4613-8B15-C06AA649881F}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {F30FD499-8F88-4613-8B15-C06AA649881F}.Debug|x86.Build.0 = Debug|Any CPU
+ {F30FD499-8F88-4613-8B15-C06AA649881F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F30FD499-8F88-4613-8B15-C06AA649881F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F30FD499-8F88-4613-8B15-C06AA649881F}.Release|x64.ActiveCfg = Release|Any CPU
+ {F30FD499-8F88-4613-8B15-C06AA649881F}.Release|x64.Build.0 = Release|Any CPU
+ {F30FD499-8F88-4613-8B15-C06AA649881F}.Release|x86.ActiveCfg = Release|Any CPU
+ {F30FD499-8F88-4613-8B15-C06AA649881F}.Release|x86.Build.0 = Release|Any CPU
+ {220CFDCE-5B3B-49C7-A340-C19B7C28BF79}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {220CFDCE-5B3B-49C7-A340-C19B7C28BF79}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {220CFDCE-5B3B-49C7-A340-C19B7C28BF79}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {220CFDCE-5B3B-49C7-A340-C19B7C28BF79}.Debug|x64.Build.0 = Debug|Any CPU
+ {220CFDCE-5B3B-49C7-A340-C19B7C28BF79}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {220CFDCE-5B3B-49C7-A340-C19B7C28BF79}.Debug|x86.Build.0 = Debug|Any CPU
+ {220CFDCE-5B3B-49C7-A340-C19B7C28BF79}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {220CFDCE-5B3B-49C7-A340-C19B7C28BF79}.Release|Any CPU.Build.0 = Release|Any CPU
+ {220CFDCE-5B3B-49C7-A340-C19B7C28BF79}.Release|x64.ActiveCfg = Release|Any CPU
+ {220CFDCE-5B3B-49C7-A340-C19B7C28BF79}.Release|x64.Build.0 = Release|Any CPU
+ {220CFDCE-5B3B-49C7-A340-C19B7C28BF79}.Release|x86.ActiveCfg = Release|Any CPU
+ {220CFDCE-5B3B-49C7-A340-C19B7C28BF79}.Release|x86.Build.0 = Release|Any CPU
+ {D17190BB-8C0E-4CC3-8644-F0B120EDBD55}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D17190BB-8C0E-4CC3-8644-F0B120EDBD55}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D17190BB-8C0E-4CC3-8644-F0B120EDBD55}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {D17190BB-8C0E-4CC3-8644-F0B120EDBD55}.Debug|x64.Build.0 = Debug|Any CPU
+ {D17190BB-8C0E-4CC3-8644-F0B120EDBD55}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {D17190BB-8C0E-4CC3-8644-F0B120EDBD55}.Debug|x86.Build.0 = Debug|Any CPU
+ {D17190BB-8C0E-4CC3-8644-F0B120EDBD55}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D17190BB-8C0E-4CC3-8644-F0B120EDBD55}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D17190BB-8C0E-4CC3-8644-F0B120EDBD55}.Release|x64.ActiveCfg = Release|Any CPU
+ {D17190BB-8C0E-4CC3-8644-F0B120EDBD55}.Release|x64.Build.0 = Release|Any CPU
+ {D17190BB-8C0E-4CC3-8644-F0B120EDBD55}.Release|x86.ActiveCfg = Release|Any CPU
+ {D17190BB-8C0E-4CC3-8644-F0B120EDBD55}.Release|x86.Build.0 = Release|Any CPU
+ {6A489E6B-CD18-442D-A856-89DDE710B009}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6A489E6B-CD18-442D-A856-89DDE710B009}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6A489E6B-CD18-442D-A856-89DDE710B009}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {6A489E6B-CD18-442D-A856-89DDE710B009}.Debug|x64.Build.0 = Debug|Any CPU
+ {6A489E6B-CD18-442D-A856-89DDE710B009}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {6A489E6B-CD18-442D-A856-89DDE710B009}.Debug|x86.Build.0 = Debug|Any CPU
+ {6A489E6B-CD18-442D-A856-89DDE710B009}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6A489E6B-CD18-442D-A856-89DDE710B009}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6A489E6B-CD18-442D-A856-89DDE710B009}.Release|x64.ActiveCfg = Release|Any CPU
+ {6A489E6B-CD18-442D-A856-89DDE710B009}.Release|x64.Build.0 = Release|Any CPU
+ {6A489E6B-CD18-442D-A856-89DDE710B009}.Release|x86.ActiveCfg = Release|Any CPU
+ {6A489E6B-CD18-442D-A856-89DDE710B009}.Release|x86.Build.0 = Release|Any CPU
+ {E060A98E-50F0-45B0-AB5C-D33A90ADC1E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E060A98E-50F0-45B0-AB5C-D33A90ADC1E6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E060A98E-50F0-45B0-AB5C-D33A90ADC1E6}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {E060A98E-50F0-45B0-AB5C-D33A90ADC1E6}.Debug|x64.Build.0 = Debug|Any CPU
+ {E060A98E-50F0-45B0-AB5C-D33A90ADC1E6}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {E060A98E-50F0-45B0-AB5C-D33A90ADC1E6}.Debug|x86.Build.0 = Debug|Any CPU
+ {E060A98E-50F0-45B0-AB5C-D33A90ADC1E6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E060A98E-50F0-45B0-AB5C-D33A90ADC1E6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E060A98E-50F0-45B0-AB5C-D33A90ADC1E6}.Release|x64.ActiveCfg = Release|Any CPU
+ {E060A98E-50F0-45B0-AB5C-D33A90ADC1E6}.Release|x64.Build.0 = Release|Any CPU
+ {E060A98E-50F0-45B0-AB5C-D33A90ADC1E6}.Release|x86.ActiveCfg = Release|Any CPU
+ {E060A98E-50F0-45B0-AB5C-D33A90ADC1E6}.Release|x86.Build.0 = Release|Any CPU
+ {86B65707-7652-4ED0-8B0A-6C4907E8FF86}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {86B65707-7652-4ED0-8B0A-6C4907E8FF86}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {86B65707-7652-4ED0-8B0A-6C4907E8FF86}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {86B65707-7652-4ED0-8B0A-6C4907E8FF86}.Debug|x64.Build.0 = Debug|Any CPU
+ {86B65707-7652-4ED0-8B0A-6C4907E8FF86}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {86B65707-7652-4ED0-8B0A-6C4907E8FF86}.Debug|x86.Build.0 = Debug|Any CPU
+ {86B65707-7652-4ED0-8B0A-6C4907E8FF86}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {86B65707-7652-4ED0-8B0A-6C4907E8FF86}.Release|Any CPU.Build.0 = Release|Any CPU
+ {86B65707-7652-4ED0-8B0A-6C4907E8FF86}.Release|x64.ActiveCfg = Release|Any CPU
+ {86B65707-7652-4ED0-8B0A-6C4907E8FF86}.Release|x64.Build.0 = Release|Any CPU
+ {86B65707-7652-4ED0-8B0A-6C4907E8FF86}.Release|x86.ActiveCfg = Release|Any CPU
+ {86B65707-7652-4ED0-8B0A-6C4907E8FF86}.Release|x86.Build.0 = Release|Any CPU
+ {686BA896-BD38-490A-88C1-A8D0B48D05E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {686BA896-BD38-490A-88C1-A8D0B48D05E6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {686BA896-BD38-490A-88C1-A8D0B48D05E6}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {686BA896-BD38-490A-88C1-A8D0B48D05E6}.Debug|x64.Build.0 = Debug|Any CPU
+ {686BA896-BD38-490A-88C1-A8D0B48D05E6}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {686BA896-BD38-490A-88C1-A8D0B48D05E6}.Debug|x86.Build.0 = Debug|Any CPU
+ {686BA896-BD38-490A-88C1-A8D0B48D05E6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {686BA896-BD38-490A-88C1-A8D0B48D05E6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {686BA896-BD38-490A-88C1-A8D0B48D05E6}.Release|x64.ActiveCfg = Release|Any CPU
+ {686BA896-BD38-490A-88C1-A8D0B48D05E6}.Release|x64.Build.0 = Release|Any CPU
+ {686BA896-BD38-490A-88C1-A8D0B48D05E6}.Release|x86.ActiveCfg = Release|Any CPU
+ {686BA896-BD38-490A-88C1-A8D0B48D05E6}.Release|x86.Build.0 = Release|Any CPU
+ {A737F617-3916-4AC1-AEEE-69585FF89838}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A737F617-3916-4AC1-AEEE-69585FF89838}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A737F617-3916-4AC1-AEEE-69585FF89838}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {A737F617-3916-4AC1-AEEE-69585FF89838}.Debug|x64.Build.0 = Debug|Any CPU
+ {A737F617-3916-4AC1-AEEE-69585FF89838}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {A737F617-3916-4AC1-AEEE-69585FF89838}.Debug|x86.Build.0 = Debug|Any CPU
+ {A737F617-3916-4AC1-AEEE-69585FF89838}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A737F617-3916-4AC1-AEEE-69585FF89838}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A737F617-3916-4AC1-AEEE-69585FF89838}.Release|x64.ActiveCfg = Release|Any CPU
+ {A737F617-3916-4AC1-AEEE-69585FF89838}.Release|x64.Build.0 = Release|Any CPU
+ {A737F617-3916-4AC1-AEEE-69585FF89838}.Release|x86.ActiveCfg = Release|Any CPU
+ {A737F617-3916-4AC1-AEEE-69585FF89838}.Release|x86.Build.0 = Release|Any CPU
+ {A1B60C73-0FAC-43EF-93F9-4E6AD121F9D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A1B60C73-0FAC-43EF-93F9-4E6AD121F9D4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A1B60C73-0FAC-43EF-93F9-4E6AD121F9D4}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {A1B60C73-0FAC-43EF-93F9-4E6AD121F9D4}.Debug|x64.Build.0 = Debug|Any CPU
+ {A1B60C73-0FAC-43EF-93F9-4E6AD121F9D4}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {A1B60C73-0FAC-43EF-93F9-4E6AD121F9D4}.Debug|x86.Build.0 = Debug|Any CPU
+ {A1B60C73-0FAC-43EF-93F9-4E6AD121F9D4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A1B60C73-0FAC-43EF-93F9-4E6AD121F9D4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A1B60C73-0FAC-43EF-93F9-4E6AD121F9D4}.Release|x64.ActiveCfg = Release|Any CPU
+ {A1B60C73-0FAC-43EF-93F9-4E6AD121F9D4}.Release|x64.Build.0 = Release|Any CPU
+ {A1B60C73-0FAC-43EF-93F9-4E6AD121F9D4}.Release|x86.ActiveCfg = Release|Any CPU
+ {A1B60C73-0FAC-43EF-93F9-4E6AD121F9D4}.Release|x86.Build.0 = Release|Any CPU
+ {335C2372-CB51-4569-B04D-CE8AFC8EC755}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {335C2372-CB51-4569-B04D-CE8AFC8EC755}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {335C2372-CB51-4569-B04D-CE8AFC8EC755}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {335C2372-CB51-4569-B04D-CE8AFC8EC755}.Debug|x64.Build.0 = Debug|Any CPU
+ {335C2372-CB51-4569-B04D-CE8AFC8EC755}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {335C2372-CB51-4569-B04D-CE8AFC8EC755}.Debug|x86.Build.0 = Debug|Any CPU
+ {335C2372-CB51-4569-B04D-CE8AFC8EC755}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {335C2372-CB51-4569-B04D-CE8AFC8EC755}.Release|Any CPU.Build.0 = Release|Any CPU
+ {335C2372-CB51-4569-B04D-CE8AFC8EC755}.Release|x64.ActiveCfg = Release|Any CPU
+ {335C2372-CB51-4569-B04D-CE8AFC8EC755}.Release|x64.Build.0 = Release|Any CPU
+ {335C2372-CB51-4569-B04D-CE8AFC8EC755}.Release|x86.ActiveCfg = Release|Any CPU
+ {335C2372-CB51-4569-B04D-CE8AFC8EC755}.Release|x86.Build.0 = Release|Any CPU
+ {59976F36-A8F0-425E-9620-FA78E68E145F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {59976F36-A8F0-425E-9620-FA78E68E145F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {59976F36-A8F0-425E-9620-FA78E68E145F}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {59976F36-A8F0-425E-9620-FA78E68E145F}.Debug|x64.Build.0 = Debug|Any CPU
+ {59976F36-A8F0-425E-9620-FA78E68E145F}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {59976F36-A8F0-425E-9620-FA78E68E145F}.Debug|x86.Build.0 = Debug|Any CPU
+ {59976F36-A8F0-425E-9620-FA78E68E145F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {59976F36-A8F0-425E-9620-FA78E68E145F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {59976F36-A8F0-425E-9620-FA78E68E145F}.Release|x64.ActiveCfg = Release|Any CPU
+ {59976F36-A8F0-425E-9620-FA78E68E145F}.Release|x64.Build.0 = Release|Any CPU
+ {59976F36-A8F0-425E-9620-FA78E68E145F}.Release|x86.ActiveCfg = Release|Any CPU
+ {59976F36-A8F0-425E-9620-FA78E68E145F}.Release|x86.Build.0 = Release|Any CPU
+ {3D4E6246-667E-4243-B2F3-B9CE12743B8D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3D4E6246-667E-4243-B2F3-B9CE12743B8D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3D4E6246-667E-4243-B2F3-B9CE12743B8D}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {3D4E6246-667E-4243-B2F3-B9CE12743B8D}.Debug|x64.Build.0 = Debug|Any CPU
+ {3D4E6246-667E-4243-B2F3-B9CE12743B8D}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {3D4E6246-667E-4243-B2F3-B9CE12743B8D}.Debug|x86.Build.0 = Debug|Any CPU
+ {3D4E6246-667E-4243-B2F3-B9CE12743B8D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3D4E6246-667E-4243-B2F3-B9CE12743B8D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3D4E6246-667E-4243-B2F3-B9CE12743B8D}.Release|x64.ActiveCfg = Release|Any CPU
+ {3D4E6246-667E-4243-B2F3-B9CE12743B8D}.Release|x64.Build.0 = Release|Any CPU
+ {3D4E6246-667E-4243-B2F3-B9CE12743B8D}.Release|x86.ActiveCfg = Release|Any CPU
+ {3D4E6246-667E-4243-B2F3-B9CE12743B8D}.Release|x86.Build.0 = Release|Any CPU
+ {BBC09072-9D08-454F-90BF-FD754CD60B93}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {BBC09072-9D08-454F-90BF-FD754CD60B93}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BBC09072-9D08-454F-90BF-FD754CD60B93}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {BBC09072-9D08-454F-90BF-FD754CD60B93}.Debug|x64.Build.0 = Debug|Any CPU
+ {BBC09072-9D08-454F-90BF-FD754CD60B93}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {BBC09072-9D08-454F-90BF-FD754CD60B93}.Debug|x86.Build.0 = Debug|Any CPU
+ {BBC09072-9D08-454F-90BF-FD754CD60B93}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BBC09072-9D08-454F-90BF-FD754CD60B93}.Release|Any CPU.Build.0 = Release|Any CPU
+ {BBC09072-9D08-454F-90BF-FD754CD60B93}.Release|x64.ActiveCfg = Release|Any CPU
+ {BBC09072-9D08-454F-90BF-FD754CD60B93}.Release|x64.Build.0 = Release|Any CPU
+ {BBC09072-9D08-454F-90BF-FD754CD60B93}.Release|x86.ActiveCfg = Release|Any CPU
+ {BBC09072-9D08-454F-90BF-FD754CD60B93}.Release|x86.Build.0 = Release|Any CPU
+ {43CF5F69-38A8-493E-960A-0877E248996F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {43CF5F69-38A8-493E-960A-0877E248996F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {43CF5F69-38A8-493E-960A-0877E248996F}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {43CF5F69-38A8-493E-960A-0877E248996F}.Debug|x64.Build.0 = Debug|Any CPU
+ {43CF5F69-38A8-493E-960A-0877E248996F}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {43CF5F69-38A8-493E-960A-0877E248996F}.Debug|x86.Build.0 = Debug|Any CPU
+ {43CF5F69-38A8-493E-960A-0877E248996F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {43CF5F69-38A8-493E-960A-0877E248996F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {43CF5F69-38A8-493E-960A-0877E248996F}.Release|x64.ActiveCfg = Release|Any CPU
+ {43CF5F69-38A8-493E-960A-0877E248996F}.Release|x64.Build.0 = Release|Any CPU
+ {43CF5F69-38A8-493E-960A-0877E248996F}.Release|x86.ActiveCfg = Release|Any CPU
+ {43CF5F69-38A8-493E-960A-0877E248996F}.Release|x86.Build.0 = Release|Any CPU
+ {C7D0416E-9785-40A1-921D-C92A56A9848F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C7D0416E-9785-40A1-921D-C92A56A9848F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C7D0416E-9785-40A1-921D-C92A56A9848F}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {C7D0416E-9785-40A1-921D-C92A56A9848F}.Debug|x64.Build.0 = Debug|Any CPU
+ {C7D0416E-9785-40A1-921D-C92A56A9848F}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {C7D0416E-9785-40A1-921D-C92A56A9848F}.Debug|x86.Build.0 = Debug|Any CPU
+ {C7D0416E-9785-40A1-921D-C92A56A9848F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C7D0416E-9785-40A1-921D-C92A56A9848F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C7D0416E-9785-40A1-921D-C92A56A9848F}.Release|x64.ActiveCfg = Release|Any CPU
+ {C7D0416E-9785-40A1-921D-C92A56A9848F}.Release|x64.Build.0 = Release|Any CPU
+ {C7D0416E-9785-40A1-921D-C92A56A9848F}.Release|x86.ActiveCfg = Release|Any CPU
+ {C7D0416E-9785-40A1-921D-C92A56A9848F}.Release|x86.Build.0 = Release|Any CPU
+ {2A113076-21F0-4F11-A1CF-D0E4A1D14503}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2A113076-21F0-4F11-A1CF-D0E4A1D14503}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2A113076-21F0-4F11-A1CF-D0E4A1D14503}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {2A113076-21F0-4F11-A1CF-D0E4A1D14503}.Debug|x64.Build.0 = Debug|Any CPU
+ {2A113076-21F0-4F11-A1CF-D0E4A1D14503}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {2A113076-21F0-4F11-A1CF-D0E4A1D14503}.Debug|x86.Build.0 = Debug|Any CPU
+ {2A113076-21F0-4F11-A1CF-D0E4A1D14503}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2A113076-21F0-4F11-A1CF-D0E4A1D14503}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2A113076-21F0-4F11-A1CF-D0E4A1D14503}.Release|x64.ActiveCfg = Release|Any CPU
+ {2A113076-21F0-4F11-A1CF-D0E4A1D14503}.Release|x64.Build.0 = Release|Any CPU
+ {2A113076-21F0-4F11-A1CF-D0E4A1D14503}.Release|x86.ActiveCfg = Release|Any CPU
+ {2A113076-21F0-4F11-A1CF-D0E4A1D14503}.Release|x86.Build.0 = Release|Any CPU
+ {A7BA896C-1D7E-4336-BE50-032BF8E62D81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A7BA896C-1D7E-4336-BE50-032BF8E62D81}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A7BA896C-1D7E-4336-BE50-032BF8E62D81}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {A7BA896C-1D7E-4336-BE50-032BF8E62D81}.Debug|x64.Build.0 = Debug|Any CPU
+ {A7BA896C-1D7E-4336-BE50-032BF8E62D81}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {A7BA896C-1D7E-4336-BE50-032BF8E62D81}.Debug|x86.Build.0 = Debug|Any CPU
+ {A7BA896C-1D7E-4336-BE50-032BF8E62D81}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A7BA896C-1D7E-4336-BE50-032BF8E62D81}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A7BA896C-1D7E-4336-BE50-032BF8E62D81}.Release|x64.ActiveCfg = Release|Any CPU
+ {A7BA896C-1D7E-4336-BE50-032BF8E62D81}.Release|x64.Build.0 = Release|Any CPU
+ {A7BA896C-1D7E-4336-BE50-032BF8E62D81}.Release|x86.ActiveCfg = Release|Any CPU
+ {A7BA896C-1D7E-4336-BE50-032BF8E62D81}.Release|x86.Build.0 = Release|Any CPU
+ {F964D761-6542-4A3C-8ADC-41F5B8F77529}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F964D761-6542-4A3C-8ADC-41F5B8F77529}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F964D761-6542-4A3C-8ADC-41F5B8F77529}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {F964D761-6542-4A3C-8ADC-41F5B8F77529}.Debug|x64.Build.0 = Debug|Any CPU
+ {F964D761-6542-4A3C-8ADC-41F5B8F77529}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {F964D761-6542-4A3C-8ADC-41F5B8F77529}.Debug|x86.Build.0 = Debug|Any CPU
+ {F964D761-6542-4A3C-8ADC-41F5B8F77529}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F964D761-6542-4A3C-8ADC-41F5B8F77529}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F964D761-6542-4A3C-8ADC-41F5B8F77529}.Release|x64.ActiveCfg = Release|Any CPU
+ {F964D761-6542-4A3C-8ADC-41F5B8F77529}.Release|x64.Build.0 = Release|Any CPU
+ {F964D761-6542-4A3C-8ADC-41F5B8F77529}.Release|x86.ActiveCfg = Release|Any CPU
+ {F964D761-6542-4A3C-8ADC-41F5B8F77529}.Release|x86.Build.0 = Release|Any CPU
+ {148B26DF-05B6-44D6-890D-8AFBFAD9F7D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {148B26DF-05B6-44D6-890D-8AFBFAD9F7D9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {148B26DF-05B6-44D6-890D-8AFBFAD9F7D9}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {148B26DF-05B6-44D6-890D-8AFBFAD9F7D9}.Debug|x64.Build.0 = Debug|Any CPU
+ {148B26DF-05B6-44D6-890D-8AFBFAD9F7D9}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {148B26DF-05B6-44D6-890D-8AFBFAD9F7D9}.Debug|x86.Build.0 = Debug|Any CPU
+ {148B26DF-05B6-44D6-890D-8AFBFAD9F7D9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {148B26DF-05B6-44D6-890D-8AFBFAD9F7D9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {148B26DF-05B6-44D6-890D-8AFBFAD9F7D9}.Release|x64.ActiveCfg = Release|Any CPU
+ {148B26DF-05B6-44D6-890D-8AFBFAD9F7D9}.Release|x64.Build.0 = Release|Any CPU
+ {148B26DF-05B6-44D6-890D-8AFBFAD9F7D9}.Release|x86.ActiveCfg = Release|Any CPU
+ {148B26DF-05B6-44D6-890D-8AFBFAD9F7D9}.Release|x86.Build.0 = Release|Any CPU
+ {6455B74A-6CB8-4DC3-9329-3D9CBB56173A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6455B74A-6CB8-4DC3-9329-3D9CBB56173A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6455B74A-6CB8-4DC3-9329-3D9CBB56173A}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {6455B74A-6CB8-4DC3-9329-3D9CBB56173A}.Debug|x64.Build.0 = Debug|Any CPU
+ {6455B74A-6CB8-4DC3-9329-3D9CBB56173A}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {6455B74A-6CB8-4DC3-9329-3D9CBB56173A}.Debug|x86.Build.0 = Debug|Any CPU
+ {6455B74A-6CB8-4DC3-9329-3D9CBB56173A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6455B74A-6CB8-4DC3-9329-3D9CBB56173A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6455B74A-6CB8-4DC3-9329-3D9CBB56173A}.Release|x64.ActiveCfg = Release|Any CPU
+ {6455B74A-6CB8-4DC3-9329-3D9CBB56173A}.Release|x64.Build.0 = Release|Any CPU
+ {6455B74A-6CB8-4DC3-9329-3D9CBB56173A}.Release|x86.ActiveCfg = Release|Any CPU
+ {6455B74A-6CB8-4DC3-9329-3D9CBB56173A}.Release|x86.Build.0 = Release|Any CPU
+ {84AF37C1-52A8-4F3F-8F2C-E7394E789523}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {84AF37C1-52A8-4F3F-8F2C-E7394E789523}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {84AF37C1-52A8-4F3F-8F2C-E7394E789523}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {84AF37C1-52A8-4F3F-8F2C-E7394E789523}.Debug|x64.Build.0 = Debug|Any CPU
+ {84AF37C1-52A8-4F3F-8F2C-E7394E789523}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {84AF37C1-52A8-4F3F-8F2C-E7394E789523}.Debug|x86.Build.0 = Debug|Any CPU
+ {84AF37C1-52A8-4F3F-8F2C-E7394E789523}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {84AF37C1-52A8-4F3F-8F2C-E7394E789523}.Release|Any CPU.Build.0 = Release|Any CPU
+ {84AF37C1-52A8-4F3F-8F2C-E7394E789523}.Release|x64.ActiveCfg = Release|Any CPU
+ {84AF37C1-52A8-4F3F-8F2C-E7394E789523}.Release|x64.Build.0 = Release|Any CPU
+ {84AF37C1-52A8-4F3F-8F2C-E7394E789523}.Release|x86.ActiveCfg = Release|Any CPU
+ {84AF37C1-52A8-4F3F-8F2C-E7394E789523}.Release|x86.Build.0 = Release|Any CPU
+ {75110CC0-5A94-4491-BA7E-234ACC39106D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {75110CC0-5A94-4491-BA7E-234ACC39106D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {75110CC0-5A94-4491-BA7E-234ACC39106D}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {75110CC0-5A94-4491-BA7E-234ACC39106D}.Debug|x64.Build.0 = Debug|Any CPU
+ {75110CC0-5A94-4491-BA7E-234ACC39106D}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {75110CC0-5A94-4491-BA7E-234ACC39106D}.Debug|x86.Build.0 = Debug|Any CPU
+ {75110CC0-5A94-4491-BA7E-234ACC39106D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {75110CC0-5A94-4491-BA7E-234ACC39106D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {75110CC0-5A94-4491-BA7E-234ACC39106D}.Release|x64.ActiveCfg = Release|Any CPU
+ {75110CC0-5A94-4491-BA7E-234ACC39106D}.Release|x64.Build.0 = Release|Any CPU
+ {75110CC0-5A94-4491-BA7E-234ACC39106D}.Release|x86.ActiveCfg = Release|Any CPU
+ {75110CC0-5A94-4491-BA7E-234ACC39106D}.Release|x86.Build.0 = Release|Any CPU
+ {8F1471D6-CEFA-4B4F-84C5-FB4B5CB035BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8F1471D6-CEFA-4B4F-84C5-FB4B5CB035BD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8F1471D6-CEFA-4B4F-84C5-FB4B5CB035BD}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {8F1471D6-CEFA-4B4F-84C5-FB4B5CB035BD}.Debug|x64.Build.0 = Debug|Any CPU
+ {8F1471D6-CEFA-4B4F-84C5-FB4B5CB035BD}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {8F1471D6-CEFA-4B4F-84C5-FB4B5CB035BD}.Debug|x86.Build.0 = Debug|Any CPU
+ {8F1471D6-CEFA-4B4F-84C5-FB4B5CB035BD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8F1471D6-CEFA-4B4F-84C5-FB4B5CB035BD}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8F1471D6-CEFA-4B4F-84C5-FB4B5CB035BD}.Release|x64.ActiveCfg = Release|Any CPU
+ {8F1471D6-CEFA-4B4F-84C5-FB4B5CB035BD}.Release|x64.Build.0 = Release|Any CPU
+ {8F1471D6-CEFA-4B4F-84C5-FB4B5CB035BD}.Release|x86.ActiveCfg = Release|Any CPU
+ {8F1471D6-CEFA-4B4F-84C5-FB4B5CB035BD}.Release|x86.Build.0 = Release|Any CPU
+ {A57D4155-3F27-4BDA-900E-1A39B022BE00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A57D4155-3F27-4BDA-900E-1A39B022BE00}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A57D4155-3F27-4BDA-900E-1A39B022BE00}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {A57D4155-3F27-4BDA-900E-1A39B022BE00}.Debug|x64.Build.0 = Debug|Any CPU
+ {A57D4155-3F27-4BDA-900E-1A39B022BE00}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {A57D4155-3F27-4BDA-900E-1A39B022BE00}.Debug|x86.Build.0 = Debug|Any CPU
+ {A57D4155-3F27-4BDA-900E-1A39B022BE00}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A57D4155-3F27-4BDA-900E-1A39B022BE00}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A57D4155-3F27-4BDA-900E-1A39B022BE00}.Release|x64.ActiveCfg = Release|Any CPU
+ {A57D4155-3F27-4BDA-900E-1A39B022BE00}.Release|x64.Build.0 = Release|Any CPU
+ {A57D4155-3F27-4BDA-900E-1A39B022BE00}.Release|x86.ActiveCfg = Release|Any CPU
+ {A57D4155-3F27-4BDA-900E-1A39B022BE00}.Release|x86.Build.0 = Release|Any CPU
+ {86E04393-86E7-4BBF-A9EF-9433B91E4988}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {86E04393-86E7-4BBF-A9EF-9433B91E4988}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {86E04393-86E7-4BBF-A9EF-9433B91E4988}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {86E04393-86E7-4BBF-A9EF-9433B91E4988}.Debug|x64.Build.0 = Debug|Any CPU
+ {86E04393-86E7-4BBF-A9EF-9433B91E4988}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {86E04393-86E7-4BBF-A9EF-9433B91E4988}.Debug|x86.Build.0 = Debug|Any CPU
+ {86E04393-86E7-4BBF-A9EF-9433B91E4988}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {86E04393-86E7-4BBF-A9EF-9433B91E4988}.Release|Any CPU.Build.0 = Release|Any CPU
+ {86E04393-86E7-4BBF-A9EF-9433B91E4988}.Release|x64.ActiveCfg = Release|Any CPU
+ {86E04393-86E7-4BBF-A9EF-9433B91E4988}.Release|x64.Build.0 = Release|Any CPU
+ {86E04393-86E7-4BBF-A9EF-9433B91E4988}.Release|x86.ActiveCfg = Release|Any CPU
+ {86E04393-86E7-4BBF-A9EF-9433B91E4988}.Release|x86.Build.0 = Release|Any CPU
+ {B9C9B2A4-562B-45BB-BD91-CE5027F41C3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B9C9B2A4-562B-45BB-BD91-CE5027F41C3F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B9C9B2A4-562B-45BB-BD91-CE5027F41C3F}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {B9C9B2A4-562B-45BB-BD91-CE5027F41C3F}.Debug|x64.Build.0 = Debug|Any CPU
+ {B9C9B2A4-562B-45BB-BD91-CE5027F41C3F}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {B9C9B2A4-562B-45BB-BD91-CE5027F41C3F}.Debug|x86.Build.0 = Debug|Any CPU
+ {B9C9B2A4-562B-45BB-BD91-CE5027F41C3F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B9C9B2A4-562B-45BB-BD91-CE5027F41C3F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B9C9B2A4-562B-45BB-BD91-CE5027F41C3F}.Release|x64.ActiveCfg = Release|Any CPU
+ {B9C9B2A4-562B-45BB-BD91-CE5027F41C3F}.Release|x64.Build.0 = Release|Any CPU
+ {B9C9B2A4-562B-45BB-BD91-CE5027F41C3F}.Release|x86.ActiveCfg = Release|Any CPU
+ {B9C9B2A4-562B-45BB-BD91-CE5027F41C3F}.Release|x86.Build.0 = Release|Any CPU
+ {435B3E59-C5B2-412A-86DC-795F1E52C223}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {435B3E59-C5B2-412A-86DC-795F1E52C223}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {435B3E59-C5B2-412A-86DC-795F1E52C223}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {435B3E59-C5B2-412A-86DC-795F1E52C223}.Debug|x64.Build.0 = Debug|Any CPU
+ {435B3E59-C5B2-412A-86DC-795F1E52C223}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {435B3E59-C5B2-412A-86DC-795F1E52C223}.Debug|x86.Build.0 = Debug|Any CPU
+ {435B3E59-C5B2-412A-86DC-795F1E52C223}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {435B3E59-C5B2-412A-86DC-795F1E52C223}.Release|Any CPU.Build.0 = Release|Any CPU
+ {435B3E59-C5B2-412A-86DC-795F1E52C223}.Release|x64.ActiveCfg = Release|Any CPU
+ {435B3E59-C5B2-412A-86DC-795F1E52C223}.Release|x64.Build.0 = Release|Any CPU
+ {435B3E59-C5B2-412A-86DC-795F1E52C223}.Release|x86.ActiveCfg = Release|Any CPU
+ {435B3E59-C5B2-412A-86DC-795F1E52C223}.Release|x86.Build.0 = Release|Any CPU
+ {6E4E4453-36C8-42FC-9B8A-C8FEBCC6A643}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6E4E4453-36C8-42FC-9B8A-C8FEBCC6A643}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6E4E4453-36C8-42FC-9B8A-C8FEBCC6A643}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {6E4E4453-36C8-42FC-9B8A-C8FEBCC6A643}.Debug|x64.Build.0 = Debug|Any CPU
+ {6E4E4453-36C8-42FC-9B8A-C8FEBCC6A643}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {6E4E4453-36C8-42FC-9B8A-C8FEBCC6A643}.Debug|x86.Build.0 = Debug|Any CPU
+ {6E4E4453-36C8-42FC-9B8A-C8FEBCC6A643}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6E4E4453-36C8-42FC-9B8A-C8FEBCC6A643}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6E4E4453-36C8-42FC-9B8A-C8FEBCC6A643}.Release|x64.ActiveCfg = Release|Any CPU
+ {6E4E4453-36C8-42FC-9B8A-C8FEBCC6A643}.Release|x64.Build.0 = Release|Any CPU
+ {6E4E4453-36C8-42FC-9B8A-C8FEBCC6A643}.Release|x86.ActiveCfg = Release|Any CPU
+ {6E4E4453-36C8-42FC-9B8A-C8FEBCC6A643}.Release|x86.Build.0 = Release|Any CPU
+ {CAD2DE5B-A359-4E51-8138-A02FDCA8E2A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CAD2DE5B-A359-4E51-8138-A02FDCA8E2A6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CAD2DE5B-A359-4E51-8138-A02FDCA8E2A6}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {CAD2DE5B-A359-4E51-8138-A02FDCA8E2A6}.Debug|x64.Build.0 = Debug|Any CPU
+ {CAD2DE5B-A359-4E51-8138-A02FDCA8E2A6}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {CAD2DE5B-A359-4E51-8138-A02FDCA8E2A6}.Debug|x86.Build.0 = Debug|Any CPU
+ {CAD2DE5B-A359-4E51-8138-A02FDCA8E2A6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CAD2DE5B-A359-4E51-8138-A02FDCA8E2A6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CAD2DE5B-A359-4E51-8138-A02FDCA8E2A6}.Release|x64.ActiveCfg = Release|Any CPU
+ {CAD2DE5B-A359-4E51-8138-A02FDCA8E2A6}.Release|x64.Build.0 = Release|Any CPU
+ {CAD2DE5B-A359-4E51-8138-A02FDCA8E2A6}.Release|x86.ActiveCfg = Release|Any CPU
+ {CAD2DE5B-A359-4E51-8138-A02FDCA8E2A6}.Release|x86.Build.0 = Release|Any CPU
+ {A3D2D2D1-413E-4367-B1D5-0AEEB1551240}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A3D2D2D1-413E-4367-B1D5-0AEEB1551240}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A3D2D2D1-413E-4367-B1D5-0AEEB1551240}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {A3D2D2D1-413E-4367-B1D5-0AEEB1551240}.Debug|x64.Build.0 = Debug|Any CPU
+ {A3D2D2D1-413E-4367-B1D5-0AEEB1551240}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {A3D2D2D1-413E-4367-B1D5-0AEEB1551240}.Debug|x86.Build.0 = Debug|Any CPU
+ {A3D2D2D1-413E-4367-B1D5-0AEEB1551240}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A3D2D2D1-413E-4367-B1D5-0AEEB1551240}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A3D2D2D1-413E-4367-B1D5-0AEEB1551240}.Release|x64.ActiveCfg = Release|Any CPU
+ {A3D2D2D1-413E-4367-B1D5-0AEEB1551240}.Release|x64.Build.0 = Release|Any CPU
+ {A3D2D2D1-413E-4367-B1D5-0AEEB1551240}.Release|x86.ActiveCfg = Release|Any CPU
+ {A3D2D2D1-413E-4367-B1D5-0AEEB1551240}.Release|x86.Build.0 = Release|Any CPU
+ {03FA4F94-962D-4324-B71E-D36E638D13D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {03FA4F94-962D-4324-B71E-D36E638D13D7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {03FA4F94-962D-4324-B71E-D36E638D13D7}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {03FA4F94-962D-4324-B71E-D36E638D13D7}.Debug|x64.Build.0 = Debug|Any CPU
+ {03FA4F94-962D-4324-B71E-D36E638D13D7}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {03FA4F94-962D-4324-B71E-D36E638D13D7}.Debug|x86.Build.0 = Debug|Any CPU
+ {03FA4F94-962D-4324-B71E-D36E638D13D7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {03FA4F94-962D-4324-B71E-D36E638D13D7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {03FA4F94-962D-4324-B71E-D36E638D13D7}.Release|x64.ActiveCfg = Release|Any CPU
+ {03FA4F94-962D-4324-B71E-D36E638D13D7}.Release|x64.Build.0 = Release|Any CPU
+ {03FA4F94-962D-4324-B71E-D36E638D13D7}.Release|x86.ActiveCfg = Release|Any CPU
+ {03FA4F94-962D-4324-B71E-D36E638D13D7}.Release|x86.Build.0 = Release|Any CPU
+ {197B01F2-1974-46A1-9C40-7303E405BFA7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {197B01F2-1974-46A1-9C40-7303E405BFA7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {197B01F2-1974-46A1-9C40-7303E405BFA7}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {197B01F2-1974-46A1-9C40-7303E405BFA7}.Debug|x64.Build.0 = Debug|Any CPU
+ {197B01F2-1974-46A1-9C40-7303E405BFA7}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {197B01F2-1974-46A1-9C40-7303E405BFA7}.Debug|x86.Build.0 = Debug|Any CPU
+ {197B01F2-1974-46A1-9C40-7303E405BFA7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {197B01F2-1974-46A1-9C40-7303E405BFA7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {197B01F2-1974-46A1-9C40-7303E405BFA7}.Release|x64.ActiveCfg = Release|Any CPU
+ {197B01F2-1974-46A1-9C40-7303E405BFA7}.Release|x64.Build.0 = Release|Any CPU
+ {197B01F2-1974-46A1-9C40-7303E405BFA7}.Release|x86.ActiveCfg = Release|Any CPU
+ {197B01F2-1974-46A1-9C40-7303E405BFA7}.Release|x86.Build.0 = Release|Any CPU
+ {0C0BBD9A-1AC7-4B54-B725-03535AE033F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0C0BBD9A-1AC7-4B54-B725-03535AE033F1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0C0BBD9A-1AC7-4B54-B725-03535AE033F1}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {0C0BBD9A-1AC7-4B54-B725-03535AE033F1}.Debug|x64.Build.0 = Debug|Any CPU
+ {0C0BBD9A-1AC7-4B54-B725-03535AE033F1}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {0C0BBD9A-1AC7-4B54-B725-03535AE033F1}.Debug|x86.Build.0 = Debug|Any CPU
+ {0C0BBD9A-1AC7-4B54-B725-03535AE033F1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0C0BBD9A-1AC7-4B54-B725-03535AE033F1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0C0BBD9A-1AC7-4B54-B725-03535AE033F1}.Release|x64.ActiveCfg = Release|Any CPU
+ {0C0BBD9A-1AC7-4B54-B725-03535AE033F1}.Release|x64.Build.0 = Release|Any CPU
+ {0C0BBD9A-1AC7-4B54-B725-03535AE033F1}.Release|x86.ActiveCfg = Release|Any CPU
+ {0C0BBD9A-1AC7-4B54-B725-03535AE033F1}.Release|x86.Build.0 = Release|Any CPU
+ {1093E8A9-77C3-43A0-ACA2-E0857EC19AED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1093E8A9-77C3-43A0-ACA2-E0857EC19AED}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1093E8A9-77C3-43A0-ACA2-E0857EC19AED}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {1093E8A9-77C3-43A0-ACA2-E0857EC19AED}.Debug|x64.Build.0 = Debug|Any CPU
+ {1093E8A9-77C3-43A0-ACA2-E0857EC19AED}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {1093E8A9-77C3-43A0-ACA2-E0857EC19AED}.Debug|x86.Build.0 = Debug|Any CPU
+ {1093E8A9-77C3-43A0-ACA2-E0857EC19AED}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1093E8A9-77C3-43A0-ACA2-E0857EC19AED}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1093E8A9-77C3-43A0-ACA2-E0857EC19AED}.Release|x64.ActiveCfg = Release|Any CPU
+ {1093E8A9-77C3-43A0-ACA2-E0857EC19AED}.Release|x64.Build.0 = Release|Any CPU
+ {1093E8A9-77C3-43A0-ACA2-E0857EC19AED}.Release|x86.ActiveCfg = Release|Any CPU
+ {1093E8A9-77C3-43A0-ACA2-E0857EC19AED}.Release|x86.Build.0 = Release|Any CPU
+ {FC2045E2-A48F-4389-9F72-90EDE8918FCC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {FC2045E2-A48F-4389-9F72-90EDE8918FCC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {FC2045E2-A48F-4389-9F72-90EDE8918FCC}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {FC2045E2-A48F-4389-9F72-90EDE8918FCC}.Debug|x64.Build.0 = Debug|Any CPU
+ {FC2045E2-A48F-4389-9F72-90EDE8918FCC}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {FC2045E2-A48F-4389-9F72-90EDE8918FCC}.Debug|x86.Build.0 = Debug|Any CPU
+ {FC2045E2-A48F-4389-9F72-90EDE8918FCC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {FC2045E2-A48F-4389-9F72-90EDE8918FCC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {FC2045E2-A48F-4389-9F72-90EDE8918FCC}.Release|x64.ActiveCfg = Release|Any CPU
+ {FC2045E2-A48F-4389-9F72-90EDE8918FCC}.Release|x64.Build.0 = Release|Any CPU
+ {FC2045E2-A48F-4389-9F72-90EDE8918FCC}.Release|x86.ActiveCfg = Release|Any CPU
+ {FC2045E2-A48F-4389-9F72-90EDE8918FCC}.Release|x86.Build.0 = Release|Any CPU
+ {9B3D5A8F-16CD-4644-A239-EC2519195C80}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9B3D5A8F-16CD-4644-A239-EC2519195C80}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9B3D5A8F-16CD-4644-A239-EC2519195C80}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {9B3D5A8F-16CD-4644-A239-EC2519195C80}.Debug|x64.Build.0 = Debug|Any CPU
+ {9B3D5A8F-16CD-4644-A239-EC2519195C80}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {9B3D5A8F-16CD-4644-A239-EC2519195C80}.Debug|x86.Build.0 = Debug|Any CPU
+ {9B3D5A8F-16CD-4644-A239-EC2519195C80}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9B3D5A8F-16CD-4644-A239-EC2519195C80}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9B3D5A8F-16CD-4644-A239-EC2519195C80}.Release|x64.ActiveCfg = Release|Any CPU
+ {9B3D5A8F-16CD-4644-A239-EC2519195C80}.Release|x64.Build.0 = Release|Any CPU
+ {9B3D5A8F-16CD-4644-A239-EC2519195C80}.Release|x86.ActiveCfg = Release|Any CPU
+ {9B3D5A8F-16CD-4644-A239-EC2519195C80}.Release|x86.Build.0 = Release|Any CPU
+ {BB30719A-3461-493E-972F-F6CEF534163E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {BB30719A-3461-493E-972F-F6CEF534163E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BB30719A-3461-493E-972F-F6CEF534163E}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {BB30719A-3461-493E-972F-F6CEF534163E}.Debug|x64.Build.0 = Debug|Any CPU
+ {BB30719A-3461-493E-972F-F6CEF534163E}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {BB30719A-3461-493E-972F-F6CEF534163E}.Debug|x86.Build.0 = Debug|Any CPU
+ {BB30719A-3461-493E-972F-F6CEF534163E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BB30719A-3461-493E-972F-F6CEF534163E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {BB30719A-3461-493E-972F-F6CEF534163E}.Release|x64.ActiveCfg = Release|Any CPU
+ {BB30719A-3461-493E-972F-F6CEF534163E}.Release|x64.Build.0 = Release|Any CPU
+ {BB30719A-3461-493E-972F-F6CEF534163E}.Release|x86.ActiveCfg = Release|Any CPU
+ {BB30719A-3461-493E-972F-F6CEF534163E}.Release|x86.Build.0 = Release|Any CPU
+ {9ECB1F3B-3076-4C98-9ECA-6ACE8C8CAB2A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9ECB1F3B-3076-4C98-9ECA-6ACE8C8CAB2A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9ECB1F3B-3076-4C98-9ECA-6ACE8C8CAB2A}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {9ECB1F3B-3076-4C98-9ECA-6ACE8C8CAB2A}.Debug|x64.Build.0 = Debug|Any CPU
+ {9ECB1F3B-3076-4C98-9ECA-6ACE8C8CAB2A}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {9ECB1F3B-3076-4C98-9ECA-6ACE8C8CAB2A}.Debug|x86.Build.0 = Debug|Any CPU
+ {9ECB1F3B-3076-4C98-9ECA-6ACE8C8CAB2A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9ECB1F3B-3076-4C98-9ECA-6ACE8C8CAB2A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9ECB1F3B-3076-4C98-9ECA-6ACE8C8CAB2A}.Release|x64.ActiveCfg = Release|Any CPU
+ {9ECB1F3B-3076-4C98-9ECA-6ACE8C8CAB2A}.Release|x64.Build.0 = Release|Any CPU
+ {9ECB1F3B-3076-4C98-9ECA-6ACE8C8CAB2A}.Release|x86.ActiveCfg = Release|Any CPU
+ {9ECB1F3B-3076-4C98-9ECA-6ACE8C8CAB2A}.Release|x86.Build.0 = Release|Any CPU
+ {E6476EE0-71CA-4E5D-BE05-E57825E8AECB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E6476EE0-71CA-4E5D-BE05-E57825E8AECB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E6476EE0-71CA-4E5D-BE05-E57825E8AECB}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {E6476EE0-71CA-4E5D-BE05-E57825E8AECB}.Debug|x64.Build.0 = Debug|Any CPU
+ {E6476EE0-71CA-4E5D-BE05-E57825E8AECB}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {E6476EE0-71CA-4E5D-BE05-E57825E8AECB}.Debug|x86.Build.0 = Debug|Any CPU
+ {E6476EE0-71CA-4E5D-BE05-E57825E8AECB}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E6476EE0-71CA-4E5D-BE05-E57825E8AECB}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E6476EE0-71CA-4E5D-BE05-E57825E8AECB}.Release|x64.ActiveCfg = Release|Any CPU
+ {E6476EE0-71CA-4E5D-BE05-E57825E8AECB}.Release|x64.Build.0 = Release|Any CPU
+ {E6476EE0-71CA-4E5D-BE05-E57825E8AECB}.Release|x86.ActiveCfg = Release|Any CPU
+ {E6476EE0-71CA-4E5D-BE05-E57825E8AECB}.Release|x86.Build.0 = Release|Any CPU
+ {984CD20C-0F0A-4458-A3ED-6CC9BE2A1DC8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {984CD20C-0F0A-4458-A3ED-6CC9BE2A1DC8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {984CD20C-0F0A-4458-A3ED-6CC9BE2A1DC8}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {984CD20C-0F0A-4458-A3ED-6CC9BE2A1DC8}.Debug|x64.Build.0 = Debug|Any CPU
+ {984CD20C-0F0A-4458-A3ED-6CC9BE2A1DC8}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {984CD20C-0F0A-4458-A3ED-6CC9BE2A1DC8}.Debug|x86.Build.0 = Debug|Any CPU
+ {984CD20C-0F0A-4458-A3ED-6CC9BE2A1DC8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {984CD20C-0F0A-4458-A3ED-6CC9BE2A1DC8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {984CD20C-0F0A-4458-A3ED-6CC9BE2A1DC8}.Release|x64.ActiveCfg = Release|Any CPU
+ {984CD20C-0F0A-4458-A3ED-6CC9BE2A1DC8}.Release|x64.Build.0 = Release|Any CPU
+ {984CD20C-0F0A-4458-A3ED-6CC9BE2A1DC8}.Release|x86.ActiveCfg = Release|Any CPU
+ {984CD20C-0F0A-4458-A3ED-6CC9BE2A1DC8}.Release|x86.Build.0 = Release|Any CPU
+ {20D9470B-F39E-4E67-A00D-9714D6E857DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {20D9470B-F39E-4E67-A00D-9714D6E857DE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {20D9470B-F39E-4E67-A00D-9714D6E857DE}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {20D9470B-F39E-4E67-A00D-9714D6E857DE}.Debug|x64.Build.0 = Debug|Any CPU
+ {20D9470B-F39E-4E67-A00D-9714D6E857DE}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {20D9470B-F39E-4E67-A00D-9714D6E857DE}.Debug|x86.Build.0 = Debug|Any CPU
+ {20D9470B-F39E-4E67-A00D-9714D6E857DE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {20D9470B-F39E-4E67-A00D-9714D6E857DE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {20D9470B-F39E-4E67-A00D-9714D6E857DE}.Release|x64.ActiveCfg = Release|Any CPU
+ {20D9470B-F39E-4E67-A00D-9714D6E857DE}.Release|x64.Build.0 = Release|Any CPU
+ {20D9470B-F39E-4E67-A00D-9714D6E857DE}.Release|x86.ActiveCfg = Release|Any CPU
+ {20D9470B-F39E-4E67-A00D-9714D6E857DE}.Release|x86.Build.0 = Release|Any CPU
+ {5D54DD79-59AA-4C85-8ECA-CAC59D86F251}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5D54DD79-59AA-4C85-8ECA-CAC59D86F251}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5D54DD79-59AA-4C85-8ECA-CAC59D86F251}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {5D54DD79-59AA-4C85-8ECA-CAC59D86F251}.Debug|x64.Build.0 = Debug|Any CPU
+ {5D54DD79-59AA-4C85-8ECA-CAC59D86F251}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {5D54DD79-59AA-4C85-8ECA-CAC59D86F251}.Debug|x86.Build.0 = Debug|Any CPU
+ {5D54DD79-59AA-4C85-8ECA-CAC59D86F251}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5D54DD79-59AA-4C85-8ECA-CAC59D86F251}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5D54DD79-59AA-4C85-8ECA-CAC59D86F251}.Release|x64.ActiveCfg = Release|Any CPU
+ {5D54DD79-59AA-4C85-8ECA-CAC59D86F251}.Release|x64.Build.0 = Release|Any CPU
+ {5D54DD79-59AA-4C85-8ECA-CAC59D86F251}.Release|x86.ActiveCfg = Release|Any CPU
+ {5D54DD79-59AA-4C85-8ECA-CAC59D86F251}.Release|x86.Build.0 = Release|Any CPU
+ {F10A1DEA-545F-4C9F-AFA1-A115726BD415}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F10A1DEA-545F-4C9F-AFA1-A115726BD415}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F10A1DEA-545F-4C9F-AFA1-A115726BD415}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {F10A1DEA-545F-4C9F-AFA1-A115726BD415}.Debug|x64.Build.0 = Debug|Any CPU
+ {F10A1DEA-545F-4C9F-AFA1-A115726BD415}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {F10A1DEA-545F-4C9F-AFA1-A115726BD415}.Debug|x86.Build.0 = Debug|Any CPU
+ {F10A1DEA-545F-4C9F-AFA1-A115726BD415}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F10A1DEA-545F-4C9F-AFA1-A115726BD415}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F10A1DEA-545F-4C9F-AFA1-A115726BD415}.Release|x64.ActiveCfg = Release|Any CPU
+ {F10A1DEA-545F-4C9F-AFA1-A115726BD415}.Release|x64.Build.0 = Release|Any CPU
+ {F10A1DEA-545F-4C9F-AFA1-A115726BD415}.Release|x86.ActiveCfg = Release|Any CPU
+ {F10A1DEA-545F-4C9F-AFA1-A115726BD415}.Release|x86.Build.0 = Release|Any CPU
+ {0330D3D5-5A97-4F6D-AE22-993256ABDFEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0330D3D5-5A97-4F6D-AE22-993256ABDFEE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0330D3D5-5A97-4F6D-AE22-993256ABDFEE}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {0330D3D5-5A97-4F6D-AE22-993256ABDFEE}.Debug|x64.Build.0 = Debug|Any CPU
+ {0330D3D5-5A97-4F6D-AE22-993256ABDFEE}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {0330D3D5-5A97-4F6D-AE22-993256ABDFEE}.Debug|x86.Build.0 = Debug|Any CPU
+ {0330D3D5-5A97-4F6D-AE22-993256ABDFEE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0330D3D5-5A97-4F6D-AE22-993256ABDFEE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0330D3D5-5A97-4F6D-AE22-993256ABDFEE}.Release|x64.ActiveCfg = Release|Any CPU
+ {0330D3D5-5A97-4F6D-AE22-993256ABDFEE}.Release|x64.Build.0 = Release|Any CPU
+ {0330D3D5-5A97-4F6D-AE22-993256ABDFEE}.Release|x86.ActiveCfg = Release|Any CPU
+ {0330D3D5-5A97-4F6D-AE22-993256ABDFEE}.Release|x86.Build.0 = Release|Any CPU
+ {565FC442-5E49-42AD-AC33-14AD14E11961}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {565FC442-5E49-42AD-AC33-14AD14E11961}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {565FC442-5E49-42AD-AC33-14AD14E11961}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {565FC442-5E49-42AD-AC33-14AD14E11961}.Debug|x64.Build.0 = Debug|Any CPU
+ {565FC442-5E49-42AD-AC33-14AD14E11961}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {565FC442-5E49-42AD-AC33-14AD14E11961}.Debug|x86.Build.0 = Debug|Any CPU
+ {565FC442-5E49-42AD-AC33-14AD14E11961}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {565FC442-5E49-42AD-AC33-14AD14E11961}.Release|Any CPU.Build.0 = Release|Any CPU
+ {565FC442-5E49-42AD-AC33-14AD14E11961}.Release|x64.ActiveCfg = Release|Any CPU
+ {565FC442-5E49-42AD-AC33-14AD14E11961}.Release|x64.Build.0 = Release|Any CPU
+ {565FC442-5E49-42AD-AC33-14AD14E11961}.Release|x86.ActiveCfg = Release|Any CPU
+ {565FC442-5E49-42AD-AC33-14AD14E11961}.Release|x86.Build.0 = Release|Any CPU
+ {F6BB74C6-971A-44D7-BC10-3B37B74DE264}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F6BB74C6-971A-44D7-BC10-3B37B74DE264}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F6BB74C6-971A-44D7-BC10-3B37B74DE264}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {F6BB74C6-971A-44D7-BC10-3B37B74DE264}.Debug|x64.Build.0 = Debug|Any CPU
+ {F6BB74C6-971A-44D7-BC10-3B37B74DE264}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {F6BB74C6-971A-44D7-BC10-3B37B74DE264}.Debug|x86.Build.0 = Debug|Any CPU
+ {F6BB74C6-971A-44D7-BC10-3B37B74DE264}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F6BB74C6-971A-44D7-BC10-3B37B74DE264}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F6BB74C6-971A-44D7-BC10-3B37B74DE264}.Release|x64.ActiveCfg = Release|Any CPU
+ {F6BB74C6-971A-44D7-BC10-3B37B74DE264}.Release|x64.Build.0 = Release|Any CPU
+ {F6BB74C6-971A-44D7-BC10-3B37B74DE264}.Release|x86.ActiveCfg = Release|Any CPU
+ {F6BB74C6-971A-44D7-BC10-3B37B74DE264}.Release|x86.Build.0 = Release|Any CPU
+ {8B54C3E2-5F8B-471B-83DF-26239DF1C5C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8B54C3E2-5F8B-471B-83DF-26239DF1C5C8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8B54C3E2-5F8B-471B-83DF-26239DF1C5C8}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {8B54C3E2-5F8B-471B-83DF-26239DF1C5C8}.Debug|x64.Build.0 = Debug|Any CPU
+ {8B54C3E2-5F8B-471B-83DF-26239DF1C5C8}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {8B54C3E2-5F8B-471B-83DF-26239DF1C5C8}.Debug|x86.Build.0 = Debug|Any CPU
+ {8B54C3E2-5F8B-471B-83DF-26239DF1C5C8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8B54C3E2-5F8B-471B-83DF-26239DF1C5C8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8B54C3E2-5F8B-471B-83DF-26239DF1C5C8}.Release|x64.ActiveCfg = Release|Any CPU
+ {8B54C3E2-5F8B-471B-83DF-26239DF1C5C8}.Release|x64.Build.0 = Release|Any CPU
+ {8B54C3E2-5F8B-471B-83DF-26239DF1C5C8}.Release|x86.ActiveCfg = Release|Any CPU
+ {8B54C3E2-5F8B-471B-83DF-26239DF1C5C8}.Release|x86.Build.0 = Release|Any CPU
+ {1C8C9F58-A63E-4ED6-A185-F26F672EEDE0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1C8C9F58-A63E-4ED6-A185-F26F672EEDE0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1C8C9F58-A63E-4ED6-A185-F26F672EEDE0}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {1C8C9F58-A63E-4ED6-A185-F26F672EEDE0}.Debug|x64.Build.0 = Debug|Any CPU
+ {1C8C9F58-A63E-4ED6-A185-F26F672EEDE0}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {1C8C9F58-A63E-4ED6-A185-F26F672EEDE0}.Debug|x86.Build.0 = Debug|Any CPU
+ {1C8C9F58-A63E-4ED6-A185-F26F672EEDE0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1C8C9F58-A63E-4ED6-A185-F26F672EEDE0}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1C8C9F58-A63E-4ED6-A185-F26F672EEDE0}.Release|x64.ActiveCfg = Release|Any CPU
+ {1C8C9F58-A63E-4ED6-A185-F26F672EEDE0}.Release|x64.Build.0 = Release|Any CPU
+ {1C8C9F58-A63E-4ED6-A185-F26F672EEDE0}.Release|x86.ActiveCfg = Release|Any CPU
+ {1C8C9F58-A63E-4ED6-A185-F26F672EEDE0}.Release|x86.Build.0 = Release|Any CPU
+ {46397A4D-287C-410E-BDE9-1F09D7E64EF0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {46397A4D-287C-410E-BDE9-1F09D7E64EF0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {46397A4D-287C-410E-BDE9-1F09D7E64EF0}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {46397A4D-287C-410E-BDE9-1F09D7E64EF0}.Debug|x64.Build.0 = Debug|Any CPU
+ {46397A4D-287C-410E-BDE9-1F09D7E64EF0}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {46397A4D-287C-410E-BDE9-1F09D7E64EF0}.Debug|x86.Build.0 = Debug|Any CPU
+ {46397A4D-287C-410E-BDE9-1F09D7E64EF0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {46397A4D-287C-410E-BDE9-1F09D7E64EF0}.Release|Any CPU.Build.0 = Release|Any CPU
+ {46397A4D-287C-410E-BDE9-1F09D7E64EF0}.Release|x64.ActiveCfg = Release|Any CPU
+ {46397A4D-287C-410E-BDE9-1F09D7E64EF0}.Release|x64.Build.0 = Release|Any CPU
+ {46397A4D-287C-410E-BDE9-1F09D7E64EF0}.Release|x86.ActiveCfg = Release|Any CPU
+ {46397A4D-287C-410E-BDE9-1F09D7E64EF0}.Release|x86.Build.0 = Release|Any CPU
+ {12E352F9-40EE-4C12-BD9D-5B4ABDEB5487}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {12E352F9-40EE-4C12-BD9D-5B4ABDEB5487}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {12E352F9-40EE-4C12-BD9D-5B4ABDEB5487}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {12E352F9-40EE-4C12-BD9D-5B4ABDEB5487}.Debug|x64.Build.0 = Debug|Any CPU
+ {12E352F9-40EE-4C12-BD9D-5B4ABDEB5487}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {12E352F9-40EE-4C12-BD9D-5B4ABDEB5487}.Debug|x86.Build.0 = Debug|Any CPU
+ {12E352F9-40EE-4C12-BD9D-5B4ABDEB5487}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {12E352F9-40EE-4C12-BD9D-5B4ABDEB5487}.Release|Any CPU.Build.0 = Release|Any CPU
+ {12E352F9-40EE-4C12-BD9D-5B4ABDEB5487}.Release|x64.ActiveCfg = Release|Any CPU
+ {12E352F9-40EE-4C12-BD9D-5B4ABDEB5487}.Release|x64.Build.0 = Release|Any CPU
+ {12E352F9-40EE-4C12-BD9D-5B4ABDEB5487}.Release|x86.ActiveCfg = Release|Any CPU
+ {12E352F9-40EE-4C12-BD9D-5B4ABDEB5487}.Release|x86.Build.0 = Release|Any CPU
+ {D6BDA325-9615-42F5-8D42-EEF27F9E7339}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D6BDA325-9615-42F5-8D42-EEF27F9E7339}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D6BDA325-9615-42F5-8D42-EEF27F9E7339}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {D6BDA325-9615-42F5-8D42-EEF27F9E7339}.Debug|x64.Build.0 = Debug|Any CPU
+ {D6BDA325-9615-42F5-8D42-EEF27F9E7339}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {D6BDA325-9615-42F5-8D42-EEF27F9E7339}.Debug|x86.Build.0 = Debug|Any CPU
+ {D6BDA325-9615-42F5-8D42-EEF27F9E7339}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D6BDA325-9615-42F5-8D42-EEF27F9E7339}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D6BDA325-9615-42F5-8D42-EEF27F9E7339}.Release|x64.ActiveCfg = Release|Any CPU
+ {D6BDA325-9615-42F5-8D42-EEF27F9E7339}.Release|x64.Build.0 = Release|Any CPU
+ {D6BDA325-9615-42F5-8D42-EEF27F9E7339}.Release|x86.ActiveCfg = Release|Any CPU
+ {D6BDA325-9615-42F5-8D42-EEF27F9E7339}.Release|x86.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {ED67A1EA-E467-4ABC-B627-8D8A184E20E9} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
+ {06B21109-54A2-47FD-B701-C5B572059F88} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
+ {603D8145-0AD4-44BE-A730-678B9BE015E1} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
+ {D1B4CF99-7947-DD6C-81D0-5B5B3989814C} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
+ {D93CAD63-3252-5A7F-9C37-FCA27F08B606} = {D1B4CF99-7947-DD6C-81D0-5B5B3989814C}
+ {F905EB30-20A0-42F6-9035-98C27D1CCF81} = {D93CAD63-3252-5A7F-9C37-FCA27F08B606}
+ {EBC55A44-EF7F-4D3E-8E94-8848C00C3059} = {D93CAD63-3252-5A7F-9C37-FCA27F08B606}
+ {CE4B62D4-26FD-4C6A-B1C4-6871A057CE3F} = {D93CAD63-3252-5A7F-9C37-FCA27F08B606}
+ {C1E54278-5FE4-4370-AA98-AB20F037AD6A} = {D93CAD63-3252-5A7F-9C37-FCA27F08B606}
+ {1982CF30-6D02-45F6-808A-C35A75623343} = {D93CAD63-3252-5A7F-9C37-FCA27F08B606}
+ {ABCDCFAB-666F-40FA-906F-AFC3916E25D3} = {D93CAD63-3252-5A7F-9C37-FCA27F08B606}
+ {69C45190-79DC-4F2F-B0D2-0FD84B77F8A9} = {D93CAD63-3252-5A7F-9C37-FCA27F08B606}
+ {FAD8476C-AE4E-4298-9F08-044ED3C0BA08} = {D93CAD63-3252-5A7F-9C37-FCA27F08B606}
+ {02668954-D273-4140-9FD2-1BDE615A401E} = {D93CAD63-3252-5A7F-9C37-FCA27F08B606}
+ {8565CE50-713C-4336-88AD-EAC732DC921A} = {D93CAD63-3252-5A7F-9C37-FCA27F08B606}
+ {5D83CDEA-106C-4002-BCC9-1F2BBB63EBE5} = {D93CAD63-3252-5A7F-9C37-FCA27F08B606}
+ {F884FA9A-2A79-48AB-938C-A34E8A8BCF3E} = {D93CAD63-3252-5A7F-9C37-FCA27F08B606}
+ {239809C7-5679-4A85-AC76-254AB83C1DF0} = {D93CAD63-3252-5A7F-9C37-FCA27F08B606}
+ {7B1997D7-7E82-4094-97FF-F267AB1E628E} = {D93CAD63-3252-5A7F-9C37-FCA27F08B606}
+ {464B25D1-BF6F-4F52-B8C1-2F783A4865AE} = {D93CAD63-3252-5A7F-9C37-FCA27F08B606}
+ {74BE5736-B441-4371-AB5B-F1C7A61341C2} = {D93CAD63-3252-5A7F-9C37-FCA27F08B606}
+ {54B7ACB9-F2CD-4B30-ABA6-191C37E336A0} = {D93CAD63-3252-5A7F-9C37-FCA27F08B606}
+ {F30FD499-8F88-4613-8B15-C06AA649881F} = {D93CAD63-3252-5A7F-9C37-FCA27F08B606}
+ {220CFDCE-5B3B-49C7-A340-C19B7C28BF79} = {D93CAD63-3252-5A7F-9C37-FCA27F08B606}
+ {D17190BB-8C0E-4CC3-8644-F0B120EDBD55} = {D93CAD63-3252-5A7F-9C37-FCA27F08B606}
+ {6A489E6B-CD18-442D-A856-89DDE710B009} = {D93CAD63-3252-5A7F-9C37-FCA27F08B606}
+ {E060A98E-50F0-45B0-AB5C-D33A90ADC1E6} = {D93CAD63-3252-5A7F-9C37-FCA27F08B606}
+ {86B65707-7652-4ED0-8B0A-6C4907E8FF86} = {D93CAD63-3252-5A7F-9C37-FCA27F08B606}
+ {686BA896-BD38-490A-88C1-A8D0B48D05E6} = {D93CAD63-3252-5A7F-9C37-FCA27F08B606}
+ {A737F617-3916-4AC1-AEEE-69585FF89838} = {D93CAD63-3252-5A7F-9C37-FCA27F08B606}
+ {A1B60C73-0FAC-43EF-93F9-4E6AD121F9D4} = {D93CAD63-3252-5A7F-9C37-FCA27F08B606}
+ {335C2372-CB51-4569-B04D-CE8AFC8EC755} = {D93CAD63-3252-5A7F-9C37-FCA27F08B606}
+ {59976F36-A8F0-425E-9620-FA78E68E145F} = {D93CAD63-3252-5A7F-9C37-FCA27F08B606}
+ {3D4E6246-667E-4243-B2F3-B9CE12743B8D} = {D93CAD63-3252-5A7F-9C37-FCA27F08B606}
+ {BBC09072-9D08-454F-90BF-FD754CD60B93} = {D93CAD63-3252-5A7F-9C37-FCA27F08B606}
+ {43CF5F69-38A8-493E-960A-0877E248996F} = {D93CAD63-3252-5A7F-9C37-FCA27F08B606}
+ {C7D0416E-9785-40A1-921D-C92A56A9848F} = {D93CAD63-3252-5A7F-9C37-FCA27F08B606}
+ {1740D47D-DA94-6415-017E-48203BA1C52C} = {D1B4CF99-7947-DD6C-81D0-5B5B3989814C}
+ {2A113076-21F0-4F11-A1CF-D0E4A1D14503} = {1740D47D-DA94-6415-017E-48203BA1C52C}
+ {A7BA896C-1D7E-4336-BE50-032BF8E62D81} = {1740D47D-DA94-6415-017E-48203BA1C52C}
+ {F964D761-6542-4A3C-8ADC-41F5B8F77529} = {1740D47D-DA94-6415-017E-48203BA1C52C}
+ {148B26DF-05B6-44D6-890D-8AFBFAD9F7D9} = {1740D47D-DA94-6415-017E-48203BA1C52C}
+ {6455B74A-6CB8-4DC3-9329-3D9CBB56173A} = {1740D47D-DA94-6415-017E-48203BA1C52C}
+ {84AF37C1-52A8-4F3F-8F2C-E7394E789523} = {1740D47D-DA94-6415-017E-48203BA1C52C}
+ {75110CC0-5A94-4491-BA7E-234ACC39106D} = {1740D47D-DA94-6415-017E-48203BA1C52C}
+ {8F1471D6-CEFA-4B4F-84C5-FB4B5CB035BD} = {1740D47D-DA94-6415-017E-48203BA1C52C}
+ {A57D4155-3F27-4BDA-900E-1A39B022BE00} = {1740D47D-DA94-6415-017E-48203BA1C52C}
+ {86E04393-86E7-4BBF-A9EF-9433B91E4988} = {1740D47D-DA94-6415-017E-48203BA1C52C}
+ {B9C9B2A4-562B-45BB-BD91-CE5027F41C3F} = {1740D47D-DA94-6415-017E-48203BA1C52C}
+ {435B3E59-C5B2-412A-86DC-795F1E52C223} = {1740D47D-DA94-6415-017E-48203BA1C52C}
+ {6E4E4453-36C8-42FC-9B8A-C8FEBCC6A643} = {1740D47D-DA94-6415-017E-48203BA1C52C}
+ {CAD2DE5B-A359-4E51-8138-A02FDCA8E2A6} = {1740D47D-DA94-6415-017E-48203BA1C52C}
+ {A3D2D2D1-413E-4367-B1D5-0AEEB1551240} = {1740D47D-DA94-6415-017E-48203BA1C52C}
+ {03FA4F94-962D-4324-B71E-D36E638D13D7} = {1740D47D-DA94-6415-017E-48203BA1C52C}
+ {197B01F2-1974-46A1-9C40-7303E405BFA7} = {1740D47D-DA94-6415-017E-48203BA1C52C}
+ {0C0BBD9A-1AC7-4B54-B725-03535AE033F1} = {1740D47D-DA94-6415-017E-48203BA1C52C}
+ {1093E8A9-77C3-43A0-ACA2-E0857EC19AED} = {1740D47D-DA94-6415-017E-48203BA1C52C}
+ {FC2045E2-A48F-4389-9F72-90EDE8918FCC} = {1740D47D-DA94-6415-017E-48203BA1C52C}
+ {9B3D5A8F-16CD-4644-A239-EC2519195C80} = {1740D47D-DA94-6415-017E-48203BA1C52C}
+ {BB30719A-3461-493E-972F-F6CEF534163E} = {1740D47D-DA94-6415-017E-48203BA1C52C}
+ {9ECB1F3B-3076-4C98-9ECA-6ACE8C8CAB2A} = {1740D47D-DA94-6415-017E-48203BA1C52C}
+ {E6476EE0-71CA-4E5D-BE05-E57825E8AECB} = {1740D47D-DA94-6415-017E-48203BA1C52C}
+ {984CD20C-0F0A-4458-A3ED-6CC9BE2A1DC8} = {1740D47D-DA94-6415-017E-48203BA1C52C}
+ {20D9470B-F39E-4E67-A00D-9714D6E857DE} = {1740D47D-DA94-6415-017E-48203BA1C52C}
+ {5D54DD79-59AA-4C85-8ECA-CAC59D86F251} = {1740D47D-DA94-6415-017E-48203BA1C52C}
+ {F10A1DEA-545F-4C9F-AFA1-A115726BD415} = {1740D47D-DA94-6415-017E-48203BA1C52C}
+ {0330D3D5-5A97-4F6D-AE22-993256ABDFEE} = {1740D47D-DA94-6415-017E-48203BA1C52C}
+ {565FC442-5E49-42AD-AC33-14AD14E11961} = {1740D47D-DA94-6415-017E-48203BA1C52C}
+ {F6BB74C6-971A-44D7-BC10-3B37B74DE264} = {1740D47D-DA94-6415-017E-48203BA1C52C}
+ {8B54C3E2-5F8B-471B-83DF-26239DF1C5C8} = {1740D47D-DA94-6415-017E-48203BA1C52C}
+ {1C8C9F58-A63E-4ED6-A185-F26F672EEDE0} = {1740D47D-DA94-6415-017E-48203BA1C52C}
+ {46397A4D-287C-410E-BDE9-1F09D7E64EF0} = {1740D47D-DA94-6415-017E-48203BA1C52C}
+ {12E352F9-40EE-4C12-BD9D-5B4ABDEB5487} = {D93CAD63-3252-5A7F-9C37-FCA27F08B606}
+ {D6BDA325-9615-42F5-8D42-EEF27F9E7339} = {D93CAD63-3252-5A7F-9C37-FCA27F08B606}
+ EndGlobalSection
+EndGlobal
diff --git a/Elmish.WPF.slnx b/Elmish.WPF.slnx
new file mode 100644
index 00000000..f9bc0077
--- /dev/null
+++ b/Elmish.WPF.slnx
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index e3497c09..afe21462 100644
--- a/README.md
+++ b/README.md
@@ -5,13 +5,25 @@ WPF done the Elmish Way
[](https://www.nuget.org/packages/Elmish.WPF) [](https://www.nuget.org/packages/Elmish.WPF) [](https://github.com/elmish/Elmish.WPF/actions/workflows/continuous_integration.yml)
-**The good parts of MVVM (the data bindings) with the simplicity and robustness of an MVU architecture for the rest of your app. Never write an overly-complex ViewModel class again!**
+**The good parts of MVVM (the data bindings) with the simplicity and robustness of an MVU architecture for the rest of
+your app. Choose between dynamic bindings for flexibility or statically-typed ViewModels for safety and tooling support!
+**
### Elevator pitch
-Elmish.WPF is a **production-ready** library that allows you to write WPF apps with the robust, simple, well-known, and battle-tested MVU architecture, while still allowing you to use all your XAML knowledge and tooling to create UIs.
+Elmish.WPF is a **production-ready** library that allows you to write WPF apps with the robust, simple, well-known, and
+battle-tested MVU architecture, while still allowing you to use all your XAML knowledge and tooling to create UIs.
-Some benefits of MVU you’ll get with Elmish.WPF include:
+**Two powerful approaches in one library:**
+
+- **Dynamic Bindings**: Flexible, functional approach with runtime binding definitions
+- **Statically-Typed ViewModels**: Compile-time safety with full IntelliSense and design-time support
+
+Choose **Dynamic Bindings** for rapid prototyping, simple apps, or when you prefer functional composition. Choose *
+*Statically-Typed ViewModels** for large applications, when working with designers, or when you want compile-time
+validation and superior tooling support.
+
+Some benefits of MVU you'll get with Elmish.WPF include:
* Simple-to-understand, unidirectional data flow
* Single source of truth for all the state in your app
@@ -22,7 +34,10 @@ Some benefits of MVU you’ll get with Elmish.WPF include:
* Simple optimization
* 78% more rockets 🚀
-Even with static views, your central model/update code can follow an idiomatic Elmish/MVU architecture. You could, if you wanted, use the same model/update code to implement an app using a dynamic UI library such as [Fabulous](https://github.com/fsprojects/Fabulous) or [Fable.React](https://github.com/fable-compiler/fable-react), by just rewriting the “U” part of MVU.
+Even with static views, your central model/update code can follow an idiomatic Elmish/MVU architecture. You could, if
+you wanted, use the same model/update code to implement an app using a dynamic UI library such
+as [Fabulous](https://github.com/fsprojects/Fabulous) or [Fable.React](https://github.com/fable-compiler/fable-react),
+by just rewriting the “U” part of MVU.
**Static XAML views is a feature, not a limitation. See the FAQ for several unique benefits to this approach!**
@@ -34,28 +49,47 @@ Big thanks to [@MrMattSim](https://github.com/MrMattSim) for the wonderful logo!
[](https://www.jetbrains.com/?from=Elmish.WPF)
-Thanks to JetBrains for sponsoring Elmish.WPF with [OSS licenses](https://www.jetbrains.com/community/opensource/#support)!
+Thanks to JetBrains for sponsoring Elmish.WPF
+with [OSS licenses](https://www.jetbrains.com/community/opensource/#support)!
Recommended resources
---------------------
-* The [Elmish.WPF tutorial](https://github.com/elmish/Elmish.WPF/blob/master/TUTORIAL.md) explains how to use Elmish.WPF, starting with general Elmish/MVU concepts and ending with complex optimizations.
-* The [Elmish.WPF binding reference](https://github.com/elmish/Elmish.WPF/blob/master/REFERENCE.md) explains Elmish.WPF's bindings and library functions for modifying bindings.
+* The [Elmish.WPF tutorial](https://github.com/elmish/Elmish.WPF/blob/master/TUTORIAL.md) explains how to use
+ Elmish.WPF, starting with general Elmish/MVU concepts and ending with complex optimizations.
+* The [Elmish.WPF binding reference](https://github.com/elmish/Elmish.WPF/blob/master/REFERENCE.md) explains
+ Elmish.WPF's bindings and library functions for modifying bindings.
* The [Elmish docs site](https://elmish.github.io/elmish) also explains the general MVU architecture and principles.
-* The [Elmish.WPF samples](https://github.com/elmish/Elmish.WPF/tree/master/src/Samples) provide many concrete usage examples.
+* The [Elmish.WPF samples](https://github.com/elmish/Elmish.WPF/tree/master/src/Samples) provide many concrete usage
+ examples, organized into [Dynamic](https://github.com/elmish/Elmish.WPF/tree/master/src/Samples/Dynamic)
+ and [Typed](https://github.com/elmish/Elmish.WPF/tree/master/src/Samples/Typed) approaches.
* Blog posts:
- * [Getting Elmish in .NET with Elmish.WPF](https://medium.com/swlh/getting-elmish-in-net-with-elmish-wpf-cd44e3eddc27) ("getting started" guide by Matt Eland)
-* Elm resources may also provide some guidance, but note that not everything is relevant. A significant difference between “normal” Elm architecture and Elmish.WPF is that in Elmish.WPF, the views are statically defined using XAML, and the “view” function does not render views, but set up bindings. See the [tutorial](https://github.com/elmish/Elmish.WPF/blob/master/TUTORIAL.md) for details.
- * [Official Elm guide](https://guide.elm-lang.org)
- * Two talks: [Summarising Elm scaling strategy](https://dev.to/elmupdate/summarising-elm-scaling-strategy-1bjn)
- * Reddit: [Resources regarding scaling Elm apps](https://www.reddit.com/r/elm/comments/65s0g4/resources_regarding_scaling_elm_apps/)
- * Reddit: [How to structure Elm with multiple models](https://www.reddit.com/r/elm/comments/5jd2xn/how_to_structure_elm_with_multiple_models/dbuu0m4/)
- * Reddit: [Elm Architecture with a Redux-like store pattern](https://www.reddit.com/r/elm/comments/5xdl9z/elm_architecture_with_a_reduxlike_store_pattern/)
+ * [Getting Elmish in .NET with Elmish.WPF](https://medium.com/swlh/getting-elmish-in-net-with-elmish-wpf-cd44e3eddc27) ("
+ getting started" guide by Matt Eland)
+* Elm resources may also provide some guidance, but note that not everything is relevant. A significant difference
+ between “normal” Elm architecture and Elmish.WPF is that in Elmish.WPF, the views are statically defined using XAML,
+ and the “view” function does not render views, but set up bindings. See
+ the [tutorial](https://github.com/elmish/Elmish.WPF/blob/master/TUTORIAL.md) for details.
+ * [Official Elm guide](https://guide.elm-lang.org)
+ * Two talks: [Summarising Elm scaling strategy](https://dev.to/elmupdate/summarising-elm-scaling-strategy-1bjn)
+ *
+ Reddit: [Resources regarding scaling Elm apps](https://www.reddit.com/r/elm/comments/65s0g4/resources_regarding_scaling_elm_apps/)
+ *
+ Reddit: [How to structure Elm with multiple models](https://www.reddit.com/r/elm/comments/5jd2xn/how_to_structure_elm_with_multiple_models/dbuu0m4/)
+ *
+ Reddit: [Elm Architecture with a Redux-like store pattern](https://www.reddit.com/r/elm/comments/5xdl9z/elm_architecture_with_a_reduxlike_store_pattern/)
Getting started with Elmish.WPF
-------------------------------
-See the [SingleCounter](https://github.com/elmish/Elmish.WPF/tree/master/src/Samples) sample for a very simple app. The central points are (assuming up-to-date VS2019):
+Elmish.WPF offers two approaches for creating bindings. Choose the one that best fits your needs:
+
+**Quick Start**: See
+the [Dynamic SingleCounter](https://github.com/elmish/Elmish.WPF/tree/master/src/Samples/Dynamic/SingleCounter)
+or [Typed SingleCounter](https://github.com/elmish/Elmish.WPF/tree/master/src/Samples/Typed/SingleCounter) samples for
+very simple apps demonstrating each approach.
+
+The central points are (assuming up-to-date Visual Studio):
1. Create an F# Class Library. If targeting .NET 5 or .NET Core, the project file should look like this:
@@ -109,9 +143,16 @@ See the [SingleCounter](https://github.com/elmish/Elmish.WPF/tree/master/src/Sam
| SetStepSize x -> { m with StepSize = x }
```
-6. Define the “view” function using the `Bindings` module. This is the central public API of Elmish.WPF.
+6. Define your bindings using either the **Dynamic** or **Typed** approach:
+
+### Approach A: Dynamic Bindings
+
+Define the "view" function using the `Bindings` module. This is the central public API of Elmish.WPF.
- Normally in Elm/Elmish this function is called `view` and would take a model and a dispatch function (to dispatch new messages to the update loop) and return the UI (e.g. a HTML DOM to be rendered), but in Elmish.WPF this function is in general only run once and simply sets up bindings that XAML-defined views can use. Therefore, let’s call it `bindings` instead of `view`.
+Normally in Elm/Elmish this function is called `view` and would take a model and a dispatch function (to dispatch new
+messages to the update loop) and return the UI (e.g. a HTML DOM to be rendered), but in Elmish.WPF this function is
+in general only run once and simply sets up bindings that XAML-defined views can use. Therefore, let's call it
+`bindings` instead of `view`.
```F#
open Elmish.WPF
@@ -127,52 +168,73 @@ See the [SingleCounter](https://github.com/elmish/Elmish.WPF/tree/master/src/Sam
]
```
- The strings identify the binding names to be used in the XAML views. The Binding module has many functions to create various types of bindings.
+The strings identify the binding names to be used in the XAML views. The Binding module has many functions to create
+various types of bindings.
+### Approach B: Statically-Typed ViewModels
- Alternatively, use statically-typed view models in order to get better IDE support in the XAML.
+Use statically-typed view models to get better IDE support in XAML, compile-time safety, and superior design-time
+experience.
```f#
open Elmish.WPF
+ []
type CounterViewModel(args) =
inherit ViewModelBase(args)
- member _.CounterValue = base.Get() (Binding.OneWayT.id >> Binding.mapModel (fun m -> m.Count))
- member _.Increment = base.Get() (Binding.CmdT.setAlways Counter.Increment)
- member _.Decrement = base.Get() (Binding.CmdT.setAlways Counter.Decrement)
- member _.StepSize
- with get() = base.Get() (Binding.OneWayT.id >> Binding.mapModel (fun m -> m.StepSize))
- and set(v) = base.Set(v) (Binding.OneWayToSourceT.id >> Binding.mapMsg Counter.Msg.SetStepSize)
+ member _.CounterValue =
+ base.Get () (Binding.OneWayT.id >> Binding.mapModel (fun m -> m.Count))
+
+ member _.Increment =
+ base.Get () (Binding.CmdT.setAlways Increment)
+
+ member _.Decrement =
+ base.Get () (Binding.CmdT.setAlways Decrement)
+
+ let stepSizeBinding =
+ Binding.TwoWayT.id
+ >> Binding.mapModel (fun m -> float m.StepSize)
+ >> Binding.mapMsg (int >> SetStepSize)
+
+ member this.StepSize
+ with get () = base.Get () stepSizeBinding
+ and set (value) = base.Set (value) stepSizeBinding
```
-7. Create a function that accepts the app’s main window (to be created) and configures and starts the Elmish loop for the window with your `init`, `update` and `bindings`:
+7. Create a function that configures and starts the Elmish loop:
+
+### For Dynamic Bindings:
```F#
open Elmish.WPF
let main window =
- Program.mkSimple init update bindings
- |> Program.runElmishLoop window
+ WpfProgram.mkSimple init update bindings
+ |> WpfProgram.startElmishLoop window
```
- Alternatively, use a statically-typed view model at the top level.
+### For Typed ViewModels:
```F#
open Elmish.WPF
let main window =
- Program.mkSimpleT init update CounterViewModel
- |> Program.runElmishLoop window
+ WpfProgram.mkSimpleT init update CounterViewModel
+ |> WpfProgram.startElmishLoop window
```
- In the code above, `Program.runElmishLoop` will set the window’s `DataContext` to the specified bindings and start the Elmish dispatch loop for the window.
+In both cases, `WpfProgram.startElmishLoop` will set the window's `DataContext` and start the Elmish dispatch loop.
-8. Create a WPF app project (using the Visual Studio template called `WPF App (.NET)`). This will be your entry point and contain the XAML views. Add a reference to the F# project, and make the following changes in the `csproj` file:
+8. Create a WPF app project (using the Visual Studio template called `WPF App (.NET)`). This will be your entry point
+ and contain the XAML views. Add a reference to the F# project, and make the following changes in the `csproj` file:
- * Currently, the core Elmish logs are only output to the console. If you want a console window for displaying Elmish logs, change `WinExe` to `Exe` and add `true`.
- * If the project file starts with the now legacy ``, change it to ``
- * Change the target framework to match the one used in the F# project (e.g. `net5.0-windows`).
+ * Currently, the core Elmish logs are only output to the console. If you want a console window for displaying Elmish
+ logs, change `WinExe` to `Exe` and add
+ `true`.
+ * If the project file starts with the now legacy ``, change it to
+ ``
+ * Change the target framework to match the one used in the F# project (e.g. `net5.0-windows`).
Make the following changes to `App.xaml.cs` to initialize Elmish when the app starts:
@@ -214,58 +276,131 @@ See the [SingleCounter](https://github.com/elmish/Elmish.WPF/tree/master/src/Sam
Further resources:
-* The [Elmish.WPF tutorial](https://github.com/elmish/Elmish.WPF/blob/master/TUTORIAL.md) provides information on general MVU/Elmish concepts and how they apply to Elmish.WPF, as well as the various Elmish.WPF bindings.
-* The [samples](https://github.com/elmish/Elmish.WPF/tree/master/src/Samples) are complete, working mini-apps demonstrating selected aspects of Elmish.WPF.
-* If you'd like to contribute, please read and follow the [Contributor guidelines](https://github.com/elmish/Elmish.WPF/blob/master/.github/CONTRIBUTING.md).
+* The [Elmish.WPF tutorial](https://github.com/elmish/Elmish.WPF/blob/master/TUTORIAL.md) provides information on
+ general MVU/Elmish concepts and how they apply to Elmish.WPF, as well as the various Elmish.WPF bindings.
+* The [samples](https://github.com/elmish/Elmish.WPF/tree/master/src/Samples) are complete, working mini-apps
+ demonstrating selected aspects of Elmish.WPF. Browse
+ the [Dynamic samples](https://github.com/elmish/Elmish.WPF/tree/master/src/Samples/Dynamic) for traditional functional
+ bindings or the [Typed samples](https://github.com/elmish/Elmish.WPF/tree/master/src/Samples/Typed) for
+ statically-typed ViewModels.
+* If you'd like to contribute, please read and follow
+ the [Contributor guidelines](https://github.com/elmish/Elmish.WPF/blob/master/.github/CONTRIBUTING.md).
FAQ
---
-#### Static views in MVU? Isn’t that just a half-baked solution that only exists due to a lack of better alternatives?
+#### Should I use Dynamic Bindings or Statically-Typed ViewModels?
+
+**Use Dynamic Bindings when:**
+
+- Rapid prototyping or building simple applications
+- You prefer functional composition over object-oriented patterns
+- Working alone or in small teams familiar with F#
+- You need maximum flexibility in binding definitions
+- Building MVU-style applications where the view layer should be minimal
+
+**Use Statically-Typed ViewModels when:**
+
+- Building large, complex applications
+- Working with designers who need design-time data and IntelliSense
+- You want compile-time validation of binding names and types
+- Integrating with existing WPF/MVVM codebases
+- Multiple developers need clear contracts between view and model layers
+- You require superior tooling support in Visual Studio
+
+Both approaches use the same underlying MVU architecture and can coexist in the same application.
+
+#### How do I migrate between Dynamic and Typed approaches?
+
+**Dynamic to Typed:**
+
+1. Create a class inheriting from `ViewModelBase<'model, 'msg>`
+2. Convert each binding in your `bindings()` function to a property
+3. Use `Binding.OneWayT`, `Binding.CmdT`, etc. instead of the non-T variants
+4. For TwoWay bindings, create a binding variable and implement getter/setter properties
+5. Update your program creation to use `WpfProgram.mkSimpleT`
+
+**Typed to Dynamic:**
+
+1. Extract all properties into a `bindings()` function returning `Binding<'model, 'msg> list`
+2. Convert property names to string-keyed bindings
+3. Use `Binding.oneWay`, `Binding.cmd`, etc. instead of the T variants
+4. Update your program creation to use `WpfProgram.mkSimple`
+
+#### Static views in MVU? Isn't that just a half-baked solution that only exists due to a lack of better alternatives?
Not at all! 🙂
-It’s true that static views aren’t as composable as dynamic views. It’s also true that at the time of writing, there are no solid, production-ready dynamic UI libraries for WPF (though there are no lack of half-finished attempts or proof-of-concepts: [Elmish.WPF.Dynamic](https://github.com/cmeeren/Elmish.WPF.Dynamic), [Fabulous.WPF](https://github.com/TimLariviere/Fabulous.WPF), [Skylight](https://github.com/gerardtoconnor/Skylight), [Uil](https://github.com/elmish/Uil)). Heck, it’s even true that Elmish.WPF was originally created with static views due to the difficulty of creating a dynamic UI library, as described in [issue #1](https://github.com/elmish/Elmish.WPF/issues/1).
+It’s true that static views aren’t as composable as dynamic views. It’s also true that at the time of writing, there are
+no solid, production-ready dynamic UI libraries for WPF (though there are no lack of half-finished attempts or
+proof-of-concepts: [Elmish.WPF.Dynamic](https://github.com/cmeeren/Elmish.WPF.Dynamic), [Fabulous.WPF](https://github.com/TimLariviere/Fabulous.WPF), [Skylight](https://github.com/gerardtoconnor/Skylight), [Uil](https://github.com/elmish/Uil)).
+Heck, it’s even true that Elmish.WPF was originally created with static views due to the difficulty of creating a
+dynamic UI library, as described in [issue #1](https://github.com/elmish/Elmish.WPF/issues/1).
However, Elmish.WPF’s static-view-based solution has several unique benefits:
-- You can use your existing XAML and MVVM knowledge (that is, the best part of MVVM – the UI bindings – without having to deal with `NavigationService`s, `ViewModelLocator`s, state synchronization, `INotifyPropertyChanged`, etc.)
-- Huge mindshare – there are tons of relevant XAML and MVVM resources on the net which can help with the UI and data binding part if you get stuck
-- Automatic support for all 3rd party WPF UI libraries like [MaterialDesignInXamlToolkit](https://github.com/MaterialDesignInXAML/MaterialDesignInXamlToolkit), since it just uses XAML and bindings (support for 3rd party libraries is commonly a major pain point for dynamic UI solutions)
+- You can use your existing XAML and MVVM knowledge (that is, the best part of MVVM – the UI bindings – without having
+ to deal with `NavigationService`s, `ViewModelLocator`s, state synchronization, `INotifyPropertyChanged`, etc.)
+- Huge mindshare – there are tons of relevant XAML and MVVM resources on the net which can help with the UI and data
+ binding part if you get stuck
+- Automatic support for all 3rd party WPF UI libraries
+ like [MaterialDesignInXamlToolkit](https://github.com/MaterialDesignInXAML/MaterialDesignInXamlToolkit), since it just
+ uses XAML and bindings (support for 3rd party libraries is commonly a major pain point for dynamic UI solutions)
- You can use the XAML designer (including design-time data binding)
-- Automatically puts all the power of WPF at your fingertips, whereas dynamic UI solutions have [inherent limitations](https://github.com/cmeeren/Elmish.WPF.Dynamic/tree/e9f04b6e330754f045df093368fa4917c892399d#current-limitations) that are not easy to work around
+- Automatically puts all the power of WPF at your fingertips, whereas dynamic UI solutions
+ have [inherent limitations](https://github.com/cmeeren/Elmish.WPF.Dynamic/tree/e9f04b6e330754f045df093368fa4917c892399d#current-limitations)
+ that are not easy to work around
In short, for WPF apps, a solution based on static XAML views is currently the way to go.
#### Do I have to use the project structure outlined above?
-Not at all. The above example, as well as the samples, keep all non-UI code in a single project for simplicity, and all the XAML in a C# project for better tooling.
+Not at all. The above example, as well as the samples, keep all non-UI code in a single project for simplicity, and all
+the XAML in a C# project for better tooling.
-An alternative with a clearer separation of UI and core logic can be implemented by splitting the F# project into two projects:
+An alternative with a clearer separation of UI and core logic can be implemented by splitting the F# project into two
+projects:
* A core library containing the model definitions and `update` functions.
- * This library can include a reference to Elmish (e.g. for the `Cmd` module helpers), but not to Elmish.WPF, which depends on WPF and has a UI-centered API (specifying bindings). This will ensure your core logic (such as the `update` function) is free from any UI concerns, and allow you to re-use the core library should you want to port your app to another Elmish-based solution (e.g. Fable.React).
+ * This library can include a reference to Elmish (e.g. for the `Cmd` module helpers), but not to Elmish.WPF, which
+ depends on WPF and has a UI-centered API (specifying bindings). This will ensure your core logic (such as the
+ `update` function) is free from any UI concerns, and allow you to re-use the core library should you want to port
+ your app to another Elmish-based solution (e.g. Fable.React).
* An Elmish.WPF project that contains the `bindings` (or `view`) function and the call to `Program.runElmishLoop`.
- * This project would reference the core library and `Elmish.WPF`.
+ * This project would reference the core library and `Elmish.WPF`.
-Another alternative is to turn the sample code on its head and have the F# project be a console app containing your entry point (with a call to `Program.runWindow`) and referencing the C#/XAML project (instead of the other way around, as demonstrated above).
+Another alternative is to turn the sample code on its head and have the F# project be a console app containing your
+entry point (with a call to `Program.runWindow`) and referencing the C#/XAML project (instead of the other way around,
+as demonstrated above).
In general, you have a large amount of freedom in how you structure your solution and what kind of entry point you use.
#### How can I test commands? What is the CmdMsg pattern?
-Since the commands (`Cmd`) returned by `init` and `update` are lists of functions, they are not particularly testable. A general pattern to get around this is to replace the commands with pure data that are transformed to the actual commands elsewhere:
+Since the commands (`Cmd`) returned by `init` and `update` are lists of functions, they are not particularly
+testable. A general pattern to get around this is to replace the commands with pure data that are transformed to the
+actual commands elsewhere:
* Create a `CmdMsg` union type with cases for each command you want to execute in the app.
-* Make `init` and `update` return `model * CmdMsg list` instead of `model * Cmd`. Since `init` and `update` now return data, they are much easier to test.
+* Make `init` and `update` return `model * CmdMsg list` instead of `model * Cmd`. Since `init` and `update` now
+ return data, they are much easier to test.
* Create a trivial/too-boring-to-test `cmdMsgToCmd` function that transforms a `CmdMsg` to the corresponding `Cmd`.
-* Finally, create “normal” versions of `init` and `update` that you can use when creating `Program`. Elmish.WPF provides `Program.mkProgramWpfWithCmdMsg` that does this for you (but there’s no magic going on – it’s really easy to do yourself).
+* Finally, create “normal” versions of `init` and `update` that you can use when creating `Program`. Elmish.WPF provides
+ `Program.mkProgramWpfWithCmdMsg` that does this for you (but there’s no magic going on – it’s really easy to do
+ yourself).
-The [FileDialogsCmdMsg sample](https://github.com/elmish/Elmish.WPF/tree/master/src/Samples) demonstrates this approach. For more information, see the [Fabulous documentation](https://fsprojects.github.io/Fabulous/Fabulous.XamarinForms/update.html#replacing-commands-with-command-messages-for-better-testability). For reference, here is [the discussion that led to this pattern](https://github.com/fsprojects/Fabulous/pull/320#issuecomment-491522737).
+The [FileDialogsCmdMsg sample](https://github.com/elmish/Elmish.WPF/tree/master/src/Samples/Dynamic/FileDialogsCmdMsg)
+demonstrates this approach.
+For more information, see
+the [Fabulous documentation](https://fsprojects.github.io/Fabulous/Fabulous.XamarinForms/update.html#replacing-commands-with-command-messages-for-better-testability).
+For reference, here
+is [the discussion that led to this pattern](https://github.com/fsprojects/Fabulous/pull/320#issuecomment-491522737).
#### Can I use design-time view models?
-Yes. Assuming you have a C# XAML and entry point project referencing the F# project, simply use `ViewModel.designInstance` (e.g. in the F# project) to create a view model instance that your XAML can use at design-time:
+Yes. Assuming you have a C# XAML and entry point project referencing the F# project, simply use
+`ViewModel.designInstance` (e.g. in the F# project) to create a view model instance that your XAML can use at
+design-time:
```F#
module MyAssembly.DesignViewModels
@@ -286,14 +421,15 @@ Then use the following attributes wherever you need a design-time VM:
When targeting legacy .NET Framework, “Project code” must be enabled in the XAML designer for this to work.
-If you are using static view models, make sure that the View Model type is in a namespace and add a default constructor that passes a model into `ViewModelArgs.simple`:
+If you are using static view models, make sure that the View Model type is in a namespace and add a default constructor
+that passes a model into `ViewModelArgs.simple`:
```F#
namespace ViewModels
type [] AppViewModel (args) =
inherit ViewModelBase(args)
-
+
new() = AppViewModel(App.init () |> ViewModelArgs.simple)
```
@@ -311,7 +447,11 @@ Then use the following attributes just like you would in a normal C# MVVM projec
##### .NET Core 3 workaround
-When targeting .NET Core 3, a bug in the XAML designer causes design-time data to not be displayed through `DataContext` bindings. See [this issue](https://developercommunity.visualstudio.com/content/problem/1133390/design-time-data-in-datacontext-binding-not-displa.html) for details. One workaround is to add a `d:DataContext` binding alongside your normal `DataContext` binding. Another workaround is to change
+When targeting .NET Core 3, a bug in the XAML designer causes design-time data to not be displayed through `DataContext`
+bindings.
+See [this issue](https://developercommunity.visualstudio.com/content/problem/1133390/design-time-data-in-datacontext-binding-not-displa.html)
+for details. One workaround is to add a `d:DataContext` binding alongside your normal `DataContext` binding. Another
+workaround is to change
```xaml
@@ -326,21 +466,29 @@ to
RelativeSource={RelativeSource AncestorType=T}}" />
```
-where `T` is the type of the parent object that contains `local:MyControl` (or a more distant ancestor, though there are issues with using `Window` as the type).
+where `T` is the type of the parent object that contains `local:MyControl` (or a more distant ancestor, though there are
+issues with using `Window` as the type).
#### Can I open new windows/dialogs?
-Sure! Just use `Binding.subModelWin`. It works like `Binding.subModel`, but has a `WindowState` wrapper around the returned model to control whether the window is closed, hidden, or visible. You can use both modal and non-modal windows/dialogs, and everything is a part of the Elmish core loop. Check out the [NewWindow sample](https://github.com/elmish/Elmish.WPF/tree/master/src/Samples).
+Sure! Just use `Binding.subModelWin`. It works like `Binding.subModel`, but has a `WindowState` wrapper around the
+returned model to control whether the window is closed, hidden, or visible. You can use both modal and non-modal
+windows/dialogs, and everything is a part of the Elmish core loop. Check out
+the [NewWindow sample](https://github.com/elmish/Elmish.WPF/tree/master/src/Samples/Dynamic/NewWindow) ([Dynamic](https://github.com/elmish/Elmish.WPF/tree/master/src/Samples/Dynamic/NewWindow) | [Typed](https://github.com/elmish/Elmish.WPF/tree/master/src/Samples/Typed/NewWindow)).
-Note that if you use `App.xaml` startup, you may want to set `ShutdownMode="OnMainWindowClose"` in `App.xaml` if that’s the desired behavior.
+Note that if you use `App.xaml` startup, you may want to set `ShutdownMode="OnMainWindowClose"` in `App.xaml` if that’s
+the desired behavior.
#### Can I bind to events and use behaviors?
-Sure! Check out the [EventBindingsAndBehaviors sample](https://github.com/elmish/Elmish.WPF/tree/master/src/Samples). Note that you have to install the NuGet package `Microsoft.Xaml.Behaviors.Wpf`.
+Sure! Check out
+the [EventBindingsAndBehaviors sample](https://github.com/elmish/Elmish.WPF/tree/master/src/Samples/Dynamic/EventBindingsAndBehaviors) ([Dynamic](https://github.com/elmish/Elmish.WPF/tree/master/src/Samples/Dynamic/EventBindingsAndBehaviors) | [Typed](https://github.com/elmish/Elmish.WPF/tree/master/src/Samples/Typed/EventBindingsAndBehaviors)).
+Note that you have to install the NuGet package `Microsoft.Xaml.Behaviors.Wpf`.
#### How can I control logging?
-Elmish.WPF uses `Microsoft.Extensions.Logging`. To see Elmish.WPF output in your favorite logging framework, use `WpfProgram.withLogger` to pass an `ILoggerFactory`:
+Elmish.WPF uses `Microsoft.Extensions.Logging`. To see Elmish.WPF output in your favorite logging framework, use
+`WpfProgram.withLogger` to pass an `ILoggerFactory`:
```f#
WpfProgram.mkSimple init update bindings
@@ -348,15 +496,22 @@ WpfProgram.mkSimple init update bindings
|> WpfProgram.runWindow window
```
-For example, in Serilog, you need to install Serilog.Extensions.Logging and instantiate `SerilogLoggerFactory`. The samples demonstrate this.
+For example, in Serilog, you need to install Serilog.Extensions.Logging and instantiate `SerilogLoggerFactory`. The
+samples demonstrate this.
Elmish.WPF logs to these categories:
* `Elmish.WPF.Update`: Logs exceptions (Error level) and messages/models (Trace/Verbose level) during `update`.
-* `Elmish.WPF.Bindings`: Logs events related to bindings. Some logging is done at the Error level (e.g. developer errors such as duplicated binding names, using non-existent bindings in XAML, etc.), but otherwise it’s generally just Trace/Verbose for when you really want to see everything that’s happening (triggering `PropertyChanged`, WPF getting/setting bindings, etc.)
-* `Elmish.WPF.Performance`: Logs the performance of the functions you pass when creating bindings (`get`, `set`, `map`, `equals`, etc.) at the Trace/Verbose level. Use `WpfProgram.withPerformanceLogThreshold` to set the minimum duration to log.
-
-The specific method of controlling what Elmish.WPF logs depends on your logging framework. For Serilog you can use `.MinimumLevel.Override(...)` to specify the minimum log level per category, like this:
+* `Elmish.WPF.Bindings`: Logs events related to bindings. Some logging is done at the Error level (e.g. developer errors
+ such as duplicated binding names, using non-existent bindings in XAML, etc.), but otherwise it’s generally just
+ Trace/Verbose for when you really want to see everything that’s happening (triggering `PropertyChanged`, WPF
+ getting/setting bindings, etc.)
+* `Elmish.WPF.Performance`: Logs the performance of the functions you pass when creating bindings (`get`, `set`, `map`,
+ `equals`, etc.) at the Trace/Verbose level. Use `WpfProgram.withPerformanceLogThreshold` to set the minimum duration
+ to log.
+
+The specific method of controlling what Elmish.WPF logs depends on your logging framework. For Serilog you can use
+`.MinimumLevel.Override(...)` to specify the minimum log level per category, like this:
```f#
myLoggerConfiguration
diff --git a/REFERENCE.md b/REFERENCE.md
index 5a0dae42..d9a3aa18 100644
--- a/REFERENCE.md
+++ b/REFERENCE.md
@@ -5,39 +5,46 @@ Table of contents
-----------------
* [The Elmish.WPF bindings](#the-elmishwpf-bindings)
- + [One-way bindings](#one-way-bindings)
- - [Binding to option-wrapped values](#binding-to-option-wrapped-values)
- + [Two-way bindings](#two-way-bindings)
- - [Binding to option-wrapped values](#binding-to-option-wrapped-values-1)
- - [Using validation with two-way bindings](#using-validation-with-two-way-bindings)
- + [Command bindings](#command-bindings)
- - [Conditional commands (where you control `CanExecute`)](#conditional-commands-where-you-control-canexecute)
- - [Using the `CommandParameter`](#using-the-commandparameter)
- + [Sub-model bindings](#sub-model-bindings)
- - [Level 1: No separate message type or customization of model for sub-bindings](#level-1-no-separate-message-type-or-customization-of-model-for-sub-bindings)
- - [Level 2: Separate message type but no customization of model for sub-bindings](#level-2-separate-message-type-but-no-customization-of-model-for-sub-bindings)
- - [Level 3: Separate message type and arbitrary customization of model for sub-bindings](#level-3-separate-message-type-and-arbitrary-customization-of-model-for-sub-bindings)
- - [Optional and “sticky” sub-model bindings](#optional-and-sticky-sub-model-bindings)
- + [Sub-model window bindings](#sub-model-window-bindings)
- + [Sub-model sequence bindings](#sub-model-sequence-bindings)
- + [Other bindings](#other-bindings)
- - [`subModelSelectedItem`](#submodelselecteditem)
- - [`oneWaySeq`](#onewayseq)
- + [Lazy bindings](#lazy-bindings)
+ + [One-way bindings](#one-way-bindings)
+ - [Binding to option-wrapped values](#binding-to-option-wrapped-values)
+ + [Two-way bindings](#two-way-bindings)
+ - [Binding to option-wrapped values](#binding-to-option-wrapped-values-1)
+ - [Using validation with two-way bindings](#using-validation-with-two-way-bindings)
+ + [Command bindings](#command-bindings)
+ - [Conditional commands (where you control `CanExecute`)](#conditional-commands-where-you-control-canexecute)
+ - [Using the `CommandParameter`](#using-the-commandparameter)
+ + [Sub-model bindings](#sub-model-bindings)
+ - [Level 1: No separate message type or customization of model for sub-bindings](#level-1-no-separate-message-type-or-customization-of-model-for-sub-bindings)
+ - [Level 2: Separate message type but no customization of model for sub-bindings](#level-2-separate-message-type-but-no-customization-of-model-for-sub-bindings)
+ - [Level 3: Separate message type and arbitrary customization of model for sub-bindings](#level-3-separate-message-type-and-arbitrary-customization-of-model-for-sub-bindings)
+ - [Optional and “sticky” sub-model bindings](#optional-and-sticky-sub-model-bindings)
+ + [Sub-model window bindings](#sub-model-window-bindings)
+ + [Sub-model sequence bindings](#sub-model-sequence-bindings)
+ + [Other bindings](#other-bindings)
+ - [`subModelSelectedItem`](#submodelselecteditem)
+ - [`oneWaySeq`](#onewayseq)
+ + [Lazy bindings](#lazy-bindings)
* [Modifying bindings](#modifying-bindings)
- + [Lazy updating](#lazy-updating)
- + [Caching](#caching)
- + [Mapping bindings](#mapping-bindings)
- - [Example use of `mapModel` and `mapMsg`](#example-use-of-mapModel-and-mapMsg)
- - [Theory behind `mapModel` and `mapMsg`](#theory-behind-mapModel-and-mapMsg)
+ + [Lazy updating](#lazy-updating)
+ + [Caching](#caching)
+ + [Mapping bindings](#mapping-bindings)
+ - [Example use of `mapModel` and `mapMsg`](#example-use-of-mapModel-and-mapMsg)
+ - [Theory behind `mapModel` and `mapMsg`](#theory-behind-mapModel-and-mapMsg)
+* [Choosing Your Binding Approach](#choosing-your-binding-approach)
+ + [Dynamic Bindings vs Statically-Typed ViewModels](#dynamic-bindings-vs-statically-typed-viewmodels)
+ + [Migration Between Approaches](#migration-between-approaches)
* [Statically-typed view models](#statically-typed-view-models)
- + [Inherit from `ViewModelBase<'model, 'msg>`](#inherit-from-viewmodelbasemodel-msg)
- + [Typed Bindings](#typed-bindings)
- - [Typed One-way Bindings](#typed-one-way-bindings)
- - [Typed SubModel Bindings](#typed-submodel-bindings)
- - [Typed WpfProgram Bindings](#typed-wpfprogram-bindings)
- - [Mixing and matching bindings](#mixing-and-matching-bindings)
-
+ + [Inherit from `ViewModelBase<'model, 'msg>`](#inherit-from-viewmodelbasemodel-msg)
+ + [Advantages of Statically-Typed ViewModels](#advantages-of-statically-typed-viewmodels)
+ + [Basic Typed ViewModel Structure](#basic-typed-viewmodel-structure)
+ + [Typed Bindings](#typed-bindings)
+ - [Typed One-way Bindings](#typed-one-way-bindings)
+ - [Typed TwoWay Bindings](#typed-twoway-bindings)
+ - [Typed TwoWay Bindings with Validation](#typed-twoway-bindings-with-validation)
+ - [Typed SubModel Bindings](#typed-submodel-bindings)
+ - [Typed SubModel Sequence Bindings](#typed-submodel-sequence-bindings)
+ - [Typed WpfProgram Bindings](#typed-wpfprogram-bindings)
+ - [Mixing and matching bindings](#mixing-and-matching-bindings)
The Elmish.WPF bindings
----------------------------
@@ -45,19 +52,25 @@ The Elmish.WPF bindings
The Elmish.WPF bindings can be categorized into the following types:
- **One-way bindings**, for when you want to bind to a simple value.
-- **Two-way bindings**, for when you want to bind to a simple value as well as update this value by dispatching a message. Used for inputs, checkboxes, sliders, etc. Can optionally support validation (e.g. provide an error message using `INotifyDataErrorInfo` that can be displayed when an input is not valid).
+- **Two-way bindings**, for when you want to bind to a simple value as well as update this value by dispatching a
+ message. Used for inputs, checkboxes, sliders, etc. Can optionally support validation (e.g. provide an error message
+ using `INotifyDataErrorInfo` that can be displayed when an input is not valid).
- **Command bindings**, for when you want a message to be dispatched when something happens (e.g. a button is clicked).
- **Sub-model bindings**, for when you want to bind to a complex object that has its own bindings.
- **Sub-model window bindings**, for when you want to control the opening/closing/hiding of new windows.
-- **Sub-model sequence bindings**, for when you want to bind to a collection of complex objects, each of which has its own bindings.
+- **Sub-model sequence bindings**, for when you want to bind to a collection of complex objects, each of which has its
+ own bindings.
- **Other bindings** not fitting into the categories above
-- **Lazy bindings**, optimizations of various other bindings that allow skipping potentially expensive computations if the input is unchanged
+- **Lazy bindings**, optimizations of various other bindings that allow skipping potentially expensive computations if
+ the input is unchanged
-Additionally, there is a section explaining how most dispatching bindings allow you to wrap the dispatcher to support debouncing/throttling etc.
+Additionally, there is a section explaining how most dispatching bindings allow you to wrap the dispatcher to support
+debouncing/throttling etc.
### One-way bindings
-*Relevant sample: SingleCounter - ([XAML views](src/Samples/SingleCounter) and [F# core](src/Samples/SingleCounter.Core))*
+*Relevant sample:
+SingleCounter - ([Dynamic](src/Samples/Dynamic/SingleCounter) | [Typed](src/Samples/Typed/SingleCounter))*
One-way bindings are used when you want to bind to a simple value.
@@ -77,13 +90,22 @@ A one-way binding simply accepts a function `get: 'model -> 'a` that retrieves t
#### Binding to option-wrapped values
-In F#, it’s common to model missing values using the `Option` type. However, WPF uses `null` and doesn’t know how to handle the F# `Option` type. You could simply convert from `Option` to `null` (or `Nullable<_>`) in the `get` function using `Option.toObj` (or `Option.toNullable`), but this is such a common scenario that Elmish.WPF has a variant of the one-way binding called `oneWayOpt` with this behavior built-in. The `oneWayOpt` binding accepts a function `get: 'model -> 'a option`. If it returns `None`, the UI will receive `null`. If it returns `Some`, the UI will receive the inner value.
+In F#, it’s common to model missing values using the `Option` type. However, WPF uses `null` and doesn’t know how to
+handle the F# `Option` type. You could simply convert from `Option` to `null` (or `Nullable<_>`) in the `get` function
+using `Option.toObj` (or `Option.toNullable`), but this is such a common scenario that Elmish.WPF has a variant of the
+one-way binding called `oneWayOpt` with this behavior built-in. The `oneWayOpt` binding accepts a function
+`get: 'model -> 'a option`. If it returns `None`, the UI will receive `null`. If it returns `Some`, the UI will receive
+the inner value.
### Two-way bindings
-*Relevant sample: SingleCounter - ([XAML views](src/Samples/SingleCounter) and [F# core](src/Samples/SingleCounter.Core))*
+*Relevant sample:
+SingleCounter - ([Dynamic](src/Samples/Dynamic/SingleCounter) | [Typed](src/Samples/Typed/SingleCounter))*
-Two-way bindings are commonly used for any kind of input (textboxes, checkboxes, sliders, etc.). The two-way bindings accept two functions: A function `get: 'model -> 'a` just like the one-way binding, and a function `set: 'a -> 'model -> 'msg` that accepts the UI value to be set and the current model, and returns the message to be dispatched.
+Two-way bindings are commonly used for any kind of input (textboxes, checkboxes, sliders, etc.). The two-way bindings
+accept two functions: A function `get: 'model -> 'a` just like the one-way binding, and a function
+`set: 'a -> 'model -> 'msg` that accepts the UI value to be set and the current model, and returns the message to be
+dispatched.
In the counter example above, the two-way binding to the slider value may look like this:
@@ -105,11 +127,17 @@ The corresponding XAML may look like this:
IsSnapToTickEnabled="True" />
```
-The WPF slider’s value is a `float`, but in the model we use an `int`. Therefore the binding’s `get` function must convert the model’s integer to a float, and conversely, the binding’s “setter” must convert the UI value from a float to an int.
+The WPF slider’s value is a `float`, but in the model we use an `int`. Therefore the binding’s `get` function must
+convert the model’s integer to a float, and conversely, the binding’s “setter” must convert the UI value from a float to
+an int.
-You might think that the `get` function doesn’t have to cast to `float`. However, `'a` is the same in both `get` and `set`, and if you return `int` in `get`, then Elmish.WPF expects the value coming from the UI (which is `obj`) to also be `int`, and will try to unbox it to `int` when being set. Since it actually is a `float`, this will fail.
+You might think that the `get` function doesn’t have to cast to `float`. However, `'a` is the same in both `get` and
+`set`, and if you return `int` in `get`, then Elmish.WPF expects the value coming from the UI (which is `obj`) to also
+be `int`, and will try to unbox it to `int` when being set. Since it actually is a `float`, this will fail.
-It’s common for the `set` function to rely only on the value to be set, not on the model. Therefore, the two-way binding also has an overload where the `set` function accepts only the value, not the model. This allows a more shorthand notation:
+It’s common for the `set` function to rely only on the value to be set, not on the model. Therefore, the two-way binding
+also has an overload where the `set` function accepts only the value, not the model. This allows a more shorthand
+notation:
```f#
"StepSize" |> Binding.twoWay(
@@ -120,39 +148,54 @@ It’s common for the `set` function to rely only on the value to be set, not on
#### Binding to option-wrapped values
-Just like one-way bindings, there is a variant of the two-way binding for `option`-wrapped values. The `option` wrapping is used in both `get` and `set`. Elmish.WPF will convert both ways between a possibly `null` raw value and an `option`-wrapped value.
+Just like one-way bindings, there is a variant of the two-way binding for `option`-wrapped values. The `option` wrapping
+is used in both `get` and `set`. Elmish.WPF will convert both ways between a possibly `null` raw value and an `option`
+-wrapped value.
#### Using validation with two-way bindings
-*Relevant sample: Validation - ([XAML views](src/Samples/Validation) and [F# core](src/Samples/Validation.Core))*
+*Relevant sample: Validation - ([Dynamic](src/Samples/Dynamic/Validation) | [Typed](src/Samples/Typed/Validation))*
-You might want to display validation errors when the input is invalid. The best way to do this in WPF is through `INotifyDataErrorInfo`. Elmish.WPF supports this directly through the `twoWayValidate` bindings. In addition to `get` and `set`, this binding also accepts a third parameter that returns the error string to be displayed. This can be returned as `string option` (where `None` indicates no error), or `Result<_, string>` (where `Ok` indicates no error; this variant might allow you to easily reuse existing validation functions you have).
+You might want to display validation errors when the input is invalid. The best way to do this in WPF is through
+`INotifyDataErrorInfo`. Elmish.WPF supports this directly through the `twoWayValidate` bindings. In addition to `get`
+and `set`, this binding also accepts a third parameter that returns the error string to be displayed. This can be
+returned as `string option` (where `None` indicates no error), or `Result<_, string>` (where `Ok` indicates no error;
+this variant might allow you to easily reuse existing validation functions you have).
-Keep in mind that by default, WPF controls do not display errors. To display errors, either use 3rd party controls/styles (such as [MaterialDesignInXamlToolkit](https://github.com/MaterialDesignInXAML/MaterialDesignInXamlToolkit)) or add your own styles (the `Validation` sample in this repo demonstrates this).
+Keep in mind that by default, WPF controls do not display errors. To display errors, either use 3rd party
+controls/styles (such
+as [MaterialDesignInXamlToolkit](https://github.com/MaterialDesignInXAML/MaterialDesignInXamlToolkit)) or add your own
+styles (the `Validation` sample in this repo demonstrates this).
There are also variants of the two-way validating bindings for option-wrapped values.
### Command bindings
-*Relevant sample: SingleCounter - ([XAML views](src/Samples/SingleCounter) and [F# core](src/Samples/SingleCounter.Core))*
+*Relevant sample:
+SingleCounter - ([Dynamic](src/Samples/Dynamic/SingleCounter) | [Typed](src/Samples/Typed/SingleCounter))*
Command bindings are used whenever you use `Command`/`CommandParameter` in XAML, such as for button clicks.
-For example, for the counter app we have been looking at, the XAML binding to execute a command when the “Increment” button is clicked might look like this:
+For example, for the counter app we have been looking at, the XAML binding to execute a command when the “Increment”
+button is clicked might look like this:
```xaml
```
-The corresponding Elmish.WPF binding that dispatches `Msg.Increment` when the command is executed generally looks like this:
+The corresponding Elmish.WPF binding that dispatches `Msg.Increment` when the command is executed generally looks like
+this:
```f#
"Increment" |> Binding.cmd (fun m -> Increment)
```
-The binding accepts a single function `exec: 'model -> 'msg` that accepts the current model and returns the message to be dispatched. Elmish.WPF will convert the message to an `ICommand` that dispatches the message when the command is invoked.
+The binding accepts a single function `exec: 'model -> 'msg` that accepts the current model and returns the message to
+be dispatched. Elmish.WPF will convert the message to an `ICommand` that dispatches the message when the command is
+invoked.
-For convenience, if you don’t need the model, there is also an overload that directly accepts the message (instead of a model-accepting function). The above can therefore be written like this:
+For convenience, if you don’t need the model, there is also an overload that directly accepts the message (instead of a
+model-accepting function). The above can therefore be written like this:
```f#
"Increment" |> Binding.cmd Increment
@@ -160,11 +203,14 @@ For convenience, if you don’t need the model, there is also an overload that d
#### Conditional commands (where you control `CanExecute`)
-*Relevant sample: SingleCounter - ([XAML views](src/Samples/SingleCounter) and [F# core](src/Samples/SingleCounter.Core))*
+*Relevant sample:
+SingleCounter - ([Dynamic](src/Samples/Dynamic/SingleCounter) | [Typed](src/Samples/Typed/SingleCounter))*
-A command may not always be executable. As you might know, WPF’s `ICommand` interface contains a `CanExecute` method that, if `false`, will cause WPF to disable the bound control (e.g. the button).
+A command may not always be executable. As you might know, WPF’s `ICommand` interface contains a `CanExecute` method
+that, if `false`, will cause WPF to disable the bound control (e.g. the button).
-In the counter example, we might want to prohibit negative numbers, disabling the `Decrement` button when the `model.Count = 0`. This can be written using `cmdIf`:
+In the counter example, we might want to prohibit negative numbers, disabling the `Decrement` button when the
+`model.Count = 0`. This can be written using `cmdIf`:
```f#
"Decrement" |> Binding.cmdIf (
@@ -177,25 +223,34 @@ There are several ways to indicate that a command can‘t execute. The `cmdIf` b
- `exec: 'model -> 'msg option`, where the command is disabled if `exec` returns `None`
- `exec: 'model -> Result<'msg, _>`, where the command is disabled if `exec` returns `Error`
-- `exec: 'model -> 'msg * canExec: 'model -> bool` (as the example above shows), where the command is disabled if `canExec` returns `false` (and as with `cmd`, there is also an overload where `exec` is simply the message to dispatch)
+- `exec: 'model -> 'msg * canExec: 'model -> bool` (as the example above shows), where the command is disabled if
+ `canExec` returns `false` (and as with `cmd`, there is also an overload where `exec` is simply the message to
+ dispatch)
#### Using the `CommandParameter`
-*Relevant sample: UiBoundCmdParam - ([XAML views](src/Samples/UiBoundCmdParam) and [F# core](src/Samples/UiBoundCmdParam.Core))*
+*Relevant sample:
+UiBoundCmdParam - ([Dynamic](src/Samples/Dynamic/UiBoundCmdParam) | [Typed](src/Samples/Typed/UiBoundCmdParam))*
-There may be times you need to use the XAML `CommandParameter` property. You then need to use Elmish.WPF’s `cmdParam` binding, which works exactly like `cmd` but where `exec` function accepts the command parameter as its first parameter.
+There may be times you need to use the XAML `CommandParameter` property. You then need to use Elmish.WPF’s `cmdParam`
+binding, which works exactly like `cmd` but where `exec` function accepts the command parameter as its first parameter.
There is also `cmdParamIf` which combines `cmdParam` and `cmdIf`, allowing you to override the command’s `CanExecute`.
### Sub-model bindings
-*Relevant sample: SubModel - ([XAML views](src/Samples/SubModel) and [F# core](src/Samples/SubModel.Core))*
+*Relevant sample: SubModel - ([Dynamic](src/Samples/Dynamic/SubModel) | [Typed](src/Samples/Typed/SubModelStatic))*
-Sub-model bindings are used when you want to bind to a complex object that has its own bindings. In MVVM, this happens when one of your view-model properties is another view model with its own properties the UI can bind to.
+Sub-model bindings are used when you want to bind to a complex object that has its own bindings. In MVVM, this happens
+when one of your view-model properties is another view model with its own properties the UI can bind to.
-Perhaps the most compelling use-case for sub-models is when binding the `ItemsSource` of a `ListView` or similar. Each item in the collection you bind to is a view-model with its own properties that is used when rendering each item. However, the same principles apply when there’s only a single sub-model. Collections are treated later; this section focuses on a single sub-model.
+Perhaps the most compelling use-case for sub-models is when binding the `ItemsSource` of a `ListView` or similar. Each
+item in the collection you bind to is a view-model with its own properties that is used when rendering each item.
+However, the same principles apply when there’s only a single sub-model. Collections are treated later; this section
+focuses on a single sub-model.
-The `subModel` binding has three overloads, increasing in complexity depending on how much you need to customize the sub-bindings.
+The `subModel` binding has three overloads, increasing in complexity depending on how much you need to customize the
+sub-bindings.
#### Level 1: No separate message type or customization of model for sub-bindings
@@ -204,7 +259,8 @@ This is sufficient for many purposes. The overload accepts two parameters:
- `getSubModel: 'model -> 'subModel` to obtain the sub-model
- `bindings: unit -> Binding<'model * 'subModel, 'msg> list`, the bindings for the sub-model
-In other words, inside the sub-bindings, the model parameter (in each binding) is a tuple with the parent model and the sub-model.
+In other words, inside the sub-bindings, the model parameter (in each binding) is a tuple with the parent model and the
+sub-model.
For example, let’s say that we have an app where a counter is a part of the app. We might do this:
@@ -218,9 +274,14 @@ For example, let’s say that we have an app where a counter is a part of the ap
)
```
-As you can see, inside the sub-bindings (which could be extracted to their own `bindings` function), the model parameter is a tuple containing the parent state as well as the sub-model state. This is a good default because it’s the most general signature, allowing you access to everything from the parent as well as the sub-model you are binding to. (This is particularly important for sub-model sequence bindings, which are described later.)
+As you can see, inside the sub-bindings (which could be extracted to their own `bindings` function), the model parameter
+is a tuple containing the parent state as well as the sub-model state. This is a good default because it’s the most
+general signature, allowing you access to everything from the parent as well as the sub-model you are binding to. (This
+is particularly important for sub-model sequence bindings, which are described later.)
-Note also that the sub-bindings still use the top-level message type. There is no separate child message type for the sub-model; `IncrementCounter` is a case of the parent message type. This is also a good default for the reasons described in the earlier “child components and scaling” section.
+Note also that the sub-bindings still use the top-level message type. There is no separate child message type for the
+sub-model; `IncrementCounter` is a case of the parent message type. This is also a good default for the reasons
+described in the earlier “child components and scaling” section.
#### Level 2: Separate message type but no customization of model for sub-bindings
@@ -230,7 +291,8 @@ This overload is just like the first one except it has an additional parameter t
- `toMsg: 'subMsg -> 'msg` to wrap the child message in a parent message
- `bindings: unit -> Binding<'model * 'subModel, 'subMsg> list`, the bindings for the sub-model
-This is useful if you want to use a separate message type in the sub-model bindings. For the `toMsg` parameter, you would typically pass a parent message case that wraps the child message type. For example:
+This is useful if you want to use a separate message type in the sub-model bindings. For the `toMsg` parameter, you
+would typically pass a parent message case that wraps the child message type. For example:
```f#
"Counter" |> Binding.subModel(
@@ -243,9 +305,11 @@ This is useful if you want to use a separate message type in the sub-model bindi
)
```
-Here, `Increment` is a case of the child message type, and `CounterMsg` is a parent message case that wraps the counter message type.
+Here, `Increment` is a case of the child message type, and `CounterMsg` is a parent message case that wraps the counter
+message type.
-If you had passed `id` as the `toMsg` parameter, you would have the same behavior as the previous simpler overload with no `toMsg`.
+If you had passed `id` as the `toMsg` parameter, you would have the same behavior as the previous simpler overload with
+no `toMsg`.
#### Level 3: Separate message type and arbitrary customization of model for sub-bindings
@@ -256,7 +320,8 @@ This is the most complex one, and is required for the following cases:
The reasons it’s required for these cases are described further below.
-It’s also nice to have if you simply want to “clean up” or otherwise customize the model used in the bindings (e.g. if you don’t need the parent model, only the child model).
+It’s also nice to have if you simply want to “clean up” or otherwise customize the model used in the bindings (e.g. if
+you don’t need the parent model, only the child model).
Compared to the “level 2” overload, it has one additional parameter, `toBindingModel`. All the parameters are:
@@ -279,11 +344,16 @@ Continuing with the counter example above, it could look like this:
)
```
-As you see, we transform the default `(parent, counter)` tuple into just the `counter`, so that the model used in the sub-bindings is only the `'subModel`. Otherwise the example is the same. If you had passed `id` to `toBindingModel` and `toMsg`, you would end up with the same behavior as the simplest variant without `toBindingModel` and `toMsg`.
+As you see, we transform the default `(parent, counter)` tuple into just the `counter`, so that the model used in the
+sub-bindings is only the `'subModel`. Otherwise the example is the same. If you had passed `id` to `toBindingModel` and
+`toMsg`, you would end up with the same behavior as the simplest variant without `toBindingModel` and `toMsg`.
-The model transformation allowed by this overload is required for a proper, separate “child component” with its own model/message/bindings, because the child component’s bindings would of course not know anything about any parent model. I.e., as demonstrated above, you need the model to be just `'subModel` and not `'model * 'subModel`
+The model transformation allowed by this overload is required for a proper, separate “child component” with its own
+model/message/bindings, because the child component’s bindings would of course not know anything about any parent model.
+I.e., as demonstrated above, you need the model to be just `'subModel` and not `'model * 'subModel`
-The model transformation is also required for recursive bindings. Imagine that a counter can contain another counter (in a `ChildCounter` property). You would define the (recursive) counter bindings as:
+The model transformation is also required for recursive bindings. Imagine that a counter can contain another counter (in
+a `ChildCounter` property). You would define the (recursive) counter bindings as:
```f#
let rec counterBindings () : Binding list = [
@@ -297,25 +367,33 @@ let rec counterBindings () : Binding list = [
])
```
-If you could not transform `(parent, counter)` “back” to `counter`, you could not reuse the same bindings, and hence not create recursive bindings.
+If you could not transform `(parent, counter)` “back” to `counter`, you could not reuse the same bindings, and hence not
+create recursive bindings.
Recursive bindings are demonstrated in the `SubModelSeq` sample.
-You now have the power to create child components. Use it with great care; as mentioned in the earlier “child components and scaling” section, such separation will often do more harm than good.
+You now have the power to create child components. Use it with great care; as mentioned in the earlier “child components
+and scaling” section, such separation will often do more harm than good.
#### Optional and “sticky” sub-model bindings
-*Relevant sample: SubModelOpt - ([XAML views](src/Samples/SubModelOpt) and [F# core](src/Samples/SubModelOpt.Core))*
+*Relevant sample: SubModelOpt - ([Dynamic](src/Samples/Dynamic/SubModelOpt) | [Typed](src/Samples/Typed/SubModelOpt))*
-You can also use the `subModelOpt` binding. The signature is the same as the variants described above, except that `getSubModel` returns `'subModel option`. The UI will receive `null` when the sub-model is `None`.
+You can also use the `subModelOpt` binding. The signature is the same as the variants described above, except that
+`getSubModel` returns `'subModel option`. The UI will receive `null` when the sub-model is `None`.
-Additionally, these bindings have an optional `sticky: bool` parameter. If `true`, Elmish.WPF will “remember” and return the most recent non-null sub-model when the `getSubModel` returns `None`. This can be useful for example when you want to animate away the UI for the sub-component when it’s set to `None`. If you do not use `sticky`, the UI will be cleared at the start of the animation, which may look weird.
+Additionally, these bindings have an optional `sticky: bool` parameter. If `true`, Elmish.WPF will “remember” and return
+the most recent non-null sub-model when the `getSubModel` returns `None`. This can be useful for example when you want
+to animate away the UI for the sub-component when it’s set to `None`. If you do not use `sticky`, the UI will be cleared
+at the start of the animation, which may look weird.
### Sub-model window bindings
-*Relevant sample: NewWindow - ([XAML views](src/Samples/NewWindow) and [F# core](src/Samples/NewWindow.Core))*
+*Relevant sample: NewWindow - ([Dynamic](src/Samples/Dynamic/NewWindow) | [Typed](src/Samples/Typed/NewWindow))*
-The `subModelWin` binding is a variant of `subModelOpt` that allows you to control the opening/closing/hiding of new windows. It has the same overloads as `subModel` and `subModelOpt`, with two key differences: First, the sub-model is wrapped in a custom type called `WindowState` that is defined like this:
+The `subModelWin` binding is a variant of `subModelOpt` that allows you to control the opening/closing/hiding of new
+windows. It has the same overloads as `subModel` and `subModelOpt`, with two key differences: First, the sub-model is
+wrapped in a custom type called `WindowState` that is defined like this:
```f#
[]
@@ -325,7 +403,9 @@ type WindowState<'model> =
| Visible of 'model
```
-By wrapping the sub-model in `WindowState.Hidden` or `WindowState.Visible` or returning `WindowState.Closed`, you control the opening, closing, showing, and hiding of a window whose `DataContext` will be automatically set to the wrapped model. Check out the `NewWindow` sample to see it in action.
+By wrapping the sub-model in `WindowState.Hidden` or `WindowState.Visible` or returning `WindowState.Closed`, you
+control the opening, closing, showing, and hiding of a window whose `DataContext` will be automatically set to the
+wrapped model. Check out the `NewWindow` sample to see it in action.
Secondly, all overloads have the following parameter:
@@ -333,23 +413,37 @@ Secondly, all overloads have the following parameter:
getWindow: 'model -> Dispatch<'msg> -> #Window
```
-This is what’s actually called to create the window. You have access to the current model as well as the `dispatch` in case you need to set up message-dispatching event subscriptions for the window.
+This is what’s actually called to create the window. You have access to the current model as well as the `dispatch` in
+case you need to set up message-dispatching event subscriptions for the window.
-Additionally, all `subModelWin` overloads have two optional parameters. The first is `?onCloseRequested: 'msg`. Returning `WindowState.Closed` is the only way to close the window. In order to support closing using external mechanisms (the Close/X button, Alt+F4, or System Menu -> Close), this parameter allows you to specify a message that will be dispatched for these events. You can then react to this message by updating your state so that the binding returns `WindowState.Closed`
+Additionally, all `subModelWin` overloads have two optional parameters. The first is `?onCloseRequested: 'msg`.
+Returning `WindowState.Closed` is the only way to close the window. In order to support closing using external
+mechanisms (the Close/X button, Alt+F4, or System Menu -> Close), this parameter allows you to specify a message that
+will be dispatched for these events. You can then react to this message by updating your state so that the binding
+returns `WindowState.Closed`
-The second optional parameter is `?isModal: bool`. This specifies whether the window will be shown modally (using `window.ShowDialog`, blocking the rest of the UI) or non-modally (using `window.Show`).
+The second optional parameter is `?isModal: bool`. This specifies whether the window will be shown modally (using
+`window.ShowDialog`, blocking the rest of the UI) or non-modally (using `window.Show`).
Again, check out the `NewWindow` sample to see `subModelWin` in action.
### Sub-model sequence bindings
-*Relevant sample: SubModelSeq - ([XAML views](src/Samples/SubModelSeq) and [F# core](src/Samples/SubModelSeq.Core))*
+*Relevant sample: SubModelSeq - ([Dynamic](src/Samples/Dynamic/SubModelSeq) | [Typed](src/Samples/Typed/SubModelSeq))*
-If you understand `subModel`, then `subModelSeq` isn’t much more complex. It has similar overloads, but instead of returning a single sub-model, you return `#seq<'subModel>`. Furthermore, all overloads have an additional parameter `getId` (which for the “level 1” and “level 2” overloads has signature `'subModel -> 'id`) that gets a unique identifier for each model. This identifier must be unique among all sub-models in the collection, and is used to know which items to add, remove, re-order, and update.
+If you understand `subModel`, then `subModelSeq` isn’t much more complex. It has similar overloads, but instead of
+returning a single sub-model, you return `#seq<'subModel>`. Furthermore, all overloads have an additional parameter
+`getId` (which for the “level 1” and “level 2” overloads has signature `'subModel -> 'id`) that gets a unique identifier
+for each model. This identifier must be unique among all sub-models in the collection, and is used to know which items
+to add, remove, re-order, and update.
-The `toMsg` parameter in the “level 2” and “level 3” overloads has the signature `'id * 'subMsg -> 'msg` (compared with just `'subMsg -> 'msg` for `subModel`). For this parameter you would typically use a parent message case that wraps both the child ID and the child message. You need the ID to know which sub-model it came from, and thus which sub-model to pass the message along to.
+The `toMsg` parameter in the “level 2” and “level 3” overloads has the signature `'id * 'subMsg -> 'msg` (compared with
+just `'subMsg -> 'msg` for `subModel`). For this parameter you would typically use a parent message case that wraps both
+the child ID and the child message. You need the ID to know which sub-model it came from, and thus which sub-model to
+pass the message along to.
-Finally, in the “level 3” overload that allows you to transform the model used for the bindings, the `getId` parameter has signature `'bindingModel -> 'id` (instead of `'subModel -> 'id` for the two simpler overloads).
+Finally, in the “level 3” overload that allows you to transform the model used for the bindings, the `getId` parameter
+has signature `'bindingModel -> 'id` (instead of `'subModel -> 'id` for the two simpler overloads).
### Other bindings
@@ -357,32 +451,49 @@ There are two special bindings not yet covered.
#### `subModelSelectedItem`
-*Relevant sample: SubModelSelectedItem - ([XAML views](src/Samples/SubModelSelectedItem) and [F# core](src/Samples/SubModelSelectedItem.Core))*
+*Relevant sample:
+SubModelSelectedItem - ([Dynamic](src/Samples/Dynamic/SubModelSelectedItem) | [Typed](src/Samples/Typed/SubModelSelectedItem))*
-The section on model normalization made it clear that it’s better to use IDs than complex objects in messages. This means that for bindings to the selected value of a `ListBox` or similar, you’ll likely have better luck using `SelectedValue` and `SelectedValuePath` rather than `SelectedItem`.
+The section on model normalization made it clear that it’s better to use IDs than complex objects in messages. This
+means that for bindings to the selected value of a `ListBox` or similar, you’ll likely have better luck using
+`SelectedValue` and `SelectedValuePath` rather than `SelectedItem`.
-Unfortunately some selection-enabled WPF controls only have `SelectedItem` and do not support `SelectedValue` and `SelectedValuePath`. Using `SelectedItem` is particularly cumbersome in Elmish.WPF since the value is not your sub-model, but an instance of the Elmish.WPF view-model. To help with this, Elmish.WPF provides the `subModelSelectedItem` binding.
+Unfortunately some selection-enabled WPF controls only have `SelectedItem` and do not support `SelectedValue` and
+`SelectedValuePath`. Using `SelectedItem` is particularly cumbersome in Elmish.WPF since the value is not your
+sub-model, but an instance of the Elmish.WPF view-model. To help with this, Elmish.WPF provides the
+`subModelSelectedItem` binding.
-This binding works together with a `subModelSeq` binding in the same binding list, and allows you to use the `subModelSeq` binding’s IDs in your model while still using `SelectedItem` from XAML. For example, if you use `subModelSeq` to display a list of books identified by a `BookId`, the `subModelSelectedItem` binding allows you to use `SelectedBook: BookId` in your model.
+This binding works together with a `subModelSeq` binding in the same binding list, and allows you to use the
+`subModelSeq` binding’s IDs in your model while still using `SelectedItem` from XAML. For example, if you use
+`subModelSeq` to display a list of books identified by a `BookId`, the `subModelSelectedItem` binding allows you to use
+`SelectedBook: BookId` in your model.
The `subModelSelectedItem` binding has the following parameters:
- `subModelSeqBindingName: string`, where you identify the binding name for the corresponding `subModelSeq` binding
-- `get: 'model -> 'id option`, where you return the ID of the sub-model in the `subModelSeq` binding that should be selected
-- `set: 'id option -> 'msg`, where you return the message to dispatch when the selected item changes (typically this will be a message case wrapping the ID).
+- `get: 'model -> 'id option`, where you return the ID of the sub-model in the `subModelSeq` binding that should be
+ selected
+- `set: 'id option -> 'msg`, where you return the message to dispatch when the selected item changes (typically this
+ will be a message case wrapping the ID).
-You bind the `SelectedItem` of a control to the `subModelSelectedItem` binding. Then, Elmish.WPF will take care of the following:
+You bind the `SelectedItem` of a control to the `subModelSelectedItem` binding. Then, Elmish.WPF will take care of the
+following:
-- When the UI retrieves the selected item, Elmish.WPF gets the ID using `get`, looks up the correct view-model in the `subModelSeq` binding identified by `subModelSeqBindingName`, and returns that view-model to the UI.
-- When the UI sets the selected item (which it sets to an Elmish.WPF view-model), Elmish.WPF calls `set` with the ID of the sub-model corresponding to that view-model.
+- When the UI retrieves the selected item, Elmish.WPF gets the ID using `get`, looks up the correct view-model in the
+ `subModelSeq` binding identified by `subModelSeqBindingName`, and returns that view-model to the UI.
+- When the UI sets the selected item (which it sets to an Elmish.WPF view-model), Elmish.WPF calls `set` with the ID of
+ the sub-model corresponding to that view-model.
#### `oneWaySeq`
-*Relevant sample: OneWaySeq - ([XAML views](src/Samples/OneWaySeq) and [F# core](src/Samples/OneWaySeq.Core))*
+*Relevant sample: OneWaySeq - ([Dynamic](src/Samples/Dynamic/OneWaySeq) | [Typed](src/Samples/Typed/OneWaySeq))*
-In some cases, you might want to have a one-way binding not to a single, simple value, but to a potentially large collection of simple values. If you use `oneWay` for this, the entire list will be replaced and re-rendered each time the model updates.
+In some cases, you might want to have a one-way binding not to a single, simple value, but to a potentially large
+collection of simple values. If you use `oneWay` for this, the entire list will be replaced and re-rendered each time
+the model updates.
-In the special case that you want to bind to a collection of **simple** (can be bound to directly) and **distinct** values, you can use `oneWaySeq`. This will ensure that only changed items are replaced/moved.
+In the special case that you want to bind to a collection of **simple** (can be bound to directly) and **distinct**
+values, you can use `oneWaySeq`. This will ensure that only changed items are replaced/moved.
The `oneWaySeq` binding has the following parameters:
@@ -390,45 +501,69 @@ The `oneWaySeq` binding has the following parameters:
- `itemEquals: 'a -> 'a -> bool`, to determine whether an item has changed
- `getId: 'a -> 'id`, to track which items are added, removed, re-ordered, and changed
-If the values are not simple (e.g. not strings or numbers), then you can instead use `subModelSeq` to set up separate bindings for each item. And if the values are not distinct (i.e., can not be uniquely identified in the collection), then Elmish.WPF won’t be able to track which items are moved, and you can’t use this optimization.
+If the values are not simple (e.g. not strings or numbers), then you can instead use `subModelSeq` to set up separate
+bindings for each item. And if the values are not distinct (i.e., can not be uniquely identified in the collection),
+then Elmish.WPF won’t be able to track which items are moved, and you can’t use this optimization.
-Note that you can always use `subModelSeq` instead of `oneWaySeq` (the opposite is not true.) The `oneWaySeq` binding is slightly simpler than `subModelSeq` if the elements are simple values that can be bound to directly.
+Note that you can always use `subModelSeq` instead of `oneWaySeq` (the opposite is not true.) The `oneWaySeq` binding is
+slightly simpler than `subModelSeq` if the elements are simple values that can be bound to directly.
### Lazy bindings
-You may find yourself doing potentially expensive work in one-way bindings. To facilitate simple optimization in these cases, Elmish.WPF provides the bindings `oneWayLazy`, `oneWayOptLazy`, and `oneWaySeqLazy`, which add [lazy updating](#lazy-updating) and [caching](#caching). These have two extra parameters: `equals` and `map`.
+You may find yourself doing potentially expensive work in one-way bindings. To facilitate simple optimization in these
+cases, Elmish.WPF provides the bindings `oneWayLazy`, `oneWayOptLazy`, and `oneWaySeqLazy`, which
+add [lazy updating](#lazy-updating) and [caching](#caching). These have two extra parameters: `equals` and `map`.
-As with the non-lazy bindings, the initial `get` function is called. For the lazy bindings, this should be cheap; it should basically just return what you need from from the model (e.g. a single item or a tuple or record with multiple items). Lazy updating is evaluated based on the value from `get`. Only if the binding updates is the output of `get` passed to `map`, which may be expensive.
+As with the non-lazy bindings, the initial `get` function is called. For the lazy bindings, this should be cheap; it
+should basically just return what you need from from the model (e.g. a single item or a tuple or record with multiple
+items). Lazy updating is evaluated based on the value from `get`. Only if the binding updates is the output of `get`
+passed to `map`, which may be expensive.
Modifying bindings
------------------
### Lazy updating
-For performance optimization, `Binding.addLazy` allows you to add an `equals` parameter to update the viewmodel only when necessary.
+For performance optimization, `Binding.addLazy` allows you to add an `equals` parameter to update the viewmodel only
+when necessary.
-`equals` is used to compare the current model with the previous. If equals returns true, the rest of the update process is skipped entirely. If equals returns false, the binding is updated normally.
+`equals` is used to compare the current model with the previous. If equals returns true, the rest of the update process
+is skipped entirely. If equals returns false, the binding is updated normally.
Elmish.WPF provides two helpers you can often use as the `equals` parameter: `refEq` and `elmEq`.
-- `refEq` is a good choice if `get` returns a single item (not an inline-created tuple, record, or other wrapper) from your model. It is simply an alias for `LanguagePrimitives.PhysicalEquality` (which is essentially `Object.ReferenceEquals` with better typing). Since the Elmish model is generally immutable, a reference equality check for the output of `get` is a very efficient way to short-circuit the update process. It may cause false negatives if two values are structurally equal but not referentially equal, but this should not be a common case, and structural equality may be prohibitively expensive if comparing e.g. large lists, defeating the purpose.
-- `elmEq` is a good choice if `get` returns multiple items from the model wrapped inline in a tuple or record. It will compare each member of the `get` return value separately (i.e. each record field, or each tuple item). Reference-typed members will be compared using reference equality, and string members and value-typed members will be compared using structural equality.
+- `refEq` is a good choice if `get` returns a single item (not an inline-created tuple, record, or other wrapper) from
+ your model. It is simply an alias for `LanguagePrimitives.PhysicalEquality` (which is essentially
+ `Object.ReferenceEquals` with better typing). Since the Elmish model is generally immutable, a reference equality
+ check for the output of `get` is a very efficient way to short-circuit the update process. It may cause false
+ negatives if two values are structurally equal but not referentially equal, but this should not be a common case, and
+ structural equality may be prohibitively expensive if comparing e.g. large lists, defeating the purpose.
+- `elmEq` is a good choice if `get` returns multiple items from the model wrapped inline in a tuple or record. It will
+ compare each member of the `get` return value separately (i.e. each record field, or each tuple item). Reference-typed
+ members will be compared using reference equality, and string members and value-typed members will be compared using
+ structural equality.
-You may pass any function you want for `equals`; it does not have to be one of the above. For example, if you want structural comparison (note the caveat above however), you can pass `(=)`.
+You may pass any function you want for `equals`; it does not have to be one of the above. For example, if you want
+structural comparison (note the caveat above however), you can pass `(=)`.
### Caching
For performance optimization, `Binding.addCaching` caches viewmodel values.
-Cached bindings store values retrieved using `get`, so if the view tries to get a value that has not yet been updated, the binding will return the previous value rather than calling `get` again. Non-cached bindings call `get` every time.
+Cached bindings store values retrieved using `get`, so if the view tries to get a value that has not yet been updated,
+the binding will return the previous value rather than calling `get` again. Non-cached bindings call `get` every time.
### Mapping bindings
-Sometimes duplicate mapping code exists across several bindings. The duplicate mappings could be from the parent model to a common child model or it could be the wrapping of a child message in a parent message, which might depend on the parent model. The duplicate mapping code can be extracted and written once using the mapping functions `mapModel`, `mapMsg`, and `mapMsgWithModel`.
+Sometimes duplicate mapping code exists across several bindings. The duplicate mappings could be from the parent model
+to a common child model or it could be the wrapping of a child message in a parent message, which might depend on the
+parent model. The duplicate mapping code can be extracted and written once using the mapping functions `mapModel`,
+`mapMsg`, and `mapMsgWithModel`.
#### Example use of `mapModel` and `mapMsg`
Here is a simple example that uses these model and message types.
+
```F#
type ChildModel =
{ GrandChild1: GrandChild1
@@ -455,6 +590,7 @@ let parentBindings () : Binding list = [
```
The functions `mapModel` and `mapMsg` can remove this duplication.
+
```F#
let childBindings () : Binding list = [
"GrandChild1" |> Binding.twoWay((fun child -> child.GrandChild1), SetGrandChild1)
@@ -469,20 +605,136 @@ let parentBindings () : Binding list =
#### Benefit for design-time view models
-With such duplicate mapping code extracted, it is easier to create a design-time view model for the XAML code containing the bindings to `GrandChild1` and `GrandChild2`. Specifically, instead of creating the design-time view model from the `parentBindings` bindings, it can now be created from the `childBindings` bindings. The `SubModelSeq` sample uses this benefit to create a design-time view model for `Counter.xaml`.
+With such duplicate mapping code extracted, it is easier to create a design-time view model for the XAML code containing
+the bindings to `GrandChild1` and `GrandChild2`. Specifically, instead of creating the design-time view model from the
+`parentBindings` bindings, it can now be created from the `childBindings` bindings. The `SubModelSeq` sample uses this
+benefit to create a design-time view model for `Counter.xaml`.
#### Theory behind `mapModel` and `mapMsg`
-A binding in Elmish.WPF is represented by an instance of type `Binding<'model, 'msg>`. It is a profunctor, which means that
+A binding in Elmish.WPF is represented by an instance of type `Binding<'model, 'msg>`. It is a profunctor, which means
+that
+
- it is a contravariant functor in `'model` with `mapModel` as the corresponding mapping function for this functor and
- it is a covariant functor in `'msg` with `mapMsg` as the corresponding mapping function for this functor.
+Choosing Your Binding Approach
+------------------------------
+
+Elmish.WPF offers two approaches for creating bindings between your F# model and WPF views. Understanding when to use
+each approach will help you make the best choice for your application.
+
+### Dynamic Bindings vs Statically-Typed ViewModels
+
+| Aspect | Dynamic Bindings | Statically-Typed ViewModels |
+|--------------------|------------------------------------|------------------------------------------|
+| **Syntax** | Functional, list-based | Object-oriented, property-based |
+| **Type Safety** | Runtime binding resolution | Compile-time binding validation |
+| **XAML Support** | Basic IntelliSense | Full IntelliSense and auto-completion |
+| **Design-time** | Limited design-time support | Rich design-time experience |
+| **Performance** | Good | Slightly better (direct property access) |
+| **Flexibility** | Very flexible, dynamic composition | More structured, explicit definitions |
+| **Learning Curve** | Familiar to functional programmers | Familiar to WPF/C# developers |
+| **Refactoring** | Manual updates needed | Automatic refactoring support |
+
+### When to Choose Dynamic Bindings
+
+Choose **dynamic bindings** when:
+
+- **Rapid prototyping**: Quick iteration and experimentation
+- **Simple applications**: Small apps with few UI interactions
+- **Functional preference**: You prefer functional composition over OOP
+- **Dynamic scenarios**: Bindings need to be created or modified at runtime
+- **MVU purists**: You want minimal view layer complexity
+- **Small teams**: Working alone or with F#-experienced developers
+
+### When to Choose Statically-Typed ViewModels
+
+Choose **statically-typed ViewModels** when:
+
+- **Large applications**: Complex apps with many views and interactions
+- **Design collaboration**: Working with UI designers who need design-time data
+- **Type safety**: Compile-time validation is critical
+- **XAML-heavy development**: Extensive use of XAML features and tooling
+- **Mixed teams**: Developers with varying F# experience levels
+- **Legacy integration**: Integrating with existing WPF/MVVM codebases
+- **Third-party tools**: Using tools that expect traditional ViewModels
+
+### Migration Between Approaches
+
+Both approaches use the same underlying MVU architecture, making migration straightforward:
+
+#### From Dynamic to Typed
+
+1. **Create ViewModel class**:
+ ```F#
+ []
+ type MyViewModel(args) =
+ inherit ViewModelBase(args)
+ new() = MyViewModel(initialModel |> ViewModelArgs.simple)
+ ```
+
+2. **Convert bindings to properties**:
+ ```F#
+ // From: "CounterValue" |> Binding.oneWay (fun m -> m.Count)
+ // To:
+ member _.CounterValue =
+ base.Get () (Binding.OneWayT.id >> Binding.mapModel (fun m -> m.Count))
+ ```
+
+3. **Handle TwoWay bindings**:
+ ```F#
+ // From: "StepSize" |> Binding.twoWay((fun m -> float m.StepSize), int >> SetStepSize)
+ // To:
+ let stepSizeBinding =
+ Binding.TwoWayT.id
+ >> Binding.mapModel (fun m -> float m.StepSize)
+ >> Binding.mapMsg (int >> SetStepSize)
+
+ member this.StepSize
+ with get () = base.Get () stepSizeBinding
+ and set (value) = base.Set (value) stepSizeBinding
+ ```
+
+4. **Update program creation**:
+ ```F#
+ // From: WpfProgram.mkSimple init update bindings
+ // To: WpfProgram.mkSimpleT init update MyViewModel
+ ```
+
+#### From Typed to Dynamic
+
+1. **Extract properties to binding list**:
+ ```F#
+ let bindings () = [
+ "CounterValue" |> Binding.oneWay (fun m -> m.Count)
+ "StepSize" |> Binding.twoWay((fun m -> float m.StepSize), int >> SetStepSize)
+ ]
+ ```
+
+2. **Update program creation**:
+ ```F#
+ // From: WpfProgram.mkSimpleT init update MyViewModel
+ // To: WpfProgram.mkSimple init update bindings
+ ```
+
+### Mixed Approach
+
+You can use both approaches within the same application:
+
+- Use dynamic bindings for simple, frequently-changing views
+- Use typed ViewModels for complex, stable views
+- Convert between approaches as requirements evolve
+
Statically-Typed View Models
----------------------------
### Inherit from `ViewModelBase<'model, 'msg>`
-If you want full design-time support for your view models, consider defining your view models as a class rather than as a list of bindings. This will give you lots of quality-of-life improvements when working in the XAML, such as type checking for errors, auto-completion, and static types to mention in `DataTemplate.DataType` and similar properties for template matching.
+If you want full design-time support for your view models, consider defining your view models as a class rather than as
+a list of bindings. This will give you lots of quality-of-life improvements when working in the XAML, such as type
+checking for errors, auto-completion, and static types to mention in `DataTemplate.DataType` and similar properties for
+template matching.
```F#
type [] CounterViewModel (args) =
@@ -499,30 +751,181 @@ type [] CounterViewModel (args) =
member _.Reset = base.Get() (Binding.CmdT.set Counter.canReset Counter.Reset)
```
+### Advantages of Statically-Typed ViewModels
+
+Statically-typed ViewModels provide several benefits over dynamic bindings:
+
+- **Compile-time safety**: XAML binding names are validated at compile time
+- **IntelliSense support**: Full auto-completion in XAML editors
+- **Design-time data**: Rich design-time experience in Visual Studio and Blend
+- **Refactoring support**: Reliable refactoring across view and view model
+- **Type safety**: Strongly-typed property access eliminates runtime binding errors
+- **Performance**: Slightly better performance due to direct property access
+
+### Basic Typed ViewModel Structure
+
+A typical typed ViewModel follows this pattern:
+
+```F#
+[]
+type MyViewModel(args) =
+ inherit ViewModelBase(args)
+
+ // Default constructor for design-time support
+ new() = MyViewModel(initialModel |> ViewModelArgs.simple)
+
+ // OneWay properties
+ member _.SomeValue =
+ base.Get () (Binding.OneWayT.id >> Binding.mapModel (fun m -> m.SomeValue))
+
+ // Command properties
+ member _.SomeCommand =
+ base.Get () (Binding.CmdT.setAlways SomeMessage)
+
+ // TwoWay properties (see detailed examples below)
+```
+
### Typed Bindings
-When creating a list of bindings, the output type of each property must be boxed (to `obj`) in order to insert them into the same list. With individually declared bindings on a class member, this restriction is lifted. Therefore we have a set of bindings (denoted with the `T` suffix on the function or the containing module) that do not box the output type.
+When creating a list of bindings, the output type of each property must be boxed (to `obj`) in order to insert them into
+the same list. With individually declared bindings on a class member, this restriction is lifted. Therefore we have a
+set of bindings (denoted with the `T` suffix on the function or the containing module) that do not box the output type.
#### Typed One-way Bindings
These bindings work very similarly to their non-`T` counterparts, except they make exclusive use of the composable api.
- `Binding.OneWayT.id`
-- `Binding.OneWayToSource.id`
+- `Binding.OneWayToSourceT.id`
- `Binding.CmdT.setAlways`
+Example usage:
+
+```F#
+// Simple one-way binding
+member _.CounterValue =
+ base.Get () (Binding.OneWayT.id >> Binding.mapModel (fun m -> m.Count))
+
+// One-way binding with lazy evaluation
+member _.ExpensiveCalculation =
+ base.Get () (Binding.OneWayT.id
+ >> Binding.addLazy (=)
+ >> Binding.mapModel calculateExpensiveValue)
+
+// Command binding
+member _.IncrementCommand =
+ base.Get () (Binding.CmdT.setAlways Increment)
+
+// Conditional command binding
+member _.ResetCommand =
+ base.Get () (Binding.CmdT.set (fun m -> m.Count <> 0) Reset)
+```
+
+#### Typed TwoWay Bindings
+
+TwoWay bindings in typed ViewModels require a specific pattern with getter and setter properties. This is crucial for
+proper WPF data binding:
+
+```F#
+[]
+type CounterViewModel(args) =
+ inherit ViewModelBase(args)
+
+ // Define the binding pipeline as a let-bound value
+ let stepSizeBinding =
+ Binding.TwoWayT.id
+ >> Binding.addLazy (=)
+ >> Binding.mapModel (fun m -> float m.StepSize) // Convert from model type
+ >> Binding.mapMsg (int >> SetStepSize) // Convert to message type
+
+ // Implement property with explicit getter and setter
+ member this.StepSize
+ with get () = base.Get () stepSizeBinding
+ and set (value) = base.Set (value) stepSizeBinding
+```
+
+**Important**: The binding variable (`stepSizeBinding`) must be defined as a `let` binding inside the class, and both
+`get()` and `set()` must be implemented for TwoWay bindings to work correctly.
+
+**Type Conversion**: Notice how we convert between model types (`int`) and WPF types (`float`) in the binding pipeline.
+This is common when binding to controls like `Slider` that use `float` values.
+
+#### Typed TwoWay Bindings with Validation
+
+You can add validation to TwoWay bindings in typed ViewModels:
+
+```F#
+let emailBinding =
+ Binding.TwoWayT.id
+ >> Binding.addLazy (=)
+ >> Binding.mapModel (fun m -> m.Email)
+ >> Binding.mapMsg SetEmail
+ >> Binding.addValidation (fun m ->
+ if String.IsNullOrEmpty(m.Email) then ["Email is required"]
+ elif not (m.Email.Contains("@")) then ["Invalid email format"]
+ else [])
+
+member this.Email
+ with get () = base.Get () emailBinding
+ and set (value) = base.Set (value) emailBinding
+```
+
#### Typed SubModel Bindings
-You can create strongly-typed SubModels in much the same way as you can create normal SubModels - simply replace the `Binding<'model, 'msg> list` with the constructor for a type that implements `ViewModelBase<'model, 'msg>(args)` and takes in that `args` parameter, and use one of the following functions to create the binding:
+You can create strongly-typed SubModels in much the same way as you can create normal SubModels - simply replace the
+`Binding<'model, 'msg> list` with the constructor for a type that implements `ViewModelBase<'model, 'msg>(args)` and
+takes in that `args` parameter, and use one of the following functions to create the binding:
+
+- `Binding.SubModelT.req` - Required sub-model
+- `Binding.SubModelT.opt` - Optional sub-model
+- `Binding.SubModelSeqUnkeyedT.id` - Collection without keys
+- `Binding.SubModelSeqKeyedT.id` - Collection with keys for efficient updates
+- `Binding.SubModelWinT.id` - Sub-model controlling window state
+
+Example of typed sub-model binding:
+
+```F#
+// Child ViewModel
+[]
+type CounterViewModel(args) =
+ inherit ViewModelBase(args)
+
+ member _.Count =
+ base.Get () (Binding.OneWayT.id >> Binding.mapModel (fun m -> m.Count))
+
+ member _.Increment =
+ base.Get () (Binding.CmdT.setAlways Counter.Increment)
+
+// Parent ViewModel
+[]
+type MainViewModel(args) =
+ inherit ViewModelBase(args)
+
+ member _.Counter =
+ base.Get () (Binding.SubModelT.req CounterViewModel
+ >> Binding.mapModel (fun m -> m.Counter)
+ >> Binding.mapMsg App.CounterMsg)
+```
+
+#### Typed SubModel Sequence Bindings
+
+For collections of sub-models, use `SubModelSeqKeyedT.id`:
-- `Binding.SubModelT.req`
-- `Binding.SubModelSeqUnkeyedT.id`
-- `Binding.SubModelSeqKeyedT.id`
-- `Binding.SubModelWinT.id`
+```F#
+[]
+type CounterListViewModel(args) =
+ inherit ViewModelBase(args)
+
+ member _.Counters =
+ base.Get () (Binding.SubModelSeqKeyedT.id CounterViewModel (fun m -> m.Id)
+ >> Binding.mapModel (fun m -> m.Counters)
+ >> Binding.mapMsg (fun (id, msg) -> CounterMsg(id, msg)))
+```
#### Typed WpfProgram Bindings
-Something very similar to the above transformation can also be accomplished at the top level by using one of the following `T` functions to create the program:
+Something very similar to the above transformation can also be accomplished at the top level by using one of the
+following `T` functions to create the program:
- `WpfProgram.mkSimpleT`
- `WpfProgram.mkProgramT`
@@ -530,6 +933,10 @@ Something very similar to the above transformation can also be accomplished at t
#### Mixing and matching bindings
-When migrating to a statically-typed view model, you can use all of the existing bindings as-is, with the exception of two-way bindings (must be split up into `OneWay` and `OneWayToSource` bindings). These will type as `obj` until you upgrade them to the equivalent `T` bindings.
+When migrating to a statically-typed view model, you can use all of the existing bindings as-is, with the exception of
+two-way bindings (must be split up into `OneWay` and `OneWayToSource` bindings). These will type as `obj` until you
+upgrade them to the equivalent `T` bindings.
-When intruducing a statically-typed view model as a submodel to a binding defined by the boxed binding list, simply use one of the new [Typed SubModel Bindings](#typed-submodel-bindings) and then call `Binding.boxT` to map the output type. (e.g., `"TypedSubModel" |> Binding.SubModelT.req TypedSubModel >> Binding.boxT`).
+When intruducing a statically-typed view model as a submodel to a binding defined by the boxed binding list, simply use
+one of the new [Typed SubModel Bindings](#typed-submodel-bindings) and then call `Binding.boxT` to map the output
+type. (e.g., `"TypedSubModel" |> Binding.SubModelT.req TypedSubModel >> Binding.boxT`).
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index aa03d24d..3338ef30 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -1,86 +1,124 @@
#### 4.0.0-beta-56
+
* Add NotifyPropertyChanged for the HasErrors property.
#### 4.0.0-beta-55
+
* Add TwoWayT bindings for workflows that reuse bindings.
* Add OneWayToSeqT bindings.
* Add vopt and opt bindings for OneWayT, OneWayToSourceT, and TwoWayT bindings.
* Add .NET 8 target and remove .NET Core 3.1 support
#### 4.0.0-beta-54
+
* Added the Readme file to the generated nuspec file so that it shows up on nuget.org.
* Added some debug logs to the performance logger at the top level.
* Brought up new functions to parity with the performance logger.
#### 4.0.0-beta-53
-* Corrected the threaded dispatch case when an immediate UI update was needed. Done by adding an unschedule `Dispatcher` job at the same priority as the other scheduler jobs, which will force it to run in the correct order before any of the execute update jobs arrive to the queue, thus preventing any of the old scheduler jobs from getting executed _after_ this immediate UI update.
-* Added debug logging with counters to represent the increasing sequence of `setUiState` being called and scheduled on other threads (to ensure proper ordering).
+
+* Corrected the threaded dispatch case when an immediate UI update was needed. Done by adding an unschedule `Dispatcher`
+ job at the same priority as the other scheduler jobs, which will force it to run in the correct order before any of
+ the execute update jobs arrive to the queue, thus preventing any of the old scheduler jobs from getting executed
+ _after_ this immediate UI update.
+* Added debug logging with counters to represent the increasing sequence of `setUiState` being called and scheduled on
+ other threads (to ensure proper ordering).
#### 4.0.0-beta-52
+
* Fixed a bug in the threaded dispatch case that could result in an out-of-date model being updated.
* Skip intermediate updates to view model when they queue up in the threaded case.
* Reverted the following change from beta-51 because Set caching is problematic:
- * Notify Property Changed directly on Set rather than waiting around for the next update. Set value is now cached until the next time update/dispatch is called.
+ * Notify Property Changed directly on Set rather than waiting around for the next update. Set value is now cached
+ until the next time update/dispatch is called.
#### 4.0.0-beta-51
+
* Allow skipping intermediate updates when they pile up on UI Thread.
-* Notify Property Changed directly on Set rather than waiting around for the next update. Set value is now cached until the next time update/dispatch is called.
+* Notify Property Changed directly on Set rather than waiting around for the next update. Set value is now cached until
+ the next time update/dispatch is called.
#### 4.0.0-beta-50
+
* Upgraded to Elmish v4
- * **BREAKING:** Changed syntax of `WpfProgram.withSubscription` to now take named list of `Subscribe<'msg> = Dispatch<'msg> -> IDisposable` (note the `IDisposable` return). This function now gets called every time `'model` updates (previously it was only called on startup). This allows starting and stopping of subscriptions from this function by the given string list identifier.
- * **BREAKING:** Changed `Sub<'msg>` to `Effect<'msg>` everywhere (`Effect<'msg> = Dispatch<'msg> -> unit`, see difference with `Subscribe<'msg>` above). Also renames helper functions. This does _not_ break `Cmd<'msg>`s that didn't name the `Sub<'msg>` type (or helpers).
- * Added `WpfProgram.withTermination` to allow conditional exit of the elmish loop on a specific `'msg`, as well as specify a side effect on exit.
+ * **BREAKING:** Changed syntax of `WpfProgram.withSubscription` to now take named list of
+ `Subscribe<'msg> = Dispatch<'msg> -> IDisposable` (note the `IDisposable` return). This function now gets called
+ every time `'model` updates (previously it was only called on startup). This allows starting and stopping of
+ subscriptions from this function by the given string list identifier.
+ * **BREAKING:** Changed `Sub<'msg>` to `Effect<'msg>` everywhere (`Effect<'msg> = Dispatch<'msg> -> unit`, see
+ difference with `Subscribe<'msg>` above). Also renames helper functions. This does _not_ break `Cmd<'msg>`s that
+ didn't name the `Sub<'msg>` type (or helpers).
+ * Added `WpfProgram.withTermination` to allow conditional exit of the elmish loop on a specific `'msg`, as well as
+ specify a side effect on exit.
* Added more documentation throughout the helpers.
* Updated some of the sample projects for clarity.
#### 4.0.0-beta-49
-* Added `Binding.SubModelT.seq` that allows a `seq` of static sub models that are all properly updated when there is a dispatch.
+
+* Added `Binding.SubModelT.seq` that allows a `seq` of static sub models that are all properly updated when there is a
+ dispatch.
#### 4.0.0-beta-48
+
* Added ability to run elmish update loop on a background thread rather than only on the main UI thread.
* Added a `Threading` sample project demonstrating above feature.
#### 4.0.0-beta-47
-* Improved `ViewModelBase` to infer view model property types from the Model getter rather than needing to be explicitly specified.
-* Added `'t` type parameter to `Binding<'model, 'msg, 't>` everywhere to support above feature. `Binding<'model, 'msg>` is defined as `Binding<'model, 'msg, obj>` for full backwards compatibility.
+
+* Improved `ViewModelBase` to infer view model property types from the Model getter rather than needing to be explicitly
+ specified.
+* Added `'t` type parameter to `Binding<'model, 'msg, 't>` everywhere to support above feature. `Binding<'model, 'msg>`
+ is defined as `Binding<'model, 'msg, obj>` for full backwards compatibility.
* Added `Binding.boxT` and `Binding.unboxT` to support moving back and forth between the two.
-* Added `Binding.OneWayT`, `Binding.OneWayToSourceT` and `Binding.CmdT` modules for creating strongly typed primitives for the top feature.
+* Added `Binding.OneWayT`, `Binding.OneWayToSourceT` and `Binding.CmdT` modules for creating strongly typed primitives
+ for the top feature.
* Added types internally to carry everything through (mostly provably) correctly.
-* Added `'viewModel` type parameter to `WpfProgram<'model, 'msg, 'viewModel>` to support using a static view model at the top level. `WpfProgram<'model, 'msg>` is defined as `WpfProgram<'model, 'msg, obj>` for full backwards compatibility. Also made `WpfProgram` core type more generic, replacing the list of bindings with equivalent but more flexible `CreateViewModel` and `UpdateViewModel` functions.
-* Added `WpfProgram.mkSimpleT`, `WpfProgram.mkProgramT` and `WpfProgram.mkProgramWithCmdMsgT` for making programs that use static view models as the top-level data context.
+* Added `'viewModel` type parameter to `WpfProgram<'model, 'msg, 'viewModel>` to support using a static view model at
+ the top level. `WpfProgram<'model, 'msg>` is defined as `WpfProgram<'model, 'msg, obj>` for full backwards
+ compatibility. Also made `WpfProgram` core type more generic, replacing the list of bindings with equivalent but more
+ flexible `CreateViewModel` and `UpdateViewModel` functions.
+* Added `WpfProgram.mkSimpleT`, `WpfProgram.mkProgramT` and `WpfProgram.mkProgramWithCmdMsgT` for making programs that
+ use static view models as the top-level data context.
* Modified `SubModelStatic` sample project to use new static view model features.
#### 4.0.0-beta-46
-* Added `ViewModelBase` which allows view models to be defined as static types with real properties rather than unnamed dynamic types with stringly named properties.
-* Added `Binding.SubModelT`, `Binding.SubModelSeqUnkeyedT`, `Binding.SubModelSeqKeyedT` and `Binding.SubModelWinT` modules for creating these static types as sub models.
+
+* Added `ViewModelBase` which allows view models to be defined as static types with real properties rather than unnamed
+ dynamic types with stringly named properties.
+* Added `Binding.SubModelT`, `Binding.SubModelSeqUnkeyedT`, `Binding.SubModelSeqKeyedT` and `Binding.SubModelWinT`
+ modules for creating these static types as sub models.
* Replaced internal usage of refs in dynamic view model with get/set functions to allow for matting of the type.
* Added some internal types to support `ViewModelBase`.
* Improved documentation.
* Added a `SubModelStatic` sample project using above feature.
#### 4.0.0-beta-45
+
* Improved performance of Lazy effect by reducing calls to later model mappings
* Removed `SourceOrTarget` and `DuplicateIdException` from public API (added in `4.0.0-beta-42`)
* Improved caching effect to not invalidating the cache too early (an issue introduced in version `3.5.3 `via PR 181)
* Renamed `Binding.SetMsgWithModel` to `Binding.setMsgWithModel` (breaking public API added in `4.0.0-beta-42`)
-* Fixed bug with `mapMsgWithModel` and `setMsgWithModel` where the original model was sometimes given (an issue introduced in `4.0.0-beta-42`)
+* Fixed bug with `mapMsgWithModel` and `setMsgWithModel` where the original model was sometimes given (an issue
+ introduced in `4.0.0-beta-42`)
* Replaced framework targets `net461` and `net5.0-windows` with `net480` and `net6.0-windows` respectively.
* Updated minimum `FSharp.Core` version to `6.0.5`.
* Updated minimum `Microsoft.Extensions.Logging.Abstractions` version to `6.0.1`.
* Fixed broken log statement called when a `SubModelSelectedItem` binding can't find an item in a `SubModelSeq` binding.
#### 4.0.0-beta-44
+
* Fixed sticky effect that was broken in `4.0.0-beta-42`
#### 4.0.0-beta-43
+
* Added `WpfProgram.withElmishErrorHandler`
* Improved debugging experience by overriding `GetDynamicMemberNames`
* Relaxed version constrains on `FSharp.Core` and `Microsoft.Extensions.Logging.Abstractions`
#### 4.0.0-beta-42
+
* Improved API of `WindowState<_>`
-* Dropped support for .NET Core 3.0. Still have support for .NET Core 3.1.
+* Dropped support for .NET Core 3.0. Still have support for .NET Core 3.1.
* Added `setMsg` in the `Binding` module
* Added `setMsgWithModel` in the `Binding` module
* Removed `id` in the `Binding` module (that was added in 4.0.0-beta-3)
@@ -92,7 +130,8 @@
* Added to the `SubModelSeq` method API an overload that only takes bindings
* Added support for a `SubModelSeq` variant that does involve IDs
* `SubModelSeq` variant that involves IDs will now merge elements without considering IDs if duplicate IDs are detected
-* Fixed bug (present in 3.5.8) where `ArgumentNullException` is thrown from `INotifyDataErrorInfo.GetErrors` when given `null`
+* Fixed bug (present in 3.5.8) where `ArgumentNullException` is thrown from `INotifyDataErrorInfo.GetErrors` when given
+ `null`
* Now logging and returning `false` to WPF if selection in a `SubModelSelectedItem` binding fails
* Added prebuilt bindings for `Selector.SelectedIndex`
* Added `Binding.TwoWay.id` to API
@@ -100,28 +139,38 @@
* Added one-way-to-source binding
* Added function-based API for for one-way bindings
* Added composable monomorphic dispatch wrapping
-* Switched the order of inputs in the function given to `Binding.mapMsgWithModel`. This is breaking for public API introduced in 4.0.0-beta-1.
-* Added `alterMsgStream` to API. This feature is a replacement for what was previously called `wrapDispatch`.
+* Switched the order of inputs in the function given to `Binding.mapMsgWithModel`. This is breaking for public API
+ introduced in 4.0.0-beta-1.
+* Added `alterMsgStream` to API. This feature is a replacement for what was previously called `wrapDispatch`.
#### 4.0.0-beta-41
-* Fixed "backwards typing" (#373) and other bugs (like #371) introduced in 4.0.0-beta-40 with more careful use of the Dispatcher (#374)
+
+* Fixed "backwards typing" (#373) and other bugs (like #371) introduced in 4.0.0-beta-40 with more careful use of the
+ Dispatcher (#374)
#### 4.0.0-beta-40
+
* Removed recently added trace logging of INotifyDataErrorInfo.HasErrors (#354)
* Fixed typos in documentation and logging (#357)
* Fixed race condition with Dispatcher (#359)
* Removed overload of `ViewModel<_,_>.ToString` because of slow performance (#370)
#### 4.0.0-beta-3
+
* Added support for composable binding stickiness `sticky`
* Added support for composable binding validation `withValidation`
* Added support for composable binding laziness via `lazy'`
* Improved logging
* Changed CurrentModel and UpdateModel on ViewModel<_,_> from public to internal
-* `runWindow` now shows the given window after settings its `DataContext`. This removes the need to have `Visibility` values default to `Collapsed`.
-* Changed minimum Elmish version from 3.0.3 to 3.1.0 (which is currently the latest). Commands created with `OfAsync` are now executed on a threadpool thread. For example, it is now easier to show file dialogs without blocking the Elmish dispatch loop. See [this diff](https://github.com/elmish/Elmish.WPF/commit/d1ec823ccd7f377a860b76bc2358706dc6a70c84).
+* `runWindow` now shows the given window after settings its `DataContext`. This removes the need to have `Visibility`
+ values default to `Collapsed`.
+* Changed minimum Elmish version from 3.0.3 to 3.1.0 (which is currently the latest). Commands created with `OfAsync`
+ are now executed on a threadpool thread. For example, it is now easier to show file dialogs without blocking the
+ Elmish dispatch loop.
+ See [this diff](https://github.com/elmish/Elmish.WPF/commit/d1ec823ccd7f377a860b76bc2358706dc6a70c84).
#### 4.0.0-beta-2
+
* Added logging when INotifyDataErrorInfo.HasErrors is called
* Fixed bug in INotifyDataErrorInfo.HasErrors where `true` always returned after `true` first correctly returned
@@ -129,34 +178,46 @@
* **Breaking:** Removed the obsolete binding functions in the `BindingFn` module
* **Breaking:** Removed the obsolete function `Elmish.WPF.Cmd.showWindow`
-* **Breaking:** Removed all occurrences of the argument `wrapDispatch` from the methods used to create a binding. There is currently no migration path. Please create an issue if this is a negative impact for you.
+* **Breaking:** Removed all occurrences of the argument `wrapDispatch` from the methods used to create a binding. There
+ is currently no migration path. Please create an issue if this is a negative impact for you.
* **Breaking:** App initialization is now done using the `WpfProgram` module instead of the `Program` module
-* **Breaking:** Removed `ElmConfig`. For controlling logging, see below. For specifying a binding performance log threshold (corresponding to the old `ElmConfig.MeasureLimitMs` field), use `WpfProgram.withBindingPerformanceLogThreshold`
-* **Breaking:** The method `Binding.oneWaySeq` is implemented by calling the method `Binding.oneWaySeqLazy` with `equals` = `refEq` and `map` = `id`. This is a breaking change when using a mutable data structure for the sequence. Compensate by directly calling `Binding.oneWaySeqLazy` with `equals` = `fun _ _ = false`.
-* **Breaking:** Some calls to `Binding` methods now include an equality constraint. This only is only breaking if the corresponding type included the `NoEquality` attribute.
+* **Breaking:** Removed `ElmConfig`. For controlling logging, see below. For specifying a binding performance log
+ threshold (corresponding to the old `ElmConfig.MeasureLimitMs` field), use
+ `WpfProgram.withBindingPerformanceLogThreshold`
+* **Breaking:** The method `Binding.oneWaySeq` is implemented by calling the method `Binding.oneWaySeqLazy` with
+ `equals` = `refEq` and `map` = `id`. This is a breaking change when using a mutable data structure for the sequence.
+ Compensate by directly calling `Binding.oneWaySeqLazy` with `equals` = `fun _ _ = false`.
+* **Breaking:** Some calls to `Binding` methods now include an equality constraint. This only is only breaking if the
+ corresponding type included the `NoEquality` attribute.
* Added binding mapping functions
- * Added `mapModel`, `mapMsg`, and `mapMsgWithModel` in both the `Binding` and `Bindings` modules
- * These functions enable common model and message mapping logic to be extracted
- * See the `SubModelSeq` sample for an excellent use of `mapModel` and `mapMsg`
+ * Added `mapModel`, `mapMsg`, and `mapMsgWithModel` in both the `Binding` and `Bindings` modules
+ * These functions enable common model and message mapping logic to be extracted
+ * See the `SubModelSeq` sample for an excellent use of `mapModel` and `mapMsg`
* Improved logging:
- * Now uses `Microsoft.Extensions.Logging` for wide compatibility and easy integration into common log frameworks
- * Use `WpfProgram.WithLogger` to pass an `ILoggerFactory` for your chosen log framework
- * Can control specific log categories
- * See the samples for a demonstration using Serilog
+ * Now uses `Microsoft.Extensions.Logging` for wide compatibility and easy integration into common log frameworks
+ * Use `WpfProgram.WithLogger` to pass an `ILoggerFactory` for your chosen log framework
+ * Can control specific log categories
+ * See the samples for a demonstration using Serilog
#### 3.5.8
+
* Removed overload of `ViewModel<_,_>.ToString` because of slow performance (#370)
#### 3.5.7
+
* Excluded 4.* prereleases from possibilities for version of Elmish dependency
* Added support for multiple validation errors
* Added target `net5.0-windows`
-* `ViewModel<'model, 'msg>` now overrides `object.ToString()` and returns a string representation of the current `'model` instance. This is only intended for debugging. No guarantees are given about the exact structure of the returned string.
+* `ViewModel<'model, 'msg>` now overrides `object.ToString()` and returns a string representation of the current
+ `'model` instance. This is only intended for debugging. No guarantees are given about the exact structure of the
+ returned string.
* Fixed incorrect spelling of a word in a log message
#### 3.5.6
-* The amount of time used to update `OneWaySeq` and `SubModelSeq` bindings has been significantly decreased. This includes all cases of a `SubModelSeq` binding and all cases of a `OneWaySeq` binding for which `equals` returns `false`.
+* The amount of time used to update `OneWaySeq` and `SubModelSeq` bindings has been significantly decreased. This
+ includes all cases of a `SubModelSeq` binding and all cases of a `OneWaySeq` binding for which `equals` returns
+ `false`.
#### 3.5.5
@@ -192,7 +253,8 @@
#### 3.3.0
-* Added optional dispatch wrapper to two-way bindings and command bindings, which allows dispatches to be throttled/debounced etc.
+* Added optional dispatch wrapper to two-way bindings and command bindings, which allows dispatches to be
+ throttled/debounced etc.
#### 3.2.1
@@ -200,33 +262,50 @@
#### 3.2.0
-* Added proper dialog/window support using `Binding.subModelWin`. See [the readme](https://github.com/elmish/Elmish.WPF/tree/feature-windows-binding#can-i-open-new-windowsdialogs) for more and the [NewWindow sample](https://github.com/elmish/Elmish.WPF/tree/master/src/Samples) for an example.
+* Added proper dialog/window support using `Binding.subModelWin`.
+ See [the readme](https://github.com/elmish/Elmish.WPF/tree/feature-windows-binding#can-i-open-new-windowsdialogs) for
+ more and the [NewWindow sample](https://github.com/elmish/Elmish.WPF/tree/master/src/Samples) for an example.
* Deprecated `Cmd.showWindow` (use `Binding.subModelWin` instead)
#### 3.1.0
-* Added `Program.withDebugTrace` which is similar to `withConsoleTrace` but writes using `System.Diagnostics.Debug.WriteLine` (e.g. to the VS output window)
+* Added `Program.withDebugTrace` which is similar to `withConsoleTrace` but writes using
+ `System.Diagnostics.Debug.WriteLine` (e.g. to the VS output window)
#### 3.0.0
* The most massive (and hopefully useful) update yet!
-* Breaking: Overload-based syntax for `Binding`. The old `Binding` module is deprecated and renamed to `BindingFn`. The new `Binding` is a static class with static methods, providing many overloads for flexibility. To migrate, replace all occurrences of `Binding.` with `BindingFn.` and follow the deprecation warnings.
-* Breaking: The `Elmish.WPF.Internal` namespace has been removed and everything in it that should actually be internal has been marked `internal`. This includes `ViewModel`.
-* Breaking: `Elmish.WPF.Internal.BindingSpec<_,_>` has been moved/renamed to `Elmish.WPF.Binding<_,_>`. It should thus be more pleasant to use in type annotations.
-* Breaking: `Elmish.WPF.Utilities.ViewModel.designInstance` has been moved to `Elmish.WPF.ViewModel`. Furthermore, it returns `obj` since `ViewModel` is internal.
-* Breaking: Removed `twoWayIfValid`. It hasn’t worked for a while due to core Elmish internals, and was of suspect utility anyway.
+* Breaking: Overload-based syntax for `Binding`. The old `Binding` module is deprecated and renamed to `BindingFn`. The
+ new `Binding` is a static class with static methods, providing many overloads for flexibility. To migrate, replace all
+ occurrences of `Binding.` with `BindingFn.` and follow the deprecation warnings.
+* Breaking: The `Elmish.WPF.Internal` namespace has been removed and everything in it that should actually be internal
+ has been marked `internal`. This includes `ViewModel`.
+* Breaking: `Elmish.WPF.Internal.BindingSpec<_,_>` has been moved/renamed to `Elmish.WPF.Binding<_,_>`. It should thus
+ be more pleasant to use in type annotations.
+* Breaking: `Elmish.WPF.Utilities.ViewModel.designInstance` has been moved to `Elmish.WPF.ViewModel`. Furthermore, it
+ returns `obj` since `ViewModel` is internal.
+* Breaking: Removed `twoWayIfValid`. It hasn’t worked for a while due to core Elmish internals, and was of suspect
+ utility anyway.
* New: Many more helpful `Binding` signatures available due to the new overload-based syntax.
-* New: More general `Binding.subModel` and `Binding.subModelSeq` overloads that allow a more idiomatic Elm architecture even with static views. For background information, see [#86](https://github.com/elmish/Elmish.WPF/issues/86) (the issue is otherwise outdated).
-* New: Sticky `subModelOpt` bindings that returns the last non-null model when model is `None` (useful when animating out stuff)
-* New: `elmEq` and `refEq` as useful equality defaults for lazy bindings. `elmEq` efficiently uses reflection to do a comparison for each member that is referential for reference types except strings, and structural for strings and value types.
+* New: More general `Binding.subModel` and `Binding.subModelSeq` overloads that allow a more idiomatic Elm architecture
+ even with static views. For background information, see [#86](https://github.com/elmish/Elmish.WPF/issues/86) (the
+ issue is otherwise outdated).
+* New: Sticky `subModelOpt` bindings that returns the last non-null model when model is `None` (useful when animating
+ out stuff)
+* New: `elmEq` and `refEq` as useful equality defaults for lazy bindings. `elmEq` efficiently uses reflection to do a
+ comparison for each member that is referential for reference types except strings, and structural for strings and
+ value types.
* New: `Program.mkSimpleWpf` and `Program.mkProgramWpf` with more WPF-friendly signatures.
-* New: `Program.mkProgramWpfWithCmdMsg` for easily following the `CmdMsg` pattern to allow testable commands. See the FAQ in the readme for details.
+* New: `Program.mkProgramWpfWithCmdMsg` for easily following the `CmdMsg` pattern to allow testable commands. See the
+ FAQ in the readme for details.
* New: `Cmd.showWindow` helper to open a new window.
* New: Slow calls can be logged (configurable threshold).
-* New: Made available `Program.startElmishLoop` which is a low-level function that starts an Elmish loop given an Elmish `Program` and a WPF `FrameworkElement`. You probably won’t need it.
+* New: Made available `Program.startElmishLoop` which is a low-level function that starts an Elmish loop given an Elmish
+ `Program` and a WPF `FrameworkElement`. You probably won’t need it.
* Improvement: Logs now indicate the binding path.
* Improvement: Possibly better performance due to internals now using `ValueOption` instead of `Option`.
-* Improvement: Finally added (lots of) unit tests, so confidence of correct functionality is higher. (No critical bugs were found when creating the tests.)
+* Improvement: Finally added (lots of) unit tests, so confidence of correct functionality is higher. (No critical bugs
+ were found when creating the tests.)
#### 2.0.0
@@ -261,11 +340,14 @@
#### 2.0.0-beta-5
-* Fix `subModelSeq` items being unselected during updates
+* Fix `subModelSeq` items being unselected during updates
#### 2.0.0-beta-4
-* Breaking: Change order of `oneWayLazyWith` arguments to and rename it to `oneWayLazy`, removing the existing `oneWayLazy` function. The rationale is explained in [#60](https://github.com/elmish/Elmish.WPF/issues/60) . To migrate from 2.0.0-beta-3 to 2.0.0-beta-4: Add `(=)` as the `equals` parameter to `oneWayLazy` usages, and rename `oneWayLazyWith` usages to `oneWayLazy`.
+* Breaking: Change order of `oneWayLazyWith` arguments to and rename it to `oneWayLazy`, removing the existing
+ `oneWayLazy` function. The rationale is explained in [#60](https://github.com/elmish/Elmish.WPF/issues/60) . To
+ migrate from 2.0.0-beta-3 to 2.0.0-beta-4: Add `(=)` as the `equals` parameter to `oneWayLazy` usages, and rename
+ `oneWayLazyWith` usages to `oneWayLazy`.
* Add `Binding.oneWaySeqLazy`
#### 2.0.0-beta-3
@@ -279,14 +361,17 @@
#### 2.0.0-beta-1
* Complete rewrite, several breaking changes and new features
-* `twoWayValidation` is called `twoWayIfValid` (because that’s what it is, and it clearly separates it from the new `twoWayValidate`)
+* `twoWayValidation` is called `twoWayIfValid` (because that’s what it is, and it clearly separates it from the new
+ `twoWayValidate`)
* `oneWayMap` is called `oneWayLazy` (its implementation has changed, and the use case has expanded, but is similar)
* `cmd` and `cmdIf` have been renamed `paramCmd` and `paramCmdIf`, because the old names have new signatures/use-cases
-* `model` has been renamed `subModel` because it’s more clear, and consistent with the new `subModelOpt` and `subModelSeq`
+* `model` has been renamed `subModel` because it’s more clear, and consistent with the new `subModelOpt` and
+ `subModelSeq`
* `Program.runDebugWindow` has been removed in favour of `Program.runWindowWithConfig`
* Bundled Elmish has been removed, and Elmish 2.0 is used as an external dependency
* Any `Application ` instance instantiated before calling `Program.run...` will now be used
-* Several new functions in the `Binding` module; dot into it in your IDE or see the repository for samples or source code
+* Several new functions in the `Binding` module; dot into it in your IDE or see the repository for samples or source
+ code
#### 1.0.0-beta-7
diff --git a/TUTORIAL.md b/TUTORIAL.md
index 0d531b0e..f7e3a8d9 100644
--- a/TUTORIAL.md
+++ b/TUTORIAL.md
@@ -3,44 +3,61 @@ Elmish.WPF Tutorial
-The aim of this tutorial is to explain how to use Elmish.WPF, building in complexity from start (what is MVU?) to end (using complex bindings and applying optimizations).
+The aim of this tutorial is to explain how to use Elmish.WPF, building in complexity from start (what is MVU?) to end (
+using complex bindings and applying optimizations).
-This tutorial is not directly related to the many samples in the Elmish.WPF repository, but complements them well. The samples are complete, fully functional apps demonstrating selected aspects of Elmish.WPF. The samples *show*; the tutorial *explains*.
+This tutorial is not directly related to the many samples in the Elmish.WPF repository, but complements them well. The
+samples are complete, fully functional apps demonstrating selected aspects of Elmish.WPF. The samples *show*; the
+tutorial *explains*.
-This tutorial assumes working F# knowledge. If you’re new to F#, Scott Wlaschin’s blog [F# for fun and profit](https://fsharpforfunandprofit.com/) is a great place to start (and continue) learning the ins and outs of F# and functional programming. His book [Domain Modeling Made Functional](https://pragprog.com/book/swdddf/domain-modeling-made-functional) is also a great resource for learning F# (and in particular how it can be used for domain modeling). You can find many more excellent resources at [fsharp.org](https://fsharp.org).
+This tutorial assumes working F# knowledge. If you’re new to F#, Scott Wlaschin’s
+blog [F# for fun and profit](https://fsharpforfunandprofit.com/) is a great place to start (and continue) learning the
+ins and outs of F# and functional programming. His
+book [Domain Modeling Made Functional](https://pragprog.com/book/swdddf/domain-modeling-made-functional) is also a great
+resource for learning F# (and in particular how it can be used for domain modeling). You can find many more excellent
+resources at [fsharp.org](https://fsharp.org).
This tutorial also assumes some knowledge of WPF and MVVM.
-Suggestions for improvements are welcome. For large changes, please open an issue. For small changes (e.g. typos), simply submit a PR.
+Suggestions for improvements are welcome. For large changes, please open an issue. For small changes (e.g. typos),
+simply submit a PR.
Table of contents
-----------------
* [The MVU (Elm/Elmish) architecture](#the-mvu-elmelmish-architecture)
- + [Model](#model)
- + [Message](#message)
- + [Update](#update)
- + [View in standard MVU (not Elmish.WPF)](#view-in-standard-mvu-not-elmishwpf)
- + [View in Elmish.WPF](#view-in-elmishwpf)
- + [Commands (and subscriptions)](#commands-and-subscriptions)
+ + [Model](#model)
+ + [Message](#message)
+ + [Update](#update)
+ + [View in standard MVU (not Elmish.WPF)](#view-in-standard-mvu-not-elmishwpf)
+ + [View in Elmish.WPF](#view-in-elmishwpf)
+ + [Commands (and subscriptions)](#commands-and-subscriptions)
* [Some MVU tips for beginners](#some-mvu-tips-for-beginners)
- + [Normalize your model; use IDs instead of duplicating entities](#normalize-your-model-use-ids-instead-of-duplicating-entities)
- + [Use commands for anything impure](#use-commands-for-anything-impure)
- + [Child components and scaling](#child-components-and-scaling)
- + [Optimize easily with memoization](#optimize-easily-with-memoization)
+ + [Normalize your model; use IDs instead of duplicating entities](#normalize-your-model-use-ids-instead-of-duplicating-entities)
+ + [Use commands for anything impure](#use-commands-for-anything-impure)
+ + [Child components and scaling](#child-components-and-scaling)
+ + [Optimize easily with memoization](#optimize-easily-with-memoization)
* [Getting started with Elmish.WPF](#getting-started-with-elmishwpf)
* [Additional resources](#additional-resources)
The MVU (Elm/Elmish) architecture
---------------------------------
-MVU stands for Model-View-Update. It is a purely functional front-end architecture commonly used in Elm, a strongly typed pure functional language that compiles to JavaScript.
+MVU stands for Model-View-Update. It is a purely functional front-end architecture commonly used in Elm, a strongly
+typed pure functional language that compiles to JavaScript.
### Model
-The “model” part of the MVU name refers to an immutable data structure that contains all the state in your app. By “all the state” we mean all the state that influences any kind of domain/business logic, and all the state that is needed to render the UI. By storing all the state in a single “atom”, data synchronization problems between different parts of the app are a thing of the past. Note that the model is concerned with domain concepts, not UI concepts. Ideally (if not always in practice), you should be able to use the same model to target different UIs using the MVU pattern (a WPF app, a React web app using [Feliz](https://github.com/Zaid-Ajaj/Feliz/), a console app using [Terminal.Gui.Elmish](https://github.com/DieselMeister/Terminal.Gui.Elmish), etc.)
+The “model” part of the MVU name refers to an immutable data structure that contains all the state in your app. By “all
+the state” we mean all the state that influences any kind of domain/business logic, and all the state that is needed to
+render the UI. By storing all the state in a single “atom”, data synchronization problems between different parts of the
+app are a thing of the past. Note that the model is concerned with domain concepts, not UI concepts. Ideally (if not
+always in practice), you should be able to use the same model to target different UIs using the MVU pattern (a WPF app,
+a React web app using [Feliz](https://github.com/Zaid-Ajaj/Feliz/), a console app
+using [Terminal.Gui.Elmish](https://github.com/DieselMeister/Terminal.Gui.Elmish), etc.)
-For example, the type definition below may be the whole state for an app containing a single counter that you can increment/decrement by a customizable step size (the classic “hello world” of MVU apps):
+For example, the type definition below may be the whole state for an app containing a single counter that you can
+increment/decrement by a customizable step size (the classic “hello world” of MVU apps):
```f#
type Model = {
@@ -62,9 +79,12 @@ let init () = {
### Message
-While not part of the MVU name, the message is a central component. It’s just a type that specifies everything that can happen in your app – all the reasons your state may change (all of the “events” in the app, if you will). It’s typically modelled by a discriminated union.
+While not part of the MVU name, the message is a central component. It’s just a type that specifies everything that can
+happen in your app – all the reasons your state may change (all of the “events” in the app, if you will). It’s typically
+modelled by a discriminated union.
-For example, the type definition below may describe all the possible things that can happen in the counter app described above:
+For example, the type definition below may describe all the possible things that can happen in the counter app described
+above:
```f#
type Msg =
@@ -79,9 +99,12 @@ As with the model, the message type is concerned with the domain, and is ideally
### Update
-The “update” part of the MVU name refers to the function that is responsible for updating your model in response to incoming messages. It has the signature `'msg -> 'model -> 'model`. In other words, it is a pure function that accepts a message (something that happened) and the old state, and returns the new state.
+The “update” part of the MVU name refers to the function that is responsible for updating your model in response to
+incoming messages. It has the signature `'msg -> 'model -> 'model`. In other words, it is a pure function that accepts a
+message (something that happened) and the old state, and returns the new state.
-For example, for the counter app we have defined the model and message types for, the `update` function will look like this:
+For example, for the counter app we have defined the model and message types for, the `update` function will look like
+this:
```f#
let update (msg: Msg) (model: Model) : Model =
@@ -95,11 +118,15 @@ let update (msg: Msg) (model: Model) : Model =
This is where MVU frameworks will differ, since every UI technology is different.
-At its core, `view` is a function that accepts 1) a model and 2) a function to dispatch messages, and returns something that specifies how the UI will be rendered. This may in theory be the actual UI, though that would be very inefficient. Generally, `view` returns a “shadow DOM” (a cheap object graph reflecting the UI) that the framework will intelligently compare with the actual UI so that only the changed parts of the UI will be updated.
+At its core, `view` is a function that accepts 1) a model and 2) a function to dispatch messages, and returns something
+that specifies how the UI will be rendered. This may in theory be the actual UI, though that would be very inefficient.
+Generally, `view` returns a “shadow DOM” (a cheap object graph reflecting the UI) that the framework will intelligently
+compare with the actual UI so that only the changed parts of the UI will be updated.
In other words: **In MVU, the UI is simply a function of the current model**.
-For example, the function below shows how the UI function might look like for the counter app above (using an imaginary UI library/syntax):
+For example, the function below shows how the UI function might look like for the counter app above (using an imaginary
+UI library/syntax):
```f#
let view (model: Model) (dispatch: Msg -> unit) =
@@ -133,11 +160,15 @@ Therefore, you will normally see `dispatch` typed as `Dispatch<'msg>` instead of
### View in Elmish.WPF
-The `view` example above shows *dynamic views*, which is how “proper” MVU works. Creating views as a simple function of the model is a very powerful technique, is conceptually very simple, and allows for good composability.
+The `view` example above shows *dynamic views*, which is how “proper” MVU works. Creating views as a simple function of
+the model is a very powerful technique, is conceptually very simple, and allows for good composability.
-In Elmish.WPF, however, the views are defined externally in XAML. The UI is *static* and is not defined or changed by the `view` code; hence, Elmish.WPF is said to use *static views*.
+In Elmish.WPF, however, the views are defined externally in XAML. The UI is *static* and is not defined or changed by
+the `view` code; hence, Elmish.WPF is said to use *static views*.
-You set up bindings in the XAML views as you normally would if using MVVM. Then, in the `view` function, you use Elmish.WPF to declaratively create a “view model” of sorts that contain the data the view will bind to. Therefore the `view` function is normally called `bindings` in Elmish.WPF.
+You set up bindings in the XAML views as you normally would if using MVVM. Then, in the `view` function, you use
+Elmish.WPF to declaratively create a “view model” of sorts that contain the data the view will bind to. Therefore the
+`view` function is normally called `bindings` in Elmish.WPF.
For example, the counter app may look like this:
@@ -167,23 +198,41 @@ type CounterViewModel(args) =
and set(v) = base.Set(v) (Binding.OneWayToSourceT.id >> Binding.mapMsg Counter.Msg.SetStepSize)
```
-The actual bindings will be explained in detail later, but explained simply, the code above will create a view-model with:
+The actual bindings will be explained in detail later, but explained simply, the code above will create a view-model
+with:
- an `int` get-only property `CounterValue` returning `model.Count`
-- two get-only properties `Increment` and `Decrement` that are `ICommand`s that can always execute and, when executed, dispatches the `Increment` and `Decrement` messages, respectively
-- a `float` get-set property `StepSize` returning `model.StepSize` and which, when set, dispatches the `SetStepSize` message with the number
-
-Another important difference between normal MVU `view` functions and Elmish.WPF’s `update` function is that `view` is called every time the model has been updated, whereas `bindings` is only called once, when the “view model” is initialized. After that, it is the functions used in the bindings themselves that are called when the model is updated. Therefore, `bindings` do not accept a `model` or `dispatch` parameter. The `model` is instead passed separately in each binding, and the `dispatch` isn’t visible at all; you simply specify the message to be dispatched, and Elmish.WPF will take care of dispatching the message.
-
-In the statically-typed version, the bindings for individual properties work in exactly the same way as above, with a few exceptions. The `model` and `dispatch` parameters are passed in the default `CounterViewModel(args)` constructor through the `args` parameter, but this is immediately passed into the `inherit ViewModelBase(args)` line to surface up through the `base.Get()` and `base.Set(v)` helpers in exactly the same way as above. Also there is no `twoWay` with properties, as the getter and the setter of a property must be defined separately.
+- two get-only properties `Increment` and `Decrement` that are `ICommand`s that can always execute and, when executed,
+ dispatches the `Increment` and `Decrement` messages, respectively
+- a `float` get-set property `StepSize` returning `model.StepSize` and which, when set, dispatches the `SetStepSize`
+ message with the number
+
+Another important difference between normal MVU `view` functions and Elmish.WPF’s `update` function is that `view` is
+called every time the model has been updated, whereas `bindings` is only called once, when the “view model” is
+initialized. After that, it is the functions used in the bindings themselves that are called when the model is updated.
+Therefore, `bindings` do not accept a `model` or `dispatch` parameter. The `model` is instead passed separately in each
+binding, and the `dispatch` isn’t visible at all; you simply specify the message to be dispatched, and Elmish.WPF will
+take care of dispatching the message.
+
+In the statically-typed version, the bindings for individual properties work in exactly the same way as above, with a
+few exceptions. The `model` and `dispatch` parameters are passed in the default `CounterViewModel(args)` constructor
+through the `args` parameter, but this is immediately passed into the `inherit ViewModelBase(args)` line to
+surface up through the `base.Get()` and `base.Set(v)` helpers in exactly the same way as above. Also there is no
+`twoWay` with properties, as the getter and the setter of a property must be defined separately.
### Commands (and subscriptions)
-This is yet another part of MVU that is not in the name. Not to be confused with WPF’s `ICommand`, the command in MVU is the only way you do side effects.
+This is yet another part of MVU that is not in the name. Not to be confused with WPF’s `ICommand`, the command in MVU is
+the only way you do side effects.
-Think about it: If the update function must be pure, how can we do side effects like making an HTTP call or reading from disk? Or alternatively, if we decided to make `update` impure (which is possible in F#, but not in Elm) and do some long-running IO there, wouldn’t that block the whole app (since the update loop can only process one message at a time for concurrency reasons)?
+Think about it: If the update function must be pure, how can we do side effects like making an HTTP call or reading from
+disk? Or alternatively, if we decided to make `update` impure (which is possible in F#, but not in Elm) and do some
+long-running IO there, wouldn’t that block the whole app (since the update loop can only process one message at a time
+for concurrency reasons)?
-The answer is that there are actually two variants of the `update` function: For very simple apps, as shown above, you can use the simple `update` version that just returns the new model. For more complex apps that need to use commands, the `update` function can return both the new model and a command in a tuple:
+The answer is that there are actually two variants of the `update` function: For very simple apps, as shown above, you
+can use the simple `update` version that just returns the new model. For more complex apps that need to use commands,
+the `update` function can return both the new model and a command in a tuple:
```f#
update: 'msg -> 'model -> 'model * Cmd<'msg>
@@ -197,11 +246,16 @@ type Sub<'msg> = Dispatch<'msg> -> unit
type Cmd<'msg> = Sub<'msg> list
```
-We have encountered `Dispatch<'msg>` previously. It is the type of the `dispatch` argument to the normal MVU `view` function. It is simply an alias for a function that accepts a message and sends it to the MVU framework so that it ends up being passed into `update`.
+We have encountered `Dispatch<'msg>` previously. It is the type of the `dispatch` argument to the normal MVU `view`
+function. It is simply an alias for a function that accepts a message and sends it to the MVU framework so that it ends
+up being passed into `update`.
-The next alias, `Sub<'msg>` (short for “subscription”) is simply a function that accepts a dispatcher and returns `unit`. This function can then dispatch whatever messages it wants whenever it wants, e.g. by setting up event subscriptions.
+The next alias, `Sub<'msg>` (short for “subscription”) is simply a function that accepts a dispatcher and returns
+`unit`. This function can then dispatch whatever messages it wants whenever it wants, e.g. by setting up event
+subscriptions.
-For example, here is such a function that, when called, will start dispatching a `SetTime` message every second. The whole `timerTick` function (without applying `dispatch`) has the signature `Sub`:
+For example, here is such a function that, when called, will start dispatching a `SetTime` message every second. The
+whole `timerTick` function (without applying `dispatch`) has the signature `Sub`:
```f#
let timerTick (dispatch: Dispatch) =
@@ -210,30 +264,49 @@ let timerTick (dispatch: Dispatch) =
timer.Start()
```
-This is the kind of function that you pass to `Program.withSubscription`, which allows you to start arbitrary non-UI message dispatchers when the app starts. For example, you can start timers (as shown above), subscribe to other non-UI events, start a `MailboxProcessor`, etc.
+This is the kind of function that you pass to `Program.withSubscription`, which allows you to start arbitrary non-UI
+message dispatchers when the app starts. For example, you can start timers (as shown above), subscribe to other non-UI
+events, start a `MailboxProcessor`, etc.
-The final alias, `Cmd<'msg>`, is just a list of `Sub<'msg>`, i.e. a list of `Dispatch<'msg> -> unit` functions. In other words, the `update` function can return a list of `Dispatch<'msg> -> unit` functions that the MVU framework will execute by providing a dispatch function. These functions, as you saw above, can then dispatch any message at any time. Therefore, if you need to do impure stuff such as calling a web API, you simply create a function accepting `dispatch`, perform the work within it, and then use the `dispatch` argument (provided by the MVU framework) to dispatch further messages (e.g. representing the result of the action) into the MVU event loop.
+The final alias, `Cmd<'msg>`, is just a list of `Sub<'msg>`, i.e. a list of `Dispatch<'msg> -> unit` functions. In other
+words, the `update` function can return a list of `Dispatch<'msg> -> unit` functions that the MVU framework will execute
+by providing a dispatch function. These functions, as you saw above, can then dispatch any message at any time.
+Therefore, if you need to do impure stuff such as calling a web API, you simply create a function accepting `dispatch`,
+perform the work within it, and then use the `dispatch` argument (provided by the MVU framework) to dispatch further
+messages (e.g. representing the result of the action) into the MVU event loop.
-In other words, the `Cmd<'msg>` returned by `update` will be invoked by the MVU framework. From the point of view of your model, everything happens asynchronously: the MVU update loop executes the command and continues without waiting for it to complete, and the command may dispatch future messages into the event loop at any time.
+In other words, the `Cmd<'msg>` returned by `update` will be invoked by the MVU framework. From the point of view of
+your model, everything happens asynchronously: the MVU update loop executes the command and continues without waiting
+for it to complete, and the command may dispatch future messages into the event loop at any time.
For example:
- The user clicks a button to log in, which dispatches a `SignInRequested` message
-- The `update` function returns a new model with an `IsBusy = true` value (which can be used to show an animation such as a spinner) as well as a command that asynchronously calls the API and, when the API responds, dispatches a message representing the response (e.g. `SignInSuccessful` or `SignInFailed`).
-- The MVU framework updates the view using the new model and invokes the command by executing each function in the list with a `dispatch` function.
-- The app continues to work as normal - the spinner spins because `IsBusy = true` and any other messages are processed as normal. Note that you are of course free to process messages differently based on the fact that `IsBusy = true`. For example, you may choose to ignore additional `SignInRequested` messages.
-- When the API call finally returns, and the function that called the API uses its `dispatch` argument to dispatch a suitable message (e.g. `SignInSuccessful` or `SignInFailed`).
-
-Elmish has several helpers in the `Cmd` module to easily create commands from normal functions, but if they don’t suit your use-case, you can always write a command directly as a list of `Dispatch<'msg> -> unit` functions.
+- The `update` function returns a new model with an `IsBusy = true` value (which can be used to show an animation such
+ as a spinner) as well as a command that asynchronously calls the API and, when the API responds, dispatches a message
+ representing the response (e.g. `SignInSuccessful` or `SignInFailed`).
+- The MVU framework updates the view using the new model and invokes the command by executing each function in the list
+ with a `dispatch` function.
+- The app continues to work as normal - the spinner spins because `IsBusy = true` and any other messages are processed
+ as normal. Note that you are of course free to process messages differently based on the fact that `IsBusy = true`.
+ For example, you may choose to ignore additional `SignInRequested` messages.
+- When the API call finally returns, and the function that called the API uses its `dispatch` argument to dispatch a
+ suitable message (e.g. `SignInSuccessful` or `SignInFailed`).
+
+Elmish has several helpers in the `Cmd` module to easily create commands from normal functions, but if they don’t suit
+your use-case, you can always write a command directly as a list of `Dispatch<'msg> -> unit` functions.
Some MVU tips for beginners
---------------------------
### Normalize your model; use IDs instead of duplicating entities
-It is generally recommended that you aggressively normalize your model. This is because everything is (normally) immutable, so if a single entity occurs multiple places in your model and that entity should be updated, it must be updated every place it occurs. This increases the chance of introducing state synchronization bugs.
+It is generally recommended that you aggressively normalize your model. This is because everything is (normally)
+immutable, so if a single entity occurs multiple places in your model and that entity should be updated, it must be
+updated every place it occurs. This increases the chance of introducing state synchronization bugs.
-For example, say you have an app that can display a list of books, and you can click on a book in the list to open a detail view of that book. You might think to represent it with the following model:
+For example, say you have an app that can display a list of books, and you can click on a book in the list to open a
+detail view of that book. You might think to represent it with the following model:
```f#
type Model = {
@@ -242,9 +315,11 @@ type Model = {
}
```
-However, what if you now want to edit a book? The book may exist in two places – both in the list, and in the `DetailView` property.
+However, what if you now want to edit a book? The book may exist in two places – both in the list, and in the
+`DetailView` property.
-A better solution is to have the list be the only place to store the `Book` objects, and then simply refer to books by ID everywhere else:
+A better solution is to have the list be the only place to store the `Book` objects, and then simply refer to books by
+ID everywhere else:
```f#
type Model = {
@@ -253,21 +328,31 @@ type Model = {
}
```
-(You don’t have to use `list`; often it will make sense to have `Map` to easily and efficiently get a book by its ID.)
-
-This principle also extends to data in messages: If you have a choice between passing an entity ID and a complete entity object in a message, using an entity ID will usually be the better choice (even if it may not be immediately obvious).
+(You don’t have to use `list`; often it will make sense to have `Map` to easily and efficiently get a book
+by its ID.)
+This principle also extends to data in messages: If you have a choice between passing an entity ID and a complete entity
+object in a message, using an entity ID will usually be the better choice (even if it may not be immediately obvious).
### Use commands for anything impure
-Keep the XAML (and any code-behind) focused on the view, keep `bindings` focused on bindings, and keep your model and `update` pure. If you need to do anything impure, that's what `Command` is for, whether it's writing to disk, connecting to a DB, calling a web API, talking to actors, or anything else. All impure operations can be implemented using commands.
-
-Note that there's nothing stopping you from having mutable state outside your model. For example, if you have persistent connections (e.g. SignalR) that you need to start and stop during the lifetime of your app, you can define them elsewhere and use them in commands from your `update`. If you need an unknown number of them, such as one connection per item in a list in your model, you can store them in a dictionary or similar, keyed by the item's ID. This allows you to create, dispose, and remove items according to the data in your model.
+Keep the XAML (and any code-behind) focused on the view, keep `bindings` focused on bindings, and keep your model and
+`update` pure. If you need to do anything impure, that's what `Command` is for, whether it's writing to disk,
+connecting to a DB, calling a web API, talking to actors, or anything else. All impure operations can be implemented
+using commands.
+Note that there's nothing stopping you from having mutable state outside your model. For example, if you have persistent
+connections (e.g. SignalR) that you need to start and stop during the lifetime of your app, you can define them
+elsewhere and use them in commands from your `update`. If you need an unknown number of them, such as one connection per
+item in a list in your model, you can store them in a dictionary or similar, keyed by the item's ID. This allows you to
+create, dispose, and remove items according to the data in your model.
### Child components and scaling
-When starting out with MVU, it’s easy to fall into the trap of thinking ahead and wondering “how can I split my model/message/update/view into separate components?” For example, if you have two separate “pages” in your app, you might be inclined to think that each page should have its own separate model, message, update, and view. While this technique is needed with many other non-MVU architectures, it is often counterproductive in MVU.
+When starting out with MVU, it’s easy to fall into the trap of thinking ahead and wondering “how can I split my
+model/message/update/view into separate components?” For example, if you have two separate “pages” in your app, you
+might be inclined to think that each page should have its own separate model, message, update, and view. While this
+technique is needed with many other non-MVU architectures, it is often counterproductive in MVU.
Before delving into the problems, let’s see how it’s done:
@@ -294,26 +379,50 @@ module Parent =
| ChildMsg of childMsg -> { model with Child = Child.update childMsg model }
```
-As you can see, there’s some boilerplate involved in the parent component: You must have a model field for the child model, a wrapping message case for the child message, and an `update` branch that passes the child message on to the child model.
+As you can see, there’s some boilerplate involved in the parent component: You must have a model field for the child
+model, a wrapping message case for the child message, and an `update` branch that passes the child message on to the
+child model.
Now for the problems.
-One important problem is that often, “child components” are not in fact separate from their parents, but need access to some of the parent state. Continuing the book example above, say that you want to split the app into a “list component” and a “detail component” with separate models. If you want to have auto-complete of author names when editing a book in the detail component, you need access to the list all books. The only way to accomplish that reliably is to have the complete book list in the child component, too. But that means that every time you update a book, you need to remember to update it in the child component, too. This incurs boilerplate for every piece of duplicated state (since you must have a child message case for updating each piece of duplicated state), and, again, easily causes state synchronization bugs.
+One important problem is that often, “child components” are not in fact separate from their parents, but need access to
+some of the parent state. Continuing the book example above, say that you want to split the app into a “list component”
+and a “detail component” with separate models. If you want to have auto-complete of author names when editing a book in
+the detail component, you need access to the list all books. The only way to accomplish that reliably is to have the
+complete book list in the child component, too. But that means that every time you update a book, you need to remember
+to update it in the child component, too. This incurs boilerplate for every piece of duplicated state (since you must
+have a child message case for updating each piece of duplicated state), and, again, easily causes state synchronization
+bugs.
-Another important problem, again following from the fact that the components are often not separate, is that a child component might need to communicate with its parent. For example, when saving a book, the parent component needs to get the updated book in a message, but the child component can only dispatch its child message type. There are ways to solve this (e.g. make the child `update` also return a separate “parent message” type, or have the parent intercept certain child messages), but all of them are usually unnecessary complications and not without drawbacks.
+Another important problem, again following from the fact that the components are often not separate, is that a child
+component might need to communicate with its parent. For example, when saving a book, the parent component needs to get
+the updated book in a message, but the child component can only dispatch its child message type. There are ways to solve
+this (e.g. make the child `update` also return a separate “parent message” type, or have the parent intercept certain
+child messages), but all of them are usually unnecessary complications and not without drawbacks.
-What should you do instead, then? The answer is, simply put, to scale the model/message/update/view **separately**, and **only when needed**. It is highly recommended that you read the following reddit thread replies by user `rtfeldman`:
+What should you do instead, then? The answer is, simply put, to scale the model/message/update/view **separately**, and
+**only when needed**. It is highly recommended that you read the following reddit thread replies by user `rtfeldman`:
- [Elm Architecture with a Redux-like store pattern](https://www.reddit.com/r/elm/comments/5xdl9z/elm_architecture_with_a_reduxlike_store_pattern/dehrcx8/)
- [How to structure Elm with multiple models?](https://www.reddit.com/r/elm/comments/5jd2xn/how_to_structure_elm_with_multiple_models/dbuu0m4/)
### Optimize easily with memoization
-First: Never optimize prematurely. Only optimize if you can actually measure that a certain piece of code is giving you problems.
+First: Never optimize prematurely. Only optimize if you can actually measure that a certain piece of code is giving you
+problems.
-That said: Since everything in MVU is just (pure) functions, functions, and more functions, *memoization* is a technique that will allow you to easily skip work if inputs are equal. Memoization is simply about storing the inputs and outputs, and if the function is called with a known value, the already computed result is returned. If not, the result is computed by calling the actual function, and the result is stored to be reused later.
+That said: Since everything in MVU is just (pure) functions, functions, and more functions, *memoization* is a technique
+that will allow you to easily skip work if inputs are equal. Memoization is simply about storing the inputs and outputs,
+and if the function is called with a known value, the already computed result is returned. If not, the result is
+computed by calling the actual function, and the result is stored to be reused later.
-In general, there are several ways to memoize: You can memoize all inputs/outputs (may be memory heavy), or just the latest; you can memoize based on structural comparison of inputs (may be expensive), or use referential equality. In MVU architectures, you often need to ensure that when you use parts of your model to compute a result, you only compute the result once until the input changes. Specifically, you might not care about remembering old values, because generally these will never be used. In that case, you can often get very far with this general memoization implementation that memoizes only the last computed value and stores it using the input reference (which works because everything is normally immutable):
+In general, there are several ways to memoize: You can memoize all inputs/outputs (may be memory heavy), or just the
+latest; you can memoize based on structural comparison of inputs (may be expensive), or use referential equality. In MVU
+architectures, you often need to ensure that when you use parts of your model to compute a result, you only compute the
+result once until the input changes. Specifically, you might not care about remembering old values, because generally
+these will never be used. In that case, you can often get very far with this general memoization implementation that
+memoizes only the last computed value and stores it using the input reference (which works because everything is
+normally immutable):
```f#
let memoize (f: 'a -> 'b) : 'a -> 'b =
@@ -337,26 +446,34 @@ let myExpensiveFunMemoized = memoize myExpensiveFun
Then you simply call `myExpensiveFunMemoized` instead of `myExpensiveFun` in the rest of your code.
-It is important that `myExpensiveFunMemoized` is defined without arguments to ensure that `memoize` is applied only once. If you had written
+It is important that `myExpensiveFunMemoized` is defined without arguments to ensure that `memoize` is applied only
+once. If you had written
```f#
let myExpensiveFunMemoized x = memoize myExpensiveFun x
```
-then a new memoized version would be created each for each call, which defeats the purpose of memoizing it in the first place.
+then a new memoized version would be created each for each call, which defeats the purpose of memoizing it in the first
+place.
-Furthermore, the implementation above only memoizes functions with a single input. If you need more parameters, you need to create `memoize2`, `memoize3`, etc. (You could also pass a single tuple argument, but that will never be referentially equal, so you’d need to use structural comparison instead. That might be prohibitively expensive if the input is, say, a large collection of domain objects. Alternatively you might use functionality similar to Elmish.WPF’s `elmEq` helper, which is explained later.)
+Furthermore, the implementation above only memoizes functions with a single input. If you need more parameters, you need
+to create `memoize2`, `memoize3`, etc. (You could also pass a single tuple argument, but that will never be
+referentially equal, so you’d need to use structural comparison instead. That might be prohibitively expensive if the
+input is, say, a large collection of domain objects. Alternatively you might use functionality similar to Elmish.WPF’s
+`elmEq` helper, which is explained later.)
Getting started with Elmish.WPF
-------------------------------
-The [readme](https://github.com/elmish/Elmish.WPF/blob/master/README.md) has a “getting started” section that will have you up and running quickly with a simple skeleton solution.
+The [readme](https://github.com/elmish/Elmish.WPF/blob/master/README.md) has a “getting started” section that will have
+you up and running quickly with a simple skeleton solution.
Additional resources
--------------------
The [Elmish.WPF readme](https://github.com/elmish/Elmish.WPF/blob/master/README.md) contains
- - a “getting started” section that will get you quickly up and running
- - a FAQ with miscellaneous useful information
+
+- a “getting started” section that will get you quickly up and running
+- a FAQ with miscellaneous useful information
The [Elmish.WPF reference](https://github.com/elmish/Elmish.WPF/blob/master/REFERENCE.md)
diff --git a/jetbrains.svg b/jetbrains.svg
index 75d4d217..55bbf1a1 100644
--- a/jetbrains.svg
+++ b/jetbrains.svg
@@ -1,62 +1,65 @@
-