From 66ffb32274d27dd1ff92fea222ba70b71b1096f6 Mon Sep 17 00:00:00 2001 From: Tim Rudat Date: Fri, 18 Jul 2025 14:41:00 +0200 Subject: [PATCH 01/18] Migrate solution file to .slnx format Replace legacy .sln format with modern .slnx format and relocate from src/ to project root --- Elmish.WPF.slnx | 47 ++++++ src/Elmish.WPF.sln | 287 --------------------------------- src/Elmish.WPF.sln.DotSettings | 14 -- 3 files changed, 47 insertions(+), 301 deletions(-) create mode 100644 Elmish.WPF.slnx delete mode 100644 src/Elmish.WPF.sln delete mode 100644 src/Elmish.WPF.sln.DotSettings diff --git a/Elmish.WPF.slnx b/Elmish.WPF.slnx new file mode 100644 index 00000000..2e608ed0 --- /dev/null +++ b/Elmish.WPF.slnx @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Elmish.WPF.sln b/src/Elmish.WPF.sln deleted file mode 100644 index e72c6a8d..00000000 --- a/src/Elmish.WPF.sln +++ /dev/null @@ -1,287 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.28803.352 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{BBAFEB1E-93C0-4C7E-8E0A-026BB05C88EC}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SingleCounter", "Samples\SingleCounter\SingleCounter.csproj", "{55F79BA4-8265-4612-8354-D04F91BF9B03}" -EndProject -Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "SingleCounter.Core", "Samples\SingleCounter.Core\SingleCounter.Core.fsproj", "{2DBB8062-2843-43A0-B73C-4777A30BD4FF}" -EndProject -Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Elmish.WPF", "Elmish.WPF\Elmish.WPF.fsproj", "{8C6E8D34-7205-4C57-9722-87E30E4FC5CE}" -EndProject -Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "UiBoundCmdParam.Core", "Samples\UiBoundCmdParam.Core\UiBoundCmdParam.Core.fsproj", "{9581A1DE-70A6-4AC2-AF65-FD6E95F3A983}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UiBoundCmdParam", "Samples\UiBoundCmdParam\UiBoundCmdParam.csproj", "{574786BE-3A84-4A0F-99DE-0FB10ED4161F}" -EndProject -Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Validation.Core", "Samples\Validation.Core\Validation.Core.fsproj", "{69DED389-B4BB-4295-926D-1DFFB8995628}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Validation", "Samples\Validation\Validation.csproj", "{4BD0B0AF-C522-43F9-92BF-FB6205A9E2F6}" -EndProject -Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "SubModel.Core", "Samples\SubModel.Core\SubModel.Core.fsproj", "{EA0640D2-D64B-4AE6-8876-5E33C53BD50E}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SubModel", "Samples\SubModel\SubModel.csproj", "{A15C50F5-B4CF-45D3-A35B-DD385AEC5BB7}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OneWaySeq", "Samples\OneWaySeq\OneWaySeq.csproj", "{4A7B4B16-0FC3-4338-BE43-497F63818749}" -EndProject -Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "OneWaySeq.Core", "Samples\OneWaySeq.Core\OneWaySeq.Core.fsproj", "{E97A831B-0525-4248-8143-4E1114612071}" -EndProject -Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "SubModelOpt.Core", "Samples\SubModelOpt.Core\SubModelOpt.Core.fsproj", "{47ADE238-C32B-4F19-9028-68EB8E09927C}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SubModelOpt", "Samples\SubModelOpt\SubModelOpt.csproj", "{EB80831A-431C-4CEC-AC41-8E5163E4B857}" -EndProject -Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Elmish.WPF.Tests", "Elmish.WPF.Tests\Elmish.WPF.Tests.fsproj", "{54DFF909-F5DF-48A9-B57D-E57B0EE743FF}" -EndProject -Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "NewWindow.Core", "Samples\NewWindow.Core\NewWindow.Core.fsproj", "{9D01D459-4EAA-446E-BC2E-F5F66D29833A}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NewWindow", "Samples\NewWindow\NewWindow.csproj", "{7595D7F1-5B28-415F-97C2-470A375E3BCC}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SubModelSeq", "Samples\SubModelSeq\SubModelSeq.csproj", "{DFAFDCF3-83E0-4440-AD8C-E45878C80F64}" -EndProject -Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "SubModelSeq.Core", "Samples\SubModelSeq.Core\SubModelSeq.Core.fsproj", "{2946EBC5-5937-45EE-8F19-7AF0792B5A5B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SubModelSelectedItem", "Samples\SubModelSelectedItem\SubModelSelectedItem.csproj", "{6023315A-BC97-4C4A-930E-D6D01E1DC549}" -EndProject -Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "SubModelSelectedItem.Core", "Samples\SubModelSelectedItem.Core\SubModelSelectedItem.Core.fsproj", "{5B2B78EA-DA5C-406B-97C2-2964E557E2DB}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EventBindingsAndBehaviors", "Samples\EventBindingsAndBehaviors\EventBindingsAndBehaviors.csproj", "{A4D16DF7-7BA8-4833-8F06-1D151CC81DC2}" -EndProject -Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "EventBindingsAndBehaviors.Core", "Samples\EventBindingsAndBehaviors.Core\EventBindingsAndBehaviors.Core.fsproj", "{35149AA6-9132-4D6F-B4BF-272D70CD81C5}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileDialogs", "Samples\FileDialogs\FileDialogs.csproj", "{E084B944-E21D-45A5-A1AE-C5A6A641CE92}" -EndProject -Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FileDialogs.Core", "Samples\FileDialogs.Core\FileDialogs.Core.fsproj", "{36A4AEAF-8282-47EC-B751-BB3D16AB1A20}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileDialogsCmdMsg", "Samples\FileDialogsCmdMsg\FileDialogsCmdMsg.csproj", "{F66F6E21-357E-4CE8-9807-042C5171AB06}" -EndProject -Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FileDialogsCmdMsg.Core", "Samples\FileDialogsCmdMsg.Core\FileDialogsCmdMsg.Core.fsproj", "{F095E7E4-28ED-4223-A92F-4E86922E34EF}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{51DF5F61-CE23-4BB5-A1D6-8421974B760B}" - ProjectSection(SolutionItems) = preProject - ..\README.md = ..\README.md - ..\RELEASE_NOTES.md = ..\RELEASE_NOTES.md - ..\TUTORIAL.md = ..\TUTORIAL.md - ..\REFERENCE.md = ..\REFERENCE.md - EndProjectSection -EndProject -Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Sticky.Core", "Samples\Sticky.Core\Sticky.Core.fsproj", "{F251BEA3-6AD1-4C14-9F3D-87FCFA4292EB}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sticky", "Samples\Sticky\Sticky.csproj", "{668E3AAD-B2AA-4934-804E-234C4F502038}" -EndProject -Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Capabilities.Core", "Samples\Capabilities.Core\Capabilities.Core.fsproj", "{B3BD88DC-7362-46F8-8040-6574C7B6B759}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Capabilities", "Samples\Capabilities\Capabilities.csproj", "{BDA46408-691C-47FA-8670-92A5D04663EB}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Multiselect", "Samples\Multiselect\Multiselect.csproj", "{57EB1D0A-9182-4A7F-818B-8FDCA58D7321}" -EndProject -Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Multiselect.Core", "Samples\Multiselect.Core\Multiselect.Core.fsproj", "{DFF15AD9-337E-4301-9A05-5CFA147C457E}" -EndProject -Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Elmish.WPF.Benchmarks", "Elmish.WPF.Benchmarks\Elmish.WPF.Benchmarks.fsproj", "{240E981A-BEEF-41A1-A214-135E52DCA956}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SubModelStatic", "Samples\SubModelStatic\SubModelStatic.csproj", "{3F50DF04-DE1F-4368-9584-E318FE41AC2C}" -EndProject -Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "SubModelStatic.Core", "Samples\SubModelStatic.Core\SubModelStatic.Core.fsproj", "{F0064852-8E7F-437B-A414-72EB0FA46711}" -EndProject -Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Threading.Core", "Samples\Threading.Core\Threading.Core.fsproj", "{B5029D1D-73A0-4C08-A4A0-1BF52CE919C4}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Threading", "Samples\Threading\Threading.csproj", "{36565661-EA23-4965-97A0-2F4FDA0B4B67}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {55F79BA4-8265-4612-8354-D04F91BF9B03}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {55F79BA4-8265-4612-8354-D04F91BF9B03}.Debug|Any CPU.Build.0 = Debug|Any CPU - {55F79BA4-8265-4612-8354-D04F91BF9B03}.Release|Any CPU.ActiveCfg = Release|Any CPU - {55F79BA4-8265-4612-8354-D04F91BF9B03}.Release|Any CPU.Build.0 = Release|Any CPU - {2DBB8062-2843-43A0-B73C-4777A30BD4FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2DBB8062-2843-43A0-B73C-4777A30BD4FF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2DBB8062-2843-43A0-B73C-4777A30BD4FF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2DBB8062-2843-43A0-B73C-4777A30BD4FF}.Release|Any CPU.Build.0 = Release|Any CPU - {8C6E8D34-7205-4C57-9722-87E30E4FC5CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8C6E8D34-7205-4C57-9722-87E30E4FC5CE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8C6E8D34-7205-4C57-9722-87E30E4FC5CE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8C6E8D34-7205-4C57-9722-87E30E4FC5CE}.Release|Any CPU.Build.0 = Release|Any CPU - {9581A1DE-70A6-4AC2-AF65-FD6E95F3A983}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9581A1DE-70A6-4AC2-AF65-FD6E95F3A983}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9581A1DE-70A6-4AC2-AF65-FD6E95F3A983}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9581A1DE-70A6-4AC2-AF65-FD6E95F3A983}.Release|Any CPU.Build.0 = Release|Any CPU - {574786BE-3A84-4A0F-99DE-0FB10ED4161F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {574786BE-3A84-4A0F-99DE-0FB10ED4161F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {574786BE-3A84-4A0F-99DE-0FB10ED4161F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {574786BE-3A84-4A0F-99DE-0FB10ED4161F}.Release|Any CPU.Build.0 = Release|Any CPU - {69DED389-B4BB-4295-926D-1DFFB8995628}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {69DED389-B4BB-4295-926D-1DFFB8995628}.Debug|Any CPU.Build.0 = Debug|Any CPU - {69DED389-B4BB-4295-926D-1DFFB8995628}.Release|Any CPU.ActiveCfg = Release|Any CPU - {69DED389-B4BB-4295-926D-1DFFB8995628}.Release|Any CPU.Build.0 = Release|Any CPU - {4BD0B0AF-C522-43F9-92BF-FB6205A9E2F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4BD0B0AF-C522-43F9-92BF-FB6205A9E2F6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4BD0B0AF-C522-43F9-92BF-FB6205A9E2F6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4BD0B0AF-C522-43F9-92BF-FB6205A9E2F6}.Release|Any CPU.Build.0 = Release|Any CPU - {EA0640D2-D64B-4AE6-8876-5E33C53BD50E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EA0640D2-D64B-4AE6-8876-5E33C53BD50E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EA0640D2-D64B-4AE6-8876-5E33C53BD50E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EA0640D2-D64B-4AE6-8876-5E33C53BD50E}.Release|Any CPU.Build.0 = Release|Any CPU - {A15C50F5-B4CF-45D3-A35B-DD385AEC5BB7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A15C50F5-B4CF-45D3-A35B-DD385AEC5BB7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A15C50F5-B4CF-45D3-A35B-DD385AEC5BB7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A15C50F5-B4CF-45D3-A35B-DD385AEC5BB7}.Release|Any CPU.Build.0 = Release|Any CPU - {4A7B4B16-0FC3-4338-BE43-497F63818749}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4A7B4B16-0FC3-4338-BE43-497F63818749}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4A7B4B16-0FC3-4338-BE43-497F63818749}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4A7B4B16-0FC3-4338-BE43-497F63818749}.Release|Any CPU.Build.0 = Release|Any CPU - {E97A831B-0525-4248-8143-4E1114612071}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E97A831B-0525-4248-8143-4E1114612071}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E97A831B-0525-4248-8143-4E1114612071}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E97A831B-0525-4248-8143-4E1114612071}.Release|Any CPU.Build.0 = Release|Any CPU - {47ADE238-C32B-4F19-9028-68EB8E09927C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {47ADE238-C32B-4F19-9028-68EB8E09927C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {47ADE238-C32B-4F19-9028-68EB8E09927C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {47ADE238-C32B-4F19-9028-68EB8E09927C}.Release|Any CPU.Build.0 = Release|Any CPU - {EB80831A-431C-4CEC-AC41-8E5163E4B857}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EB80831A-431C-4CEC-AC41-8E5163E4B857}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EB80831A-431C-4CEC-AC41-8E5163E4B857}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EB80831A-431C-4CEC-AC41-8E5163E4B857}.Release|Any CPU.Build.0 = Release|Any CPU - {54DFF909-F5DF-48A9-B57D-E57B0EE743FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {54DFF909-F5DF-48A9-B57D-E57B0EE743FF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {54DFF909-F5DF-48A9-B57D-E57B0EE743FF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {54DFF909-F5DF-48A9-B57D-E57B0EE743FF}.Release|Any CPU.Build.0 = Release|Any CPU - {9D01D459-4EAA-446E-BC2E-F5F66D29833A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9D01D459-4EAA-446E-BC2E-F5F66D29833A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9D01D459-4EAA-446E-BC2E-F5F66D29833A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9D01D459-4EAA-446E-BC2E-F5F66D29833A}.Release|Any CPU.Build.0 = Release|Any CPU - {7595D7F1-5B28-415F-97C2-470A375E3BCC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7595D7F1-5B28-415F-97C2-470A375E3BCC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7595D7F1-5B28-415F-97C2-470A375E3BCC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7595D7F1-5B28-415F-97C2-470A375E3BCC}.Release|Any CPU.Build.0 = Release|Any CPU - {DFAFDCF3-83E0-4440-AD8C-E45878C80F64}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DFAFDCF3-83E0-4440-AD8C-E45878C80F64}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DFAFDCF3-83E0-4440-AD8C-E45878C80F64}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DFAFDCF3-83E0-4440-AD8C-E45878C80F64}.Release|Any CPU.Build.0 = Release|Any CPU - {2946EBC5-5937-45EE-8F19-7AF0792B5A5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2946EBC5-5937-45EE-8F19-7AF0792B5A5B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2946EBC5-5937-45EE-8F19-7AF0792B5A5B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2946EBC5-5937-45EE-8F19-7AF0792B5A5B}.Release|Any CPU.Build.0 = Release|Any CPU - {6023315A-BC97-4C4A-930E-D6D01E1DC549}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6023315A-BC97-4C4A-930E-D6D01E1DC549}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6023315A-BC97-4C4A-930E-D6D01E1DC549}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6023315A-BC97-4C4A-930E-D6D01E1DC549}.Release|Any CPU.Build.0 = Release|Any CPU - {5B2B78EA-DA5C-406B-97C2-2964E557E2DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5B2B78EA-DA5C-406B-97C2-2964E557E2DB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5B2B78EA-DA5C-406B-97C2-2964E557E2DB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5B2B78EA-DA5C-406B-97C2-2964E557E2DB}.Release|Any CPU.Build.0 = Release|Any CPU - {A4D16DF7-7BA8-4833-8F06-1D151CC81DC2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A4D16DF7-7BA8-4833-8F06-1D151CC81DC2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A4D16DF7-7BA8-4833-8F06-1D151CC81DC2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A4D16DF7-7BA8-4833-8F06-1D151CC81DC2}.Release|Any CPU.Build.0 = Release|Any CPU - {35149AA6-9132-4D6F-B4BF-272D70CD81C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {35149AA6-9132-4D6F-B4BF-272D70CD81C5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {35149AA6-9132-4D6F-B4BF-272D70CD81C5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {35149AA6-9132-4D6F-B4BF-272D70CD81C5}.Release|Any CPU.Build.0 = Release|Any CPU - {E084B944-E21D-45A5-A1AE-C5A6A641CE92}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E084B944-E21D-45A5-A1AE-C5A6A641CE92}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E084B944-E21D-45A5-A1AE-C5A6A641CE92}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E084B944-E21D-45A5-A1AE-C5A6A641CE92}.Release|Any CPU.Build.0 = Release|Any CPU - {36A4AEAF-8282-47EC-B751-BB3D16AB1A20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {36A4AEAF-8282-47EC-B751-BB3D16AB1A20}.Debug|Any CPU.Build.0 = Debug|Any CPU - {36A4AEAF-8282-47EC-B751-BB3D16AB1A20}.Release|Any CPU.ActiveCfg = Release|Any CPU - {36A4AEAF-8282-47EC-B751-BB3D16AB1A20}.Release|Any CPU.Build.0 = Release|Any CPU - {F66F6E21-357E-4CE8-9807-042C5171AB06}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F66F6E21-357E-4CE8-9807-042C5171AB06}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F66F6E21-357E-4CE8-9807-042C5171AB06}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F66F6E21-357E-4CE8-9807-042C5171AB06}.Release|Any CPU.Build.0 = Release|Any CPU - {F095E7E4-28ED-4223-A92F-4E86922E34EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F095E7E4-28ED-4223-A92F-4E86922E34EF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F095E7E4-28ED-4223-A92F-4E86922E34EF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F095E7E4-28ED-4223-A92F-4E86922E34EF}.Release|Any CPU.Build.0 = Release|Any CPU - {F251BEA3-6AD1-4C14-9F3D-87FCFA4292EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F251BEA3-6AD1-4C14-9F3D-87FCFA4292EB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F251BEA3-6AD1-4C14-9F3D-87FCFA4292EB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F251BEA3-6AD1-4C14-9F3D-87FCFA4292EB}.Release|Any CPU.Build.0 = Release|Any CPU - {668E3AAD-B2AA-4934-804E-234C4F502038}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {668E3AAD-B2AA-4934-804E-234C4F502038}.Debug|Any CPU.Build.0 = Debug|Any CPU - {668E3AAD-B2AA-4934-804E-234C4F502038}.Release|Any CPU.ActiveCfg = Release|Any CPU - {668E3AAD-B2AA-4934-804E-234C4F502038}.Release|Any CPU.Build.0 = Release|Any CPU - {B3BD88DC-7362-46F8-8040-6574C7B6B759}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B3BD88DC-7362-46F8-8040-6574C7B6B759}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B3BD88DC-7362-46F8-8040-6574C7B6B759}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B3BD88DC-7362-46F8-8040-6574C7B6B759}.Release|Any CPU.Build.0 = Release|Any CPU - {BDA46408-691C-47FA-8670-92A5D04663EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BDA46408-691C-47FA-8670-92A5D04663EB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BDA46408-691C-47FA-8670-92A5D04663EB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BDA46408-691C-47FA-8670-92A5D04663EB}.Release|Any CPU.Build.0 = Release|Any CPU - {57EB1D0A-9182-4A7F-818B-8FDCA58D7321}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {57EB1D0A-9182-4A7F-818B-8FDCA58D7321}.Debug|Any CPU.Build.0 = Debug|Any CPU - {57EB1D0A-9182-4A7F-818B-8FDCA58D7321}.Release|Any CPU.ActiveCfg = Release|Any CPU - {57EB1D0A-9182-4A7F-818B-8FDCA58D7321}.Release|Any CPU.Build.0 = Release|Any CPU - {DFF15AD9-337E-4301-9A05-5CFA147C457E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DFF15AD9-337E-4301-9A05-5CFA147C457E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DFF15AD9-337E-4301-9A05-5CFA147C457E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DFF15AD9-337E-4301-9A05-5CFA147C457E}.Release|Any CPU.Build.0 = Release|Any CPU - {240E981A-BEEF-41A1-A214-135E52DCA956}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {240E981A-BEEF-41A1-A214-135E52DCA956}.Debug|Any CPU.Build.0 = Debug|Any CPU - {240E981A-BEEF-41A1-A214-135E52DCA956}.Release|Any CPU.ActiveCfg = Release|Any CPU - {240E981A-BEEF-41A1-A214-135E52DCA956}.Release|Any CPU.Build.0 = Release|Any CPU - {3F50DF04-DE1F-4368-9584-E318FE41AC2C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3F50DF04-DE1F-4368-9584-E318FE41AC2C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3F50DF04-DE1F-4368-9584-E318FE41AC2C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3F50DF04-DE1F-4368-9584-E318FE41AC2C}.Release|Any CPU.Build.0 = Release|Any CPU - {F0064852-8E7F-437B-A414-72EB0FA46711}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F0064852-8E7F-437B-A414-72EB0FA46711}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F0064852-8E7F-437B-A414-72EB0FA46711}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F0064852-8E7F-437B-A414-72EB0FA46711}.Release|Any CPU.Build.0 = Release|Any CPU - {B5029D1D-73A0-4C08-A4A0-1BF52CE919C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B5029D1D-73A0-4C08-A4A0-1BF52CE919C4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B5029D1D-73A0-4C08-A4A0-1BF52CE919C4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B5029D1D-73A0-4C08-A4A0-1BF52CE919C4}.Release|Any CPU.Build.0 = Release|Any CPU - {36565661-EA23-4965-97A0-2F4FDA0B4B67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {36565661-EA23-4965-97A0-2F4FDA0B4B67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {36565661-EA23-4965-97A0-2F4FDA0B4B67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {36565661-EA23-4965-97A0-2F4FDA0B4B67}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {55F79BA4-8265-4612-8354-D04F91BF9B03} = {BBAFEB1E-93C0-4C7E-8E0A-026BB05C88EC} - {2DBB8062-2843-43A0-B73C-4777A30BD4FF} = {BBAFEB1E-93C0-4C7E-8E0A-026BB05C88EC} - {9581A1DE-70A6-4AC2-AF65-FD6E95F3A983} = {BBAFEB1E-93C0-4C7E-8E0A-026BB05C88EC} - {574786BE-3A84-4A0F-99DE-0FB10ED4161F} = {BBAFEB1E-93C0-4C7E-8E0A-026BB05C88EC} - {69DED389-B4BB-4295-926D-1DFFB8995628} = {BBAFEB1E-93C0-4C7E-8E0A-026BB05C88EC} - {4BD0B0AF-C522-43F9-92BF-FB6205A9E2F6} = {BBAFEB1E-93C0-4C7E-8E0A-026BB05C88EC} - {EA0640D2-D64B-4AE6-8876-5E33C53BD50E} = {BBAFEB1E-93C0-4C7E-8E0A-026BB05C88EC} - {A15C50F5-B4CF-45D3-A35B-DD385AEC5BB7} = {BBAFEB1E-93C0-4C7E-8E0A-026BB05C88EC} - {4A7B4B16-0FC3-4338-BE43-497F63818749} = {BBAFEB1E-93C0-4C7E-8E0A-026BB05C88EC} - {E97A831B-0525-4248-8143-4E1114612071} = {BBAFEB1E-93C0-4C7E-8E0A-026BB05C88EC} - {47ADE238-C32B-4F19-9028-68EB8E09927C} = {BBAFEB1E-93C0-4C7E-8E0A-026BB05C88EC} - {EB80831A-431C-4CEC-AC41-8E5163E4B857} = {BBAFEB1E-93C0-4C7E-8E0A-026BB05C88EC} - {9D01D459-4EAA-446E-BC2E-F5F66D29833A} = {BBAFEB1E-93C0-4C7E-8E0A-026BB05C88EC} - {7595D7F1-5B28-415F-97C2-470A375E3BCC} = {BBAFEB1E-93C0-4C7E-8E0A-026BB05C88EC} - {DFAFDCF3-83E0-4440-AD8C-E45878C80F64} = {BBAFEB1E-93C0-4C7E-8E0A-026BB05C88EC} - {2946EBC5-5937-45EE-8F19-7AF0792B5A5B} = {BBAFEB1E-93C0-4C7E-8E0A-026BB05C88EC} - {6023315A-BC97-4C4A-930E-D6D01E1DC549} = {BBAFEB1E-93C0-4C7E-8E0A-026BB05C88EC} - {5B2B78EA-DA5C-406B-97C2-2964E557E2DB} = {BBAFEB1E-93C0-4C7E-8E0A-026BB05C88EC} - {A4D16DF7-7BA8-4833-8F06-1D151CC81DC2} = {BBAFEB1E-93C0-4C7E-8E0A-026BB05C88EC} - {35149AA6-9132-4D6F-B4BF-272D70CD81C5} = {BBAFEB1E-93C0-4C7E-8E0A-026BB05C88EC} - {E084B944-E21D-45A5-A1AE-C5A6A641CE92} = {BBAFEB1E-93C0-4C7E-8E0A-026BB05C88EC} - {36A4AEAF-8282-47EC-B751-BB3D16AB1A20} = {BBAFEB1E-93C0-4C7E-8E0A-026BB05C88EC} - {F66F6E21-357E-4CE8-9807-042C5171AB06} = {BBAFEB1E-93C0-4C7E-8E0A-026BB05C88EC} - {F095E7E4-28ED-4223-A92F-4E86922E34EF} = {BBAFEB1E-93C0-4C7E-8E0A-026BB05C88EC} - {F251BEA3-6AD1-4C14-9F3D-87FCFA4292EB} = {BBAFEB1E-93C0-4C7E-8E0A-026BB05C88EC} - {668E3AAD-B2AA-4934-804E-234C4F502038} = {BBAFEB1E-93C0-4C7E-8E0A-026BB05C88EC} - {B3BD88DC-7362-46F8-8040-6574C7B6B759} = {BBAFEB1E-93C0-4C7E-8E0A-026BB05C88EC} - {BDA46408-691C-47FA-8670-92A5D04663EB} = {BBAFEB1E-93C0-4C7E-8E0A-026BB05C88EC} - {57EB1D0A-9182-4A7F-818B-8FDCA58D7321} = {BBAFEB1E-93C0-4C7E-8E0A-026BB05C88EC} - {DFF15AD9-337E-4301-9A05-5CFA147C457E} = {BBAFEB1E-93C0-4C7E-8E0A-026BB05C88EC} - {3F50DF04-DE1F-4368-9584-E318FE41AC2C} = {BBAFEB1E-93C0-4C7E-8E0A-026BB05C88EC} - {F0064852-8E7F-437B-A414-72EB0FA46711} = {BBAFEB1E-93C0-4C7E-8E0A-026BB05C88EC} - {B5029D1D-73A0-4C08-A4A0-1BF52CE919C4} = {BBAFEB1E-93C0-4C7E-8E0A-026BB05C88EC} - {36565661-EA23-4965-97A0-2F4FDA0B4B67} = {BBAFEB1E-93C0-4C7E-8E0A-026BB05C88EC} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {3125D461-08F4-4071-AAE5-1038EF84A360} - EndGlobalSection -EndGlobal diff --git a/src/Elmish.WPF.sln.DotSettings b/src/Elmish.WPF.sln.DotSettings deleted file mode 100644 index cc7500a2..00000000 --- a/src/Elmish.WPF.sln.DotSettings +++ /dev/null @@ -1,14 +0,0 @@ - - True - True - True - True - True - True - True - True - True - True - True - True - From 5fff0a1b0651d1db13000ef1f997b9f2efb45486 Mon Sep 17 00:00:00 2001 From: Tim Rudat Date: Fri, 18 Jul 2025 14:51:46 +0200 Subject: [PATCH 02/18] Configure EditorConfig for project file formatting Restrict 2-space indentation to XML-based files (xml, slnx, fsproj, csproj, xaml) Install dotnet tool fantomas to handle F# formatting --- .config/dotnet-tools.json | 13 +++++++++++++ .editorconfig | 2 ++ 2 files changed, 15 insertions(+) create mode 100644 .config/dotnet-tools.json 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 From ff8c2532a45e7388f7e1487a5262e3c86a945f9d Mon Sep 17 00:00:00 2001 From: Tim Rudat Date: Sun, 20 Jul 2025 15:09:55 +0200 Subject: [PATCH 03/18] Add files to .slnx Include .editorconfig, .gitignore, and LICENSE.md in solution folder --- Elmish.WPF.slnx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Elmish.WPF.slnx b/Elmish.WPF.slnx index 2e608ed0..94d7e7b5 100644 --- a/Elmish.WPF.slnx +++ b/Elmish.WPF.slnx @@ -40,8 +40,11 @@ + + + - + \ No newline at end of file From 929b851a4a6261936c5dd40d5e4aa5c94e752b0b Mon Sep 17 00:00:00 2001 From: Tim Rudat Date: Sun, 20 Jul 2025 15:41:46 +0200 Subject: [PATCH 04/18] Apply Fantomas code formatting across entire codebase Format all F# source files in src/Elmish.WPF/ Format all F# test files in src/Elmish.WPF.Tests/ Format all F# sample project files Standardize code style and indentation --- src/Elmish.WPF.Benchmarks/Program.fs | 44 +- src/Elmish.WPF.Tests/AutoOpen.fs | 110 +- src/Elmish.WPF.Tests/BindingTests.fs | 4463 +++++---- src/Elmish.WPF.Tests/BindingVmHelpersTests.fs | 203 +- src/Elmish.WPF.Tests/DynamicViewModelTests.fs | 2546 +++--- src/Elmish.WPF.Tests/MergeTests.fs | 549 +- src/Elmish.WPF.Tests/StaticViewModelTests.fs | 494 +- src/Elmish.WPF.Tests/UtilsTests.fs | 272 +- src/Elmish.WPF/Binding.fs | 8130 ++++++++--------- src/Elmish.WPF/BindingData.fs | 1592 ++-- src/Elmish.WPF/BindingVmHelpers.fs | 1596 ++-- src/Elmish.WPF/Command.fs | 41 +- src/Elmish.WPF/InternalUtils.fs | 130 +- src/Elmish.WPF/Merge.fs | 494 +- src/Elmish.WPF/Utils.fs | 51 +- src/Elmish.WPF/ViewModelArgs.fs | 68 +- src/Elmish.WPF/ViewModelModule.fs | 4 +- src/Elmish.WPF/ViewModels.fs | 727 +- src/Elmish.WPF/WindowState.fs | 81 +- src/Elmish.WPF/WpfProgram.fs | 733 +- src/Samples/Capabilities.Core/Program.fs | 96 +- src/Samples/Capabilities.Core/Selection.fs | 117 +- src/Samples/Capabilities.Core/Utilities.fs | 3 +- .../EventBindingsAndBehaviors.Core/Program.fs | 111 +- src/Samples/FileDialogs.Core/Program.fs | 167 +- src/Samples/FileDialogsCmdMsg.Core/Program.fs | 208 +- src/Samples/Multiselect.Core/Program.fs | 97 +- src/Samples/NewWindow.Core/App.fs | 108 +- src/Samples/NewWindow.Core/AutoOpen.fs | 32 +- src/Samples/NewWindow.Core/Program.fs | 39 +- src/Samples/NewWindow.Core/Window1.fs | 6 +- src/Samples/NewWindow.Core/Window2.fs | 119 +- src/Samples/OneWaySeq.Core/Program.fs | 57 +- src/Samples/SingleCounter.Core/Program.fs | 65 +- src/Samples/Sticky.Core/Program.fs | 71 +- src/Samples/SubModel.Core/Program.fs | 272 +- src/Samples/SubModelOpt.Core/Program.fs | 209 +- .../SubModelSelectedItem.Core/Program.fs | 69 +- src/Samples/SubModelSeq.Core/Program.fs | 450 +- src/Samples/SubModelStatic.Core/Program.fs | 355 +- src/Samples/Threading.Core/Program.fs | 98 +- src/Samples/UiBoundCmdParam.Core/Program.fs | 59 +- src/Samples/Validation.Core/Program.fs | 137 +- 43 files changed, 13054 insertions(+), 12219 deletions(-) diff --git a/src/Elmish.WPF.Benchmarks/Program.fs b/src/Elmish.WPF.Benchmarks/Program.fs index 3ef14862..e79b0467 100644 --- a/src/Elmish.WPF.Benchmarks/Program.fs +++ b/src/Elmish.WPF.Benchmarks/Program.fs @@ -4,34 +4,36 @@ open BenchmarkDotNet.Attributes open BenchmarkDotNet.Running type public BenchmarkDynamicViewModel() = - let mutable model = 0 - let mutable vm = DynamicViewModel(ViewModelArgs.simple model, []) + let mutable model = 0 + let mutable vm = DynamicViewModel(ViewModelArgs.simple model, []) - [] - member public x.GlobalSetup() = - let createBinding i = - Binding.oneWay id $"testBinding_%i{i}" + [] + member public x.GlobalSetup() = + let createBinding i = Binding.oneWay id $"testBinding_%i{i}" - let bindings = - System.Linq.Enumerable.Range(0, x.BindingCount) |> Seq.map createBinding |> Seq.toList + let bindings = + System.Linq.Enumerable.Range(0, x.BindingCount) + |> Seq.map createBinding + |> Seq.toList - vm <- DynamicViewModel(ViewModelArgs.simple model, bindings) + vm <- DynamicViewModel(ViewModelArgs.simple model, bindings) - [] - member public x.Update() = - model <- 0 - while model < x.UpdateCount do - model <- model + 1 - IViewModel.updateModel (vm, model) + [] + member public x.Update() = + model <- 0 - vm :> obj + while model < x.UpdateCount do + model <- model + 1 + IViewModel.updateModel (vm, model) - [] - member val public BindingCount = 0 with get, set + vm :> obj - [] - member val public UpdateCount = 0 with get, set + [] + member val public BindingCount = 0 with get, set + [] + member val public UpdateCount = 0 with get, set -let _ = BenchmarkRunner.Run() + +let _ = BenchmarkRunner.Run() \ No newline at end of file diff --git a/src/Elmish.WPF.Tests/AutoOpen.fs b/src/Elmish.WPF.Tests/AutoOpen.fs index f6216e3b..d7c5ef81 100644 --- a/src/Elmish.WPF.Tests/AutoOpen.fs +++ b/src/Elmish.WPF.Tests/AutoOpen.fs @@ -3,72 +3,76 @@ module AutoOpen type InvokeTester<'a, 'b>(f: 'a -> 'b) = - let mutable count = 0 - let mutable values = [] - let wrapped a = - count <- count + 1 - values <- values @ [a] - f a - member __.Fn = wrapped - member __.Count = count - member __.Values = values - member __.Reset () = - count <- 0 - values <- [] + let mutable count = 0 + let mutable values = [] + + let wrapped a = + count <- count + 1 + values <- values @ [ a ] + f a + + member __.Fn = wrapped + member __.Count = count + member __.Values = values + + member __.Reset() = + count <- 0 + values <- [] type InvokeTester2<'a, 'b, 'c>(f: 'a -> 'b -> 'c) = - let mutable count = 0 - let mutable values = [] - let wrapped a b = - count <- count + 1 - values <- values @ [a, b] - f a b - member __.Fn = wrapped - member __.Count = count - member __.Values = values - member __.Reset () = - count <- 0 - values <- [] + let mutable count = 0 + let mutable values = [] + + let wrapped a b = + count <- count + 1 + values <- values @ [ a, b ] + f a b + + member __.Fn = wrapped + member __.Count = count + member __.Values = values + + member __.Reset() = + count <- 0 + values <- [] type InvokeTester3<'a, 'b, 'c, 'd>(f: 'a -> 'b -> 'c -> 'd) = - let mutable count = 0 - let mutable values = [] - let wrapped a b c = - count <- count + 1 - values <- values @ [a, b, c] - f a b c - member __.Fn = wrapped - member __.Count = count - member __.Values = values - member __.Reset () = - count <- 0 - values <- [] + let mutable count = 0 + let mutable values = [] + + let wrapped a b c = + count <- count + 1 + values <- values @ [ a, b, c ] + f a b c + + member __.Fn = wrapped + member __.Count = count + member __.Values = values + + member __.Reset() = + count <- 0 + values <- [] [] module String = - let length (s: string) = s.Length + let length (s: string) = s.Length [] module List = - let swap i j = - List.permute - (function - | a when a = i -> j - | a when a = j -> i - | a -> a) - - let insert i a ma = - (ma |> List.take i) - @ [ a ] - @ (ma |> List.skip i) - - let replace i a ma = - (ma |> List.take i) - @ [ a ] - @ (ma |> List.skip (i + 1)) + let swap i j = + List.permute (function + | a when a = i -> j + | a when a = j -> i + | a -> a) + + let insert i a ma = + (ma |> List.take i) @ [ a ] @ (ma |> List.skip i) + + let replace i a ma = + (ma |> List.take i) @ [ a ] @ (ma |> List.skip (i + 1)) \ No newline at end of file diff --git a/src/Elmish.WPF.Tests/BindingTests.fs b/src/Elmish.WPF.Tests/BindingTests.fs index 5f805f73..c3607ddb 100644 --- a/src/Elmish.WPF.Tests/BindingTests.fs +++ b/src/Elmish.WPF.Tests/BindingTests.fs @@ -10,2986 +10,3401 @@ open Elmish.WPF [] module internal Helpers = - let fail _ = failwith "Placeholder function was invoked" - let fail2 _ _ = failwith "Placeholder function was invoked" - - let rec getBaseBindingData = function - | BaseBindingData d -> d - | CachingData d -> getBaseBindingData d - | ValidationData d -> getBaseBindingData d.BindingData - | LazyData d -> - d.BindingData - |> BindingData.mapModel d.Get - |> BindingData.mapMsgWithModel d.Set - |> getBaseBindingData - | AlterMsgStreamData _ -> raise (System.NotSupportedException()) // hack: reasonable because this is test code and the tests don't currently use this case - - let getOneWayData f = - match getBaseBindingData (f "").Data with - | OneWayData d -> d - | _ -> failwith "Incorrect binding" - - let getOneWaySeqData f = - match getBaseBindingData (f "").Data with - | OneWaySeqData d -> d - | _ -> failwith "Incorrect binding" - - let getTwoWayData f = - match getBaseBindingData (f "").Data with - | TwoWayData d -> d - | _ -> failwith "Incorrect binding" - - let getCmdData f = - match getBaseBindingData (f "").Data with - | CmdData d -> d - | _ -> failwith "Incorrect binding" - - let getSubModelData f = - match getBaseBindingData (f "").Data with - | SubModelData d -> d - | _ -> failwith "Incorrect binding" - - let getSubModelSeqKeyedData f = - match getBaseBindingData (f "").Data with - | SubModelSeqKeyedData d -> d - | _ -> failwith "Incorrect binding" - - let getSubModelSelectedItemData f = - match getBaseBindingData (f "").Data with - | SubModelSelectedItemData d -> d - | _ -> failwith "Incorrect binding" - - let getValidationData f = - match f "" with - | { Data = ValidationData d } -> d - | _ -> failwith "Incorrect binding" + let fail _ = + failwith "Placeholder function was invoked" + + let fail2 _ _ = + failwith "Placeholder function was invoked" + + let rec getBaseBindingData = + function + | BaseBindingData d -> d + | CachingData d -> getBaseBindingData d + | ValidationData d -> getBaseBindingData d.BindingData + | LazyData d -> + d.BindingData + |> BindingData.mapModel d.Get + |> BindingData.mapMsgWithModel d.Set + |> getBaseBindingData + | AlterMsgStreamData _ -> raise (System.NotSupportedException()) // hack: reasonable because this is test code and the tests don't currently use this case + + let getOneWayData f = + match getBaseBindingData (f "").Data with + | OneWayData d -> d + | _ -> failwith "Incorrect binding" + + let getOneWaySeqData f = + match getBaseBindingData (f "").Data with + | OneWaySeqData d -> d + | _ -> failwith "Incorrect binding" + + let getTwoWayData f = + match getBaseBindingData (f "").Data with + | TwoWayData d -> d + | _ -> failwith "Incorrect binding" + + let getCmdData f = + match getBaseBindingData (f "").Data with + | CmdData d -> d + | _ -> failwith "Incorrect binding" + + let getSubModelData f = + match getBaseBindingData (f "").Data with + | SubModelData d -> d + | _ -> failwith "Incorrect binding" + + let getSubModelSeqKeyedData f = + match getBaseBindingData (f "").Data with + | SubModelSeqKeyedData d -> d + | _ -> failwith "Incorrect binding" + + let getSubModelSelectedItemData f = + match getBaseBindingData (f "").Data with + | SubModelSelectedItemData d -> d + | _ -> failwith "Incorrect binding" + + let getValidationData f = + match f "" with + | { Data = ValidationData d } -> d + | _ -> failwith "Incorrect binding" module oneWay = - [] - let ``sets the correct binding name`` () = - Property.check <| property { - let! bindingName = GenX.auto - let binding = bindingName |> Binding.OneWay.id - test <@ binding.Name = bindingName @> - } + [] + let ``sets the correct binding name`` () = + Property.check + <| property { + let! bindingName = GenX.auto + let binding = bindingName |> Binding.OneWay.id + test <@ binding.Name = bindingName @> + } - [] - let ``final get returns value from original get`` () = - Property.check <| property { - let! x = GenX.auto + [] + let ``final get returns value from original get`` () = + Property.check + <| property { + let! x = GenX.auto - let get = string - let d = Binding.oneWay(get) |> getOneWayData + let get = string + let d = Binding.oneWay (get) |> getOneWayData - test <@ d.Get x |> unbox = get x @> - } + test <@ d.Get x |> unbox = get x @> + } module oneWayOpt = - module option = + module option = - [] - let ``when original get returns Some, final get returns the inner value`` () = - Property.check <| property { - let! x = GenX.auto + [] + let ``when original get returns Some, final get returns the inner value`` () = + Property.check + <| property { + let! x = GenX.auto - let get = string >> Some - let d = Binding.oneWayOpt(get) |> getOneWayData + let get = string >> Some + let d = Binding.oneWayOpt (get) |> getOneWayData - test <@ d.Get x |> unbox = (get x).Value @> - } + test <@ d.Get x |> unbox = (get x).Value @> + } - [] - let ``when original get returns None, final get returns null`` () = - Property.check <| property { - let! x = GenX.auto + [] + let ``when original get returns None, final get returns null`` () = + Property.check + <| property { + let! x = GenX.auto - let get _ = None - let d = Binding.oneWayOpt(get) |> getOneWayData + let get _ = None + let d = Binding.oneWayOpt (get) |> getOneWayData - test <@ isNull (d.Get x) @> - } + test <@ isNull (d.Get x) @> + } - module voption = + module voption = - [] - let ``sets the correct binding name`` () = - Property.check <| property { - let! bindingName = GenX.auto - let binding = bindingName |> Binding.oneWayOpt((fail: _ -> _ voption)) - test <@ binding.Name = bindingName @> - } + [] + let ``sets the correct binding name`` () = + Property.check + <| property { + let! bindingName = GenX.auto + let binding = bindingName |> Binding.oneWayOpt ((fail: _ -> _ voption)) + test <@ binding.Name = bindingName @> + } - [] - let ``when original get returns ValueSome, final get returns the inner value`` () = - Property.check <| property { - let! x = GenX.auto + [] + let ``when original get returns ValueSome, final get returns the inner value`` () = + Property.check + <| property { + let! x = GenX.auto - let get = string >> ValueSome - let d = Binding.oneWayOpt(get) |> getOneWayData + let get = string >> ValueSome + let d = Binding.oneWayOpt (get) |> getOneWayData - test <@ d.Get x |> unbox = (get x).Value @> - } + test <@ d.Get x |> unbox = (get x).Value @> + } - [] - let ``when original get returns ValueNone, final get returns null`` () = - Property.check <| property { - let! x = GenX.auto + [] + let ``when original get returns ValueNone, final get returns null`` () = + Property.check + <| property { + let! x = GenX.auto - let get _ = ValueNone - let d = Binding.oneWayOpt(get) |> getOneWayData + let get _ = ValueNone + let d = Binding.oneWayOpt (get) |> getOneWayData - test <@ isNull (d.Get x) @> - } + test <@ isNull (d.Get x) @> + } module oneWaySeq = - [] - let ``sets the correct binding name`` () = - Property.check <| property { - let! bindingName = GenX.auto - let binding = bindingName |> Binding.oneWaySeq(fail, fail2, fail) - test <@ binding.Name = bindingName @> - } + [] + let ``sets the correct binding name`` () = + Property.check + <| property { + let! bindingName = GenX.auto + let binding = bindingName |> Binding.oneWaySeq (fail, fail2, fail) + test <@ binding.Name = bindingName @> + } - [] - let ``final get returns value from original get`` () = - Property.check <| property { - let! x = GenX.auto + [] + let ``final get returns value from original get`` () = + Property.check + <| property { + let! x = GenX.auto - let get (i: int) = Seq.singleton i - let d = Binding.OneWaySeq.create get fail2 fail |> getOneWaySeqData + let get (i: int) = Seq.singleton i + let d = Binding.OneWaySeq.create get fail2 fail |> getOneWaySeqData - test <@ d.Get x |> Seq.map unbox |> Seq.toList = (get x |> Seq.toList) @> - } + test <@ d.Get x |> Seq.map unbox |> Seq.toList = (get x |> Seq.toList) @> + } - [] - let ``final getId returns value from original getId`` () = - Property.check <| property { - let! x = GenX.auto + [] + let ``final getId returns value from original getId`` () = + Property.check + <| property { + let! x = GenX.auto - let getId = string - let d = Binding.oneWaySeq(fail, fail2, getId) |> getOneWaySeqData + let getId = string + let d = Binding.oneWaySeq (fail, fail2, getId) |> getOneWaySeqData - test <@ d.GetId (box x) |> unbox = getId x @> - } + test <@ d.GetId(box x) |> unbox = getId x @> + } - [] - let ``final itemEquals returns value from original itemEquals`` () = - Property.check <| property { - let! x = GenX.auto - let! y = GenX.auto + [] + let ``final itemEquals returns value from original itemEquals`` () = + Property.check + <| property { + let! x = GenX.auto + let! y = GenX.auto - let itemEquals : int -> int -> bool = (=) - let d = Binding.oneWaySeq(fail, itemEquals, fail) |> getOneWaySeqData + let itemEquals: int -> int -> bool = (=) + let d = Binding.oneWaySeq (fail, itemEquals, fail) |> getOneWaySeqData - test <@ d.ItemEquals (box x) (box y) = itemEquals x y @> - } + test <@ d.ItemEquals (box x) (box y) = itemEquals x y @> + } module oneWaySeqLazy = - [] - let ``sets the correct binding name`` () = - Property.check <| property { - let! bindingName = GenX.auto - let binding = bindingName |> Binding.oneWaySeqLazy(fail, fail2, fail, fail2, fail) - test <@ binding.Name = bindingName @> - } + [] + let ``sets the correct binding name`` () = + Property.check + <| property { + let! bindingName = GenX.auto + let binding = bindingName |> Binding.oneWaySeqLazy (fail, fail2, fail, fail2, fail) + test <@ binding.Name = bindingName @> + } + + [] + let ``final getId returns value from original getId`` () = + Property.check + <| property { + let! x = GenX.auto - [] - let ``final getId returns value from original getId`` () = - Property.check <| property { - let! x = GenX.auto + let getId = string + let d = Binding.oneWaySeqLazy (fail, fail2, fail, fail2, getId) |> getOneWaySeqData - let getId = string - let d = Binding.oneWaySeqLazy(fail, fail2, fail, fail2, getId) |> getOneWaySeqData + test <@ d.GetId(box x) |> unbox = getId x @> + } - test <@ d.GetId (box x) |> unbox = getId x @> - } + [] + let ``final itemEquals returns value from original itemEquals`` () = + Property.check + <| property { + let! x = GenX.auto + let! y = GenX.auto - [] - let ``final itemEquals returns value from original itemEquals`` () = - Property.check <| property { - let! x = GenX.auto - let! y = GenX.auto + let itemEquals: int -> int -> bool = (=) - let itemEquals : int -> int -> bool = (=) - let d = Binding.oneWaySeqLazy(fail, fail2, fail, itemEquals, fail) |> getOneWaySeqData + let d = + Binding.oneWaySeqLazy (fail, fail2, fail, itemEquals, fail) |> getOneWaySeqData - test <@ d.ItemEquals (box x) (box y) = itemEquals x y @> - } + test <@ d.ItemEquals (box x) (box y) = itemEquals x y @> + } module twoWay = - module setModel = + module setModel = - [] - let ``sets the correct binding name`` () = - Property.check <| property { - let! bindingName = GenX.auto - let binding = bindingName |> Binding.TwoWay.id - test <@ binding.Name = bindingName @> - } + [] + let ``sets the correct binding name`` () = + Property.check + <| property { + let! bindingName = GenX.auto + let binding = bindingName |> Binding.TwoWay.id + test <@ binding.Name = bindingName @> + } - [] - let ``final get returns value from original get`` () = - Property.check <| property { - let! x = GenX.auto + [] + let ``final get returns value from original get`` () = + Property.check + <| property { + let! x = GenX.auto - let get = string - let d = Binding.twoWay(get, fail2) |> getTwoWayData + let get = string + let d = Binding.twoWay (get, fail2) |> getTwoWayData - test <@ d.Get x |> unbox = get x @> - } + test <@ d.Get x |> unbox = get x @> + } - [] - let ``final set returns value from original set`` () = - Property.check <| property { - let! m = GenX.auto - let! p = GenX.auto + [] + let ``final set returns value from original set`` () = + Property.check + <| property { + let! m = GenX.auto + let! p = GenX.auto - let set (p: string) (m: int) = p + string m - let d = Binding.twoWay((fun _ -> ""), set) |> getTwoWayData + let set (p: string) (m: int) = p + string m + let d = Binding.twoWay ((fun _ -> ""), set) |> getTwoWayData - test <@ d.Set (box p) m |> unbox = set p m @> - } + test <@ d.Set (box p) m |> unbox = set p m @> + } - module noSetModel = + module noSetModel = - [] - let ``sets the correct binding name`` () = - Property.check <| property { - let! bindingName = GenX.auto - let binding = bindingName |> Binding.twoWay(fail, (fail: string -> int)) - test <@ binding.Name = bindingName @> - } + [] + let ``sets the correct binding name`` () = + Property.check + <| property { + let! bindingName = GenX.auto + let binding = bindingName |> Binding.twoWay (fail, (fail: string -> int)) + test <@ binding.Name = bindingName @> + } - [] - let ``final get returns value from original get`` () = - Property.check <| property { - let! x = GenX.auto + [] + let ``final get returns value from original get`` () = + Property.check + <| property { + let! x = GenX.auto - let get = string - let d = Binding.twoWay(get, (fail: string -> int)) |> getTwoWayData + let get = string + let d = Binding.twoWay (get, (fail: string -> int)) |> getTwoWayData - test <@ d.Get x |> unbox = get x @> - } + test <@ d.Get x |> unbox = get x @> + } - [] - let ``final set returns value from original set`` () = - Property.check <| property { - let! m = GenX.auto - let! p = GenX.auto + [] + let ``final set returns value from original set`` () = + Property.check + <| property { + let! m = GenX.auto + let! p = GenX.auto - let set (p: string) = p + p - let d = Binding.twoWay((fun _ -> ""), set) |> getTwoWayData + let set (p: string) = p + p + let d = Binding.twoWay ((fun _ -> ""), set) |> getTwoWayData - test <@ d.Set (box p) m |> unbox = set p @> - } + test <@ d.Set (box p) m |> unbox = set p @> + } module twoWayOpt = - module option_setModel = + module option_setModel = - [] - let ``when original get returns Some, final get returns the inner value`` () = - Property.check <| property { - let! x = GenX.auto + [] + let ``when original get returns Some, final get returns the inner value`` () = + Property.check + <| property { + let! x = GenX.auto - let get = string >> Some - let d = Binding.twoWayOpt(get, fail2) |> getTwoWayData + let get = string >> Some + let d = Binding.twoWayOpt (get, fail2) |> getTwoWayData - test <@ d.Get x |> unbox = (get x).Value @> - } + test <@ d.Get x |> unbox = (get x).Value @> + } - [] - let ``when original get returns None, final get returns null`` () = - Property.check <| property { - let! x = GenX.auto + [] + let ``when original get returns None, final get returns null`` () = + Property.check + <| property { + let! x = GenX.auto - let get _ = None - let d = Binding.twoWayOpt(get, fail2) |> getTwoWayData + let get _ = None + let d = Binding.twoWayOpt (get, fail2) |> getTwoWayData - test <@ isNull (d.Get x) @> - } + test <@ isNull (d.Get x) @> + } - [] - let ``when final set receives a non-null value, original get receives Some`` () = - Property.check <| property { - let! m = GenX.auto - let! p = GenX.auto + [] + let ``when final set receives a non-null value, original get receives Some`` () = + Property.check + <| property { + let! m = GenX.auto + let! p = GenX.auto - let set (p: string option) (m: int) = p |> Option.map ((+) (string m)) - let d = Binding.twoWayOpt((fun _ -> Some ""), set) |> getTwoWayData + let set (p: string option) (m: int) = p |> Option.map ((+) (string m)) + let d = Binding.twoWayOpt ((fun _ -> Some ""), set) |> getTwoWayData - test <@ d.Set (box p) m |> unbox = set (Some p) m @> - } + test <@ d.Set (box p) m |> unbox = set (Some p) m @> + } - [] - let ``when final set receives null, original get receives None`` () = - Property.check <| property { - let! m = GenX.auto + [] + let ``when final set receives null, original get receives None`` () = + Property.check + <| property { + let! m = GenX.auto - let set (p: string option) (m: int) = p |> Option.map ((+) (string m)) - let d = Binding.twoWayOpt((fun _ -> Some ""), set) |> getTwoWayData + let set (p: string option) (m: int) = p |> Option.map ((+) (string m)) + let d = Binding.twoWayOpt ((fun _ -> Some ""), set) |> getTwoWayData - test <@ d.Set null m |> unbox = set None m @> - } + test <@ d.Set null m |> unbox = set None m @> + } - module voption_setModel = + module voption_setModel = - [] - let ``when original get returns ValueSome, final get returns the inner value`` () = - Property.check <| property { - let! x = GenX.auto + [] + let ``when original get returns ValueSome, final get returns the inner value`` () = + Property.check + <| property { + let! x = GenX.auto - let get = string >> ValueSome - let d = Binding.twoWayOpt(get, fail2) |> getTwoWayData + let get = string >> ValueSome + let d = Binding.twoWayOpt (get, fail2) |> getTwoWayData - test <@ d.Get x |> unbox = (get x).Value @> - } + test <@ d.Get x |> unbox = (get x).Value @> + } - [] - let ``when original get returns ValueNone, final get returns null`` () = - Property.check <| property { - let! x = GenX.auto + [] + let ``when original get returns ValueNone, final get returns null`` () = + Property.check + <| property { + let! x = GenX.auto - let get _ = ValueNone - let d = Binding.twoWayOpt(get, fail2) |> getTwoWayData + let get _ = ValueNone + let d = Binding.twoWayOpt (get, fail2) |> getTwoWayData - test <@ isNull (d.Get x) @> - } + test <@ isNull (d.Get x) @> + } - [] - let ``when final set receives a non-null value, original get receives ValueSome`` () = - Property.check <| property { - let! m = GenX.auto - let! p = GenX.auto + [] + let ``when final set receives a non-null value, original get receives ValueSome`` () = + Property.check + <| property { + let! m = GenX.auto + let! p = GenX.auto - let set (p: string voption) (m: int) = p |> ValueOption.map ((+) (string m)) - let d = Binding.twoWayOpt((fun _ -> ValueSome ""), set) |> getTwoWayData + let set (p: string voption) (m: int) = p |> ValueOption.map ((+) (string m)) + let d = Binding.twoWayOpt ((fun _ -> ValueSome ""), set) |> getTwoWayData - test <@ d.Set (box p) m |> unbox = set (ValueSome p) m @> - } + test <@ d.Set (box p) m |> unbox = set (ValueSome p) m @> + } - [] - let ``when final set receives null, original get receives ValueNone`` () = - Property.check <| property { - let! m = GenX.auto + [] + let ``when final set receives null, original get receives ValueNone`` () = + Property.check + <| property { + let! m = GenX.auto - let set (p: string voption) (m: int) = p |> ValueOption.map ((+) (string m)) - let d = Binding.twoWayOpt((fun _ -> ValueSome ""), set) |> getTwoWayData + let set (p: string voption) (m: int) = p |> ValueOption.map ((+) (string m)) + let d = Binding.twoWayOpt ((fun _ -> ValueSome ""), set) |> getTwoWayData - test <@ d.Set null m |> unbox = set ValueNone m @> - } + test <@ d.Set null m |> unbox = set ValueNone m @> + } - module option_noSetModel = + module option_noSetModel = - [] - let ``when original get returns Some, final get returns the inner value`` () = - Property.check <| property { - let! x = GenX.auto + [] + let ``when original get returns Some, final get returns the inner value`` () = + Property.check + <| property { + let! x = GenX.auto - let get = string >> Some - let d = Binding.twoWayOpt(get, (fail: _ option -> int)) |> getTwoWayData + let get = string >> Some + let d = Binding.twoWayOpt (get, (fail: _ option -> int)) |> getTwoWayData - test <@ d.Get x |> unbox = (get x).Value @> - } + test <@ d.Get x |> unbox = (get x).Value @> + } - [] - let ``when original get returns None, final get returns null`` () = - Property.check <| property { - let! x = GenX.auto + [] + let ``when original get returns None, final get returns null`` () = + Property.check + <| property { + let! x = GenX.auto - let get _ = None - let d = Binding.twoWayOpt(get, (fail: _ option -> int)) |> getTwoWayData + let get _ = None + let d = Binding.twoWayOpt (get, (fail: _ option -> int)) |> getTwoWayData - test <@ isNull (d.Get x) @> - } + test <@ isNull (d.Get x) @> + } - [] - let ``when final set receives a non-null value, original get receives Some`` () = - Property.check <| property { - let! m = GenX.auto - let! p = GenX.auto + [] + let ``when final set receives a non-null value, original get receives Some`` () = + Property.check + <| property { + let! m = GenX.auto + let! p = GenX.auto - let set (p: string option) = p |> Option.map ((+) (string m)) - let d = Binding.twoWayOpt((fun _ -> Some ""), set) |> getTwoWayData + let set (p: string option) = p |> Option.map ((+) (string m)) + let d = Binding.twoWayOpt ((fun _ -> Some ""), set) |> getTwoWayData - test <@ d.Set (box p) m |> unbox = set (Some p) @> - } + test <@ d.Set (box p) m |> unbox = set (Some p) @> + } - [] - let ``when final set receives null, original get receives None`` () = - Property.check <| property { - let! m = GenX.auto + [] + let ``when final set receives null, original get receives None`` () = + Property.check + <| property { + let! m = GenX.auto - let set (p: string option) = p |> Option.map ((+) (string m)) - let d = Binding.twoWayOpt((fun _ -> Some ""), set) |> getTwoWayData + let set (p: string option) = p |> Option.map ((+) (string m)) + let d = Binding.twoWayOpt ((fun _ -> Some ""), set) |> getTwoWayData - test <@ d.Set null m |> unbox = set None @> - } + test <@ d.Set null m |> unbox = set None @> + } - module voption_noSetModel = + module voption_noSetModel = - [] - let ``when original get returns ValueSome, final get returns the inner value`` () = - Property.check <| property { - let! x = GenX.auto + [] + let ``when original get returns ValueSome, final get returns the inner value`` () = + Property.check + <| property { + let! x = GenX.auto - let get = string >> ValueSome - let d = Binding.twoWayOpt(get, (fail: _ voption -> int)) |> getTwoWayData + let get = string >> ValueSome + let d = Binding.twoWayOpt (get, (fail: _ voption -> int)) |> getTwoWayData - test <@ d.Get x |> unbox = (get x).Value @> - } + test <@ d.Get x |> unbox = (get x).Value @> + } - [] - let ``when original get returns ValueNone, final get returns null`` () = - Property.check <| property { - let! x = GenX.auto + [] + let ``when original get returns ValueNone, final get returns null`` () = + Property.check + <| property { + let! x = GenX.auto - let get _ = ValueNone - let d = Binding.twoWayOpt(get, (fail: _ voption -> int)) |> getTwoWayData + let get _ = ValueNone + let d = Binding.twoWayOpt (get, (fail: _ voption -> int)) |> getTwoWayData - test <@ isNull (d.Get x) @> - } + test <@ isNull (d.Get x) @> + } - [] - let ``when final set receives a non-null value, original get receives ValueSome`` () = - Property.check <| property { - let! m = GenX.auto - let! p = GenX.auto + [] + let ``when final set receives a non-null value, original get receives ValueSome`` () = + Property.check + <| property { + let! m = GenX.auto + let! p = GenX.auto - let set (p: string voption) = p |> ValueOption.map ((+) (string m)) - let d = Binding.twoWayOpt((fun _ -> ValueSome ""), set) |> getTwoWayData + let set (p: string voption) = p |> ValueOption.map ((+) (string m)) + let d = Binding.twoWayOpt ((fun _ -> ValueSome ""), set) |> getTwoWayData - test <@ d.Set (box p) m |> unbox = set (ValueSome p) @> - } + test <@ d.Set (box p) m |> unbox = set (ValueSome p) @> + } - [] - let ``when final set receives null, original get receives ValueNone`` () = - Property.check <| property { - let! m = GenX.auto + [] + let ``when final set receives null, original get receives ValueNone`` () = + Property.check + <| property { + let! m = GenX.auto - let set (p: string voption) = p |> ValueOption.map ((+) (string m)) - let d = Binding.twoWayOpt((fun _ -> ValueSome ""), set) |> getTwoWayData + let set (p: string voption) = p |> ValueOption.map ((+) (string m)) + let d = Binding.twoWayOpt ((fun _ -> ValueSome ""), set) |> getTwoWayData - test <@ d.Set null m |> unbox = set ValueNone @> - } + test <@ d.Set null m |> unbox = set ValueNone @> + } module twoWayValidate = - module setModel_validateVoption = + module setModel_validateVoption = - //[] - //let ``final get returns value from original get`` () = - // Property.check <| property { - // let! x = GenX.auto + //[] + //let ``final get returns value from original get`` () = + // Property.check <| property { + // let! x = GenX.auto - // let get = string - // let d = Binding.twoWayValidate(get, fail2, (fail: _ -> _ voption)) |> getValidationData + // let get = string + // let d = Binding.twoWayValidate(get, fail2, (fail: _ -> _ voption)) |> getValidationData - // test <@ d.Get x |> unbox = get x @> - // } + // test <@ d.Get x |> unbox = get x @> + // } - //[] - //let ``final set returns value from original set`` () = - // Property.check <| property { - // let! m = GenX.auto - // let! p = GenX.auto + //[] + //let ``final set returns value from original set`` () = + // Property.check <| property { + // let! m = GenX.auto + // let! p = GenX.auto - // let set (p: string) (m: int) = p + string m - // let d = Binding.twoWayValidate(fail, set, (fail: _ -> _ voption)) |> getValidationData + // let set (p: string) (m: int) = p + string m + // let d = Binding.twoWayValidate(fail, set, (fail: _ -> _ voption)) |> getValidationData - // test <@ d.Set (box p) m |> unbox = set p m @> - // } + // test <@ d.Set (box p) m |> unbox = set p m @> + // } - [] - let ``final validate returns value from original validate`` () = - Property.check <| property { - let! x = GenX.auto - let! err = GenX.auto + [] + let ``final validate returns value from original validate`` () = + Property.check + <| property { + let! x = GenX.auto + let! err = GenX.auto - let validate x = if x < 0 then [ err ] else [] - let d = Binding.twoWayValidate(fail, fail2, validate) |> getValidationData + let validate x = if x < 0 then [ err ] else [] + let d = Binding.twoWayValidate (fail, fail2, validate) |> getValidationData - test <@ d.Validate x |> unbox = validate x @> - } + test <@ d.Validate x |> unbox = validate x @> + } - module setModel_validateOption = + module setModel_validateOption = - //[] - //let ``final get returns value from original get`` () = - // Property.check <| property { - // let! x = GenX.auto + //[] + //let ``final get returns value from original get`` () = + // Property.check <| property { + // let! x = GenX.auto - // let get = string - // let d = Binding.twoWayValidate(get, fail2, (fail: _ -> _ option)) |> getValidationData + // let get = string + // let d = Binding.twoWayValidate(get, fail2, (fail: _ -> _ option)) |> getValidationData - // test <@ d.Get x |> unbox = get x @> - // } + // test <@ d.Get x |> unbox = get x @> + // } - //[] - //let ``final set returns value from original set`` () = - // Property.check <| property { - // let! m = GenX.auto - // let! p = GenX.auto + //[] + //let ``final set returns value from original set`` () = + // Property.check <| property { + // let! m = GenX.auto + // let! p = GenX.auto - // let set (p: string) (m: int) = p + string m - // let d = Binding.twoWayValidate(fail, set, (fail: _ -> _ option)) |> getValidationData + // let set (p: string) (m: int) = p + string m + // let d = Binding.twoWayValidate(fail, set, (fail: _ -> _ option)) |> getValidationData - // test <@ d.Set (box p) m |> unbox = set p m @> - // } + // test <@ d.Set (box p) m |> unbox = set p m @> + // } - [] - let ``final validate returns value from original validate`` () = - Property.check <| property { - let! x = GenX.auto - let! err = GenX.auto + [] + let ``final validate returns value from original validate`` () = + Property.check + <| property { + let! x = GenX.auto + let! err = GenX.auto - let validate x = if x < 0 then [ err ] else [] - let d = Binding.twoWayValidate(fail, fail2, validate) |> getValidationData + let validate x = if x < 0 then [ err ] else [] + let d = Binding.twoWayValidate (fail, fail2, validate) |> getValidationData - test <@ d.Validate x |> unbox = validate x @> - } + test <@ d.Validate x |> unbox = validate x @> + } - module setModel_validateResult = + module setModel_validateResult = - //[] - //let ``final get returns value from original get`` () = - // Property.check <| property { - // let! x = GenX.auto + //[] + //let ``final get returns value from original get`` () = + // Property.check <| property { + // let! x = GenX.auto - // let get = string - // let d = Binding.twoWayValidate(get, fail2, (fail: _ -> Result<_,_>)) |> getValidationData + // let get = string + // let d = Binding.twoWayValidate(get, fail2, (fail: _ -> Result<_,_>)) |> getValidationData - // test <@ d.Get x |> unbox = get x @> - // } + // test <@ d.Get x |> unbox = get x @> + // } - //[] - //let ``final set returns value from original set`` () = - // Property.check <| property { - // let! m = GenX.auto - // let! p = GenX.auto + //[] + //let ``final set returns value from original set`` () = + // Property.check <| property { + // let! m = GenX.auto + // let! p = GenX.auto - // let set (p: string) (m: int) = p + string m - // let d = Binding.twoWayValidate(fail, set, (fail: _ -> Result<_,_>)) |> getValidationData + // let set (p: string) (m: int) = p + string m + // let d = Binding.twoWayValidate(fail, set, (fail: _ -> Result<_,_>)) |> getValidationData - // test <@ d.Set (box p) m |> unbox = set p m @> - // } + // test <@ d.Set (box p) m |> unbox = set p m @> + // } - [] - let ``final validate returns value from original validate`` () = - Property.check <| property { - let! x = GenX.auto - let! err = GenX.auto + [] + let ``final validate returns value from original validate`` () = + Property.check + <| property { + let! x = GenX.auto + let! err = GenX.auto - let validate x = if x < 0 then [] else [ err ] - let d = Binding.twoWayValidate(fail, fail2, validate) |> getValidationData + let validate x = if x < 0 then [] else [ err ] + let d = Binding.twoWayValidate (fail, fail2, validate) |> getValidationData - test <@ d.Validate x |> unbox = validate x @> - } + test <@ d.Validate x |> unbox = validate x @> + } - module noSetModel_validateVoption = + module noSetModel_validateVoption = - //[] - //let ``final get returns value from original get`` () = - // Property.check <| property { - // let! x = GenX.auto + //[] + //let ``final get returns value from original get`` () = + // Property.check <| property { + // let! x = GenX.auto - // let get = string - // let d = Binding.twoWayValidate(get, (fail: string -> int), (fail: _ -> _ voption)) |> getValidationData + // let get = string + // let d = Binding.twoWayValidate(get, (fail: string -> int), (fail: _ -> _ voption)) |> getValidationData - // test <@ d.Get x |> unbox = get x @> - // } + // test <@ d.Get x |> unbox = get x @> + // } - //[] - //let ``final set returns value from original set`` () = - // Property.check <| property { - // let! m = GenX.auto - // let! p = GenX.auto + //[] + //let ``final set returns value from original set`` () = + // Property.check <| property { + // let! m = GenX.auto + // let! p = GenX.auto - // let set (p: string) = p + p - // let d = Binding.twoWayValidate(fail, set, (fail: _ -> _ voption)) |> getValidationData + // let set (p: string) = p + p + // let d = Binding.twoWayValidate(fail, set, (fail: _ -> _ voption)) |> getValidationData - // test <@ d.Set (box p) m |> unbox = set p @> - // } + // test <@ d.Set (box p) m |> unbox = set p @> + // } - [] - let ``final validate returns value from original validate`` () = - Property.check <| property { - let! x = GenX.auto - let! err = GenX.auto + [] + let ``final validate returns value from original validate`` () = + Property.check + <| property { + let! x = GenX.auto + let! err = GenX.auto - let validate x = if x < 0 then [ err ] else [] - let d = Binding.twoWayValidate(fail, (fail: string -> int), validate) |> getValidationData + let validate x = if x < 0 then [ err ] else [] - test <@ d.Validate x |> unbox = validate x @> - } + let d = + Binding.twoWayValidate (fail, (fail: string -> int), validate) + |> getValidationData + test <@ d.Validate x |> unbox = validate x @> + } - module noSetModel_validateOption = + module noSetModel_validateOption = - //[] - //let ``final get returns value from original get`` () = - // Property.check <| property { - // let! x = GenX.auto - // let get = string - // let d = Binding.twoWayValidate(get, (fail: string -> int), (fail: _ -> _ option)) |> getValidationData + //[] + //let ``final get returns value from original get`` () = + // Property.check <| property { + // let! x = GenX.auto - // test <@ d.Get x |> unbox = get x @> - // } + // let get = string + // let d = Binding.twoWayValidate(get, (fail: string -> int), (fail: _ -> _ option)) |> getValidationData + // test <@ d.Get x |> unbox = get x @> + // } - //[] - //let ``final set returns value from original set`` () = - // Property.check <| property { - // let! m = GenX.auto - // let! p = GenX.auto - // let set (p: string) = p + p - // let d = Binding.twoWayValidate(fail, set, (fail: _ -> _ option)) |> getValidationData + //[] + //let ``final set returns value from original set`` () = + // Property.check <| property { + // let! m = GenX.auto + // let! p = GenX.auto - // test <@ d.Set (box p) m |> unbox = set p @> - // } + // let set (p: string) = p + p + // let d = Binding.twoWayValidate(fail, set, (fail: _ -> _ option)) |> getValidationData + // test <@ d.Set (box p) m |> unbox = set p @> + // } - [] - let ``final validate returns value from original validate`` () = - Property.check <| property { - let! x = GenX.auto - let! err = GenX.auto - let validate x = if x < 0 then [ err ] else [] - let d = Binding.twoWayValidate(fail, (fail: string -> int), validate) |> getValidationData + [] + let ``final validate returns value from original validate`` () = + Property.check + <| property { + let! x = GenX.auto + let! err = GenX.auto - test <@ d.Validate x |> unbox = validate x @> - } + let validate x = if x < 0 then [ err ] else [] + let d = + Binding.twoWayValidate (fail, (fail: string -> int), validate) + |> getValidationData + test <@ d.Validate x |> unbox = validate x @> + } - module noSetModel_validateResult = - //[] - //let ``final get returns value from original get`` () = - // Property.check <| property { - // let! x = GenX.auto + module noSetModel_validateResult = - // let get = string - // let d = Binding.twoWayValidate(get, (fail: string -> int), (fail: _ -> Result<_,_>)) |> getValidationData - // test <@ d.Get x |> unbox = get x @> - // } + //[] + //let ``final get returns value from original get`` () = + // Property.check <| property { + // let! x = GenX.auto + // let get = string + // let d = Binding.twoWayValidate(get, (fail: string -> int), (fail: _ -> Result<_,_>)) |> getValidationData - //[] - //let ``final set returns value from original set`` () = - // Property.check <| property { - // let! m = GenX.auto - // let! p = GenX.auto + // test <@ d.Get x |> unbox = get x @> + // } - // let set (p: string) = p + p - // let d = Binding.twoWayValidate(fail, set, (fail: _ -> Result<_,_>)) |> getValidationData - // test <@ d.Set (box p) m |> unbox = set p @> - // } + //[] + //let ``final set returns value from original set`` () = + // Property.check <| property { + // let! m = GenX.auto + // let! p = GenX.auto + // let set (p: string) = p + p + // let d = Binding.twoWayValidate(fail, set, (fail: _ -> Result<_,_>)) |> getValidationData - [] - let ``final validate returns value from original validate`` () = - Property.check <| property { - let! x = GenX.auto - let! err = GenX.auto + // test <@ d.Set (box p) m |> unbox = set p @> + // } + + + [] + let ``final validate returns value from original validate`` () = + Property.check + <| property { + let! x = GenX.auto + let! err = GenX.auto - let validate x = if x < 0 then [] else [ err ] - let d = Binding.twoWayValidate(fail, (fail: string -> int), validate) |> getValidationData + let validate x = if x < 0 then [] else [ err ] - test <@ d.Validate x |> unbox = validate x @> - } + let d = + Binding.twoWayValidate (fail, (fail: string -> int), validate) + |> getValidationData + + test <@ d.Validate x |> unbox = validate x @> + } module twoWayOptValidate = - module voption_setModel_validateVoption = + module voption_setModel_validateVoption = - //[] - //let ``when original get returns ValueSome, final get returns the inner value`` () = - // Property.check <| property { - // let! x = GenX.auto + //[] + //let ``when original get returns ValueSome, final get returns the inner value`` () = + // Property.check <| property { + // let! x = GenX.auto - // let get = string >> ValueSome - // let d = Binding.twoWayOptValidate(get, fail2, (fail: _ -> _ voption)) |> getValidationData + // let get = string >> ValueSome + // let d = Binding.twoWayOptValidate(get, fail2, (fail: _ -> _ voption)) |> getValidationData - // test <@ d.Get x |> unbox = (get x).Value @> - // } + // test <@ d.Get x |> unbox = (get x).Value @> + // } - //[] - //let ``when original get returns ValueNone, final get returns null`` () = - // Property.check <| property { - // let! x = GenX.auto + //[] + //let ``when original get returns ValueNone, final get returns null`` () = + // Property.check <| property { + // let! x = GenX.auto - // let get _ = ValueNone - // let d = Binding.twoWayOptValidate(get, fail2, (fail: _ -> _ voption)) |> getValidationData + // let get _ = ValueNone + // let d = Binding.twoWayOptValidate(get, fail2, (fail: _ -> _ voption)) |> getValidationData - // test <@ isNull (d.Get x) @> - // } + // test <@ isNull (d.Get x) @> + // } - //[] - //let ``when final set receives a non-null value, original get receives ValueSome`` () = - // Property.check <| property { - // let! m = GenX.auto - // let! p = GenX.auto + //[] + //let ``when final set receives a non-null value, original get receives ValueSome`` () = + // Property.check <| property { + // let! m = GenX.auto + // let! p = GenX.auto - // let set (p: string voption) (m: int) = p |> ValueOption.map ((+) (string m)) - // let d = Binding.twoWayOptValidate(fail, set, (fail: _ -> _ voption)) |> getValidationData + // let set (p: string voption) (m: int) = p |> ValueOption.map ((+) (string m)) + // let d = Binding.twoWayOptValidate(fail, set, (fail: _ -> _ voption)) |> getValidationData - // test <@ d.Set (box p) m |> unbox = set (ValueSome p) m @> - // } + // test <@ d.Set (box p) m |> unbox = set (ValueSome p) m @> + // } - //[] - //let ``when final set receives null, original get receives ValueNone`` () = - // Property.check <| property { - // let! m = GenX.auto + //[] + //let ``when final set receives null, original get receives ValueNone`` () = + // Property.check <| property { + // let! m = GenX.auto - // let set (p: string voption) (m: int) = p |> ValueOption.map ((+) (string m)) - // let d = Binding.twoWayOptValidate(fail, set, (fail: _ -> _ voption)) |> getValidationData + // let set (p: string voption) (m: int) = p |> ValueOption.map ((+) (string m)) + // let d = Binding.twoWayOptValidate(fail, set, (fail: _ -> _ voption)) |> getValidationData - // test <@ d.Set null m |> unbox = set ValueNone m @> - // } + // test <@ d.Set null m |> unbox = set ValueNone m @> + // } - [] - let ``final validate returns value from original validate`` () = - Property.check <| property { - let! x = GenX.auto - let! err = GenX.auto + [] + let ``final validate returns value from original validate`` () = + Property.check + <| property { + let! x = GenX.auto + let! err = GenX.auto - let validate x = if x < 0 then [ err ] else [] - let d = Binding.twoWayOptValidate((fail: _ -> _ voption), fail2, validate) |> getValidationData + let validate x = if x < 0 then [ err ] else [] - test <@ d.Validate x |> unbox = validate x @> - } + let d = + Binding.twoWayOptValidate ((fail: _ -> _ voption), fail2, validate) + |> getValidationData + test <@ d.Validate x |> unbox = validate x @> + } - module voption_setModel_validateOption = + module voption_setModel_validateOption = - //[] - //let ``when original get returns ValueSome, final get returns the inner value`` () = - // Property.check <| property { - // let! x = GenX.auto - // let get = string >> ValueSome - // let d = Binding.twoWayOptValidate(get, fail2, (fail: _ -> _ option)) |> getValidationData + //[] + //let ``when original get returns ValueSome, final get returns the inner value`` () = + // Property.check <| property { + // let! x = GenX.auto - // test <@ d.Get x |> unbox = (get x).Value @> - // } + // let get = string >> ValueSome + // let d = Binding.twoWayOptValidate(get, fail2, (fail: _ -> _ option)) |> getValidationData + // test <@ d.Get x |> unbox = (get x).Value @> + // } - //[] - //let ``when original get returns ValueNone, final get returns null`` () = - // Property.check <| property { - // let! x = GenX.auto - // let get _ = ValueNone - // let d = Binding.twoWayOptValidate(get, fail2, (fail: _ -> _ option)) |> getValidationData + //[] + //let ``when original get returns ValueNone, final get returns null`` () = + // Property.check <| property { + // let! x = GenX.auto - // test <@ isNull (d.Get x) @> - // } + // let get _ = ValueNone + // let d = Binding.twoWayOptValidate(get, fail2, (fail: _ -> _ option)) |> getValidationData + // test <@ isNull (d.Get x) @> + // } - //[] - //let ``when final set receives a non-null value, original get receives ValueSome`` () = - // Property.check <| property { - // let! m = GenX.auto - // let! p = GenX.auto - // let set (p: string voption) (m: int) = p |> ValueOption.map ((+) (string m)) - // let d = Binding.twoWayOptValidate(fail, set, (fail: _ -> _ option)) |> getValidationData + //[] + //let ``when final set receives a non-null value, original get receives ValueSome`` () = + // Property.check <| property { + // let! m = GenX.auto + // let! p = GenX.auto - // test <@ d.Set (box p) m |> unbox = set (ValueSome p) m @> - // } + // let set (p: string voption) (m: int) = p |> ValueOption.map ((+) (string m)) + // let d = Binding.twoWayOptValidate(fail, set, (fail: _ -> _ option)) |> getValidationData + // test <@ d.Set (box p) m |> unbox = set (ValueSome p) m @> + // } - //[] - //let ``when final set receives null, original get receives ValueNone`` () = - // Property.check <| property { - // let! m = GenX.auto - // let set (p: string voption) (m: int) = p |> ValueOption.map ((+) (string m)) - // let d = Binding.twoWayOptValidate(fail, set, (fail: _ -> _ option)) |> getValidationData + //[] + //let ``when final set receives null, original get receives ValueNone`` () = + // Property.check <| property { + // let! m = GenX.auto - // test <@ d.Set null m |> unbox = set ValueNone m @> - // } + // let set (p: string voption) (m: int) = p |> ValueOption.map ((+) (string m)) + // let d = Binding.twoWayOptValidate(fail, set, (fail: _ -> _ option)) |> getValidationData + // test <@ d.Set null m |> unbox = set ValueNone m @> + // } - [] - let ``final validate returns value from original validate`` () = - Property.check <| property { - let! x = GenX.auto - let! err = GenX.auto - let validate x = if x < 0 then [ err ] else [] - let d = Binding.twoWayOptValidate((fail: _ -> _ voption), fail2, validate) |> getValidationData + [] + let ``final validate returns value from original validate`` () = + Property.check + <| property { + let! x = GenX.auto + let! err = GenX.auto - test <@ d.Validate x |> unbox = validate x @> - } + let validate x = if x < 0 then [ err ] else [] + let d = + Binding.twoWayOptValidate ((fail: _ -> _ voption), fail2, validate) + |> getValidationData + test <@ d.Validate x |> unbox = validate x @> + } - module voption_setModel_validateResult = - //[] - //let ``when original get returns ValueSome, final get returns the inner value`` () = - // Property.check <| property { - // let! x = GenX.auto + module voption_setModel_validateResult = - // let get = string >> ValueSome - // let d = Binding.twoWayOptValidate(get, fail2, (fail: _ -> Result<_,_>)) |> getValidationData - // test <@ d.Get x |> unbox = (get x).Value @> - // } + //[] + //let ``when original get returns ValueSome, final get returns the inner value`` () = + // Property.check <| property { + // let! x = GenX.auto + // let get = string >> ValueSome + // let d = Binding.twoWayOptValidate(get, fail2, (fail: _ -> Result<_,_>)) |> getValidationData - //[] - //let ``when original get returns ValueNone, final get returns null`` () = - // Property.check <| property { - // let! x = GenX.auto + // test <@ d.Get x |> unbox = (get x).Value @> + // } - // let get _ = ValueNone - // let d = Binding.twoWayOptValidate(get, fail2, (fail: _ -> Result<_,_>)) |> getValidationData - // test <@ isNull (d.Get x) @> - // } + //[] + //let ``when original get returns ValueNone, final get returns null`` () = + // Property.check <| property { + // let! x = GenX.auto + // let get _ = ValueNone + // let d = Binding.twoWayOptValidate(get, fail2, (fail: _ -> Result<_,_>)) |> getValidationData - //[] - //let ``when final set receives a non-null value, original get receives ValueSome`` () = - // Property.check <| property { - // let! m = GenX.auto - // let! p = GenX.auto + // test <@ isNull (d.Get x) @> + // } - // let set (p: string voption) (m: int) = p |> ValueOption.map ((+) (string m)) - // let d = Binding.twoWayOptValidate(fail, set, (fail: _ -> Result<_,_>)) |> getValidationData - // test <@ d.Set (box p) m |> unbox = set (ValueSome p) m @> - // } + //[] + //let ``when final set receives a non-null value, original get receives ValueSome`` () = + // Property.check <| property { + // let! m = GenX.auto + // let! p = GenX.auto + // let set (p: string voption) (m: int) = p |> ValueOption.map ((+) (string m)) + // let d = Binding.twoWayOptValidate(fail, set, (fail: _ -> Result<_,_>)) |> getValidationData - //[] - //let ``when final set receives null, original get receives ValueNone`` () = - // Property.check <| property { - // let! m = GenX.auto + // test <@ d.Set (box p) m |> unbox = set (ValueSome p) m @> + // } - // let set (p: string voption) (m: int) = p |> ValueOption.map ((+) (string m)) - // let d = Binding.twoWayOptValidate(fail, set, (fail: _ -> Result<_,_>)) |> getValidationData - // test <@ d.Set null m |> unbox = set ValueNone m @> - // } + //[] + //let ``when final set receives null, original get receives ValueNone`` () = + // Property.check <| property { + // let! m = GenX.auto + // let set (p: string voption) (m: int) = p |> ValueOption.map ((+) (string m)) + // let d = Binding.twoWayOptValidate(fail, set, (fail: _ -> Result<_,_>)) |> getValidationData - [] - let ``final validate returns value from original validate`` () = - Property.check <| property { - let! x = GenX.auto - let! err = GenX.auto + // test <@ d.Set null m |> unbox = set ValueNone m @> + // } - let validate x = if x < 0 then [] else [ err ] - let d = Binding.twoWayOptValidate((fail: _ -> _ voption), fail2, validate) |> getValidationData - test <@ d.Validate x |> unbox = validate x @> - } + [] + let ``final validate returns value from original validate`` () = + Property.check + <| property { + let! x = GenX.auto + let! err = GenX.auto + let validate x = if x < 0 then [] else [ err ] + let d = + Binding.twoWayOptValidate ((fail: _ -> _ voption), fail2, validate) + |> getValidationData - module option_setModel_validateVoption = + test <@ d.Validate x |> unbox = validate x @> + } - //[] - //let ``when original get returns Some, final get returns the inner value`` () = - // Property.check <| property { - // let! x = GenX.auto - // let get = string >> Some - // let d = Binding.twoWayOptValidate(get, fail2, (fail: _ -> _ voption)) |> getValidationData + module option_setModel_validateVoption = - // test <@ d.Get x |> unbox = (get x).Value @> - // } + //[] + //let ``when original get returns Some, final get returns the inner value`` () = + // Property.check <| property { + // let! x = GenX.auto - //[] - //let ``when original get returns None, final get returns null`` () = - // Property.check <| property { - // let! x = GenX.auto + // let get = string >> Some + // let d = Binding.twoWayOptValidate(get, fail2, (fail: _ -> _ voption)) |> getValidationData - // let get _ = None - // let d = Binding.twoWayOptValidate(get, fail2, (fail: _ -> _ voption)) |> getValidationData + // test <@ d.Get x |> unbox = (get x).Value @> + // } - // test <@ isNull (d.Get x) @> - // } + //[] + //let ``when original get returns None, final get returns null`` () = + // Property.check <| property { + // let! x = GenX.auto - //[] - //let ``when final set receives a non-null value, original get receives Some`` () = - // Property.check <| property { - // let! m = GenX.auto - // let! p = GenX.auto + // let get _ = None + // let d = Binding.twoWayOptValidate(get, fail2, (fail: _ -> _ voption)) |> getValidationData - // let set (p: string option) (m: int) = p |> Option.map ((+) (string m)) - // let d = Binding.twoWayOptValidate(fail, set, (fail: _ -> _ voption)) |> getValidationData + // test <@ isNull (d.Get x) @> + // } - // test <@ d.Set (box p) m |> unbox = set (Some p) m @> - // } + //[] + //let ``when final set receives a non-null value, original get receives Some`` () = + // Property.check <| property { + // let! m = GenX.auto + // let! p = GenX.auto - //[] - //let ``when final set receives null, original get receives None`` () = - // Property.check <| property { - // let! m = GenX.auto + // let set (p: string option) (m: int) = p |> Option.map ((+) (string m)) + // let d = Binding.twoWayOptValidate(fail, set, (fail: _ -> _ voption)) |> getValidationData - // let set (p: string option) (m: int) = p |> Option.map ((+) (string m)) - // let d = Binding.twoWayOptValidate(fail, set, (fail: _ -> _ voption)) |> getValidationData + // test <@ d.Set (box p) m |> unbox = set (Some p) m @> + // } - // test <@ d.Set null m |> unbox = set None m @> - // } + //[] + //let ``when final set receives null, original get receives None`` () = + // Property.check <| property { + // let! m = GenX.auto - [] - let ``final validate returns value from original validate`` () = - Property.check <| property { - let! x = GenX.auto - let! err = GenX.auto + // let set (p: string option) (m: int) = p |> Option.map ((+) (string m)) + // let d = Binding.twoWayOptValidate(fail, set, (fail: _ -> _ voption)) |> getValidationData - let validate x = if x < 0 then [ err ] else [] - let d = Binding.twoWayOptValidate((fail: _ -> _ option), fail2, validate) |> getValidationData + // test <@ d.Set null m |> unbox = set None m @> + // } - test <@ d.Validate x |> unbox = validate x @> - } + [] + let ``final validate returns value from original validate`` () = + Property.check + <| property { + let! x = GenX.auto + let! err = GenX.auto + let validate x = if x < 0 then [ err ] else [] - module option_setModel_validateOption = + let d = + Binding.twoWayOptValidate ((fail: _ -> _ option), fail2, validate) + |> getValidationData + test <@ d.Validate x |> unbox = validate x @> + } - //[] - //let ``when original get returns Some, final get returns the inner value`` () = - // Property.check <| property { - // let! x = GenX.auto - // let get = string >> Some - // let d = Binding.twoWayOptValidate(get, fail2, (fail: _ -> _ option)) |> getValidationData - // test <@ d.Get x |> unbox = (get x).Value @> - // } + module option_setModel_validateOption = - //[] - //let ``when original get returns None, final get returns null`` () = - // Property.check <| property { - // let! x = GenX.auto + //[] + //let ``when original get returns Some, final get returns the inner value`` () = + // Property.check <| property { + // let! x = GenX.auto - // let get _ = None - // let d = Binding.twoWayOptValidate(get, fail2, (fail: _ -> _ option)) |> getValidationData + // let get = string >> Some + // let d = Binding.twoWayOptValidate(get, fail2, (fail: _ -> _ option)) |> getValidationData - // test <@ isNull (d.Get x) @> - // } + // test <@ d.Get x |> unbox = (get x).Value @> + // } - //[] - //let ``when final set receives a non-null value, original get receives Some`` () = - // Property.check <| property { - // let! m = GenX.auto - // let! p = GenX.auto + //[] + //let ``when original get returns None, final get returns null`` () = + // Property.check <| property { + // let! x = GenX.auto - // let set (p: string option) (m: int) = p |> Option.map ((+) (string m)) - // let d = Binding.twoWayOptValidate(fail, set, (fail: _ -> _ option)) |> getValidationData + // let get _ = None + // let d = Binding.twoWayOptValidate(get, fail2, (fail: _ -> _ option)) |> getValidationData - // test <@ d.Set (box p) m |> unbox = set (Some p) m @> - // } + // test <@ isNull (d.Get x) @> + // } - //[] - //let ``when final set receives null, original get receives None`` () = - // Property.check <| property { - // let! m = GenX.auto + //[] + //let ``when final set receives a non-null value, original get receives Some`` () = + // Property.check <| property { + // let! m = GenX.auto + // let! p = GenX.auto - // let set (p: string option) (m: int) = p |> Option.map ((+) (string m)) - // let d = Binding.twoWayOptValidate(fail, set, (fail: _ -> _ option)) |> getValidationData + // let set (p: string option) (m: int) = p |> Option.map ((+) (string m)) + // let d = Binding.twoWayOptValidate(fail, set, (fail: _ -> _ option)) |> getValidationData - // test <@ d.Set null m |> unbox = set None m @> - // } + // test <@ d.Set (box p) m |> unbox = set (Some p) m @> + // } - [] - let ``final validate returns value from original validate`` () = - Property.check <| property { - let! x = GenX.auto - let! err = GenX.auto + //[] + //let ``when final set receives null, original get receives None`` () = + // Property.check <| property { + // let! m = GenX.auto - let validate x = if x < 0 then [ err ] else [] - let d = Binding.twoWayOptValidate((fail: _ -> _ option), fail2, validate) |> getValidationData + // let set (p: string option) (m: int) = p |> Option.map ((+) (string m)) + // let d = Binding.twoWayOptValidate(fail, set, (fail: _ -> _ option)) |> getValidationData - test <@ d.Validate x |> unbox = validate x @> - } + // test <@ d.Set null m |> unbox = set None m @> + // } + [] + let ``final validate returns value from original validate`` () = + Property.check + <| property { + let! x = GenX.auto + let! err = GenX.auto - module option_setModel_validateResult = + let validate x = if x < 0 then [ err ] else [] + let d = + Binding.twoWayOptValidate ((fail: _ -> _ option), fail2, validate) + |> getValidationData - //[] - //let ``when original get returns Some, final get returns the inner value`` () = - // Property.check <| property { - // let! x = GenX.auto + test <@ d.Validate x |> unbox = validate x @> + } - // let get = string >> Some - // let d = Binding.twoWayOptValidate(get, fail2, (fail: _ -> Result<_,_>)) |> getValidationData - // test <@ d.Get x |> unbox = (get x).Value @> - // } + module option_setModel_validateResult = - //[] - //let ``when original get returns None, final get returns null`` () = - // Property.check <| property { - // let! x = GenX.auto - // let get _ = None - // let d = Binding.twoWayOptValidate(get, fail2, (fail: _ -> Result<_,_>)) |> getValidationData + //[] + //let ``when original get returns Some, final get returns the inner value`` () = + // Property.check <| property { + // let! x = GenX.auto - // test <@ isNull (d.Get x) @> - // } + // let get = string >> Some + // let d = Binding.twoWayOptValidate(get, fail2, (fail: _ -> Result<_,_>)) |> getValidationData + // test <@ d.Get x |> unbox = (get x).Value @> + // } - //[] - //let ``when final set receives a non-null value, original get receives Some`` () = - // Property.check <| property { - // let! m = GenX.auto - // let! p = GenX.auto - // let set (p: string option) (m: int) = p |> Option.map ((+) (string m)) - // let d = Binding.twoWayOptValidate(fail, set, (fail: _ -> Result<_,_>)) |> getValidationData + //[] + //let ``when original get returns None, final get returns null`` () = + // Property.check <| property { + // let! x = GenX.auto - // test <@ d.Set (box p) m |> unbox = set (Some p) m @> - // } + // let get _ = None + // let d = Binding.twoWayOptValidate(get, fail2, (fail: _ -> Result<_,_>)) |> getValidationData + // test <@ isNull (d.Get x) @> + // } - //[] - //let ``when final set receives null, original get receives None`` () = - // Property.check <| property { - // let! m = GenX.auto - // let set (p: string option) (m: int) = p |> Option.map ((+) (string m)) - // let d = Binding.twoWayOptValidate(fail, set, (fail: _ -> Result<_,_>)) |> getValidationData + //[] + //let ``when final set receives a non-null value, original get receives Some`` () = + // Property.check <| property { + // let! m = GenX.auto + // let! p = GenX.auto - // test <@ d.Set null m |> unbox = set None m @> - // } + // let set (p: string option) (m: int) = p |> Option.map ((+) (string m)) + // let d = Binding.twoWayOptValidate(fail, set, (fail: _ -> Result<_,_>)) |> getValidationData + // test <@ d.Set (box p) m |> unbox = set (Some p) m @> + // } - [] - let ``final validate returns value from original validate`` () = - Property.check <| property { - let! x = GenX.auto - let! err = GenX.auto - let validate x = if x < 0 then [] else [ err ] - let d = Binding.twoWayOptValidate((fail: _ -> _ option), fail2, validate) |> getValidationData + //[] + //let ``when final set receives null, original get receives None`` () = + // Property.check <| property { + // let! m = GenX.auto - test <@ d.Validate x |> unbox = validate x @> - } + // let set (p: string option) (m: int) = p |> Option.map ((+) (string m)) + // let d = Binding.twoWayOptValidate(fail, set, (fail: _ -> Result<_,_>)) |> getValidationData + // test <@ d.Set null m |> unbox = set None m @> + // } - module voption_noSetModel_validateVoption = + [] + let ``final validate returns value from original validate`` () = + Property.check + <| property { + let! x = GenX.auto + let! err = GenX.auto + let validate x = if x < 0 then [] else [ err ] - //[] - //let ``when original get returns ValueSome, final get returns the inner value`` () = - // Property.check <| property { - // let! x = GenX.auto + let d = + Binding.twoWayOptValidate ((fail: _ -> _ option), fail2, validate) + |> getValidationData - // let get = string >> ValueSome - // let d = Binding.twoWayOptValidate(get, (fail: _ -> int), (fail: _ -> _ voption)) |> getValidationData + test <@ d.Validate x |> unbox = validate x @> + } - // test <@ d.Get x |> unbox = (get x).Value @> - // } - //[] - //let ``when original get returns ValueNone, final get returns null`` () = - // Property.check <| property { - // let! x = GenX.auto + module voption_noSetModel_validateVoption = - // let get _ = ValueNone - // let d = Binding.twoWayOptValidate(get, (fail: _ -> int), (fail: _ -> _ voption)) |> getValidationData - // test <@ isNull (d.Get x) @> - // } + //[] + //let ``when original get returns ValueSome, final get returns the inner value`` () = + // Property.check <| property { + // let! x = GenX.auto + // let get = string >> ValueSome + // let d = Binding.twoWayOptValidate(get, (fail: _ -> int), (fail: _ -> _ voption)) |> getValidationData - //[] - //let ``when final set receives a non-null value, original get receives ValueSome`` () = - // Property.check <| property { - // let! m = GenX.auto - // let! p = GenX.auto + // test <@ d.Get x |> unbox = (get x).Value @> + // } - // let set (p: string voption) = p |> ValueOption.map (fun x -> x + x) - // let d = Binding.twoWayOptValidate(fail, set, (fail: _ -> _ voption)) |> getValidationData - // test <@ d.Set (box p) m |> unbox = set (ValueSome p) @> - // } + //[] + //let ``when original get returns ValueNone, final get returns null`` () = + // Property.check <| property { + // let! x = GenX.auto + // let get _ = ValueNone + // let d = Binding.twoWayOptValidate(get, (fail: _ -> int), (fail: _ -> _ voption)) |> getValidationData - //[] - //let ``when final set receives null, original get receives ValueNone`` () = - // Property.check <| property { - // let! m = GenX.auto + // test <@ isNull (d.Get x) @> + // } - // let set (p: string voption) = p |> ValueOption.map (fun x -> x + x) - // let d = Binding.twoWayOptValidate(fail, set, (fail: _ -> _ voption)) |> getValidationData - // test <@ d.Set null m |> unbox = set ValueNone @> - // } + //[] + //let ``when final set receives a non-null value, original get receives ValueSome`` () = + // Property.check <| property { + // let! m = GenX.auto + // let! p = GenX.auto + // let set (p: string voption) = p |> ValueOption.map (fun x -> x + x) + // let d = Binding.twoWayOptValidate(fail, set, (fail: _ -> _ voption)) |> getValidationData - [] - let ``final validate returns value from original validate`` () = - Property.check <| property { - let! x = GenX.auto - let! err = GenX.auto + // test <@ d.Set (box p) m |> unbox = set (ValueSome p) @> + // } - let validate x = if x < 0 then [ err ] else [] - let d = Binding.twoWayOptValidate((fail: _ -> _ voption), (fail: _ -> int), validate) |> getValidationData - test <@ d.Validate x |> unbox = validate x @> - } + //[] + //let ``when final set receives null, original get receives ValueNone`` () = + // Property.check <| property { + // let! m = GenX.auto + // let set (p: string voption) = p |> ValueOption.map (fun x -> x + x) + // let d = Binding.twoWayOptValidate(fail, set, (fail: _ -> _ voption)) |> getValidationData + // test <@ d.Set null m |> unbox = set ValueNone @> + // } - module voption_noSetModel_validateOption = + [] + let ``final validate returns value from original validate`` () = + Property.check + <| property { + let! x = GenX.auto + let! err = GenX.auto - //[] - //let ``when original get returns ValueSome, final get returns the inner value`` () = - // Property.check <| property { - // let! x = GenX.auto + let validate x = if x < 0 then [ err ] else [] - // let get = string >> ValueSome - // let d = Binding.twoWayOptValidate(get, (fail: _ -> int), (fail: _ -> _ option)) |> getValidationData + let d = + Binding.twoWayOptValidate ((fail: _ -> _ voption), (fail: _ -> int), validate) + |> getValidationData - // test <@ d.Get x |> unbox = (get x).Value @> - // } + test <@ d.Validate x |> unbox = validate x @> + } - //[] - //let ``when original get returns ValueNone, final get returns null`` () = - // Property.check <| property { - // let! x = GenX.auto - // let get _ = ValueNone - // let d = Binding.twoWayOptValidate(get, (fail: _ -> int), (fail: _ -> _ option)) |> getValidationData + module voption_noSetModel_validateOption = - // test <@ isNull (d.Get x) @> - // } + //[] + //let ``when original get returns ValueSome, final get returns the inner value`` () = + // Property.check <| property { + // let! x = GenX.auto - //[] - //let ``when final set receives a non-null value, original get receives ValueSome`` () = - // Property.check <| property { - // let! m = GenX.auto - // let! p = GenX.auto + // let get = string >> ValueSome + // let d = Binding.twoWayOptValidate(get, (fail: _ -> int), (fail: _ -> _ option)) |> getValidationData - // let set (p: string voption) = p |> ValueOption.map (fun x -> x + x) - // let d = Binding.twoWayOptValidate(fail, set, (fail: _ -> _ option)) |> getValidationData + // test <@ d.Get x |> unbox = (get x).Value @> + // } - // test <@ d.Set (box p) m |> unbox = set (ValueSome p) @> - // } + //[] + //let ``when original get returns ValueNone, final get returns null`` () = + // Property.check <| property { + // let! x = GenX.auto - //[] - //let ``when final set receives null, original get receives ValueNone`` () = - // Property.check <| property { - // let! m = GenX.auto + // let get _ = ValueNone + // let d = Binding.twoWayOptValidate(get, (fail: _ -> int), (fail: _ -> _ option)) |> getValidationData - // let set (p: string voption) = p |> ValueOption.map (fun x -> x + x) - // let d = Binding.twoWayOptValidate(fail, set, (fail: _ -> _ option)) |> getValidationData + // test <@ isNull (d.Get x) @> + // } - // test <@ d.Set null m |> unbox = set ValueNone @> - // } + //[] + //let ``when final set receives a non-null value, original get receives ValueSome`` () = + // Property.check <| property { + // let! m = GenX.auto + // let! p = GenX.auto - [] - let ``final validate returns value from original validate`` () = - Property.check <| property { - let! x = GenX.auto - let! err = GenX.auto + // let set (p: string voption) = p |> ValueOption.map (fun x -> x + x) + // let d = Binding.twoWayOptValidate(fail, set, (fail: _ -> _ option)) |> getValidationData - let validate x = if x < 0 then [ err ] else [] - let d = Binding.twoWayOptValidate((fail: _ -> _ voption), (fail: _ -> int), validate) |> getValidationData + // test <@ d.Set (box p) m |> unbox = set (ValueSome p) @> + // } - test <@ d.Validate x |> unbox = validate x @> - } + //[] + //let ``when final set receives null, original get receives ValueNone`` () = + // Property.check <| property { + // let! m = GenX.auto + // let set (p: string voption) = p |> ValueOption.map (fun x -> x + x) + // let d = Binding.twoWayOptValidate(fail, set, (fail: _ -> _ option)) |> getValidationData - module voption_noSetModel_validateResult = + // test <@ d.Set null m |> unbox = set ValueNone @> + // } - //[] - //let ``when original get returns ValueSome, final get returns the inner value`` () = - // Property.check <| property { - // let! x = GenX.auto + [] + let ``final validate returns value from original validate`` () = + Property.check + <| property { + let! x = GenX.auto + let! err = GenX.auto - // let get = string >> ValueSome - // let d = Binding.twoWayOptValidate(get, (fail: _ -> int), (fail: _ -> Result<_,_>)) |> getValidationData + let validate x = if x < 0 then [ err ] else [] - // test <@ d.Get x |> unbox = (get x).Value @> - // } + let d = + Binding.twoWayOptValidate ((fail: _ -> _ voption), (fail: _ -> int), validate) + |> getValidationData + test <@ d.Validate x |> unbox = validate x @> + } - //[] - //let ``when original get returns ValueNone, final get returns null`` () = - // Property.check <| property { - // let! x = GenX.auto - // let get _ = ValueNone - // let d = Binding.twoWayOptValidate(get, (fail: _ -> int), (fail: _ -> Result<_,_>)) |> getValidationData - // test <@ isNull (d.Get x) @> - // } + module voption_noSetModel_validateResult = - //[] - //let ``when final set receives a non-null value, original get receives ValueSome`` () = - // Property.check <| property { - // let! m = GenX.auto - // let! p = GenX.auto + //[] + //let ``when original get returns ValueSome, final get returns the inner value`` () = + // Property.check <| property { + // let! x = GenX.auto - // let set (p: string voption) = p |> ValueOption.map (fun x -> x + x) - // let d = Binding.twoWayOptValidate(fail, set, (fail: _ -> Result<_,_>)) |> getValidationData + // let get = string >> ValueSome + // let d = Binding.twoWayOptValidate(get, (fail: _ -> int), (fail: _ -> Result<_,_>)) |> getValidationData - // test <@ d.Set (box p) m |> unbox = set (ValueSome p) @> - // } + // test <@ d.Get x |> unbox = (get x).Value @> + // } - //[] - //let ``when final set receives null, original get receives ValueNone`` () = - // Property.check <| property { - // let! m = GenX.auto + //[] + //let ``when original get returns ValueNone, final get returns null`` () = + // Property.check <| property { + // let! x = GenX.auto - // let set (p: string voption) = p |> ValueOption.map (fun x -> x + x) - // let d = Binding.twoWayOptValidate(fail, set, (fail: _ -> Result<_,_>)) |> getValidationData + // let get _ = ValueNone + // let d = Binding.twoWayOptValidate(get, (fail: _ -> int), (fail: _ -> Result<_,_>)) |> getValidationData - // test <@ d.Set null m |> unbox = set ValueNone @> - // } + // test <@ isNull (d.Get x) @> + // } - [] - let ``final validate returns value from original validate`` () = - Property.check <| property { - let! x = GenX.auto - let! err = GenX.auto + //[] + //let ``when final set receives a non-null value, original get receives ValueSome`` () = + // Property.check <| property { + // let! m = GenX.auto + // let! p = GenX.auto - let validate x = if x < 0 then [] else [ err ] - let d = Binding.twoWayOptValidate((fail: _ -> _ voption), (fail: _ -> int), validate) |> getValidationData + // let set (p: string voption) = p |> ValueOption.map (fun x -> x + x) + // let d = Binding.twoWayOptValidate(fail, set, (fail: _ -> Result<_,_>)) |> getValidationData - test <@ d.Validate x |> unbox = validate x @> - } + // test <@ d.Set (box p) m |> unbox = set (ValueSome p) @> + // } + //[] + //let ``when final set receives null, original get receives ValueNone`` () = + // Property.check <| property { + // let! m = GenX.auto - module option_noSetModel_validateVoption = + // let set (p: string voption) = p |> ValueOption.map (fun x -> x + x) + // let d = Binding.twoWayOptValidate(fail, set, (fail: _ -> Result<_,_>)) |> getValidationData + // test <@ d.Set null m |> unbox = set ValueNone @> + // } - //[] - //let ``when original get returns Some, final get returns the inner value`` () = - // Property.check <| property { - // let! x = GenX.auto - // let get = string >> Some - // let d = Binding.twoWayOptValidate(get, (fail: _ -> int), (fail: _ -> _ voption)) |> getValidationData + [] + let ``final validate returns value from original validate`` () = + Property.check + <| property { + let! x = GenX.auto + let! err = GenX.auto - // test <@ d.Get x |> unbox = (get x).Value @> - // } + let validate x = if x < 0 then [] else [ err ] + let d = + Binding.twoWayOptValidate ((fail: _ -> _ voption), (fail: _ -> int), validate) + |> getValidationData - //[] - //let ``when original get returns None, final get returns null`` () = - // Property.check <| property { - // let! x = GenX.auto + test <@ d.Validate x |> unbox = validate x @> + } - // let get _ = None - // let d = Binding.twoWayOptValidate(get, (fail: _ -> int), (fail: _ -> _ voption)) |> getValidationData - // test <@ isNull (d.Get x) @> - // } + module option_noSetModel_validateVoption = - //[] - //let ``when final set receives a non-null value, original get receives Some`` () = - // Property.check <| property { - // let! m = GenX.auto - // let! p = GenX.auto - // let set (p: string option) = p |> Option.map (fun x -> x + x) - // let d = Binding.twoWayOptValidate(fail, set, (fail: _ -> _ voption)) |> getValidationData + //[] + //let ``when original get returns Some, final get returns the inner value`` () = + // Property.check <| property { + // let! x = GenX.auto - // test <@ d.Set (box p) m |> unbox = set (Some p) @> - // } + // let get = string >> Some + // let d = Binding.twoWayOptValidate(get, (fail: _ -> int), (fail: _ -> _ voption)) |> getValidationData + // test <@ d.Get x |> unbox = (get x).Value @> + // } - //[] - //let ``when final set receives null, original get receives None`` () = - // Property.check <| property { - // let! m = GenX.auto - // let set (p: string option) = p |> Option.map (fun x -> x + x) - // let d = Binding.twoWayOptValidate(fail, set, (fail: _ -> _ voption)) |> getValidationData + //[] + //let ``when original get returns None, final get returns null`` () = + // Property.check <| property { + // let! x = GenX.auto - // test <@ d.Set null m |> unbox = set None @> - // } + // let get _ = None + // let d = Binding.twoWayOptValidate(get, (fail: _ -> int), (fail: _ -> _ voption)) |> getValidationData + // test <@ isNull (d.Get x) @> + // } - [] - let ``final validate returns value from original validate`` () = - Property.check <| property { - let! x = GenX.auto - let! err = GenX.auto - let validate x = if x < 0 then [ err ] else [] - let d = Binding.twoWayOptValidate((fail: _ -> _ option), (fail: _ -> int), validate) |> getValidationData + //[] + //let ``when final set receives a non-null value, original get receives Some`` () = + // Property.check <| property { + // let! m = GenX.auto + // let! p = GenX.auto - test <@ d.Validate x |> unbox = validate x @> - } + // let set (p: string option) = p |> Option.map (fun x -> x + x) + // let d = Binding.twoWayOptValidate(fail, set, (fail: _ -> _ voption)) |> getValidationData + // test <@ d.Set (box p) m |> unbox = set (Some p) @> + // } - module option_noSetModel_validateOption = + //[] + //let ``when final set receives null, original get receives None`` () = + // Property.check <| property { + // let! m = GenX.auto + // let set (p: string option) = p |> Option.map (fun x -> x + x) + // let d = Binding.twoWayOptValidate(fail, set, (fail: _ -> _ voption)) |> getValidationData - //[] - //let ``when original get returns Some, final get returns the inner value`` () = - // Property.check <| property { - // let! x = GenX.auto + // test <@ d.Set null m |> unbox = set None @> + // } - // let get = string >> Some - // let d = Binding.twoWayOptValidate(get, (fail: _ -> int), (fail: _ -> _ option)) |> getValidationData - // test <@ d.Get x |> unbox = (get x).Value @> - // } + [] + let ``final validate returns value from original validate`` () = + Property.check + <| property { + let! x = GenX.auto + let! err = GenX.auto + let validate x = if x < 0 then [ err ] else [] - //[] - //let ``when original get returns None, final get returns null`` () = - // Property.check <| property { - // let! x = GenX.auto + let d = + Binding.twoWayOptValidate ((fail: _ -> _ option), (fail: _ -> int), validate) + |> getValidationData - // let get _ = None - // let d = Binding.twoWayOptValidate(get, (fail: _ -> int), (fail: _ -> _ option)) |> getValidationData + test <@ d.Validate x |> unbox = validate x @> + } - // test <@ isNull (d.Get x) @> - // } - //[] - //let ``when final set receives a non-null value, original get receives Some`` () = - // Property.check <| property { - // let! m = GenX.auto - // let! p = GenX.auto + module option_noSetModel_validateOption = - // let set (p: string option) = p |> Option.map (fun x -> x + x) - // let d = Binding.twoWayOptValidate(fail, set, (fail: _ -> _ option)) |> getValidationData - // test <@ d.Set (box p) m |> unbox = set (Some p) @> - // } + //[] + //let ``when original get returns Some, final get returns the inner value`` () = + // Property.check <| property { + // let! x = GenX.auto + // let get = string >> Some + // let d = Binding.twoWayOptValidate(get, (fail: _ -> int), (fail: _ -> _ option)) |> getValidationData - //[] - //let ``when final set receives null, original get receives None`` () = - // Property.check <| property { - // let! m = GenX.auto + // test <@ d.Get x |> unbox = (get x).Value @> + // } - // let set (p: string option) = p |> Option.map (fun x -> x + x) - // let d = Binding.twoWayOptValidate(fail, set, (fail: _ -> _ option)) |> getValidationData - // test <@ d.Set null m |> unbox = set None @> - // } + //[] + //let ``when original get returns None, final get returns null`` () = + // Property.check <| property { + // let! x = GenX.auto + // let get _ = None + // let d = Binding.twoWayOptValidate(get, (fail: _ -> int), (fail: _ -> _ option)) |> getValidationData - [] - let ``final validate returns value from original validate`` () = - Property.check <| property { - let! x = GenX.auto - let! err = GenX.auto + // test <@ isNull (d.Get x) @> + // } - let validate x = if x < 0 then [ err ] else [] - let d = Binding.twoWayOptValidate((fail: _ -> _ option), (fail: _ -> int), validate) |> getValidationData - test <@ d.Validate x |> unbox = validate x @> - } + //[] + //let ``when final set receives a non-null value, original get receives Some`` () = + // Property.check <| property { + // let! m = GenX.auto + // let! p = GenX.auto + // let set (p: string option) = p |> Option.map (fun x -> x + x) + // let d = Binding.twoWayOptValidate(fail, set, (fail: _ -> _ option)) |> getValidationData + // test <@ d.Set (box p) m |> unbox = set (Some p) @> + // } - module option_noSetModel_validateResult = + //[] + //let ``when final set receives null, original get receives None`` () = + // Property.check <| property { + // let! m = GenX.auto - //[] - //let ``when original get returns Some, final get returns the inner value`` () = - // Property.check <| property { - // let! x = GenX.auto + // let set (p: string option) = p |> Option.map (fun x -> x + x) + // let d = Binding.twoWayOptValidate(fail, set, (fail: _ -> _ option)) |> getValidationData - // let get = string >> Some - // let d = Binding.twoWayOptValidate(get, (fail: _ -> int), (fail: _ -> Result<_,_>)) |> getValidationData + // test <@ d.Set null m |> unbox = set None @> + // } - // test <@ d.Get x |> unbox = (get x).Value @> - // } + [] + let ``final validate returns value from original validate`` () = + Property.check + <| property { + let! x = GenX.auto + let! err = GenX.auto - //[] - //let ``when original get returns None, final get returns null`` () = - // Property.check <| property { - // let! x = GenX.auto + let validate x = if x < 0 then [ err ] else [] - // let get _ = None - // let d = Binding.twoWayOptValidate(get, (fail: _ -> int), (fail: _ -> Result<_,_>)) |> getValidationData + let d = + Binding.twoWayOptValidate ((fail: _ -> _ option), (fail: _ -> int), validate) + |> getValidationData - // test <@ isNull (d.Get x) @> - // } + test <@ d.Validate x |> unbox = validate x @> + } - //[] - //let ``when final set receives a non-null value, original get receives Some`` () = - // Property.check <| property { - // let! m = GenX.auto - // let! p = GenX.auto - // let set (p: string option) = p |> Option.map (fun x -> x + x) - // let d = Binding.twoWayOptValidate(fail, set, (fail: _ -> Result<_,_>)) |> getValidationData + module option_noSetModel_validateResult = - // test <@ d.Set (box p) m |> unbox = set (Some p) @> - // } + //[] + //let ``when original get returns Some, final get returns the inner value`` () = + // Property.check <| property { + // let! x = GenX.auto - //[] - //let ``when final set receives null, original get receives None`` () = - // Property.check <| property { - // let! m = GenX.auto + // let get = string >> Some + // let d = Binding.twoWayOptValidate(get, (fail: _ -> int), (fail: _ -> Result<_,_>)) |> getValidationData - // let set (p: string option) = p |> Option.map (fun x -> x + x) - // let d = Binding.twoWayOptValidate(fail, set, (fail: _ -> Result<_,_>)) |> getValidationData + // test <@ d.Get x |> unbox = (get x).Value @> + // } - // test <@ d.Set null m |> unbox = set None @> - // } + //[] + //let ``when original get returns None, final get returns null`` () = + // Property.check <| property { + // let! x = GenX.auto + + // let get _ = None + // let d = Binding.twoWayOptValidate(get, (fail: _ -> int), (fail: _ -> Result<_,_>)) |> getValidationData + + // test <@ isNull (d.Get x) @> + // } + + + //[] + //let ``when final set receives a non-null value, original get receives Some`` () = + // Property.check <| property { + // let! m = GenX.auto + // let! p = GenX.auto + + // let set (p: string option) = p |> Option.map (fun x -> x + x) + // let d = Binding.twoWayOptValidate(fail, set, (fail: _ -> Result<_,_>)) |> getValidationData + + // test <@ d.Set (box p) m |> unbox = set (Some p) @> + // } - [] - let ``final validate returns value from original validate`` () = - Property.check <| property { - let! x = GenX.auto - let! err = GenX.auto - let validate x = if x < 0 then [] else [ err ] - let d = Binding.twoWayOptValidate((fail: _ -> _ option), (fail: _ -> int), validate) |> getValidationData + //[] + //let ``when final set receives null, original get receives None`` () = + // Property.check <| property { + // let! m = GenX.auto - test <@ d.Validate x |> unbox = validate x @> - } + // let set (p: string option) = p |> Option.map (fun x -> x + x) + // let d = Binding.twoWayOptValidate(fail, set, (fail: _ -> Result<_,_>)) |> getValidationData + + // test <@ d.Set null m |> unbox = set None @> + // } + + + [] + let ``final validate returns value from original validate`` () = + Property.check + <| property { + let! x = GenX.auto + let! err = GenX.auto + + let validate x = if x < 0 then [] else [ err ] + + let d = + Binding.twoWayOptValidate ((fail: _ -> _ option), (fail: _ -> int), validate) + |> getValidationData + + test <@ d.Validate x |> unbox = validate x @> + } module cmd = - module model = + module model = - [] - let ``sets the correct binding name`` () = - Property.check <| property { - let! bindingName = GenX.auto - let binding = bindingName |> Binding.cmd(fail) - test <@ binding.Name = bindingName @> - } + [] + let ``sets the correct binding name`` () = + Property.check + <| property { + let! bindingName = GenX.auto + let binding = bindingName |> Binding.cmd (fail) + test <@ binding.Name = bindingName @> + } - module noModel = + module noModel = - [] - let ``sets the correct binding name`` () = - Property.check <| property { - let! bindingName = GenX.auto - let binding = bindingName |> Binding.cmd(obj()) - test <@ binding.Name = bindingName @> - } + [] + let ``sets the correct binding name`` () = + Property.check + <| property { + let! bindingName = GenX.auto + let binding = bindingName |> Binding.cmd (obj ()) + test <@ binding.Name = bindingName @> + } module cmdIf = - module explicitCanExec_model = + module explicitCanExec_model = - [] - let ``sets the correct binding name`` () = - Property.check <| property { - let! bindingName = GenX.auto - let binding = bindingName |> Binding.cmdIf(fail, fail, id) - test <@ binding.Name = bindingName @> - } + [] + let ``sets the correct binding name`` () = + Property.check + <| property { + let! bindingName = GenX.auto + let binding = bindingName |> Binding.cmdIf (fail, fail, id) + test <@ binding.Name = bindingName @> + } - module explicitCanExec_noModel = + module explicitCanExec_noModel = - [] - let ``sets the correct binding name`` () = - Property.check <| property { - let! bindingName = GenX.auto - let binding = bindingName |> Binding.cmdIf(obj(), fail) - test <@ binding.Name = bindingName @> - } + [] + let ``sets the correct binding name`` () = + Property.check + <| property { + let! bindingName = GenX.auto + let binding = bindingName |> Binding.cmdIf (obj (), fail) + test <@ binding.Name = bindingName @> + } - module voption = + module voption = - [] - let ``sets the correct binding name`` () = - Property.check <| property { - let! bindingName = GenX.auto - let binding = bindingName |> Binding.cmdIf(fail: _ -> _ voption) - test <@ binding.Name = bindingName @> - } + [] + let ``sets the correct binding name`` () = + Property.check + <| property { + let! bindingName = GenX.auto + let binding = bindingName |> Binding.cmdIf (fail: _ -> _ voption) + test <@ binding.Name = bindingName @> + } - module option = + module option = - [] - let ``sets the correct binding name`` () = - Property.check <| property { - let! bindingName = GenX.auto - let binding = bindingName |> Binding.cmdIf(fail: _ -> _ option) - test <@ binding.Name = bindingName @> - } + [] + let ``sets the correct binding name`` () = + Property.check + <| property { + let! bindingName = GenX.auto + let binding = bindingName |> Binding.cmdIf (fail: _ -> _ option) + test <@ binding.Name = bindingName @> + } - module result = + module result = - [] - let ``sets the correct binding name`` () = - Property.check <| property { - let! bindingName = GenX.auto - let binding = bindingName |> Binding.cmdIf(fail: _ -> Result<_,_>) - test <@ binding.Name = bindingName @> - } + [] + let ``sets the correct binding name`` () = + Property.check + <| property { + let! bindingName = GenX.auto + let binding = bindingName |> Binding.cmdIf (fail: _ -> Result<_, _>) + test <@ binding.Name = bindingName @> + } module cmdParam = - module model = + module model = - [] - let ``sets the correct binding name`` () = - Property.check <| property { - let! bindingName = GenX.auto - let binding = bindingName |> Binding.cmdParam(fail2) - test <@ binding.Name = bindingName @> - } + [] + let ``sets the correct binding name`` () = + Property.check + <| property { + let! bindingName = GenX.auto + let binding = bindingName |> Binding.cmdParam (fail2) + test <@ binding.Name = bindingName @> + } - [] - let ``final exec returns value from original exec wrapped in ValueSome`` () = - Property.check <| property { - let! m = GenX.auto - let! p = GenX.auto + [] + let ``final exec returns value from original exec wrapped in ValueSome`` () = + Property.check + <| property { + let! m = GenX.auto + let! p = GenX.auto - let exec (p: obj) (m: int) = unbox p + string m - let d = Binding.cmdParam(exec) |> getCmdData + let exec (p: obj) (m: int) = unbox p + string m + let d = Binding.cmdParam (exec) |> getCmdData - test <@ d.Exec (box p) m = (exec p m |> ValueSome) @> - } + test <@ d.Exec (box p) m = (exec p m |> ValueSome) @> + } - [] - let ``canExec always returns true`` () = - Property.check <| property { - let! m = GenX.auto - let! p = GenX.auto - let d = Binding.cmdParam(fail2) |> getCmdData - test <@ d.CanExec (box p) m = true @> - } + [] + let ``canExec always returns true`` () = + Property.check + <| property { + let! m = GenX.auto + let! p = GenX.auto + let d = Binding.cmdParam (fail2) |> getCmdData + test <@ d.CanExec (box p) m = true @> + } - [] - let ``autoRequery is false`` () = - Property.check <| property { - let d = Binding.cmdParam(fail2) |> getCmdData - test <@ d.AutoRequery = false @> - } + [] + let ``autoRequery is false`` () = + Property.check + <| property { + let d = Binding.cmdParam (fail2) |> getCmdData + test <@ d.AutoRequery = false @> + } - module noModel = + module noModel = - [] - let ``sets the correct binding name`` () = - Property.check <| property { - let! bindingName = GenX.auto - let binding = bindingName |> Binding.cmdParam(fail: obj -> obj) - test <@ binding.Name = bindingName @> - } + [] + let ``sets the correct binding name`` () = + Property.check + <| property { + let! bindingName = GenX.auto + let binding = bindingName |> Binding.cmdParam (fail: obj -> obj) + test <@ binding.Name = bindingName @> + } - [] - let ``final exec returns original value wrapped in ValueSome`` () = - Property.check <| property { - let! m = GenX.auto - let! p = GenX.auto + [] + let ``final exec returns original value wrapped in ValueSome`` () = + Property.check + <| property { + let! m = GenX.auto + let! p = GenX.auto - let exec (p: obj) = string p - let d = Binding.cmdParam(exec) |> getCmdData + let exec (p: obj) = string p + let d = Binding.cmdParam (exec) |> getCmdData - test <@ d.Exec (box p) m = (exec p |> ValueSome) @> - } + test <@ d.Exec (box p) m = (exec p |> ValueSome) @> + } - [] - let ``canExec always returns true`` () = - Property.check <| property { - let! m = GenX.auto - let! p = GenX.auto - let d = Binding.cmdParam(fail: obj -> obj) |> getCmdData - test <@ d.CanExec (box p) m = true @> - } + [] + let ``canExec always returns true`` () = + Property.check + <| property { + let! m = GenX.auto + let! p = GenX.auto + let d = Binding.cmdParam (fail: obj -> obj) |> getCmdData + test <@ d.CanExec (box p) m = true @> + } - [] - let ``autoRequery is false`` () = - Property.check <| property { - let d = Binding.cmdParam(fail: obj -> obj) |> getCmdData - test <@ d.AutoRequery = false @> - } + [] + let ``autoRequery is false`` () = + Property.check + <| property { + let d = Binding.cmdParam (fail: obj -> obj) |> getCmdData + test <@ d.AutoRequery = false @> + } module cmdParamIf = - module explicitCanExec_model = + module explicitCanExec_model = - [] - let ``sets the correct binding name`` () = - Property.check <| property { - let! bindingName = GenX.auto - let binding = bindingName |> Binding.cmdParamIf(fail, fail, id) - test <@ binding.Name = bindingName @> - } + [] + let ``sets the correct binding name`` () = + Property.check + <| property { + let! bindingName = GenX.auto + let binding = bindingName |> Binding.cmdParamIf (fail, fail, id) + test <@ binding.Name = bindingName @> + } - [] - let ``final exec returns value from original exec wrapped in ValueSome`` () = - Property.check <| property { - let! m = GenX.auto - let! p = GenX.auto + [] + let ``final exec returns value from original exec wrapped in ValueSome`` () = + Property.check + <| property { + let! m = GenX.auto + let! p = GenX.auto - let exec (p: obj) (m: int) = unbox p + string m - let d = Binding.cmdParamIf(exec, fail) |> getCmdData + let exec (p: obj) (m: int) = unbox p + string m + let d = Binding.cmdParamIf (exec, fail) |> getCmdData - test <@ d.Exec (box p) m = (exec p m |> ValueSome) @> - } + test <@ d.Exec (box p) m = (exec p m |> ValueSome) @> + } - [] - let ``final canExec returns value from original canExec`` () = - Property.check <| property { - let! m = GenX.auto - let! p = GenX.auto + [] + let ``final canExec returns value from original canExec`` () = + Property.check + <| property { + let! m = GenX.auto + let! p = GenX.auto - let canExec (p: obj) m = (unbox p).Length + m > 0 - let d = Binding.cmdParamIf(fail, canExec) |> getCmdData + let canExec (p: obj) m = (unbox p).Length + m > 0 + let d = Binding.cmdParamIf (fail, canExec) |> getCmdData - test <@ d.CanExec (box p) m = canExec p m @> - } + test <@ d.CanExec (box p) m = canExec p m @> + } - [] - let ``final autoRequery defaults to false`` () = - Property.check <| property { - let d = Binding.cmdParamIf(fail, fail, false) |> getCmdData - test <@ d.AutoRequery = false @> - } + [] + let ``final autoRequery defaults to false`` () = + Property.check + <| property { + let d = Binding.cmdParamIf (fail, fail, false) |> getCmdData + test <@ d.AutoRequery = false @> + } - [] - let ``final autoRequery equals original uiBoundCmdParam`` () = - Property.check <| property { - let! uiBoundCmdParam = GenX.auto - let d = Binding.cmdParamIf(fail, fail, uiBoundCmdParam) |> getCmdData - test <@ d.AutoRequery = uiBoundCmdParam @> - } + [] + let ``final autoRequery equals original uiBoundCmdParam`` () = + Property.check + <| property { + let! uiBoundCmdParam = GenX.auto + let d = Binding.cmdParamIf (fail, fail, uiBoundCmdParam) |> getCmdData + test <@ d.AutoRequery = uiBoundCmdParam @> + } - module voption_model = + module voption_model = - [] - let ``sets the correct binding name`` () = - Property.check <| property { - let! bindingName = GenX.auto - let binding = bindingName |> Binding.cmdParamIf(fail2: _ -> _ -> _ voption) - test <@ binding.Name = bindingName @> - } + [] + let ``sets the correct binding name`` () = + Property.check + <| property { + let! bindingName = GenX.auto + let binding = bindingName |> Binding.cmdParamIf (fail2: _ -> _ -> _ voption) + test <@ binding.Name = bindingName @> + } - [] - let ``final exec returns value from original exec`` () = - Property.check <| property { - let! m = GenX.auto - let! p = GenX.auto + [] + let ``final exec returns value from original exec`` () = + Property.check + <| property { + let! m = GenX.auto + let! p = GenX.auto - let exec (p: obj) m = (p :?> string).Length + m |> ValueSome |> ValueOption.filter (fun x -> x > 0) - let d = Binding.cmdParamIf(exec) |> getCmdData + let exec (p: obj) m = + (p :?> string).Length + m |> ValueSome |> ValueOption.filter (fun x -> x > 0) - test <@ d.Exec (box p) m = exec p m @> - } + let d = Binding.cmdParamIf (exec) |> getCmdData + test <@ d.Exec (box p) m = exec p m @> + } - [] - let ``final canExec returns true if original exec returns ValueSome`` () = - Property.check <| property { - let! m = GenX.auto - let! p = GenX.auto - let exec (p: obj) m = (p :?> string).Length + m |> ValueSome - let d = Binding.cmdParamIf(exec) |> getCmdData + [] + let ``final canExec returns true if original exec returns ValueSome`` () = + Property.check + <| property { + let! m = GenX.auto + let! p = GenX.auto - test <@ d.CanExec (box p) m = true @> - } + let exec (p: obj) m = (p :?> string).Length + m |> ValueSome + let d = Binding.cmdParamIf (exec) |> getCmdData + test <@ d.CanExec (box p) m = true @> + } - [] - let ``final canExec returns false if original exec returns ValueNone`` () = - Property.check <| property { - let! m = GenX.auto - let! p = GenX.auto - let exec (_: obj) _ = ValueNone - let d = Binding.cmdParamIf(exec) |> getCmdData + [] + let ``final canExec returns false if original exec returns ValueNone`` () = + Property.check + <| property { + let! m = GenX.auto + let! p = GenX.auto - test <@ d.CanExec (box p) m = false @> - } + let exec (_: obj) _ = ValueNone + let d = Binding.cmdParamIf (exec) |> getCmdData + test <@ d.CanExec (box p) m = false @> + } - [] - let ``final autoRequery defaults to false`` () = - Property.check <| property { - let d = Binding.cmdParamIf((fail2: _ -> _ -> _ voption)) |> getCmdData - test <@ d.AutoRequery = false @> - } + [] + let ``final autoRequery defaults to false`` () = + Property.check + <| property { + let d = Binding.cmdParamIf ((fail2: _ -> _ -> _ voption)) |> getCmdData + test <@ d.AutoRequery = false @> + } - [] - let ``final autoRequery equals original uiBoundCmdParam`` () = - Property.check <| property { - let! uiBoundCmdParam = GenX.auto - let d = Binding.cmdParamIf((fail2: _ -> _ -> _ voption), uiBoundCmdParam = uiBoundCmdParam) |> getCmdData - test <@ d.AutoRequery = uiBoundCmdParam @> - } + [] + let ``final autoRequery equals original uiBoundCmdParam`` () = + Property.check + <| property { + let! uiBoundCmdParam = GenX.auto + let d = + Binding.cmdParamIf ((fail2: _ -> _ -> _ voption), uiBoundCmdParam = uiBoundCmdParam) + |> getCmdData - module option_model = + test <@ d.AutoRequery = uiBoundCmdParam @> + } - [] - let ``sets the correct binding name`` () = - Property.check <| property { - let! bindingName = GenX.auto - let binding = bindingName |> Binding.cmdParamIf(fail2: _ -> _ -> _ option) - test <@ binding.Name = bindingName @> - } + module option_model = - [] - let ``final exec returns value from original exec converted to ValueOption`` () = - Property.check <| property { - let! m = GenX.auto - let! p = GenX.auto - let exec (p: obj) m = (p :?> string).Length + m |> Some |> Option.filter (fun x -> x > 0) - let d = Binding.cmdParamIf(exec) |> getCmdData + [] + let ``sets the correct binding name`` () = + Property.check + <| property { + let! bindingName = GenX.auto + let binding = bindingName |> Binding.cmdParamIf (fail2: _ -> _ -> _ option) + test <@ binding.Name = bindingName @> + } - test <@ d.Exec (box p) m = (exec p m |> ValueOption.ofOption) @> - } + [] + let ``final exec returns value from original exec converted to ValueOption`` () = + Property.check + <| property { + let! m = GenX.auto + let! p = GenX.auto - [] - let ``final canExec returns true if original exec returns Some`` () = - Property.check <| property { - let! m = GenX.auto - let! p = GenX.auto + let exec (p: obj) m = + (p :?> string).Length + m |> Some |> Option.filter (fun x -> x > 0) - let exec (p: obj) m = (p :?> string).Length + m |> Some - let d = Binding.cmdParamIf(exec) |> getCmdData + let d = Binding.cmdParamIf (exec) |> getCmdData - test <@ d.CanExec (box p) m = true @> - } + test <@ d.Exec (box p) m = (exec p m |> ValueOption.ofOption) @> + } - [] - let ``final canExec returns false if original exec returns None`` () = - Property.check <| property { - let! m = GenX.auto - let! p = GenX.auto + [] + let ``final canExec returns true if original exec returns Some`` () = + Property.check + <| property { + let! m = GenX.auto + let! p = GenX.auto - let exec (_: obj) _ = None - let d = Binding.cmdParamIf(exec) |> getCmdData + let exec (p: obj) m = (p :?> string).Length + m |> Some + let d = Binding.cmdParamIf (exec) |> getCmdData - test <@ d.CanExec (box p) m = false @> - } + test <@ d.CanExec (box p) m = true @> + } - [] - let ``final autoRequery defaults to false`` () = - Property.check <| property { - let d = Binding.cmdParamIf((fail2: _ -> _ -> _ option)) |> getCmdData - test <@ d.AutoRequery = false @> - } + [] + let ``final canExec returns false if original exec returns None`` () = + Property.check + <| property { + let! m = GenX.auto + let! p = GenX.auto + let exec (_: obj) _ = None + let d = Binding.cmdParamIf (exec) |> getCmdData - [] - let ``final autoRequery equals original uiBoundCmdParam`` () = - Property.check <| property { - let! uiBoundCmdParam = GenX.auto - let d = Binding.cmdParamIf((fail2: _ -> _ -> _ option), uiBoundCmdParam = uiBoundCmdParam) |> getCmdData - test <@ d.AutoRequery = uiBoundCmdParam @> - } + test <@ d.CanExec (box p) m = false @> + } + [] + let ``final autoRequery defaults to false`` () = + Property.check + <| property { + let d = Binding.cmdParamIf ((fail2: _ -> _ -> _ option)) |> getCmdData + test <@ d.AutoRequery = false @> + } - module result_model = + [] + let ``final autoRequery equals original uiBoundCmdParam`` () = + Property.check + <| property { + let! uiBoundCmdParam = GenX.auto - [] - let ``sets the correct binding name`` () = - Property.check <| property { - let! bindingName = GenX.auto - let binding = bindingName |> Binding.cmdParamIf(fail2: _ -> _ -> Result<_,_>) - test <@ binding.Name = bindingName @> - } + let d = + Binding.cmdParamIf ((fail2: _ -> _ -> _ option), uiBoundCmdParam = uiBoundCmdParam) + |> getCmdData + test <@ d.AutoRequery = uiBoundCmdParam @> + } - [] - let ``final exec returns Ok value from original exec converted to ValueOption`` () = - Property.check <| property { - let! m = GenX.auto - let! p = GenX.auto - let exec (p: obj) m = - let x = (p :?> string).Length + m - if x > 0 then Ok x else Error (string x) - let d = Binding.cmdParamIf(exec) |> getCmdData - test <@ d.Exec (box p) m = (exec p m |> ValueOption.ofOk) @> - } + module result_model = - [] - let ``final canExec returns true if original exec returns Ok`` () = - Property.check <| property { - let! m = GenX.auto - let! p = GenX.auto + [] + let ``sets the correct binding name`` () = + Property.check + <| property { + let! bindingName = GenX.auto + let binding = bindingName |> Binding.cmdParamIf (fail2: _ -> _ -> Result<_, _>) + test <@ binding.Name = bindingName @> + } - let exec (p: obj) m = (p :?> string).Length + m |> Ok - let d = Binding.cmdParamIf(exec) |> getCmdData - test <@ d.CanExec (box p) m = true @> - } + [] + let ``final exec returns Ok value from original exec converted to ValueOption`` () = + Property.check + <| property { + let! m = GenX.auto + let! p = GenX.auto + let exec (p: obj) m = + let x = (p :?> string).Length + m + if x > 0 then Ok x else Error(string x) - [] - let ``final canExec returns false if original exec returns Error`` () = - Property.check <| property { - let! m = GenX.auto - let! p = GenX.auto - let! err = GenX.auto + let d = Binding.cmdParamIf (exec) |> getCmdData - let exec (_: obj) _ = Error err - let d = Binding.cmdParamIf(exec) |> getCmdData + test <@ d.Exec (box p) m = (exec p m |> ValueOption.ofOk) @> + } - test <@ d.CanExec (box p) m = false @> - } + [] + let ``final canExec returns true if original exec returns Ok`` () = + Property.check + <| property { + let! m = GenX.auto + let! p = GenX.auto - [] - let ``final autoRequery defaults to false`` () = - Property.check <| property { - let d = Binding.cmdParamIf((fail2: _ -> _ -> Result<_,_>)) |> getCmdData - test <@ d.AutoRequery = false @> - } + let exec (p: obj) m = (p :?> string).Length + m |> Ok + let d = Binding.cmdParamIf (exec) |> getCmdData + test <@ d.CanExec (box p) m = true @> + } - [] - let ``final autoRequery equals original uiBoundCmdParam`` () = - Property.check <| property { - let! uiBoundCmdParam = GenX.auto - let d = Binding.cmdParamIf((fail2: _ -> _ -> Result<_,_>), uiBoundCmdParam = uiBoundCmdParam) |> getCmdData - test <@ d.AutoRequery = uiBoundCmdParam @> - } + [] + let ``final canExec returns false if original exec returns Error`` () = + Property.check + <| property { + let! m = GenX.auto + let! p = GenX.auto + let! err = GenX.auto + let exec (_: obj) _ = Error err + let d = Binding.cmdParamIf (exec) |> getCmdData - module explicitCanExec_noModel = + test <@ d.CanExec (box p) m = false @> + } - [] - let ``sets the correct binding name`` () = - Property.check <| property { - let! bindingName = GenX.auto - let binding = bindingName |> Binding.cmdParamIf((fail: obj -> obj), fail) - test <@ binding.Name = bindingName @> - } + [] + let ``final autoRequery defaults to false`` () = + Property.check + <| property { + let d = Binding.cmdParamIf ((fail2: _ -> _ -> Result<_, _>)) |> getCmdData + test <@ d.AutoRequery = false @> + } - [] - let ``final exec returns value from original exec wrapped in ValueSome`` () = - Property.check <| property { - let! m = GenX.auto - let! p = GenX.auto + [] + let ``final autoRequery equals original uiBoundCmdParam`` () = + Property.check + <| property { + let! uiBoundCmdParam = GenX.auto - let exec (p: obj) = (unbox p).Length - let d = Binding.cmdParamIf(exec, fail) |> getCmdData + let d = + Binding.cmdParamIf ((fail2: _ -> _ -> Result<_, _>), uiBoundCmdParam = uiBoundCmdParam) + |> getCmdData - test <@ d.Exec (box p) m = (exec p |> ValueSome) @> - } + test <@ d.AutoRequery = uiBoundCmdParam @> + } - [] - let ``final canExec returns value from original canExec`` () = - Property.check <| property { - let! m = GenX.auto - let! p = GenX.auto - let canExec (p: obj) = (unbox p).Length + m > 0 - let d = Binding.cmdParamIf(fail, canExec) |> getCmdData + module explicitCanExec_noModel = - test <@ d.CanExec (box p) m = canExec p @> - } + [] + let ``sets the correct binding name`` () = + Property.check + <| property { + let! bindingName = GenX.auto + let binding = bindingName |> Binding.cmdParamIf ((fail: obj -> obj), fail) + test <@ binding.Name = bindingName @> + } - [] - let ``final autoRequery defaults to false`` () = - Property.check <| property { - let d = Binding.cmdParamIf((fail: obj -> obj), fail) |> getCmdData - test <@ d.AutoRequery = false @> - } + [] + let ``final exec returns value from original exec wrapped in ValueSome`` () = + Property.check + <| property { + let! m = GenX.auto + let! p = GenX.auto - [] - let ``final autoRequery equals original uiBoundCmdParam`` () = - Property.check <| property { - let! uiBoundCmdParam = GenX.auto - let d = Binding.cmdParamIf((fail: obj -> obj), fail, uiBoundCmdParam = uiBoundCmdParam) |> getCmdData - test <@ d.AutoRequery = uiBoundCmdParam @> - } + let exec (p: obj) = (unbox p).Length + let d = Binding.cmdParamIf (exec, fail) |> getCmdData + test <@ d.Exec (box p) m = (exec p |> ValueSome) @> + } - module voption_noModel = + [] + let ``final canExec returns value from original canExec`` () = + Property.check + <| property { + let! m = GenX.auto + let! p = GenX.auto - [] - let ``sets the correct binding name`` () = - Property.check <| property { - let! bindingName = GenX.auto - let binding = bindingName |> Binding.cmdParamIf(fail: _ -> _ voption) - test <@ binding.Name = bindingName @> - } + let canExec (p: obj) = (unbox p).Length + m > 0 + let d = Binding.cmdParamIf (fail, canExec) |> getCmdData + test <@ d.CanExec (box p) m = canExec p @> + } - [] - let ``final exec returns value from original exec`` () = - Property.check <| property { - let! m = GenX.auto - let! p = GenX.auto - let exec (p: obj) = (p :?> string).Length |> ValueSome |> ValueOption.filter (fun x -> x > 0) - let d = Binding.cmdParamIf(exec) |> getCmdData + [] + let ``final autoRequery defaults to false`` () = + Property.check + <| property { + let d = Binding.cmdParamIf ((fail: obj -> obj), fail) |> getCmdData + test <@ d.AutoRequery = false @> + } - test <@ d.Exec (box p) m = exec p @> - } + [] + let ``final autoRequery equals original uiBoundCmdParam`` () = + Property.check + <| property { + let! uiBoundCmdParam = GenX.auto - [] - let ``final canExec returns true if original exec returns ValueSome`` () = - Property.check <| property { - let! m = GenX.auto - let! p = GenX.auto + let d = + Binding.cmdParamIf ((fail: obj -> obj), fail, uiBoundCmdParam = uiBoundCmdParam) + |> getCmdData - let exec (p: obj) = (p :?> string).Length |> ValueSome - let d = Binding.cmdParamIf(exec) |> getCmdData + test <@ d.AutoRequery = uiBoundCmdParam @> + } - test <@ d.CanExec (box p) m = true @> - } + module voption_noModel = - [] - let ``final canExec returns false if original exec returns ValueNone`` () = - Property.check <| property { - let! m = GenX.auto - let! p = GenX.auto - let exec (_: obj) = ValueNone - let d = Binding.cmdParamIf(exec) |> getCmdData + [] + let ``sets the correct binding name`` () = + Property.check + <| property { + let! bindingName = GenX.auto + let binding = bindingName |> Binding.cmdParamIf (fail: _ -> _ voption) + test <@ binding.Name = bindingName @> + } - test <@ d.CanExec (box p) m = false @> - } + [] + let ``final exec returns value from original exec`` () = + Property.check + <| property { + let! m = GenX.auto + let! p = GenX.auto - [] - let ``final autoRequery defaults to false`` () = - Property.check <| property { - let d = Binding.cmdParamIf((fail: _ -> _ voption)) |> getCmdData - test <@ d.AutoRequery = false @> - } + let exec (p: obj) = + (p :?> string).Length |> ValueSome |> ValueOption.filter (fun x -> x > 0) + let d = Binding.cmdParamIf (exec) |> getCmdData - [] - let ``final autoRequery equals original uiBoundCmdParam`` () = - Property.check <| property { - let! uiBoundCmdParam = GenX.auto - let d = Binding.cmdParamIf((fail: _ -> _ voption), uiBoundCmdParam = uiBoundCmdParam) |> getCmdData - test <@ d.AutoRequery = uiBoundCmdParam @> - } + test <@ d.Exec (box p) m = exec p @> + } + [] + let ``final canExec returns true if original exec returns ValueSome`` () = + Property.check + <| property { + let! m = GenX.auto + let! p = GenX.auto - module option_noModel = + let exec (p: obj) = (p :?> string).Length |> ValueSome + let d = Binding.cmdParamIf (exec) |> getCmdData + test <@ d.CanExec (box p) m = true @> + } - [] - let ``sets the correct binding name`` () = - Property.check <| property { - let! bindingName = GenX.auto - let binding = bindingName |> Binding.cmdParamIf(fail: _ -> _ option) - test <@ binding.Name = bindingName @> - } + [] + let ``final canExec returns false if original exec returns ValueNone`` () = + Property.check + <| property { + let! m = GenX.auto + let! p = GenX.auto - [] - let ``final exec returns value from original exec converted to ValueOption`` () = - Property.check <| property { - let! m = GenX.auto - let! p = GenX.auto + let exec (_: obj) = ValueNone + let d = Binding.cmdParamIf (exec) |> getCmdData - let exec (p: obj) = (p :?> string).Length |> Some |> Option.filter (fun x -> x > 0) - let d = Binding.cmdParamIf(exec) |> getCmdData + test <@ d.CanExec (box p) m = false @> + } - test <@ d.Exec (box p) m = (exec p |> ValueOption.ofOption) @> - } + [] + let ``final autoRequery defaults to false`` () = + Property.check + <| property { + let d = Binding.cmdParamIf ((fail: _ -> _ voption)) |> getCmdData + test <@ d.AutoRequery = false @> + } - [] - let ``final canExec returns true if original exec returns Some`` () = - Property.check <| property { - let! m = GenX.auto - let! p = GenX.auto - let exec (p: obj) = (p :?> string).Length |> Some - let d = Binding.cmdParamIf(exec) |> getCmdData + [] + let ``final autoRequery equals original uiBoundCmdParam`` () = + Property.check + <| property { + let! uiBoundCmdParam = GenX.auto - test <@ d.CanExec (box p) m = true @> - } + let d = + Binding.cmdParamIf ((fail: _ -> _ voption), uiBoundCmdParam = uiBoundCmdParam) + |> getCmdData + test <@ d.AutoRequery = uiBoundCmdParam @> + } - [] - let ``final canExec returns false if original exec returns None`` () = - Property.check <| property { - let! m = GenX.auto - let! p = GenX.auto - let exec (_: obj) = None - let d = Binding.cmdParamIf(exec) |> getCmdData - test <@ d.CanExec (box p) m = false @> - } + module option_noModel = - [] - let ``final autoRequery defaults to false`` () = - Property.check <| property { - let d = Binding.cmdParamIf((fail: _ -> _ option)) |> getCmdData - test <@ d.AutoRequery = false @> - } + [] + let ``sets the correct binding name`` () = + Property.check + <| property { + let! bindingName = GenX.auto + let binding = bindingName |> Binding.cmdParamIf (fail: _ -> _ option) + test <@ binding.Name = bindingName @> + } - [] - let ``final autoRequery equals original uiBoundCmdParam`` () = - Property.check <| property { - let! uiBoundCmdParam = GenX.auto - let d = Binding.cmdParamIf((fail: _ -> _ option), uiBoundCmdParam = uiBoundCmdParam) |> getCmdData - test <@ d.AutoRequery = uiBoundCmdParam @> - } + [] + let ``final exec returns value from original exec converted to ValueOption`` () = + Property.check + <| property { + let! m = GenX.auto + let! p = GenX.auto + let exec (p: obj) = + (p :?> string).Length |> Some |> Option.filter (fun x -> x > 0) + let d = Binding.cmdParamIf (exec) |> getCmdData - module result_noModel = + test <@ d.Exec (box p) m = (exec p |> ValueOption.ofOption) @> + } - [] - let ``sets the correct binding name`` () = - Property.check <| property { - let! bindingName = GenX.auto - let binding = bindingName |> Binding.cmdParamIf(fail: _ -> Result<_,_>) - test <@ binding.Name = bindingName @> - } + [] + let ``final canExec returns true if original exec returns Some`` () = + Property.check + <| property { + let! m = GenX.auto + let! p = GenX.auto + let exec (p: obj) = (p :?> string).Length |> Some + let d = Binding.cmdParamIf (exec) |> getCmdData - [] - let ``final exec returns Ok value from original exec converted to ValueOption`` () = - Property.check <| property { - let! m = GenX.auto - let! p = GenX.auto + test <@ d.CanExec (box p) m = true @> + } - let exec (p: obj) = - let x = (p :?> string).Length - if x > 0 then Ok x else Error (string x) - let d = Binding.cmdParamIf(exec) |> getCmdData - test <@ d.Exec (box p) m = (exec p |> ValueOption.ofOk) @> - } + [] + let ``final canExec returns false if original exec returns None`` () = + Property.check + <| property { + let! m = GenX.auto + let! p = GenX.auto + let exec (_: obj) = None + let d = Binding.cmdParamIf (exec) |> getCmdData - [] - let ``final canExec returns true if original exec returns Ok`` () = - Property.check <| property { - let! m = GenX.auto - let! p = GenX.auto + test <@ d.CanExec (box p) m = false @> + } - let exec (p: obj) = (p :?> string).Length |> Ok - let d = Binding.cmdParamIf(exec) |> getCmdData - test <@ d.CanExec (box p) m = true @> - } + [] + let ``final autoRequery defaults to false`` () = + Property.check + <| property { + let d = Binding.cmdParamIf ((fail: _ -> _ option)) |> getCmdData + test <@ d.AutoRequery = false @> + } - [] - let ``final canExec returns false if original exec returns Error`` () = - Property.check <| property { - let! m = GenX.auto - let! p = GenX.auto - let! err = GenX.auto + [] + let ``final autoRequery equals original uiBoundCmdParam`` () = + Property.check + <| property { + let! uiBoundCmdParam = GenX.auto - let exec (_: obj) = Error err - let d = Binding.cmdParamIf(exec) |> getCmdData + let d = + Binding.cmdParamIf ((fail: _ -> _ option), uiBoundCmdParam = uiBoundCmdParam) + |> getCmdData - test <@ d.CanExec (box p) m = false @> - } + test <@ d.AutoRequery = uiBoundCmdParam @> + } - [] - let ``final autoRequery defaults to false`` () = - Property.check <| property { - let d = Binding.cmdParamIf((fail: _ -> Result<_,_>)) |> getCmdData - test <@ d.AutoRequery = false @> - } + module result_noModel = - [] - let ``final autoRequery equals original uiBoundCmdParam`` () = - Property.check <| property { - let! uiBoundCmdParam = GenX.auto - let d = Binding.cmdParamIf((fail: _ -> Result<_,_>), uiBoundCmdParam = uiBoundCmdParam) |> getCmdData - test <@ d.AutoRequery = uiBoundCmdParam @> - } + + [] + let ``sets the correct binding name`` () = + Property.check + <| property { + let! bindingName = GenX.auto + let binding = bindingName |> Binding.cmdParamIf (fail: _ -> Result<_, _>) + test <@ binding.Name = bindingName @> + } + + + [] + let ``final exec returns Ok value from original exec converted to ValueOption`` () = + Property.check + <| property { + let! m = GenX.auto + let! p = GenX.auto + + let exec (p: obj) = + let x = (p :?> string).Length + if x > 0 then Ok x else Error(string x) + + let d = Binding.cmdParamIf (exec) |> getCmdData + + test <@ d.Exec (box p) m = (exec p |> ValueOption.ofOk) @> + } + + + [] + let ``final canExec returns true if original exec returns Ok`` () = + Property.check + <| property { + let! m = GenX.auto + let! p = GenX.auto + + let exec (p: obj) = (p :?> string).Length |> Ok + let d = Binding.cmdParamIf (exec) |> getCmdData + + test <@ d.CanExec (box p) m = true @> + } + + + [] + let ``final canExec returns false if original exec returns Error`` () = + Property.check + <| property { + let! m = GenX.auto + let! p = GenX.auto + let! err = GenX.auto + + let exec (_: obj) = Error err + let d = Binding.cmdParamIf (exec) |> getCmdData + + test <@ d.CanExec (box p) m = false @> + } + + + [] + let ``final autoRequery defaults to false`` () = + Property.check + <| property { + let d = Binding.cmdParamIf ((fail: _ -> Result<_, _>)) |> getCmdData + test <@ d.AutoRequery = false @> + } + + + [] + let ``final autoRequery equals original uiBoundCmdParam`` () = + Property.check + <| property { + let! uiBoundCmdParam = GenX.auto + + let d = + Binding.cmdParamIf ((fail: _ -> Result<_, _>), uiBoundCmdParam = uiBoundCmdParam) + |> getCmdData + + test <@ d.AutoRequery = uiBoundCmdParam @> + } module subModel = - module noToMsg_noToBindingModel = + module noToMsg_noToBindingModel = - [] - let ``sets the correct binding name`` () = - Property.check <| property { - let! bindingName = GenX.auto - let binding = bindingName |> Binding.subModel(fail, fail) - test <@ binding.Name = bindingName @> - } + [] + let ``sets the correct binding name`` () = + Property.check + <| property { + let! bindingName = GenX.auto + let binding = bindingName |> Binding.subModel (fail, fail) + test <@ binding.Name = bindingName @> + } - [] - let ``final getModel combines main model and return value of getSubModel, and wraps in ValueSome`` () = - Property.check <| property { - let! x = GenX.auto - let getSubModel = string - let d = Binding.subModel(getSubModel, fail) |> getSubModelData - test <@ d.GetModel x = ((x, getSubModel x) |> box |> ValueSome) @> - } + [] + let ``final getModel combines main model and return value of getSubModel, and wraps in ValueSome`` () = + Property.check + <| property { + let! x = GenX.auto + let getSubModel = string + let d = Binding.subModel (getSubModel, fail) |> getSubModelData + test <@ d.GetModel x = ((x, getSubModel x) |> box |> ValueSome) @> + } - [] - let ``final toMsg simply unboxes`` () = - Property.check <| property { - let! m = GenX.auto - let! x = GenX.auto - let d = Binding.subModel((fun _ -> 0), fail) |> getSubModelData - test <@ d.ToMsg m (box x) = x @> - } + [] + let ``final toMsg simply unboxes`` () = + Property.check + <| property { + let! m = GenX.auto + let! x = GenX.auto + let d = Binding.subModel ((fun _ -> 0), fail) |> getSubModelData + test <@ d.ToMsg m (box x) = x @> + } - module toMsg_noToBindingModel = + module toMsg_noToBindingModel = - [] - let ``sets the correct binding name`` () = - Property.check <| property { - let! bindingName = GenX.auto - let binding = bindingName |> Binding.subModel(fail, fail, fail) - test <@ binding.Name = bindingName @> - } + [] + let ``sets the correct binding name`` () = + Property.check + <| property { + let! bindingName = GenX.auto + let binding = bindingName |> Binding.subModel (fail, fail, fail) + test <@ binding.Name = bindingName @> + } - [] - let ``final getModel combines main model and return value of getSubModel, and wraps in ValueSome`` () = - Property.check <| property { - let! x = GenX.auto - let getSubModel = string - let d = Binding.subModel(getSubModel, fail, fail) |> getSubModelData - test <@ d.GetModel x = ((x, getSubModel x) |> box |> ValueSome) @> - } + [] + let ``final getModel combines main model and return value of getSubModel, and wraps in ValueSome`` () = + Property.check + <| property { + let! x = GenX.auto + let getSubModel = string + let d = Binding.subModel (getSubModel, fail, fail) |> getSubModelData + test <@ d.GetModel x = ((x, getSubModel x) |> box |> ValueSome) @> + } - [] - let ``final toMsg returns value from original toMsg`` () = - Property.check <| property { - let! m = GenX.auto - let! x = GenX.auto + [] + let ``final toMsg returns value from original toMsg`` () = + Property.check + <| property { + let! m = GenX.auto + let! x = GenX.auto - let toMsg = string - let d = Binding.subModel((fun _ -> 0), toMsg, fail) |> getSubModelData + let toMsg = string + let d = Binding.subModel ((fun _ -> 0), toMsg, fail) |> getSubModelData - test <@ d.ToMsg m (box x) = toMsg x @> - } + test <@ d.ToMsg m (box x) = toMsg x @> + } - module toMsg_toBindingModel = + module toMsg_toBindingModel = - [] - let ``sets the correct binding name`` () = - Property.check <| property { - let! bindingName = GenX.auto - let binding = bindingName |> Binding.subModel(fail, fail, fail, fail) - test <@ binding.Name = bindingName @> - } + [] + let ``sets the correct binding name`` () = + Property.check + <| property { + let! bindingName = GenX.auto + let binding = bindingName |> Binding.subModel (fail, fail, fail, fail) + test <@ binding.Name = bindingName @> + } - [] - let ``final getModel calls toBindingModel on main model and return value of getSubModel, and wraps in ValueSome`` () = - Property.check <| property { - let! x = GenX.auto - let getSubModel = string - let toBindingModel (m: int, s: string) = m + s.Length - let d = Binding.subModel(getSubModel, toBindingModel, fail, fail) |> getSubModelData - test <@ d.GetModel x = ((x, getSubModel x) |> toBindingModel |> box |> ValueSome) @> - } + [] + let ``final getModel calls toBindingModel on main model and return value of getSubModel, and wraps in ValueSome`` + () + = + Property.check + <| property { + let! x = GenX.auto + let getSubModel = string + let toBindingModel (m: int, s: string) = m + s.Length + let d = + Binding.subModel (getSubModel, toBindingModel, fail, fail) |> getSubModelData - [] - let ``final toMsg returns value from original toMsg`` () = - Property.check <| property { - let! m = GenX.auto - let! x = GenX.auto + test <@ d.GetModel x = ((x, getSubModel x) |> toBindingModel |> box |> ValueSome) @> + } + + + [] + let ``final toMsg returns value from original toMsg`` () = + Property.check + <| property { + let! m = GenX.auto + let! x = GenX.auto - let toMsg = string - let d = Binding.subModel((fun _ -> 0), (fun _ -> 0), toMsg, fail) |> getSubModelData + let toMsg = string - test <@ d.ToMsg m (box x) = toMsg x @> - } + let d = + Binding.subModel ((fun _ -> 0), (fun _ -> 0), toMsg, fail) |> getSubModelData + + test <@ d.ToMsg m (box x) = toMsg x @> + } module subModelOpt = - module voption_noToMsg_noToBindingModel = + module voption_noToMsg_noToBindingModel = - [] - let ``sets the correct binding name`` () = - Property.check <| property { - let! bindingName = GenX.auto - let binding = bindingName |> Binding.subModelOpt((fail: _ -> _ voption), fail) - test <@ binding.Name = bindingName @> - } + [] + let ``sets the correct binding name`` () = + Property.check + <| property { + let! bindingName = GenX.auto + let binding = bindingName |> Binding.subModelOpt ((fail: _ -> _ voption), fail) + test <@ binding.Name = bindingName @> + } - [] - let ``final getModel combines main model and inner return value of getSubModel if ValueSome`` () = - Property.check <| property { - let! x = GenX.auto - let getSubModel = string >> ValueSome - let d = Binding.subModelOpt(getSubModel, fail) |> getSubModelData - test <@ d.GetModel x = ((x, (getSubModel x).Value) |> box |> ValueSome) @> - } + [] + let ``final getModel combines main model and inner return value of getSubModel if ValueSome`` () = + Property.check + <| property { + let! x = GenX.auto + let getSubModel = string >> ValueSome + let d = Binding.subModelOpt (getSubModel, fail) |> getSubModelData + test <@ d.GetModel x = ((x, (getSubModel x).Value) |> box |> ValueSome) @> + } - [] - let ``final getModel returns ValueNone if getSubModel returns ValueNone`` () = - Property.check <| property { - let! x = GenX.auto - let getSubModel (_: int) : string voption = ValueNone - let d = Binding.subModelOpt(getSubModel, fail) |> getSubModelData - test <@ d.GetModel x = ValueNone @> - } + [] + let ``final getModel returns ValueNone if getSubModel returns ValueNone`` () = + Property.check + <| property { + let! x = GenX.auto + let getSubModel (_: int) : string voption = ValueNone + let d = Binding.subModelOpt (getSubModel, fail) |> getSubModelData + test <@ d.GetModel x = ValueNone @> + } + + + [] + let ``final toMsg simply unboxes`` () = + Property.check + <| property { + let! m = GenX.auto + let! x = GenX.auto + let d = Binding.subModelOpt ((fun _ -> ValueSome 0), fail) |> getSubModelData + test <@ d.ToMsg m (box x) = x @> + } - [] - let ``final toMsg simply unboxes`` () = - Property.check <| property { - let! m = GenX.auto - let! x = GenX.auto - let d = Binding.subModelOpt((fun _ -> ValueSome 0), fail) |> getSubModelData - test <@ d.ToMsg m (box x) = x @> - } + module option_noToMsg_noToBindingModel = - module option_noToMsg_noToBindingModel = + [] + let ``sets the correct binding name`` () = + Property.check + <| property { + let! bindingName = GenX.auto + let binding = bindingName |> Binding.subModelOpt ((fail: _ -> _ option), fail) + test <@ binding.Name = bindingName @> + } - [] - let ``sets the correct binding name`` () = - Property.check <| property { - let! bindingName = GenX.auto - let binding = bindingName |> Binding.subModelOpt((fail: _ -> _ option), fail) - test <@ binding.Name = bindingName @> - } + [] + let ``final getModel combines main model and inner return value of getSubModel if ValueSome`` () = + Property.check + <| property { + let! x = GenX.auto + let getSubModel = string >> Some + let d = Binding.subModelOpt (getSubModel, fail) |> getSubModelData + test <@ d.GetModel x = ((x, (getSubModel x).Value) |> box |> ValueSome) @> + } - [] - let ``final getModel combines main model and inner return value of getSubModel if ValueSome`` () = - Property.check <| property { - let! x = GenX.auto - let getSubModel = string >> Some - let d = Binding.subModelOpt(getSubModel, fail) |> getSubModelData - test <@ d.GetModel x = ((x, (getSubModel x).Value) |> box |> ValueSome) @> - } + [] + let ``final getModel returns ValueNone if getSubModel returns ValueNone`` () = + Property.check + <| property { + let! x = GenX.auto + let getSubModel (_: int) : string option = None + let d = Binding.subModelOpt (getSubModel, fail) |> getSubModelData + test <@ d.GetModel x = ValueNone @> + } - [] - let ``final getModel returns ValueNone if getSubModel returns ValueNone`` () = - Property.check <| property { - let! x = GenX.auto - let getSubModel (_: int) : string option = None - let d = Binding.subModelOpt(getSubModel, fail) |> getSubModelData - test <@ d.GetModel x = ValueNone @> - } + [] + let ``final toMsg simply unboxes`` () = + Property.check + <| property { + let! m = GenX.auto + let! x = GenX.auto + let d = Binding.subModelOpt ((fun _ -> Some 0), fail) |> getSubModelData + test <@ d.ToMsg m (box x) = x @> + } - [] - let ``final toMsg simply unboxes`` () = - Property.check <| property { - let! m = GenX.auto - let! x = GenX.auto - let d = Binding.subModelOpt((fun _ -> Some 0), fail) |> getSubModelData - test <@ d.ToMsg m (box x) = x @> - } + module voption_toMsg_noToBindingModel = - module voption_toMsg_noToBindingModel = + [] + let ``sets the correct binding name`` () = + Property.check + <| property { + let! bindingName = GenX.auto + let binding = + bindingName |> Binding.subModelOpt ((fail: _ -> _ voption), fail, fail) + + test <@ binding.Name = bindingName @> + } - [] - let ``sets the correct binding name`` () = - Property.check <| property { - let! bindingName = GenX.auto - let binding = bindingName |> Binding.subModelOpt((fail: _ -> _ voption), fail, fail) - test <@ binding.Name = bindingName @> - } + [] + let ``final getModel combines main model and inner return value of getSubModel if ValueSome`` () = + Property.check + <| property { + let! x = GenX.auto + let getSubModel = string >> ValueSome + let d = Binding.subModelOpt (getSubModel, fail, fail) |> getSubModelData + test <@ d.GetModel x = ((x, (getSubModel x).Value) |> box |> ValueSome) @> + } - [] - let ``final getModel combines main model and inner return value of getSubModel if ValueSome`` () = - Property.check <| property { - let! x = GenX.auto - let getSubModel = string >> ValueSome - let d = Binding.subModelOpt(getSubModel, fail, fail) |> getSubModelData - test <@ d.GetModel x = ((x, (getSubModel x).Value) |> box |> ValueSome) @> - } + [] + let ``final getModel returns ValueNone if getSubModel returns ValueNone`` () = + Property.check + <| property { + let! x = GenX.auto + let getSubModel (_: int) : string voption = ValueNone + let d = Binding.subModelOpt (getSubModel, fail, fail) |> getSubModelData + test <@ d.GetModel x = ValueNone @> + } - [] - let ``final getModel returns ValueNone if getSubModel returns ValueNone`` () = - Property.check <| property { - let! x = GenX.auto - let getSubModel (_: int) : string voption = ValueNone - let d = Binding.subModelOpt(getSubModel, fail, fail) |> getSubModelData - test <@ d.GetModel x = ValueNone @> - } + [] + let ``final toMsg returns value from original toMsg`` () = + Property.check + <| property { + let! m = GenX.auto + let! x = GenX.auto - [] - let ``final toMsg returns value from original toMsg`` () = - Property.check <| property { - let! m = GenX.auto - let! x = GenX.auto + let toMsg = string + let d = Binding.subModelOpt ((fun _ -> ValueSome 0), toMsg, fail) |> getSubModelData - let toMsg = string - let d = Binding.subModelOpt((fun _ -> ValueSome 0), toMsg, fail) |> getSubModelData + test <@ d.ToMsg m (box x) = toMsg x @> + } - test <@ d.ToMsg m (box x) = toMsg x @> - } + module option_toMsg_noToBindingModel = - module option_toMsg_noToBindingModel = + [] + let ``sets the correct binding name`` () = + Property.check + <| property { + let! bindingName = GenX.auto + let binding = bindingName |> Binding.subModelOpt ((fail: _ -> _ option), fail, fail) + test <@ binding.Name = bindingName @> + } - [] - let ``sets the correct binding name`` () = - Property.check <| property { - let! bindingName = GenX.auto - let binding = bindingName |> Binding.subModelOpt((fail: _ -> _ option), fail, fail) - test <@ binding.Name = bindingName @> - } + [] + let ``final getModel combines main model and inner return value of getSubModel if ValueSome`` () = + Property.check + <| property { + let! x = GenX.auto + let getSubModel = string >> Some + let d = Binding.subModelOpt (getSubModel, fail, fail) |> getSubModelData + test <@ d.GetModel x = ((x, (getSubModel x).Value) |> box |> ValueSome) @> + } - [] - let ``final getModel combines main model and inner return value of getSubModel if ValueSome`` () = - Property.check <| property { - let! x = GenX.auto - let getSubModel = string >> Some - let d = Binding.subModelOpt(getSubModel, fail, fail) |> getSubModelData - test <@ d.GetModel x = ((x, (getSubModel x).Value) |> box |> ValueSome) @> - } + [] + let ``final getModel returns ValueNone if getSubModel returns ValueNone`` () = + Property.check + <| property { + let! x = GenX.auto + let getSubModel (_: int) : string option = None + let d = Binding.subModelOpt (getSubModel, fail, fail) |> getSubModelData + test <@ d.GetModel x = ValueNone @> + } - [] - let ``final getModel returns ValueNone if getSubModel returns ValueNone`` () = - Property.check <| property { - let! x = GenX.auto - let getSubModel (_: int) : string option = None - let d = Binding.subModelOpt(getSubModel, fail, fail) |> getSubModelData - test <@ d.GetModel x = ValueNone @> - } + [] + let ``final toMsg returns value from original toMsg`` () = + Property.check + <| property { + let! m = GenX.auto + let! x = GenX.auto - [] - let ``final toMsg returns value from original toMsg`` () = - Property.check <| property { - let! m = GenX.auto - let! x = GenX.auto + let toMsg = string + let d = Binding.subModelOpt ((fun _ -> Some 0), toMsg, fail) |> getSubModelData - let toMsg = string - let d = Binding.subModelOpt((fun _ -> Some 0), toMsg, fail) |> getSubModelData + test <@ d.ToMsg m (box x) = toMsg x @> + } - test <@ d.ToMsg m (box x) = toMsg x @> - } + module voption_toMsg_toBindingModel = - module voption_toMsg_toBindingModel = + [] + let ``sets the correct binding name`` () = + Property.check + <| property { + let! bindingName = GenX.auto - [] - let ``sets the correct binding name`` () = - Property.check <| property { - let! bindingName = GenX.auto - let binding = bindingName |> Binding.subModelOpt((fail: _ -> _ voption), fail, fail, fail) - test <@ binding.Name = bindingName @> - } + let binding = + bindingName |> Binding.subModelOpt ((fail: _ -> _ voption), fail, fail, fail) + test <@ binding.Name = bindingName @> + } - [] - let ``final getModel calls toBindingModel on main model and inner return value of getSubModel if ValueSome`` () = - Property.check <| property { - let! x = GenX.auto - let getSubModel = string >> ValueSome - let toBindingModel (m: int, s: string) = m + s.Length - let d = Binding.subModelOpt(getSubModel, toBindingModel, fail, fail) |> getSubModelData - test <@ d.GetModel x = ((x, (getSubModel x).Value) |> toBindingModel |> box |> ValueSome) @> - } + [] + let ``final getModel calls toBindingModel on main model and inner return value of getSubModel if ValueSome`` + () + = + Property.check + <| property { + let! x = GenX.auto + let getSubModel = string >> ValueSome + let toBindingModel (m: int, s: string) = m + s.Length - [] - let ``final getModel returns ValueNone if getSubModel returns ValueNone`` () = - Property.check <| property { - let! x = GenX.auto - let getSubModel (_: int) : string voption = ValueNone - let d = Binding.subModelOpt(getSubModel, fail, fail, fail) |> getSubModelData - test <@ d.GetModel x = ValueNone @> - } + let d = + Binding.subModelOpt (getSubModel, toBindingModel, fail, fail) |> getSubModelData + test <@ d.GetModel x = ((x, (getSubModel x).Value) |> toBindingModel |> box |> ValueSome) @> + } - [] - let ``final toMsg returns value from original toMsg`` () = - Property.check <| property { - let! m = GenX.auto - let! x = GenX.auto - let toMsg = string - let d = Binding.subModelOpt((fun _ -> ValueSome 0), (fun _ -> ValueSome 0), toMsg, fail) |> getSubModelData + [] + let ``final getModel returns ValueNone if getSubModel returns ValueNone`` () = + Property.check + <| property { + let! x = GenX.auto + let getSubModel (_: int) : string voption = ValueNone + let d = Binding.subModelOpt (getSubModel, fail, fail, fail) |> getSubModelData + test <@ d.GetModel x = ValueNone @> + } - test <@ d.ToMsg m (box x) = toMsg x @> - } + [] + let ``final toMsg returns value from original toMsg`` () = + Property.check + <| property { + let! m = GenX.auto + let! x = GenX.auto + let toMsg = string - module option_toMsg_toBindingModel = + let d = + Binding.subModelOpt ((fun _ -> ValueSome 0), (fun _ -> ValueSome 0), toMsg, fail) + |> getSubModelData + test <@ d.ToMsg m (box x) = toMsg x @> + } - [] - let ``sets the correct binding name`` () = - Property.check <| property { - let! bindingName = GenX.auto - let binding = bindingName |> Binding.subModelOpt((fail: _ -> _ option), fail, fail, fail) - test <@ binding.Name = bindingName @> - } - [] - let ``final getModel calls toBindingModel on main model and inner return value of getSubModel if Some`` () = - Property.check <| property { - let! x = GenX.auto - let getSubModel = string >> Some - let toBindingModel (m: int, s: string) = m + s.Length - let d = Binding.subModelOpt(getSubModel, toBindingModel, fail, fail) |> getSubModelData - test <@ d.GetModel x = ((x, (getSubModel x).Value) |> toBindingModel |> box |> ValueSome) @> - } + module option_toMsg_toBindingModel = - [] - let ``final getModel returns ValueNone if getSubModel returns ValueNone`` () = - Property.check <| property { - let! x = GenX.auto - let getSubModel (_: int) : string option = None - let d = Binding.subModelOpt(getSubModel, fail, fail, fail) |> getSubModelData - test <@ d.GetModel x = ValueNone @> - } + [] + let ``sets the correct binding name`` () = + Property.check + <| property { + let! bindingName = GenX.auto + let binding = + bindingName |> Binding.subModelOpt ((fail: _ -> _ option), fail, fail, fail) - [] - let ``final toMsg returns value from original toMsg`` () = - Property.check <| property { - let! m = GenX.auto - let! x = GenX.auto + test <@ binding.Name = bindingName @> + } + + + [] + let ``final getModel calls toBindingModel on main model and inner return value of getSubModel if Some`` () = + Property.check + <| property { + let! x = GenX.auto + let getSubModel = string >> Some + let toBindingModel (m: int, s: string) = m + s.Length + + let d = + Binding.subModelOpt (getSubModel, toBindingModel, fail, fail) |> getSubModelData + + test <@ d.GetModel x = ((x, (getSubModel x).Value) |> toBindingModel |> box |> ValueSome) @> + } + + + [] + let ``final getModel returns ValueNone if getSubModel returns ValueNone`` () = + Property.check + <| property { + let! x = GenX.auto + let getSubModel (_: int) : string option = None + let d = Binding.subModelOpt (getSubModel, fail, fail, fail) |> getSubModelData + test <@ d.GetModel x = ValueNone @> + } - let toMsg = string - let d = Binding.subModelOpt((fun _ -> Some 0), (fun _ -> Some 0), toMsg, fail) |> getSubModelData - test <@ d.ToMsg m (box x) = toMsg x @> - } + [] + let ``final toMsg returns value from original toMsg`` () = + Property.check + <| property { + let! m = GenX.auto + let! x = GenX.auto + + let toMsg = string + + let d = + Binding.subModelOpt ((fun _ -> Some 0), (fun _ -> Some 0), toMsg, fail) + |> getSubModelData + + test <@ d.ToMsg m (box x) = toMsg x @> + } module subModelSeqKeyed = - module noToMsg_noToBindingModel = + module noToMsg_noToBindingModel = + + [] + let ``sets the correct binding name`` () = + Property.check + <| property { + let! bindingName = GenX.auto + let binding = bindingName |> Binding.subModelSeq (fail, fail, fail) + test <@ binding.Name = bindingName @> + } + + + [] + let ``final getModels returns tuples of the items returned by getSubModels and the main model`` () = + Property.check + <| property { + let! m = GenX.auto + let getSubModels: string -> char list = Seq.toList + let d = Binding.subModelSeq (getSubModels, fail, fail) |> getSubModelSeqKeyedData + + test + <@ d.GetSubModels m |> Seq.map unbox |> Seq.toList = (m |> getSubModels |> List.map (fun s -> m, s)) @> + } + + + [] + let ``final getId returns the ID of each element in final getModels`` () = + Property.check + <| property { + let! m = GenX.auto + let getSubModels: string -> char list = Seq.toList + let getId: char -> string = string + let d = Binding.subModelSeq (getSubModels, getId, fail) |> getSubModelSeqKeyedData + + test + <@ + d.GetSubModels m |> Seq.map d.BmToId |> Seq.map unbox |> Seq.toList = (m + |> getSubModels + |> List.map getId) + @> + } + + + module toMsg_noToBindingModel = + + + [] + let ``sets the correct binding name`` () = + Property.check + <| property { + let! bindingName = GenX.auto + let binding = bindingName |> Binding.subModelSeq (fail, fail, fail, fail) + test <@ binding.Name = bindingName @> + } + + + [] + let ``final getModels returns tuples of the items returned by getSubModels and the main model`` () = + Property.check + <| property { + let! m = GenX.auto + let getSubModels: string -> char list = Seq.toList + + let d = + Binding.subModelSeq (getSubModels, fail, fail, fail) |> getSubModelSeqKeyedData + + test + <@ d.GetSubModels m |> Seq.map unbox |> Seq.toList = (m |> getSubModels |> List.map (fun s -> m, s)) @> + } + + + [] + let ``final getId returns the ID of each element in final getModels`` () = + Property.check + <| property { + let! m = GenX.auto + let getSubModels: string -> char list = Seq.toList + let getId: char -> string = string + + let d = + Binding.subModelSeq (getSubModels, getId, fail, fail) |> getSubModelSeqKeyedData + + test + <@ + d.GetSubModels m |> Seq.map d.BmToId |> Seq.map unbox |> Seq.toList = (m + |> getSubModels + |> List.map getId) + @> + } + + + module toMsg_toBindingModel = + + + [] + let ``sets the correct binding name`` () = + Property.check + <| property { + let! bindingName = GenX.auto + let binding = bindingName |> Binding.subModelSeq (fail, fail, fail, fail, fail) + test <@ binding.Name = bindingName @> + } + + + [] + let ``final getModels returns output of toBindingModel called with tuples of the items returned by getSubModels and the main model`` + () + = + Property.check + <| property { + let! m = GenX.auto + let getSubModels: string -> char list = Seq.toList + let toBindingModel (m: string, c: char) = (m + string c).Length + + let d = + Binding.subModelSeq (getSubModels, toBindingModel, fail, fail, fail) + |> getSubModelSeqKeyedData + + test + <@ + d.GetSubModels m |> Seq.map unbox |> Seq.toList = (m + |> getSubModels + |> List.map (fun s -> toBindingModel (m, s))) + @> + } + + + [] + let ``final getId returns the ID of each element in final getModels`` () = + Property.check + <| property { + let! m = GenX.auto + let getSubModels: string -> char list = Seq.toList + let toBindingModel (m: string, c: char) = (m + string c).Length + let getId i = i * 2 + + let d = + Binding.subModelSeq (getSubModels, toBindingModel, getId, fail, fail) + |> getSubModelSeqKeyedData + + test + <@ + d.GetSubModels m |> Seq.map d.BmToId |> Seq.map unbox |> Seq.toList = (m + |> getSubModels + |> List.map (fun s -> + toBindingModel ( + m, + s + )) + |> List.map getId) + @> + } - [] - let ``sets the correct binding name`` () = - Property.check <| property { - let! bindingName = GenX.auto - let binding = bindingName |> Binding.subModelSeq(fail, fail, fail) - test <@ binding.Name = bindingName @> - } - [] - let ``final getModels returns tuples of the items returned by getSubModels and the main model`` () = - Property.check <| property { - let! m = GenX.auto - let getSubModels : string -> char list = Seq.toList - let d = Binding.subModelSeq(getSubModels, fail, fail) |> getSubModelSeqKeyedData - test <@ d.GetSubModels m |> Seq.map unbox |> Seq.toList = (m |> getSubModels |> List.map (fun s -> m, s)) @> - } +module subModelSelectedItem = - [] - let ``final getId returns the ID of each element in final getModels`` () = - Property.check <| property { - let! m = GenX.auto - let getSubModels : string -> char list = Seq.toList - let getId : char -> string = string - let d = Binding.subModelSeq(getSubModels, getId, fail) |> getSubModelSeqKeyedData - test <@ d.GetSubModels m |> Seq.map d.BmToId |> Seq.map unbox |> Seq.toList = (m |> getSubModels |> List.map getId) @> - } + module voption_setModel = - module toMsg_noToBindingModel = + [] + let ``sets the correct binding name`` () = + Property.check + <| property { + let! bindingName = GenX.auto + let binding = + bindingName |> Binding.subModelSelectedItem ("", (fail: _ -> _ voption), fail2) - [] - let ``sets the correct binding name`` () = - Property.check <| property { - let! bindingName = GenX.auto - let binding = bindingName |> Binding.subModelSeq(fail, fail, fail, fail) - test <@ binding.Name = bindingName @> - } + test <@ binding.Name = bindingName @> + } - [] - let ``final getModels returns tuples of the items returned by getSubModels and the main model`` () = - Property.check <| property { - let! m = GenX.auto - let getSubModels : string -> char list = Seq.toList - let d = Binding.subModelSeq(getSubModels, fail, fail, fail) |> getSubModelSeqKeyedData - test <@ d.GetSubModels m |> Seq.map unbox |> Seq.toList = (m |> getSubModels |> List.map (fun s -> m, s)) @> - } + [] + let ``sets the correct subModelSeqBindingName`` () = + Property.check + <| property { + let! name = GenX.auto + let d = + Binding.subModelSelectedItem (name, (fail: _ -> _ voption), fail2) + |> getSubModelSelectedItemData - [] - let ``final getId returns the ID of each element in final getModels`` () = - Property.check <| property { - let! m = GenX.auto - let getSubModels : string -> char list = Seq.toList - let getId : char -> string = string - let d = Binding.subModelSeq(getSubModels, getId, fail, fail) |> getSubModelSeqKeyedData - test <@ d.GetSubModels m |> Seq.map d.BmToId |> Seq.map unbox |> Seq.toList = (m |> getSubModels |> List.map getId) @> - } + test <@ d.SubModelSeqBindingName = name @> + } - module toMsg_toBindingModel = + [] + let ``final get returns value from original get`` () = + Property.check + <| property { + let! x = GenX.auto + let! useNone = Gen.bool + let get (x: int) = + if useNone then ValueNone else x |> string |> ValueSome - [] - let ``sets the correct binding name`` () = - Property.check <| property { - let! bindingName = GenX.auto - let binding = bindingName |> Binding.subModelSeq(fail, fail, fail, fail, fail) - test <@ binding.Name = bindingName @> - } + let d = Binding.subModelSelectedItem ("", get, fail2) |> getSubModelSelectedItemData + test <@ d.Get x |> ValueOption.map unbox = get x @> + } - [] - let ``final getModels returns output of toBindingModel called with tuples of the items returned by getSubModels and the main model`` () = - Property.check <| property { - let! m = GenX.auto - let getSubModels : string -> char list = Seq.toList - let toBindingModel (m: string, c: char) = (m + string c).Length - let d = Binding.subModelSeq(getSubModels, toBindingModel, fail, fail, fail) |> getSubModelSeqKeyedData - test <@ d.GetSubModels m |> Seq.map unbox |> Seq.toList = (m |> getSubModels |> List.map (fun s -> toBindingModel (m, s))) @> - } + [] + let ``final set returns value from original set`` () = + Property.check + <| property { + let! m = GenX.auto + let! p = GenX.auto + let get _ = ValueNone + let set (p: string voption) m = + p |> ValueOption.map (fun p -> p.Length + m |> string) - [] - let ``final getId returns the ID of each element in final getModels`` () = - Property.check <| property { - let! m = GenX.auto - let getSubModels : string -> char list = Seq.toList - let toBindingModel (m: string, c: char) = (m + string c).Length - let getId i = i * 2 - let d = Binding.subModelSeq(getSubModels, toBindingModel, getId, fail, fail) |> getSubModelSeqKeyedData - test <@ d.GetSubModels m |> Seq.map d.BmToId |> Seq.map unbox |> Seq.toList = (m |> getSubModels |> List.map (fun s -> toBindingModel (m, s)) |> List.map getId) @> - } + let d = Binding.subModelSelectedItem ("", get, set) |> getSubModelSelectedItemData + test <@ d.Set (p |> ValueOption.map box) m = set p m @> + } + module option_setModel = -module subModelSelectedItem = + [] + let ``sets the correct binding name`` () = + Property.check + <| property { + let! bindingName = GenX.auto - module voption_setModel = + let binding = + bindingName |> Binding.subModelSelectedItem ("", (fail: _ -> _ option), fail2) + test <@ binding.Name = bindingName @> + } - [] - let ``sets the correct binding name`` () = - Property.check <| property { - let! bindingName = GenX.auto - let binding = bindingName |> Binding.subModelSelectedItem("", (fail: _ -> _ voption), fail2) - test <@ binding.Name = bindingName @> - } + [] + let ``sets the correct subModelSeqBindingName`` () = + Property.check + <| property { + let! name = GenX.auto - [] - let ``sets the correct subModelSeqBindingName`` () = - Property.check <| property { - let! name = GenX.auto - let d = Binding.subModelSelectedItem(name, (fail: _ -> _ voption), fail2) |> getSubModelSelectedItemData - test <@ d.SubModelSeqBindingName = name @> - } + let d = + Binding.subModelSelectedItem (name, (fail: _ -> _ option), fail2) + |> getSubModelSelectedItemData + test <@ d.SubModelSeqBindingName = name @> + } - [] - let ``final get returns value from original get`` () = - Property.check <| property { - let! x = GenX.auto - let! useNone = Gen.bool - let get (x: int) = if useNone then ValueNone else x |> string |> ValueSome - let d = Binding.subModelSelectedItem("", get, fail2) |> getSubModelSelectedItemData - test <@ d.Get x |> ValueOption.map unbox = get x @> - } + [] + let ``final get returns value from original get converted to ValueOption`` () = + Property.check + <| property { + let! x = GenX.auto + let! useNone = Gen.bool - [] - let ``final set returns value from original set`` () = - Property.check <| property { - let! m = GenX.auto - let! p = GenX.auto - let get _ = ValueNone - let set (p: string voption) m = p |> ValueOption.map (fun p -> p.Length + m |> string) - let d = Binding.subModelSelectedItem("", get, set) |> getSubModelSelectedItemData - test <@ d.Set (p |> ValueOption.map box) m = set p m @> - } + let get (x: int) = + if useNone then None else x |> string |> Some + let d = Binding.subModelSelectedItem ("", get, fail2) |> getSubModelSelectedItemData + test <@ d.Get x |> ValueOption.map unbox = (get x |> ValueOption.ofOption) @> + } - module option_setModel = + [] + let ``final set returns value from original set`` () = + Property.check + <| property { + let! m = GenX.auto + let! p = GenX.auto + let get _ = None - [] - let ``sets the correct binding name`` () = - Property.check <| property { - let! bindingName = GenX.auto - let binding = bindingName |> Binding.subModelSelectedItem("", (fail: _ -> _ option), fail2) - test <@ binding.Name = bindingName @> - } + let set (p: string option) m = + p |> Option.map (fun p -> p.Length + m |> string) + let d = Binding.subModelSelectedItem ("", get, set) |> getSubModelSelectedItemData + test <@ d.Set (p |> Option.map box |> ValueOption.ofOption) m = set p m @> + } - [] - let ``sets the correct subModelSeqBindingName`` () = - Property.check <| property { - let! name = GenX.auto - let d = Binding.subModelSelectedItem(name, (fail: _ -> _ option), fail2) |> getSubModelSelectedItemData - test <@ d.SubModelSeqBindingName = name @> - } + module voption_noSetModel = - [] - let ``final get returns value from original get converted to ValueOption`` () = - Property.check <| property { - let! x = GenX.auto - let! useNone = Gen.bool - let get (x: int) = if useNone then None else x |> string |> Some - let d = Binding.subModelSelectedItem("", get, fail2) |> getSubModelSelectedItemData - test <@ d.Get x |> ValueOption.map unbox = (get x |> ValueOption.ofOption) @> - } + [] + let ``sets the correct binding name`` () = + Property.check + <| property { + let! bindingName = GenX.auto - [] - let ``final set returns value from original set`` () = - Property.check <| property { - let! m = GenX.auto - let! p = GenX.auto - let get _ = None - let set (p: string option) m = p |> Option.map (fun p -> p.Length + m |> string) - let d = Binding.subModelSelectedItem("", get, set) |> getSubModelSelectedItemData - test <@ d.Set (p |> Option.map box |> ValueOption.ofOption) m = set p m @> - } - - - module voption_noSetModel = - - - [] - let ``sets the correct binding name`` () = - Property.check <| property { - let! bindingName = GenX.auto - let binding = bindingName |> Binding.subModelSelectedItem("", (fail: _ -> _ voption), (fail: _ -> obj)) - test <@ binding.Name = bindingName @> - } + let binding = + bindingName + |> Binding.subModelSelectedItem ("", (fail: _ -> _ voption), (fail: _ -> obj)) + test <@ binding.Name = bindingName @> + } - [] - let ``sets the correct subModelSeqBindingName`` () = - Property.check <| property { - let! name = GenX.auto - let d = Binding.subModelSelectedItem(name, (fail: _ -> _ voption), (fail: _ -> obj)) |> getSubModelSelectedItemData - test <@ d.SubModelSeqBindingName = name @> - } + [] + let ``sets the correct subModelSeqBindingName`` () = + Property.check + <| property { + let! name = GenX.auto - [] - let ``final get returns value from original get`` () = - Property.check <| property { - let! x = GenX.auto - let! useNone = Gen.bool - let get (x: int) = if useNone then ValueNone else x |> string |> ValueSome - let d = Binding.subModelSelectedItem("", get, (fail: _ -> obj)) |> getSubModelSelectedItemData - test <@ d.Get x |> ValueOption.map unbox = get x @> - } + let d = + Binding.subModelSelectedItem (name, (fail: _ -> _ voption), (fail: _ -> obj)) + |> getSubModelSelectedItemData + test <@ d.SubModelSeqBindingName = name @> + } - [] - let ``final set returns value from original set`` () = - Property.check <| property { - let! m = GenX.auto - let! p = GenX.auto - let get _ = ValueNone - let set (p: string voption) = p |> ValueOption.map (fun p -> p.Length |> string) - let d = Binding.subModelSelectedItem("", get, set) |> getSubModelSelectedItemData - test <@ d.Set (p |> ValueOption.map box) m = set p @> - } + [] + let ``final get returns value from original get`` () = + Property.check + <| property { + let! x = GenX.auto + let! useNone = Gen.bool - module option_noSetModel = + let get (x: int) = + if useNone then ValueNone else x |> string |> ValueSome + let d = + Binding.subModelSelectedItem ("", get, (fail: _ -> obj)) + |> getSubModelSelectedItemData - [] - let ``sets the correct binding name`` () = - Property.check <| property { - let! bindingName = GenX.auto - let binding = bindingName |> Binding.subModelSelectedItem("", (fail: _ -> _ option), (fail: _ -> obj)) - test <@ binding.Name = bindingName @> - } + test <@ d.Get x |> ValueOption.map unbox = get x @> + } - [] - let ``sets the correct subModelSeqBindingName`` () = - Property.check <| property { - let! name = GenX.auto - let d = Binding.subModelSelectedItem(name, (fail: _ -> _ option), (fail: _ -> obj)) |> getSubModelSelectedItemData - test <@ d.SubModelSeqBindingName = name @> - } + [] + let ``final set returns value from original set`` () = + Property.check + <| property { + let! m = GenX.auto + let! p = GenX.auto + let get _ = ValueNone + let set (p: string voption) = + p |> ValueOption.map (fun p -> p.Length |> string) - [] - let ``final get returns value from original get converted to ValueOption`` () = - Property.check <| property { - let! x = GenX.auto - let! useNone = Gen.bool - let get (x: int) = if useNone then None else x |> string |> Some - let d = Binding.subModelSelectedItem("", get, (fail: _ -> obj)) |> getSubModelSelectedItemData - test <@ d.Get x |> ValueOption.map unbox = (get x |> ValueOption.ofOption) @> - } + let d = Binding.subModelSelectedItem ("", get, set) |> getSubModelSelectedItemData + test <@ d.Set (p |> ValueOption.map box) m = set p @> + } - [] - let ``final set returns value from original set`` () = - Property.check <| property { - let! m = GenX.auto - let! p = GenX.auto - let get _ = None - let set (p: string option) = p |> Option.map (fun p -> p.Length |> string) - let d = Binding.subModelSelectedItem("", get, set) |> getSubModelSelectedItemData - test <@ d.Set (p |> Option.map box |> ValueOption.ofOption) m = set p @> - } + module option_noSetModel = + + + [] + let ``sets the correct binding name`` () = + Property.check + <| property { + let! bindingName = GenX.auto + + let binding = + bindingName + |> Binding.subModelSelectedItem ("", (fail: _ -> _ option), (fail: _ -> obj)) + + test <@ binding.Name = bindingName @> + } + + + [] + let ``sets the correct subModelSeqBindingName`` () = + Property.check + <| property { + let! name = GenX.auto + + let d = + Binding.subModelSelectedItem (name, (fail: _ -> _ option), (fail: _ -> obj)) + |> getSubModelSelectedItemData + + test <@ d.SubModelSeqBindingName = name @> + } + + + [] + let ``final get returns value from original get converted to ValueOption`` () = + Property.check + <| property { + let! x = GenX.auto + let! useNone = Gen.bool + + let get (x: int) = + if useNone then None else x |> string |> Some + + let d = + Binding.subModelSelectedItem ("", get, (fail: _ -> obj)) + |> getSubModelSelectedItemData + + test <@ d.Get x |> ValueOption.map unbox = (get x |> ValueOption.ofOption) @> + } + + + [] + let ``final set returns value from original set`` () = + Property.check + <| property { + let! m = GenX.auto + let! p = GenX.auto + let get _ = None + + let set (p: string option) = + p |> Option.map (fun p -> p.Length |> string) + + let d = Binding.subModelSelectedItem ("", get, set) |> getSubModelSelectedItemData + test <@ d.Set (p |> Option.map box |> ValueOption.ofOption) m = set p @> + } module sorting = - open BindingVmHelpers + open BindingVmHelpers - [] + [] let ``SubModelSelectedItemData sorted last`` () = - Property.check <| property { - let! s = GenX.auto - let data = - [ SubModelSelectedItemData { Get = fail; Set = fail2; SubModelSeqBindingName = s } - SubModelSeqKeyedData { GetSubModels = fail; BmToId = fail; CreateViewModel = fail; CreateCollection = fail; UpdateViewModel = fail; ToMsg = fail; VmToId = fail } - SubModelSelectedItemData { Get = fail; Set = fail2; SubModelSeqBindingName = s } - ] |> List.map BaseBindingData - let sorted = data |> List.sortWith (SubModelSelectedItemLast().CompareBindingDatas()) - match sorted with - | [_; BaseBindingData (SubModelSelectedItemData _); BaseBindingData (SubModelSelectedItemData _)] -> () - | _ -> failwith "SubModelSelectedItemData was not sorted last" - } + Property.check + <| property { + let! s = GenX.auto + + let data = + [ SubModelSelectedItemData + { Get = fail + Set = fail2 + SubModelSeqBindingName = s } + SubModelSeqKeyedData + { GetSubModels = fail + BmToId = fail + CreateViewModel = fail + CreateCollection = fail + UpdateViewModel = fail + ToMsg = fail + VmToId = fail } + SubModelSelectedItemData + { Get = fail + Set = fail2 + SubModelSeqBindingName = s } ] + |> List.map BaseBindingData + + let sorted = + data |> List.sortWith (SubModelSelectedItemLast().CompareBindingDatas()) + + match sorted with + | [ _; BaseBindingData(SubModelSelectedItemData _); BaseBindingData(SubModelSelectedItemData _) ] -> () + | _ -> failwith "SubModelSelectedItemData was not sorted last" + } \ No newline at end of file diff --git a/src/Elmish.WPF.Tests/BindingVmHelpersTests.fs b/src/Elmish.WPF.Tests/BindingVmHelpersTests.fs index 91e96e7b..8e1d97a3 100644 --- a/src/Elmish.WPF.Tests/BindingVmHelpersTests.fs +++ b/src/Elmish.WPF.Tests/BindingVmHelpersTests.fs @@ -12,125 +12,138 @@ open Elmish.WPF.BindingVmHelpers let name = "name" -let noGetSelectedItemCall _ = failwith "Should not call get selected item" + +let noGetSelectedItemCall _ = + failwith "Should not call get selected item" module Initialize = - [] - let ``Initialize doesn't call getCurrentModel`` () = - let binding = - BindingData.OneWay.id - |> BindingData.addValidation List.singleton + [] + let ``Initialize doesn't call getCurrentModel`` () = + let binding = + BindingData.OneWay.id + |> BindingData.addValidation List.singleton - let vmBinding = - Initialize(LoggingViewModelArgs.none, name, noGetSelectedItemCall) - .Recursive("", ignore, (fun _ -> failwith "Should not call getCurrentModel on initialize"), binding) + let vmBinding = + Initialize(LoggingViewModelArgs.none, name, noGetSelectedItemCall) + .Recursive("", ignore, (fun _ -> failwith "Should not call getCurrentModel on initialize"), binding) - test <@ vmBinding.IsSome @> + test <@ vmBinding.IsSome @> module Get = - let check<'a when 'a : equality> (g: Gen<'a>) = - Property.check <| property { - let! expectedModel = g + let check<'a when 'a: equality> (g: Gen<'a>) = + Property.check + <| property { + let! expectedModel = g + + let binding = BindingData.OneWay.id - let binding = - BindingData.OneWay.id - let vmBinding = - Initialize(LoggingViewModelArgs.none, name, noGetSelectedItemCall) - .Recursive(expectedModel, ignore, (fun () -> expectedModel), binding) - .Value + let vmBinding = + Initialize(LoggingViewModelArgs.none, name, noGetSelectedItemCall) + .Recursive(expectedModel, ignore, (fun () -> expectedModel), binding) + .Value - let actualModel = Get(name).Recursive(expectedModel, vmBinding) + let actualModel = Get(name).Recursive(expectedModel, vmBinding) - test <@ actualModel = Ok expectedModel @> - } + test <@ actualModel = Ok expectedModel @> + } - [] - let ``get succeeds for various types`` () = - GenX.auto |> check - GenX.auto |> check - GenX.auto |> check - GenX.auto |> GenX.withNull |> check - GenX.auto |> GenX.withNull |> check + [] + let ``get succeeds for various types`` () = + GenX.auto |> check + GenX.auto |> check + GenX.auto |> check + GenX.auto |> GenX.withNull |> check + GenX.auto |> GenX.withNull |> check - [] - let ``should return error on bad typing`` () = - let binding = Binding.SubModel.opt (fun () -> []) >> Binding.mapModel (fun () -> None) <| "" + [] + let ``should return error on bad typing`` () = + let binding = + Binding.SubModel.opt (fun () -> []) >> Binding.mapModel (fun () -> None) <| "" - let dispatch msg = - failwith $"Should not dispatch, got {msg}" + let dispatch msg = + failwith $"Should not dispatch, got {msg}" - let vmBinding = - Initialize(LoggingViewModelArgs.none, "Nothing", (fun _ -> failwith "Should not call get selected item")) - .Recursive((), dispatch, (fun () -> ()), binding.Data) - |> Option.defaultWith (fun () -> failwith $"Could not create VmBinding after passing in BindingData: {binding}") + let vmBinding = + Initialize(LoggingViewModelArgs.none, "Nothing", (fun _ -> failwith "Should not call get selected item")) + .Recursive((), dispatch, (fun () -> ()), binding.Data) + |> Option.defaultWith (fun () -> + failwith $"Could not create VmBinding after passing in BindingData: {binding}") - let vmBinding2 = vmBinding |> MapOutputType.unboxVm + let vmBinding2 = vmBinding |> MapOutputType.unboxVm - let getResult: Result = Get("Nothing").Recursive((), vmBinding2) + let getResult: Result = Get("Nothing").Recursive((), vmBinding2) - test <@ match getResult with | Error (GetError.ToNullError (ValueOption.ToNullError.ValueCannotBeNull _)) -> true | _ -> false @> + test + <@ + match getResult with + | Error(GetError.ToNullError(ValueOption.ToNullError.ValueCannotBeNull _)) -> true + | _ -> false + @> module Set = - let check<'a when 'a : equality> (g: Gen<'a>) = - Property.check <| property { - let! initialModel = g - let! newModel = g |> GenX.notEqualTo initialModel - - let model = ref initialModel - let dispatch msg = model.Value <- msg - let binding = - BindingData.TwoWay.id - - let vmBinding = - Initialize(LoggingViewModelArgs.none, name, noGetSelectedItemCall) - .Recursive(initialModel, dispatch, (fun () -> model.Value), binding) - .Value - - test <@ Set(newModel).Recursive(model.Value, vmBinding) @> - test <@ model.Value = newModel @> - } - - [] - let ``set successful for various types`` () = - GenX.auto |> check - GenX.auto |> check - GenX.auto |> check - GenX.auto |> GenX.withNull |> check - GenX.auto |> GenX.withNull |> check + let check<'a when 'a: equality> (g: Gen<'a>) = + Property.check + <| property { + let! initialModel = g + let! newModel = g |> GenX.notEqualTo initialModel + + let model = ref initialModel + let dispatch msg = model.Value <- msg + let binding = BindingData.TwoWay.id + + let vmBinding = + Initialize(LoggingViewModelArgs.none, name, noGetSelectedItemCall) + .Recursive(initialModel, dispatch, (fun () -> model.Value), binding) + .Value + + test <@ Set(newModel).Recursive(model.Value, vmBinding) @> + test <@ model.Value = newModel @> + } + + [] + let ``set successful for various types`` () = + GenX.auto |> check + GenX.auto |> check + GenX.auto |> check + GenX.auto |> GenX.withNull |> check + GenX.auto |> GenX.withNull |> check module Update = - let check<'a when 'a : equality> (g: Gen<'a>) = - Property.check <| property { - let! initialModel = g - let! newModel = g |> GenX.notEqualTo initialModel - - let model = ref initialModel - let dispatch msg = failwith $"Should not dispatch message {msg}" - let binding = - BindingData.TwoWay.id - let vmBinding = - Initialize(LoggingViewModelArgs.none, name, noGetSelectedItemCall) - .Recursive(initialModel, dispatch, (fun () -> model.Value), binding) - .Value - - let updateResult = - Update(LoggingViewModelArgs.none, name) - .Recursive(initialModel, newModel, vmBinding) - - test <@ updateResult |> List.length = 1 @> - } - - [] - let ``update successful for various types`` () = - GenX.auto |> check - GenX.auto |> check - GenX.auto |> check - GenX.auto |> GenX.withNull |> check - GenX.auto |> GenX.withNull |> check + let check<'a when 'a: equality> (g: Gen<'a>) = + Property.check + <| property { + let! initialModel = g + let! newModel = g |> GenX.notEqualTo initialModel + + let model = ref initialModel + + let dispatch msg = + failwith $"Should not dispatch message {msg}" + + let binding = BindingData.TwoWay.id + + let vmBinding = + Initialize(LoggingViewModelArgs.none, name, noGetSelectedItemCall) + .Recursive(initialModel, dispatch, (fun () -> model.Value), binding) + .Value + + let updateResult = + Update(LoggingViewModelArgs.none, name).Recursive(initialModel, newModel, vmBinding) + + test <@ updateResult |> List.length = 1 @> + } + + [] + let ``update successful for various types`` () = + GenX.auto |> check + GenX.auto |> check + GenX.auto |> check + GenX.auto |> GenX.withNull |> check + GenX.auto |> GenX.withNull |> check \ No newline at end of file diff --git a/src/Elmish.WPF.Tests/DynamicViewModelTests.fs b/src/Elmish.WPF.Tests/DynamicViewModelTests.fs index e54888af..d9a5e016 100644 --- a/src/Elmish.WPF.Tests/DynamicViewModelTests.fs +++ b/src/Elmish.WPF.Tests/DynamicViewModelTests.fs @@ -19,83 +19,77 @@ open Elmish.WPF [] module Extensions = - type DynamicViewModel<'model, 'msg> with + type DynamicViewModel<'model, 'msg> with - member internal this.Get propName = - (?) this propName + member internal this.Get propName = (?) this propName - member internal this.Set propName value = - (?<-) this propName value + member internal this.Set propName value = (?<-) this propName value type internal TestVm<'model, 'msg>(model, bindings) as this = - inherit DynamicViewModel<'model, 'msg>({ initialModel = model; dispatch = (fun x -> this.Dispatch x); loggingArgs = LoggingViewModelArgs.none }, bindings) + inherit + DynamicViewModel<'model, 'msg>( + { initialModel = model + dispatch = (fun x -> this.Dispatch x) + loggingArgs = LoggingViewModelArgs.none }, + bindings + ) + + let pcTriggers = ConcurrentDictionary() + let ecTriggers = ConcurrentDictionary() - let pcTriggers = ConcurrentDictionary() - let ecTriggers = ConcurrentDictionary() - let ccTriggers = ConcurrentDictionary() - let cecTriggers = ConcurrentDictionary() - let dispatchMsgs = ResizeArray<'msg> () + let ccTriggers = + ConcurrentDictionary() + let cecTriggers = ConcurrentDictionary() + let dispatchMsgs = ResizeArray<'msg>() - do - (this :> INotifyPropertyChanged).PropertyChanged.Add (fun e -> - pcTriggers.AddOrUpdate(e.PropertyName, 1, (fun _ count -> count + 1)) |> ignore - ) - (this :> INotifyDataErrorInfo).ErrorsChanged.Add (fun e -> - ecTriggers.AddOrUpdate(e.PropertyName, 1, (fun _ count -> count + 1)) |> ignore - ) + do + (this :> INotifyPropertyChanged) + .PropertyChanged.Add(fun e -> + pcTriggers.AddOrUpdate(e.PropertyName, 1, (fun _ count -> count + 1)) |> ignore) - new(model, binding) = TestVm(model, [binding]) + (this :> INotifyDataErrorInfo) + .ErrorsChanged.Add(fun e -> ecTriggers.AddOrUpdate(e.PropertyName, 1, (fun _ count -> count + 1)) |> ignore) - member _.UpdateModel(m) = IViewModel.updateModel(this, m) + new(model, binding) = TestVm(model, [ binding ]) - member private __.Dispatch x = - dispatchMsgs.Add x + member _.UpdateModel(m) = IViewModel.updateModel (this, m) - member __.NumPcTriggersFor propName = - pcTriggers.TryGetValue propName |> snd + member private __.Dispatch x = dispatchMsgs.Add x - member __.NumEcTriggersFor propName = - ecTriggers.TryGetValue propName |> snd + member __.NumPcTriggersFor propName = pcTriggers.TryGetValue propName |> snd - member __.NumCcTriggersFor propName = - ccTriggers.GetOrAdd(propName, []).Length + member __.NumEcTriggersFor propName = ecTriggers.TryGetValue propName |> snd - member __.NumCecTriggersFor propName = - cecTriggers.TryGetValue propName |> snd + member __.NumCcTriggersFor propName = + ccTriggers.GetOrAdd(propName, []).Length - member __.Dispatches = - dispatchMsgs |> Seq.toList + member __.NumCecTriggersFor propName = cecTriggers.TryGetValue propName |> snd - member __.CcTriggersFor propName = - ccTriggers.TryGetValue propName |> snd |> Seq.toList + member __.Dispatches = dispatchMsgs |> Seq.toList - /// Starts tracking CollectionChanged triggers for the specified prop. - /// Will cause the property to be retrieved. - member this.TrackCcTriggersFor propName = - try - (this.Get propName : INotifyCollectionChanged).CollectionChanged.Add (fun e -> - ccTriggers.AddOrUpdate( - propName, - [e], - (fun _ me -> e :: me)) |> ignore - ) - with _ -> - (this.Get propName |> unbox).CollectionChanged.Add (fun e -> - ccTriggers.AddOrUpdate( - propName, - [e], - (fun _ me -> e :: me)) |> ignore - ) + member __.CcTriggersFor propName = + ccTriggers.TryGetValue propName |> snd |> Seq.toList - /// Starts tracking CanExecuteChanged triggers for the specified prop. - /// Will cause the property to be retrieved. - member this.TrackCecTriggersFor propName = - (this.Get propName : ICommand).CanExecuteChanged.Add (fun _ -> - cecTriggers.AddOrUpdate(propName, 1, (fun _ count -> count + 1)) |> ignore - ) + /// Starts tracking CollectionChanged triggers for the specified prop. + /// Will cause the property to be retrieved. + member this.TrackCcTriggersFor propName = + try + (this.Get propName: INotifyCollectionChanged) + .CollectionChanged.Add(fun e -> + ccTriggers.AddOrUpdate(propName, [ e ], (fun _ me -> e :: me)) |> ignore) + with _ -> + (this.Get propName |> unbox) + .CollectionChanged.Add(fun e -> + ccTriggers.AddOrUpdate(propName, [ e ], (fun _ me -> e :: me)) |> ignore) + + /// Starts tracking CanExecuteChanged triggers for the specified prop. + /// Will cause the property to be retrieved. + member this.TrackCecTriggersFor propName = + (this.Get propName: ICommand) + .CanExecuteChanged.Add(fun _ -> cecTriggers.AddOrUpdate(propName, 1, (fun _ count -> count + 1)) |> ignore) @@ -103,1548 +97,1714 @@ type internal TestVm<'model, 'msg>(model, bindings) as this = module Helpers = - let internal oneWay x = x |> Binding.oneWay - let internal oneWayLazy x = x |> Func3.curry Binding.oneWayLazy - let internal oneWaySeqLazy x = x |> Func5.curry Binding.oneWaySeqLazy - let internal twoWay x = x |> Func2.curry Binding.twoWay - let internal twoWayValidate - name - (get: 'model -> 'a) - (set: 'a -> 'model -> 'msg) - (validate: 'model -> string voption) = - Binding.twoWayValidate (get, set, validate) name - + let internal oneWay x = x |> Binding.oneWay + let internal oneWayLazy x = x |> Func3.curry Binding.oneWayLazy + let internal oneWaySeqLazy x = x |> Func5.curry Binding.oneWaySeqLazy + let internal twoWay x = x |> Func2.curry Binding.twoWay - let internal cmd x = x |> Binding.Cmd.create + let internal twoWayValidate + name + (get: 'model -> 'a) + (set: 'a -> 'model -> 'msg) + (validate: 'model -> string voption) + = + Binding.twoWayValidate (get, set, validate) name + let internal cmd x = x |> Binding.Cmd.create - let internal cmdParam - name - (exec: 'a -> 'model -> 'msg voption) - (canExec: 'a -> 'model -> bool) - (autoRequery: bool) = - ({ Exec = unbox >> exec - CanExec = unbox >> canExec - AutoRequery = autoRequery } - |> CmdData - |> BaseBindingData - |> createBinding) name - let internal subModel - name - (getModel: 'model -> 'subModel voption) - (toMsg: 'subMsg -> 'msg) - (bindings: Binding<'subModel, 'subMsg> list) - (sticky: bool) = - Binding.subModelOpt(getModel, snd, toMsg, (fun () -> bindings), sticky) name + let internal cmdParam + name + (exec: 'a -> 'model -> 'msg voption) + (canExec: 'a -> 'model -> bool) + (autoRequery: bool) + = + ({ Exec = unbox >> exec + CanExec = unbox >> canExec + AutoRequery = autoRequery } + |> CmdData + |> BaseBindingData + |> createBinding) + name + + + let internal subModel + name + (getModel: 'model -> 'subModel voption) + (toMsg: 'subMsg -> 'msg) + (bindings: Binding<'subModel, 'subMsg> list) + (sticky: bool) + = + Binding.subModelOpt (getModel, snd, toMsg, (fun () -> bindings), sticky) name - let internal subModelSeq - name - (getModels: 'model -> 'subModel list) - (getId: 'subModel -> 'id) - (toMsg: 'id * 'subMsg -> 'msg) - (bindings: Binding<'subModel, 'subMsg> list) = - name - |> Binding.subModelSeq (getBindings = (fun () -> bindings), getId = getId) - |> Binding.mapModel (fun m -> upcast getModels m) - |> Binding.mapMsg toMsg + let internal subModelSeq + name + (getModels: 'model -> 'subModel list) + (getId: 'subModel -> 'id) + (toMsg: 'id * 'subMsg -> 'msg) + (bindings: Binding<'subModel, 'subMsg> list) + = + name + |> Binding.subModelSeq (getBindings = (fun () -> bindings), getId = getId) + |> Binding.mapModel (fun m -> upcast getModels m) + |> Binding.mapMsg toMsg - let internal subModelSelectedItem - name - subModelSeqBindingName - (get: 'model -> 'id voption) - (set: 'id voption -> 'model -> 'msg) = - Binding.subModelSelectedItem (subModelSeqBindingName, get, set) name + let internal subModelSelectedItem + name + subModelSeqBindingName + (get: 'model -> 'id voption) + (set: 'id voption -> 'model -> 'msg) + = + Binding.subModelSelectedItem (subModelSeqBindingName, get, set) name module OneWay = - [] - let ``when retrieved, should always return the value returned by get`` () = - Property.check <| property { - let! name = GenX.auto - let! m1 = GenX.auto - let! m2 = GenX.auto + [] + let ``when retrieved, should always return the value returned by get`` () = + Property.check + <| property { + let! name = GenX.auto + let! m1 = GenX.auto + let! m2 = GenX.auto - let get = string + let get = string - let binding = oneWay get name - let vm = TestVm(m1, binding) + let binding = oneWay get name + let vm = TestVm(m1, binding) - test <@ vm.Get name = get m1 @> + test <@ vm.Get name = get m1 @> - vm.UpdateModel m2 + vm.UpdateModel m2 - test <@ vm.Get name = get m2 @> - } + test <@ vm.Get name = get m2 @> + } - [] - let ``when model is updated, should trigger PC once iff the return value of get changes`` () = - Property.check <| property { - let! name = GenX.auto - let! m1 = GenX.auto - let! m2 = GenX.auto + [] + let ``when model is updated, should trigger PC once iff the return value of get changes`` () = + Property.check + <| property { + let! name = GenX.auto + let! m1 = GenX.auto + let! m2 = GenX.auto - let get = string + let get = string - let binding = oneWay get name - let vm = TestVm(m1, binding) + let binding = oneWay get name + let vm = TestVm(m1, binding) - vm.UpdateModel m2 - test <@ vm.NumPcTriggersFor name = if get m1 = get m2 then 0 else 1 @> - } + vm.UpdateModel m2 + test <@ vm.NumPcTriggersFor name = if get m1 = get m2 then 0 else 1 @> + } - [] - let ``on model increment, sticky-to-even binding returns even number`` () = - let isEven x = x % 2 = 0 + [] + let ``on model increment, sticky-to-even binding returns even number`` () = + let isEven x = x % 2 = 0 - let returnEven a = - function - | b when isEven b -> b - | _ -> a + let returnEven a = + function + | b when isEven b -> b + | _ -> a - Property.check <| property { - let! name = GenX.auto - let! m = GenX.auto + Property.check + <| property { + let! name = GenX.auto + let! m = GenX.auto - let binding = oneWay id name |> Binding.addSticky isEven - let vm = TestVm(m, binding) + let binding = oneWay id name |> Binding.addSticky isEven + let vm = TestVm(m, binding) - vm.UpdateModel (m + 1) - test <@ vm.Get name = returnEven m (m + 1) @> - } + vm.UpdateModel(m + 1) + test <@ vm.Get name = returnEven m (m + 1) @> + } - [] - let ``when model updated, event is not called before view model property is updated`` () = - Property.check <| property { - let! name = GenX.auto - let! m1 = GenX.auto - let! m2 = GenX.auto |> GenX.notEqualTo m1 + [] + let ``when model updated, event is not called before view model property is updated`` () = + Property.check + <| property { + let! name = GenX.auto + let! m1 = GenX.auto + let! m2 = GenX.auto |> GenX.notEqualTo m1 - let get = string + let get = string - let binding = oneWay get name - let vm = TestVm(m1, binding) - let mutable eventFired = false + let binding = oneWay get name + let vm = TestVm(m1, binding) + let mutable eventFired = false - (vm :> INotifyPropertyChanged).PropertyChanged.Add (fun e -> - test <@ e.PropertyName = name @> - test <@ vm.Get name = get m2 @> - eventFired <- true - ) + (vm :> INotifyPropertyChanged) + .PropertyChanged.Add(fun e -> + test <@ e.PropertyName = name @> + test <@ vm.Get name = get m2 @> + eventFired <- true) - vm.UpdateModel m2 + vm.UpdateModel m2 - test <@ eventFired @> - } + test <@ eventFired @> + } module OneWayLazy = - [] - let ``when retrieved initially, should return the value returned by map`` () = - Property.check <| property { - let! name = GenX.auto - let! m = GenX.auto - - let get = string - let equals = (=) - let map = String.length - - let binding = oneWayLazy get equals map name - let vm = TestVm(m, binding) + [] + let ``when retrieved initially, should return the value returned by map`` () = + Property.check + <| property { + let! name = GenX.auto + let! m = GenX.auto - test <@ vm.Get name = (m |> get |> map) @> - } + let get = string + let equals = (=) + let map = String.length + let binding = oneWayLazy get equals map name + let vm = TestVm(m, binding) - [] - let ``when retrieved after update and equals returns false, should return the value returned by map`` () = - Property.check <| property { - let! name = GenX.auto - let! m1 = GenX.auto - let! m2 = GenX.auto + test <@ vm.Get name = (m |> get |> map) @> + } - let get = string - let equals _ _ = false - let map = String.length - let binding = oneWayLazy get equals map name - let vm = TestVm(m1, binding) - vm.UpdateModel m2 + [] + let ``when retrieved after update and equals returns false, should return the value returned by map`` () = + Property.check + <| property { + let! name = GenX.auto + let! m1 = GenX.auto + let! m2 = GenX.auto - test <@ vm.Get name = (m2 |> get |> map) @> - } + let get = string + let equals _ _ = false + let map = String.length + let binding = oneWayLazy get equals map name + let vm = TestVm(m1, binding) + vm.UpdateModel m2 - [] - let ``when retrieved after update and equals returns true, should return the previous value returned by map`` () = - Property.check <| property { - let! name = GenX.auto - let! m1 = GenX.auto - let! m2 = GenX.auto + test <@ vm.Get name = (m2 |> get |> map) @> + } - let get = string - let equals _ _ = true - let map = String.length - let binding = oneWayLazy get equals map name - let vm = TestVm(m1, binding) - vm.Get name |> ignore // populate cache - vm.UpdateModel m2 + [] + let ``when retrieved after update and equals returns true, should return the previous value returned by map`` () = + Property.check + <| property { + let! name = GenX.auto + let! m1 = GenX.auto + let! m2 = GenX.auto + + let get = string + let equals _ _ = true + let map = String.length + + let binding = oneWayLazy get equals map name + let vm = TestVm(m1, binding) + vm.Get name |> ignore // populate cache + vm.UpdateModel m2 - test <@ vm.Get name = (m1 |> get |> map) @> - } + test <@ vm.Get name = (m1 |> get |> map) @> + } - [] - let ``when retrieved, updated, and retrieved again, should call map once after the update iff equals returns false`` () = - Property.check <| property { - let! name = GenX.auto - let! m1 = GenX.auto - let! m2 = GenX.auto - let! eq = Gen.bool + [] + let ``when retrieved, updated, and retrieved again, should call map once after the update iff equals returns false`` + () + = + Property.check + <| property { + let! name = GenX.auto + let! m1 = GenX.auto + let! m2 = GenX.auto + let! eq = Gen.bool - let get = string - let equals _ _ = eq - let map = InvokeTester String.length + let get = string + let equals _ _ = eq + let map = InvokeTester String.length - let binding = oneWayLazy get equals map.Fn name - let vm = TestVm(m1, binding) + let binding = oneWayLazy get equals map.Fn name + let vm = TestVm(m1, binding) - vm.Get name |> ignore - vm.UpdateModel m2 - map.Reset () - vm.Get name |> ignore + vm.Get name |> ignore + vm.UpdateModel m2 + map.Reset() + vm.Get name |> ignore - test <@ map.Count = if eq then 0 else 1 @> - } + test <@ map.Count = if eq then 0 else 1 @> + } - [] - let ``map should never be called during model update`` () = - Property.check <| property { - let! name = GenX.auto - let! m1 = GenX.auto - let! m2 = GenX.auto + [] + let ``map should never be called during model update`` () = + Property.check + <| property { + let! name = GenX.auto + let! m1 = GenX.auto + let! m2 = GenX.auto - let get = string - let equals = (=) - let map = InvokeTester String.length + let get = string + let equals = (=) + let map = InvokeTester String.length - let binding = oneWayLazy get equals map.Fn name - let vm = TestVm(m1, binding) + let binding = oneWayLazy get equals map.Fn name + let vm = TestVm(m1, binding) - vm.UpdateModel m2 + vm.UpdateModel m2 - test <@ map.Count = 0 @> - } + test <@ map.Count = 0 @> + } - [] - let ``when retrieved several times between updates, map is called at most once`` () = - Property.check <| property { - let! name = GenX.auto - let! m1 = GenX.auto - let! m2 = GenX.auto + [] + let ``when retrieved several times between updates, map is called at most once`` () = + Property.check + <| property { + let! name = GenX.auto + let! m1 = GenX.auto + let! m2 = GenX.auto - let get = string - let equals = (=) - let map = InvokeTester String.length + let get = string + let equals = (=) + let map = InvokeTester String.length - let binding = oneWayLazy get equals map.Fn name - let vm = TestVm(m1, binding) + let binding = oneWayLazy get equals map.Fn name + let vm = TestVm(m1, binding) - vm.Get name |> ignore - vm.Get name |> ignore - test <@ map.Count <= 1 @> + vm.Get name |> ignore + vm.Get name |> ignore + test <@ map.Count <= 1 @> - map.Reset () - vm.UpdateModel m2 - vm.Get name |> ignore - vm.Get name |> ignore - test <@ map.Count <= 1 @> - } + map.Reset() + vm.UpdateModel m2 + vm.Get name |> ignore + vm.Get name |> ignore + test <@ map.Count <= 1 @> + } - [] - let ``when model is updated, should trigger PC once iff equals is false`` () = - Property.check <| property { - let! name = GenX.auto - let! m1 = GenX.auto - let! m2 = GenX.auto - let! eq = Gen.bool + [] + let ``when model is updated, should trigger PC once iff equals is false`` () = + Property.check + <| property { + let! name = GenX.auto + let! m1 = GenX.auto + let! m2 = GenX.auto + let! eq = Gen.bool - let get = string - let equals _ _ = eq - let map = String.length + let get = string + let equals _ _ = eq + let map = String.length - let binding = oneWayLazy get equals map name - let vm = TestVm(m1, binding) - vm.UpdateModel m2 + let binding = oneWayLazy get equals map name + let vm = TestVm(m1, binding) + vm.UpdateModel m2 - test <@ vm.NumPcTriggersFor name = if not eq then 1 else 0 @> - } + test <@ vm.NumPcTriggersFor name = if not eq then 1 else 0 @> + } module OneWaySeqLazy = - let private testObservableCollectionContainsExpectedItems (vm: DynamicViewModel<_, _>) name expected = - let actual = (vm.Get name : ObservableCollection<_>) |> Seq.toList - test <@ expected = actual @> + let private testObservableCollectionContainsExpectedItems (vm: DynamicViewModel<_, _>) name expected = + let actual = (vm.Get name: ObservableCollection<_>) |> Seq.toList + test <@ expected = actual @> - [] - let ``when retrieved initially, should return an ObservableCollection with the values returned by map`` () = - Property.check <| property { - let! name = GenX.auto - let! m = GenX.auto + [] + let ``when retrieved initially, should return an ObservableCollection with the values returned by map`` () = + Property.check + <| property { + let! name = GenX.auto + let! m = GenX.auto - let get = id - let equals = (=) - let map = id - let itemEquals = (=) - let getId = id + let get = id + let equals = (=) + let map = id + let itemEquals = (=) + let getId = id - let binding = oneWaySeqLazy get equals map itemEquals getId name - let vm = TestVm(m, binding) + let binding = oneWaySeqLazy get equals map itemEquals getId name + let vm = TestVm(m, binding) - testObservableCollectionContainsExpectedItems vm name (m |> get |> map) - } + testObservableCollectionContainsExpectedItems vm name (m |> get |> map) + } - [] - let ``given equals returns false, when retrieved after update, should return an ObservableCollection with the new values returned by map`` () = - Property.check <| property { - let! name = GenX.auto - let! m1 = GenX.auto - let! m2 = GenX.auto + [] + let ``given equals returns false, when retrieved after update, should return an ObservableCollection with the new values returned by map`` + () + = + Property.check + <| property { + let! name = GenX.auto + let! m1 = GenX.auto + let! m2 = GenX.auto - let get = id - let equals _ _ = false - let map = id - let itemEquals = (=) - let getId = id + let get = id + let equals _ _ = false + let map = id + let itemEquals = (=) + let getId = id - let binding = oneWaySeqLazy get equals map itemEquals getId name - let vm = TestVm(m1, binding) + let binding = oneWaySeqLazy get equals map itemEquals getId name + let vm = TestVm(m1, binding) - vm.UpdateModel m2 + vm.UpdateModel m2 - testObservableCollectionContainsExpectedItems vm name (m2 |> get |> map) - } + testObservableCollectionContainsExpectedItems vm name (m2 |> get |> map) + } - [] - let ``given equals returns true, when retrieved after update, should return an ObservableCollection with the previous values returned by map`` () = - Property.check <| property { - let! name = GenX.auto - let! m1 = GenX.auto - let! m2 = GenX.auto + [] + let ``given equals returns true, when retrieved after update, should return an ObservableCollection with the previous values returned by map`` + () + = + Property.check + <| property { + let! name = GenX.auto + let! m1 = GenX.auto + let! m2 = GenX.auto - let get = id - let equals _ _ = true - let map = id - let itemEquals = (=) - let getId = id + let get = id + let equals _ _ = true + let map = id + let itemEquals = (=) + let getId = id - let binding = oneWaySeqLazy get equals map itemEquals getId name - let vm = TestVm(m1, binding) + let binding = oneWaySeqLazy get equals map itemEquals getId name + let vm = TestVm(m1, binding) - vm.UpdateModel m2 + vm.UpdateModel m2 - testObservableCollectionContainsExpectedItems vm name (m1 |> get |> map) - } + testObservableCollectionContainsExpectedItems vm name (m1 |> get |> map) + } - [] - let ``during VM instantiation, get should be called at most once`` () = - Property.check <| property { - let! name = GenX.auto - let! m1 = GenX.auto - let! eq = Gen.bool + [] + let ``during VM instantiation, get should be called at most once`` () = + Property.check + <| property { + let! name = GenX.auto + let! m1 = GenX.auto + let! eq = Gen.bool - let get = InvokeTester id - let equals _ _ = eq - let map = id - let itemEquals = (=) - let getId = id + let get = InvokeTester id + let equals _ _ = eq + let map = id + let itemEquals = (=) + let getId = id - let binding = oneWaySeqLazy get.Fn equals map itemEquals getId name - TestVm(m1, binding) |> ignore + let binding = oneWaySeqLazy get.Fn equals map itemEquals getId name + TestVm(m1, binding) |> ignore - test <@ get.Count <= 1 @> - } + test <@ get.Count <= 1 @> + } - [] - let ``during VM instantiation, map should have be called at most once`` () = - Property.check <| property { - let! name = GenX.auto - let! m1 = GenX.auto - let! eq = Gen.bool + [] + let ``during VM instantiation, map should have be called at most once`` () = + Property.check + <| property { + let! name = GenX.auto + let! m1 = GenX.auto + let! eq = Gen.bool + + let get = id + let equals _ _ = eq + let map = InvokeTester id + let itemEquals = (=) + let getId = id + + let binding = oneWaySeqLazy get equals map.Fn itemEquals getId name + TestVm(m1, binding) |> ignore - let get = id - let equals _ _ = eq - let map = InvokeTester id - let itemEquals = (=) - let getId = id + test <@ map.Count <= 1 @> + } - let binding = oneWaySeqLazy get equals map.Fn itemEquals getId name - TestVm(m1, binding) |> ignore - test <@ map.Count <= 1 @> - } + [] + let ``given equals returns true, during model update, map should be called at most once`` () = + Property.check + <| property { + let! name = GenX.auto + let! m1 = GenX.auto + let! m2 = GenX.auto + + let get = id + let equals _ _ = true + let map = InvokeTester id + let itemEquals = (=) + let getId = id + + let binding = oneWaySeqLazy get equals map.Fn itemEquals getId name + let vm = TestVm(m1, binding) + map.Reset() + vm.UpdateModel m2 - [] - let ``given equals returns true, during model update, map should be called at most once`` () = - Property.check <| property { - let! name = GenX.auto - let! m1 = GenX.auto - let! m2 = GenX.auto + test <@ map.Count = 0 @> + } - let get = id - let equals _ _ = true - let map = InvokeTester id - let itemEquals = (=) - let getId = id - let binding = oneWaySeqLazy get equals map.Fn itemEquals getId name - let vm = TestVm(m1, binding) + [] + let ``when equals returns false, during model update, map should be called at most once`` () = + Property.check + <| property { + let! name = GenX.auto + let! m1 = GenX.auto + let! m2 = GenX.auto + + let get = id + let equals _ _ = false + let map = InvokeTester id + let itemEquals = (=) + let getId = id + + let binding = oneWaySeqLazy get equals map.Fn itemEquals getId name + let vm = TestVm(m1, binding) + + map.Reset() + vm.UpdateModel m2 - map.Reset () - vm.UpdateModel m2 + test <@ map.Count <= 1 @> + } - test <@ map.Count = 0 @> - } + [] + let ``during model update, get should be called at most twice`` () = // once on current model and once on new model + Property.check + <| property { + let! name = GenX.auto + let! m1 = GenX.auto + let! m2 = GenX.auto + let! eq = Gen.bool - [] - let ``when equals returns false, during model update, map should be called at most once`` () = - Property.check <| property { - let! name = GenX.auto - let! m1 = GenX.auto - let! m2 = GenX.auto + let get = InvokeTester id + let equals _ _ = eq + let map = id + let itemEquals = (=) + let getId = id + + let binding = oneWaySeqLazy get.Fn equals map itemEquals getId name + let vm = TestVm(m1, binding) - let get = id - let equals _ _ = false - let map = InvokeTester id - let itemEquals = (=) - let getId = id + get.Reset() + vm.UpdateModel m2 - let binding = oneWaySeqLazy get equals map.Fn itemEquals getId name - let vm = TestVm(m1, binding) + test <@ get.Count <= 2 @> + } - map.Reset () - vm.UpdateModel m2 - test <@ map.Count <= 1 @> - } + [] + let ``when retrieved several times after VM initialization, map is called at most once`` () = + Property.check + <| property { + let! name = GenX.auto + let! m1 = GenX.auto + + let get = id + let equals = (=) + let map = InvokeTester id + let itemEquals = (=) + let getId = id + + let binding = oneWaySeqLazy get equals map.Fn itemEquals getId name + let vm = TestVm(m1, binding) + + vm.Get name |> ignore + vm.Get name |> ignore + test <@ map.Count <= 1 @> + } - [] - let ``during model update, get should be called at most twice`` () = // once on current model and once on new model - Property.check <| property { - let! name = GenX.auto - let! m1 = GenX.auto - let! m2 = GenX.auto - let! eq = Gen.bool - let get = InvokeTester id - let equals _ _ = eq - let map = id - let itemEquals = (=) - let getId = id + [] + let ``when retrieved several times after update, map is called at most once`` () = + Property.check + <| property { + let! name = GenX.auto + let! m1 = GenX.auto + let! m2 = GenX.auto - let binding = oneWaySeqLazy get.Fn equals map itemEquals getId name - let vm = TestVm(m1, binding) + let get = id + let equals = (=) + let map = InvokeTester id + let itemEquals = (=) + let getId = id - get.Reset () - vm.UpdateModel m2 + let binding = oneWaySeqLazy get equals map.Fn itemEquals getId name + let vm = TestVm(m1, binding) - test <@ get.Count <= 2 @> - } + map.Reset() + vm.UpdateModel m2 + vm.Get name |> ignore + vm.Get name |> ignore - [] - let ``when retrieved several times after VM initialization, map is called at most once`` () = - Property.check <| property { - let! name = GenX.auto - let! m1 = GenX.auto + test <@ map.Count <= 1 @> + } - let get = id - let equals = (=) - let map = InvokeTester id - let itemEquals = (=) - let getId = id - let binding = oneWaySeqLazy get equals map.Fn itemEquals getId name - let vm = TestVm(m1, binding) + [] + let ``for any behavior of equals or itemEquals, when model is updated, should never trigger PC`` () = // because this binding should only trigger CC + Property.check + <| property { + let! name = GenX.auto + let! m1 = GenX.auto + let! m2 = GenX.auto + let! eq = Gen.bool + let! itemEq = Gen.bool - vm.Get name |> ignore - vm.Get name |> ignore + let get = id + let equals _ _ = eq + let map = InvokeTester id + let itemEquals _ _ = itemEq + let getId = id - test <@ map.Count <= 1 @> - } + let binding = oneWaySeqLazy get equals map.Fn itemEquals getId name + let vm = TestVm(m1, binding) + vm.UpdateModel m2 - [] - let ``when retrieved several times after update, map is called at most once`` () = - Property.check <| property { - let! name = GenX.auto - let! m1 = GenX.auto - let! m2 = GenX.auto + test <@ vm.NumPcTriggersFor name = 0 @> + } - let get = id - let equals = (=) - let map = InvokeTester id - let itemEquals = (=) - let getId = id - let binding = oneWaySeqLazy get equals map.Fn itemEquals getId name - let vm = TestVm(m1, binding) + [] + let ``given equals returns true, when model is updated, should never trigger CC`` () = + Property.check + <| property { + let! name = GenX.auto + let! m1 = GenX.auto + let! m2 = GenX.auto - map.Reset () - vm.UpdateModel m2 + let get = id + let equals _ _ = true + let map = id + let itemEquals = (=) + let getId = id - vm.Get name |> ignore - vm.Get name |> ignore + let binding = oneWaySeqLazy get equals map itemEquals getId name + let vm = TestVm(m1, binding) - test <@ map.Count <= 1 @> - } + vm.TrackCcTriggersFor name + vm.UpdateModel m2 + test <@ vm.NumCcTriggersFor name = 0 @> + } - [] - let ``for any behavior of equals or itemEquals, when model is updated, should never trigger PC`` () = // because this binding should only trigger CC - Property.check <| property { - let! name = GenX.auto - let! m1 = GenX.auto - let! m2 = GenX.auto - let! eq = Gen.bool - let! itemEq = Gen.bool - let get = id - let equals _ _ = eq - let map = InvokeTester id - let itemEquals _ _ = itemEq - let getId = id + [] + let ``given equals returns false and itemEquals returns false, when model is updated, should contain expected items in collection`` + () + = + Property.check + <| property { + let! name = GenX.auto + let! m1 = Gen.guid |> Gen.list (Range.constant 1 50) + let! m2 = Gen.guid |> Gen.list (Range.constant 1 50) - let binding = oneWaySeqLazy get equals map.Fn itemEquals getId name - let vm = TestVm(m1, binding) + let get = id + let equals _ _ = false + let map = id + let itemEquals _ _ = false + let getId = id - vm.UpdateModel m2 + let binding = oneWaySeqLazy get equals map itemEquals getId name + let vm = TestVm(m1, binding) - test <@ vm.NumPcTriggersFor name = 0 @> - } + vm.UpdateModel m2 + testObservableCollectionContainsExpectedItems vm name m2 + } - [] - let ``given equals returns true, when model is updated, should never trigger CC`` () = - Property.check <| property { - let! name = GenX.auto - let! m1 = GenX.auto - let! m2 = GenX.auto - let get = id - let equals _ _ = true - let map = id - let itemEquals = (=) - let getId = id - let binding = oneWaySeqLazy get equals map itemEquals getId name - let vm = TestVm(m1, binding) +module TwoWay = - vm.TrackCcTriggersFor name - vm.UpdateModel m2 - test <@ vm.NumCcTriggersFor name = 0 @> - } + [] + let ``when retrieved, should always return the value returned by get`` () = + Property.check + <| property { + let! name = GenX.auto + let! m1 = GenX.auto + let! m2 = GenX.auto + let get = string + let set _ _ = () - [] - let ``given equals returns false and itemEquals returns false, when model is updated, should contain expected items in collection`` () = - Property.check <| property { - let! name = GenX.auto - let! m1 = Gen.guid |> Gen.list (Range.constant 1 50) - let! m2 = Gen.guid |> Gen.list (Range.constant 1 50) + let binding = twoWay get set name + let vm = TestVm(m1, binding) - let get = id - let equals _ _ = false - let map = id - let itemEquals _ _ = false - let getId = id + test <@ vm.Get name = get m1 @> - let binding = oneWaySeqLazy get equals map itemEquals getId name - let vm = TestVm(m1, binding) + vm.UpdateModel m2 - vm.UpdateModel m2 + test <@ vm.Get name = get m2 @> + } - testObservableCollectionContainsExpectedItems vm name m2 - } + [] + let ``when model is updated, should trigger PC once iff the return value of get changes`` () = + Property.check + <| property { + let! name = GenX.auto + let! m1 = GenX.auto + let! m2 = GenX.auto + let get = string + let set _ _ = () -module TwoWay = + let binding = twoWay get set name + let vm = TestVm(m1, binding) + vm.UpdateModel m2 + test <@ vm.NumPcTriggersFor name = if get m1 = get m2 then 0 else 1 @> + } - [] - let ``when retrieved, should always return the value returned by get`` () = - Property.check <| property { - let! name = GenX.auto - let! m1 = GenX.auto - let! m2 = GenX.auto - let get = string - let set _ _ = () + [] + let ``when set, should call dispatch once with the value returned by set`` () = + Property.check + <| property { + let! name = GenX.auto + let! m = GenX.auto + let! p = GenX.auto - let binding = twoWay get set name - let vm = TestVm(m1, binding) + let get = string + let set (p: string) (m: int) = string m + p - test <@ vm.Get name = get m1 @> + let binding = twoWay get set name + let vm = TestVm(m, binding) - vm.UpdateModel m2 + vm.Set name p - test <@ vm.Get name = get m2 @> - } + test <@ vm.Dispatches = [ set p m ] @> + } - [] - let ``when model is updated, should trigger PC once iff the return value of get changes`` () = - Property.check <| property { - let! name = GenX.auto - let! m1 = GenX.auto - let! m2 = GenX.auto - let get = string - let set _ _ = () +module TwoWayValidate = - let binding = twoWay get set name - let vm = TestVm(m1, binding) - vm.UpdateModel m2 - test <@ vm.NumPcTriggersFor name = if get m1 = get m2 then 0 else 1 @> - } + [] + let ``when retrieved, should always return the value returned by get`` () = + Property.check + <| property { + let! name = GenX.auto + let! m1 = GenX.auto + let! m2 = GenX.auto + let get = string + let set _ _ = () + let validate _ = ValueNone - [] - let ``when set, should call dispatch once with the value returned by set`` () = - Property.check <| property { - let! name = GenX.auto - let! m = GenX.auto - let! p = GenX.auto + let binding = twoWayValidate name get set validate + let vm = TestVm(m1, binding) - let get = string - let set (p: string) (m: int) = string m + p + test <@ vm.Get name = get m1 @> - let binding = twoWay get set name - let vm = TestVm(m, binding) + vm.UpdateModel m2 - vm.Set name p + test <@ vm.Get name = get m2 @> + } - test <@ vm.Dispatches = [set p m] @> - } + [] + let ``when model is updated, should trigger PC once iff the return value of get changes`` () = + Property.check + <| property { + let! name = GenX.auto + let! m1 = GenX.auto + let! m2 = GenX.auto + let get = string + let set _ _ = () + let validate _ = ValueNone -module TwoWayValidate = + let binding = twoWayValidate name get set validate + let vm = TestVm(m1, binding) + vm.UpdateModel m2 - [] - let ``when retrieved, should always return the value returned by get`` () = - Property.check <| property { - let! name = GenX.auto - let! m1 = GenX.auto - let! m2 = GenX.auto + test <@ vm.NumPcTriggersFor name = if get m1 = get m2 then 0 else 1 @> + } - let get = string - let set _ _ = () - let validate _ = ValueNone - let binding = twoWayValidate name get set validate - let vm = TestVm(m1, binding) + [] + let ``when set, should call dispatch once with the value returned by set`` () = + Property.check + <| property { + let! name = GenX.auto + let! m = GenX.auto + let! p = GenX.auto - test <@ vm.Get name = get m1 @> + let get = string + let set (p: string) (m: int) = string m + p + let validate _ = ValueNone - vm.UpdateModel m2 + let binding = twoWayValidate name get set validate + let vm = TestVm(m, binding) - test <@ vm.Get name = get m2 @> - } + vm.Set name p + test <@ vm.Dispatches = [ set p m ] @> + } - [] - let ``when model is updated, should trigger PC once iff the return value of get changes`` () = - Property.check <| property { - let! name = GenX.auto - let! m1 = GenX.auto - let! m2 = GenX.auto - let get = string - let set _ _ = () - let validate _ = ValueNone + [] + let ``when model is updated, should trigger ErrorsChanged iff the value returned by validate changes`` () = + Property.check + <| property { + let! name = GenX.auto + let! m1 = GenX.auto + let! m2 = GenX.auto - let binding = twoWayValidate name get set validate - let vm = TestVm(m1, binding) + let get _ = () + let set _ _ = () - vm.UpdateModel m2 + let validate m = + if m < 0 then ValueSome(string m) else ValueNone - test <@ vm.NumPcTriggersFor name = if get m1 = get m2 then 0 else 1 @> - } + let binding = twoWayValidate name get set validate + let vm = TestVm(m1, binding) + vm.UpdateModel m2 - [] - let ``when set, should call dispatch once with the value returned by set`` () = - Property.check <| property { - let! name = GenX.auto - let! m = GenX.auto - let! p = GenX.auto + test <@ vm.NumEcTriggersFor name = if validate m1 = validate m2 then 0 else 1 @> + } - let get = string - let set (p: string) (m: int) = string m + p - let validate _ = ValueNone - let binding = twoWayValidate name get set validate - let vm = TestVm(m, binding) + [] + let ``when model is updated, should trigger NotifyPropertyChanged on HasErrors iff the value returned by validate changes`` + () + = + Property.check + <| property { + let! name = GenX.auto + let! m1 = GenX.auto + let! m2 = GenX.auto - vm.Set name p + let get _ = () + let set _ _ = () - test <@ vm.Dispatches = [set p m] @> - } + let validate m = + if m < 0 then ValueSome(string m) else ValueNone + let binding = twoWayValidate name get set validate + let vm = TestVm(m1, binding) - [] - let ``when model is updated, should trigger ErrorsChanged iff the value returned by validate changes`` () = - Property.check <| property { - let! name = GenX.auto - let! m1 = GenX.auto - let! m2 = GenX.auto + vm.UpdateModel m2 - let get _ = () - let set _ _ = () - let validate m = if m < 0 then ValueSome (string m) else ValueNone + test + <@ + vm.NumPcTriggersFor "HasErrors" = if + (validate m1 |> ValueOption.isNone) = (validate m2 + |> ValueOption.isNone) + then + 0 + else + 1 + @> + } - let binding = twoWayValidate name get set validate - let vm = TestVm(m1, binding) - vm.UpdateModel m2 + [] + let ``when validate returns ValueNone, HasErrors should return false and GetErrors should return an empty collection`` + () + = + Property.check + <| property { + let! name = GenX.auto + let! m1 = GenX.auto + let! m2 = GenX.auto - test <@ vm.NumEcTriggersFor name = if validate m1 = validate m2 then 0 else 1 @> - } + let get _ = () + let set _ _ = () + let validate _ = ValueNone + let binding = twoWayValidate name get set validate + let vm = TestVm(m1, binding) + let vm' = vm :> INotifyDataErrorInfo - [] - let ``when model is updated, should trigger NotifyPropertyChanged on HasErrors iff the value returned by validate changes`` () = - Property.check <| property { - let! name = GenX.auto - let! m1 = GenX.auto - let! m2 = GenX.auto + test <@ vm'.HasErrors = false @> + test <@ vm'.GetErrors name |> Seq.cast |> Seq.isEmpty @> - let get _ = () - let set _ _ = () - let validate m = if m < 0 then ValueSome (string m) else ValueNone + vm.UpdateModel m2 - let binding = twoWayValidate name get set validate - let vm = TestVm(m1, binding) + test <@ vm'.HasErrors = false @> + test <@ vm'.GetErrors name |> Seq.cast |> Seq.isEmpty @> + } - vm.UpdateModel m2 - test <@ vm.NumPcTriggersFor "HasErrors" = if (validate m1 |> ValueOption.isNone) = (validate m2 |> ValueOption.isNone) then 0 else 1 @> - } + [] + let ``when validate returns ValueSome, HasErrors should return true and GetErrors should return a collection with a single element equal to the inner value returned by validate`` + () + = + Property.check + <| property { + let! name = GenX.auto + let! m1 = GenX.auto + let! m2 = GenX.auto + let get _ = () + let set _ _ = () + let validate m = ValueSome(string m) - [] - let ``when validate returns ValueNone, HasErrors should return false and GetErrors should return an empty collection`` () = - Property.check <| property { - let! name = GenX.auto - let! m1 = GenX.auto - let! m2 = GenX.auto + let binding = twoWayValidate name get set validate + let vm = TestVm(m1, binding) + let vm' = vm :> INotifyDataErrorInfo - let get _ = () - let set _ _ = () - let validate _ = ValueNone + test <@ vm'.HasErrors = true @> + test <@ vm'.GetErrors name |> Seq.cast |> Seq.toList = [ (validate m1).Value ] @> - let binding = twoWayValidate name get set validate - let vm = TestVm(m1, binding) - let vm' = vm :> INotifyDataErrorInfo + vm.UpdateModel m2 - test <@ vm'.HasErrors = false @> - test <@ vm'.GetErrors name |> Seq.cast |> Seq.isEmpty @> + test <@ vm'.HasErrors = true @> + test <@ vm'.GetErrors name |> Seq.cast |> Seq.toList = [ (validate m2).Value ] @> + } - vm.UpdateModel m2 - test <@ vm'.HasErrors = false @> - test <@ vm'.GetErrors name |> Seq.cast |> Seq.isEmpty @> - } + [] + let ``when validate returns no ValueNone after returning ValueSome, HasErrors should return false and GetErrors should return an empty collection`` + () + = + Property.check + <| property { + let! name = GenX.auto + let! m1 = GenX.auto + let! m2 = GenX.auto |> GenX.notEqualTo m1 + let get _ = () + let set _ _ = () - [] - let ``when validate returns ValueSome, HasErrors should return true and GetErrors should return a collection with a single element equal to the inner value returned by validate`` () = - Property.check <| property { - let! name = GenX.auto - let! m1 = GenX.auto - let! m2 = GenX.auto + let validate m = + if m = m1 then ValueSome(string m) else ValueNone - let get _ = () - let set _ _ = () - let validate m = ValueSome (string m) + let binding = twoWayValidate name get set validate + let vm = TestVm(m1, binding) + let vm' = vm :> INotifyDataErrorInfo - let binding = twoWayValidate name get set validate - let vm = TestVm(m1, binding) - let vm' = vm :> INotifyDataErrorInfo + test <@ vm'.HasErrors = true @> + test <@ vm'.GetErrors name |> Seq.cast |> Seq.toList = [ (validate m1).Value ] @> - test <@ vm'.HasErrors = true @> - test <@ vm'.GetErrors name |> Seq.cast |> Seq.toList = [(validate m1).Value] @> + vm.UpdateModel m2 - vm.UpdateModel m2 + test <@ vm'.HasErrors = false @> + test <@ vm'.GetErrors name |> Seq.cast |> Seq.isEmpty @> + } - test <@ vm'.HasErrors = true @> - test <@ vm'.GetErrors name |> Seq.cast |> Seq.toList = [(validate m2).Value] @> - } - [] - let ``when validate returns no ValueNone after returning ValueSome, HasErrors should return false and GetErrors should return an empty collection`` () = - Property.check <| property { - let! name = GenX.auto - let! m1 = GenX.auto - let! m2 = GenX.auto |> GenX.notEqualTo m1 +module Cmd = - let get _ = () - let set _ _ = () - let validate m = - if m = m1 - then ValueSome (string m) - else ValueNone - let binding = twoWayValidate name get set validate - let vm = TestVm(m1, binding) - let vm' = vm :> INotifyDataErrorInfo + [] + let ``the retrieved command's Execute should call dispatch once with the inner value returned by exec`` () = + Property.check + <| property { + let! name = GenX.auto + let! m = GenX.auto + let! p = GenX.auto |> GenX.withNull - test <@ vm'.HasErrors = true @> - test <@ vm'.GetErrors name |> Seq.cast |> Seq.toList = [(validate m1).Value] @> + let exec m = + if m < 0 then ValueNone else ValueSome(string m) - vm.UpdateModel m2 + let canExec m = m < 0 - test <@ vm'.HasErrors = false @> - test <@ vm'.GetErrors name |> Seq.cast |> Seq.isEmpty @> - } + let binding = cmd exec canExec name + let vm = TestVm(m, binding) + (vm.Get name: ICommand).Execute(p) + match exec m with + | ValueSome msg -> test <@ vm.Dispatches = [ msg ] @> + | ValueNone -> test <@ vm.Dispatches = [] @> + } -module Cmd = + [] + let ``the retrieved command's CanExecute should return the value returned by canExec`` () = + Property.check + <| property { + let! name = GenX.auto + let! m = GenX.auto + let! p = GenX.auto |> GenX.withNull - [] - let ``the retrieved command's Execute should call dispatch once with the inner value returned by exec`` () = - Property.check <| property { - let! name = GenX.auto - let! m = GenX.auto - let! p = GenX.auto |> GenX.withNull + let exec m = + if m < 0 then ValueNone else ValueSome(string m) - let exec m = if m < 0 then ValueNone else ValueSome (string m) - let canExec m = m < 0 + let canExec m = m < 0 - let binding = cmd exec canExec name - let vm = TestVm(m, binding) + let binding = cmd exec canExec name + let vm = TestVm(m, binding) - (vm.Get name : ICommand).Execute(p) + test <@ (vm.Get name: ICommand).CanExecute(p) = canExec m @> + } - match exec m with - | ValueSome msg -> test <@ vm.Dispatches = [msg] @> - | ValueNone -> test <@ vm.Dispatches = [] @> - } + [] + let ``when model is updated, should trigger CanExecuteChanged iff the output of canExec changes`` () = + Property.check + <| property { + let! name = GenX.auto + let! m1 = GenX.auto + let! m2 = GenX.auto - [] - let ``the retrieved command's CanExecute should return the value returned by canExec`` () = - Property.check <| property { - let! name = GenX.auto - let! m = GenX.auto - let! p = GenX.auto |> GenX.withNull + let exec m = + if m < 0 then ValueNone else ValueSome(string m) - let exec m = if m < 0 then ValueNone else ValueSome (string m) - let canExec m = m < 0 + let canExec m = m < 0 - let binding = cmd exec canExec name - let vm = TestVm(m, binding) + let binding = cmd exec canExec name + let vm = TestVm(m1, binding) - test <@ (vm.Get name : ICommand).CanExecute(p) = canExec m @> - } + vm.TrackCecTriggersFor name + vm.UpdateModel m2 + test <@ vm.NumCecTriggersFor name = if canExec m1 = canExec m2 then 0 else 1 @> + } - [] - let ``when model is updated, should trigger CanExecuteChanged iff the output of canExec changes`` () = - Property.check <| property { - let! name = GenX.auto - let! m1 = GenX.auto - let! m2 = GenX.auto - let exec m = if m < 0 then ValueNone else ValueSome (string m) - let canExec m = m < 0 + [] + let ``when model is updated, should never trigger PC`` () = + Property.check + <| property { + let! name = GenX.auto + let! m1 = GenX.auto + let! m2 = GenX.auto - let binding = cmd exec canExec name - let vm = TestVm(m1, binding) + let exec m = + if m < 0 then ValueNone else ValueSome(string m) - vm.TrackCecTriggersFor name - vm.UpdateModel m2 + let canExec m = m < 0 - test <@ vm.NumCecTriggersFor name = if canExec m1 = canExec m2 then 0 else 1 @> - } + let binding = cmd exec canExec name + let vm = TestVm(m1, binding) + vm.UpdateModel m2 - [] - let ``when model is updated, should never trigger PC`` () = - Property.check <| property { - let! name = GenX.auto - let! m1 = GenX.auto - let! m2 = GenX.auto + test <@ vm.NumPcTriggersFor name = 0 @> + } - let exec m = if m < 0 then ValueNone else ValueSome (string m) - let canExec m = m < 0 - let binding = cmd exec canExec name - let vm = TestVm(m1, binding) - vm.UpdateModel m2 +module CmdParam = - test <@ vm.NumPcTriggersFor name = 0 @> - } + [] + let ``the retrieved command's Execute should call dispatch once with the inner value returned by exec`` () = + Property.check + <| property { + let! name = GenX.auto + let! m = GenX.auto + let! p = GenX.auto + let! autoRequery = Gen.bool + let exec (p: string) m = + if p.Length + m < 0 then + ValueNone + else + ValueSome(string m + p) -module CmdParam = + let canExec (p: string) m = p.Length + m < 0 + let binding = cmdParam name exec canExec autoRequery + let vm = TestVm(m, binding) - [] - let ``the retrieved command's Execute should call dispatch once with the inner value returned by exec`` () = - Property.check <| property { - let! name = GenX.auto - let! m = GenX.auto - let! p = GenX.auto - let! autoRequery = Gen.bool + (vm.Get name: ICommand).Execute(p) - let exec (p: string) m = if p.Length + m < 0 then ValueNone else ValueSome (string m + p) - let canExec (p: string) m = p.Length + m < 0 + match exec p m with + | ValueSome msg -> test <@ vm.Dispatches = [ msg ] @> + | ValueNone -> test <@ vm.Dispatches = [] @> + } - let binding = cmdParam name exec canExec autoRequery - let vm = TestVm(m, binding) - (vm.Get name : ICommand).Execute(p) + [] + let ``the retrieved command's CanExecute should return the value returned by canExec`` () = + Property.check + <| property { + let! name = GenX.auto + let! m = GenX.auto + let! p = GenX.auto + let! autoRequery = Gen.bool - match exec p m with - | ValueSome msg -> test <@ vm.Dispatches = [msg] @> - | ValueNone -> test <@ vm.Dispatches = [] @> - } + let exec (p: string) m = + if p.Length + m < 0 then + ValueNone + else + ValueSome(string m + p) + let canExec (p: string) m = p.Length + m < 0 - [] - let ``the retrieved command's CanExecute should return the value returned by canExec`` () = - Property.check <| property { - let! name = GenX.auto - let! m = GenX.auto - let! p = GenX.auto - let! autoRequery = Gen.bool + let binding = cmdParam name exec canExec autoRequery + let vm = TestVm(m, binding) - let exec (p: string) m = if p.Length + m < 0 then ValueNone else ValueSome (string m + p) - let canExec (p: string) m = p.Length + m < 0 + test <@ (vm.Get name: ICommand).CanExecute(p) = canExec p m @> + } - let binding = cmdParam name exec canExec autoRequery - let vm = TestVm(m, binding) - test <@ (vm.Get name : ICommand).CanExecute(p) = canExec p m @> - } + [] + let ``when model is updated, should always trigger CanExecuteChanged`` () = + Property.check + <| property { + let! name = GenX.auto + let! m1 = GenX.auto + let! m2 = GenX.auto + let! autoRequery = Gen.bool + let exec (p: string) m = + if p.Length + m < 0 then + ValueNone + else + ValueSome(string m + p) - [] - let ``when model is updated, should always trigger CanExecuteChanged`` () = - Property.check <| property { - let! name = GenX.auto - let! m1 = GenX.auto - let! m2 = GenX.auto - let! autoRequery = Gen.bool + let canExec (p: string) m = p.Length + m < 0 - let exec (p: string) m = if p.Length + m < 0 then ValueNone else ValueSome (string m + p) - let canExec (p: string) m = p.Length + m < 0 + let binding = cmdParam name exec canExec autoRequery + let vm = TestVm(m1, binding) - let binding = cmdParam name exec canExec autoRequery - let vm = TestVm(m1, binding) + vm.TrackCecTriggersFor name + vm.UpdateModel m2 - vm.TrackCecTriggersFor name - vm.UpdateModel m2 + test <@ vm.NumCecTriggersFor name = 1 @> + } - test <@ vm.NumCecTriggersFor name = 1 @> - } + [] + let ``when model is updated, should never trigger PC`` () = + Property.check + <| property { + let! name = GenX.auto + let! m1 = GenX.auto + let! m2 = GenX.auto + let! autoRequery = Gen.bool - [] - let ``when model is updated, should never trigger PC`` () = - Property.check <| property { - let! name = GenX.auto - let! m1 = GenX.auto - let! m2 = GenX.auto - let! autoRequery = Gen.bool + let exec (p: string) m = + if p.Length + m < 0 then + ValueNone + else + ValueSome(string m + p) - let exec (p: string) m = if p.Length + m < 0 then ValueNone else ValueSome (string m + p) - let canExec (p: string) m = p.Length + m < 0 + let canExec (p: string) m = p.Length + m < 0 - let binding = cmdParam name exec canExec autoRequery - let vm = TestVm(m1, binding) + let binding = cmdParam name exec canExec autoRequery + let vm = TestVm(m1, binding) - vm.UpdateModel m2 + vm.UpdateModel m2 - test <@ vm.NumPcTriggersFor name = 0 @> - } + test <@ vm.NumPcTriggersFor name = 0 @> + } module SubModel = - [] - let ``when retrieved and getModel returns ValueSome, should return a ViewModel whose CurrentModel is the inner value returned by getModel`` () = - Property.check <| property { - let! name = GenX.auto - let! m1 = GenX.auto - let! m2 = GenX.auto - let! sticky = Gen.bool + [] + let ``when retrieved and getModel returns ValueSome, should return a ViewModel whose CurrentModel is the inner value returned by getModel`` + () + = + Property.check + <| property { + let! name = GenX.auto + let! m1 = GenX.auto + let! m2 = GenX.auto + let! sticky = Gen.bool + + let getModel (m: byte * int) = (snd m) / 2 |> ValueSome + let toMsg _ = () + + let binding = subModel name getModel toMsg [] sticky + let vm = TestVm(m1, binding) + + test <@ (vm.Get name: IViewModel).CurrentModel = (getModel m1).Value @> + + vm.UpdateModel m2 - let getModel (m: byte * int) = (snd m) / 2 |> ValueSome - let toMsg _ = () + test <@ (vm.Get name: IViewModel).CurrentModel = (getModel m2).Value @> + } - let binding = subModel name getModel toMsg [] sticky - let vm = TestVm(m1, binding) - test <@ (vm.Get name : IViewModel).CurrentModel = (getModel m1).Value @> + [] + let ``when retrieved initially and getModel returns ValueNone, should return null`` () = + Property.check + <| property { + let! name = GenX.auto + let! m = GenX.auto + let! sticky = Gen.bool - vm.UpdateModel m2 + let getModel _ = ValueNone + let toMsg _ = () - test <@ (vm.Get name : IViewModel).CurrentModel = (getModel m2).Value @> - } + let binding = subModel name getModel toMsg [] sticky + let vm = TestVm(m, binding) + test <@ vm.Get name |> isNull @> + } - [] - let ``when retrieved initially and getModel returns ValueNone, should return null`` () = - Property.check <| property { - let! name = GenX.auto - let! m = GenX.auto - let! sticky = Gen.bool - let getModel _ = ValueNone - let toMsg _ = () + [] + let ``when retrieved after update and getModel changes between ValueSome and ValueNone, should return null if sticky is false, otherwise the last non-null value`` + () + = + Property.check + <| property { + let! name = GenX.auto + let! m1 = GenX.auto + let! m2 = GenX.auto |> GenX.notEqualTo m1 + let! m3 = GenX.auto |> GenX.notEqualTo m1 |> GenX.notEqualTo m2 + let! sticky = Gen.bool - let binding = subModel name getModel toMsg [] sticky - let vm = TestVm(m, binding) + let getModel (m: byte * int) = + if m = m1 then (snd m) / 2 |> ValueSome + elif m = m2 then ValueNone + elif m = m3 then (snd m) / 3 |> ValueSome + else failwith "Should never happen" - test <@ vm.Get name |> isNull @> - } + let toMsg _ = () + let binding = subModel name getModel toMsg [] sticky + let vm = TestVm(m1, binding) - [] - let ``when retrieved after update and getModel changes between ValueSome and ValueNone, should return null if sticky is false, otherwise the last non-null value`` () = - Property.check <| property { - let! name = GenX.auto - let! m1 = GenX.auto - let! m2 = GenX.auto |> GenX.notEqualTo m1 - let! m3 = GenX.auto |> GenX.notEqualTo m1 |> GenX.notEqualTo m2 - let! sticky = Gen.bool + test <@ (vm.Get name: IViewModel).CurrentModel = (getModel m1).Value @> - let getModel (m: byte * int) = - if m = m1 then (snd m) / 2 |> ValueSome - elif m = m2 then ValueNone - elif m = m3 then (snd m) / 3 |> ValueSome - else failwith "Should never happen" - let toMsg _ = () + vm.UpdateModel m2 - let binding = subModel name getModel toMsg [] sticky - let vm = TestVm(m1, binding) + if sticky then + test <@ (vm.Get name: IViewModel).CurrentModel = (getModel m1).Value @> + else + test <@ vm.Get name |> isNull @> - test <@ (vm.Get name : IViewModel).CurrentModel = (getModel m1).Value @> + vm.UpdateModel m3 - vm.UpdateModel m2 + test <@ (vm.Get name: IViewModel).CurrentModel = (getModel m3).Value @> + } - if sticky then - test <@ (vm.Get name : IViewModel).CurrentModel = (getModel m1).Value @> - else - test <@ vm.Get name |> isNull @> - vm.UpdateModel m3 + [] + let ``when model is updated, should trigger PC once iff getModel changes from ValueNone to ValueSome, or from ValueSome to ValueNone when sticky is false`` + () + = + Property.check + <| property { + let! name = GenX.auto + let! m1 = GenX.auto + let! m2 = GenX.auto |> GenX.notEqualTo m1 + let! sticky = Gen.bool - test <@ (vm.Get name : IViewModel).CurrentModel = (getModel m3).Value @> - } + let getModel (m: byte * int) = + if snd m < 0 then ValueNone else (snd m) / 2 |> ValueSome + let toMsg _ = () - [] - let ``when model is updated, should trigger PC once iff getModel changes from ValueNone to ValueSome, or from ValueSome to ValueNone when sticky is false`` () = - Property.check <| property { - let! name = GenX.auto - let! m1 = GenX.auto - let! m2 = GenX.auto |> GenX.notEqualTo m1 - let! sticky = Gen.bool + let binding = subModel name getModel toMsg [] sticky + let vm = TestVm(m1, binding) - let getModel (m: byte * int) = if snd m < 0 then ValueNone else (snd m) / 2 |> ValueSome - let toMsg _ = () + vm.UpdateModel m2 - let binding = subModel name getModel toMsg [] sticky - let vm = TestVm(m1, binding) + let wasSome = (getModel m1).IsSome + let wasNone = (getModel m1).IsNone + let isSome = (getModel m2).IsSome + let isNone = (getModel m2).IsNone - vm.UpdateModel m2 + test + <@ + vm.NumPcTriggersFor name = if wasNone && isSome then 1 + elif wasSome && isNone && not sticky then 1 + else 0 + @> + } - let wasSome = (getModel m1).IsSome - let wasNone = (getModel m1).IsNone - let isSome = (getModel m2).IsSome - let isNone = (getModel m2).IsNone - test <@ vm.NumPcTriggersFor name = - if wasNone && isSome then 1 - elif wasSome && isNone && not sticky then 1 - else 0 @> - } + [] + let ``smoke test: when a sub-model OneWay binding is retrieved, returns the value returned by get`` () = + Property.check + <| property { + let! name = GenX.auto + let! subName = GenX.auto + let! m = GenX.auto + let! sticky = Gen.bool - [] - let ``smoke test: when a sub-model OneWay binding is retrieved, returns the value returned by get`` () = - Property.check <| property { - let! name = GenX.auto - let! subName = GenX.auto - let! m = GenX.auto - let! sticky = Gen.bool + let getModel = snd >> ValueSome + let toMsg _ = () + let subGet = string - let getModel = snd >> ValueSome - let toMsg _ = () - let subGet = string + let subBinding = oneWay subGet subName + let binding = subModel name getModel toMsg [ subBinding ] sticky + let vm = TestVm(m, binding) - let subBinding = oneWay subGet subName - let binding = subModel name getModel toMsg [subBinding] sticky - let vm = TestVm(m, binding) + test <@ (vm.Get name: DynamicViewModel).Get subName = ((getModel m).Value |> subGet) @> + } - test <@ (vm.Get name : DynamicViewModel).Get subName = ((getModel m).Value |> subGet) @> - } + [] + let ``smoke test: when a sub-model TwoWay binding is set, dispatches the value returned by set transformed by toMsg`` + () + = + Property.check + <| property { + let! name = GenX.auto + let! subName = GenX.auto + let! m = GenX.auto + let! p = GenX.auto + let! sticky = Gen.bool - [] - let ``smoke test: when a sub-model TwoWay binding is set, dispatches the value returned by set transformed by toMsg`` () = - Property.check <| property { - let! name = GenX.auto - let! subName = GenX.auto - let! m = GenX.auto - let! p = GenX.auto - let! sticky = Gen.bool + let getModel: byte * int -> int voption = snd >> ValueSome + let toMsg = String.length + let subGet: int -> string = string + let subSet (p: string) (m: int) = p + string m - let getModel : byte * int -> int voption = snd >> ValueSome - let toMsg = String.length - let subGet : int -> string = string - let subSet (p: string) (m: int) = p + string m + let subBinding = twoWay subGet subSet subName + let binding = subModel name getModel toMsg [ subBinding ] sticky + let vm = TestVm(m, binding) - let subBinding = twoWay subGet subSet subName - let binding = subModel name getModel toMsg [subBinding] sticky - let vm = TestVm(m, binding) + (vm.Get name: DynamicViewModel).Set subName p - (vm.Get name : DynamicViewModel).Set subName p - - test <@ vm.Dispatches = [subSet p (getModel m).Value |> toMsg] @> - } + test <@ vm.Dispatches = [ subSet p (getModel m).Value |> toMsg ] @> + } - [] - let ``setMsgWithModel given current model after new submodel created`` () = - Property.check <| property { - let! name = GenX.auto - let! subName = GenX.auto - let! initialModel = GenX.auto - let! newModel = GenX.auto |> GenX.notEqualTo initialModel + [] + let ``setMsgWithModel given current model after new submodel created`` () = + Property.check + <| property { + let! name = GenX.auto + let! subName = GenX.auto + let! initialModel = GenX.auto + let! newModel = GenX.auto |> GenX.notEqualTo initialModel - let subBinding = cmd ValueSome (fun _ -> true) subName - let binding = - Binding.SubModel.opt (fun () -> [subBinding]) name - |> Binding.mapModel (fun m -> if m <> initialModel then Some m else None) - |> Binding.setMsgWithModel id - let vm = TestVm(initialModel, binding) + let subBinding = cmd ValueSome (fun _ -> true) subName - vm.UpdateModel newModel - let subVm = vm.Get name : DynamicViewModel - let command = subVm.Get subName : ICommand - command.Execute(true) + let binding = + Binding.SubModel.opt (fun () -> [ subBinding ]) name + |> Binding.mapModel (fun m -> if m <> initialModel then Some m else None) + |> Binding.setMsgWithModel id - test <@ vm.Dispatches = [newModel] @> - } + let vm = TestVm(initialModel, binding) + + vm.UpdateModel newModel + let subVm = vm.Get name: DynamicViewModel + let command = subVm.Get subName: ICommand + command.Execute(true) + + test <@ vm.Dispatches = [ newModel ] @> + } module SubModelSeq = - let private testObservableCollectionContainsExpectedItems (vm: DynamicViewModel) name expected = - let actual = - vm.Get name - |> unbox>> - |> Seq.map IViewModel.currentModel - |> Seq.toList - test <@ expected = actual @> + let private testObservableCollectionContainsExpectedItems + (vm: DynamicViewModel) + name + expected + = + let actual = + vm.Get name + |> unbox>> + |> Seq.map IViewModel.currentModel + |> Seq.toList + + test <@ expected = actual @> + + [] + let ``when retrieved, should return an ObservableCollection with ViewModels whose CurrentModel is the corresponding value returned by getModels`` + () + = + Property.check + <| property { + let! name = GenX.auto + let! m = GenX.auto + + let getModels = id + let getId = id + let toMsg = id + + let binding = subModelSeq name getModels getId toMsg [] + let vm = TestVm(m, binding) + + testObservableCollectionContainsExpectedItems vm name m + } + + + [] + let ``when model is updated, should never trigger PC`` () = // because this binding should only trigger CC + Property.check + <| property { + let! name = GenX.auto + let! m1 = GenX.auto + let! m2 = GenX.auto - [] - let ``when retrieved, should return an ObservableCollection with ViewModels whose CurrentModel is the corresponding value returned by getModels`` () = - Property.check <| property { - let! name = GenX.auto - let! m = GenX.auto + let getModels = id + let getId = id + let toMsg = id - let getModels = id - let getId = id - let toMsg = id + let binding = subModelSeq name getModels getId toMsg [] + let vm = TestVm(m1, binding) - let binding = subModelSeq name getModels getId toMsg [] - let vm = TestVm(m, binding) + vm.UpdateModel m2 - testObservableCollectionContainsExpectedItems vm name m - } + test <@ vm.NumPcTriggersFor name = 0 @> + } - [] - let ``when model is updated, should never trigger PC`` () = // because this binding should only trigger CC - Property.check <| property { - let! name = GenX.auto - let! m1 = GenX.auto - let! m2 = GenX.auto + [] + let ``given elements are the same, when model is updated, should not trigger CC`` () = + Property.check + <| property { + let! name = GenX.auto + let! m = GenX.auto - let getModels = id - let getId = id - let toMsg = id + let getModels = id + let getId = id + let toMsg = id - let binding = subModelSeq name getModels getId toMsg [] - let vm = TestVm(m1, binding) + let binding = subModelSeq name getModels getId toMsg [] + let vm = TestVm(m, binding) - vm.UpdateModel m2 + vm.TrackCcTriggersFor name - test <@ vm.NumPcTriggersFor name = 0 @> - } + vm.UpdateModel m + test <@ vm.NumCcTriggersFor name = 0 @> + } - [] - let ``given elements are the same, when model is updated, should not trigger CC`` () = - Property.check <| property { - let! name = GenX.auto - let! m = GenX.auto - let getModels = id - let getId = id - let toMsg = id + [] + let ``smoke test: when a sub-model OneWay binding is retrieved, returns the value returned by get`` () = + Property.check + <| property { + let! name = GenX.auto + let! subName = GenX.auto + let! m = GenX.auto - let binding = subModelSeq name getModels getId toMsg [] - let vm = TestVm(m, binding) + let getModels = id + let getId = id + let toMsg = id + let subGet = string - vm.TrackCcTriggersFor name + let subBinding = oneWay subGet subName + let binding = subModelSeq name getModels getId toMsg [ subBinding ] + let vm = TestVm(m, binding) - vm.UpdateModel m + let actual = + vm.Get name + |> unbox>> + |> Seq.map (fun vm -> vm.Get subName |> unbox) + |> Seq.toList - test <@ vm.NumCcTriggersFor name = 0 @> - } + let expected = getModels m |> Seq.map subGet |> Seq.toList + test <@ expected = actual @> + } - [] - let ``smoke test: when a sub-model OneWay binding is retrieved, returns the value returned by get`` () = - Property.check <| property { - let! name = GenX.auto - let! subName = GenX.auto - let! m = GenX.auto + [] + let ``smoke test: when a sub-model TwoWay binding is set, dispatches the value returned by set transformed by toMsg`` + () + = + Property.check + <| property { + let! name = GenX.auto + let! subName = GenX.auto + let! m = GenX.auto + let! p = GenX.auto - let getModels = id - let getId = id - let toMsg = id - let subGet = string + let getModels = id + let getId = string + let toMsg (id: string, subMsg: string) = (id + subMsg).Length + let subGet = string + let subSet (p: string) (m: Guid) = p + string m - let subBinding = oneWay subGet subName - let binding = subModelSeq name getModels getId toMsg [subBinding] - let vm = TestVm(m, binding) + let subBinding = twoWay subGet subSet subName + let binding = subModelSeq name getModels getId toMsg [ subBinding ] + let vm = TestVm(m, binding) - let actual = - vm.Get name - |> unbox>> - |> Seq.map (fun vm -> vm.Get subName |> unbox) - |> Seq.toList + vm.Get name + |> unbox>> + |> Seq.iter (fun vm -> vm.Set subName p) - let expected = getModels m |> Seq.map subGet |> Seq.toList - test <@ expected = actual @> - } + let expected = m |> getModels |> List.map (fun m -> (getId m, subSet p m) |> toMsg) + test <@ expected = vm.Dispatches @> + } - [] - let ``smoke test: when a sub-model TwoWay binding is set, dispatches the value returned by set transformed by toMsg`` () = - Property.check <| property { - let! name = GenX.auto - let! subName = GenX.auto - let! m = GenX.auto - let! p = GenX.auto - let getModels = id - let getId = string - let toMsg (id: string, subMsg: string) = (id + subMsg).Length - let subGet = string - let subSet (p: string) (m: Guid) = p + string m +module SubModelSelectedItem = - let subBinding = twoWay subGet subSet subName - let binding = subModelSeq name getModels getId toMsg [subBinding] - let vm = TestVm(m, binding) - vm.Get name - |> unbox>> - |> Seq.iter (fun vm -> vm.Set subName p) + [] + let ``Should return the VM corresponding to the ID that has been set`` () = + Property.check + <| property { + let! subModelSeqName = GenX.auto + let! selectedItemName = GenX.auto |> GenX.notEqualTo subModelSeqName + let! m = GenX.auto - let expected = m |> getModels |> List.map (fun m -> (getId m, subSet p m) |> toMsg) - test <@ expected = vm.Dispatches @> - } + let! selectedSubModel = + match snd m with + | [] -> Gen.constant ValueNone + | xs -> Gen.item xs |> Gen.map ValueSome + let getModels: int * Guid list -> Guid list = snd + let getId: Guid -> string = string + let toMsg = snd + let get _ = + selectedSubModel |> ValueOption.map getId -module SubModelSelectedItem = + let set _ _ = () + let subModelSeqBinding = subModelSeq subModelSeqName getModels getId toMsg [] + + let selectedItemBinding = + subModelSelectedItem selectedItemName subModelSeqName get set + + let vm = TestVm(m, [ subModelSeqBinding; selectedItemBinding ]) + + match selectedSubModel with + | ValueNone -> test <@ vm.Get selectedItemName = null @> + | ValueSome sm -> + test + <@ + (vm.Get selectedItemName |> unbox>) + |> Option.ofObj + |> Option.map (fun vm -> vm.CurrentModel) = (m + |> getModels + |> List.tryFind (fun x -> getId x = getId sm)) + @> + } - [] - let ``Should return the VM corresponding to the ID that has been set`` () = - Property.check <| property { - let! subModelSeqName = GenX.auto - let! selectedItemName = GenX.auto |> GenX.notEqualTo subModelSeqName - let! m = GenX.auto - let! selectedSubModel = - match snd m with - | [] -> Gen.constant ValueNone - | xs -> Gen.item xs |> Gen.map ValueSome - - let getModels : int * Guid list -> Guid list = snd - let getId : Guid -> string = string - let toMsg = snd - - let get _ = selectedSubModel |> ValueOption.map getId - let set _ _ = () - - let subModelSeqBinding = subModelSeq subModelSeqName getModels getId toMsg [] - let selectedItemBinding = subModelSelectedItem selectedItemName subModelSeqName get set - - let vm = TestVm(m, [subModelSeqBinding; selectedItemBinding]) - - match selectedSubModel with - | ValueNone -> - test <@ vm.Get selectedItemName = null @> - | ValueSome sm -> - test <@ (vm.Get selectedItemName |> unbox>) |> Option.ofObj |> Option.map (fun vm -> vm.CurrentModel) - = (m |> getModels |> List.tryFind (fun x -> getId x = getId sm)) - @> - } - - - [] - let ``when set, should dispatch the message returned by set`` () = - Property.check <| property { - let! subModelSeqName = GenX.auto - let! selectedItemName = GenX.auto |> GenX.notEqualTo subModelSeqName - let! m = GenX.auto - let! selectedSubModel = - match snd m with - | [] -> Gen.constant ValueNone - | xs -> Gen.item xs |> Gen.map ValueSome - - let getModels : int * Guid list -> Guid list = snd - let getId : Guid -> string = string - let toMsg = snd - - let get _ = selectedSubModel |> ValueOption.map getId - let set (p: string voption) (m: int * Guid list) = - p |> ValueOption.map (String.length >> (+) (fst m)) - - let subModelSeqBinding = subModelSeq subModelSeqName getModels getId toMsg [] - let selectedItemBinding = subModelSelectedItem selectedItemName subModelSeqName get set - - let vm = TestVm(m, [subModelSeqBinding; selectedItemBinding]) - - let selectedVm = - selectedSubModel |> ValueOption.bind (fun sm -> - vm.Get subModelSeqName - |> unbox>> - |> Seq.tryFind (fun vm -> vm |> IViewModel.currentModel |> getId = getId sm) - |> ValueOption.ofOption - ) - |> ValueOption.toObj - - vm.Set selectedItemName selectedVm - - test <@ vm.Dispatches = [ set (selectedSubModel |> ValueOption.map getId) m ] @> - } - - [] - let ``attempting to select a nonexistent item throws RuntimeBinderException`` () = - let selectedItemName = "Foo" - let subModelSeqName = "Bar" - let bindings = - [ selectedItemName |> Binding.subModelSelectedItem (subModelSeqName, Some, ignore) - subModelSeqName |> Binding.subModelSeq ((fun _ -> []), ignore, (fun () -> [])) ] - let mutable error : string option = None - let loggingArgs = - { LoggingViewModelArgs.none - with - log = - { new Microsoft.Extensions.Logging.ILogger - with - member _.BeginScope _ = { new IDisposable with member _.Dispose() = () } - member _.IsEnabled _ = true - member _.Log (_, _, state, ex, formatter) = error <- formatter.Invoke(state, ex) |> Some } } - let viewModelArgs = ViewModelArgs.create 0.0 ignore "main" loggingArgs - let vm = DynamicViewModel(viewModelArgs, bindings) - - raises <@ vm.Get selectedItemName @> - test <@ error.Value.Contains "TryGetMember FAILED: Failed to find an element" @> + + [] + let ``when set, should dispatch the message returned by set`` () = + Property.check + <| property { + let! subModelSeqName = GenX.auto + let! selectedItemName = GenX.auto |> GenX.notEqualTo subModelSeqName + let! m = GenX.auto + + let! selectedSubModel = + match snd m with + | [] -> Gen.constant ValueNone + | xs -> Gen.item xs |> Gen.map ValueSome + + let getModels: int * Guid list -> Guid list = snd + let getId: Guid -> string = string + let toMsg = snd + + let get _ = + selectedSubModel |> ValueOption.map getId + + let set (p: string voption) (m: int * Guid list) = + p |> ValueOption.map (String.length >> (+) (fst m)) + + let subModelSeqBinding = subModelSeq subModelSeqName getModels getId toMsg [] + + let selectedItemBinding = + subModelSelectedItem selectedItemName subModelSeqName get set + + let vm = TestVm(m, [ subModelSeqBinding; selectedItemBinding ]) + + let selectedVm = + selectedSubModel + |> ValueOption.bind (fun sm -> + vm.Get subModelSeqName + |> unbox>> + |> Seq.tryFind (fun vm -> vm |> IViewModel.currentModel |> getId = getId sm) + |> ValueOption.ofOption) + |> ValueOption.toObj + + vm.Set selectedItemName selectedVm + + test <@ vm.Dispatches = [ set (selectedSubModel |> ValueOption.map getId) m ] @> + } + + [] + let ``attempting to select a nonexistent item throws RuntimeBinderException`` () = + let selectedItemName = "Foo" + let subModelSeqName = "Bar" + + let bindings = + [ selectedItemName |> Binding.subModelSelectedItem (subModelSeqName, Some, ignore) + subModelSeqName |> Binding.subModelSeq ((fun _ -> []), ignore, (fun () -> [])) ] + + let mutable error: string option = None + + let loggingArgs = + { LoggingViewModelArgs.none with + log = + { new Microsoft.Extensions.Logging.ILogger with + member _.BeginScope _ = + { new IDisposable with + member _.Dispose() = () } + + member _.IsEnabled _ = true + + member _.Log(_, _, state, ex, formatter) = + error <- formatter.Invoke(state, ex) |> Some } } + + let viewModelArgs = ViewModelArgs.create 0.0 ignore "main" loggingArgs + let vm = DynamicViewModel(viewModelArgs, bindings) + + raises <@ vm.Get selectedItemName @> + test <@ error.Value.Contains "TryGetMember FAILED: Failed to find an element" @> module CacheEffect = - [] - let ``model mapping called exactly once when Get called twice`` () = + [] + let ``model mapping called exactly once when Get called twice`` () = - Property.check <| property { - let! name = GenX.auto - let! model = GenX.auto - let! bindingComponsitionOrder = Gen.bool + Property.check + <| property { + let! name = GenX.auto + let! model = GenX.auto + let! bindingComponsitionOrder = Gen.bool - let mapping = InvokeTester id - let cachingAndMapping = - if bindingComponsitionOrder - then Binding.mapModel mapping.Fn >> Binding.addCaching - else Binding.addCaching >> Binding.mapModel mapping.Fn - let binding = - name - |> Binding.OneWay.id - |> cachingAndMapping - let vm = TestVm(model, binding) + let mapping = InvokeTester id + + let cachingAndMapping = + if bindingComponsitionOrder then + Binding.mapModel mapping.Fn >> Binding.addCaching + else + Binding.addCaching >> Binding.mapModel mapping.Fn + + let binding = name |> Binding.OneWay.id |> cachingAndMapping + let vm = TestVm(model, binding) + + vm.Get name |> ignore // populate cache + vm.Get name |> ignore + + test <@ 1 = mapping.Count @> + } - vm.Get name |> ignore // populate cache - vm.Get name |> ignore - test <@ 1 = mapping.Count @> - } + [] + let ``second Get returns new model after first Get and then Update`` () = + let name = "" + let model = 0 + let newModel = 1 + let binding = name |> Binding.OneWay.id |> Binding.addCaching + let vm = TestVm(model, binding) + vm.Get name |> ignore // populate cache + vm.UpdateModel newModel // clear cache + let actual = vm.Get name |> unbox - [] - let ``second Get returns new model after first Get and then Update`` () = - let name = "" - let model = 0 - let newModel = 1 - let binding = - name - |> Binding.OneWay.id - |> Binding.addCaching - let vm = TestVm(model, binding) + test <@ newModel = actual @> - vm.Get name |> ignore // populate cache - vm.UpdateModel newModel // clear cache - let actual = vm.Get name |> unbox - test <@ newModel = actual @> + [] + let ``cache not cleared on Set`` () = + let name = "" + let initialModel = 0 + let newModel = 1 + let mapping = InvokeTester(fun x -> x) + let binding = + name |> Binding.TwoWay.id |> Binding.mapModel mapping.Fn |> Binding.addCaching - [] - let ``cache not cleared on Set`` () = - let name = "" - let initialModel = 0 - let newModel = 1 - let mapping = InvokeTester (fun x -> x) - let binding = - name - |> Binding.TwoWay.id - |> Binding.mapModel mapping.Fn - |> Binding.addCaching - let vm = TestVm(initialModel, binding) + let vm = TestVm(initialModel, binding) - vm.Get name |> ignore // populate cache - vm.Set name newModel - mapping.Reset() // Set calls mapping function, so reset count - let actual = vm.Get name |> unbox + vm.Get name |> ignore // populate cache + vm.Set name newModel + mapping.Reset() // Set calls mapping function, so reset count + let actual = vm.Get name |> unbox - test <@ initialModel = actual @> - test <@ 0 = mapping.Count @> + test <@ initialModel = actual @> + test <@ 0 = mapping.Count @> module LazyEffect = - [] - let ``model mapping called exactly once on initialize`` () = - let name = "" - let model = 0 - let mapping = InvokeTester id - let binding = - name - |> Binding.TwoWay.id - |> Binding.addLazy (=) - |> Binding.addLazy (=) - |> Binding.mapModel mapping.Fn + [] + let ``model mapping called exactly once on initialize`` () = + let name = "" + let model = 0 + let mapping = InvokeTester id - TestVm(model, binding) |> ignore + let binding = + name + |> Binding.TwoWay.id + |> Binding.addLazy (=) + |> Binding.addLazy (=) + |> Binding.mapModel mapping.Fn - test <@ 1 = mapping.Count @> + TestVm(model, binding) |> ignore + test <@ 1 = mapping.Count @> - [] - let ``model mapping called exactly twice on update when new model is equal`` () = - let name = "" - let model = 0 - let mapping = InvokeTester id - let binding = - name - |> Binding.TwoWay.id - |> Binding.addLazy (=) - |> Binding.addLazy (=) - |> Binding.mapModel mapping.Fn - let vm = TestVm(model, binding) - mapping.Reset () - vm.UpdateModel model + [] + let ``model mapping called exactly twice on update when new model is equal`` () = + let name = "" + let model = 0 + let mapping = InvokeTester id - test <@ 2 = mapping.Count @> + let binding = + name + |> Binding.TwoWay.id + |> Binding.addLazy (=) + |> Binding.addLazy (=) + |> Binding.mapModel mapping.Fn + let vm = TestVm(model, binding) + mapping.Reset() - [] - let ``model mapping called exactly twice on update when new model is unequal`` () = - let name = "" - let initialModel = 0 - let newModel = 1 - let mapping = InvokeTester id - let binding = - name - |> Binding.TwoWay.id - |> Binding.addLazy (=) - |> Binding.addLazy (=) - |> Binding.mapModel mapping.Fn - let vm = TestVm(initialModel, binding) - mapping.Reset () + vm.UpdateModel model - vm.UpdateModel newModel + test <@ 2 = mapping.Count @> - test <@ 2 = mapping.Count @> + + [] + let ``model mapping called exactly twice on update when new model is unequal`` () = + let name = "" + let initialModel = 0 + let newModel = 1 + let mapping = InvokeTester id + + let binding = + name + |> Binding.TwoWay.id + |> Binding.addLazy (=) + |> Binding.addLazy (=) + |> Binding.mapModel mapping.Fn + + let vm = TestVm(initialModel, binding) + mapping.Reset() + + vm.UpdateModel newModel + + test <@ 2 = mapping.Count @> module AlterMsgStream = - [] - let ``message stream alteration only invoked once when set called twice`` () = - let name = "" - let model = 0 - let get = ignore - let set _ _ = () - let alteration = InvokeTester id - let binding = - twoWay get set name - |> Binding.alterMsgStream alteration.Fn - let vm = TestVm(model, binding) - - vm.Set name () - vm.Set name () - - test <@ 1 = alteration.Count @> + [] + let ``message stream alteration only invoked once when set called twice`` () = + let name = "" + let model = 0 + let get = ignore + let set _ _ = () + let alteration = InvokeTester id + let binding = twoWay get set name |> Binding.alterMsgStream alteration.Fn + let vm = TestVm(model, binding) + + vm.Set name () + vm.Set name () + + test <@ 1 = alteration.Count @> \ No newline at end of file diff --git a/src/Elmish.WPF.Tests/MergeTests.fs b/src/Elmish.WPF.Tests/MergeTests.fs index 3ab11048..839eeb03 100644 --- a/src/Elmish.WPF.Tests/MergeTests.fs +++ b/src/Elmish.WPF.Tests/MergeTests.fs @@ -18,267 +18,352 @@ let internal merge x = x |> Merge.keyed let private trackCC (observableCollection: ObservableCollection<_>) = - let collection = Collection<_> () - observableCollection.CollectionChanged.Add collection.Add - collection + let collection = Collection<_>() + observableCollection.CollectionChanged.Add collection.Add + collection let private testObservableCollectionContainsDataInArray observableCollection array = - let actual = observableCollection |> Seq.toList - let expected = array |> Array.toList - test <@ expected = actual @> + let actual = observableCollection |> Seq.toList + let expected = array |> Array.toList + test <@ expected = actual @> [] -let ``starting from empty, when items merged, should contain those items and call create exactly once for each item and never call update`` () = - Property.check <| property { - let! array = GenX.auto - - let observableCollection = ObservableCollection<_> () - let createTracker = InvokeTester2 createAsId - let updateTracker = InvokeTester3 updateNoOp - - merge getIdAsId getIdAsId createTracker.Fn updateTracker.Fn (observableCollection |> CollectionTarget.create) array - - testObservableCollectionContainsDataInArray observableCollection array - test <@ createTracker.Count = array.Length @> - test <@ updateTracker.Count = 0 @> - } +let ``starting from empty, when items merged, should contain those items and call create exactly once for each item and never call update`` + () + = + Property.check + <| property { + let! array = GenX.auto + + let observableCollection = ObservableCollection<_>() + let createTracker = InvokeTester2 createAsId + let updateTracker = InvokeTester3 updateNoOp + + merge + getIdAsId + getIdAsId + createTracker.Fn + updateTracker.Fn + (observableCollection |> CollectionTarget.create) + array + + testObservableCollectionContainsDataInArray observableCollection array + test <@ createTracker.Count = array.Length @> + test <@ updateTracker.Count = 0 @> + } [] -let ``starting with random items, when merging the same items, should still contain those items and never call create and call update exactly once for each item and trigger no CC event`` () = - Property.check <| property { - let! array = GenX.auto - - let observableCollection = ObservableCollection<_> array - let createTracker = InvokeTester2 createAsId - let updateTracker = InvokeTester3 updateNoOp - let ccEvents = trackCC observableCollection - - merge getIdAsId getIdAsId createTracker.Fn updateTracker.Fn (observableCollection |> CollectionTarget.create) array - - testObservableCollectionContainsDataInArray observableCollection array - test <@ createTracker.Count = 0 @> - test <@ updateTracker.Count = array.Length @> - test <@ ccEvents.Count = 0 @> - } +let ``starting with random items, when merging the same items, should still contain those items and never call create and call update exactly once for each item and trigger no CC event`` + () + = + Property.check + <| property { + let! array = GenX.auto + + let observableCollection = ObservableCollection<_> array + let createTracker = InvokeTester2 createAsId + let updateTracker = InvokeTester3 updateNoOp + let ccEvents = trackCC observableCollection + + merge + getIdAsId + getIdAsId + createTracker.Fn + updateTracker.Fn + (observableCollection |> CollectionTarget.create) + array + + testObservableCollectionContainsDataInArray observableCollection array + test <@ createTracker.Count = 0 @> + test <@ updateTracker.Count = array.Length @> + test <@ ccEvents.Count = 0 @> + } [] let ``starting with random items, when merging random items, should contain the random items`` () = - Property.check <| property { - let! array1 = GenX.auto - let! array2 = GenX.auto + Property.check + <| property { + let! array1 = GenX.auto + let! array2 = GenX.auto - let observableCollection = ObservableCollection<_> array1 + let observableCollection = ObservableCollection<_> array1 - merge getIdAsId getIdAsId createAsId updateNoOp (observableCollection |> CollectionTarget.create) array2 + merge getIdAsId getIdAsId createAsId updateNoOp (observableCollection |> CollectionTarget.create) array2 - testObservableCollectionContainsDataInArray observableCollection array2 - } + testObservableCollectionContainsDataInArray observableCollection array2 + } [] -let ``starting with random items, when merging after an addition, should contain the merged items and call create exactly once and call update exactly once for each original item`` () = - Property.check <| property { - let! list1 = GenX.auto - let! addedItem = Gen.guid - let! list2 = list1 |> Gen.constant |> GenX.addElement addedItem - - let observableCollection = ObservableCollection<_> list1 - let array2 = list2 |> List.toArray - let createTracker = InvokeTester2 createAsId - let updateTracker = InvokeTester3 updateNoOp - - merge getIdAsId getIdAsId createTracker.Fn updateTracker.Fn (observableCollection |> CollectionTarget.create) array2 - - testObservableCollectionContainsDataInArray observableCollection array2 - test <@ createTracker.Count = 1 @> - test <@ updateTracker.Count = array2.Length - 1 @> - } +let ``starting with random items, when merging after an addition, should contain the merged items and call create exactly once and call update exactly once for each original item`` + () + = + Property.check + <| property { + let! list1 = GenX.auto + let! addedItem = Gen.guid + let! list2 = list1 |> Gen.constant |> GenX.addElement addedItem + + let observableCollection = ObservableCollection<_> list1 + let array2 = list2 |> List.toArray + let createTracker = InvokeTester2 createAsId + let updateTracker = InvokeTester3 updateNoOp + + merge + getIdAsId + getIdAsId + createTracker.Fn + updateTracker.Fn + (observableCollection |> CollectionTarget.create) + array2 + + testObservableCollectionContainsDataInArray observableCollection array2 + test <@ createTracker.Count = 1 @> + test <@ updateTracker.Count = array2.Length - 1 @> + } [] -let ``starting with random items, when merging after a removal, should contain the merged items and never call create and call update exactly once for each remaining item`` () = - Property.check <| property { - let! list2 = GenX.auto - let! removedItem = Gen.guid - let! list1 = list2 |> Gen.constant |> GenX.addElement removedItem - - let observableCollection = ObservableCollection<_> list1 - let array2 = list2 |> List.toArray - let createTracker = InvokeTester2 createAsId - let updateTracker = InvokeTester3 updateNoOp - - merge getIdAsId getIdAsId createTracker.Fn updateTracker.Fn (observableCollection |> CollectionTarget.create) array2 - - testObservableCollectionContainsDataInArray observableCollection array2 - test <@ createTracker.Count = 0 @> - test <@ updateTracker.Count = array2.Length @> - } +let ``starting with random items, when merging after a removal, should contain the merged items and never call create and call update exactly once for each remaining item`` + () + = + Property.check + <| property { + let! list2 = GenX.auto + let! removedItem = Gen.guid + let! list1 = list2 |> Gen.constant |> GenX.addElement removedItem + + let observableCollection = ObservableCollection<_> list1 + let array2 = list2 |> List.toArray + let createTracker = InvokeTester2 createAsId + let updateTracker = InvokeTester3 updateNoOp + + merge + getIdAsId + getIdAsId + createTracker.Fn + updateTracker.Fn + (observableCollection |> CollectionTarget.create) + array2 + + testObservableCollectionContainsDataInArray observableCollection array2 + test <@ createTracker.Count = 0 @> + test <@ updateTracker.Count = array2.Length @> + } [] -let ``starting with random items, when merging after a move, should contain the merged items and never call create and call update exactly once for each item`` () = - Property.check <| property { - let! list = GenX.auto - let! movedItem = Gen.guid - let! additionalItem = Gen.guid - let! i1 = (0, list.Length + 1) ||> Range.constant |> Gen.int32 - let! i2 = (0, list.Length + 1) ||> Range.constant |> Gen.int32 |> GenX.notEqualTo i1 - - let list = additionalItem :: list - let list1 = list |> List.insert i1 movedItem - let array2 = list |> List.insert i2 movedItem |> List.toArray - let observableCollection = ObservableCollection<_> list1 - let createTracker = InvokeTester2 createAsId - let updateTracker = InvokeTester3 updateNoOp - - merge getIdAsId getIdAsId createTracker.Fn updateTracker.Fn (observableCollection |> CollectionTarget.create) array2 - - testObservableCollectionContainsDataInArray observableCollection array2 - test <@ createTracker.Count = 0 @> - test <@ updateTracker.Count = array2.Length @> - } +let ``starting with random items, when merging after a move, should contain the merged items and never call create and call update exactly once for each item`` + () + = + Property.check + <| property { + let! list = GenX.auto + let! movedItem = Gen.guid + let! additionalItem = Gen.guid + let! i1 = (0, list.Length + 1) ||> Range.constant |> Gen.int32 + let! i2 = (0, list.Length + 1) ||> Range.constant |> Gen.int32 |> GenX.notEqualTo i1 + + let list = additionalItem :: list + let list1 = list |> List.insert i1 movedItem + let array2 = list |> List.insert i2 movedItem |> List.toArray + let observableCollection = ObservableCollection<_> list1 + let createTracker = InvokeTester2 createAsId + let updateTracker = InvokeTester3 updateNoOp + + merge + getIdAsId + getIdAsId + createTracker.Fn + updateTracker.Fn + (observableCollection |> CollectionTarget.create) + array2 + + testObservableCollectionContainsDataInArray observableCollection array2 + test <@ createTracker.Count = 0 @> + test <@ updateTracker.Count = array2.Length @> + } [] -let ``starting with random items, when merging after a replacement, should contain the merged items and call create exactly once and call update exactly once for each original item that remains`` () = - Property.check <| property { - let! list1Head = Gen.guid - let! list1Tail = GenX.auto - let! list2Replacement = Gen.guid - let! replcementIndex = (0, list1Tail.Length) ||> Range.constant |> Gen.int32 - - let list1 = list1Head :: list1Tail - let observableCollection = ObservableCollection<_> list1 - let array2 = - list1 - |> List.replace replcementIndex list2Replacement - |> List.toArray - let createTracker = InvokeTester2 createAsId - let updateTracker = InvokeTester3 updateNoOp - - merge getIdAsId getIdAsId createTracker.Fn updateTracker.Fn (observableCollection |> CollectionTarget.create) array2 - - testObservableCollectionContainsDataInArray observableCollection array2 - test <@ createTracker.Count = 1 @> - test <@ updateTracker.Count = array2.Length - 1 @> - } +let ``starting with random items, when merging after a replacement, should contain the merged items and call create exactly once and call update exactly once for each original item that remains`` + () + = + Property.check + <| property { + let! list1Head = Gen.guid + let! list1Tail = GenX.auto + let! list2Replacement = Gen.guid + let! replcementIndex = (0, list1Tail.Length) ||> Range.constant |> Gen.int32 + + let list1 = list1Head :: list1Tail + let observableCollection = ObservableCollection<_> list1 + let array2 = list1 |> List.replace replcementIndex list2Replacement |> List.toArray + let createTracker = InvokeTester2 createAsId + let updateTracker = InvokeTester3 updateNoOp + + merge + getIdAsId + getIdAsId + createTracker.Fn + updateTracker.Fn + (observableCollection |> CollectionTarget.create) + array2 + + testObservableCollectionContainsDataInArray observableCollection array2 + test <@ createTracker.Count = 1 @> + test <@ updateTracker.Count = array2.Length - 1 @> + } [] -let ``starting with random items, when merging after swapping two adjacent items, should contain the merged items and never call create and call update exactly once for each item`` () = - Property.check <| property { - let! list1 = Gen.guid |> Gen.list (Range.constant 2 50) - let! firstSwapIndex = (0, list1.Length - 2) ||> Range.constant |> Gen.int32 - - let observableCollection = ObservableCollection<_> list1 - let array2 = - list1 - |> List.swap firstSwapIndex (firstSwapIndex + 1) - |> List.toArray - let createTracker = InvokeTester2 createAsId - let updateTracker = InvokeTester3 updateNoOp - - merge getIdAsId getIdAsId createTracker.Fn updateTracker.Fn (observableCollection |> CollectionTarget.create) array2 - - testObservableCollectionContainsDataInArray observableCollection array2 - test <@ createTracker.Count = 0 @> - test <@ updateTracker.Count = array2.Length @> - } +let ``starting with random items, when merging after swapping two adjacent items, should contain the merged items and never call create and call update exactly once for each item`` + () + = + Property.check + <| property { + let! list1 = Gen.guid |> Gen.list (Range.constant 2 50) + let! firstSwapIndex = (0, list1.Length - 2) ||> Range.constant |> Gen.int32 + + let observableCollection = ObservableCollection<_> list1 + let array2 = list1 |> List.swap firstSwapIndex (firstSwapIndex + 1) |> List.toArray + let createTracker = InvokeTester2 createAsId + let updateTracker = InvokeTester3 updateNoOp + + merge + getIdAsId + getIdAsId + createTracker.Fn + updateTracker.Fn + (observableCollection |> CollectionTarget.create) + array2 + + testObservableCollectionContainsDataInArray observableCollection array2 + test <@ createTracker.Count = 0 @> + test <@ updateTracker.Count = array2.Length @> + } [] -let ``starting with random items, when merging after swapping two items, should contain the merged items and never call create and call update exactly once for each item`` () = - Property.check <| property { - let! list1 = Gen.guid |> Gen.list (Range.constant 2 50) - let! i = (0, list1.Length - 1) ||> Range.constant |> Gen.int32 - let! j = (0, list1.Length - 1) ||> Range.constant |> Gen.int32 |> GenX.notEqualTo i - - let observableCollection = ObservableCollection<_> list1 - let array2 = - list1 - |> List.swap i j - |> List.toArray - let createTracker = InvokeTester2 createAsId - let updateTracker = InvokeTester3 updateNoOp - - merge getIdAsId getIdAsId createTracker.Fn updateTracker.Fn (observableCollection |> CollectionTarget.create) array2 - - testObservableCollectionContainsDataInArray observableCollection array2 - test <@ createTracker.Count = 0 @> - test <@ updateTracker.Count = array2.Length @> - } +let ``starting with random items, when merging after swapping two items, should contain the merged items and never call create and call update exactly once for each item`` + () + = + Property.check + <| property { + let! list1 = Gen.guid |> Gen.list (Range.constant 2 50) + let! i = (0, list1.Length - 1) ||> Range.constant |> Gen.int32 + let! j = (0, list1.Length - 1) ||> Range.constant |> Gen.int32 |> GenX.notEqualTo i + + let observableCollection = ObservableCollection<_> list1 + let array2 = list1 |> List.swap i j |> List.toArray + let createTracker = InvokeTester2 createAsId + let updateTracker = InvokeTester3 updateNoOp + + merge + getIdAsId + getIdAsId + createTracker.Fn + updateTracker.Fn + (observableCollection |> CollectionTarget.create) + array2 + + testObservableCollectionContainsDataInArray observableCollection array2 + test <@ createTracker.Count = 0 @> + test <@ updateTracker.Count = array2.Length @> + } [] -let ``starting with random items, when merging after shuffling, should contain the merged items and never call create and call update eactly once for each item`` () = - Property.check <| property { - let! list1 = Gen.guid |> Gen.list (Range.constant 2 50) - let! list2 = list1 |> GenX.shuffle |> GenX.notEqualTo list1 - - let observableCollection = ObservableCollection<_> list1 - let array2 = list2 |> List.toArray - let createTracker = InvokeTester2 createAsId - let updateTracker = InvokeTester3 updateNoOp - - merge getIdAsId getIdAsId createTracker.Fn updateTracker.Fn (observableCollection |> CollectionTarget.create) array2 - - testObservableCollectionContainsDataInArray observableCollection array2 - test <@ createTracker.Count = 0 @> - test <@ updateTracker.Count = array2.Length @> - } - -type TestClass (id: int, data: string) = - member _.Id = id - member _.Data = data - override __.GetHashCode() = 0 - override __.Equals that = - // All instances of TestClass are considered equal. - // Not very helpful, but a valid implementation. - that :? TestClass +let ``starting with random items, when merging after shuffling, should contain the merged items and never call create and call update eactly once for each item`` + () + = + Property.check + <| property { + let! list1 = Gen.guid |> Gen.list (Range.constant 2 50) + let! list2 = list1 |> GenX.shuffle |> GenX.notEqualTo list1 + + let observableCollection = ObservableCollection<_> list1 + let array2 = list2 |> List.toArray + let createTracker = InvokeTester2 createAsId + let updateTracker = InvokeTester3 updateNoOp + + merge + getIdAsId + getIdAsId + createTracker.Fn + updateTracker.Fn + (observableCollection |> CollectionTarget.create) + array2 + + testObservableCollectionContainsDataInArray observableCollection array2 + test <@ createTracker.Count = 0 @> + test <@ updateTracker.Count = array2.Length @> + } + +type TestClass(id: int, data: string) = + member _.Id = id + member _.Data = data + override __.GetHashCode() = 0 + + override __.Equals that = + // All instances of TestClass are considered equal. + // Not very helpful, but a valid implementation. + that :? TestClass [] -let ``starting with two TestClass instances, when merging after removing the last one, should trigger CC-Remove for removed item`` () = - // In test name, using "CC-Remove" instead of "CC.Remove" to avoid this bug: - // https://developercommunity.visualstudio.com/t/test-explorer-doesnt-show-tests-correctly-when-dot/297822 - Property.check <| property { - let! id1 = GenX.auto - let! id2 = GenX.auto |> GenX.notEqualTo id1 - let! data1 = GenX.auto - let! data2 = GenX.auto - - let tc1 = TestClass(id1, data1) - let tc2 = TestClass(id2, data2) - let array1 = [| tc1; tc2 |] - let array2 = [| tc1 |] - let observableCollection = ObservableCollection<_> array1 - let ccEvents = trackCC observableCollection - let getId (tc: TestClass) = tc.Id - - merge getId getId createAsId updateNoOp (observableCollection |> CollectionTarget.create) array2 - - test <@ ((ccEvents - |> Seq.filter (fun e -> e.Action = NotifyCollectionChangedAction.Remove) - |> Seq.head).OldItems.[0] :?> TestClass).Id = tc2.Id @> - } +let ``starting with two TestClass instances, when merging after removing the last one, should trigger CC-Remove for removed item`` + () + = + // In test name, using "CC-Remove" instead of "CC.Remove" to avoid this bug: + // https://developercommunity.visualstudio.com/t/test-explorer-doesnt-show-tests-correctly-when-dot/297822 + Property.check + <| property { + let! id1 = GenX.auto + let! id2 = GenX.auto |> GenX.notEqualTo id1 + let! data1 = GenX.auto + let! data2 = GenX.auto + + let tc1 = TestClass(id1, data1) + let tc2 = TestClass(id2, data2) + let array1 = [| tc1; tc2 |] + let array2 = [| tc1 |] + let observableCollection = ObservableCollection<_> array1 + let ccEvents = trackCC observableCollection + let getId (tc: TestClass) = tc.Id + + merge getId getId createAsId updateNoOp (observableCollection |> CollectionTarget.create) array2 + + test + <@ + ((ccEvents + |> Seq.filter (fun e -> e.Action = NotifyCollectionChangedAction.Remove) + |> Seq.head) + .OldItems.[0] + :?> TestClass) + .Id = tc2.Id + @> + } [] -let ``starting with two TestClass instances, when merging after updating the last one, should call update on updated item`` () = - Property.check <| property { - let! id1 = GenX.auto - let! id2 = GenX.auto |> GenX.notEqualTo id1 - let! data1 = GenX.auto - let! data2 = GenX.auto - let! data3 = GenX.auto |> GenX.notEqualTo data2 - - let tc1 = TestClass(id1, data1) - let tc2 = TestClass(id2, data2) - let tc3 = TestClass(id2, data3) - let array1 = [| tc1; tc2 |] - let array2 = [| tc1; tc3 |] - let observableCollection = ObservableCollection<_> array1 - let getId (tc: TestClass) = tc.Id - - let mutable mTarget = None - let update target _ _ = - mTarget <- Some target - - merge getId getId createAsId update (observableCollection |> CollectionTarget.create) array2 - - let actual = mTarget - test <@ actual.Value.Id = tc2.Id @> - } +let ``starting with two TestClass instances, when merging after updating the last one, should call update on updated item`` + () + = + Property.check + <| property { + let! id1 = GenX.auto + let! id2 = GenX.auto |> GenX.notEqualTo id1 + let! data1 = GenX.auto + let! data2 = GenX.auto + let! data3 = GenX.auto |> GenX.notEqualTo data2 + + let tc1 = TestClass(id1, data1) + let tc2 = TestClass(id2, data2) + let tc3 = TestClass(id2, data3) + let array1 = [| tc1; tc2 |] + let array2 = [| tc1; tc3 |] + let observableCollection = ObservableCollection<_> array1 + let getId (tc: TestClass) = tc.Id + + let mutable mTarget = None + let update target _ _ = mTarget <- Some target + + merge getId getId createAsId update (observableCollection |> CollectionTarget.create) array2 + + let actual = mTarget + test <@ actual.Value.Id = tc2.Id @> + } \ No newline at end of file diff --git a/src/Elmish.WPF.Tests/StaticViewModelTests.fs b/src/Elmish.WPF.Tests/StaticViewModelTests.fs index a494fcb7..5f4ddf59 100644 --- a/src/Elmish.WPF.Tests/StaticViewModelTests.fs +++ b/src/Elmish.WPF.Tests/StaticViewModelTests.fs @@ -15,69 +15,68 @@ open Elmish.WPF -type internal TestVm<'model, 'msg, 'B1>(model, binding: string -> Binding<'model,'msg>) as this = - inherit ViewModelBase<'model, 'msg>({ initialModel = model; dispatch = (fun x -> this.Dispatch x); loggingArgs = LoggingViewModelArgs.none }) +type internal TestVm<'model, 'msg, 'B1>(model, binding: string -> Binding<'model, 'msg>) as this = + inherit + ViewModelBase<'model, 'msg>( + { initialModel = model + dispatch = (fun x -> this.Dispatch x) + loggingArgs = LoggingViewModelArgs.none } + ) - let pcTriggers = ConcurrentDictionary() - let ecTriggers = ConcurrentDictionary() - let ccTriggers = ConcurrentDictionary() - let cecTriggers = ConcurrentDictionary() - let dispatchMsgs = ResizeArray<'msg> () + let pcTriggers = ConcurrentDictionary() + let ecTriggers = ConcurrentDictionary() + let ccTriggers = + ConcurrentDictionary() - do - (this :> INotifyPropertyChanged).PropertyChanged.Add (fun e -> - pcTriggers.AddOrUpdate(e.PropertyName, 1, (fun _ count -> count + 1)) |> ignore - ) + let cecTriggers = ConcurrentDictionary() + let dispatchMsgs = ResizeArray<'msg>() - (this :> INotifyDataErrorInfo).ErrorsChanged.Add (fun e -> - ecTriggers.AddOrUpdate(e.PropertyName, 1, (fun _ count -> count + 1)) |> ignore - ) - member _.UpdateModel(m) = IViewModel.updateModel(this, m) + do + (this :> INotifyPropertyChanged) + .PropertyChanged.Add(fun e -> + pcTriggers.AddOrUpdate(e.PropertyName, 1, (fun _ count -> count + 1)) |> ignore) - member x.GetPropertyName = nameof(x.GetProperty) - member _.GetProperty = base.Get<'B1>() (binding >> Binding.unboxT) + (this :> INotifyDataErrorInfo) + .ErrorsChanged.Add(fun e -> ecTriggers.AddOrUpdate(e.PropertyName, 1, (fun _ count -> count + 1)) |> ignore) - member private __.Dispatch x = - dispatchMsgs.Add x + member _.UpdateModel(m) = IViewModel.updateModel (this, m) - member __.NumPcTriggersFor propName = - pcTriggers.TryGetValue propName |> snd + member x.GetPropertyName = nameof (x.GetProperty) + member _.GetProperty = base.Get<'B1> () (binding >> Binding.unboxT) - member __.NumEcTriggersFor propName = - ecTriggers.TryGetValue propName |> snd + member private __.Dispatch x = dispatchMsgs.Add x - member __.NumCcTriggersFor propName = - ccTriggers.GetOrAdd(propName, []).Length + member __.NumPcTriggersFor propName = pcTriggers.TryGetValue propName |> snd - member __.NumCecTriggersFor propName = - cecTriggers.TryGetValue propName |> snd + member __.NumEcTriggersFor propName = ecTriggers.TryGetValue propName |> snd - member __.Dispatches = - dispatchMsgs |> Seq.toList + member __.NumCcTriggersFor propName = + ccTriggers.GetOrAdd(propName, []).Length - member __.CcTriggersFor propName = - ccTriggers.TryGetValue propName |> snd |> Seq.toList + member __.NumCecTriggersFor propName = cecTriggers.TryGetValue propName |> snd - /// Starts tracking CollectionChanged triggers for the specified prop. - /// Will cause the property to be retrieved. - member this.TrackCcTriggersForGetProperty () = - (this.GetProperty |> unbox).CollectionChanged.Add - (fun e -> - ccTriggers.AddOrUpdate( - this.GetPropertyName, - [e], - (fun _ me -> e :: me)) |> ignore - ) + member __.Dispatches = dispatchMsgs |> Seq.toList - /// Starts tracking CanExecuteChanged triggers for the specified prop. - /// Will cause the property to be retrieved. - member this.TrackCecTriggersForGetProperty () = - (this.GetProperty |> unbox).CanExecuteChanged.Add - (fun _ -> - cecTriggers.AddOrUpdate(this.GetPropertyName, 1, (fun _ count -> count + 1)) |> ignore - ) + member __.CcTriggersFor propName = + ccTriggers.TryGetValue propName |> snd |> Seq.toList + + /// Starts tracking CollectionChanged triggers for the specified prop. + /// Will cause the property to be retrieved. + member this.TrackCcTriggersForGetProperty() = + (this.GetProperty |> unbox) + .CollectionChanged.Add(fun e -> + ccTriggers.AddOrUpdate(this.GetPropertyName, [ e ], (fun _ me -> e :: me)) + |> ignore) + + /// Starts tracking CanExecuteChanged triggers for the specified prop. + /// Will cause the property to be retrieved. + member this.TrackCecTriggersForGetProperty() = + (this.GetProperty |> unbox) + .CanExecuteChanged.Add(fun _ -> + cecTriggers.AddOrUpdate(this.GetPropertyName, 1, (fun _ count -> count + 1)) + |> ignore) @@ -85,263 +84,282 @@ type internal TestVm<'model, 'msg, 'B1>(model, binding: string -> Binding<'model module Helpers = - let internal oneWay x = x |> Binding.oneWay - let internal oneWayLazy x = x |> Func3.curry Binding.oneWayLazy - let internal oneWaySeqLazy x = x |> Func5.curry Binding.oneWaySeqLazy - let internal twoWay x = x |> Func2.curry Binding.twoWay - let internal twoWayValidate - name - (get: 'model -> 'a) - (set: 'a -> 'model -> 'msg) - (validate: 'model -> string voption) = - Binding.twoWayValidate (get, set, validate) name + let internal oneWay x = x |> Binding.oneWay + let internal oneWayLazy x = x |> Func3.curry Binding.oneWayLazy + let internal oneWaySeqLazy x = x |> Func5.curry Binding.oneWaySeqLazy + let internal twoWay x = x |> Func2.curry Binding.twoWay + let internal twoWayValidate + name + (get: 'model -> 'a) + (set: 'a -> 'model -> 'msg) + (validate: 'model -> string voption) + = + Binding.twoWayValidate (get, set, validate) name - let internal cmd x = x |> Binding.Cmd.create + let internal cmd x = x |> Binding.Cmd.create - let internal cmdParam - name - (exec: 'a -> 'model -> 'msg voption) - (canExec: 'a -> 'model -> bool) - (autoRequery: bool) = - ({ Exec = unbox >> exec - CanExec = unbox >> canExec - AutoRequery = autoRequery } - |> CmdData - |> BaseBindingData - |> createBinding) name + let internal cmdParam + name + (exec: 'a -> 'model -> 'msg voption) + (canExec: 'a -> 'model -> bool) + (autoRequery: bool) + = + ({ Exec = unbox >> exec + CanExec = unbox >> canExec + AutoRequery = autoRequery } + |> CmdData + |> BaseBindingData + |> createBinding) + name - let internal subModel - name - (getModel: 'model -> 'subModel voption) - (toMsg: 'subMsg -> 'msg) - (bindings: Binding<'subModel, 'subMsg> list) - (sticky: bool) = - Binding.subModelOpt(getModel, snd, toMsg, (fun () -> bindings), sticky) name + let internal subModel + name + (getModel: 'model -> 'subModel voption) + (toMsg: 'subMsg -> 'msg) + (bindings: Binding<'subModel, 'subMsg> list) + (sticky: bool) + = + Binding.subModelOpt (getModel, snd, toMsg, (fun () -> bindings), sticky) name - let internal subModelSeq - name - (getModels: 'model -> 'subModel list) - (getId: 'subModel -> 'id) - (toMsg: 'id * 'subMsg -> 'msg) - (bindings: Binding<'subModel, 'subMsg> list) = - name - |> Binding.subModelSeq (getBindings = (fun () -> bindings), getId = getId) - |> Binding.mapModel (fun m -> upcast getModels m) - |> Binding.mapMsg toMsg + let internal subModelSeq + name + (getModels: 'model -> 'subModel list) + (getId: 'subModel -> 'id) + (toMsg: 'id * 'subMsg -> 'msg) + (bindings: Binding<'subModel, 'subMsg> list) + = + name + |> Binding.subModelSeq (getBindings = (fun () -> bindings), getId = getId) + |> Binding.mapModel (fun m -> upcast getModels m) + |> Binding.mapMsg toMsg - let internal subModelSelectedItem - name - subModelSeqBindingName - (get: 'model -> 'id voption) - (set: 'id voption -> 'model -> 'msg) = - Binding.subModelSelectedItem (subModelSeqBindingName, get, set) name + + let internal subModelSelectedItem + name + subModelSeqBindingName + (get: 'model -> 'id voption) + (set: 'id voption -> 'model -> 'msg) + = + Binding.subModelSelectedItem (subModelSeqBindingName, get, set) name module OneWay = - [] - let ``when retrieved, should always return the value returned by get`` () = - Property.check <| property { - let! m1 = GenX.auto - let! m2 = GenX.auto + [] + let ``when retrieved, should always return the value returned by get`` () = + Property.check + <| property { + let! m1 = GenX.auto + let! m2 = GenX.auto - let get = string + let get = string - let binding = oneWay get - let vm = TestVm(m1, binding) + let binding = oneWay get + let vm = TestVm(m1, binding) - test <@ vm.GetProperty = get m1 @> + test <@ vm.GetProperty = get m1 @> - vm.UpdateModel m2 + vm.UpdateModel m2 - test <@ vm.GetProperty = get m2 @> - } + test <@ vm.GetProperty = get m2 @> + } - [] - let ``when model is updated, should trigger PC once iff the return value of get changes`` () = - Property.check <| property { - let! m1 = GenX.auto - let! m2 = GenX.auto + [] + let ``when model is updated, should trigger PC once iff the return value of get changes`` () = + Property.check + <| property { + let! m1 = GenX.auto + let! m2 = GenX.auto - let get = string + let get = string - let binding = oneWay get - let vm = TestVm(m1, binding) - let _ = vm.GetProperty + let binding = oneWay get + let vm = TestVm(m1, binding) + let _ = vm.GetProperty - vm.UpdateModel m2 - test <@ vm.NumPcTriggersFor vm.GetPropertyName = if get m1 = get m2 then 0 else 1 @> - } + vm.UpdateModel m2 + test <@ vm.NumPcTriggersFor vm.GetPropertyName = if get m1 = get m2 then 0 else 1 @> + } - [] - let ``on model increment, sticky-to-even binding returns even number`` () = - let isEven x = x % 2 = 0 + [] + let ``on model increment, sticky-to-even binding returns even number`` () = + let isEven x = x % 2 = 0 - let returnEven a = - function - | b when isEven b -> b - | _ -> a + let returnEven a = + function + | b when isEven b -> b + | _ -> a - Property.check <| property { - let! m = GenX.auto + Property.check + <| property { + let! m = GenX.auto - let binding = oneWay id >> Binding.addSticky isEven - let vm = TestVm(m, binding) + let binding = oneWay id >> Binding.addSticky isEven + let vm = TestVm(m, binding) - vm.UpdateModel (m + 1) - test <@ vm.GetProperty = returnEven m (m + 1) @> - } + vm.UpdateModel(m + 1) + test <@ vm.GetProperty = returnEven m (m + 1) @> + } module OneWayLazy = - [] - let ``when retrieved initially, should return the value returned by map`` () = - Property.check <| property { - let! m = GenX.auto + [] + let ``when retrieved initially, should return the value returned by map`` () = + Property.check + <| property { + let! m = GenX.auto + + let get = string + let equals = (=) + let map = String.length - let get = string - let equals = (=) - let map = String.length + let binding = oneWayLazy get equals map + let vm = TestVm(m, binding) - let binding = oneWayLazy get equals map - let vm = TestVm(m, binding) + test <@ vm.GetProperty = (m |> get |> map) @> + } - test <@ vm.GetProperty = (m |> get |> map) @> - } + [] + let ``when retrieved after update and equals returns false, should return the value returned by map`` () = + Property.check + <| property { + let! m1 = GenX.auto + let! m2 = GenX.auto - [] - let ``when retrieved after update and equals returns false, should return the value returned by map`` () = - Property.check <| property { - let! m1 = GenX.auto - let! m2 = GenX.auto + let get = string + let equals _ _ = false + let map = String.length - let get = string - let equals _ _ = false - let map = String.length + let binding = oneWayLazy get equals map + let vm = TestVm(m1, binding) + vm.UpdateModel m2 - let binding = oneWayLazy get equals map - let vm = TestVm(m1, binding) - vm.UpdateModel m2 + test <@ vm.GetProperty = (m2 |> get |> map) @> + } - test <@ vm.GetProperty = (m2 |> get |> map) @> - } + [] + let ``when retrieved after update and equals returns true, should return the previous value returned by map`` () = + Property.check + <| property { + let! m1 = GenX.auto + let! m2 = GenX.auto - [] - let ``when retrieved after update and equals returns true, should return the previous value returned by map`` () = - Property.check <| property { - let! m1 = GenX.auto - let! m2 = GenX.auto + let get = string + let equals _ _ = true + let map = String.length - let get = string - let equals _ _ = true - let map = String.length + let binding = oneWayLazy get equals map + let vm = TestVm(m1, binding) + let _ = vm.GetProperty // populate cache + vm.UpdateModel m2 - let binding = oneWayLazy get equals map - let vm = TestVm(m1, binding) - let _ = vm.GetProperty // populate cache - vm.UpdateModel m2 + test <@ vm.GetProperty = (m1 |> get |> map) @> + } - test <@ vm.GetProperty = (m1 |> get |> map) @> - } + [] + let ``when retrieved, updated, and retrieved again, should call map once after the update iff equals returns false`` + () + = + Property.check + <| property { + let! m1 = GenX.auto + let! m2 = GenX.auto + let! eq = Gen.bool - [] - let ``when retrieved, updated, and retrieved again, should call map once after the update iff equals returns false`` () = - Property.check <| property { - let! m1 = GenX.auto - let! m2 = GenX.auto - let! eq = Gen.bool + let get = string + let equals _ _ = eq + let map = InvokeTester String.length - let get = string - let equals _ _ = eq - let map = InvokeTester String.length + let binding = oneWayLazy get equals map.Fn + let vm = TestVm(m1, binding) - let binding = oneWayLazy get equals map.Fn - let vm = TestVm(m1, binding) - - let _ = vm.GetProperty - vm.UpdateModel m2 - map.Reset () - let _ = vm.GetProperty + let _ = vm.GetProperty + vm.UpdateModel m2 + map.Reset() + let _ = vm.GetProperty - test <@ map.Count = if eq then 0 else 1 @> - } + test <@ map.Count = if eq then 0 else 1 @> + } - [] - let ``map should never be called during model update`` () = - Property.check <| property { - let! m1 = GenX.auto - let! m2 = GenX.auto + [] + let ``map should never be called during model update`` () = + Property.check + <| property { + let! m1 = GenX.auto + let! m2 = GenX.auto - let get = string - let equals = (=) - let map = InvokeTester String.length + let get = string + let equals = (=) + let map = InvokeTester String.length - let binding = oneWayLazy get equals map.Fn - let vm = TestVm(m1, binding) - let _ = vm.GetProperty + let binding = oneWayLazy get equals map.Fn + let vm = TestVm(m1, binding) + let _ = vm.GetProperty - test <@ map.Count = 1 @> + test <@ map.Count = 1 @> - vm.UpdateModel m2 + vm.UpdateModel m2 - test <@ map.Count = 1 @> - } + test <@ map.Count = 1 @> + } - [] - let ``when retrieved several times between updates, map is called at most once`` () = - Property.check <| property { - let! m1 = GenX.auto - let! m2 = GenX.auto + [] + let ``when retrieved several times between updates, map is called at most once`` () = + Property.check + <| property { + let! m1 = GenX.auto + let! m2 = GenX.auto - let get = string - let equals = (=) - let map = InvokeTester String.length + let get = string + let equals = (=) + let map = InvokeTester String.length - let binding = oneWayLazy get equals map.Fn - let vm = TestVm(m1, binding) - - let _ = vm.GetProperty - let _ = vm.GetProperty - test <@ map.Count <= 1 @> + let binding = oneWayLazy get equals map.Fn + let vm = TestVm(m1, binding) + + let _ = vm.GetProperty + let _ = vm.GetProperty + test <@ map.Count <= 1 @> - map.Reset () - vm.UpdateModel m2 - let _ = vm.GetProperty - let _ = vm.GetProperty - test <@ map.Count <= 1 @> - } + map.Reset() + vm.UpdateModel m2 + let _ = vm.GetProperty + let _ = vm.GetProperty + test <@ map.Count <= 1 @> + } - [] - let ``when model is updated, should trigger PC once iff equals is false`` () = - Property.check <| property { - let! m1 = GenX.auto - let! m2 = GenX.auto - let! eq = Gen.bool + [] + let ``when model is updated, should trigger PC once iff equals is false`` () = + Property.check + <| property { + let! m1 = GenX.auto + let! m2 = GenX.auto + let! eq = Gen.bool + + let get = string + let equals _ _ = eq + let map = String.length - let get = string - let equals _ _ = eq - let map = String.length - - let binding = oneWayLazy get equals map - let vm = TestVm(m1, binding) - let _ = vm.GetProperty - vm.UpdateModel m2 + let binding = oneWayLazy get equals map + let vm = TestVm(m1, binding) + let _ = vm.GetProperty + vm.UpdateModel m2 - test <@ vm.NumPcTriggersFor vm.GetPropertyName = if not eq then 1 else 0 @> - } + test <@ vm.NumPcTriggersFor vm.GetPropertyName = if not eq then 1 else 0 @> + } \ No newline at end of file diff --git a/src/Elmish.WPF.Tests/UtilsTests.fs b/src/Elmish.WPF.Tests/UtilsTests.fs index e8694cf3..5e93975d 100644 --- a/src/Elmish.WPF.Tests/UtilsTests.fs +++ b/src/Elmish.WPF.Tests/UtilsTests.fs @@ -10,159 +10,171 @@ open Elmish.WPF module refEq = - [] - let ``returns true if the arguments are referentially equal`` () = - Property.check <| property { - let! x = GenX.auto - let y = x - test <@ refEq x y = true @> - } - - - [] - let ``returns true if the arguments are not referentially equal`` () = - Property.check <| property { - let! x = GenX.auto - let! y = GenX.auto - test <@ refEq x y = false @> - } - - - -module elmEq = - - - type TestObj = { X: int } - - - module Tuples = - - [] - let ``returns false if any non-string reference type member is not referentially equal`` () = - PropertyConfig.defaultConfig - |> PropertyConfig.withTests 1000 - |> Property.checkWith <| property { - let! x1 = GenX.auto - let! y1 = GenX.auto - let! x2 = GenX.auto - let! y2 = GenX.auto - let! x3 = GenX.auto - let! y3 = GenX.auto - test <@ elmEq (x1, x2, x3) (y1, y2, y3) = false @> - } + let ``returns true if the arguments are referentially equal`` () = + Property.check + <| property { + let! x = GenX.auto + let y = x + test <@ refEq x y = true @> + } [] - let ``returns false if all non-string reference type members are referentially equal and all string and value type members are structurally equal`` () = - PropertyConfig.defaultConfig - |> PropertyConfig.withTests 1000 - |> Property.checkWith <| property { - let! x1 = GenX.auto - let! y1 = GenX.auto - let! x2 = GenX.auto - let! y2 = GenX.auto - let! x3 = GenX.auto - let y3 = x3 - test <@ elmEq (x1, x2, x3) (y1, y2, y3) = (x1 = y1 && x2 = y2) @> - } - + let ``returns true if the arguments are not referentially equal`` () = + Property.check + <| property { + let! x = GenX.auto + let! y = GenX.auto + test <@ refEq x y = false @> + } - module Records = - type TestValues = { i: int; s: string; t: TestObj } +module elmEq = - [] - let ``returns false if any non-string reference type member is not referentially equal`` () = - PropertyConfig.defaultConfig - |> PropertyConfig.withTests 1000 - |> Property.checkWith <| property { - let! t1 = GenX.auto - let! t2 = GenX.auto - test <@ elmEq t1 t2 = false @> - } + type TestObj = { X: int } - [] - let ``returns false if all non-string reference type members are referentially equal and all string and value type members are structurally equal`` () = - PropertyConfig.defaultConfig - |> PropertyConfig.withTests 1000 - |> Property.checkWith <| property { - let! t1 = GenX.auto - let! t2 = GenX.auto - let t2 = { t2 with t = t1.t } - test <@ elmEq t1 t2 = (t1.i = t2.i && t1.s = t2.s) @> - } + module Tuples = -module ValueOption = + [] + let ``returns false if any non-string reference type member is not referentially equal`` () = + PropertyConfig.defaultConfig + |> PropertyConfig.withTests 1000 + |> Property.checkWith + <| property { + let! x1 = GenX.auto + let! y1 = GenX.auto + let! x2 = GenX.auto + let! y2 = GenX.auto + let! x3 = GenX.auto + let! y3 = GenX.auto + test <@ elmEq (x1, x2, x3) (y1, y2, y3) = false @> + } - open System - module toNull = + [] + let ``returns false if all non-string reference type members are referentially equal and all string and value type members are structurally equal`` + () + = + PropertyConfig.defaultConfig + |> PropertyConfig.withTests 1000 + |> Property.checkWith + <| property { + let! x1 = GenX.auto + let! y1 = GenX.auto + let! x2 = GenX.auto + let! y2 = GenX.auto + let! x3 = GenX.auto + let y3 = x3 + test <@ elmEq (x1, x2, x3) (y1, y2, y3) = (x1 = y1 && x2 = y2) @> + } - let testNonNull (ga: Gen<'a>) = - Property.check <| property { - let! expected = ga - test <@ Ok expected = (expected |> ValueSome |> ValueOption.toNull) @> - } - [] - let ``toNull returns contents of ValueSome when given ValueSome`` () = - testNonNull GenX.auto - testNonNull GenX.auto - testNonNull GenX.auto - testNonNull GenX.auto + module Records = - let testNullForNullable<'a when 'a : equality> () = - test <@ Ok Unchecked.defaultof<'a> = ValueOption.toNull<'a> ValueNone @> - [] - let ``toNull returns null when given ValueNone for nullable type`` () = - testNullForNullable () - testNullForNullable () - testNullForNullable> () - testNullForNullable> () + type TestValues = { i: int; s: string; t: TestObj } - let testNullForNonNullable<'a when 'a : equality> () = - let expected = typeof<'a>.Name |> ValueOption.ToNullError.ValueCannotBeNull |> Error - test <@ expected = ValueOption.toNull<'a> ValueNone @> - [] - let ``toNull returns ValueCannotBeNull Error when given ValueNone for non-nullable type`` () = - testNullForNonNullable () - testNullForNonNullable () + [] + let ``returns false if any non-string reference type member is not referentially equal`` () = + PropertyConfig.defaultConfig + |> PropertyConfig.withTests 1000 + |> Property.checkWith + <| property { + let! t1 = GenX.auto + let! t2 = GenX.auto + test <@ elmEq t1 t2 = false @> + } - type Foo = { Foo: unit } - type Bar = Bar of unit - [] - let ``toNull does not throw NullReferenceException given reference type`` () = - ValueOption.toNull ValueNone |> ignore - ValueOption.toNull ValueNone |> ignore - - module ofNull = - - let testNull<'a when 'a : equality> () = - let input = Unchecked.defaultof<'a> - test <@ ValueNone = ValueOption.ofNull input @> + [] + let ``returns false if all non-string reference type members are referentially equal and all string and value type members are structurally equal`` + () + = + PropertyConfig.defaultConfig + |> PropertyConfig.withTests 1000 + |> Property.checkWith + <| property { + let! t1 = GenX.auto + let! t2 = GenX.auto + let t2 = { t2 with t = t1.t } + test <@ elmEq t1 t2 = (t1.i = t2.i && t1.s = t2.s) @> + } - [] - let ``ofNull returns ValueNone when input is null`` () = - testNull () - testNull () - testNull> () - let testNonNull (ga: Gen<'a>) = - Property.check <| property { - let! input = ga - test <@ ValueSome input = ValueOption.ofNull input @> - } +module ValueOption = - [] - let ``ofNull returns ValueSome of input when input is nonnull`` () = - testNonNull GenX.auto - testNonNull GenX.auto - testNonNull GenX.auto + open System + + module toNull = + + let testNonNull (ga: Gen<'a>) = + Property.check + <| property { + let! expected = ga + test <@ Ok expected = (expected |> ValueSome |> ValueOption.toNull) @> + } + + [] + let ``toNull returns contents of ValueSome when given ValueSome`` () = + testNonNull GenX.auto + testNonNull GenX.auto + testNonNull GenX.auto + testNonNull GenX.auto + + let testNullForNullable<'a when 'a: equality> () = + test <@ Ok Unchecked.defaultof<'a> = ValueOption.toNull<'a> ValueNone @> + + [] + let ``toNull returns null when given ValueNone for nullable type`` () = + testNullForNullable () + testNullForNullable () + testNullForNullable> () + testNullForNullable> () + + let testNullForNonNullable<'a when 'a: equality> () = + let expected = typeof<'a>.Name |> ValueOption.ToNullError.ValueCannotBeNull |> Error + test <@ expected = ValueOption.toNull<'a> ValueNone @> + + [] + let ``toNull returns ValueCannotBeNull Error when given ValueNone for non-nullable type`` () = + testNullForNonNullable () + testNullForNonNullable () + + type Foo = { Foo: unit } + type Bar = Bar of unit + + [] + let ``toNull does not throw NullReferenceException given reference type`` () = + ValueOption.toNull ValueNone |> ignore + ValueOption.toNull ValueNone |> ignore + + module ofNull = + + let testNull<'a when 'a: equality> () = + let input = Unchecked.defaultof<'a> + test <@ ValueNone = ValueOption.ofNull input @> + + [] + let ``ofNull returns ValueNone when input is null`` () = + testNull () + testNull () + testNull> () + + let testNonNull (ga: Gen<'a>) = + Property.check + <| property { + let! input = ga + test <@ ValueSome input = ValueOption.ofNull input @> + } + + [] + let ``ofNull returns ValueSome of input when input is nonnull`` () = + testNonNull GenX.auto + testNonNull GenX.auto + testNonNull GenX.auto \ No newline at end of file diff --git a/src/Elmish.WPF/Binding.fs b/src/Elmish.WPF/Binding.fs index c95c56d9..1a71758e 100644 --- a/src/Elmish.WPF/Binding.fs +++ b/src/Elmish.WPF/Binding.fs @@ -1,4190 +1,3944 @@ -namespace Elmish.WPF - -open System.Windows - -open Elmish -open System.Windows.Input -open System.Collections.ObjectModel - - -module Binding = - open BindingData - - let internal mapData f binding = - { Name = binding.Name - Data = binding.Data |> f } - - /// Boxes the output parameter. - /// Allows using a strongly-typed submodel binding (from a module ending in "T") - /// in a binding list (rather than in a view model class member as normal). - let boxT (binding: Binding<'b, 'msg, 't>) = BindingData.boxT |> mapData <| binding - - /// Unboxes the output parameter - let unboxT (binding: Binding<'b, 'msg>): Binding<'b, 'msg, 't> = BindingData.unboxT |> mapData <| binding - - /// Maps the model of a binding via a contravariant mapping. - let mapModel (f: 'a -> 'b) (binding: Binding<'b, 'msg, 't>) = f |> mapModel |> mapData <| binding - - /// Maps the message of a binding with access to the model via a covariant mapping. - let mapMsgWithModel (f: 'a -> 'model -> 'b) (binding: Binding<'model, 'a, 't>) = f |> mapMsgWithModel |> mapData <| binding - - /// Maps the message of a binding via a covariant mapping. - let mapMsg (f: 'a -> 'b) (binding: Binding<'model, 'a, 't>) = f |> mapMsg |> mapData <| binding - - /// Sets the message of a binding with access to the model. - let setMsgWithModel (f: 'model -> 'b) (binding: Binding<'model, 'a, 't>) = f |> setMsgWithModel |> mapData <| binding - - /// Sets the message of a binding. - let setMsg (msg: 'b) (binding: Binding<'model, 'a, 't>) = msg |> setMsg |> mapData <| binding - - - /// Restricts the binding to models that satisfy the predicate after some model satisfies the predicate. - let addSticky (predicate: 'model -> bool) (binding: Binding<'model, 'msg, 't>) = predicate |> addSticky |> mapData <| binding - - /// - /// Adds caching to the given binding. The cache holds a single value and - /// is invalidated after the given binding raises the - /// PropertyChanged event. - /// - /// The binding to which caching is added. - let addCaching (binding: Binding<'model, 'msg, 't>) : Binding<'model, 'msg, 't> = - binding - |> mapData addCaching - - /// - /// Adds validation to the given binding using INotifyDataErrorInfo. - /// - /// Returns the errors associated with the given model. - /// The binding to which validation is added. - let addValidation (validate: 'model -> string list) (binding: Binding<'model, 'msg, 't>) : Binding<'model, 'msg, 't> = - binding - |> mapData (addValidation validate) - - /// - /// Adds laziness to the updating of the given binding. If the models are considered equal, - /// then updating of the given binding is skipped. - /// - /// Updating skipped when this function returns true. - /// The binding to which the laziness is added. - let addLazy (equals: 'model -> 'model -> bool) (binding: Binding<'model, 'msg, 't>) : Binding<'model, 'msg, 't> = - binding - |> mapData (addLazy equals) - - /// - /// Alters the message stream via the given function. - /// Ideally suited for use with Reactive Extensions. - /// - /// open FSharp.Control.Reactive - /// let delay dispatch = - /// let subject = Subject.broadcast - /// let observable = subject :> System.IObservable<_> - /// observable - /// |> Observable.delay (System.TimeSpan.FromSeconds 1.0) - /// |> Observable.subscribe dispatch - /// |> ignore - /// subject.OnNext - /// - /// // ... - /// - /// binding |> Binding.alterMsgStream delay - /// - /// - /// The function that can alter the message stream. - /// The binding of the altered message stream. - let alterMsgStream (alteration: ('b -> unit) -> 'a -> unit) (binding: Binding<'model, 'a, 't>) : Binding<'model, 'b, 't> = - binding - |> mapData (alterMsgStream alteration) - - - /// - /// Strongly-typed bindings that update the view from the model. - /// - module OneWayT = - - /// Elemental instance of a one-way binding. - let id<'a, 'msg> : string -> Binding<'a, 'msg, 'a> = - OneWay.id - |> createBindingT - - /// Creates a one-way binding to an optional value. The binding - /// automatically converts between a missing value in the model and - /// a null value in the view. - let opt x : Binding<'a option, 'msg, System.Nullable<'a>> = - x - |> id - |> mapModel Option.toNullable - - /// Creates a one-way binding to an optional value. The binding - /// automatically converts between a missing value in the model and - /// a null value in the view. - let vopt x : Binding<'a voption, 'msg, System.Nullable<'a>> = - x - |> id - |> mapModel ValueOption.toNullable - - /// Creates a one-way binding to an optional value. The binding - /// automatically converts between a missing value in the model and - /// a null value in the view. - let optobj<'a, 'msg when 'a : null> : string -> Binding<'a option, 'msg, 'a> = - id<'a, 'msg> - >> mapModel Option.toObj - - /// Creates a one-way binding to an optional value. The binding - /// automatically converts between a missing value in the model and - /// a null value in the view. - let voptobj<'a, 'msg when 'a : null> : string -> Binding<'a voption, 'msg, 'a> = - id<'a, 'msg> - >> mapModel ValueOption.toObj - - /// - /// Strongly-typed bindings that update the model from the view. - /// - module OneWayToSourceT = - - /// Elemental instance of a one-way-to-source binding. - let id<'model, 'a> : string -> Binding<'model, 'a, 'a> = - OneWayToSource.id - |> createBindingT - - /// Creates a one-way-to-source binding to an optional value. The binding - /// automatically converts between a missing value in the model and - /// a null value in the view. - let optobj<'a, 'model when 'a : null> : string -> Binding<'model, 'a option, 'a> = - id<'model, 'a> - >> mapMsg Option.ofObj - - /// Creates a one-way-to-source binding to an optional value. The binding - /// automatically converts between a missing value in the model and - /// a null value in the view. - let voptobj<'a, 'model when 'a : null> : string -> Binding<'model, 'a voption, 'a> = - id<'model, 'a> - >> mapMsg ValueOption.ofObj - - /// Creates a one-way-to-source binding to an optional value. The binding - /// automatically converts between a missing value in the model and - /// a null value in the view. - let opt x : Binding<'model, 'a option, System.Nullable<'a>> = - x - |> id - |> mapMsg Option.ofNullable - - /// Creates a one-way-to-source binding to an optional value. The binding - /// automatically converts between a missing value in the model and - /// a null value in the view. - let vopt x : Binding<'model, 'a voption, System.Nullable<'a>> = - x - |> id - |> mapMsg ValueOption.ofNullable - - /// - /// Strongly-typed bindings that update both ways - /// - module TwoWayT = - - /// Elemental instance of a two-way binding. - let id<'a> : string -> Binding<'a, 'a, 'a> = - TwoWay.id - |> createBindingT - - /// Creates a two-way binding to an optional value. The binding - /// automatically converts between a missing value in the model and - /// a null value in the view. - let opt x : Binding<'a option, 'a option, System.Nullable<'a>> = - x - |> id - |> mapMsg Option.ofNullable - |> mapModel Option.toNullable - - /// Creates a two-way binding to an optional value. The binding - /// automatically converts between a missing value in the model and - /// a null value in the view. - let vopt x : Binding<'a voption, 'a voption, System.Nullable<'a>> = - x - |> id - |> mapMsg ValueOption.ofNullable - |> mapModel ValueOption.toNullable - - /// Creates a two-way binding to an optional value. The binding - /// automatically converts between a missing value in the model and - /// a null value in the view. - let optobj<'a when 'a : null> : string -> Binding<'a option, 'a option, 'a> = - id<'a> - >> mapModel Option.toObj - >> mapMsg Option.ofObj - - /// Creates a two-way binding to an optional value. The binding - /// automatically converts between a missing value in the model and - /// a null value in the view. - let voptobj<'a when 'a : null> : string -> Binding<'a voption, 'a voption, 'a> = - id<'a> - >> mapMsg ValueOption.ofObj - >> mapModel ValueOption.toObj - - /// - /// The strongly-typed counterpart of Binding.oneWaySeq with parameter getId. - /// Exposes an ObservableCollection of child items for binding. - /// Allows a more efficient update than would be possible without using ids. - /// - module OneWaySeqT = - +namespace Elmish.WPF + +open System.Windows + +open Elmish +open System.Windows.Input +open System.Collections.ObjectModel + + +module Binding = + open BindingData + + let internal mapData f binding = + { Name = binding.Name + Data = binding.Data |> f } + + /// Boxes the output parameter. + /// Allows using a strongly-typed submodel binding (from a module ending in "T") + /// in a binding list (rather than in a view model class member as normal). + let boxT (binding: Binding<'b, 'msg, 't>) = BindingData.boxT |> mapData <| binding + + /// Unboxes the output parameter + let unboxT (binding: Binding<'b, 'msg>) : Binding<'b, 'msg, 't> = + BindingData.unboxT |> mapData <| binding + + /// Maps the model of a binding via a contravariant mapping. + let mapModel (f: 'a -> 'b) (binding: Binding<'b, 'msg, 't>) = f |> mapModel |> mapData <| binding + + /// Maps the message of a binding with access to the model via a covariant mapping. + let mapMsgWithModel (f: 'a -> 'model -> 'b) (binding: Binding<'model, 'a, 't>) = + f |> mapMsgWithModel |> mapData <| binding + + /// Maps the message of a binding via a covariant mapping. + let mapMsg (f: 'a -> 'b) (binding: Binding<'model, 'a, 't>) = f |> mapMsg |> mapData <| binding + + /// Sets the message of a binding with access to the model. + let setMsgWithModel (f: 'model -> 'b) (binding: Binding<'model, 'a, 't>) = + f |> setMsgWithModel |> mapData <| binding + + /// Sets the message of a binding. + let setMsg (msg: 'b) (binding: Binding<'model, 'a, 't>) = msg |> setMsg |> mapData <| binding + + + /// Restricts the binding to models that satisfy the predicate after some model satisfies the predicate. + let addSticky (predicate: 'model -> bool) (binding: Binding<'model, 'msg, 't>) = + predicate |> addSticky |> mapData <| binding + /// - /// Elemental instance of a OneWaySeqT binding + /// Adds caching to the given binding. The cache holds a single value and + /// is invalidated after the given binding raises the + /// PropertyChanged event. /// - /// Defines whether an item is "equal" and needs to be updated if the ids are the same - /// Unique identifier for each item in the list (for efficient updates). - let id itemEquals (getId: 'a -> 'id) : string -> Binding<_, 'msg, _> = - OneWaySeq.create itemEquals getId - |> createBindingT - - /// - /// Strongly-typed bindings that dispatch messages from the view. - /// - module CmdT = - - /// - /// Elemental instance of a Command binding. - /// Creates a Command binding that only passes the CommandParameter) - /// - /// - /// If true, CanExecuteChanged will trigger every time WPF's - /// CommandManager - /// detects UI changes that could potentially influence the command's - /// ability to execute. This will likely lead to many more triggers than - /// necessary, but is needed if you have bound the CommandParameter - /// to another UI property. - /// - /// Indicates whether the command can execute. - let id<'model> uiBoundCmdParam canExec - : string -> Binding<'model, obj, ICommand> = - Cmd.createWithParam - (fun p _ -> ValueSome p) - canExec - uiBoundCmdParam - |> createBindingT - - /// - /// Creates a Command binding that depends only on the model (not the - /// CommandParameter). - /// - /// Indicates whether the command can execute. - /// Returns the message to dispatch. - let model - canExec - (exec: 'model -> 'msg) - : string -> Binding<'model, 'msg, ICommand> = - id false (fun _ m -> m |> canExec) - >> mapMsgWithModel (fun _ y -> y |> exec) - >> addLazy (fun m1 m2 -> canExec m1 = canExec m2) - - /// - /// Creates a Command binding that dispatches the specified message. - /// - /// Indicates whether the command can execute. - /// The message to dispatch. - let set - canExec - (msg: 'msg) - : string -> Binding<'model, 'msg, ICommand> = - id false (fun _ m -> m |> canExec) - >> setMsg msg - - /// - /// Creates a Command binding that depends only on the model (not the - /// CommandParameter) and always executes. - /// - /// Returns the message to dispatch. - let modelAlways - (exec: 'model -> 'msg) - : string -> Binding<'model, 'msg, ICommand> = - model (fun _ -> true) exec - - /// - /// Creates a Command binding that dispatches the specified message - /// and always executes. - /// - /// The message to dispatch. - let setAlways - (msg: 'msg) - : string -> Binding<'model, 'msg, ICommand> = - set (fun _ -> true) msg - - module OneWay = - - /// Elemental instance of a one-way binding. - let id<'a, 'msg> : string -> Binding<'a, 'msg> = - OneWay.id - |> createBinding - - /// Creates a one-way binding to an optional value. The binding - /// automatically converts between a missing value in the model and - /// a null value in the view. - let opt<'a, 'msg> : string -> Binding<'a option, 'msg> = - id - >> mapModel Option.box - - /// Creates a one-way binding to an optional value. The binding - /// automatically converts between a missing value in the model and - /// a null value in the view. - let vopt<'a, 'msg> : string -> Binding<'a voption, 'msg> = - id - >> mapModel ValueOption.box - - - module OneWayToSource = - - /// Elemental instance of a one-way-to-source binding. - let id<'model, 'a> : string -> Binding<'model, 'a> = - OneWayToSource.id - |> createBinding - - /// Creates a one-way-to-source binding to an optional value. The binding - /// automatically converts between a missing value in the model and - /// a null value in the view. - let vopt<'model, 'a> : string -> Binding<'model, 'a voption> = - id<'model, obj> - >> mapMsg ValueOption.unbox - - /// Creates a one-way-to-source binding to an optional value. The binding - /// automatically converts between a missing value in the model and - /// a null value in the view. - let opt<'model, 'a> : string -> Binding<'model, 'a option> = - id<'model, obj> - >> mapMsg Option.unbox - - - module TwoWay = - - /// Elemental instance of a two-way binding. - let id<'a> : string -> Binding<'a, 'a> = - TwoWay.id - |> createBinding - - /// Creates a two-way binding to an optional value. The binding - /// automatically converts between a missing value in the model and - /// a null value in the view. - let vopt<'a> : string -> Binding<'a voption, 'a voption> = - id - >> mapModel ValueOption.box - >> mapMsg ValueOption.unbox - - /// Creates a two-way binding to an optional value. The binding - /// automatically converts between a missing value in the model and - /// a null value in the view. - let opt<'a> : string -> Binding<'a option, 'a option> = - id - >> mapModel Option.box - >> mapMsg Option.unbox - - - module SubModelSelectedItem = - - /// Creates a two-way binding to a SelectedItem-like property where - /// the ItemsSource-like property is a - /// binding. Automatically converts the dynamically created Elmish.WPF view - /// models to/from their corresponding IDs, so the Elmish user code only has - /// to work with the IDs. - /// - /// Only use this if you are unable to use some kind of SelectedValue - /// or SelectedIndex property with a normal - /// binding. This binding is less type-safe. It will throw when initializing - /// the bindings if - /// does not correspond to a binding, and it will - /// throw at runtime if the inferred 'id type does not match the - /// actual ID type used in that binding. - let vopt subModelSeqBindingName : string -> Binding<'id voption, 'id voption> = - SubModelSelectedItem.create subModelSeqBindingName - |> createBinding - >> mapModel (ValueOption.map box) - >> mapMsg (ValueOption.map unbox) - - /// Creates a two-way binding to a SelectedItem-like property where - /// the ItemsSource-like property is a - /// binding. Automatically converts the dynamically created Elmish.WPF view - /// models to/from their corresponding IDs, so the Elmish user code only has - /// to work with the IDs. - /// - /// Only use this if you are unable to use some kind of SelectedValue - /// or SelectedIndex property with a normal - /// binding. This binding is less type-safe. It will throw when initializing - /// the bindings if - /// does not correspond to a binding, and it will - /// throw at runtime if the inferred 'id type does not match the - /// actual ID type used in that binding. - let opt subModelSeqBindingName : string -> Binding<'id option, 'id option> = - vopt subModelSeqBindingName - >> mapModel ValueOption.ofOption - >> mapMsg ValueOption.toOption - - - module Cmd = - - let internal createWithParam exec canExec autoRequery = - Cmd.createWithParam exec canExec autoRequery - |> createBinding - - let internal create exec canExec = - createWithParam - (fun _ -> exec) - (fun _ -> canExec) - false - >> addLazy (fun m1 m2 -> canExec m1 = canExec m2) - - - module OneWaySeq = - - let internal create get itemEquals getId = - OneWaySeq.create itemEquals getId - |> BindingData.mapModel get - |> createBinding - - - module SubModel = - - /// - /// Creates a binding to a sub-model/component. You typically bind this - /// to the DataContext of a UserControl or similar. - /// - /// Returns the bindings for the sub-model. - let vopt (bindings: unit -> Binding<'model, 'msg> list) - : string -> Binding<'model voption, 'msg> = - SubModel.create - (fun args -> DynamicViewModel<'model, 'msg>(args, bindings ())) - IViewModel.updateModel - |> createBinding - - /// - /// Creates a binding to a sub-model/component. You typically bind this - /// to the DataContext of a UserControl or similar. - /// - /// Returns the bindings for the sub-model. - let opt (bindings: unit -> Binding<'model, 'msg> list) - : string -> Binding<'model option, 'msg> = - vopt bindings - >> mapModel ValueOption.ofOption - - /// - /// Creates a binding to a sub-model/component. You typically bind this - /// to the DataContext of a UserControl or similar. - /// - /// Returns the bindings for the sub-model. - let required (bindings: unit -> Binding<'model, 'msg> list) - : string -> Binding<'model, 'msg> = - vopt bindings - >> mapModel ValueSome - - /// - /// The strongly-typed counterpart of module SubModel. - /// For creating bindings to child view models that have their own bindings. - /// Typically bound from WPF using DataContext and Binding. - /// Can be used in binding lists if boxed using . - /// - module SubModelT = - - /// Exposes an optional view model member for binding. - let opt - (createVm: ViewModelArgs<'bindingModel, 'msg> -> #IViewModel<'bindingModel, 'msg>) - : (string -> Binding<'bindingModel voption, 'msg, #IViewModel<'bindingModel, 'msg>>) - = - SubModel.create createVm IViewModel.updateModel - |> createBindingT - - /// Exposes a non-optional view model member for binding. - let req - (createVm: ViewModelArgs<'bindingModel, 'msg> -> #IViewModel<'bindingModel, 'msg>) - : (string -> Binding<'bindingModel, 'msg, #IViewModel<'bindingModel, 'msg>>) - = - SubModel.create createVm IViewModel.updateModel - |> createBindingT - >> mapModel ValueSome - - /// - /// Exposes a 'a seq (IEnumerable<'a>) view model member for binding. - /// Used rarely; usually, you want to expose an ObservableCollection<'a> - /// using SubModelSeqKeyedT or SubModelSeqUnkeyedT. - /// - let seq - (createVm: ViewModelArgs<'bindingModel, 'msg> -> #seq<#IViewModel<'bindingModel, 'msg>>) - : (string -> Binding<'bindingModel, 'msg, #seq<#IViewModel<'bindingModel, 'msg>>>) - = - SubModel.create createVm (fun (vms, m) -> vms |> Seq.iter (fun vm -> IViewModel.updateModel (vm, m))) - |> createBindingT - >> mapModel ValueSome - - /// - /// The strongly-typed counterpart of Binding.subModelSeq without parameter getId. - /// Exposes an ObservableCollection of child view models for binding. - /// Identifies elements by index; - /// if possible, use SubModelSeqKeyedT (which uses parameter getId) instead. - /// Typically bound from WPF using DataContext and Binding. - /// Can be used in binding lists if boxed using . - /// - module SubModelSeqUnkeyedT = - - /// - /// Creates an elemental SubModelSeqUnkeyedT binding. - /// - /// - /// The function applied to every element of the bound ObservableCollection - /// to create a child view model. - /// - let id - (createVm: ViewModelArgs<'bindingModel, 'msg> -> #IViewModel<'bindingModel, 'msg>) - : (string -> Binding<'bindingModelCollection, int * 'msg, ObservableCollection<#IViewModel<'bindingModel, 'msg>>>) - = - SubModelSeqUnkeyed.create createVm IViewModel.updateModel - |> createBindingT - - /// - /// The strongly-typed counterpart of Binding.subModelSeq with parameter getId. - /// Exposes an ObservableCollection of child view models for binding. - /// Typically bound from WPF using DataContext and Binding. - /// Can be used in binding lists if boxed using . - /// - module SubModelSeqKeyedT = - - /// - /// Creates an elemental SubModelSeqUnkeyedT binding. - /// - /// - /// The function applied to every element of the bound ObservableCollection - /// to create a child view model. - /// - /// - /// The function applied to every element of the bound ObservableCollection - /// to get a key used to identify that element. - /// Should not return duplicate keys for different elements. - /// - let id - (createVm: ViewModelArgs<'bindingModel, 'msg> -> #IViewModel<'bindingModel, 'msg>) - (getId: 'bindingModel -> 'id) - : (string -> Binding<'bindingModelCollection, 'id * 'msg, ObservableCollection<#IViewModel<'bindingModel, 'msg>>>) - = - SubModelSeqKeyed.create createVm IViewModel.updateModel getId (IViewModel.currentModel >> getId) - |> createBindingT - - /// - /// The strongly-typed counterpart of Binding.subModelWin. - /// Like , but uses the WindowState wrapper - /// to show/hide/close a new window that will have the specified bindings as - /// its DataContext. - /// - /// You do not need to set the DataContext yourself (either in code - /// or in XAML). - /// - /// Can be used in binding lists if boxed using . - /// - module SubModelWinT = - - /// - /// Creates an elemental SubModelWinT binding. - /// Like , but uses the WindowState wrapper - /// to show/hide/close a new window that will have the specified bindings as - /// its DataContext. - /// - /// You do not need to set the DataContext yourself (either in code - /// or in XAML). - /// The window can only be closed/hidden by changing the return value of - /// , and cannot be directly closed by the - /// user. External close attempts (the Close/X button, Alt+F4, or System - /// Menu -> Close) will cause the message specified by - /// to be dispatched. You should supply - /// and react to this in a manner that - /// will not confuse a user trying to close the window (e.g. by closing it - /// or displaying relevant feedback to the user). - /// - /// Gets the window state and a sub-model. - /// Returns the view model for the window. - /// - /// The function used to get and configure the window. - /// - /// - /// Specifies whether the window will be shown modally (using - /// Window.ShowDialog, blocking the rest of the app) or non-modally (using - /// Window.Show). - /// - /// - /// The message to be dispatched on external close attempts (the Close/X - /// button, Alt+F4, or System Menu -> Close). - /// - let id - (getState: 'model -> WindowState<'bindingModel>) - (createVM: ViewModelArgs<'bindingModel, 'bindingMsg> -> #IViewModel<'bindingModel, 'bindingMsg>) - getWindow isModal onCloseRequested = - SubModelWin.create getState createVM IViewModel.updateModel Func2.id2 getWindow isModal onCloseRequested - |> createBindingT - - - module SelectedIndex = - /// Prebuilt binding intended for use with Selector.SelectedIndex. - let vopt = - TwoWay.id - >> mapModel (ValueOption.defaultValue -1) - >> mapMsg (fun i -> if i < 0 then ValueNone else ValueSome i) - - /// Prebuilt binding intended for use with Selector.SelectedIndex. - let opt = - vopt - >> mapModel ValueOption.ofOption - >> mapMsg ValueOption.toOption - - - module SubModelWin = - - let internal create getState createViewModel updateViewModel toMsg getWindow isModal onCloseRequested = - SubModelWin.create getState createViewModel updateViewModel toMsg getWindow isModal onCloseRequested - |> createBinding - - - module SubModelSeqUnkeyed = - - let internal create createViewModel updateViewModel = - SubModelSeqUnkeyed.create createViewModel updateViewModel - |> createBinding - - - module SubModelSeqKeyed = - - let internal create createViewModel updateViewModel bmToId vmToId = - SubModelSeqKeyed.create createViewModel updateViewModel bmToId vmToId - |> createBinding - - -module Bindings = - - /// Maps the model of a list of bindings via a contravariant mapping. - let mapModel (f: 'a -> 'b) (bindings: Binding<'b, 'msg> list) = f |> Binding.mapModel |> List.map <| bindings - - /// Maps the message of a list of bindings with access to the model via a covariant mapping. - let mapMsgWithModel (f: 'a -> 'model -> 'b) (bindings: Binding<'model, 'a> list) = f |> Binding.mapMsgWithModel |> List.map <| bindings - - /// Maps the message of a list of bindings via a covariant mapping. - let mapMsg (f: 'a -> 'b) (bindings: Binding<'model, 'a> list) = f |> Binding.mapMsg |> List.map <| bindings - - -[] -type Binding private () = - - /// - /// Creates a binding intended for use with Selector.SelectedIndex. - /// - /// Gets the selected index from the model. - /// Returns the message to dispatch. - static member selectedIndex - (get: 'model -> int voption, - set: int voption -> 'msg) = - Binding.SelectedIndex.vopt - >> Binding.mapModel get - >> Binding.mapMsg set - - /// - /// Creates a binding intended for use with Selector.SelectedIndex. - /// - /// Gets the selected index from the model. - /// Returns the message to dispatch. - static member selectedIndex - (get: 'model -> int option, - set: int option -> 'msg) = - Binding.SelectedIndex.opt - >> Binding.mapModel get - >> Binding.mapMsg set - - - /// Creates a one-way binding. - /// Gets the value from the model. - static member oneWay - (get: 'model -> 'a) - : string -> Binding<'model, 'msg> = - Binding.OneWay.id<'a, 'msg> - >> Binding.addLazy (=) - >> Binding.mapModel get - - - /// - /// Creates a one-way binding to an optional value. The binding - /// automatically converts between the optional source value and an - /// unwrapped (possibly - /// null) value on the view side. - /// - /// Gets the value from the model. - static member oneWayOpt - (get: 'model -> 'a option) - : string -> Binding<'model, 'msg> = - Binding.OneWay.opt<'a, 'msg> - >> Binding.addLazy (=) - >> Binding.mapModel get - - - /// - /// Creates a one-way binding to an optional value. The binding - /// automatically converts between the optional source value and an - /// unwrapped (possibly - /// null) value on the view side. - /// - /// Gets the value from the model. - static member oneWayOpt - (get: 'model -> 'a voption) - : string -> Binding<'model, 'msg> = - Binding.OneWay.vopt<'a, 'msg> - >> Binding.addLazy (=) - >> Binding.mapModel get - - - /// - /// Creates a lazily evaluated one-way binding. - /// will be called only when the output of changes, - /// as determined by . This may have better - /// performance than for expensive computations (but - /// may be less performant for non-expensive functions due to additional - /// overhead). - /// - /// Gets the value from the model. - /// - /// Indicates whether two intermediate values are equal. Good candidates are - /// elmEq and refEq. - /// - /// Transforms the value into the final type. - static member oneWayLazy - (get: 'model -> 'a, - equals: 'a -> 'a -> bool, - map: 'a -> 'b) - : string -> Binding<'model, 'msg> = - Binding.OneWay.id<'b, 'msg> - >> Binding.mapModel map - >> Binding.addLazy equals - >> Binding.mapModel get - >> Binding.addCaching - - - /// - /// Creates a lazily evaluated one-way binding to an optional value. The - /// binding automatically converts between the optional source value and an - /// unwrapped (possibly null) value on the view side. will be called only when the output of changes, as determined by . - /// - /// This may have better performance than a non-lazy binding for expensive - /// computations (but may be less performant for non-expensive functions due - /// to additional overhead). - /// - /// Gets the intermediate value from the model. - /// - /// Indicates whether two intermediate values are equal. Good candidates are - /// elmEq and refEq. - /// - /// Transforms the intermediate value into the final - /// type. - static member oneWayOptLazy - (get: 'model -> 'a, - equals: 'a -> 'a -> bool, - map: 'a -> 'b option) - : string -> Binding<'model, 'msg> = - Binding.OneWay.opt<'b, 'msg> - >> Binding.mapModel map - >> Binding.addLazy equals - >> Binding.mapModel get - >> Binding.addCaching - - - /// - /// Creates a lazily evaluated one-way binding to an optional value. The - /// binding automatically converts between the optional source value and an - /// unwrapped (possibly null) value on the view side. will be called only when the output of changes, as determined by . - /// - /// This may have better performance than a non-lazy binding for expensive - /// computations (but may be less performant for non-expensive functions due - /// to additional overhead). - /// - /// Gets the value from the model. - /// - /// Indicates whether two intermediate values are equal. Good candidates are - /// elmEq and refEq. - /// - /// Transforms the intermediate value into the final - /// type. - static member oneWayOptLazy - (get: 'model -> 'a, - equals: 'a -> 'a -> bool, - map: 'a -> 'b voption) - : string -> Binding<'model, 'msg> = - Binding.OneWay.vopt<'b, 'msg> - >> Binding.mapModel map - >> Binding.addLazy equals - >> Binding.mapModel get - >> Binding.addCaching - - - /// Creates a one-way-to-source binding. - /// Returns the message to dispatch. - static member oneWayToSource - (set: 'a -> 'model -> 'msg) - : string -> Binding<'model, 'msg> = - Binding.OneWayToSource.id<'model, 'a> - >> Binding.mapMsgWithModel set - - /// - /// Creates a one-way-to-source binding to an optional value. The binding - /// automatically converts between a missing value in the model and - /// a null value in the view. - /// - /// Returns the message to dispatch. - static member oneWayToSourceOpt - (set: 'a option -> 'model -> 'msg) - : string -> Binding<'model, 'msg> = - Binding.OneWayToSource.opt - >> Binding.mapMsgWithModel set - - /// - /// Creates a one-way-to-source binding to an optional value. The binding - /// automatically converts between a missing value in the model and - /// a null value in the view. - /// - /// Returns the message to dispatch. - static member oneWayToSourceOpt - (set: 'a voption -> 'model -> 'msg) - : string -> Binding<'model, 'msg> = - Binding.OneWayToSource.vopt - >> Binding.mapMsgWithModel set - - - /// - /// Creates a one-way binding to a sequence of items, each uniquely - /// identified by the value returned by . The - /// binding will not be updated if the output of - /// does not change, as determined by . - /// The binding is backed by a persistent ObservableCollection, so - /// only changed items (as determined by ) - /// will be replaced. If the items are complex and you want them updated - /// instead of replaced, consider using . - /// - /// Gets the intermediate value from the model. - /// - /// Indicates whether two intermediate values are equal. Good candidates are - /// elmEq and refEq. - /// - /// Transforms the value into the final collection. - /// - /// Indicates whether two collection items are equal. Good candidates are - /// elmEq, refEq, or simply (=). - /// - /// Gets a unique identifier for a collection - /// item. - static member oneWaySeqLazy - (get: 'model -> 'a, - equals: 'a -> 'a -> bool, - map: 'a -> #seq<'b>, - itemEquals: 'b -> 'b -> bool, - getId: 'b -> 'id) - : string -> Binding<'model, 'msg> = - Binding.OneWaySeq.create map itemEquals getId - >> Binding.addLazy equals - >> Binding.mapModel get - - - /// - /// Creates a one-way binding to a sequence of items, each uniquely - /// identified by the value returned by . The - /// binding will not be updated if the output of - /// is referentially equal. This is the same as calling - /// with equals = refEq and - /// map = id. The binding is backed by a persistent - /// ObservableCollection, so only changed items (as determined by - /// ) will be replaced. If the items are - /// complex and you want them updated instead of replaced, consider using - /// . - /// - /// Gets the collection from the model. - /// - /// Indicates whether two collection items are equal. Good candidates are - /// elmEq, refEq, or simply (=). - /// - /// Gets a unique identifier for a collection - /// item. - static member oneWaySeq - (get: 'model -> #seq<'a>, - itemEquals: 'a -> 'a -> bool, - getId: 'a -> 'id) - : string -> Binding<'model, 'msg> = - Binding.OneWaySeq.create id itemEquals getId - >> Binding.addLazy refEq - >> Binding.mapModel get - - - /// Creates a two-way binding. - /// Gets the value from the model. - /// Returns the message to dispatch. - static member twoWay - (get: 'model -> 'a, - set: 'a -> 'model -> 'msg) - : string -> Binding<'model, 'msg> = - Binding.TwoWay.id<'a> - >> Binding.addLazy (=) - >> Binding.mapModel get - >> Binding.mapMsgWithModel set - - /// Creates a two-way binding. - /// Gets the value from the model. - /// Returns the message to dispatch. - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member twoWay - (get: 'model -> 'a, - set: 'a -> 'model -> 'msg, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.twoWay (get, set) - >> Binding.alterMsgStream wrapDispatch - - - /// - /// Creates a two-way binding to an optional value. The binding - /// automatically converts between the optional source value and an - /// unwrapped (possibly null) value on the view side. - /// - /// Gets the value from the model. - /// Returns the message to dispatch. - static member twoWayOpt - (get: 'model -> 'a option, - set: 'a option -> 'model -> 'msg) - : string -> Binding<'model, 'msg> = - Binding.TwoWay.opt<'a> - >> Binding.addLazy (=) - >> Binding.mapModel get - >> Binding.mapMsgWithModel set - - /// - /// Creates a two-way binding to an optional value. The binding - /// automatically converts between the optional source value and an - /// unwrapped (possibly null) value on the view side. - /// - /// Gets the value from the model. - /// Returns the message to dispatch. - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member twoWayOpt - (get: 'model -> 'a option, - set: 'a option -> 'model -> 'msg, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.twoWayOpt (get, set) - >> Binding.alterMsgStream wrapDispatch - - - /// - /// Creates a two-way binding to an optional value. The binding - /// automatically converts between the optional source value and an - /// unwrapped (possibly null) value on the view side. - /// - /// Gets the value from the model. - /// Returns the message to dispatch. - static member twoWayOpt - (get: 'model -> 'a voption, - set: 'a voption -> 'model -> 'msg) - : string -> Binding<'model, 'msg> = - Binding.TwoWay.vopt<'a> - >> Binding.addLazy (=) - >> Binding.mapModel get - >> Binding.mapMsgWithModel set - - /// - /// Creates a two-way binding to an optional value. The binding - /// automatically converts between the optional source value and an - /// unwrapped (possibly null) value on the view side. - /// - /// Gets the value from the model. - /// Returns the message to dispatch. - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member twoWayOpt - (get: 'model -> 'a voption, - set: 'a voption -> 'model -> 'msg, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.twoWayOpt (get, set) - >> Binding.alterMsgStream wrapDispatch - - - /// - /// Creates a two-way binding with validation using - /// INotifyDataErrorInfo. - /// - /// Gets the value from the model. - /// Returns the message to dispatch. - /// - /// Returns the validation messages from the updated model. - /// - static member twoWayValidate - (get: 'model -> 'a, - set: 'a -> 'model -> 'msg, - validate: 'model -> string list) - : string -> Binding<'model, 'msg> = - Binding.TwoWay.id<'a> - >> Binding.addLazy (=) - >> Binding.mapModel get - >> Binding.mapMsgWithModel set - >> Binding.addValidation validate - - /// - /// Creates a two-way binding with validation using - /// INotifyDataErrorInfo. - /// - /// Gets the value from the model. - /// Returns the message to dispatch. - /// - /// Returns the validation messages from the updated model. - /// - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member twoWayValidate - (get: 'model -> 'a, - set: 'a -> 'model -> 'msg, - validate: 'model -> string list, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.twoWayValidate (get, set, validate) - >> Binding.alterMsgStream wrapDispatch - - - /// - /// Creates a two-way binding with validation using - /// INotifyDataErrorInfo. - /// - /// Gets the value from the model. - /// Returns the message to dispatch. - /// - /// Returns the validation message from the updated model. - /// - static member twoWayValidate - (get: 'model -> 'a, - set: 'a -> 'model -> 'msg, - validate: 'model -> string voption) - : string -> Binding<'model, 'msg> = - Binding.TwoWay.id<'a> - >> Binding.addLazy (=) - >> Binding.mapModel get - >> Binding.mapMsgWithModel set - >> Binding.addValidation (validate >> ValueOption.toList) - - /// - /// Creates a two-way binding with validation using - /// INotifyDataErrorInfo. - /// - /// Gets the value from the model. - /// Returns the message to dispatch. - /// - /// Returns the validation message from the updated model. - /// - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member twoWayValidate - (get: 'model -> 'a, - set: 'a -> 'model -> 'msg, - validate: 'model -> string voption, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.twoWayValidate (get, set, validate) - >> Binding.alterMsgStream wrapDispatch - - - /// - /// Creates a two-way binding with validation using - /// INotifyDataErrorInfo. - /// - /// Gets the value from the model. - /// Returns the message to dispatch. - /// - /// Returns the validation message from the updated model. - /// - static member twoWayValidate - (get: 'model -> 'a, - set: 'a -> 'model -> 'msg, - validate: 'model -> string option) - : string -> Binding<'model, 'msg> = - Binding.TwoWay.id<'a> - >> Binding.addLazy (=) - >> Binding.mapModel get - >> Binding.mapMsgWithModel set - >> Binding.addValidation (validate >> Option.toList) - - /// - /// Creates a two-way binding with validation using - /// INotifyDataErrorInfo. - /// - /// Gets the value from the model. - /// Returns the message to dispatch. - /// - /// Returns the validation message from the updated model. - /// - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member twoWayValidate - (get: 'model -> 'a, - set: 'a -> 'model -> 'msg, - validate: 'model -> string option, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.twoWayValidate (get, set, validate) - >> Binding.alterMsgStream wrapDispatch - - - /// - /// Creates a two-way binding with validation using - /// INotifyDataErrorInfo. - /// - /// Gets the value from the model. - /// Returns the message to dispatch. - /// - /// Returns the validation message from the updated model. - /// - static member twoWayValidate - (get: 'model -> 'a, - set: 'a -> 'model -> 'msg, - validate: 'model -> Result<'ignored, string>) - : string -> Binding<'model, 'msg> = - Binding.TwoWay.id<'a> - >> Binding.addLazy (=) - >> Binding.mapModel get - >> Binding.mapMsgWithModel set - >> Binding.addValidation (validate >> ValueOption.ofError >> ValueOption.toList) - - /// - /// Creates a two-way binding with validation using - /// INotifyDataErrorInfo. - /// - /// Gets the value from the model. - /// Returns the message to dispatch. - /// - /// Returns the validation message from the updated model. - /// - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member twoWayValidate - (get: 'model -> 'a, - set: 'a -> 'model -> 'msg, - validate: 'model -> Result<'ignored, string>, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.twoWayValidate (get, set, validate) - >> Binding.alterMsgStream wrapDispatch - - - /// - /// Creates a two-way binding to an optional value with validation using - /// INotifyDataErrorInfo. The binding automatically converts between - /// the optional source value and an unwrapped (possibly null) value - /// on the view side. - /// - /// Gets the value from the model. - /// Returns the message to dispatch. - /// - /// Returns the validation messages from the updated model. - /// - static member twoWayOptValidate - (get: 'model -> 'a voption, - set: 'a voption -> 'model -> 'msg, - validate: 'model -> string list) - : string -> Binding<'model, 'msg> = - Binding.TwoWay.vopt<'a> - >> Binding.addLazy (=) - >> Binding.mapModel get - >> Binding.mapMsgWithModel set - >> Binding.addValidation validate - - /// - /// Creates a two-way binding to an optional value with validation using - /// INotifyDataErrorInfo. The binding automatically converts between - /// the optional source value and an unwrapped (possibly null) value - /// on the view side. - /// - /// Gets the value from the model. - /// Returns the message to dispatch. - /// - /// Returns the validation messages from the updated model. - /// - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member twoWayOptValidate - (get: 'model -> 'a voption, - set: 'a voption -> 'model -> 'msg, - validate: 'model -> string list, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.twoWayOptValidate (get, set, validate) - >> Binding.alterMsgStream wrapDispatch - - - /// - /// Creates a two-way binding to an optional value with validation using - /// INotifyDataErrorInfo. The binding automatically converts between - /// the optional source value and an unwrapped (possibly null) value - /// on the view side. - /// - /// Gets the value from the model. - /// Returns the message to dispatch. - /// - /// Returns the validation message from the updated model. - /// - static member twoWayOptValidate - (get: 'model -> 'a voption, - set: 'a voption -> 'model -> 'msg, - validate: 'model -> string voption) - : string -> Binding<'model, 'msg> = - Binding.TwoWay.vopt<'a> - >> Binding.addLazy (=) - >> Binding.mapModel get - >> Binding.mapMsgWithModel set - >> Binding.addValidation (validate >> ValueOption.toList) - - /// - /// Creates a two-way binding to an optional value with validation using - /// INotifyDataErrorInfo. The binding automatically converts between - /// the optional source value and an unwrapped (possibly null) value - /// on the view side. - /// - /// Gets the value from the model. - /// Returns the message to dispatch. - /// - /// Returns the validation message from the updated model. - /// - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member twoWayOptValidate - (get: 'model -> 'a voption, - set: 'a voption -> 'model -> 'msg, - validate: 'model -> string voption, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.twoWayOptValidate (get, set, validate) - >> Binding.alterMsgStream wrapDispatch - - - /// - /// Creates a two-way binding to an optional value with validation using - /// INotifyDataErrorInfo. The binding automatically converts between - /// the optional source value and an unwrapped (possibly null) value - /// on the view side. - /// - /// Gets the value from the model. - /// Returns the message to dispatch. - /// - /// Returns the validation message from the updated model. - /// - static member twoWayOptValidate - (get: 'model -> 'a voption, - set: 'a voption -> 'model -> 'msg, - validate: 'model -> string option) - : string -> Binding<'model, 'msg> = - Binding.TwoWay.vopt<'a> - >> Binding.addLazy (=) - >> Binding.mapModel get - >> Binding.mapMsgWithModel set - >> Binding.addValidation (validate >> Option.toList) - - /// - /// Creates a two-way binding to an optional value with validation using - /// INotifyDataErrorInfo. The binding automatically converts between - /// the optional source value and an unwrapped (possibly null) value - /// on the view side. - /// - /// Gets the value from the model. - /// Returns the message to dispatch. - /// - /// Returns the validation message from the updated model. - /// - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member twoWayOptValidate - (get: 'model -> 'a voption, - set: 'a voption -> 'model -> 'msg, - validate: 'model -> string option, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.twoWayOptValidate (get, set, validate) - >> Binding.alterMsgStream wrapDispatch - - - /// - /// Creates a two-way binding to an optional value with validation using - /// INotifyDataErrorInfo. The binding automatically converts between - /// the optional source value and an unwrapped (possibly null) value - /// on the view side. - /// - /// Gets the value from the model. - /// Returns the message to dispatch. - /// - /// Returns the validation message from the updated model. - /// - static member twoWayOptValidate - (get: 'model -> 'a voption, - set: 'a voption -> 'model -> 'msg, - validate: 'model -> Result<'ignored, string>) - : string -> Binding<'model, 'msg> = - Binding.TwoWay.vopt<'a> - >> Binding.addLazy (=) - >> Binding.mapModel get - >> Binding.mapMsgWithModel set - >> Binding.addValidation (validate >> ValueOption.ofError >> ValueOption.toList) - - /// - /// Creates a two-way binding to an optional value with validation using - /// INotifyDataErrorInfo. The binding automatically converts between - /// the optional source value and an unwrapped (possibly null) value - /// on the view side. - /// - /// Gets the value from the model. - /// Returns the message to dispatch. - /// - /// Returns the validation message from the updated model. - /// - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member twoWayOptValidate - (get: 'model -> 'a voption, - set: 'a voption -> 'model -> 'msg, - validate: 'model -> Result<'ignored, string>, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.twoWayOptValidate (get, set, validate) - >> Binding.alterMsgStream wrapDispatch - - - /// - /// Creates a two-way binding to an optional value with validation using - /// INotifyDataErrorInfo. The binding automatically converts between - /// the optional source value and an unwrapped (possibly null) value - /// on the view side. - /// - /// Gets the value from the model. - /// Returns the message to dispatch. - /// - /// Returns the validation messages from the updated model. - /// - static member twoWayOptValidate - (get: 'model -> 'a option, - set: 'a option -> 'model -> 'msg, - validate: 'model -> string list) - : string -> Binding<'model, 'msg> = - Binding.TwoWay.opt<'a> - >> Binding.addLazy (=) - >> Binding.mapModel get - >> Binding.mapMsgWithModel set - >> Binding.addValidation validate - - /// - /// Creates a two-way binding to an optional value with validation using - /// INotifyDataErrorInfo. The binding automatically converts between - /// the optional source value and an unwrapped (possibly null) value - /// on the view side. - /// - /// Gets the value from the model. - /// Returns the message to dispatch. - /// - /// Returns the validation messages from the updated model. - /// - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member twoWayOptValidate - (get: 'model -> 'a option, - set: 'a option -> 'model -> 'msg, - validate: 'model -> string list, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.twoWayOptValidate (get, set, validate) - >> Binding.alterMsgStream wrapDispatch - - - /// - /// Creates a two-way binding to an optional value with validation using - /// INotifyDataErrorInfo. The binding automatically converts between - /// the optional source value and an unwrapped (possibly null) value - /// on the view side. - /// - /// Gets the value from the model. - /// Returns the message to dispatch. - /// - /// Returns the validation message from the updated model. - /// - static member twoWayOptValidate - (get: 'model -> 'a option, - set: 'a option -> 'model -> 'msg, - validate: 'model -> string voption) - : string -> Binding<'model, 'msg> = - Binding.TwoWay.opt<'a> - >> Binding.addLazy (=) - >> Binding.mapModel get - >> Binding.mapMsgWithModel set - >> Binding.addValidation (validate >> ValueOption.toList) - - /// - /// Creates a two-way binding to an optional value with validation using - /// INotifyDataErrorInfo. The binding automatically converts between - /// the optional source value and an unwrapped (possibly null) value - /// on the view side. - /// - /// Gets the value from the model. - /// Returns the message to dispatch. - /// - /// Returns the validation message from the updated model. - /// - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member twoWayOptValidate - (get: 'model -> 'a option, - set: 'a option -> 'model -> 'msg, - validate: 'model -> string voption, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.twoWayOptValidate (get, set, validate) - >> Binding.alterMsgStream wrapDispatch - - - /// - /// Creates a two-way binding to an optional value with validation using - /// INotifyDataErrorInfo. The binding automatically converts between - /// the optional source value and an unwrapped (possibly null) value - /// on the view side. - /// - /// Gets the value from the model. - /// Returns the message to dispatch. - /// - /// Returns the validation message from the updated model. - /// - static member twoWayOptValidate - (get: 'model -> 'a option, - set: 'a option -> 'model -> 'msg, - validate: 'model -> string option) - : string -> Binding<'model, 'msg> = - Binding.TwoWay.opt<'a> - >> Binding.addLazy (=) - >> Binding.mapModel get - >> Binding.mapMsgWithModel set - >> Binding.addValidation (validate >> Option.toList) - - /// - /// Creates a two-way binding to an optional value with validation using - /// INotifyDataErrorInfo. The binding automatically converts between - /// the optional source value and an unwrapped (possibly null) value - /// on the view side. - /// - /// Gets the value from the model. - /// Returns the message to dispatch. - /// - /// Returns the validation message from the updated model. - /// - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member twoWayOptValidate - (get: 'model -> 'a option, - set: 'a option -> 'model -> 'msg, - validate: 'model -> string option, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.twoWayOptValidate (get, set, validate) - >> Binding.alterMsgStream wrapDispatch - - - /// - /// Creates a two-way binding to an optional value with validation using - /// INotifyDataErrorInfo. The binding automatically converts between - /// the optional source value and an unwrapped (possibly null) value - /// on the view side. - /// - /// Gets the value from the model. - /// Returns the message to dispatch. - /// - /// Returns the validation message from the updated model. - /// - static member twoWayOptValidate - (get: 'model -> 'a option, - set: 'a option -> 'model -> 'msg, - validate: 'model -> Result<'ignored, string>) - : string -> Binding<'model, 'msg> = - Binding.TwoWay.opt<'a> - >> Binding.addLazy (=) - >> Binding.mapModel get - >> Binding.mapMsgWithModel set - >> Binding.addValidation (validate >> ValueOption.ofError >> ValueOption.toList) - - /// - /// Creates a two-way binding to an optional value with validation using - /// INotifyDataErrorInfo. The binding automatically converts between - /// the optional source value and an unwrapped (possibly null) value - /// on the view side. - /// - /// Gets the value from the model. - /// Returns the message to dispatch. - /// - /// Returns the validation message from the updated model. - /// - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member twoWayOptValidate - (get: 'model -> 'a option, - set: 'a option -> 'model -> 'msg, - validate: 'model -> Result<'ignored, string>, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.twoWayOptValidate (get, set, validate) - >> Binding.alterMsgStream wrapDispatch - - - /// - /// Creates a Command binding that depends only on the model (not the - /// CommandParameter) and can always execute. - /// - /// Returns the message to dispatch. - static member cmd - (exec: 'model -> 'msg) - : string -> Binding<'model, 'msg> = - Binding.Cmd.create - (exec >> ValueSome) - (fun _ -> true) - - /// - /// Creates a Command binding that depends only on the model (not the - /// CommandParameter) and can always execute. - /// - /// Returns the message to dispatch. - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member cmd - (exec: 'model -> 'msg, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.cmd exec - >> Binding.alterMsgStream wrapDispatch - - - /// - /// Creates a conditional Command binding that depends only on the - /// model (not the CommandParameter) and can execute if - /// returns true. - /// - /// Returns the message to dispatch. - /// Indicates whether the command can execute. - static member cmdIf - (exec: 'model -> 'msg, - canExec: 'model -> bool) - : string -> Binding<'model, 'msg> = - Binding.Cmd.create - (exec >> ValueSome) - canExec - - /// - /// Creates a conditional Command binding that depends only on the - /// model (not the CommandParameter) and can execute if - /// returns true. - /// - /// Returns the message to dispatch. - /// Indicates whether the command can execute. - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member cmdIf - (exec: 'model -> 'msg, - canExec: 'model -> bool, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.cmdIf (exec, canExec) - >> Binding.alterMsgStream wrapDispatch - - - /// - /// Creates a conditional Command binding that depends only on the - /// model (not the CommandParameter) and can execute if - /// returns ValueSome. - /// - /// Returns the message to dispatch. - static member cmdIf - (exec: 'model -> 'msg voption) - : string -> Binding<'model, 'msg> = - Binding.Cmd.create - exec - (exec >> ValueOption.isSome) - - /// - /// Creates a conditional Command binding that depends only on the - /// model (not the CommandParameter) and can execute if - /// returns ValueSome. - /// - /// Returns the message to dispatch. - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member cmdIf - (exec: 'model -> 'msg voption, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.cmdIf exec - >> Binding.alterMsgStream wrapDispatch - - - /// - /// Creates a conditional Command binding that depends only on the - /// model (not the CommandParameter) and can execute if - /// returns Some. - /// - /// Returns the message to dispatch. - static member cmdIf - (exec: 'model -> 'msg option) - : string -> Binding<'model, 'msg> = - Binding.Cmd.create - (exec >> ValueOption.ofOption) - (exec >> Option.isSome) - - /// - /// Creates a conditional Command binding that depends only on the - /// model (not the CommandParameter) and can execute if - /// returns Some. - /// - /// Returns the message to dispatch. - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member cmdIf - (exec: 'model -> 'msg option, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.cmdIf exec - >> Binding.alterMsgStream wrapDispatch - - - /// - /// Creates a conditional Command binding that depends only on the - /// model (not the CommandParameter) and can execute if - /// returns Ok. - /// - /// This overload allows more easily re-using the same validation functions - /// for inputs and commands. - /// - /// Returns the message to dispatch. - static member cmdIf - (exec: 'model -> Result<'msg, 'ignored>) - : string -> Binding<'model, 'msg> = - Binding.Cmd.create - (exec >> ValueOption.ofOk) - (exec >> Result.isOk) - - /// - /// Creates a conditional Command binding that depends only on the - /// model (not the CommandParameter) and can execute if - /// returns Ok. - /// - /// This overload allows more easily re-using the same validation functions - /// for inputs and commands. - /// - /// Returns the message to dispatch. - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member cmdIf - (exec: 'model -> Result<'msg, 'ignored>, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.cmdIf exec - >> Binding.alterMsgStream wrapDispatch - - - /// - /// Creates a Command binding that depends on the - /// CommandParameter - /// and can always execute. - /// - /// Returns the message to dispatch. - static member cmdParam - (exec: obj -> 'model -> 'msg) - : string -> Binding<'model, 'msg> = - Binding.Cmd.createWithParam - (fun p model -> exec p model |> ValueSome) - (fun _ _ -> true) - false - - /// - /// Creates a Command binding that depends on the - /// CommandParameter - /// and can always execute. - /// - /// Returns the message to dispatch. - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member cmdParam - (exec: obj -> 'model -> 'msg, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.cmdParam exec - >> Binding.alterMsgStream wrapDispatch - - - /// - /// Creates a Command binding that depends on the - /// CommandParameter - /// and can execute if returns true. - /// - /// Returns the message to dispatch. - /// Indicates whether the command can execute. - /// - /// If true, CanExecuteChanged will trigger every time WPF's - /// CommandManager - /// detects UI changes that could potentially influence the command's - /// ability to execute. This will likely lead to many more triggers than - /// necessary, but is needed if you have bound the CommandParameter - /// to another UI property. - /// - static member cmdParamIf - (exec: obj -> 'model -> 'msg, - canExec: obj -> 'model -> bool, - ?uiBoundCmdParam: bool) - : string -> Binding<'model, 'msg> = - Binding.Cmd.createWithParam - (fun p m -> exec p m |> ValueSome) - canExec - (defaultArg uiBoundCmdParam false) - - /// - /// Creates a Command binding that depends on the - /// CommandParameter - /// and can execute if returns true. - /// - /// Returns the message to dispatch. - /// Indicates whether the command can execute. - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member cmdParamIf - (exec: obj -> 'model -> 'msg, - canExec: obj -> 'model -> bool, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.cmdParamIf (exec, canExec) - >> Binding.alterMsgStream wrapDispatch - - /// - /// Creates a Command binding that depends on the - /// CommandParameter - /// and can execute if returns true. - /// - /// Returns the message to dispatch. - /// Indicates whether the command can execute. - /// - /// If true, CanExecuteChanged will trigger every time WPF's - /// CommandManager - /// detects UI changes that could potentially influence the command's - /// ability to execute. This will likely lead to many more triggers than - /// necessary, but is needed if you have bound the CommandParameter - /// to another UI property. - /// - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member cmdParamIf - (exec: obj -> 'model -> 'msg, - canExec: obj -> 'model -> bool, - uiBoundCmdParam: bool, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.cmdParamIf (exec, canExec, uiBoundCmdParam) - >> Binding.alterMsgStream wrapDispatch - - - /// - /// Creates a conditional Command binding that depends on the - /// CommandParameter - /// and can execute if returns ValueSome. - /// - /// Returns the message to dispatch. - /// - /// If true, CanExecuteChanged will trigger every time WPF's - /// CommandManager - /// detects UI changes that could potentially influence the command's - /// ability to execute. This will likely lead to many more triggers than - /// necessary, but is needed if you have bound the CommandParameter - /// to another UI property. - /// - static member cmdParamIf - (exec: obj -> 'model -> 'msg voption, - ?uiBoundCmdParam: bool) - : string -> Binding<'model, 'msg> = - Binding.Cmd.createWithParam - exec - (fun p m -> exec p m |> ValueOption.isSome) - (defaultArg uiBoundCmdParam false) - - /// - /// Creates a conditional Command binding that depends on the - /// CommandParameter - /// and can execute if returns ValueSome. - /// - /// Returns the message to dispatch. - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member cmdParamIf - (exec: obj -> 'model -> 'msg voption, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.cmdParamIf exec - >> Binding.alterMsgStream wrapDispatch - - /// - /// Creates a conditional Command binding that depends on the - /// CommandParameter - /// and can execute if returns ValueSome. - /// - /// Returns the message to dispatch. - /// - /// If true, CanExecuteChanged will trigger every time WPF's - /// CommandManager - /// detects UI changes that could potentially influence the command's - /// ability to execute. This will likely lead to many more triggers than - /// necessary, but is needed if you have bound the CommandParameter - /// to another UI property. - /// - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member cmdParamIf - (exec: obj -> 'model -> 'msg voption, - uiBoundCmdParam: bool, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.cmdParamIf (exec, uiBoundCmdParam) - >> Binding.alterMsgStream wrapDispatch - - - /// - /// Creates a conditional Command binding that depends on the - /// CommandParameter - /// and can execute if returns Some. - /// - /// Returns the message to dispatch. - /// - /// If true, CanExecuteChanged will trigger every time WPF's - /// CommandManager - /// detects UI changes that could potentially influence the command's - /// ability to execute. This will likely lead to many more triggers than - /// necessary, but is needed if you have bound the CommandParameter - /// to another UI property. - /// - static member cmdParamIf - (exec: obj -> 'model -> 'msg option, - ?uiBoundCmdParam: bool) - : string -> Binding<'model, 'msg> = - Binding.Cmd.createWithParam - (fun p m -> exec p m |> ValueOption.ofOption) - (fun p m -> exec p m |> Option.isSome) - (defaultArg uiBoundCmdParam false) - - /// - /// Creates a conditional Command binding that depends on the - /// CommandParameter - /// and can execute if returns Some. - /// - /// Returns the message to dispatch. - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member cmdParamIf - (exec: obj -> 'model -> 'msg option, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.cmdParamIf exec - >> Binding.alterMsgStream wrapDispatch - - /// - /// Creates a conditional Command binding that depends on the - /// CommandParameter - /// and can execute if returns Some. - /// - /// Returns the message to dispatch. - /// - /// If true, CanExecuteChanged will trigger every time WPF's - /// CommandManager - /// detects UI changes that could potentially influence the command's - /// ability to execute. This will likely lead to many more triggers than - /// necessary, but is needed if you have bound the CommandParameter - /// to another UI property. - /// - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member cmdParamIf - (exec: obj -> 'model -> 'msg option, - uiBoundCmdParam: bool, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.cmdParamIf (exec, uiBoundCmdParam) - >> Binding.alterMsgStream wrapDispatch - - - /// - /// Creates a conditional Command binding that depends on the - /// CommandParameter - /// and can execute if returns Ok. - /// - /// This overload allows more easily re-using the same validation functions - /// for inputs and commands. - /// - /// Returns the message to dispatch. - /// - /// If true, CanExecuteChanged will trigger every time WPF's - /// CommandManager - /// detects UI changes that could potentially influence the command's - /// ability to execute. This will likely lead to many more triggers than - /// necessary, but is needed if you have bound the CommandParameter - /// to another UI property. - /// - static member cmdParamIf - (exec: obj -> 'model -> Result<'msg, 'ignored>, - ?uiBoundCmdParam: bool) - : string -> Binding<'model, 'msg> = - Binding.Cmd.createWithParam - (fun p m -> exec p m |> ValueOption.ofOk) - (fun p m -> exec p m |> Result.isOk) - (defaultArg uiBoundCmdParam false) - - /// - /// Creates a conditional Command binding that depends on the - /// CommandParameter - /// and can execute if returns Ok. - /// - /// This overload allows more easily re-using the same validation functions - /// for inputs and commands. - /// - /// Returns the message to dispatch. - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member cmdParamIf - (exec: obj -> 'model -> Result<'msg, 'ignored>, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.cmdParamIf exec - >> Binding.alterMsgStream wrapDispatch - - /// - /// Creates a conditional Command binding that depends on the - /// CommandParameter - /// and can execute if returns Ok. - /// - /// This overload allows more easily re-using the same validation functions - /// for inputs and commands. - /// - /// Returns the message to dispatch. - /// - /// If true, CanExecuteChanged will trigger every time WPF's - /// CommandManager - /// detects UI changes that could potentially influence the command's - /// ability to execute. This will likely lead to many more triggers than - /// necessary, but is needed if you have bound the CommandParameter - /// to another UI property. - /// - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member cmdParamIf - (exec: obj -> 'model -> Result<'msg, 'ignored>, - uiBoundCmdParam: bool, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.cmdParamIf (exec, uiBoundCmdParam) - >> Binding.alterMsgStream wrapDispatch - - - /// - /// Creates a binding to a sub-model/component that has its own bindings and - /// message type. You typically bind this to the DataContext of a - /// UserControl or similar. - /// - /// Gets the sub-model from the model. - /// - /// Converts the models to the model used by the bindings. - /// - /// - /// Converts the messages used in the bindings to parent model messages - /// (e.g. a parent message union case that wraps the child message type). - /// - /// Returns the bindings for the sub-model. - [] - static member subModel - (getSubModel: 'model -> 'subModel, - toBindingModel: 'model * 'subModel -> 'bindingModel, - toMsg: 'bindingMsg -> 'msg, - bindings: unit -> Binding<'bindingModel, 'bindingMsg> list) - : string -> Binding<'model, 'msg> = - Binding.SubModel.required bindings - >> Binding.mapModel (fun m -> toBindingModel (m, getSubModel m)) - >> Binding.mapMsg toMsg - - /// - /// Creates a binding to a sub-model/component that has its own bindings and - /// message type. You typically bind this to the DataContext of a - /// UserControl or similar. - /// - /// Gets the sub-model from the model. - /// - /// Converts the messages used in the bindings to parent model messages - /// (e.g. a parent message union case that wraps the child message type). - /// - /// Returns the bindings for the sub-model. - [] - static member subModel - (getSubModel: 'model -> 'subModel, - toMsg: 'subMsg -> 'msg, - bindings: unit -> Binding<'model * 'subModel, 'subMsg> list) - : string -> Binding<'model, 'msg> = - Binding.SubModel.required bindings - >> Binding.mapModel (fun m -> (m, getSubModel m)) - >> Binding.mapMsg toMsg - - - /// - /// Creates a binding to a sub-model/component that has its own bindings. - /// You typically bind this to the DataContext of a - /// UserControl or similar. - /// - /// Gets the sub-model from the model. - /// Returns the bindings for the sub-model. - [ Binding<'model, 'msg> list\". To avoid a compile error when upgrading, replace this method call with its implementation.")>] - static member subModel - (getSubModel: 'model -> 'subModel, - bindings: unit -> Binding<'model * 'subModel, 'msg> list) - : string -> Binding<'model, 'msg> = - Binding.SubModel.required bindings - >> Binding.mapModel (fun m -> (m, getSubModel m)) - - - /// - /// Creates a binding to a sub-model/component that has its own bindings and - /// message type, and may not exist. If it does not exist, bindings to this - /// model will return null unless is - /// true, in which case the last non-null model will be - /// returned. You typically bind this to the DataContext of a - /// UserControl or similar. - /// - /// The 'sticky' part is useful if you want to e.g. animate away a - /// UserControl when the model is missing, but don't want the data - /// used by that control to be cleared once the animation starts. (The - /// animation must be triggered using another binding since this will never - /// return null.) - /// - /// Gets the sub-model from the model. - /// - /// Converts the models to the model used by the bindings. - /// - /// - /// Converts the messages used in the bindings to parent model messages - /// (e.g. a parent message union case that wraps the child message type). - /// - /// Returns the bindings for the sub-model. - /// - /// If true, when the model is missing, the last non-null - /// model will be returned instead of null. - /// - [] - static member subModelOpt - (getSubModel: 'model -> 'subModel voption, - toBindingModel: 'model * 'subModel -> 'bindingModel, - toMsg: 'bindingMsg -> 'msg, - bindings: unit -> Binding<'bindingModel, 'bindingMsg> list, - ?sticky: bool) - : string -> Binding<'model, 'msg> = - Binding.SubModel.vopt bindings - >> if (defaultArg sticky false) then Binding.addLazy (fun previous next -> previous.IsSome && next.IsNone) else id - >> Binding.mapModel (fun m -> getSubModel m |> ValueOption.map (fun sub -> toBindingModel (m, sub))) - >> Binding.mapMsg toMsg - - - /// - /// Creates a binding to a sub-model/component that has its own bindings and - /// message type, and may not exist. If it does not exist, bindings to this - /// model will return null unless is - /// true, in which case the last non-null model will be - /// returned. You typically bind this to the DataContext of a - /// UserControl or similar. - /// - /// The 'sticky' part is useful if you want to e.g. animate away a - /// UserControl when the model is missing, but don't want the data - /// used by that control to be cleared once the animation starts. (The - /// animation must be triggered using another binding since this will never - /// return null.) - /// - /// Gets the sub-model from the model. - /// - /// Converts the models to the model used by the bindings. - /// - /// - /// Converts the messages used in the bindings to parent model messages - /// (e.g. a parent message union case that wraps the child message type). - /// - /// Returns the bindings for the sub-model. - /// - /// If true, when the model is missing, the last non-null - /// model will be returned instead of null. - /// - [] - static member subModelOpt - (getSubModel: 'model -> 'subModel option, - toBindingModel: 'model * 'subModel -> 'bindingModel, - toMsg: 'bindingMsg -> 'msg, - bindings: unit -> Binding<'bindingModel, 'bindingMsg> list, - ?sticky: bool) - : string -> Binding<'model, 'msg> = - Binding.SubModel.opt bindings - >> if (defaultArg sticky false) then Binding.addLazy (fun previous next -> previous.IsSome && next.IsNone) else id - >> Binding.mapModel (fun m -> getSubModel m |> Option.map (fun sub -> toBindingModel (m, sub))) - >> Binding.mapMsg toMsg - - /// - /// Creates a binding to a sub-model/component that has its own bindings and - /// message type, and may not exist. If it does not exist, bindings to this - /// model will return null unless is - /// true, in which case the last non-null model will be - /// returned. You typically bind this to the DataContext of a - /// UserControl or similar. - /// - /// The 'sticky' part is useful if you want to e.g. animate away a - /// UserControl when the model is missing, but don't want the data - /// used by that control to be cleared once the animation starts. (The - /// animation must be triggered using another binding since this will never - /// return null.) - /// - /// Gets the sub-model from the model. - /// - /// Converts the messages used in the bindings to parent model messages - /// (e.g. a parent message union case that wraps the child message type). - /// - /// Returns the bindings for the sub-model. - /// - /// If true, when the model is missing, the last non-null - /// model will be returned instead of null. - /// - [] - static member subModelOpt - (getSubModel: 'model -> 'subModel voption, - toMsg: 'subMsg -> 'msg, - bindings: unit -> Binding<'model * 'subModel, 'subMsg> list, - ?sticky: bool) - : string -> Binding<'model, 'msg> = - Binding.SubModel.vopt bindings - >> if (defaultArg sticky false) then Binding.addLazy (fun previous next -> previous.IsSome && next.IsNone) else id - >> Binding.mapModel (fun m -> getSubModel m |> ValueOption.map (fun sub -> (m, sub))) - >> Binding.mapMsg toMsg - - - /// - /// Creates a binding to a sub-model/component that has its own bindings and - /// message type, and may not exist. If it does not exist, bindings to this - /// model will return null unless is - /// true, in which case the last non-null model will be - /// returned. You typically bind this to the DataContext of a - /// UserControl or similar. - /// - /// The 'sticky' part is useful if you want to e.g. animate away a - /// UserControl when the model is missing, but don't want the data - /// used by that control to be cleared once the animation starts. (The - /// animation must be triggered using another binding since this will never - /// return null.) - /// - /// Gets the sub-model from the model. - /// - /// Converts the messages used in the bindings to parent model messages - /// (e.g. a parent message union case that wraps the child message type). - /// - /// Returns the bindings for the sub-model. - /// - /// If true, when the model is missing, the last non-null - /// model will be returned instead of null. - /// - [] - static member subModelOpt - (getSubModel: 'model -> 'subModel option, - toMsg: 'subMsg -> 'msg, - bindings: unit -> Binding<'model * 'subModel, 'subMsg> list, - ?sticky: bool) - : string -> Binding<'model, 'msg> = - Binding.SubModel.opt bindings - >> if (defaultArg sticky false) then Binding.addLazy (fun previous next -> previous.IsSome && next.IsNone) else id - >> Binding.mapModel (fun m -> getSubModel m |> Option.map (fun sub -> (m, sub))) - >> Binding.mapMsg toMsg - - - /// - /// Creates a binding to a sub-model/component that has its own bindings, - /// and may not exist. If it does not exist, bindings to this model will - /// return null unless is true, in - /// which case the last non-null model will be returned. You - /// typically bind this to the DataContext of a UserControl or - /// similar. - /// - /// The 'sticky' part is useful if you want to e.g. animate away a - /// UserControl when the model is missing, but don't want the data - /// used by that control to be cleared once the animation starts. (The - /// animation must be triggered using another binding since this will never - /// return null.) - /// - /// Gets the sub-model from the model. - /// Returns the bindings for the sub-model. - /// - /// If true, when the model is missing, the last non-null - /// model will be returned instead of null. - /// - [ Binding<'model, 'msg> list\". To avoid a compile error when upgrading, replace this method call with (a specialization of) its implementation.")>] - static member subModelOpt - (getSubModel: 'model -> 'subModel voption, - bindings: unit -> Binding<'model * 'subModel, 'msg> list, - ?sticky: bool) - : string -> Binding<'model, 'msg> = - Binding.SubModel.vopt bindings - >> if (defaultArg sticky false) then Binding.addLazy (fun previous next -> previous.IsSome && next.IsNone) else id - >> Binding.mapModel (fun m -> getSubModel m |> ValueOption.map (fun sub -> (m, sub))) - - - /// - /// Creates a binding to a sub-model/component that has its own bindings, - /// and may not exist. If it does not exist, bindings to this model will - /// return null unless is true, in - /// which case the last non-null model will be returned. You - /// typically bind this to the DataContext of a UserControl or - /// similar. - /// - /// The 'sticky' part is useful if you want to e.g. animate away a - /// UserControl when the model is missing, but don't want the data - /// used by that control to be cleared once the animation starts. (The - /// animation must be triggered using another binding since this will never - /// return null.) - /// - /// Gets the sub-model from the model. - /// Returns the bindings for the sub-model. - /// - /// If true, when the model is missing, the last non-null - /// model will be returned instead of null. - /// - [ Binding<'model, 'msg> list\". To avoid a compile error when upgrading, replace this method call with (a specialization of) its implementation.")>] - static member subModelOpt - (getSubModel: 'model -> 'subModel option, - bindings: unit -> Binding<'model * 'subModel, 'msg> list, - ?sticky: bool) - : string -> Binding<'model, 'msg> = - Binding.SubModel.opt bindings - >> if (defaultArg sticky false) then Binding.addLazy (fun previous next -> previous.IsSome && next.IsNone) else id - >> Binding.mapModel (fun m -> getSubModel m |> Option.map (fun sub -> (m, sub))) - - - /// - /// Like , but uses the WindowState wrapper - /// to show/hide/close a new window that will have the specified bindings as - /// its DataContext. - /// - /// You do not need to set the DataContext yourself (neither in code - /// nor XAML). - /// - /// The window can only be closed/hidden by changing the return value of - /// , and can not be directly closed by the - /// user. External close attempts (the Close/X button, Alt+F4, or System - /// Menu -> Close) will cause the message specified by - /// to be dispatched. You should supply - /// and react to this in a manner that - /// will not confuse a user trying to close the window (e.g. by closing it, - /// or displaying relevant feedback to the user.) - /// - /// If you don't need a sub-model, you can use - /// WindowState<unit> to just control the Window visibility, - /// and pass fst to . - /// - /// Gets the window state and a sub-model. - /// - /// Converts the models to the model used by the bindings. - /// - /// - /// Converts the messages used in the bindings to parent model messages - /// (e.g. a parent message union case that wraps the child message type). - /// - /// Returns the bindings for the sub-model. - /// - /// The function used to get and configure the window. - /// - /// - /// The message to be dispatched on external close attempts (the Close/X - /// button, Alt+F4, or System Menu -> Close). - /// - /// - /// Specifies whether the window will be shown modally (using - /// window.ShowDialog, blocking the rest of the app) or non-modally (using - /// window.Show). - /// - static member subModelWin - (getState: 'model -> WindowState<'subModel>, - toBindingModel: 'model * 'subModel -> 'bindingModel, - toMsg: 'bindingMsg -> 'msg, - bindings: unit -> Binding<'bindingModel, 'bindingMsg> list, - getWindow: 'model -> Dispatch<'msg> -> #Window, - ?onCloseRequested: 'msg, - ?isModal: bool) - : string -> Binding<'model, 'msg> = - Binding.SubModelWin.create - (fun m -> getState m |> WindowState.map (fun sub -> toBindingModel (m, sub))) - (fun args -> DynamicViewModel<'bindingModel, 'bindingMsg>(args, bindings ())) - IViewModel.updateModel - (fun _ -> toMsg) - (fun m d -> upcast getWindow m d) - (defaultArg isModal false) - (fun _ -> defaultArg (onCloseRequested |> Option.map ValueSome) ValueNone) - - - /// - /// Like , but uses the WindowState wrapper - /// to show/hide/close a new window that will have the specified bindings as - /// its DataContext. - /// - /// You do not need to set the DataContext yourself (neither in code - /// nor XAML). - /// - /// The window can only be closed/hidden by changing the return value of - /// , and can not be directly closed by the - /// user. External close attempts (the Close/X button, Alt+F4, or System - /// Menu -> Close) will cause the message specified by - /// to be dispatched. You should supply - /// and react to this in a manner that - /// will not confuse a user trying to close the window (e.g. by closing it, - /// or displaying relevant feedback to the user.) - /// - /// If you don't need a sub-model, you can use - /// WindowState<unit> to just control the Window visibility, - /// and pass fst to . - /// - /// Gets the window state and a sub-model. - /// - /// Converts the models to the model used by the bindings. - /// - /// - /// Converts the messages used in the bindings to parent model messages - /// (e.g. a parent message union case that wraps the child message type). - /// - /// Returns the bindings for the sub-model. - /// - /// The function used to get and configure the window. - /// - /// - /// The message to be dispatched on external close attempts (the Close/X - /// button, Alt+F4, or System Menu -> Close). - /// - /// - /// Specifies whether the window will be shown modally (using - /// window.ShowDialog, blocking the rest of the app) or non-modally (using - /// window.Show). - /// - static member subModelWin - (getState: 'model -> WindowState<'subModel>, - toBindingModel: 'model * 'subModel -> 'bindingModel, - toMsg: 'bindingMsg -> 'msg, - bindings: unit -> Binding<'bindingModel, 'bindingMsg> list, - getWindow: unit -> #Window, - ?onCloseRequested: 'msg, - ?isModal: bool) - : string -> Binding<'model, 'msg> = - Binding.subModelWin( - getState, - toBindingModel, - toMsg, - bindings, - (fun _ _ -> getWindow ()), - ?onCloseRequested = onCloseRequested, - ?isModal = isModal - ) - - - /// - /// Like , but uses the WindowState wrapper - /// to show/hide/close a new window that will have the specified bindings as - /// its DataContext. - /// - /// You do not need to set the DataContext yourself (neither in code - /// nor XAML). - /// - /// The window can only be closed/hidden by changing the return value of - /// , and can not be directly closed by the - /// user. External close attempts (the Close/X button, Alt+F4, or System - /// Menu -> Close) will cause the message specified by - /// to be dispatched. You should supply - /// and react to this in a manner that - /// will not confuse a user trying to close the window (e.g. by closing it, - /// or displaying relevant feedback to the user.) - /// - /// Gets the window state and a sub-model. - /// - /// Converts the messages used in the bindings to parent model messages - /// (e.g. a parent message union case that wraps the child message type). - /// - /// Returns the bindings for the sub-model. - /// - /// The function used to get and configure the window. - /// - /// - /// The message to be dispatched on external close attempts (the Close/X - /// button, Alt+F4, or System Menu -> Close). - /// - /// - /// Specifies whether the window will be shown modally (using - /// window.ShowDialog, blocking the rest of the app) or non-modally (using - /// window.Show). - /// - static member subModelWin - (getState: 'model -> WindowState<'subModel>, - toMsg: 'subMsg -> 'msg, - bindings: unit -> Binding<'model * 'subModel, 'subMsg> list, - getWindow: 'model -> Dispatch<'msg> -> #Window, - ?onCloseRequested: 'msg, - ?isModal: bool) - : string -> Binding<'model, 'msg> = - Binding.SubModelWin.create - (fun m -> getState m |> WindowState.map (fun sub -> (m, sub))) - (fun args -> DynamicViewModel<'model * 'subModel, 'subMsg>(args, bindings ())) - IViewModel.updateModel - (fun _ -> toMsg) - (fun m d -> upcast getWindow m d) - (defaultArg isModal false) - (fun _ -> defaultArg (onCloseRequested |> Option.map ValueSome) ValueNone) - - - /// - /// Like , but uses the WindowState wrapper - /// to show/hide/close a new window that will have the specified bindings as - /// its DataContext. - /// - /// You do not need to set the DataContext yourself (neither in code - /// nor XAML). - /// - /// The window can only be closed/hidden by changing the return value of - /// , and can not be directly closed by the - /// user. External close attempts (the Close/X button, Alt+F4, or System - /// Menu -> Close) will cause the message specified by - /// to be dispatched. You should supply - /// and react to this in a manner that - /// will not confuse a user trying to close the window (e.g. by closing it, - /// or displaying relevant feedback to the user.) - /// - /// Gets the window state and a sub-model. - /// - /// Converts the messages used in the bindings to parent model messages - /// (e.g. a parent message union case that wraps the child message type). - /// - /// Returns the bindings for the sub-model. - /// - /// The function used to get and configure the window. - /// - /// - /// The message to be dispatched on external close attempts (the Close/X - /// button, Alt+F4, or System Menu -> Close). - /// - /// - /// Specifies whether the window will be shown modally (using - /// window.ShowDialog, blocking the rest of the app) or non-modally (using - /// window.Show). - /// - static member subModelWin - (getState: 'model -> WindowState<'subModel>, - toMsg: 'subMsg -> 'msg, - bindings: unit -> Binding<'model * 'subModel, 'subMsg> list, - getWindow: unit -> #Window, - ?onCloseRequested: 'msg, - ?isModal: bool) - : string -> Binding<'model, 'msg> = - Binding.subModelWin( - getState, - toMsg, - bindings, - (fun _ _ -> getWindow ()), - ?onCloseRequested = onCloseRequested, - ?isModal = isModal - ) - - - /// - /// Like , but uses the WindowState wrapper - /// to show/hide/close a new window that will have the specified bindings as - /// its DataContext. - /// - /// You do not need to set the DataContext yourself (neither in code - /// nor XAML). - /// - /// The window can only be closed/hidden by changing the return value of - /// , and can not be directly closed by the - /// user. External close attempts (the Close/X button, Alt+F4, or System - /// Menu -> Close) will cause the message specified by - /// to be dispatched. You should supply - /// and react to this in a manner that - /// will not confuse a user trying to close the window (e.g. by closing it, - /// or displaying relevant feedback to the user.) - /// - /// Gets the window state and a sub-model. - /// Returns the bindings for the sub-model. - /// - /// The function used to get and configure the window. - /// - /// - /// The message to be dispatched on external close attempts (the Close/X - /// button, Alt+F4, or System Menu -> Close). - /// - /// - /// Specifies whether the window will be shown modally (using - /// window.ShowDialog, blocking the rest of the app) or non-modally (using - /// window.Show). - /// - static member subModelWin - (getState: 'model -> WindowState<'subModel>, - bindings: unit -> Binding<'model * 'subModel, 'msg> list, - getWindow: 'model -> Dispatch<'msg> -> #Window, - ?onCloseRequested: 'msg, - ?isModal: bool) - : string -> Binding<'model, 'msg> = - Binding.SubModelWin.create - (fun m -> getState m |> WindowState.map (fun sub -> (m, sub))) - (fun args -> DynamicViewModel<'model * 'subModel, 'msg>(args, bindings ())) - IViewModel.updateModel - (fun _ -> id) - (fun m d -> upcast getWindow m d) - (defaultArg isModal false) - (fun _ -> defaultArg (onCloseRequested |> Option.map ValueSome) ValueNone) - - - /// - /// Like , but uses the WindowState wrapper - /// to show/hide/close a new window that will have the specified bindings as - /// its DataContext. - /// - /// You do not need to set the DataContext yourself (neither in code - /// nor XAML). - /// - /// The window can only be closed/hidden by changing the return value of - /// , and can not be directly closed by the - /// user. External close attempts (the Close/X button, Alt+F4, or System - /// Menu -> Close) will cause the message specified by - /// to be dispatched. You should supply - /// and react to this in a manner that - /// will not confuse a user trying to close the window (e.g. by closing it, - /// or displaying relevant feedback to the user.) - /// - /// Gets the window state and a sub-model. - /// Returns the bindings for the sub-model. - /// - /// The function used to get and configure the window. - /// - /// - /// The message to be dispatched on external close attempts (the Close/X - /// button, Alt+F4, or System Menu -> Close). - /// - /// - /// Specifies whether the window will be shown modally (using - /// window.ShowDialog, blocking the rest of the app) or non-modally (using - /// window.Show). - /// - static member subModelWin - (getState: 'model -> WindowState<'subModel>, - bindings: unit -> Binding<'model * 'subModel, 'msg> list, - getWindow: unit -> #Window, - ?onCloseRequested: 'msg, - ?isModal: bool) - : string -> Binding<'model, 'msg> = - Binding.subModelWin( - getState, - bindings, - (fun _ _ -> getWindow ()), - ?onCloseRequested = onCloseRequested, - ?isModal = isModal - ) - - static member subModelSeq // TODO: make into function - (getBindings: unit -> Binding<'model, 'msg> list) - : string -> Binding<'model seq, int * 'msg> = - Binding.SubModelSeqUnkeyed.create - (fun args -> DynamicViewModel<'model, 'msg>(args, getBindings ())) - IViewModel.updateModel - - static member subModelSeq // TODO: make into function - (getBindings: unit -> Binding<'model, 'msg> list, - getId: 'model -> 'id) - : string -> Binding<'model seq, 'id * 'msg> = - Binding.SubModelSeqKeyed.create - (fun args -> DynamicViewModel<'model, 'msg>(args, getBindings ())) - IViewModel.updateModel - getId - (IViewModel.currentModel >> getId) - - - /// - /// Creates a binding to a sequence of sub-models, each uniquely identified - /// by the value returned by . The sub-models have - /// their own bindings and message type. You typically bind this to the - /// ItemsSource of an ItemsControl, ListView, - /// TreeView, etc. - /// - /// Gets the sub-models from the model. - /// - /// Converts the models to the model used by the bindings. - /// - /// Gets a unique identifier for a sub-model. - /// - /// Converts the sub-model ID and messages used in the bindings to parent - /// model messages (e.g. a parent message union case that wraps the - /// sub-model ID and message type). - /// - /// Returns the bindings for the sub-model. - static member subModelSeq - (getSubModels: 'model -> #seq<'subModel>, - toBindingModel: 'model * 'subModel -> 'bindingModel, - getId: 'bindingModel -> 'id, - toMsg: 'id * 'bindingMsg -> 'msg, - bindings: unit -> Binding<'bindingModel, 'bindingMsg> list) - : string -> Binding<'model, 'msg> = - Binding.SubModelSeqKeyed.create - (fun args -> DynamicViewModel<'bindingModel, 'bindingMsg>(args, bindings ())) - IViewModel.updateModel - getId - (IViewModel.currentModel >> getId) - >> Binding.mapModel (fun m -> getSubModels m |> Seq.map (fun sub -> toBindingModel (m, sub))) - >> Binding.mapMsg toMsg - - - /// - /// Creates a binding to a sequence of sub-models, each uniquely identified - /// by the value returned by . The sub-models have - /// their own bindings and message type. You typically bind this to the - /// ItemsSource of an ItemsControl, ListView, - /// TreeView, etc. - /// - /// Gets the sub-models from the model. - /// Gets a unique identifier for a sub-model. - /// - /// Converts the sub-model ID and messages used in the bindings to parent - /// model messages (e.g. a parent message union case that wraps the - /// sub-model ID and message type). - /// - /// Returns the bindings for the sub-model. - static member subModelSeq - (getSubModels: 'model -> #seq<'subModel>, - getId: 'subModel -> 'id, - toMsg: 'id * 'subMsg -> 'msg, - bindings: unit -> Binding<'model * 'subModel, 'subMsg> list) - : string -> Binding<'model, 'msg> = - Binding.SubModelSeqKeyed.create - (fun args -> DynamicViewModel<'model * 'subModel, 'subMsg>(args, bindings ())) - IViewModel.updateModel - (snd >> getId) - (IViewModel.currentModel >> snd >> getId) - >> Binding.mapModel (fun m -> getSubModels m |> Seq.map (fun sub -> (m, sub))) - >> Binding.mapMsg toMsg - - - /// - /// Creates a binding to a sequence of sub-models, each uniquely identified - /// by the value returned by . The sub-models have - /// their own bindings. You typically bind this to the ItemsSource of - /// an - /// ItemsControl, ListView, TreeView, etc. - /// - /// Gets the sub-models from the model. - /// Gets a unique identifier for a sub-model. - /// Returns the bindings for the sub-model. - static member subModelSeq - (getSubModels: 'model -> #seq<'subModel>, - getId: 'subModel -> 'id, - bindings: unit -> Binding<'model * 'subModel, 'msg> list) - : string -> Binding<'model, 'msg> = - Binding.SubModelSeqKeyed.create - (fun args -> DynamicViewModel<'model * 'subModel, 'msg>(args, bindings ())) - IViewModel.updateModel - (snd >> getId) - (IViewModel.currentModel >> snd >> getId) - >> Binding.mapModel (fun m -> getSubModels m |> Seq.map (fun sub -> (m, sub))) - >> Binding.mapMsg snd - - - /// - /// Creates a two-way binding to a SelectedItem-like property where - /// the - /// ItemsSource-like property is a - /// binding. Automatically converts the dynamically created Elmish.WPF view - /// models to/from their corresponding IDs, so the Elmish user code only has - /// to work with the IDs. - /// - /// Only use this if you are unable to use some kind of SelectedValue - /// or - /// SelectedIndex property with a normal - /// binding. This binding is less type-safe. It will throw when initializing - /// the bindings if - /// does not correspond to a binding, and it will - /// throw at runtime if the inferred 'id type does not match the - /// actual ID type used in that binding. - /// - /// - /// The name of the binding used as the items - /// source. - /// - /// Gets the selected sub-model/sub-binding ID from the - /// model. - /// - /// Returns the message to dispatch on selections/de-selections. - /// - static member subModelSelectedItem - (subModelSeqBindingName: string, - get: 'model -> 'id voption, - set: 'id voption -> 'model -> 'msg) - : string -> Binding<'model, 'msg> = - Binding.SubModelSelectedItem.vopt subModelSeqBindingName - >> Binding.addLazy (=) - >> Binding.mapModel get - >> Binding.mapMsgWithModel set - >> Binding.addCaching - - /// - /// Creates a two-way binding to a SelectedItem-like property where - /// the - /// ItemsSource-like property is a - /// binding. Automatically converts the dynamically created Elmish.WPF view - /// models to/from their corresponding IDs, so the Elmish user code only has - /// to work with the IDs. - /// - /// Only use this if you are unable to use some kind of SelectedValue - /// or - /// SelectedIndex property with a normal - /// binding. This binding is less type-safe. It will throw when initializing - /// the bindings if - /// does not correspond to a binding, and it will - /// throw at runtime if the inferred 'id type does not match the - /// actual ID type used in that binding. - /// - /// - /// The name of the binding used as the items - /// source. - /// - /// Gets the selected sub-model/sub-binding ID from the - /// model. - /// - /// Returns the message to dispatch on selections/de-selections. - /// - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member subModelSelectedItem - (subModelSeqBindingName: string, - get: 'model -> 'id voption, - set: 'id voption -> 'model -> 'msg, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.subModelSelectedItem (subModelSeqBindingName, get, set) - >> Binding.alterMsgStream wrapDispatch - - - /// - /// Creates a two-way binding to a SelectedItem-like property where - /// the - /// ItemsSource-like property is a - /// binding. Automatically converts the dynamically created Elmish.WPF view - /// models to/from their corresponding IDs, so the Elmish user code only has - /// to work with the IDs. - /// - /// Only use this if you are unable to use some kind of SelectedValue - /// or - /// SelectedIndex property with a normal - /// binding. This binding is less type-safe. It will throw when initializing - /// the bindings if - /// does not correspond to a binding, and it will - /// throw at runtime if the inferred 'id type does not match the - /// actual ID type used in that binding. - /// - /// - /// The name of the binding used as the items - /// source. - /// - /// Gets the selected sub-model/sub-binding ID from the - /// model. - /// - /// Returns the message to dispatch on selections/de-selections. - /// - static member subModelSelectedItem - (subModelSeqBindingName: string, - get: 'model -> 'id option, - set: 'id option -> 'model -> 'msg) - : string -> Binding<'model, 'msg> = - Binding.SubModelSelectedItem.opt subModelSeqBindingName - >> Binding.addLazy (=) - >> Binding.mapModel get - >> Binding.mapMsgWithModel set - >> Binding.addCaching - - /// - /// Creates a two-way binding to a SelectedItem-like property where - /// the - /// ItemsSource-like property is a - /// binding. Automatically converts the dynamically created Elmish.WPF view - /// models to/from their corresponding IDs, so the Elmish user code only has - /// to work with the IDs. - /// - /// Only use this if you are unable to use some kind of SelectedValue - /// or - /// SelectedIndex property with a normal - /// binding. This binding is less type-safe. It will throw when initializing - /// the bindings if - /// does not correspond to a binding, and it will - /// throw at runtime if the inferred 'id type does not match the - /// actual ID type used in that binding. - /// - /// - /// The name of the binding used as the items - /// source. - /// - /// Gets the selected sub-model/sub-binding ID from the - /// model. - /// - /// Returns the message to dispatch on selections/de-selections. - /// - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member subModelSelectedItem - (subModelSeqBindingName: string, - get: 'model -> 'id option, - set: 'id option -> 'model -> 'msg, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.subModelSelectedItem (subModelSeqBindingName, get, set) - >> Binding.alterMsgStream wrapDispatch - - - -// Some members are implemented as extensions to help overload resolution -[] -module Extensions = - - type Binding with - - /// Creates a one-way-to-source binding. - /// Returns the message to dispatch. - static member oneWayToSource - (set: 'a -> 'msg) - : string -> Binding<'model, 'msg> = - Binding.OneWayToSource.id<'model, 'a> - >> Binding.mapMsg set - - /// - /// Creates a one-way-to-source binding to an optional value. The binding - /// automatically converts between a missing value in the model and - /// a null value in the view. - /// - /// Returns the message to dispatch. - static member oneWayToSourceOpt - (set: 'a option -> 'msg) - : string -> Binding<'model, 'msg> = - Binding.OneWayToSource.opt - >> Binding.mapMsg set - - /// - /// Creates a one-way-to-source binding to an optional value. The binding - /// automatically converts between a missing value in the model and - /// a null value in the view. - /// - /// Returns the message to dispatch. - static member oneWayToSourceOpt - (set: 'a voption -> 'msg) - : string -> Binding<'model, 'msg> = - Binding.OneWayToSource.vopt - >> Binding.mapMsg set - - - /// Creates a two-way binding. - /// Gets the value from the model. - /// Returns the message to dispatch. - static member twoWay - (get: 'model -> 'a, - set: 'a -> 'msg) - : string -> Binding<'model, 'msg> = - Binding.TwoWay.id<'a> - >> Binding.addLazy (=) - >> Binding.mapModel get - >> Binding.mapMsg set - - /// Creates a two-way binding. - /// Gets the value from the model. - /// Returns the message to dispatch. - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member twoWay - (get: 'model -> 'a, - set: 'a -> 'msg, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.twoWay (get, set) - >> Binding.alterMsgStream wrapDispatch - - - /// - /// Creates a two-way binding to an optional value. The binding - /// automatically converts between the optional source value and an - /// unwrapped (possibly null) value on the view side. - /// - /// Gets the value from the model. - /// Returns the message to dispatch. - static member twoWayOpt - (get: 'model -> 'a option, - set: 'a option -> 'msg) - : string -> Binding<'model, 'msg> = - Binding.TwoWay.opt<'a> - >> Binding.addLazy (=) - >> Binding.mapModel get - >> Binding.mapMsg set - - /// - /// Creates a two-way binding to an optional value. The binding - /// automatically converts between the optional source value and an - /// unwrapped (possibly null) value on the view side. - /// - /// Gets the value from the model. - /// Returns the message to dispatch. - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member twoWayOpt - (get: 'model -> 'a option, - set: 'a option -> 'msg, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.twoWayOpt (get, set) - >> Binding.alterMsgStream wrapDispatch - - - /// - /// Creates a two-way binding to an optional value. The binding - /// automatically converts between the optional source value and an - /// unwrapped (possibly null) value on the view side. - /// - /// Gets the value from the model. - /// Returns the message to dispatch. - static member twoWayOpt - (get: 'model -> 'a voption, - set: 'a voption -> 'msg) - : string -> Binding<'model, 'msg> = - Binding.TwoWay.vopt<'a> - >> Binding.addLazy (=) - >> Binding.mapModel get - >> Binding.mapMsg set - - /// - /// Creates a two-way binding to an optional value. The binding - /// automatically converts between the optional source value and an - /// unwrapped (possibly null) value on the view side. - /// - /// Gets the value from the model. - /// Returns the message to dispatch. - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member twoWayOpt - (get: 'model -> 'a voption, - set: 'a voption -> 'msg, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.twoWayOpt (get, set) - >> Binding.alterMsgStream wrapDispatch - - - /// - /// Creates a two-way binding with validation using - /// INotifyDataErrorInfo. - /// - /// Gets the value from the model. - /// Returns the message to dispatch. - /// - /// Returns the validation messages from the updated model. - /// - static member twoWayValidate - (get: 'model -> 'a, - set: 'a -> 'msg, - validate: 'model -> string list) - : string -> Binding<'model, 'msg> = - Binding.TwoWay.id<'a> - >> Binding.addLazy (=) - >> Binding.mapModel get - >> Binding.mapMsg set - >> Binding.addValidation validate - - /// - /// Creates a two-way binding with validation using - /// INotifyDataErrorInfo. - /// - /// Gets the value from the model. - /// Returns the message to dispatch. - /// - /// Returns the validation messages from the updated model. - /// - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member twoWayValidate - (get: 'model -> 'a, - set: 'a -> 'msg, - validate: 'model -> string list, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.twoWayValidate (get, set, validate) - >> Binding.alterMsgStream wrapDispatch - - - /// - /// Creates a two-way binding with validation using - /// INotifyDataErrorInfo. - /// - /// Gets the value from the model. - /// Returns the message to dispatch. - /// - /// Returns the validation message from the updated model. - /// - static member twoWayValidate - (get: 'model -> 'a, - set: 'a -> 'msg, - validate: 'model -> string voption) - : string -> Binding<'model, 'msg> = - Binding.TwoWay.id<'a> - >> Binding.addLazy (=) - >> Binding.mapModel get - >> Binding.mapMsg set - >> Binding.addValidation (validate >> ValueOption.toList) - - /// - /// Creates a two-way binding with validation using - /// INotifyDataErrorInfo. - /// - /// Gets the value from the model. - /// Returns the message to dispatch. - /// - /// Returns the validation message from the updated model. - /// - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member twoWayValidate - (get: 'model -> 'a, - set: 'a -> 'msg, - validate: 'model -> string voption, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.twoWayValidate (get, set, validate) - >> Binding.alterMsgStream wrapDispatch - - - /// - /// Creates a two-way binding with validation using - /// INotifyDataErrorInfo. - /// - /// Gets the value from the model. - /// Returns the message to dispatch. - /// - /// Returns the validation message from the updated model. - /// - static member twoWayValidate - (get: 'model -> 'a, - set: 'a -> 'msg, - validate: 'model -> string option) - : string -> Binding<'model, 'msg> = - Binding.TwoWay.id<'a> - >> Binding.addLazy (=) - >> Binding.mapModel get - >> Binding.mapMsg set - >> Binding.addValidation (validate >> Option.toList) - - /// - /// Creates a two-way binding with validation using - /// INotifyDataErrorInfo. - /// - /// Gets the value from the model. - /// Returns the message to dispatch. - /// - /// Returns the validation message from the updated model. - /// - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member twoWayValidate - (get: 'model -> 'a, - set: 'a -> 'msg, - validate: 'model -> string option, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.twoWayValidate (get, set, validate) - >> Binding.alterMsgStream wrapDispatch - - - /// - /// Creates a two-way binding with validation using - /// INotifyDataErrorInfo. - /// - /// Gets the value from the model. - /// Returns the message to dispatch. - /// - /// Returns the validation message from the updated model. - /// - static member twoWayValidate - (get: 'model -> 'a, - set: 'a -> 'msg, - validate: 'model -> Result<'ignored, string>) - : string -> Binding<'model, 'msg> = - Binding.TwoWay.id<'a> - >> Binding.addLazy (=) - >> Binding.mapModel get - >> Binding.mapMsg set - >> Binding.addValidation (validate >> ValueOption.ofError >> ValueOption.toList) - - /// - /// Creates a two-way binding with validation using - /// INotifyDataErrorInfo. - /// - /// Gets the value from the model. - /// Returns the message to dispatch. - /// - /// Returns the validation message from the updated model. - /// - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member twoWayValidate - (get: 'model -> 'a, - set: 'a -> 'msg, - validate: 'model -> Result<'ignored, string>, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.twoWayValidate (get, set, validate) - >> Binding.alterMsgStream wrapDispatch - - - /// - /// Creates a two-way binding to an optional value with validation using - /// INotifyDataErrorInfo. The binding automatically converts - /// between the optional source value and an unwrapped (possibly - /// null) value on the view side. - /// - /// Gets the value from the model. - /// Returns the message to dispatch. - /// - /// Returns the validation messages from the updated model. - /// - static member twoWayOptValidate - (get: 'model -> 'a voption, - set: 'a voption -> 'msg, - validate: 'model -> string list) - : string -> Binding<'model, 'msg> = - Binding.TwoWay.vopt<'a> - >> Binding.addLazy (=) - >> Binding.mapModel get - >> Binding.mapMsg set - >> Binding.addValidation validate - - /// - /// Creates a two-way binding to an optional value with validation using - /// INotifyDataErrorInfo. The binding automatically converts - /// between the optional source value and an unwrapped (possibly - /// null) value on the view side. - /// - /// Gets the value from the model. - /// Returns the message to dispatch. - /// - /// Returns the validation messages from the updated model. - /// - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member twoWayOptValidate - (get: 'model -> 'a voption, - set: 'a voption -> 'msg, - validate: 'model -> string list, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.twoWayOptValidate (get, set, validate) - >> Binding.alterMsgStream wrapDispatch - - - /// - /// Creates a two-way binding to an optional value with validation using - /// INotifyDataErrorInfo. The binding automatically converts - /// between the optional source value and an unwrapped (possibly - /// null) value on the view side. - /// - /// Gets the value from the model. - /// Returns the message to dispatch. - /// - /// Returns the validation message from the updated model. - /// - static member twoWayOptValidate - (get: 'model -> 'a voption, - set: 'a voption -> 'msg, - validate: 'model -> string voption) - : string -> Binding<'model, 'msg> = - Binding.TwoWay.vopt<'a> - >> Binding.addLazy (=) - >> Binding.mapModel get - >> Binding.mapMsg set - >> Binding.addValidation (validate >> ValueOption.toList) - - /// - /// Creates a two-way binding to an optional value with validation using - /// INotifyDataErrorInfo. The binding automatically converts - /// between the optional source value and an unwrapped (possibly - /// null) value on the view side. - /// - /// Gets the value from the model. - /// Returns the message to dispatch. - /// - /// Returns the validation message from the updated model. - /// - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member twoWayOptValidate - (get: 'model -> 'a voption, - set: 'a voption -> 'msg, - validate: 'model -> string voption, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.twoWayOptValidate (get, set, validate) - >> Binding.alterMsgStream wrapDispatch - - - /// - /// Creates a two-way binding to an optional value with validation using - /// INotifyDataErrorInfo. The binding automatically converts - /// between the optional source value and an unwrapped (possibly - /// null) value on the view side. - /// - /// Gets the value from the model. - /// Returns the message to dispatch. - /// - /// Returns the validation message from the updated model. - /// - static member twoWayOptValidate - (get: 'model -> 'a voption, - set: 'a voption -> 'msg, - validate: 'model -> string option) - : string -> Binding<'model, 'msg> = - Binding.TwoWay.vopt<'a> - >> Binding.addLazy (=) - >> Binding.mapModel get - >> Binding.mapMsg set - >> Binding.addValidation (validate >> Option.toList) - - /// - /// Creates a two-way binding to an optional value with validation using - /// INotifyDataErrorInfo. The binding automatically converts - /// between the optional source value and an unwrapped (possibly - /// null) value on the view side. - /// - /// Gets the value from the model. - /// Returns the message to dispatch. - /// - /// Returns the validation message from the updated model. - /// - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member twoWayOptValidate - (get: 'model -> 'a voption, - set: 'a voption -> 'msg, - validate: 'model -> string option, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.twoWayOptValidate (get, set, validate) - >> Binding.alterMsgStream wrapDispatch - - - /// - /// Creates a two-way binding to an optional value with validation using - /// INotifyDataErrorInfo. The binding automatically converts - /// between the optional source value and an unwrapped (possibly - /// null) value on the view side. - /// - /// Gets the value from the model. - /// Returns the message to dispatch. - /// - /// Returns the validation message from the updated model. - /// - static member twoWayOptValidate - (get: 'model -> 'a voption, - set: 'a voption -> 'msg, - validate: 'model -> Result<'ignored, string>) - : string -> Binding<'model, 'msg> = - Binding.TwoWay.vopt<'a> - >> Binding.addLazy (=) - >> Binding.mapModel get - >> Binding.mapMsg set - >> Binding.addValidation (validate >> ValueOption.ofError >> ValueOption.toList) - - /// - /// Creates a two-way binding to an optional value with validation using - /// INotifyDataErrorInfo. The binding automatically converts - /// between the optional source value and an unwrapped (possibly - /// null) value on the view side. - /// - /// Gets the value from the model. - /// Returns the message to dispatch. - /// - /// Returns the validation message from the updated model. - /// - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member twoWayOptValidate - (get: 'model -> 'a voption, - set: 'a voption -> 'msg, - validate: 'model -> Result<'ignored, string>, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.twoWayOptValidate (get, set, validate) - >> Binding.alterMsgStream wrapDispatch - - - /// - /// Creates a two-way binding to an optional value with validation using - /// INotifyDataErrorInfo. The binding automatically converts - /// between the optional source value and an unwrapped (possibly - /// null) value on the view side. - /// - /// Gets the value from the model. - /// Returns the message to dispatch. - /// - /// Returns the validation messages from the updated model. - /// - static member twoWayOptValidate - (get: 'model -> 'a option, - set: 'a option -> 'msg, - validate: 'model -> string list) - : string -> Binding<'model, 'msg> = - Binding.TwoWay.opt<'a> - >> Binding.addLazy (=) - >> Binding.mapModel get - >> Binding.mapMsg set - >> Binding.addValidation validate - - /// - /// Creates a two-way binding to an optional value with validation using - /// INotifyDataErrorInfo. The binding automatically converts - /// between the optional source value and an unwrapped (possibly - /// null) value on the view side. - /// - /// Gets the value from the model. - /// Returns the message to dispatch. - /// - /// Returns the validation messages from the updated model. - /// - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member twoWayOptValidate - (get: 'model -> 'a option, - set: 'a option -> 'msg, - validate: 'model -> string list, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.twoWayOptValidate (get, set, validate) - >> Binding.alterMsgStream wrapDispatch - - - /// - /// Creates a two-way binding to an optional value with validation using - /// INotifyDataErrorInfo. The binding automatically converts - /// between the optional source value and an unwrapped (possibly - /// null) value on the view side. - /// - /// Gets the value from the model. - /// Returns the message to dispatch. - /// - /// Returns the validation message from the updated model. - /// - static member twoWayOptValidate - (get: 'model -> 'a option, - set: 'a option -> 'msg, - validate: 'model -> string voption) - : string -> Binding<'model, 'msg> = - Binding.TwoWay.opt<'a> - >> Binding.addLazy (=) - >> Binding.mapModel get - >> Binding.mapMsg set - >> Binding.addValidation (validate >> ValueOption.toList) - - /// - /// Creates a two-way binding to an optional value with validation using - /// INotifyDataErrorInfo. The binding automatically converts - /// between the optional source value and an unwrapped (possibly - /// null) value on the view side. - /// - /// Gets the value from the model. - /// Returns the message to dispatch. - /// - /// Returns the validation message from the updated model. - /// - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member twoWayOptValidate - (get: 'model -> 'a option, - set: 'a option -> 'msg, - validate: 'model -> string voption, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.twoWayOptValidate (get, set, validate) - >> Binding.alterMsgStream wrapDispatch - - - /// - /// Creates a two-way binding to an optional value with validation using - /// INotifyDataErrorInfo. The binding automatically converts - /// between the optional source value and an unwrapped (possibly - /// null) value on the view side. - /// - /// Gets the value from the model. - /// Returns the message to dispatch. - /// - /// Returns the validation message from the updated model. - /// - static member twoWayOptValidate - (get: 'model -> 'a option, - set: 'a option -> 'msg, - validate: 'model -> string option) - : string -> Binding<'model, 'msg> = - Binding.TwoWay.opt<'a> - >> Binding.addLazy (=) - >> Binding.mapModel get - >> Binding.mapMsg set - >> Binding.addValidation (validate >> Option.toList) - - /// - /// Creates a two-way binding to an optional value with validation using - /// INotifyDataErrorInfo. The binding automatically converts - /// between the optional source value and an unwrapped (possibly - /// null) value on the view side. - /// - /// Gets the value from the model. - /// Returns the message to dispatch. - /// - /// Returns the validation message from the updated model. - /// - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member twoWayOptValidate - (get: 'model -> 'a option, - set: 'a option -> 'msg, - validate: 'model -> string option, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.twoWayOptValidate (get, set, validate) - >> Binding.alterMsgStream wrapDispatch - - - /// - /// Creates a two-way binding to an optional value with validation using - /// INotifyDataErrorInfo. The binding automatically converts - /// between the optional source value and an unwrapped (possibly - /// null) value on the view side. - /// - /// Gets the value from the model. - /// Returns the message to dispatch. - /// - /// Returns the validation message from the updated model. - /// - static member twoWayOptValidate - (get: 'model -> 'a option, - set: 'a option -> 'msg, - validate: 'model -> Result<'ignored, string>) - : string -> Binding<'model, 'msg> = - Binding.TwoWay.opt<'a> - >> Binding.addLazy (=) - >> Binding.mapModel get - >> Binding.mapMsg set - >> Binding.addValidation (validate >> ValueOption.ofError >> ValueOption.toList) - - /// - /// Creates a two-way binding to an optional value with validation using - /// INotifyDataErrorInfo. The binding automatically converts - /// between the optional source value and an unwrapped (possibly - /// null) value on the view side. - /// - /// Gets the value from the model. - /// Returns the message to dispatch. - /// - /// Returns the validation message from the updated model. - /// - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member twoWayOptValidate - (get: 'model -> 'a option, - set: 'a option -> 'msg, - validate: 'model -> Result<'ignored, string>, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.twoWayOptValidate (get, set, validate) - >> Binding.alterMsgStream wrapDispatch - - - /// - /// Creates a Command binding that dispatches the specified message - /// and can always execute. - /// - /// Returns the message to dispatch. - static member cmd - (exec: 'msg) - : string -> Binding<'model, 'msg> = - Binding.Cmd.create - (fun _ -> exec |> ValueSome) - (fun _ -> true) - - /// - /// Creates a Command binding that dispatches the specified message - /// and can always execute. - /// - /// Returns the message to dispatch. - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member cmd - (exec: 'msg, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.cmd exec - >> Binding.alterMsgStream wrapDispatch - - - /// - /// Creates a Command binding that dispatches the specified message - /// and can execute if returns true. - /// - /// Returns the message to dispatch. - /// Indicates whether the command can execute. - static member cmdIf - (exec: 'msg, - canExec: 'model -> bool) - : string -> Binding<'model, 'msg> = - Binding.Cmd.create - (fun _ -> exec |> ValueSome) - canExec - - /// - /// Creates a Command binding that dispatches the specified message - /// and can execute if returns true. - /// - /// Returns the message to dispatch. - /// Indicates whether the command can execute. - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member cmdIf - (exec: 'msg, - canExec: 'model -> bool, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.cmdIf (exec, canExec) - >> Binding.alterMsgStream wrapDispatch - - - /// - /// Creates a Command binding that depends on the - /// CommandParameter - /// and can always execute. - /// - /// Returns the message to dispatch. - static member cmdParam - (exec: obj -> 'msg) - : string -> Binding<'model, 'msg> = - Binding.Cmd.createWithParam - (fun p _ -> exec p |> ValueSome) - (fun _ _ -> true) - false - - /// - /// Creates a Command binding that depends on the - /// CommandParameter - /// and can always execute. - /// - /// Returns the message to dispatch. - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member cmdParam - (exec: obj -> 'msg, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.cmdParam exec - >> Binding.alterMsgStream wrapDispatch - - - /// - /// Creates a conditional Command binding that depends on the - /// CommandParameter - /// and can execute if returns ValueSome. - /// - /// Returns the message to dispatch. - /// - /// If true, CanExecuteChanged will trigger every time WPF's - /// CommandManager - /// detects UI changes that could potentially influence the command's - /// ability to execute. This will likely lead to many more triggers than - /// necessary, but is needed if you have bound the CommandParameter - /// to another UI property. - /// - static member cmdParamIf - (exec: obj -> 'msg voption, - ?uiBoundCmdParam: bool) - : string -> Binding<'model, 'msg> = - Binding.Cmd.createWithParam - (fun p _ -> exec p) - (fun p _ -> exec p |> ValueOption.isSome) - (defaultArg uiBoundCmdParam false) - - /// - /// Creates a conditional Command binding that depends on the - /// CommandParameter - /// and can execute if returns ValueSome. - /// - /// Returns the message to dispatch. - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member cmdParamIf - (exec: obj -> 'msg voption, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.cmdParamIf exec - >> Binding.alterMsgStream wrapDispatch - - /// - /// Creates a conditional Command binding that depends on the - /// CommandParameter - /// and can execute if returns ValueSome. - /// - /// Returns the message to dispatch. - /// - /// If true, CanExecuteChanged will trigger every time WPF's - /// CommandManager - /// detects UI changes that could potentially influence the command's - /// ability to execute. This will likely lead to many more triggers than - /// necessary, but is needed if you have bound the CommandParameter - /// to another UI property. - /// - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member cmdParamIf - (exec: obj -> 'msg voption, - uiBoundCmdParam: bool, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.cmdParamIf (exec, uiBoundCmdParam) - >> Binding.alterMsgStream wrapDispatch - - - /// - /// Creates a conditional Command binding that depends on the - /// CommandParameter - /// and can execute if returns Some. - /// - /// Returns the message to dispatch. - /// - /// If true, CanExecuteChanged will trigger every time WPF's - /// CommandManager - /// detects UI changes that could potentially influence the command's - /// ability to execute. This will likely lead to many more triggers than - /// necessary, but is needed if you have bound the CommandParameter - /// to another UI property. - /// - static member cmdParamIf - (exec: obj -> 'msg option, - ?uiBoundCmdParam: bool) - : string -> Binding<'model, 'msg> = - Binding.Cmd.createWithParam - (fun p _ -> exec p |> ValueOption.ofOption) - (fun p _ -> exec p |> Option.isSome) - (defaultArg uiBoundCmdParam false) - - /// - /// Creates a conditional Command binding that depends on the - /// CommandParameter - /// and can execute if returns Some. - /// - /// Returns the message to dispatch. - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member cmdParamIf - (exec: obj -> 'msg option, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.cmdParamIf exec - >> Binding.alterMsgStream wrapDispatch - - /// - /// Creates a conditional Command binding that depends on the - /// CommandParameter - /// and can execute if returns Some. - /// - /// Returns the message to dispatch. - /// - /// If true, CanExecuteChanged will trigger every time WPF's - /// CommandManager - /// detects UI changes that could potentially influence the command's - /// ability to execute. This will likely lead to many more triggers than - /// necessary, but is needed if you have bound the CommandParameter - /// to another UI property. - /// - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member cmdParamIf - (exec: obj -> 'msg option, - uiBoundCmdParam: bool, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.cmdParamIf (exec, uiBoundCmdParam) - >> Binding.alterMsgStream wrapDispatch - - - /// - /// Creates a conditional Command binding that depends on the - /// CommandParameter - /// and can execute if returns Ok. - /// - /// This overload allows more easily re-using the same validation - /// functions for inputs and commands. - /// - /// Returns the message to dispatch. - /// - /// If true, CanExecuteChanged will trigger every time WPF's - /// CommandManager - /// detects UI changes that could potentially influence the command's - /// ability to execute. This will likely lead to many more triggers than - /// necessary, but is needed if you have bound the CommandParameter - /// to another UI property. - /// - static member cmdParamIf - (exec: obj -> Result<'msg, 'ignored>, - ?uiBoundCmdParam: bool) - : string -> Binding<'model, 'msg> = - Binding.Cmd.createWithParam - (fun p _ -> exec p |> ValueOption.ofOk) - (fun p _ -> exec p |> Result.isOk) - (defaultArg uiBoundCmdParam false) - - /// - /// Creates a conditional Command binding that depends on the - /// CommandParameter - /// and can execute if returns Ok. - /// - /// This overload allows more easily re-using the same validation - /// functions for inputs and commands. - /// - /// Returns the message to dispatch. - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member cmdParamIf - (exec: obj -> Result<'msg, 'ignored>, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.cmdParamIf exec - >> Binding.alterMsgStream wrapDispatch - - /// - /// Creates a conditional Command binding that depends on the - /// CommandParameter - /// and can execute if returns Ok. - /// - /// This overload allows more easily re-using the same validation - /// functions for inputs and commands. - /// - /// Returns the message to dispatch. - /// - /// If true, CanExecuteChanged will trigger every time WPF's - /// CommandManager - /// detects UI changes that could potentially influence the command's - /// ability to execute. This will likely lead to many more triggers than - /// necessary, but is needed if you have bound the CommandParameter - /// to another UI property. - /// - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member cmdParamIf - (exec: obj -> Result<'msg, 'ignored>, - uiBoundCmdParam: bool, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.cmdParamIf (exec, uiBoundCmdParam) - >> Binding.alterMsgStream wrapDispatch - - - /// - /// Creates a Command binding that depends on the - /// CommandParameter - /// and can execute if returns true. - /// - /// Returns the message to dispatch. - /// Indicates whether the command can execute. - /// - /// If true, CanExecuteChanged will trigger every time WPF's - /// CommandManager - /// detects UI changes that could potentially influence the command's - /// ability to execute. This will likely lead to many more triggers than - /// necessary, but is needed if you have bound the CommandParameter - /// to another UI property. - /// - static member cmdParamIf - (exec: obj -> 'msg, - canExec: obj -> bool, - ?uiBoundCmdParam: bool) - : string -> Binding<'model, 'msg> = - Binding.Cmd.createWithParam - (fun p _ -> exec p |> ValueSome) - (fun p _ -> canExec p) - (defaultArg uiBoundCmdParam false) - - /// - /// Creates a Command binding that depends on the - /// CommandParameter - /// and can execute if returns true. - /// - /// Returns the message to dispatch. - /// Indicates whether the command can execute. - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member cmdParamIf - (exec: obj -> 'msg, - canExec: obj -> bool, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.cmdParamIf (exec, canExec) - >> Binding.alterMsgStream wrapDispatch - - /// - /// Creates a Command binding that depends on the - /// CommandParameter - /// and can execute if returns true. - /// - /// Returns the message to dispatch. - /// Indicates whether the command can execute. - /// - /// If true, CanExecuteChanged will trigger every time WPF's - /// CommandManager - /// detects UI changes that could potentially influence the command's - /// ability to execute. This will likely lead to many more triggers than - /// necessary, but is needed if you have bound the CommandParameter - /// to another UI property. - /// - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member cmdParamIf - (exec: obj -> 'msg, - canExec: obj -> bool, - uiBoundCmdParam: bool, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.cmdParamIf (exec, canExec, uiBoundCmdParam) - >> Binding.alterMsgStream wrapDispatch - - - /// - /// Creates a two-way binding to a SelectedItem-like property where - /// the - /// ItemsSource-like property is a - /// binding. Automatically converts the dynamically created Elmish.WPF - /// view models to/from their corresponding IDs, so the Elmish user code - /// only has to work with the IDs. - /// - /// Only use this if you are unable to use some kind of - /// SelectedValue or - /// SelectedIndex property with a normal - /// binding. This binding is less type-safe. It will throw when - /// initializing the bindings if - /// does not correspond to a binding, and it - /// will throw at runtime if the inferred 'id type does not - /// match the actual ID type used in that binding. - /// - /// - /// The name of the binding used as the items - /// source. - /// - /// Gets the selected sub-model/sub-binding ID from the - /// model. - /// - /// Returns the message to dispatch on selections/de-selections. - /// - static member subModelSelectedItem - (subModelSeqBindingName: string, - get: 'model -> 'id voption, - set: 'id voption -> 'msg) - : string -> Binding<'model, 'msg> = - Binding.SubModelSelectedItem.vopt subModelSeqBindingName - >> Binding.addLazy (=) - >> Binding.mapModel get - >> Binding.mapMsg set - >> Binding.addCaching - - /// - /// Creates a two-way binding to a SelectedItem-like property where - /// the - /// ItemsSource-like property is a - /// binding. Automatically converts the dynamically created Elmish.WPF - /// view models to/from their corresponding IDs, so the Elmish user code - /// only has to work with the IDs. - /// - /// Only use this if you are unable to use some kind of - /// SelectedValue or - /// SelectedIndex property with a normal - /// binding. This binding is less type-safe. It will throw when - /// initializing the bindings if - /// does not correspond to a binding, and it - /// will throw at runtime if the inferred 'id type does not - /// match the actual ID type used in that binding. - /// - /// - /// The name of the binding used as the items - /// source. - /// - /// Gets the selected sub-model/sub-binding ID from the - /// model. - /// - /// Returns the message to dispatch on selections/de-selections. - /// - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member subModelSelectedItem - (subModelSeqBindingName: string, - get: 'model -> 'id voption, - set: 'id voption -> 'msg, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.subModelSelectedItem (subModelSeqBindingName, get, set) - >> Binding.alterMsgStream wrapDispatch - - - /// - /// Creates a two-way binding to a SelectedItem-like property where - /// the - /// ItemsSource-like property is a - /// binding. Automatically converts the dynamically created Elmish.WPF - /// view models to/from their corresponding IDs, so the Elmish user code - /// only has to work with the IDs. - /// - /// Only use this if you are unable to use some kind of - /// SelectedValue or - /// SelectedIndex property with a normal - /// binding. This binding is less type-safe. It will throw when - /// initializing the bindings if - /// does not correspond to a binding, and it - /// will throw at runtime if the inferred 'id type does not - /// match the actual ID type used in that binding. - /// - /// - /// The name of the binding used as the items - /// source. - /// - /// Gets the selected sub-model/sub-binding ID from the - /// model. - /// - /// Returns the message to dispatch on selections/de-selections. - /// - static member subModelSelectedItem - (subModelSeqBindingName: string, - get: 'model -> 'id option, - set: 'id option -> 'msg) - : string -> Binding<'model, 'msg> = - Binding.SubModelSelectedItem.opt subModelSeqBindingName - >> Binding.addLazy (=) - >> Binding.mapModel get - >> Binding.mapMsg set - >> Binding.addCaching - - /// - /// Creates a two-way binding to a SelectedItem-like property where - /// the - /// ItemsSource-like property is a - /// binding. Automatically converts the dynamically created Elmish.WPF - /// view models to/from their corresponding IDs, so the Elmish user code - /// only has to work with the IDs. - /// - /// Only use this if you are unable to use some kind of - /// SelectedValue or - /// SelectedIndex property with a normal - /// binding. This binding is less type-safe. It will throw when - /// initializing the bindings if - /// does not correspond to a binding, and it - /// will throw at runtime if the inferred 'id type does not - /// match the actual ID type used in that binding. - /// - /// - /// The name of the binding used as the items - /// source. - /// - /// Gets the selected sub-model/sub-binding ID from the - /// model. - /// - /// Returns the message to dispatch on selections/de-selections. - /// - /// - /// Wraps the dispatch function with additional behavior, such as - /// throttling, debouncing, or limiting. - /// - [] - static member subModelSelectedItem - (subModelSeqBindingName: string, - get: 'model -> 'id option, - set: 'id option -> 'msg, - wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) - : string -> Binding<'model, 'msg> = - Binding.subModelSelectedItem (subModelSeqBindingName, get, set) - >> Binding.alterMsgStream wrapDispatch + /// The binding to which caching is added. + let addCaching (binding: Binding<'model, 'msg, 't>) : Binding<'model, 'msg, 't> = binding |> mapData addCaching + + /// + /// Adds validation to the given binding using INotifyDataErrorInfo. + /// + /// Returns the errors associated with the given model. + /// The binding to which validation is added. + let addValidation + (validate: 'model -> string list) + (binding: Binding<'model, 'msg, 't>) + : Binding<'model, 'msg, 't> = + binding |> mapData (addValidation validate) + + /// + /// Adds laziness to the updating of the given binding. If the models are considered equal, + /// then updating of the given binding is skipped. + /// + /// Updating skipped when this function returns true. + /// The binding to which the laziness is added. + let addLazy (equals: 'model -> 'model -> bool) (binding: Binding<'model, 'msg, 't>) : Binding<'model, 'msg, 't> = + binding |> mapData (addLazy equals) + + /// + /// Alters the message stream via the given function. + /// Ideally suited for use with Reactive Extensions. + /// + /// open FSharp.Control.Reactive + /// let delay dispatch = + /// let subject = Subject.broadcast + /// let observable = subject :> System.IObservable<_> + /// observable + /// |> Observable.delay (System.TimeSpan.FromSeconds 1.0) + /// |> Observable.subscribe dispatch + /// |> ignore + /// subject.OnNext + /// + /// // ... + /// + /// binding |> Binding.alterMsgStream delay + /// + /// + /// The function that can alter the message stream. + /// The binding of the altered message stream. + let alterMsgStream + (alteration: ('b -> unit) -> 'a -> unit) + (binding: Binding<'model, 'a, 't>) + : Binding<'model, 'b, 't> = + binding |> mapData (alterMsgStream alteration) + + + /// + /// Strongly-typed bindings that update the view from the model. + /// + module OneWayT = + + /// Elemental instance of a one-way binding. + let id<'a, 'msg> : string -> Binding<'a, 'msg, 'a> = OneWay.id |> createBindingT + + /// Creates a one-way binding to an optional value. The binding + /// automatically converts between a missing value in the model and + /// a null value in the view. + let opt x : Binding<'a option, 'msg, System.Nullable<'a>> = x |> id |> mapModel Option.toNullable + + /// Creates a one-way binding to an optional value. The binding + /// automatically converts between a missing value in the model and + /// a null value in the view. + let vopt x : Binding<'a voption, 'msg, System.Nullable<'a>> = + x |> id |> mapModel ValueOption.toNullable + + /// Creates a one-way binding to an optional value. The binding + /// automatically converts between a missing value in the model and + /// a null value in the view. + let optobj<'a, 'msg when 'a: null> : string -> Binding<'a option, 'msg, 'a> = + id<'a, 'msg> >> mapModel Option.toObj + + /// Creates a one-way binding to an optional value. The binding + /// automatically converts between a missing value in the model and + /// a null value in the view. + let voptobj<'a, 'msg when 'a: null> : string -> Binding<'a voption, 'msg, 'a> = + id<'a, 'msg> >> mapModel ValueOption.toObj + + /// + /// Strongly-typed bindings that update the model from the view. + /// + module OneWayToSourceT = + + /// Elemental instance of a one-way-to-source binding. + let id<'model, 'a> : string -> Binding<'model, 'a, 'a> = + OneWayToSource.id |> createBindingT + + /// Creates a one-way-to-source binding to an optional value. The binding + /// automatically converts between a missing value in the model and + /// a null value in the view. + let optobj<'a, 'model when 'a: null> : string -> Binding<'model, 'a option, 'a> = + id<'model, 'a> >> mapMsg Option.ofObj + + /// Creates a one-way-to-source binding to an optional value. The binding + /// automatically converts between a missing value in the model and + /// a null value in the view. + let voptobj<'a, 'model when 'a: null> : string -> Binding<'model, 'a voption, 'a> = + id<'model, 'a> >> mapMsg ValueOption.ofObj + + /// Creates a one-way-to-source binding to an optional value. The binding + /// automatically converts between a missing value in the model and + /// a null value in the view. + let opt x : Binding<'model, 'a option, System.Nullable<'a>> = x |> id |> mapMsg Option.ofNullable + + /// Creates a one-way-to-source binding to an optional value. The binding + /// automatically converts between a missing value in the model and + /// a null value in the view. + let vopt x : Binding<'model, 'a voption, System.Nullable<'a>> = + x |> id |> mapMsg ValueOption.ofNullable + + /// + /// Strongly-typed bindings that update both ways + /// + module TwoWayT = + + /// Elemental instance of a two-way binding. + let id<'a> : string -> Binding<'a, 'a, 'a> = TwoWay.id |> createBindingT + + /// Creates a two-way binding to an optional value. The binding + /// automatically converts between a missing value in the model and + /// a null value in the view. + let opt x : Binding<'a option, 'a option, System.Nullable<'a>> = + x |> id |> mapMsg Option.ofNullable |> mapModel Option.toNullable + + /// Creates a two-way binding to an optional value. The binding + /// automatically converts between a missing value in the model and + /// a null value in the view. + let vopt x : Binding<'a voption, 'a voption, System.Nullable<'a>> = + x |> id |> mapMsg ValueOption.ofNullable |> mapModel ValueOption.toNullable + + /// Creates a two-way binding to an optional value. The binding + /// automatically converts between a missing value in the model and + /// a null value in the view. + let optobj<'a when 'a: null> : string -> Binding<'a option, 'a option, 'a> = + id<'a> >> mapModel Option.toObj >> mapMsg Option.ofObj + + /// Creates a two-way binding to an optional value. The binding + /// automatically converts between a missing value in the model and + /// a null value in the view. + let voptobj<'a when 'a: null> : string -> Binding<'a voption, 'a voption, 'a> = + id<'a> >> mapMsg ValueOption.ofObj >> mapModel ValueOption.toObj + + /// + /// The strongly-typed counterpart of Binding.oneWaySeq with parameter getId. + /// Exposes an ObservableCollection of child items for binding. + /// Allows a more efficient update than would be possible without using ids. + /// + module OneWaySeqT = + + /// + /// Elemental instance of a OneWaySeqT binding + /// + /// Defines whether an item is "equal" and needs to be updated if the ids are the same + /// Unique identifier for each item in the list (for efficient updates). + let id itemEquals (getId: 'a -> 'id) : string -> Binding<_, 'msg, _> = + OneWaySeq.create itemEquals getId |> createBindingT + + /// + /// Strongly-typed bindings that dispatch messages from the view. + /// + module CmdT = + + /// + /// Elemental instance of a Command binding. + /// Creates a Command binding that only passes the CommandParameter) + /// + /// + /// If true, CanExecuteChanged will trigger every time WPF's + /// CommandManager + /// detects UI changes that could potentially influence the command's + /// ability to execute. This will likely lead to many more triggers than + /// necessary, but is needed if you have bound the CommandParameter + /// to another UI property. + /// + /// Indicates whether the command can execute. + let id<'model> uiBoundCmdParam canExec : string -> Binding<'model, obj, ICommand> = + Cmd.createWithParam (fun p _ -> ValueSome p) canExec uiBoundCmdParam + |> createBindingT + + /// + /// Creates a Command binding that depends only on the model (not the + /// CommandParameter). + /// + /// Indicates whether the command can execute. + /// Returns the message to dispatch. + let model canExec (exec: 'model -> 'msg) : string -> Binding<'model, 'msg, ICommand> = + id false (fun _ m -> m |> canExec) + >> mapMsgWithModel (fun _ y -> y |> exec) + >> addLazy (fun m1 m2 -> canExec m1 = canExec m2) + + /// + /// Creates a Command binding that dispatches the specified message. + /// + /// Indicates whether the command can execute. + /// The message to dispatch. + let set canExec (msg: 'msg) : string -> Binding<'model, 'msg, ICommand> = + id false (fun _ m -> m |> canExec) >> setMsg msg + + /// + /// Creates a Command binding that depends only on the model (not the + /// CommandParameter) and always executes. + /// + /// Returns the message to dispatch. + let modelAlways (exec: 'model -> 'msg) : string -> Binding<'model, 'msg, ICommand> = model (fun _ -> true) exec + + /// + /// Creates a Command binding that dispatches the specified message + /// and always executes. + /// + /// The message to dispatch. + let setAlways (msg: 'msg) : string -> Binding<'model, 'msg, ICommand> = set (fun _ -> true) msg + + module OneWay = + + /// Elemental instance of a one-way binding. + let id<'a, 'msg> : string -> Binding<'a, 'msg> = OneWay.id |> createBinding + + /// Creates a one-way binding to an optional value. The binding + /// automatically converts between a missing value in the model and + /// a null value in the view. + let opt<'a, 'msg> : string -> Binding<'a option, 'msg> = + id >> mapModel Option.box + + /// Creates a one-way binding to an optional value. The binding + /// automatically converts between a missing value in the model and + /// a null value in the view. + let vopt<'a, 'msg> : string -> Binding<'a voption, 'msg> = + id >> mapModel ValueOption.box + + + module OneWayToSource = + + /// Elemental instance of a one-way-to-source binding. + let id<'model, 'a> : string -> Binding<'model, 'a> = + OneWayToSource.id |> createBinding + + /// Creates a one-way-to-source binding to an optional value. The binding + /// automatically converts between a missing value in the model and + /// a null value in the view. + let vopt<'model, 'a> : string -> Binding<'model, 'a voption> = + id<'model, obj> >> mapMsg ValueOption.unbox + + /// Creates a one-way-to-source binding to an optional value. The binding + /// automatically converts between a missing value in the model and + /// a null value in the view. + let opt<'model, 'a> : string -> Binding<'model, 'a option> = + id<'model, obj> >> mapMsg Option.unbox + + + module TwoWay = + + /// Elemental instance of a two-way binding. + let id<'a> : string -> Binding<'a, 'a> = TwoWay.id |> createBinding + + /// Creates a two-way binding to an optional value. The binding + /// automatically converts between a missing value in the model and + /// a null value in the view. + let vopt<'a> : string -> Binding<'a voption, 'a voption> = + id >> mapModel ValueOption.box >> mapMsg ValueOption.unbox + + /// Creates a two-way binding to an optional value. The binding + /// automatically converts between a missing value in the model and + /// a null value in the view. + let opt<'a> : string -> Binding<'a option, 'a option> = + id >> mapModel Option.box >> mapMsg Option.unbox + + + module SubModelSelectedItem = + + /// Creates a two-way binding to a SelectedItem-like property where + /// the ItemsSource-like property is a + /// binding. Automatically converts the dynamically created Elmish.WPF view + /// models to/from their corresponding IDs, so the Elmish user code only has + /// to work with the IDs. + /// + /// Only use this if you are unable to use some kind of SelectedValue + /// or SelectedIndex property with a normal + /// binding. This binding is less type-safe. It will throw when initializing + /// the bindings if + /// does not correspond to a binding, and it will + /// throw at runtime if the inferred 'id type does not match the + /// actual ID type used in that binding. + let vopt subModelSeqBindingName : string -> Binding<'id voption, 'id voption> = + SubModelSelectedItem.create subModelSeqBindingName + |> createBinding + >> mapModel (ValueOption.map box) + >> mapMsg (ValueOption.map unbox) + + /// Creates a two-way binding to a SelectedItem-like property where + /// the ItemsSource-like property is a + /// binding. Automatically converts the dynamically created Elmish.WPF view + /// models to/from their corresponding IDs, so the Elmish user code only has + /// to work with the IDs. + /// + /// Only use this if you are unable to use some kind of SelectedValue + /// or SelectedIndex property with a normal + /// binding. This binding is less type-safe. It will throw when initializing + /// the bindings if + /// does not correspond to a binding, and it will + /// throw at runtime if the inferred 'id type does not match the + /// actual ID type used in that binding. + let opt subModelSeqBindingName : string -> Binding<'id option, 'id option> = + vopt subModelSeqBindingName + >> mapModel ValueOption.ofOption + >> mapMsg ValueOption.toOption + + + module Cmd = + + let internal createWithParam exec canExec autoRequery = + Cmd.createWithParam exec canExec autoRequery |> createBinding + + let internal create exec canExec = + createWithParam (fun _ -> exec) (fun _ -> canExec) false + >> addLazy (fun m1 m2 -> canExec m1 = canExec m2) + + + module OneWaySeq = + + let internal create get itemEquals getId = + OneWaySeq.create itemEquals getId |> BindingData.mapModel get |> createBinding + + + module SubModel = + + /// + /// Creates a binding to a sub-model/component. You typically bind this + /// to the DataContext of a UserControl or similar. + /// + /// Returns the bindings for the sub-model. + let vopt (bindings: unit -> Binding<'model, 'msg> list) : string -> Binding<'model voption, 'msg> = + SubModel.create (fun args -> DynamicViewModel<'model, 'msg>(args, bindings ())) IViewModel.updateModel + |> createBinding + + /// + /// Creates a binding to a sub-model/component. You typically bind this + /// to the DataContext of a UserControl or similar. + /// + /// Returns the bindings for the sub-model. + let opt (bindings: unit -> Binding<'model, 'msg> list) : string -> Binding<'model option, 'msg> = + vopt bindings >> mapModel ValueOption.ofOption + + /// + /// Creates a binding to a sub-model/component. You typically bind this + /// to the DataContext of a UserControl or similar. + /// + /// Returns the bindings for the sub-model. + let required (bindings: unit -> Binding<'model, 'msg> list) : string -> Binding<'model, 'msg> = + vopt bindings >> mapModel ValueSome + + /// + /// The strongly-typed counterpart of module SubModel. + /// For creating bindings to child view models that have their own bindings. + /// Typically bound from WPF using DataContext and Binding. + /// Can be used in binding lists if boxed using . + /// + module SubModelT = + + /// Exposes an optional view model member for binding. + let opt + (createVm: ViewModelArgs<'bindingModel, 'msg> -> #IViewModel<'bindingModel, 'msg>) + : (string -> Binding<'bindingModel voption, 'msg, #IViewModel<'bindingModel, 'msg>>) = + SubModel.create createVm IViewModel.updateModel |> createBindingT + + /// Exposes a non-optional view model member for binding. + let req + (createVm: ViewModelArgs<'bindingModel, 'msg> -> #IViewModel<'bindingModel, 'msg>) + : (string -> Binding<'bindingModel, 'msg, #IViewModel<'bindingModel, 'msg>>) = + SubModel.create createVm IViewModel.updateModel + |> createBindingT + >> mapModel ValueSome + + /// + /// Exposes a 'a seq (IEnumerable<'a>) view model member for binding. + /// Used rarely; usually, you want to expose an ObservableCollection<'a> + /// using SubModelSeqKeyedT or SubModelSeqUnkeyedT. + /// + let seq + (createVm: ViewModelArgs<'bindingModel, 'msg> -> #seq<#IViewModel<'bindingModel, 'msg>>) + : (string -> Binding<'bindingModel, 'msg, #seq<#IViewModel<'bindingModel, 'msg>>>) = + SubModel.create createVm (fun (vms, m) -> vms |> Seq.iter (fun vm -> IViewModel.updateModel (vm, m))) + |> createBindingT + >> mapModel ValueSome + + /// + /// The strongly-typed counterpart of Binding.subModelSeq without parameter getId. + /// Exposes an ObservableCollection of child view models for binding. + /// Identifies elements by index; + /// if possible, use SubModelSeqKeyedT (which uses parameter getId) instead. + /// Typically bound from WPF using DataContext and Binding. + /// Can be used in binding lists if boxed using . + /// + module SubModelSeqUnkeyedT = + + /// + /// Creates an elemental SubModelSeqUnkeyedT binding. + /// + /// + /// The function applied to every element of the bound ObservableCollection + /// to create a child view model. + /// + let id + (createVm: ViewModelArgs<'bindingModel, 'msg> -> #IViewModel<'bindingModel, 'msg>) + : (string + -> Binding<'bindingModelCollection, int * 'msg, ObservableCollection<#IViewModel<'bindingModel, 'msg>>>) + = + SubModelSeqUnkeyed.create createVm IViewModel.updateModel |> createBindingT + + /// + /// The strongly-typed counterpart of Binding.subModelSeq with parameter getId. + /// Exposes an ObservableCollection of child view models for binding. + /// Typically bound from WPF using DataContext and Binding. + /// Can be used in binding lists if boxed using . + /// + module SubModelSeqKeyedT = + + /// + /// Creates an elemental SubModelSeqUnkeyedT binding. + /// + /// + /// The function applied to every element of the bound ObservableCollection + /// to create a child view model. + /// + /// + /// The function applied to every element of the bound ObservableCollection + /// to get a key used to identify that element. + /// Should not return duplicate keys for different elements. + /// + let id + (createVm: ViewModelArgs<'bindingModel, 'msg> -> #IViewModel<'bindingModel, 'msg>) + (getId: 'bindingModel -> 'id) + : (string + -> Binding<'bindingModelCollection, 'id * 'msg, ObservableCollection<#IViewModel<'bindingModel, 'msg>>>) + = + SubModelSeqKeyed.create createVm IViewModel.updateModel getId (IViewModel.currentModel >> getId) + |> createBindingT + + /// + /// The strongly-typed counterpart of Binding.subModelWin. + /// Like , but uses the WindowState wrapper + /// to show/hide/close a new window that will have the specified bindings as + /// its DataContext. + /// + /// You do not need to set the DataContext yourself (either in code + /// or in XAML). + /// + /// Can be used in binding lists if boxed using . + /// + module SubModelWinT = + + /// + /// Creates an elemental SubModelWinT binding. + /// Like , but uses the WindowState wrapper + /// to show/hide/close a new window that will have the specified bindings as + /// its DataContext. + /// + /// You do not need to set the DataContext yourself (either in code + /// or in XAML). + /// The window can only be closed/hidden by changing the return value of + /// , and cannot be directly closed by the + /// user. External close attempts (the Close/X button, Alt+F4, or System + /// Menu -> Close) will cause the message specified by + /// to be dispatched. You should supply + /// and react to this in a manner that + /// will not confuse a user trying to close the window (e.g. by closing it + /// or displaying relevant feedback to the user). + /// + /// Gets the window state and a sub-model. + /// Returns the view model for the window. + /// + /// The function used to get and configure the window. + /// + /// + /// Specifies whether the window will be shown modally (using + /// Window.ShowDialog, blocking the rest of the app) or non-modally (using + /// Window.Show). + /// + /// + /// The message to be dispatched on external close attempts (the Close/X + /// button, Alt+F4, or System Menu -> Close). + /// + let id + (getState: 'model -> WindowState<'bindingModel>) + (createVM: ViewModelArgs<'bindingModel, 'bindingMsg> -> #IViewModel<'bindingModel, 'bindingMsg>) + getWindow + isModal + onCloseRequested + = + SubModelWin.create getState createVM IViewModel.updateModel Func2.id2 getWindow isModal onCloseRequested + |> createBindingT + + + module SelectedIndex = + /// Prebuilt binding intended for use with Selector.SelectedIndex. + let vopt = + TwoWay.id + >> mapModel (ValueOption.defaultValue -1) + >> mapMsg (fun i -> if i < 0 then ValueNone else ValueSome i) + + /// Prebuilt binding intended for use with Selector.SelectedIndex. + let opt = vopt >> mapModel ValueOption.ofOption >> mapMsg ValueOption.toOption + + + module SubModelWin = + + let internal create getState createViewModel updateViewModel toMsg getWindow isModal onCloseRequested = + SubModelWin.create getState createViewModel updateViewModel toMsg getWindow isModal onCloseRequested + |> createBinding + + + module SubModelSeqUnkeyed = + + let internal create createViewModel updateViewModel = + SubModelSeqUnkeyed.create createViewModel updateViewModel |> createBinding + + + module SubModelSeqKeyed = + + let internal create createViewModel updateViewModel bmToId vmToId = + SubModelSeqKeyed.create createViewModel updateViewModel bmToId vmToId + |> createBinding + + +module Bindings = + + /// Maps the model of a list of bindings via a contravariant mapping. + let mapModel (f: 'a -> 'b) (bindings: Binding<'b, 'msg> list) = + f |> Binding.mapModel |> List.map <| bindings + + /// Maps the message of a list of bindings with access to the model via a covariant mapping. + let mapMsgWithModel (f: 'a -> 'model -> 'b) (bindings: Binding<'model, 'a> list) = + f |> Binding.mapMsgWithModel |> List.map <| bindings + + /// Maps the message of a list of bindings via a covariant mapping. + let mapMsg (f: 'a -> 'b) (bindings: Binding<'model, 'a> list) = + f |> Binding.mapMsg |> List.map <| bindings + + +[] +type Binding private () = + + /// + /// Creates a binding intended for use with Selector.SelectedIndex. + /// + /// Gets the selected index from the model. + /// Returns the message to dispatch. + static member selectedIndex(get: 'model -> int voption, set: int voption -> 'msg) = + Binding.SelectedIndex.vopt >> Binding.mapModel get >> Binding.mapMsg set + + /// + /// Creates a binding intended for use with Selector.SelectedIndex. + /// + /// Gets the selected index from the model. + /// Returns the message to dispatch. + static member selectedIndex(get: 'model -> int option, set: int option -> 'msg) = + Binding.SelectedIndex.opt >> Binding.mapModel get >> Binding.mapMsg set + + + /// Creates a one-way binding. + /// Gets the value from the model. + static member oneWay(get: 'model -> 'a) : string -> Binding<'model, 'msg> = + Binding.OneWay.id<'a, 'msg> >> Binding.addLazy (=) >> Binding.mapModel get + + + /// + /// Creates a one-way binding to an optional value. The binding + /// automatically converts between the optional source value and an + /// unwrapped (possibly + /// null) value on the view side. + /// + /// Gets the value from the model. + static member oneWayOpt(get: 'model -> 'a option) : string -> Binding<'model, 'msg> = + Binding.OneWay.opt<'a, 'msg> >> Binding.addLazy (=) >> Binding.mapModel get + + + /// + /// Creates a one-way binding to an optional value. The binding + /// automatically converts between the optional source value and an + /// unwrapped (possibly + /// null) value on the view side. + /// + /// Gets the value from the model. + static member oneWayOpt(get: 'model -> 'a voption) : string -> Binding<'model, 'msg> = + Binding.OneWay.vopt<'a, 'msg> >> Binding.addLazy (=) >> Binding.mapModel get + + + /// + /// Creates a lazily evaluated one-way binding. + /// will be called only when the output of changes, + /// as determined by . This may have better + /// performance than for expensive computations (but + /// may be less performant for non-expensive functions due to additional + /// overhead). + /// + /// Gets the value from the model. + /// + /// Indicates whether two intermediate values are equal. Good candidates are + /// elmEq and refEq. + /// + /// Transforms the value into the final type. + static member oneWayLazy + (get: 'model -> 'a, equals: 'a -> 'a -> bool, map: 'a -> 'b) + : string -> Binding<'model, 'msg> = + Binding.OneWay.id<'b, 'msg> + >> Binding.mapModel map + >> Binding.addLazy equals + >> Binding.mapModel get + >> Binding.addCaching + + + /// + /// Creates a lazily evaluated one-way binding to an optional value. The + /// binding automatically converts between the optional source value and an + /// unwrapped (possibly null) value on the view side. will be called only when the output of changes, as determined by . + /// + /// This may have better performance than a non-lazy binding for expensive + /// computations (but may be less performant for non-expensive functions due + /// to additional overhead). + /// + /// Gets the intermediate value from the model. + /// + /// Indicates whether two intermediate values are equal. Good candidates are + /// elmEq and refEq. + /// + /// Transforms the intermediate value into the final + /// type. + static member oneWayOptLazy + (get: 'model -> 'a, equals: 'a -> 'a -> bool, map: 'a -> 'b option) + : string -> Binding<'model, 'msg> = + Binding.OneWay.opt<'b, 'msg> + >> Binding.mapModel map + >> Binding.addLazy equals + >> Binding.mapModel get + >> Binding.addCaching + + + /// + /// Creates a lazily evaluated one-way binding to an optional value. The + /// binding automatically converts between the optional source value and an + /// unwrapped (possibly null) value on the view side. will be called only when the output of changes, as determined by . + /// + /// This may have better performance than a non-lazy binding for expensive + /// computations (but may be less performant for non-expensive functions due + /// to additional overhead). + /// + /// Gets the value from the model. + /// + /// Indicates whether two intermediate values are equal. Good candidates are + /// elmEq and refEq. + /// + /// Transforms the intermediate value into the final + /// type. + static member oneWayOptLazy + (get: 'model -> 'a, equals: 'a -> 'a -> bool, map: 'a -> 'b voption) + : string -> Binding<'model, 'msg> = + Binding.OneWay.vopt<'b, 'msg> + >> Binding.mapModel map + >> Binding.addLazy equals + >> Binding.mapModel get + >> Binding.addCaching + + + /// Creates a one-way-to-source binding. + /// Returns the message to dispatch. + static member oneWayToSource(set: 'a -> 'model -> 'msg) : string -> Binding<'model, 'msg> = + Binding.OneWayToSource.id<'model, 'a> >> Binding.mapMsgWithModel set + + /// + /// Creates a one-way-to-source binding to an optional value. The binding + /// automatically converts between a missing value in the model and + /// a null value in the view. + /// + /// Returns the message to dispatch. + static member oneWayToSourceOpt(set: 'a option -> 'model -> 'msg) : string -> Binding<'model, 'msg> = + Binding.OneWayToSource.opt >> Binding.mapMsgWithModel set + + /// + /// Creates a one-way-to-source binding to an optional value. The binding + /// automatically converts between a missing value in the model and + /// a null value in the view. + /// + /// Returns the message to dispatch. + static member oneWayToSourceOpt(set: 'a voption -> 'model -> 'msg) : string -> Binding<'model, 'msg> = + Binding.OneWayToSource.vopt >> Binding.mapMsgWithModel set + + + /// + /// Creates a one-way binding to a sequence of items, each uniquely + /// identified by the value returned by . The + /// binding will not be updated if the output of + /// does not change, as determined by . + /// The binding is backed by a persistent ObservableCollection, so + /// only changed items (as determined by ) + /// will be replaced. If the items are complex and you want them updated + /// instead of replaced, consider using . + /// + /// Gets the intermediate value from the model. + /// + /// Indicates whether two intermediate values are equal. Good candidates are + /// elmEq and refEq. + /// + /// Transforms the value into the final collection. + /// + /// Indicates whether two collection items are equal. Good candidates are + /// elmEq, refEq, or simply (=). + /// + /// Gets a unique identifier for a collection + /// item. + static member oneWaySeqLazy + ( + get: 'model -> 'a, + equals: 'a -> 'a -> bool, + map: 'a -> #seq<'b>, + itemEquals: 'b -> 'b -> bool, + getId: 'b -> 'id + ) : string -> Binding<'model, 'msg> = + Binding.OneWaySeq.create map itemEquals getId + >> Binding.addLazy equals + >> Binding.mapModel get + + + /// + /// Creates a one-way binding to a sequence of items, each uniquely + /// identified by the value returned by . The + /// binding will not be updated if the output of + /// is referentially equal. This is the same as calling + /// with equals = refEq and + /// map = id. The binding is backed by a persistent + /// ObservableCollection, so only changed items (as determined by + /// ) will be replaced. If the items are + /// complex and you want them updated instead of replaced, consider using + /// . + /// + /// Gets the collection from the model. + /// + /// Indicates whether two collection items are equal. Good candidates are + /// elmEq, refEq, or simply (=). + /// + /// Gets a unique identifier for a collection + /// item. + static member oneWaySeq + (get: 'model -> #seq<'a>, itemEquals: 'a -> 'a -> bool, getId: 'a -> 'id) + : string -> Binding<'model, 'msg> = + Binding.OneWaySeq.create id itemEquals getId + >> Binding.addLazy refEq + >> Binding.mapModel get + + + /// Creates a two-way binding. + /// Gets the value from the model. + /// Returns the message to dispatch. + static member twoWay(get: 'model -> 'a, set: 'a -> 'model -> 'msg) : string -> Binding<'model, 'msg> = + Binding.TwoWay.id<'a> + >> Binding.addLazy (=) + >> Binding.mapModel get + >> Binding.mapMsgWithModel set + + /// Creates a two-way binding. + /// Gets the value from the model. + /// Returns the message to dispatch. + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member twoWay + (get: 'model -> 'a, set: 'a -> 'model -> 'msg, wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) + : string -> Binding<'model, 'msg> = + Binding.twoWay (get, set) >> Binding.alterMsgStream wrapDispatch + + + /// + /// Creates a two-way binding to an optional value. The binding + /// automatically converts between the optional source value and an + /// unwrapped (possibly null) value on the view side. + /// + /// Gets the value from the model. + /// Returns the message to dispatch. + static member twoWayOpt + (get: 'model -> 'a option, set: 'a option -> 'model -> 'msg) + : string -> Binding<'model, 'msg> = + Binding.TwoWay.opt<'a> + >> Binding.addLazy (=) + >> Binding.mapModel get + >> Binding.mapMsgWithModel set + + /// + /// Creates a two-way binding to an optional value. The binding + /// automatically converts between the optional source value and an + /// unwrapped (possibly null) value on the view side. + /// + /// Gets the value from the model. + /// Returns the message to dispatch. + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member twoWayOpt + (get: 'model -> 'a option, set: 'a option -> 'model -> 'msg, wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) + : string -> Binding<'model, 'msg> = + Binding.twoWayOpt (get, set) >> Binding.alterMsgStream wrapDispatch + + + /// + /// Creates a two-way binding to an optional value. The binding + /// automatically converts between the optional source value and an + /// unwrapped (possibly null) value on the view side. + /// + /// Gets the value from the model. + /// Returns the message to dispatch. + static member twoWayOpt + (get: 'model -> 'a voption, set: 'a voption -> 'model -> 'msg) + : string -> Binding<'model, 'msg> = + Binding.TwoWay.vopt<'a> + >> Binding.addLazy (=) + >> Binding.mapModel get + >> Binding.mapMsgWithModel set + + /// + /// Creates a two-way binding to an optional value. The binding + /// automatically converts between the optional source value and an + /// unwrapped (possibly null) value on the view side. + /// + /// Gets the value from the model. + /// Returns the message to dispatch. + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member twoWayOpt + (get: 'model -> 'a voption, set: 'a voption -> 'model -> 'msg, wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) + : string -> Binding<'model, 'msg> = + Binding.twoWayOpt (get, set) >> Binding.alterMsgStream wrapDispatch + + + /// + /// Creates a two-way binding with validation using + /// INotifyDataErrorInfo. + /// + /// Gets the value from the model. + /// Returns the message to dispatch. + /// + /// Returns the validation messages from the updated model. + /// + static member twoWayValidate + (get: 'model -> 'a, set: 'a -> 'model -> 'msg, validate: 'model -> string list) + : string -> Binding<'model, 'msg> = + Binding.TwoWay.id<'a> + >> Binding.addLazy (=) + >> Binding.mapModel get + >> Binding.mapMsgWithModel set + >> Binding.addValidation validate + + /// + /// Creates a two-way binding with validation using + /// INotifyDataErrorInfo. + /// + /// Gets the value from the model. + /// Returns the message to dispatch. + /// + /// Returns the validation messages from the updated model. + /// + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member twoWayValidate + ( + get: 'model -> 'a, + set: 'a -> 'model -> 'msg, + validate: 'model -> string list, + wrapDispatch: Dispatch<'msg> -> Dispatch<'msg> + ) : string -> Binding<'model, 'msg> = + Binding.twoWayValidate (get, set, validate) + >> Binding.alterMsgStream wrapDispatch + + + /// + /// Creates a two-way binding with validation using + /// INotifyDataErrorInfo. + /// + /// Gets the value from the model. + /// Returns the message to dispatch. + /// + /// Returns the validation message from the updated model. + /// + static member twoWayValidate + (get: 'model -> 'a, set: 'a -> 'model -> 'msg, validate: 'model -> string voption) + : string -> Binding<'model, 'msg> = + Binding.TwoWay.id<'a> + >> Binding.addLazy (=) + >> Binding.mapModel get + >> Binding.mapMsgWithModel set + >> Binding.addValidation (validate >> ValueOption.toList) + + /// + /// Creates a two-way binding with validation using + /// INotifyDataErrorInfo. + /// + /// Gets the value from the model. + /// Returns the message to dispatch. + /// + /// Returns the validation message from the updated model. + /// + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member twoWayValidate + ( + get: 'model -> 'a, + set: 'a -> 'model -> 'msg, + validate: 'model -> string voption, + wrapDispatch: Dispatch<'msg> -> Dispatch<'msg> + ) : string -> Binding<'model, 'msg> = + Binding.twoWayValidate (get, set, validate) + >> Binding.alterMsgStream wrapDispatch + + + /// + /// Creates a two-way binding with validation using + /// INotifyDataErrorInfo. + /// + /// Gets the value from the model. + /// Returns the message to dispatch. + /// + /// Returns the validation message from the updated model. + /// + static member twoWayValidate + (get: 'model -> 'a, set: 'a -> 'model -> 'msg, validate: 'model -> string option) + : string -> Binding<'model, 'msg> = + Binding.TwoWay.id<'a> + >> Binding.addLazy (=) + >> Binding.mapModel get + >> Binding.mapMsgWithModel set + >> Binding.addValidation (validate >> Option.toList) + + /// + /// Creates a two-way binding with validation using + /// INotifyDataErrorInfo. + /// + /// Gets the value from the model. + /// Returns the message to dispatch. + /// + /// Returns the validation message from the updated model. + /// + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member twoWayValidate + ( + get: 'model -> 'a, + set: 'a -> 'model -> 'msg, + validate: 'model -> string option, + wrapDispatch: Dispatch<'msg> -> Dispatch<'msg> + ) : string -> Binding<'model, 'msg> = + Binding.twoWayValidate (get, set, validate) + >> Binding.alterMsgStream wrapDispatch + + + /// + /// Creates a two-way binding with validation using + /// INotifyDataErrorInfo. + /// + /// Gets the value from the model. + /// Returns the message to dispatch. + /// + /// Returns the validation message from the updated model. + /// + static member twoWayValidate + (get: 'model -> 'a, set: 'a -> 'model -> 'msg, validate: 'model -> Result<'ignored, string>) + : string -> Binding<'model, 'msg> = + Binding.TwoWay.id<'a> + >> Binding.addLazy (=) + >> Binding.mapModel get + >> Binding.mapMsgWithModel set + >> Binding.addValidation (validate >> ValueOption.ofError >> ValueOption.toList) + + /// + /// Creates a two-way binding with validation using + /// INotifyDataErrorInfo. + /// + /// Gets the value from the model. + /// Returns the message to dispatch. + /// + /// Returns the validation message from the updated model. + /// + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member twoWayValidate + ( + get: 'model -> 'a, + set: 'a -> 'model -> 'msg, + validate: 'model -> Result<'ignored, string>, + wrapDispatch: Dispatch<'msg> -> Dispatch<'msg> + ) : string -> Binding<'model, 'msg> = + Binding.twoWayValidate (get, set, validate) + >> Binding.alterMsgStream wrapDispatch + + + /// + /// Creates a two-way binding to an optional value with validation using + /// INotifyDataErrorInfo. The binding automatically converts between + /// the optional source value and an unwrapped (possibly null) value + /// on the view side. + /// + /// Gets the value from the model. + /// Returns the message to dispatch. + /// + /// Returns the validation messages from the updated model. + /// + static member twoWayOptValidate + (get: 'model -> 'a voption, set: 'a voption -> 'model -> 'msg, validate: 'model -> string list) + : string -> Binding<'model, 'msg> = + Binding.TwoWay.vopt<'a> + >> Binding.addLazy (=) + >> Binding.mapModel get + >> Binding.mapMsgWithModel set + >> Binding.addValidation validate + + /// + /// Creates a two-way binding to an optional value with validation using + /// INotifyDataErrorInfo. The binding automatically converts between + /// the optional source value and an unwrapped (possibly null) value + /// on the view side. + /// + /// Gets the value from the model. + /// Returns the message to dispatch. + /// + /// Returns the validation messages from the updated model. + /// + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member twoWayOptValidate + ( + get: 'model -> 'a voption, + set: 'a voption -> 'model -> 'msg, + validate: 'model -> string list, + wrapDispatch: Dispatch<'msg> -> Dispatch<'msg> + ) : string -> Binding<'model, 'msg> = + Binding.twoWayOptValidate (get, set, validate) + >> Binding.alterMsgStream wrapDispatch + + + /// + /// Creates a two-way binding to an optional value with validation using + /// INotifyDataErrorInfo. The binding automatically converts between + /// the optional source value and an unwrapped (possibly null) value + /// on the view side. + /// + /// Gets the value from the model. + /// Returns the message to dispatch. + /// + /// Returns the validation message from the updated model. + /// + static member twoWayOptValidate + (get: 'model -> 'a voption, set: 'a voption -> 'model -> 'msg, validate: 'model -> string voption) + : string -> Binding<'model, 'msg> = + Binding.TwoWay.vopt<'a> + >> Binding.addLazy (=) + >> Binding.mapModel get + >> Binding.mapMsgWithModel set + >> Binding.addValidation (validate >> ValueOption.toList) + + /// + /// Creates a two-way binding to an optional value with validation using + /// INotifyDataErrorInfo. The binding automatically converts between + /// the optional source value and an unwrapped (possibly null) value + /// on the view side. + /// + /// Gets the value from the model. + /// Returns the message to dispatch. + /// + /// Returns the validation message from the updated model. + /// + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member twoWayOptValidate + ( + get: 'model -> 'a voption, + set: 'a voption -> 'model -> 'msg, + validate: 'model -> string voption, + wrapDispatch: Dispatch<'msg> -> Dispatch<'msg> + ) : string -> Binding<'model, 'msg> = + Binding.twoWayOptValidate (get, set, validate) + >> Binding.alterMsgStream wrapDispatch + + + /// + /// Creates a two-way binding to an optional value with validation using + /// INotifyDataErrorInfo. The binding automatically converts between + /// the optional source value and an unwrapped (possibly null) value + /// on the view side. + /// + /// Gets the value from the model. + /// Returns the message to dispatch. + /// + /// Returns the validation message from the updated model. + /// + static member twoWayOptValidate + (get: 'model -> 'a voption, set: 'a voption -> 'model -> 'msg, validate: 'model -> string option) + : string -> Binding<'model, 'msg> = + Binding.TwoWay.vopt<'a> + >> Binding.addLazy (=) + >> Binding.mapModel get + >> Binding.mapMsgWithModel set + >> Binding.addValidation (validate >> Option.toList) + + /// + /// Creates a two-way binding to an optional value with validation using + /// INotifyDataErrorInfo. The binding automatically converts between + /// the optional source value and an unwrapped (possibly null) value + /// on the view side. + /// + /// Gets the value from the model. + /// Returns the message to dispatch. + /// + /// Returns the validation message from the updated model. + /// + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member twoWayOptValidate + ( + get: 'model -> 'a voption, + set: 'a voption -> 'model -> 'msg, + validate: 'model -> string option, + wrapDispatch: Dispatch<'msg> -> Dispatch<'msg> + ) : string -> Binding<'model, 'msg> = + Binding.twoWayOptValidate (get, set, validate) + >> Binding.alterMsgStream wrapDispatch + + + /// + /// Creates a two-way binding to an optional value with validation using + /// INotifyDataErrorInfo. The binding automatically converts between + /// the optional source value and an unwrapped (possibly null) value + /// on the view side. + /// + /// Gets the value from the model. + /// Returns the message to dispatch. + /// + /// Returns the validation message from the updated model. + /// + static member twoWayOptValidate + (get: 'model -> 'a voption, set: 'a voption -> 'model -> 'msg, validate: 'model -> Result<'ignored, string>) + : string -> Binding<'model, 'msg> = + Binding.TwoWay.vopt<'a> + >> Binding.addLazy (=) + >> Binding.mapModel get + >> Binding.mapMsgWithModel set + >> Binding.addValidation (validate >> ValueOption.ofError >> ValueOption.toList) + + /// + /// Creates a two-way binding to an optional value with validation using + /// INotifyDataErrorInfo. The binding automatically converts between + /// the optional source value and an unwrapped (possibly null) value + /// on the view side. + /// + /// Gets the value from the model. + /// Returns the message to dispatch. + /// + /// Returns the validation message from the updated model. + /// + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member twoWayOptValidate + ( + get: 'model -> 'a voption, + set: 'a voption -> 'model -> 'msg, + validate: 'model -> Result<'ignored, string>, + wrapDispatch: Dispatch<'msg> -> Dispatch<'msg> + ) : string -> Binding<'model, 'msg> = + Binding.twoWayOptValidate (get, set, validate) + >> Binding.alterMsgStream wrapDispatch + + + /// + /// Creates a two-way binding to an optional value with validation using + /// INotifyDataErrorInfo. The binding automatically converts between + /// the optional source value and an unwrapped (possibly null) value + /// on the view side. + /// + /// Gets the value from the model. + /// Returns the message to dispatch. + /// + /// Returns the validation messages from the updated model. + /// + static member twoWayOptValidate + (get: 'model -> 'a option, set: 'a option -> 'model -> 'msg, validate: 'model -> string list) + : string -> Binding<'model, 'msg> = + Binding.TwoWay.opt<'a> + >> Binding.addLazy (=) + >> Binding.mapModel get + >> Binding.mapMsgWithModel set + >> Binding.addValidation validate + + /// + /// Creates a two-way binding to an optional value with validation using + /// INotifyDataErrorInfo. The binding automatically converts between + /// the optional source value and an unwrapped (possibly null) value + /// on the view side. + /// + /// Gets the value from the model. + /// Returns the message to dispatch. + /// + /// Returns the validation messages from the updated model. + /// + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member twoWayOptValidate + ( + get: 'model -> 'a option, + set: 'a option -> 'model -> 'msg, + validate: 'model -> string list, + wrapDispatch: Dispatch<'msg> -> Dispatch<'msg> + ) : string -> Binding<'model, 'msg> = + Binding.twoWayOptValidate (get, set, validate) + >> Binding.alterMsgStream wrapDispatch + + + /// + /// Creates a two-way binding to an optional value with validation using + /// INotifyDataErrorInfo. The binding automatically converts between + /// the optional source value and an unwrapped (possibly null) value + /// on the view side. + /// + /// Gets the value from the model. + /// Returns the message to dispatch. + /// + /// Returns the validation message from the updated model. + /// + static member twoWayOptValidate + (get: 'model -> 'a option, set: 'a option -> 'model -> 'msg, validate: 'model -> string voption) + : string -> Binding<'model, 'msg> = + Binding.TwoWay.opt<'a> + >> Binding.addLazy (=) + >> Binding.mapModel get + >> Binding.mapMsgWithModel set + >> Binding.addValidation (validate >> ValueOption.toList) + + /// + /// Creates a two-way binding to an optional value with validation using + /// INotifyDataErrorInfo. The binding automatically converts between + /// the optional source value and an unwrapped (possibly null) value + /// on the view side. + /// + /// Gets the value from the model. + /// Returns the message to dispatch. + /// + /// Returns the validation message from the updated model. + /// + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member twoWayOptValidate + ( + get: 'model -> 'a option, + set: 'a option -> 'model -> 'msg, + validate: 'model -> string voption, + wrapDispatch: Dispatch<'msg> -> Dispatch<'msg> + ) : string -> Binding<'model, 'msg> = + Binding.twoWayOptValidate (get, set, validate) + >> Binding.alterMsgStream wrapDispatch + + + /// + /// Creates a two-way binding to an optional value with validation using + /// INotifyDataErrorInfo. The binding automatically converts between + /// the optional source value and an unwrapped (possibly null) value + /// on the view side. + /// + /// Gets the value from the model. + /// Returns the message to dispatch. + /// + /// Returns the validation message from the updated model. + /// + static member twoWayOptValidate + (get: 'model -> 'a option, set: 'a option -> 'model -> 'msg, validate: 'model -> string option) + : string -> Binding<'model, 'msg> = + Binding.TwoWay.opt<'a> + >> Binding.addLazy (=) + >> Binding.mapModel get + >> Binding.mapMsgWithModel set + >> Binding.addValidation (validate >> Option.toList) + + /// + /// Creates a two-way binding to an optional value with validation using + /// INotifyDataErrorInfo. The binding automatically converts between + /// the optional source value and an unwrapped (possibly null) value + /// on the view side. + /// + /// Gets the value from the model. + /// Returns the message to dispatch. + /// + /// Returns the validation message from the updated model. + /// + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member twoWayOptValidate + ( + get: 'model -> 'a option, + set: 'a option -> 'model -> 'msg, + validate: 'model -> string option, + wrapDispatch: Dispatch<'msg> -> Dispatch<'msg> + ) : string -> Binding<'model, 'msg> = + Binding.twoWayOptValidate (get, set, validate) + >> Binding.alterMsgStream wrapDispatch + + + /// + /// Creates a two-way binding to an optional value with validation using + /// INotifyDataErrorInfo. The binding automatically converts between + /// the optional source value and an unwrapped (possibly null) value + /// on the view side. + /// + /// Gets the value from the model. + /// Returns the message to dispatch. + /// + /// Returns the validation message from the updated model. + /// + static member twoWayOptValidate + (get: 'model -> 'a option, set: 'a option -> 'model -> 'msg, validate: 'model -> Result<'ignored, string>) + : string -> Binding<'model, 'msg> = + Binding.TwoWay.opt<'a> + >> Binding.addLazy (=) + >> Binding.mapModel get + >> Binding.mapMsgWithModel set + >> Binding.addValidation (validate >> ValueOption.ofError >> ValueOption.toList) + + /// + /// Creates a two-way binding to an optional value with validation using + /// INotifyDataErrorInfo. The binding automatically converts between + /// the optional source value and an unwrapped (possibly null) value + /// on the view side. + /// + /// Gets the value from the model. + /// Returns the message to dispatch. + /// + /// Returns the validation message from the updated model. + /// + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member twoWayOptValidate + ( + get: 'model -> 'a option, + set: 'a option -> 'model -> 'msg, + validate: 'model -> Result<'ignored, string>, + wrapDispatch: Dispatch<'msg> -> Dispatch<'msg> + ) : string -> Binding<'model, 'msg> = + Binding.twoWayOptValidate (get, set, validate) + >> Binding.alterMsgStream wrapDispatch + + + /// + /// Creates a Command binding that depends only on the model (not the + /// CommandParameter) and can always execute. + /// + /// Returns the message to dispatch. + static member cmd(exec: 'model -> 'msg) : string -> Binding<'model, 'msg> = + Binding.Cmd.create (exec >> ValueSome) (fun _ -> true) + + /// + /// Creates a Command binding that depends only on the model (not the + /// CommandParameter) and can always execute. + /// + /// Returns the message to dispatch. + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member cmd + (exec: 'model -> 'msg, wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) + : string -> Binding<'model, 'msg> = + Binding.cmd exec >> Binding.alterMsgStream wrapDispatch + + + /// + /// Creates a conditional Command binding that depends only on the + /// model (not the CommandParameter) and can execute if + /// returns true. + /// + /// Returns the message to dispatch. + /// Indicates whether the command can execute. + static member cmdIf(exec: 'model -> 'msg, canExec: 'model -> bool) : string -> Binding<'model, 'msg> = + Binding.Cmd.create (exec >> ValueSome) canExec + + /// + /// Creates a conditional Command binding that depends only on the + /// model (not the CommandParameter) and can execute if + /// returns true. + /// + /// Returns the message to dispatch. + /// Indicates whether the command can execute. + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member cmdIf + (exec: 'model -> 'msg, canExec: 'model -> bool, wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) + : string -> Binding<'model, 'msg> = + Binding.cmdIf (exec, canExec) >> Binding.alterMsgStream wrapDispatch + + + /// + /// Creates a conditional Command binding that depends only on the + /// model (not the CommandParameter) and can execute if + /// returns ValueSome. + /// + /// Returns the message to dispatch. + static member cmdIf(exec: 'model -> 'msg voption) : string -> Binding<'model, 'msg> = + Binding.Cmd.create exec (exec >> ValueOption.isSome) + + /// + /// Creates a conditional Command binding that depends only on the + /// model (not the CommandParameter) and can execute if + /// returns ValueSome. + /// + /// Returns the message to dispatch. + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member cmdIf + (exec: 'model -> 'msg voption, wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) + : string -> Binding<'model, 'msg> = + Binding.cmdIf exec >> Binding.alterMsgStream wrapDispatch + + + /// + /// Creates a conditional Command binding that depends only on the + /// model (not the CommandParameter) and can execute if + /// returns Some. + /// + /// Returns the message to dispatch. + static member cmdIf(exec: 'model -> 'msg option) : string -> Binding<'model, 'msg> = + Binding.Cmd.create (exec >> ValueOption.ofOption) (exec >> Option.isSome) + + /// + /// Creates a conditional Command binding that depends only on the + /// model (not the CommandParameter) and can execute if + /// returns Some. + /// + /// Returns the message to dispatch. + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member cmdIf + (exec: 'model -> 'msg option, wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) + : string -> Binding<'model, 'msg> = + Binding.cmdIf exec >> Binding.alterMsgStream wrapDispatch + + + /// + /// Creates a conditional Command binding that depends only on the + /// model (not the CommandParameter) and can execute if + /// returns Ok. + /// + /// This overload allows more easily re-using the same validation functions + /// for inputs and commands. + /// + /// Returns the message to dispatch. + static member cmdIf(exec: 'model -> Result<'msg, 'ignored>) : string -> Binding<'model, 'msg> = + Binding.Cmd.create (exec >> ValueOption.ofOk) (exec >> Result.isOk) + + /// + /// Creates a conditional Command binding that depends only on the + /// model (not the CommandParameter) and can execute if + /// returns Ok. + /// + /// This overload allows more easily re-using the same validation functions + /// for inputs and commands. + /// + /// Returns the message to dispatch. + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member cmdIf + (exec: 'model -> Result<'msg, 'ignored>, wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) + : string -> Binding<'model, 'msg> = + Binding.cmdIf exec >> Binding.alterMsgStream wrapDispatch + + + /// + /// Creates a Command binding that depends on the + /// CommandParameter + /// and can always execute. + /// + /// Returns the message to dispatch. + static member cmdParam(exec: obj -> 'model -> 'msg) : string -> Binding<'model, 'msg> = + Binding.Cmd.createWithParam (fun p model -> exec p model |> ValueSome) (fun _ _ -> true) false + + /// + /// Creates a Command binding that depends on the + /// CommandParameter + /// and can always execute. + /// + /// Returns the message to dispatch. + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member cmdParam + (exec: obj -> 'model -> 'msg, wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) + : string -> Binding<'model, 'msg> = + Binding.cmdParam exec >> Binding.alterMsgStream wrapDispatch + + + /// + /// Creates a Command binding that depends on the + /// CommandParameter + /// and can execute if returns true. + /// + /// Returns the message to dispatch. + /// Indicates whether the command can execute. + /// + /// If true, CanExecuteChanged will trigger every time WPF's + /// CommandManager + /// detects UI changes that could potentially influence the command's + /// ability to execute. This will likely lead to many more triggers than + /// necessary, but is needed if you have bound the CommandParameter + /// to another UI property. + /// + static member cmdParamIf + (exec: obj -> 'model -> 'msg, canExec: obj -> 'model -> bool, ?uiBoundCmdParam: bool) + : string -> Binding<'model, 'msg> = + Binding.Cmd.createWithParam (fun p m -> exec p m |> ValueSome) canExec (defaultArg uiBoundCmdParam false) + + /// + /// Creates a Command binding that depends on the + /// CommandParameter + /// and can execute if returns true. + /// + /// Returns the message to dispatch. + /// Indicates whether the command can execute. + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member cmdParamIf + (exec: obj -> 'model -> 'msg, canExec: obj -> 'model -> bool, wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) + : string -> Binding<'model, 'msg> = + Binding.cmdParamIf (exec, canExec) >> Binding.alterMsgStream wrapDispatch + + /// + /// Creates a Command binding that depends on the + /// CommandParameter + /// and can execute if returns true. + /// + /// Returns the message to dispatch. + /// Indicates whether the command can execute. + /// + /// If true, CanExecuteChanged will trigger every time WPF's + /// CommandManager + /// detects UI changes that could potentially influence the command's + /// ability to execute. This will likely lead to many more triggers than + /// necessary, but is needed if you have bound the CommandParameter + /// to another UI property. + /// + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member cmdParamIf + ( + exec: obj -> 'model -> 'msg, + canExec: obj -> 'model -> bool, + uiBoundCmdParam: bool, + wrapDispatch: Dispatch<'msg> -> Dispatch<'msg> + ) : string -> Binding<'model, 'msg> = + Binding.cmdParamIf (exec, canExec, uiBoundCmdParam) + >> Binding.alterMsgStream wrapDispatch + + + /// + /// Creates a conditional Command binding that depends on the + /// CommandParameter + /// and can execute if returns ValueSome. + /// + /// Returns the message to dispatch. + /// + /// If true, CanExecuteChanged will trigger every time WPF's + /// CommandManager + /// detects UI changes that could potentially influence the command's + /// ability to execute. This will likely lead to many more triggers than + /// necessary, but is needed if you have bound the CommandParameter + /// to another UI property. + /// + static member cmdParamIf + (exec: obj -> 'model -> 'msg voption, ?uiBoundCmdParam: bool) + : string -> Binding<'model, 'msg> = + Binding.Cmd.createWithParam exec (fun p m -> exec p m |> ValueOption.isSome) (defaultArg uiBoundCmdParam false) + + /// + /// Creates a conditional Command binding that depends on the + /// CommandParameter + /// and can execute if returns ValueSome. + /// + /// Returns the message to dispatch. + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member cmdParamIf + (exec: obj -> 'model -> 'msg voption, wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) + : string -> Binding<'model, 'msg> = + Binding.cmdParamIf exec >> Binding.alterMsgStream wrapDispatch + + /// + /// Creates a conditional Command binding that depends on the + /// CommandParameter + /// and can execute if returns ValueSome. + /// + /// Returns the message to dispatch. + /// + /// If true, CanExecuteChanged will trigger every time WPF's + /// CommandManager + /// detects UI changes that could potentially influence the command's + /// ability to execute. This will likely lead to many more triggers than + /// necessary, but is needed if you have bound the CommandParameter + /// to another UI property. + /// + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member cmdParamIf + (exec: obj -> 'model -> 'msg voption, uiBoundCmdParam: bool, wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) + : string -> Binding<'model, 'msg> = + Binding.cmdParamIf (exec, uiBoundCmdParam) + >> Binding.alterMsgStream wrapDispatch + + + /// + /// Creates a conditional Command binding that depends on the + /// CommandParameter + /// and can execute if returns Some. + /// + /// Returns the message to dispatch. + /// + /// If true, CanExecuteChanged will trigger every time WPF's + /// CommandManager + /// detects UI changes that could potentially influence the command's + /// ability to execute. This will likely lead to many more triggers than + /// necessary, but is needed if you have bound the CommandParameter + /// to another UI property. + /// + static member cmdParamIf + (exec: obj -> 'model -> 'msg option, ?uiBoundCmdParam: bool) + : string -> Binding<'model, 'msg> = + Binding.Cmd.createWithParam + (fun p m -> exec p m |> ValueOption.ofOption) + (fun p m -> exec p m |> Option.isSome) + (defaultArg uiBoundCmdParam false) + + /// + /// Creates a conditional Command binding that depends on the + /// CommandParameter + /// and can execute if returns Some. + /// + /// Returns the message to dispatch. + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member cmdParamIf + (exec: obj -> 'model -> 'msg option, wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) + : string -> Binding<'model, 'msg> = + Binding.cmdParamIf exec >> Binding.alterMsgStream wrapDispatch + + /// + /// Creates a conditional Command binding that depends on the + /// CommandParameter + /// and can execute if returns Some. + /// + /// Returns the message to dispatch. + /// + /// If true, CanExecuteChanged will trigger every time WPF's + /// CommandManager + /// detects UI changes that could potentially influence the command's + /// ability to execute. This will likely lead to many more triggers than + /// necessary, but is needed if you have bound the CommandParameter + /// to another UI property. + /// + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member cmdParamIf + (exec: obj -> 'model -> 'msg option, uiBoundCmdParam: bool, wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) + : string -> Binding<'model, 'msg> = + Binding.cmdParamIf (exec, uiBoundCmdParam) + >> Binding.alterMsgStream wrapDispatch + + + /// + /// Creates a conditional Command binding that depends on the + /// CommandParameter + /// and can execute if returns Ok. + /// + /// This overload allows more easily re-using the same validation functions + /// for inputs and commands. + /// + /// Returns the message to dispatch. + /// + /// If true, CanExecuteChanged will trigger every time WPF's + /// CommandManager + /// detects UI changes that could potentially influence the command's + /// ability to execute. This will likely lead to many more triggers than + /// necessary, but is needed if you have bound the CommandParameter + /// to another UI property. + /// + static member cmdParamIf + (exec: obj -> 'model -> Result<'msg, 'ignored>, ?uiBoundCmdParam: bool) + : string -> Binding<'model, 'msg> = + Binding.Cmd.createWithParam + (fun p m -> exec p m |> ValueOption.ofOk) + (fun p m -> exec p m |> Result.isOk) + (defaultArg uiBoundCmdParam false) + + /// + /// Creates a conditional Command binding that depends on the + /// CommandParameter + /// and can execute if returns Ok. + /// + /// This overload allows more easily re-using the same validation functions + /// for inputs and commands. + /// + /// Returns the message to dispatch. + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member cmdParamIf + (exec: obj -> 'model -> Result<'msg, 'ignored>, wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) + : string -> Binding<'model, 'msg> = + Binding.cmdParamIf exec >> Binding.alterMsgStream wrapDispatch + + /// + /// Creates a conditional Command binding that depends on the + /// CommandParameter + /// and can execute if returns Ok. + /// + /// This overload allows more easily re-using the same validation functions + /// for inputs and commands. + /// + /// Returns the message to dispatch. + /// + /// If true, CanExecuteChanged will trigger every time WPF's + /// CommandManager + /// detects UI changes that could potentially influence the command's + /// ability to execute. This will likely lead to many more triggers than + /// necessary, but is needed if you have bound the CommandParameter + /// to another UI property. + /// + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member cmdParamIf + ( + exec: obj -> 'model -> Result<'msg, 'ignored>, + uiBoundCmdParam: bool, + wrapDispatch: Dispatch<'msg> -> Dispatch<'msg> + ) : string -> Binding<'model, 'msg> = + Binding.cmdParamIf (exec, uiBoundCmdParam) + >> Binding.alterMsgStream wrapDispatch + + + /// + /// Creates a binding to a sub-model/component that has its own bindings and + /// message type. You typically bind this to the DataContext of a + /// UserControl or similar. + /// + /// Gets the sub-model from the model. + /// + /// Converts the models to the model used by the bindings. + /// + /// + /// Converts the messages used in the bindings to parent model messages + /// (e.g. a parent message union case that wraps the child message type). + /// + /// Returns the bindings for the sub-model. + [] + static member subModel + ( + getSubModel: 'model -> 'subModel, + toBindingModel: 'model * 'subModel -> 'bindingModel, + toMsg: 'bindingMsg -> 'msg, + bindings: unit -> Binding<'bindingModel, 'bindingMsg> list + ) : string -> Binding<'model, 'msg> = + Binding.SubModel.required bindings + >> Binding.mapModel (fun m -> toBindingModel (m, getSubModel m)) + >> Binding.mapMsg toMsg + + /// + /// Creates a binding to a sub-model/component that has its own bindings and + /// message type. You typically bind this to the DataContext of a + /// UserControl or similar. + /// + /// Gets the sub-model from the model. + /// + /// Converts the messages used in the bindings to parent model messages + /// (e.g. a parent message union case that wraps the child message type). + /// + /// Returns the bindings for the sub-model. + [] + static member subModel + ( + getSubModel: 'model -> 'subModel, + toMsg: 'subMsg -> 'msg, + bindings: unit -> Binding<'model * 'subModel, 'subMsg> list + ) : string -> Binding<'model, 'msg> = + Binding.SubModel.required bindings + >> Binding.mapModel (fun m -> (m, getSubModel m)) + >> Binding.mapMsg toMsg + + + /// + /// Creates a binding to a sub-model/component that has its own bindings. + /// You typically bind this to the DataContext of a + /// UserControl or similar. + /// + /// Gets the sub-model from the model. + /// Returns the bindings for the sub-model. + [ Binding<'model, 'msg> list\". To avoid a compile error when upgrading, replace this method call with its implementation.")>] + static member subModel + (getSubModel: 'model -> 'subModel, bindings: unit -> Binding<'model * 'subModel, 'msg> list) + : string -> Binding<'model, 'msg> = + Binding.SubModel.required bindings + >> Binding.mapModel (fun m -> (m, getSubModel m)) + + + /// + /// Creates a binding to a sub-model/component that has its own bindings and + /// message type, and may not exist. If it does not exist, bindings to this + /// model will return null unless is + /// true, in which case the last non-null model will be + /// returned. You typically bind this to the DataContext of a + /// UserControl or similar. + /// + /// The 'sticky' part is useful if you want to e.g. animate away a + /// UserControl when the model is missing, but don't want the data + /// used by that control to be cleared once the animation starts. (The + /// animation must be triggered using another binding since this will never + /// return null.) + /// + /// Gets the sub-model from the model. + /// + /// Converts the models to the model used by the bindings. + /// + /// + /// Converts the messages used in the bindings to parent model messages + /// (e.g. a parent message union case that wraps the child message type). + /// + /// Returns the bindings for the sub-model. + /// + /// If true, when the model is missing, the last non-null + /// model will be returned instead of null. + /// + [] + static member subModelOpt + ( + getSubModel: 'model -> 'subModel voption, + toBindingModel: 'model * 'subModel -> 'bindingModel, + toMsg: 'bindingMsg -> 'msg, + bindings: unit -> Binding<'bindingModel, 'bindingMsg> list, + ?sticky: bool + ) : string -> Binding<'model, 'msg> = + Binding.SubModel.vopt bindings + >> if (defaultArg sticky false) then + Binding.addLazy (fun previous next -> previous.IsSome && next.IsNone) + else + id + >> Binding.mapModel (fun m -> getSubModel m |> ValueOption.map (fun sub -> toBindingModel (m, sub))) + >> Binding.mapMsg toMsg + + + /// + /// Creates a binding to a sub-model/component that has its own bindings and + /// message type, and may not exist. If it does not exist, bindings to this + /// model will return null unless is + /// true, in which case the last non-null model will be + /// returned. You typically bind this to the DataContext of a + /// UserControl or similar. + /// + /// The 'sticky' part is useful if you want to e.g. animate away a + /// UserControl when the model is missing, but don't want the data + /// used by that control to be cleared once the animation starts. (The + /// animation must be triggered using another binding since this will never + /// return null.) + /// + /// Gets the sub-model from the model. + /// + /// Converts the models to the model used by the bindings. + /// + /// + /// Converts the messages used in the bindings to parent model messages + /// (e.g. a parent message union case that wraps the child message type). + /// + /// Returns the bindings for the sub-model. + /// + /// If true, when the model is missing, the last non-null + /// model will be returned instead of null. + /// + [] + static member subModelOpt + ( + getSubModel: 'model -> 'subModel option, + toBindingModel: 'model * 'subModel -> 'bindingModel, + toMsg: 'bindingMsg -> 'msg, + bindings: unit -> Binding<'bindingModel, 'bindingMsg> list, + ?sticky: bool + ) : string -> Binding<'model, 'msg> = + Binding.SubModel.opt bindings + >> if (defaultArg sticky false) then + Binding.addLazy (fun previous next -> previous.IsSome && next.IsNone) + else + id + >> Binding.mapModel (fun m -> getSubModel m |> Option.map (fun sub -> toBindingModel (m, sub))) + >> Binding.mapMsg toMsg + + /// + /// Creates a binding to a sub-model/component that has its own bindings and + /// message type, and may not exist. If it does not exist, bindings to this + /// model will return null unless is + /// true, in which case the last non-null model will be + /// returned. You typically bind this to the DataContext of a + /// UserControl or similar. + /// + /// The 'sticky' part is useful if you want to e.g. animate away a + /// UserControl when the model is missing, but don't want the data + /// used by that control to be cleared once the animation starts. (The + /// animation must be triggered using another binding since this will never + /// return null.) + /// + /// Gets the sub-model from the model. + /// + /// Converts the messages used in the bindings to parent model messages + /// (e.g. a parent message union case that wraps the child message type). + /// + /// Returns the bindings for the sub-model. + /// + /// If true, when the model is missing, the last non-null + /// model will be returned instead of null. + /// + [] + static member subModelOpt + ( + getSubModel: 'model -> 'subModel voption, + toMsg: 'subMsg -> 'msg, + bindings: unit -> Binding<'model * 'subModel, 'subMsg> list, + ?sticky: bool + ) : string -> Binding<'model, 'msg> = + Binding.SubModel.vopt bindings + >> if (defaultArg sticky false) then + Binding.addLazy (fun previous next -> previous.IsSome && next.IsNone) + else + id + >> Binding.mapModel (fun m -> getSubModel m |> ValueOption.map (fun sub -> (m, sub))) + >> Binding.mapMsg toMsg + + + /// + /// Creates a binding to a sub-model/component that has its own bindings and + /// message type, and may not exist. If it does not exist, bindings to this + /// model will return null unless is + /// true, in which case the last non-null model will be + /// returned. You typically bind this to the DataContext of a + /// UserControl or similar. + /// + /// The 'sticky' part is useful if you want to e.g. animate away a + /// UserControl when the model is missing, but don't want the data + /// used by that control to be cleared once the animation starts. (The + /// animation must be triggered using another binding since this will never + /// return null.) + /// + /// Gets the sub-model from the model. + /// + /// Converts the messages used in the bindings to parent model messages + /// (e.g. a parent message union case that wraps the child message type). + /// + /// Returns the bindings for the sub-model. + /// + /// If true, when the model is missing, the last non-null + /// model will be returned instead of null. + /// + [] + static member subModelOpt + ( + getSubModel: 'model -> 'subModel option, + toMsg: 'subMsg -> 'msg, + bindings: unit -> Binding<'model * 'subModel, 'subMsg> list, + ?sticky: bool + ) : string -> Binding<'model, 'msg> = + Binding.SubModel.opt bindings + >> if (defaultArg sticky false) then + Binding.addLazy (fun previous next -> previous.IsSome && next.IsNone) + else + id + >> Binding.mapModel (fun m -> getSubModel m |> Option.map (fun sub -> (m, sub))) + >> Binding.mapMsg toMsg + + + /// + /// Creates a binding to a sub-model/component that has its own bindings, + /// and may not exist. If it does not exist, bindings to this model will + /// return null unless is true, in + /// which case the last non-null model will be returned. You + /// typically bind this to the DataContext of a UserControl or + /// similar. + /// + /// The 'sticky' part is useful if you want to e.g. animate away a + /// UserControl when the model is missing, but don't want the data + /// used by that control to be cleared once the animation starts. (The + /// animation must be triggered using another binding since this will never + /// return null.) + /// + /// Gets the sub-model from the model. + /// Returns the bindings for the sub-model. + /// + /// If true, when the model is missing, the last non-null + /// model will be returned instead of null. + /// + [ Binding<'model, 'msg> list\". To avoid a compile error when upgrading, replace this method call with (a specialization of) its implementation.")>] + static member subModelOpt + ( + getSubModel: 'model -> 'subModel voption, + bindings: unit -> Binding<'model * 'subModel, 'msg> list, + ?sticky: bool + ) : string -> Binding<'model, 'msg> = + Binding.SubModel.vopt bindings + >> if (defaultArg sticky false) then + Binding.addLazy (fun previous next -> previous.IsSome && next.IsNone) + else + id + >> Binding.mapModel (fun m -> getSubModel m |> ValueOption.map (fun sub -> (m, sub))) + + + /// + /// Creates a binding to a sub-model/component that has its own bindings, + /// and may not exist. If it does not exist, bindings to this model will + /// return null unless is true, in + /// which case the last non-null model will be returned. You + /// typically bind this to the DataContext of a UserControl or + /// similar. + /// + /// The 'sticky' part is useful if you want to e.g. animate away a + /// UserControl when the model is missing, but don't want the data + /// used by that control to be cleared once the animation starts. (The + /// animation must be triggered using another binding since this will never + /// return null.) + /// + /// Gets the sub-model from the model. + /// Returns the bindings for the sub-model. + /// + /// If true, when the model is missing, the last non-null + /// model will be returned instead of null. + /// + [ Binding<'model, 'msg> list\". To avoid a compile error when upgrading, replace this method call with (a specialization of) its implementation.")>] + static member subModelOpt + ( + getSubModel: 'model -> 'subModel option, + bindings: unit -> Binding<'model * 'subModel, 'msg> list, + ?sticky: bool + ) : string -> Binding<'model, 'msg> = + Binding.SubModel.opt bindings + >> if (defaultArg sticky false) then + Binding.addLazy (fun previous next -> previous.IsSome && next.IsNone) + else + id + >> Binding.mapModel (fun m -> getSubModel m |> Option.map (fun sub -> (m, sub))) + + + /// + /// Like , but uses the WindowState wrapper + /// to show/hide/close a new window that will have the specified bindings as + /// its DataContext. + /// + /// You do not need to set the DataContext yourself (neither in code + /// nor XAML). + /// + /// The window can only be closed/hidden by changing the return value of + /// , and can not be directly closed by the + /// user. External close attempts (the Close/X button, Alt+F4, or System + /// Menu -> Close) will cause the message specified by + /// to be dispatched. You should supply + /// and react to this in a manner that + /// will not confuse a user trying to close the window (e.g. by closing it, + /// or displaying relevant feedback to the user.) + /// + /// If you don't need a sub-model, you can use + /// WindowState<unit> to just control the Window visibility, + /// and pass fst to . + /// + /// Gets the window state and a sub-model. + /// + /// Converts the models to the model used by the bindings. + /// + /// + /// Converts the messages used in the bindings to parent model messages + /// (e.g. a parent message union case that wraps the child message type). + /// + /// Returns the bindings for the sub-model. + /// + /// The function used to get and configure the window. + /// + /// + /// The message to be dispatched on external close attempts (the Close/X + /// button, Alt+F4, or System Menu -> Close). + /// + /// + /// Specifies whether the window will be shown modally (using + /// window.ShowDialog, blocking the rest of the app) or non-modally (using + /// window.Show). + /// + static member subModelWin + ( + getState: 'model -> WindowState<'subModel>, + toBindingModel: 'model * 'subModel -> 'bindingModel, + toMsg: 'bindingMsg -> 'msg, + bindings: unit -> Binding<'bindingModel, 'bindingMsg> list, + getWindow: 'model -> Dispatch<'msg> -> #Window, + ?onCloseRequested: 'msg, + ?isModal: bool + ) : string -> Binding<'model, 'msg> = + Binding.SubModelWin.create + (fun m -> getState m |> WindowState.map (fun sub -> toBindingModel (m, sub))) + (fun args -> DynamicViewModel<'bindingModel, 'bindingMsg>(args, bindings ())) + IViewModel.updateModel + (fun _ -> toMsg) + (fun m d -> upcast getWindow m d) + (defaultArg isModal false) + (fun _ -> defaultArg (onCloseRequested |> Option.map ValueSome) ValueNone) + + + /// + /// Like , but uses the WindowState wrapper + /// to show/hide/close a new window that will have the specified bindings as + /// its DataContext. + /// + /// You do not need to set the DataContext yourself (neither in code + /// nor XAML). + /// + /// The window can only be closed/hidden by changing the return value of + /// , and can not be directly closed by the + /// user. External close attempts (the Close/X button, Alt+F4, or System + /// Menu -> Close) will cause the message specified by + /// to be dispatched. You should supply + /// and react to this in a manner that + /// will not confuse a user trying to close the window (e.g. by closing it, + /// or displaying relevant feedback to the user.) + /// + /// If you don't need a sub-model, you can use + /// WindowState<unit> to just control the Window visibility, + /// and pass fst to . + /// + /// Gets the window state and a sub-model. + /// + /// Converts the models to the model used by the bindings. + /// + /// + /// Converts the messages used in the bindings to parent model messages + /// (e.g. a parent message union case that wraps the child message type). + /// + /// Returns the bindings for the sub-model. + /// + /// The function used to get and configure the window. + /// + /// + /// The message to be dispatched on external close attempts (the Close/X + /// button, Alt+F4, or System Menu -> Close). + /// + /// + /// Specifies whether the window will be shown modally (using + /// window.ShowDialog, blocking the rest of the app) or non-modally (using + /// window.Show). + /// + static member subModelWin + ( + getState: 'model -> WindowState<'subModel>, + toBindingModel: 'model * 'subModel -> 'bindingModel, + toMsg: 'bindingMsg -> 'msg, + bindings: unit -> Binding<'bindingModel, 'bindingMsg> list, + getWindow: unit -> #Window, + ?onCloseRequested: 'msg, + ?isModal: bool + ) : string -> Binding<'model, 'msg> = + Binding.subModelWin ( + getState, + toBindingModel, + toMsg, + bindings, + (fun _ _ -> getWindow ()), + ?onCloseRequested = onCloseRequested, + ?isModal = isModal + ) + + + /// + /// Like , but uses the WindowState wrapper + /// to show/hide/close a new window that will have the specified bindings as + /// its DataContext. + /// + /// You do not need to set the DataContext yourself (neither in code + /// nor XAML). + /// + /// The window can only be closed/hidden by changing the return value of + /// , and can not be directly closed by the + /// user. External close attempts (the Close/X button, Alt+F4, or System + /// Menu -> Close) will cause the message specified by + /// to be dispatched. You should supply + /// and react to this in a manner that + /// will not confuse a user trying to close the window (e.g. by closing it, + /// or displaying relevant feedback to the user.) + /// + /// Gets the window state and a sub-model. + /// + /// Converts the messages used in the bindings to parent model messages + /// (e.g. a parent message union case that wraps the child message type). + /// + /// Returns the bindings for the sub-model. + /// + /// The function used to get and configure the window. + /// + /// + /// The message to be dispatched on external close attempts (the Close/X + /// button, Alt+F4, or System Menu -> Close). + /// + /// + /// Specifies whether the window will be shown modally (using + /// window.ShowDialog, blocking the rest of the app) or non-modally (using + /// window.Show). + /// + static member subModelWin + ( + getState: 'model -> WindowState<'subModel>, + toMsg: 'subMsg -> 'msg, + bindings: unit -> Binding<'model * 'subModel, 'subMsg> list, + getWindow: 'model -> Dispatch<'msg> -> #Window, + ?onCloseRequested: 'msg, + ?isModal: bool + ) : string -> Binding<'model, 'msg> = + Binding.SubModelWin.create + (fun m -> getState m |> WindowState.map (fun sub -> (m, sub))) + (fun args -> DynamicViewModel<'model * 'subModel, 'subMsg>(args, bindings ())) + IViewModel.updateModel + (fun _ -> toMsg) + (fun m d -> upcast getWindow m d) + (defaultArg isModal false) + (fun _ -> defaultArg (onCloseRequested |> Option.map ValueSome) ValueNone) + + + /// + /// Like , but uses the WindowState wrapper + /// to show/hide/close a new window that will have the specified bindings as + /// its DataContext. + /// + /// You do not need to set the DataContext yourself (neither in code + /// nor XAML). + /// + /// The window can only be closed/hidden by changing the return value of + /// , and can not be directly closed by the + /// user. External close attempts (the Close/X button, Alt+F4, or System + /// Menu -> Close) will cause the message specified by + /// to be dispatched. You should supply + /// and react to this in a manner that + /// will not confuse a user trying to close the window (e.g. by closing it, + /// or displaying relevant feedback to the user.) + /// + /// Gets the window state and a sub-model. + /// + /// Converts the messages used in the bindings to parent model messages + /// (e.g. a parent message union case that wraps the child message type). + /// + /// Returns the bindings for the sub-model. + /// + /// The function used to get and configure the window. + /// + /// + /// The message to be dispatched on external close attempts (the Close/X + /// button, Alt+F4, or System Menu -> Close). + /// + /// + /// Specifies whether the window will be shown modally (using + /// window.ShowDialog, blocking the rest of the app) or non-modally (using + /// window.Show). + /// + static member subModelWin + ( + getState: 'model -> WindowState<'subModel>, + toMsg: 'subMsg -> 'msg, + bindings: unit -> Binding<'model * 'subModel, 'subMsg> list, + getWindow: unit -> #Window, + ?onCloseRequested: 'msg, + ?isModal: bool + ) : string -> Binding<'model, 'msg> = + Binding.subModelWin ( + getState, + toMsg, + bindings, + (fun _ _ -> getWindow ()), + ?onCloseRequested = onCloseRequested, + ?isModal = isModal + ) + + + /// + /// Like , but uses the WindowState wrapper + /// to show/hide/close a new window that will have the specified bindings as + /// its DataContext. + /// + /// You do not need to set the DataContext yourself (neither in code + /// nor XAML). + /// + /// The window can only be closed/hidden by changing the return value of + /// , and can not be directly closed by the + /// user. External close attempts (the Close/X button, Alt+F4, or System + /// Menu -> Close) will cause the message specified by + /// to be dispatched. You should supply + /// and react to this in a manner that + /// will not confuse a user trying to close the window (e.g. by closing it, + /// or displaying relevant feedback to the user.) + /// + /// Gets the window state and a sub-model. + /// Returns the bindings for the sub-model. + /// + /// The function used to get and configure the window. + /// + /// + /// The message to be dispatched on external close attempts (the Close/X + /// button, Alt+F4, or System Menu -> Close). + /// + /// + /// Specifies whether the window will be shown modally (using + /// window.ShowDialog, blocking the rest of the app) or non-modally (using + /// window.Show). + /// + static member subModelWin + ( + getState: 'model -> WindowState<'subModel>, + bindings: unit -> Binding<'model * 'subModel, 'msg> list, + getWindow: 'model -> Dispatch<'msg> -> #Window, + ?onCloseRequested: 'msg, + ?isModal: bool + ) : string -> Binding<'model, 'msg> = + Binding.SubModelWin.create + (fun m -> getState m |> WindowState.map (fun sub -> (m, sub))) + (fun args -> DynamicViewModel<'model * 'subModel, 'msg>(args, bindings ())) + IViewModel.updateModel + (fun _ -> id) + (fun m d -> upcast getWindow m d) + (defaultArg isModal false) + (fun _ -> defaultArg (onCloseRequested |> Option.map ValueSome) ValueNone) + + + /// + /// Like , but uses the WindowState wrapper + /// to show/hide/close a new window that will have the specified bindings as + /// its DataContext. + /// + /// You do not need to set the DataContext yourself (neither in code + /// nor XAML). + /// + /// The window can only be closed/hidden by changing the return value of + /// , and can not be directly closed by the + /// user. External close attempts (the Close/X button, Alt+F4, or System + /// Menu -> Close) will cause the message specified by + /// to be dispatched. You should supply + /// and react to this in a manner that + /// will not confuse a user trying to close the window (e.g. by closing it, + /// or displaying relevant feedback to the user.) + /// + /// Gets the window state and a sub-model. + /// Returns the bindings for the sub-model. + /// + /// The function used to get and configure the window. + /// + /// + /// The message to be dispatched on external close attempts (the Close/X + /// button, Alt+F4, or System Menu -> Close). + /// + /// + /// Specifies whether the window will be shown modally (using + /// window.ShowDialog, blocking the rest of the app) or non-modally (using + /// window.Show). + /// + static member subModelWin + ( + getState: 'model -> WindowState<'subModel>, + bindings: unit -> Binding<'model * 'subModel, 'msg> list, + getWindow: unit -> #Window, + ?onCloseRequested: 'msg, + ?isModal: bool + ) : string -> Binding<'model, 'msg> = + Binding.subModelWin ( + getState, + bindings, + (fun _ _ -> getWindow ()), + ?onCloseRequested = onCloseRequested, + ?isModal = isModal + ) + + static member subModelSeq // TODO: make into function + (getBindings: unit -> Binding<'model, 'msg> list) + : string -> Binding<'model seq, int * 'msg> = + Binding.SubModelSeqUnkeyed.create + (fun args -> DynamicViewModel<'model, 'msg>(args, getBindings ())) + IViewModel.updateModel + + static member subModelSeq // TODO: make into function + (getBindings: unit -> Binding<'model, 'msg> list, getId: 'model -> 'id) + : string -> Binding<'model seq, 'id * 'msg> = + Binding.SubModelSeqKeyed.create + (fun args -> DynamicViewModel<'model, 'msg>(args, getBindings ())) + IViewModel.updateModel + getId + (IViewModel.currentModel >> getId) + + + /// + /// Creates a binding to a sequence of sub-models, each uniquely identified + /// by the value returned by . The sub-models have + /// their own bindings and message type. You typically bind this to the + /// ItemsSource of an ItemsControl, ListView, + /// TreeView, etc. + /// + /// Gets the sub-models from the model. + /// + /// Converts the models to the model used by the bindings. + /// + /// Gets a unique identifier for a sub-model. + /// + /// Converts the sub-model ID and messages used in the bindings to parent + /// model messages (e.g. a parent message union case that wraps the + /// sub-model ID and message type). + /// + /// Returns the bindings for the sub-model. + static member subModelSeq + ( + getSubModels: 'model -> #seq<'subModel>, + toBindingModel: 'model * 'subModel -> 'bindingModel, + getId: 'bindingModel -> 'id, + toMsg: 'id * 'bindingMsg -> 'msg, + bindings: unit -> Binding<'bindingModel, 'bindingMsg> list + ) : string -> Binding<'model, 'msg> = + Binding.SubModelSeqKeyed.create + (fun args -> DynamicViewModel<'bindingModel, 'bindingMsg>(args, bindings ())) + IViewModel.updateModel + getId + (IViewModel.currentModel >> getId) + >> Binding.mapModel (fun m -> getSubModels m |> Seq.map (fun sub -> toBindingModel (m, sub))) + >> Binding.mapMsg toMsg + + + /// + /// Creates a binding to a sequence of sub-models, each uniquely identified + /// by the value returned by . The sub-models have + /// their own bindings and message type. You typically bind this to the + /// ItemsSource of an ItemsControl, ListView, + /// TreeView, etc. + /// + /// Gets the sub-models from the model. + /// Gets a unique identifier for a sub-model. + /// + /// Converts the sub-model ID and messages used in the bindings to parent + /// model messages (e.g. a parent message union case that wraps the + /// sub-model ID and message type). + /// + /// Returns the bindings for the sub-model. + static member subModelSeq + ( + getSubModels: 'model -> #seq<'subModel>, + getId: 'subModel -> 'id, + toMsg: 'id * 'subMsg -> 'msg, + bindings: unit -> Binding<'model * 'subModel, 'subMsg> list + ) : string -> Binding<'model, 'msg> = + Binding.SubModelSeqKeyed.create + (fun args -> DynamicViewModel<'model * 'subModel, 'subMsg>(args, bindings ())) + IViewModel.updateModel + (snd >> getId) + (IViewModel.currentModel >> snd >> getId) + >> Binding.mapModel (fun m -> getSubModels m |> Seq.map (fun sub -> (m, sub))) + >> Binding.mapMsg toMsg + + + /// + /// Creates a binding to a sequence of sub-models, each uniquely identified + /// by the value returned by . The sub-models have + /// their own bindings. You typically bind this to the ItemsSource of + /// an + /// ItemsControl, ListView, TreeView, etc. + /// + /// Gets the sub-models from the model. + /// Gets a unique identifier for a sub-model. + /// Returns the bindings for the sub-model. + static member subModelSeq + ( + getSubModels: 'model -> #seq<'subModel>, + getId: 'subModel -> 'id, + bindings: unit -> Binding<'model * 'subModel, 'msg> list + ) : string -> Binding<'model, 'msg> = + Binding.SubModelSeqKeyed.create + (fun args -> DynamicViewModel<'model * 'subModel, 'msg>(args, bindings ())) + IViewModel.updateModel + (snd >> getId) + (IViewModel.currentModel >> snd >> getId) + >> Binding.mapModel (fun m -> getSubModels m |> Seq.map (fun sub -> (m, sub))) + >> Binding.mapMsg snd + + + /// + /// Creates a two-way binding to a SelectedItem-like property where + /// the + /// ItemsSource-like property is a + /// binding. Automatically converts the dynamically created Elmish.WPF view + /// models to/from their corresponding IDs, so the Elmish user code only has + /// to work with the IDs. + /// + /// Only use this if you are unable to use some kind of SelectedValue + /// or + /// SelectedIndex property with a normal + /// binding. This binding is less type-safe. It will throw when initializing + /// the bindings if + /// does not correspond to a binding, and it will + /// throw at runtime if the inferred 'id type does not match the + /// actual ID type used in that binding. + /// + /// + /// The name of the binding used as the items + /// source. + /// + /// Gets the selected sub-model/sub-binding ID from the + /// model. + /// + /// Returns the message to dispatch on selections/de-selections. + /// + static member subModelSelectedItem + (subModelSeqBindingName: string, get: 'model -> 'id voption, set: 'id voption -> 'model -> 'msg) + : string -> Binding<'model, 'msg> = + Binding.SubModelSelectedItem.vopt subModelSeqBindingName + >> Binding.addLazy (=) + >> Binding.mapModel get + >> Binding.mapMsgWithModel set + >> Binding.addCaching + + /// + /// Creates a two-way binding to a SelectedItem-like property where + /// the + /// ItemsSource-like property is a + /// binding. Automatically converts the dynamically created Elmish.WPF view + /// models to/from their corresponding IDs, so the Elmish user code only has + /// to work with the IDs. + /// + /// Only use this if you are unable to use some kind of SelectedValue + /// or + /// SelectedIndex property with a normal + /// binding. This binding is less type-safe. It will throw when initializing + /// the bindings if + /// does not correspond to a binding, and it will + /// throw at runtime if the inferred 'id type does not match the + /// actual ID type used in that binding. + /// + /// + /// The name of the binding used as the items + /// source. + /// + /// Gets the selected sub-model/sub-binding ID from the + /// model. + /// + /// Returns the message to dispatch on selections/de-selections. + /// + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member subModelSelectedItem + ( + subModelSeqBindingName: string, + get: 'model -> 'id voption, + set: 'id voption -> 'model -> 'msg, + wrapDispatch: Dispatch<'msg> -> Dispatch<'msg> + ) : string -> Binding<'model, 'msg> = + Binding.subModelSelectedItem (subModelSeqBindingName, get, set) + >> Binding.alterMsgStream wrapDispatch + + + /// + /// Creates a two-way binding to a SelectedItem-like property where + /// the + /// ItemsSource-like property is a + /// binding. Automatically converts the dynamically created Elmish.WPF view + /// models to/from their corresponding IDs, so the Elmish user code only has + /// to work with the IDs. + /// + /// Only use this if you are unable to use some kind of SelectedValue + /// or + /// SelectedIndex property with a normal + /// binding. This binding is less type-safe. It will throw when initializing + /// the bindings if + /// does not correspond to a binding, and it will + /// throw at runtime if the inferred 'id type does not match the + /// actual ID type used in that binding. + /// + /// + /// The name of the binding used as the items + /// source. + /// + /// Gets the selected sub-model/sub-binding ID from the + /// model. + /// + /// Returns the message to dispatch on selections/de-selections. + /// + static member subModelSelectedItem + (subModelSeqBindingName: string, get: 'model -> 'id option, set: 'id option -> 'model -> 'msg) + : string -> Binding<'model, 'msg> = + Binding.SubModelSelectedItem.opt subModelSeqBindingName + >> Binding.addLazy (=) + >> Binding.mapModel get + >> Binding.mapMsgWithModel set + >> Binding.addCaching + + /// + /// Creates a two-way binding to a SelectedItem-like property where + /// the + /// ItemsSource-like property is a + /// binding. Automatically converts the dynamically created Elmish.WPF view + /// models to/from their corresponding IDs, so the Elmish user code only has + /// to work with the IDs. + /// + /// Only use this if you are unable to use some kind of SelectedValue + /// or + /// SelectedIndex property with a normal + /// binding. This binding is less type-safe. It will throw when initializing + /// the bindings if + /// does not correspond to a binding, and it will + /// throw at runtime if the inferred 'id type does not match the + /// actual ID type used in that binding. + /// + /// + /// The name of the binding used as the items + /// source. + /// + /// Gets the selected sub-model/sub-binding ID from the + /// model. + /// + /// Returns the message to dispatch on selections/de-selections. + /// + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member subModelSelectedItem + ( + subModelSeqBindingName: string, + get: 'model -> 'id option, + set: 'id option -> 'model -> 'msg, + wrapDispatch: Dispatch<'msg> -> Dispatch<'msg> + ) : string -> Binding<'model, 'msg> = + Binding.subModelSelectedItem (subModelSeqBindingName, get, set) + >> Binding.alterMsgStream wrapDispatch + + + +// Some members are implemented as extensions to help overload resolution +[] +module Extensions = + + type Binding with + + /// Creates a one-way-to-source binding. + /// Returns the message to dispatch. + static member oneWayToSource(set: 'a -> 'msg) : string -> Binding<'model, 'msg> = + Binding.OneWayToSource.id<'model, 'a> >> Binding.mapMsg set + + /// + /// Creates a one-way-to-source binding to an optional value. The binding + /// automatically converts between a missing value in the model and + /// a null value in the view. + /// + /// Returns the message to dispatch. + static member oneWayToSourceOpt(set: 'a option -> 'msg) : string -> Binding<'model, 'msg> = + Binding.OneWayToSource.opt >> Binding.mapMsg set + + /// + /// Creates a one-way-to-source binding to an optional value. The binding + /// automatically converts between a missing value in the model and + /// a null value in the view. + /// + /// Returns the message to dispatch. + static member oneWayToSourceOpt(set: 'a voption -> 'msg) : string -> Binding<'model, 'msg> = + Binding.OneWayToSource.vopt >> Binding.mapMsg set + + + /// Creates a two-way binding. + /// Gets the value from the model. + /// Returns the message to dispatch. + static member twoWay(get: 'model -> 'a, set: 'a -> 'msg) : string -> Binding<'model, 'msg> = + Binding.TwoWay.id<'a> + >> Binding.addLazy (=) + >> Binding.mapModel get + >> Binding.mapMsg set + + /// Creates a two-way binding. + /// Gets the value from the model. + /// Returns the message to dispatch. + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member twoWay + (get: 'model -> 'a, set: 'a -> 'msg, wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) + : string -> Binding<'model, 'msg> = + Binding.twoWay (get, set) >> Binding.alterMsgStream wrapDispatch + + + /// + /// Creates a two-way binding to an optional value. The binding + /// automatically converts between the optional source value and an + /// unwrapped (possibly null) value on the view side. + /// + /// Gets the value from the model. + /// Returns the message to dispatch. + static member twoWayOpt(get: 'model -> 'a option, set: 'a option -> 'msg) : string -> Binding<'model, 'msg> = + Binding.TwoWay.opt<'a> + >> Binding.addLazy (=) + >> Binding.mapModel get + >> Binding.mapMsg set + + /// + /// Creates a two-way binding to an optional value. The binding + /// automatically converts between the optional source value and an + /// unwrapped (possibly null) value on the view side. + /// + /// Gets the value from the model. + /// Returns the message to dispatch. + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member twoWayOpt + (get: 'model -> 'a option, set: 'a option -> 'msg, wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) + : string -> Binding<'model, 'msg> = + Binding.twoWayOpt (get, set) >> Binding.alterMsgStream wrapDispatch + + + /// + /// Creates a two-way binding to an optional value. The binding + /// automatically converts between the optional source value and an + /// unwrapped (possibly null) value on the view side. + /// + /// Gets the value from the model. + /// Returns the message to dispatch. + static member twoWayOpt(get: 'model -> 'a voption, set: 'a voption -> 'msg) : string -> Binding<'model, 'msg> = + Binding.TwoWay.vopt<'a> + >> Binding.addLazy (=) + >> Binding.mapModel get + >> Binding.mapMsg set + + /// + /// Creates a two-way binding to an optional value. The binding + /// automatically converts between the optional source value and an + /// unwrapped (possibly null) value on the view side. + /// + /// Gets the value from the model. + /// Returns the message to dispatch. + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member twoWayOpt + (get: 'model -> 'a voption, set: 'a voption -> 'msg, wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) + : string -> Binding<'model, 'msg> = + Binding.twoWayOpt (get, set) >> Binding.alterMsgStream wrapDispatch + + + /// + /// Creates a two-way binding with validation using + /// INotifyDataErrorInfo. + /// + /// Gets the value from the model. + /// Returns the message to dispatch. + /// + /// Returns the validation messages from the updated model. + /// + static member twoWayValidate + (get: 'model -> 'a, set: 'a -> 'msg, validate: 'model -> string list) + : string -> Binding<'model, 'msg> = + Binding.TwoWay.id<'a> + >> Binding.addLazy (=) + >> Binding.mapModel get + >> Binding.mapMsg set + >> Binding.addValidation validate + + /// + /// Creates a two-way binding with validation using + /// INotifyDataErrorInfo. + /// + /// Gets the value from the model. + /// Returns the message to dispatch. + /// + /// Returns the validation messages from the updated model. + /// + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member twoWayValidate + ( + get: 'model -> 'a, + set: 'a -> 'msg, + validate: 'model -> string list, + wrapDispatch: Dispatch<'msg> -> Dispatch<'msg> + ) : string -> Binding<'model, 'msg> = + Binding.twoWayValidate (get, set, validate) + >> Binding.alterMsgStream wrapDispatch + + + /// + /// Creates a two-way binding with validation using + /// INotifyDataErrorInfo. + /// + /// Gets the value from the model. + /// Returns the message to dispatch. + /// + /// Returns the validation message from the updated model. + /// + static member twoWayValidate + (get: 'model -> 'a, set: 'a -> 'msg, validate: 'model -> string voption) + : string -> Binding<'model, 'msg> = + Binding.TwoWay.id<'a> + >> Binding.addLazy (=) + >> Binding.mapModel get + >> Binding.mapMsg set + >> Binding.addValidation (validate >> ValueOption.toList) + + /// + /// Creates a two-way binding with validation using + /// INotifyDataErrorInfo. + /// + /// Gets the value from the model. + /// Returns the message to dispatch. + /// + /// Returns the validation message from the updated model. + /// + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member twoWayValidate + ( + get: 'model -> 'a, + set: 'a -> 'msg, + validate: 'model -> string voption, + wrapDispatch: Dispatch<'msg> -> Dispatch<'msg> + ) : string -> Binding<'model, 'msg> = + Binding.twoWayValidate (get, set, validate) + >> Binding.alterMsgStream wrapDispatch + + + /// + /// Creates a two-way binding with validation using + /// INotifyDataErrorInfo. + /// + /// Gets the value from the model. + /// Returns the message to dispatch. + /// + /// Returns the validation message from the updated model. + /// + static member twoWayValidate + (get: 'model -> 'a, set: 'a -> 'msg, validate: 'model -> string option) + : string -> Binding<'model, 'msg> = + Binding.TwoWay.id<'a> + >> Binding.addLazy (=) + >> Binding.mapModel get + >> Binding.mapMsg set + >> Binding.addValidation (validate >> Option.toList) + + /// + /// Creates a two-way binding with validation using + /// INotifyDataErrorInfo. + /// + /// Gets the value from the model. + /// Returns the message to dispatch. + /// + /// Returns the validation message from the updated model. + /// + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member twoWayValidate + ( + get: 'model -> 'a, + set: 'a -> 'msg, + validate: 'model -> string option, + wrapDispatch: Dispatch<'msg> -> Dispatch<'msg> + ) : string -> Binding<'model, 'msg> = + Binding.twoWayValidate (get, set, validate) + >> Binding.alterMsgStream wrapDispatch + + + /// + /// Creates a two-way binding with validation using + /// INotifyDataErrorInfo. + /// + /// Gets the value from the model. + /// Returns the message to dispatch. + /// + /// Returns the validation message from the updated model. + /// + static member twoWayValidate + (get: 'model -> 'a, set: 'a -> 'msg, validate: 'model -> Result<'ignored, string>) + : string -> Binding<'model, 'msg> = + Binding.TwoWay.id<'a> + >> Binding.addLazy (=) + >> Binding.mapModel get + >> Binding.mapMsg set + >> Binding.addValidation (validate >> ValueOption.ofError >> ValueOption.toList) + + /// + /// Creates a two-way binding with validation using + /// INotifyDataErrorInfo. + /// + /// Gets the value from the model. + /// Returns the message to dispatch. + /// + /// Returns the validation message from the updated model. + /// + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member twoWayValidate + ( + get: 'model -> 'a, + set: 'a -> 'msg, + validate: 'model -> Result<'ignored, string>, + wrapDispatch: Dispatch<'msg> -> Dispatch<'msg> + ) : string -> Binding<'model, 'msg> = + Binding.twoWayValidate (get, set, validate) + >> Binding.alterMsgStream wrapDispatch + + + /// + /// Creates a two-way binding to an optional value with validation using + /// INotifyDataErrorInfo. The binding automatically converts + /// between the optional source value and an unwrapped (possibly + /// null) value on the view side. + /// + /// Gets the value from the model. + /// Returns the message to dispatch. + /// + /// Returns the validation messages from the updated model. + /// + static member twoWayOptValidate + (get: 'model -> 'a voption, set: 'a voption -> 'msg, validate: 'model -> string list) + : string -> Binding<'model, 'msg> = + Binding.TwoWay.vopt<'a> + >> Binding.addLazy (=) + >> Binding.mapModel get + >> Binding.mapMsg set + >> Binding.addValidation validate + + /// + /// Creates a two-way binding to an optional value with validation using + /// INotifyDataErrorInfo. The binding automatically converts + /// between the optional source value and an unwrapped (possibly + /// null) value on the view side. + /// + /// Gets the value from the model. + /// Returns the message to dispatch. + /// + /// Returns the validation messages from the updated model. + /// + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member twoWayOptValidate + ( + get: 'model -> 'a voption, + set: 'a voption -> 'msg, + validate: 'model -> string list, + wrapDispatch: Dispatch<'msg> -> Dispatch<'msg> + ) : string -> Binding<'model, 'msg> = + Binding.twoWayOptValidate (get, set, validate) + >> Binding.alterMsgStream wrapDispatch + + + /// + /// Creates a two-way binding to an optional value with validation using + /// INotifyDataErrorInfo. The binding automatically converts + /// between the optional source value and an unwrapped (possibly + /// null) value on the view side. + /// + /// Gets the value from the model. + /// Returns the message to dispatch. + /// + /// Returns the validation message from the updated model. + /// + static member twoWayOptValidate + (get: 'model -> 'a voption, set: 'a voption -> 'msg, validate: 'model -> string voption) + : string -> Binding<'model, 'msg> = + Binding.TwoWay.vopt<'a> + >> Binding.addLazy (=) + >> Binding.mapModel get + >> Binding.mapMsg set + >> Binding.addValidation (validate >> ValueOption.toList) + + /// + /// Creates a two-way binding to an optional value with validation using + /// INotifyDataErrorInfo. The binding automatically converts + /// between the optional source value and an unwrapped (possibly + /// null) value on the view side. + /// + /// Gets the value from the model. + /// Returns the message to dispatch. + /// + /// Returns the validation message from the updated model. + /// + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member twoWayOptValidate + ( + get: 'model -> 'a voption, + set: 'a voption -> 'msg, + validate: 'model -> string voption, + wrapDispatch: Dispatch<'msg> -> Dispatch<'msg> + ) : string -> Binding<'model, 'msg> = + Binding.twoWayOptValidate (get, set, validate) + >> Binding.alterMsgStream wrapDispatch + + + /// + /// Creates a two-way binding to an optional value with validation using + /// INotifyDataErrorInfo. The binding automatically converts + /// between the optional source value and an unwrapped (possibly + /// null) value on the view side. + /// + /// Gets the value from the model. + /// Returns the message to dispatch. + /// + /// Returns the validation message from the updated model. + /// + static member twoWayOptValidate + (get: 'model -> 'a voption, set: 'a voption -> 'msg, validate: 'model -> string option) + : string -> Binding<'model, 'msg> = + Binding.TwoWay.vopt<'a> + >> Binding.addLazy (=) + >> Binding.mapModel get + >> Binding.mapMsg set + >> Binding.addValidation (validate >> Option.toList) + + /// + /// Creates a two-way binding to an optional value with validation using + /// INotifyDataErrorInfo. The binding automatically converts + /// between the optional source value and an unwrapped (possibly + /// null) value on the view side. + /// + /// Gets the value from the model. + /// Returns the message to dispatch. + /// + /// Returns the validation message from the updated model. + /// + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member twoWayOptValidate + ( + get: 'model -> 'a voption, + set: 'a voption -> 'msg, + validate: 'model -> string option, + wrapDispatch: Dispatch<'msg> -> Dispatch<'msg> + ) : string -> Binding<'model, 'msg> = + Binding.twoWayOptValidate (get, set, validate) + >> Binding.alterMsgStream wrapDispatch + + + /// + /// Creates a two-way binding to an optional value with validation using + /// INotifyDataErrorInfo. The binding automatically converts + /// between the optional source value and an unwrapped (possibly + /// null) value on the view side. + /// + /// Gets the value from the model. + /// Returns the message to dispatch. + /// + /// Returns the validation message from the updated model. + /// + static member twoWayOptValidate + (get: 'model -> 'a voption, set: 'a voption -> 'msg, validate: 'model -> Result<'ignored, string>) + : string -> Binding<'model, 'msg> = + Binding.TwoWay.vopt<'a> + >> Binding.addLazy (=) + >> Binding.mapModel get + >> Binding.mapMsg set + >> Binding.addValidation (validate >> ValueOption.ofError >> ValueOption.toList) + + /// + /// Creates a two-way binding to an optional value with validation using + /// INotifyDataErrorInfo. The binding automatically converts + /// between the optional source value and an unwrapped (possibly + /// null) value on the view side. + /// + /// Gets the value from the model. + /// Returns the message to dispatch. + /// + /// Returns the validation message from the updated model. + /// + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member twoWayOptValidate + ( + get: 'model -> 'a voption, + set: 'a voption -> 'msg, + validate: 'model -> Result<'ignored, string>, + wrapDispatch: Dispatch<'msg> -> Dispatch<'msg> + ) : string -> Binding<'model, 'msg> = + Binding.twoWayOptValidate (get, set, validate) + >> Binding.alterMsgStream wrapDispatch + + + /// + /// Creates a two-way binding to an optional value with validation using + /// INotifyDataErrorInfo. The binding automatically converts + /// between the optional source value and an unwrapped (possibly + /// null) value on the view side. + /// + /// Gets the value from the model. + /// Returns the message to dispatch. + /// + /// Returns the validation messages from the updated model. + /// + static member twoWayOptValidate + (get: 'model -> 'a option, set: 'a option -> 'msg, validate: 'model -> string list) + : string -> Binding<'model, 'msg> = + Binding.TwoWay.opt<'a> + >> Binding.addLazy (=) + >> Binding.mapModel get + >> Binding.mapMsg set + >> Binding.addValidation validate + + /// + /// Creates a two-way binding to an optional value with validation using + /// INotifyDataErrorInfo. The binding automatically converts + /// between the optional source value and an unwrapped (possibly + /// null) value on the view side. + /// + /// Gets the value from the model. + /// Returns the message to dispatch. + /// + /// Returns the validation messages from the updated model. + /// + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member twoWayOptValidate + ( + get: 'model -> 'a option, + set: 'a option -> 'msg, + validate: 'model -> string list, + wrapDispatch: Dispatch<'msg> -> Dispatch<'msg> + ) : string -> Binding<'model, 'msg> = + Binding.twoWayOptValidate (get, set, validate) + >> Binding.alterMsgStream wrapDispatch + + + /// + /// Creates a two-way binding to an optional value with validation using + /// INotifyDataErrorInfo. The binding automatically converts + /// between the optional source value and an unwrapped (possibly + /// null) value on the view side. + /// + /// Gets the value from the model. + /// Returns the message to dispatch. + /// + /// Returns the validation message from the updated model. + /// + static member twoWayOptValidate + (get: 'model -> 'a option, set: 'a option -> 'msg, validate: 'model -> string voption) + : string -> Binding<'model, 'msg> = + Binding.TwoWay.opt<'a> + >> Binding.addLazy (=) + >> Binding.mapModel get + >> Binding.mapMsg set + >> Binding.addValidation (validate >> ValueOption.toList) + + /// + /// Creates a two-way binding to an optional value with validation using + /// INotifyDataErrorInfo. The binding automatically converts + /// between the optional source value and an unwrapped (possibly + /// null) value on the view side. + /// + /// Gets the value from the model. + /// Returns the message to dispatch. + /// + /// Returns the validation message from the updated model. + /// + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member twoWayOptValidate + ( + get: 'model -> 'a option, + set: 'a option -> 'msg, + validate: 'model -> string voption, + wrapDispatch: Dispatch<'msg> -> Dispatch<'msg> + ) : string -> Binding<'model, 'msg> = + Binding.twoWayOptValidate (get, set, validate) + >> Binding.alterMsgStream wrapDispatch + + + /// + /// Creates a two-way binding to an optional value with validation using + /// INotifyDataErrorInfo. The binding automatically converts + /// between the optional source value and an unwrapped (possibly + /// null) value on the view side. + /// + /// Gets the value from the model. + /// Returns the message to dispatch. + /// + /// Returns the validation message from the updated model. + /// + static member twoWayOptValidate + (get: 'model -> 'a option, set: 'a option -> 'msg, validate: 'model -> string option) + : string -> Binding<'model, 'msg> = + Binding.TwoWay.opt<'a> + >> Binding.addLazy (=) + >> Binding.mapModel get + >> Binding.mapMsg set + >> Binding.addValidation (validate >> Option.toList) + + /// + /// Creates a two-way binding to an optional value with validation using + /// INotifyDataErrorInfo. The binding automatically converts + /// between the optional source value and an unwrapped (possibly + /// null) value on the view side. + /// + /// Gets the value from the model. + /// Returns the message to dispatch. + /// + /// Returns the validation message from the updated model. + /// + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member twoWayOptValidate + ( + get: 'model -> 'a option, + set: 'a option -> 'msg, + validate: 'model -> string option, + wrapDispatch: Dispatch<'msg> -> Dispatch<'msg> + ) : string -> Binding<'model, 'msg> = + Binding.twoWayOptValidate (get, set, validate) + >> Binding.alterMsgStream wrapDispatch + + + /// + /// Creates a two-way binding to an optional value with validation using + /// INotifyDataErrorInfo. The binding automatically converts + /// between the optional source value and an unwrapped (possibly + /// null) value on the view side. + /// + /// Gets the value from the model. + /// Returns the message to dispatch. + /// + /// Returns the validation message from the updated model. + /// + static member twoWayOptValidate + (get: 'model -> 'a option, set: 'a option -> 'msg, validate: 'model -> Result<'ignored, string>) + : string -> Binding<'model, 'msg> = + Binding.TwoWay.opt<'a> + >> Binding.addLazy (=) + >> Binding.mapModel get + >> Binding.mapMsg set + >> Binding.addValidation (validate >> ValueOption.ofError >> ValueOption.toList) + + /// + /// Creates a two-way binding to an optional value with validation using + /// INotifyDataErrorInfo. The binding automatically converts + /// between the optional source value and an unwrapped (possibly + /// null) value on the view side. + /// + /// Gets the value from the model. + /// Returns the message to dispatch. + /// + /// Returns the validation message from the updated model. + /// + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member twoWayOptValidate + ( + get: 'model -> 'a option, + set: 'a option -> 'msg, + validate: 'model -> Result<'ignored, string>, + wrapDispatch: Dispatch<'msg> -> Dispatch<'msg> + ) : string -> Binding<'model, 'msg> = + Binding.twoWayOptValidate (get, set, validate) + >> Binding.alterMsgStream wrapDispatch + + + /// + /// Creates a Command binding that dispatches the specified message + /// and can always execute. + /// + /// Returns the message to dispatch. + static member cmd(exec: 'msg) : string -> Binding<'model, 'msg> = + Binding.Cmd.create (fun _ -> exec |> ValueSome) (fun _ -> true) + + /// + /// Creates a Command binding that dispatches the specified message + /// and can always execute. + /// + /// Returns the message to dispatch. + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member cmd + (exec: 'msg, wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) + : string -> Binding<'model, 'msg> = + Binding.cmd exec >> Binding.alterMsgStream wrapDispatch + + + /// + /// Creates a Command binding that dispatches the specified message + /// and can execute if returns true. + /// + /// Returns the message to dispatch. + /// Indicates whether the command can execute. + static member cmdIf(exec: 'msg, canExec: 'model -> bool) : string -> Binding<'model, 'msg> = + Binding.Cmd.create (fun _ -> exec |> ValueSome) canExec + + /// + /// Creates a Command binding that dispatches the specified message + /// and can execute if returns true. + /// + /// Returns the message to dispatch. + /// Indicates whether the command can execute. + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member cmdIf + (exec: 'msg, canExec: 'model -> bool, wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) + : string -> Binding<'model, 'msg> = + Binding.cmdIf (exec, canExec) >> Binding.alterMsgStream wrapDispatch + + + /// + /// Creates a Command binding that depends on the + /// CommandParameter + /// and can always execute. + /// + /// Returns the message to dispatch. + static member cmdParam(exec: obj -> 'msg) : string -> Binding<'model, 'msg> = + Binding.Cmd.createWithParam (fun p _ -> exec p |> ValueSome) (fun _ _ -> true) false + + /// + /// Creates a Command binding that depends on the + /// CommandParameter + /// and can always execute. + /// + /// Returns the message to dispatch. + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member cmdParam + (exec: obj -> 'msg, wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) + : string -> Binding<'model, 'msg> = + Binding.cmdParam exec >> Binding.alterMsgStream wrapDispatch + + + /// + /// Creates a conditional Command binding that depends on the + /// CommandParameter + /// and can execute if returns ValueSome. + /// + /// Returns the message to dispatch. + /// + /// If true, CanExecuteChanged will trigger every time WPF's + /// CommandManager + /// detects UI changes that could potentially influence the command's + /// ability to execute. This will likely lead to many more triggers than + /// necessary, but is needed if you have bound the CommandParameter + /// to another UI property. + /// + static member cmdParamIf(exec: obj -> 'msg voption, ?uiBoundCmdParam: bool) : string -> Binding<'model, 'msg> = + Binding.Cmd.createWithParam + (fun p _ -> exec p) + (fun p _ -> exec p |> ValueOption.isSome) + (defaultArg uiBoundCmdParam false) + + /// + /// Creates a conditional Command binding that depends on the + /// CommandParameter + /// and can execute if returns ValueSome. + /// + /// Returns the message to dispatch. + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member cmdParamIf + (exec: obj -> 'msg voption, wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) + : string -> Binding<'model, 'msg> = + Binding.cmdParamIf exec >> Binding.alterMsgStream wrapDispatch + + /// + /// Creates a conditional Command binding that depends on the + /// CommandParameter + /// and can execute if returns ValueSome. + /// + /// Returns the message to dispatch. + /// + /// If true, CanExecuteChanged will trigger every time WPF's + /// CommandManager + /// detects UI changes that could potentially influence the command's + /// ability to execute. This will likely lead to many more triggers than + /// necessary, but is needed if you have bound the CommandParameter + /// to another UI property. + /// + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member cmdParamIf + (exec: obj -> 'msg voption, uiBoundCmdParam: bool, wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) + : string -> Binding<'model, 'msg> = + Binding.cmdParamIf (exec, uiBoundCmdParam) + >> Binding.alterMsgStream wrapDispatch + + + /// + /// Creates a conditional Command binding that depends on the + /// CommandParameter + /// and can execute if returns Some. + /// + /// Returns the message to dispatch. + /// + /// If true, CanExecuteChanged will trigger every time WPF's + /// CommandManager + /// detects UI changes that could potentially influence the command's + /// ability to execute. This will likely lead to many more triggers than + /// necessary, but is needed if you have bound the CommandParameter + /// to another UI property. + /// + static member cmdParamIf(exec: obj -> 'msg option, ?uiBoundCmdParam: bool) : string -> Binding<'model, 'msg> = + Binding.Cmd.createWithParam + (fun p _ -> exec p |> ValueOption.ofOption) + (fun p _ -> exec p |> Option.isSome) + (defaultArg uiBoundCmdParam false) + + /// + /// Creates a conditional Command binding that depends on the + /// CommandParameter + /// and can execute if returns Some. + /// + /// Returns the message to dispatch. + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member cmdParamIf + (exec: obj -> 'msg option, wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) + : string -> Binding<'model, 'msg> = + Binding.cmdParamIf exec >> Binding.alterMsgStream wrapDispatch + + /// + /// Creates a conditional Command binding that depends on the + /// CommandParameter + /// and can execute if returns Some. + /// + /// Returns the message to dispatch. + /// + /// If true, CanExecuteChanged will trigger every time WPF's + /// CommandManager + /// detects UI changes that could potentially influence the command's + /// ability to execute. This will likely lead to many more triggers than + /// necessary, but is needed if you have bound the CommandParameter + /// to another UI property. + /// + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member cmdParamIf + (exec: obj -> 'msg option, uiBoundCmdParam: bool, wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) + : string -> Binding<'model, 'msg> = + Binding.cmdParamIf (exec, uiBoundCmdParam) + >> Binding.alterMsgStream wrapDispatch + + + /// + /// Creates a conditional Command binding that depends on the + /// CommandParameter + /// and can execute if returns Ok. + /// + /// This overload allows more easily re-using the same validation + /// functions for inputs and commands. + /// + /// Returns the message to dispatch. + /// + /// If true, CanExecuteChanged will trigger every time WPF's + /// CommandManager + /// detects UI changes that could potentially influence the command's + /// ability to execute. This will likely lead to many more triggers than + /// necessary, but is needed if you have bound the CommandParameter + /// to another UI property. + /// + static member cmdParamIf + (exec: obj -> Result<'msg, 'ignored>, ?uiBoundCmdParam: bool) + : string -> Binding<'model, 'msg> = + Binding.Cmd.createWithParam + (fun p _ -> exec p |> ValueOption.ofOk) + (fun p _ -> exec p |> Result.isOk) + (defaultArg uiBoundCmdParam false) + + /// + /// Creates a conditional Command binding that depends on the + /// CommandParameter + /// and can execute if returns Ok. + /// + /// This overload allows more easily re-using the same validation + /// functions for inputs and commands. + /// + /// Returns the message to dispatch. + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member cmdParamIf + (exec: obj -> Result<'msg, 'ignored>, wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) + : string -> Binding<'model, 'msg> = + Binding.cmdParamIf exec >> Binding.alterMsgStream wrapDispatch + + /// + /// Creates a conditional Command binding that depends on the + /// CommandParameter + /// and can execute if returns Ok. + /// + /// This overload allows more easily re-using the same validation + /// functions for inputs and commands. + /// + /// Returns the message to dispatch. + /// + /// If true, CanExecuteChanged will trigger every time WPF's + /// CommandManager + /// detects UI changes that could potentially influence the command's + /// ability to execute. This will likely lead to many more triggers than + /// necessary, but is needed if you have bound the CommandParameter + /// to another UI property. + /// + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member cmdParamIf + (exec: obj -> Result<'msg, 'ignored>, uiBoundCmdParam: bool, wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) + : string -> Binding<'model, 'msg> = + Binding.cmdParamIf (exec, uiBoundCmdParam) + >> Binding.alterMsgStream wrapDispatch + + + /// + /// Creates a Command binding that depends on the + /// CommandParameter + /// and can execute if returns true. + /// + /// Returns the message to dispatch. + /// Indicates whether the command can execute. + /// + /// If true, CanExecuteChanged will trigger every time WPF's + /// CommandManager + /// detects UI changes that could potentially influence the command's + /// ability to execute. This will likely lead to many more triggers than + /// necessary, but is needed if you have bound the CommandParameter + /// to another UI property. + /// + static member cmdParamIf + (exec: obj -> 'msg, canExec: obj -> bool, ?uiBoundCmdParam: bool) + : string -> Binding<'model, 'msg> = + Binding.Cmd.createWithParam + (fun p _ -> exec p |> ValueSome) + (fun p _ -> canExec p) + (defaultArg uiBoundCmdParam false) + + /// + /// Creates a Command binding that depends on the + /// CommandParameter + /// and can execute if returns true. + /// + /// Returns the message to dispatch. + /// Indicates whether the command can execute. + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member cmdParamIf + (exec: obj -> 'msg, canExec: obj -> bool, wrapDispatch: Dispatch<'msg> -> Dispatch<'msg>) + : string -> Binding<'model, 'msg> = + Binding.cmdParamIf (exec, canExec) >> Binding.alterMsgStream wrapDispatch + + /// + /// Creates a Command binding that depends on the + /// CommandParameter + /// and can execute if returns true. + /// + /// Returns the message to dispatch. + /// Indicates whether the command can execute. + /// + /// If true, CanExecuteChanged will trigger every time WPF's + /// CommandManager + /// detects UI changes that could potentially influence the command's + /// ability to execute. This will likely lead to many more triggers than + /// necessary, but is needed if you have bound the CommandParameter + /// to another UI property. + /// + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member cmdParamIf + ( + exec: obj -> 'msg, + canExec: obj -> bool, + uiBoundCmdParam: bool, + wrapDispatch: Dispatch<'msg> -> Dispatch<'msg> + ) : string -> Binding<'model, 'msg> = + Binding.cmdParamIf (exec, canExec, uiBoundCmdParam) + >> Binding.alterMsgStream wrapDispatch + + + /// + /// Creates a two-way binding to a SelectedItem-like property where + /// the + /// ItemsSource-like property is a + /// binding. Automatically converts the dynamically created Elmish.WPF + /// view models to/from their corresponding IDs, so the Elmish user code + /// only has to work with the IDs. + /// + /// Only use this if you are unable to use some kind of + /// SelectedValue or + /// SelectedIndex property with a normal + /// binding. This binding is less type-safe. It will throw when + /// initializing the bindings if + /// does not correspond to a binding, and it + /// will throw at runtime if the inferred 'id type does not + /// match the actual ID type used in that binding. + /// + /// + /// The name of the binding used as the items + /// source. + /// + /// Gets the selected sub-model/sub-binding ID from the + /// model. + /// + /// Returns the message to dispatch on selections/de-selections. + /// + static member subModelSelectedItem + (subModelSeqBindingName: string, get: 'model -> 'id voption, set: 'id voption -> 'msg) + : string -> Binding<'model, 'msg> = + Binding.SubModelSelectedItem.vopt subModelSeqBindingName + >> Binding.addLazy (=) + >> Binding.mapModel get + >> Binding.mapMsg set + >> Binding.addCaching + + /// + /// Creates a two-way binding to a SelectedItem-like property where + /// the + /// ItemsSource-like property is a + /// binding. Automatically converts the dynamically created Elmish.WPF + /// view models to/from their corresponding IDs, so the Elmish user code + /// only has to work with the IDs. + /// + /// Only use this if you are unable to use some kind of + /// SelectedValue or + /// SelectedIndex property with a normal + /// binding. This binding is less type-safe. It will throw when + /// initializing the bindings if + /// does not correspond to a binding, and it + /// will throw at runtime if the inferred 'id type does not + /// match the actual ID type used in that binding. + /// + /// + /// The name of the binding used as the items + /// source. + /// + /// Gets the selected sub-model/sub-binding ID from the + /// model. + /// + /// Returns the message to dispatch on selections/de-selections. + /// + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member subModelSelectedItem + ( + subModelSeqBindingName: string, + get: 'model -> 'id voption, + set: 'id voption -> 'msg, + wrapDispatch: Dispatch<'msg> -> Dispatch<'msg> + ) : string -> Binding<'model, 'msg> = + Binding.subModelSelectedItem (subModelSeqBindingName, get, set) + >> Binding.alterMsgStream wrapDispatch + + + /// + /// Creates a two-way binding to a SelectedItem-like property where + /// the + /// ItemsSource-like property is a + /// binding. Automatically converts the dynamically created Elmish.WPF + /// view models to/from their corresponding IDs, so the Elmish user code + /// only has to work with the IDs. + /// + /// Only use this if you are unable to use some kind of + /// SelectedValue or + /// SelectedIndex property with a normal + /// binding. This binding is less type-safe. It will throw when + /// initializing the bindings if + /// does not correspond to a binding, and it + /// will throw at runtime if the inferred 'id type does not + /// match the actual ID type used in that binding. + /// + /// + /// The name of the binding used as the items + /// source. + /// + /// Gets the selected sub-model/sub-binding ID from the + /// model. + /// + /// Returns the message to dispatch on selections/de-selections. + /// + static member subModelSelectedItem + (subModelSeqBindingName: string, get: 'model -> 'id option, set: 'id option -> 'msg) + : string -> Binding<'model, 'msg> = + Binding.SubModelSelectedItem.opt subModelSeqBindingName + >> Binding.addLazy (=) + >> Binding.mapModel get + >> Binding.mapMsg set + >> Binding.addCaching + + /// + /// Creates a two-way binding to a SelectedItem-like property where + /// the + /// ItemsSource-like property is a + /// binding. Automatically converts the dynamically created Elmish.WPF + /// view models to/from their corresponding IDs, so the Elmish user code + /// only has to work with the IDs. + /// + /// Only use this if you are unable to use some kind of + /// SelectedValue or + /// SelectedIndex property with a normal + /// binding. This binding is less type-safe. It will throw when + /// initializing the bindings if + /// does not correspond to a binding, and it + /// will throw at runtime if the inferred 'id type does not + /// match the actual ID type used in that binding. + /// + /// + /// The name of the binding used as the items + /// source. + /// + /// Gets the selected sub-model/sub-binding ID from the + /// model. + /// + /// Returns the message to dispatch on selections/de-selections. + /// + /// + /// Wraps the dispatch function with additional behavior, such as + /// throttling, debouncing, or limiting. + /// + [] + static member subModelSelectedItem + ( + subModelSeqBindingName: string, + get: 'model -> 'id option, + set: 'id option -> 'msg, + wrapDispatch: Dispatch<'msg> -> Dispatch<'msg> + ) : string -> Binding<'model, 'msg> = + Binding.subModelSelectedItem (subModelSeqBindingName, get, set) + >> Binding.alterMsgStream wrapDispatch \ No newline at end of file diff --git a/src/Elmish.WPF/BindingData.fs b/src/Elmish.WPF/BindingData.fs index 0b19d65e..d06e2245 100644 --- a/src/Elmish.WPF/BindingData.fs +++ b/src/Elmish.WPF/BindingData.fs @@ -10,871 +10,815 @@ open Elmish module Helper = - let mapDispatch - (getCurrentModel: unit -> 'model) - (set: 'bindingMsg -> 'model -> 'msg) - (dispatch: 'msg -> unit) - : 'bindingMsg -> unit = - fun bMsg -> getCurrentModel () |> set bMsg |> dispatch + let mapDispatch + (getCurrentModel: unit -> 'model) + (set: 'bindingMsg -> 'model -> 'msg) + (dispatch: 'msg -> unit) + : 'bindingMsg -> unit = + fun bMsg -> getCurrentModel () |> set bMsg |> dispatch -type OneWayData<'model, 'a> = - { Get: 'model -> 'a } +type OneWayData<'model, 'a> = { Get: 'model -> 'a } -type OneWayToSourceData<'model, 'msg, 'a> = - { Set: 'a -> 'model -> 'msg } +type OneWayToSourceData<'model, 'msg, 'a> = { Set: 'a -> 'model -> 'msg } -type OneWaySeqData<'model, 'a, 'aCollection, 'id when 'id : equality> = - { Get: 'model -> 'a seq - CreateCollection: 'a seq -> CollectionTarget<'a, 'aCollection> - GetId: 'a -> 'id - ItemEquals: 'a -> 'a -> bool } +type OneWaySeqData<'model, 'a, 'aCollection, 'id when 'id: equality> = + { Get: 'model -> 'a seq + CreateCollection: 'a seq -> CollectionTarget<'a, 'aCollection> + GetId: 'a -> 'id + ItemEquals: 'a -> 'a -> bool } - member d.Merge(values: CollectionTarget<'a, 'aCollection>, newModel: 'model) = - let create v _ = v - let update oldVal newVal oldIdx = - if not (d.ItemEquals newVal oldVal) then - values.SetAt (oldIdx, newVal) - let newVals = newModel |> d.Get |> Seq.toArray - Merge.keyed d.GetId d.GetId create update values newVals + member d.Merge(values: CollectionTarget<'a, 'aCollection>, newModel: 'model) = + let create v _ = v + + let update oldVal newVal oldIdx = + if not (d.ItemEquals newVal oldVal) then + values.SetAt(oldIdx, newVal) + + let newVals = newModel |> d.Get |> Seq.toArray + Merge.keyed d.GetId d.GetId create update values newVals type TwoWayData<'model, 'msg, 'a> = - { Get: 'model -> 'a - Set: 'a -> 'model -> 'msg } + { Get: 'model -> 'a + Set: 'a -> 'model -> 'msg } -type CmdData<'model, 'msg> = { - Exec: obj -> 'model -> 'msg voption - CanExec: obj -> 'model -> bool - AutoRequery: bool -} +type CmdData<'model, 'msg> = + { Exec: obj -> 'model -> 'msg voption + CanExec: obj -> 'model -> bool + AutoRequery: bool } type SubModelSelectedItemData<'model, 'msg, 'id> = - { Get: 'model -> 'id voption - Set: 'id voption -> 'model -> 'msg - SubModelSeqBindingName: string } + { Get: 'model -> 'id voption + Set: 'id voption -> 'model -> 'msg + SubModelSeqBindingName: string } -type SubModelData<'model, 'msg, 'bindingModel, 'bindingMsg, 'vm> = { - GetModel: 'model -> 'bindingModel voption - CreateViewModel: ViewModelArgs<'bindingModel, 'bindingMsg> -> 'vm - UpdateViewModel: 'vm * 'bindingModel -> unit - ToMsg: 'model -> 'bindingMsg -> 'msg -} +type SubModelData<'model, 'msg, 'bindingModel, 'bindingMsg, 'vm> = + { GetModel: 'model -> 'bindingModel voption + CreateViewModel: ViewModelArgs<'bindingModel, 'bindingMsg> -> 'vm + UpdateViewModel: 'vm * 'bindingModel -> unit + ToMsg: 'model -> 'bindingMsg -> 'msg } -and SubModelWinData<'model, 'msg, 'bindingModel, 'bindingMsg, 'vm> = { - GetState: 'model -> WindowState<'bindingModel> - CreateViewModel: ViewModelArgs<'bindingModel, 'bindingMsg> -> 'vm - UpdateViewModel: 'vm * 'bindingModel -> unit - ToMsg: 'model -> 'bindingMsg -> 'msg - GetWindow: 'model -> Dispatch<'msg> -> Window - IsModal: bool - OnCloseRequested: 'model -> 'msg voption -} +and SubModelWinData<'model, 'msg, 'bindingModel, 'bindingMsg, 'vm> = + { GetState: 'model -> WindowState<'bindingModel> + CreateViewModel: ViewModelArgs<'bindingModel, 'bindingMsg> -> 'vm + UpdateViewModel: 'vm * 'bindingModel -> unit + ToMsg: 'model -> 'bindingMsg -> 'msg + GetWindow: 'model -> Dispatch<'msg> -> Window + IsModal: bool + OnCloseRequested: 'model -> 'msg voption } and SubModelSeqUnkeyedData<'model, 'msg, 'bindingModel, 'bindingMsg, 'vm, 'vmCollection> = - { GetModels: 'model -> 'bindingModel seq - CreateViewModel: ViewModelArgs<'bindingModel, 'bindingMsg> -> 'vm - CreateCollection: 'vm seq -> CollectionTarget<'vm, 'vmCollection> - UpdateViewModel: 'vm * 'bindingModel -> unit - ToMsg: 'model -> int * 'bindingMsg -> 'msg } - - -and SubModelSeqKeyedData<'model, 'msg, 'bindingModel, 'bindingMsg, 'vm, 'vmCollection, 'id when 'id : equality> = - { GetSubModels: 'model -> 'bindingModel seq - CreateViewModel: ViewModelArgs<'bindingModel, 'bindingMsg> -> 'vm - CreateCollection: 'vm seq -> CollectionTarget<'vm, 'vmCollection> - UpdateViewModel: 'vm * 'bindingModel -> unit - ToMsg: 'model -> 'id * 'bindingMsg -> 'msg - BmToId: 'bindingModel -> 'id - VmToId: 'vm -> 'id } - - member d.MergeKeyed - (create: 'bindingModel -> 'id -> 'vm, - update: 'vm -> 'bindingModel -> unit, - values: CollectionTarget<'vm, 'vmCollection>, - newSubModels: 'bindingModel []) = - let update vm bm _ = update vm bm - Merge.keyed d.BmToId d.VmToId create update values newSubModels + { GetModels: 'model -> 'bindingModel seq + CreateViewModel: ViewModelArgs<'bindingModel, 'bindingMsg> -> 'vm + CreateCollection: 'vm seq -> CollectionTarget<'vm, 'vmCollection> + UpdateViewModel: 'vm * 'bindingModel -> unit + ToMsg: 'model -> int * 'bindingMsg -> 'msg } + + +and SubModelSeqKeyedData<'model, 'msg, 'bindingModel, 'bindingMsg, 'vm, 'vmCollection, 'id when 'id: equality> = + { GetSubModels: 'model -> 'bindingModel seq + CreateViewModel: ViewModelArgs<'bindingModel, 'bindingMsg> -> 'vm + CreateCollection: 'vm seq -> CollectionTarget<'vm, 'vmCollection> + UpdateViewModel: 'vm * 'bindingModel -> unit + ToMsg: 'model -> 'id * 'bindingMsg -> 'msg + BmToId: 'bindingModel -> 'id + VmToId: 'vm -> 'id } + + member d.MergeKeyed + ( + create: 'bindingModel -> 'id -> 'vm, + update: 'vm -> 'bindingModel -> unit, + values: CollectionTarget<'vm, 'vmCollection>, + newSubModels: 'bindingModel[] + ) = + let update vm bm _ = update vm bm + Merge.keyed d.BmToId d.VmToId create update values newSubModels and ValidationData<'model, 'msg, 't> = - { BindingData: BindingData<'model, 'msg, 't> - Validate: 'model -> string list } + { BindingData: BindingData<'model, 'msg, 't> + Validate: 'model -> string list } and LazyData<'model, 'msg, 'bindingModel, 'bindingMsg, 't> = - { BindingData: BindingData<'bindingModel, 'bindingMsg, 't> - Get: 'model -> 'bindingModel - Set: 'bindingMsg -> 'model -> 'msg - Equals: 'bindingModel -> 'bindingModel -> bool } + { BindingData: BindingData<'bindingModel, 'bindingMsg, 't> + Get: 'model -> 'bindingModel + Set: 'bindingMsg -> 'model -> 'msg + Equals: 'bindingModel -> 'bindingModel -> bool } - member this.MapDispatch - (getCurrentModel: unit -> 'model, - dispatch: 'msg -> unit) - : 'bindingMsg -> unit = - Helper.mapDispatch getCurrentModel this.Set dispatch + member this.MapDispatch(getCurrentModel: unit -> 'model, dispatch: 'msg -> unit) : 'bindingMsg -> unit = + Helper.mapDispatch getCurrentModel this.Set dispatch and AlterMsgStreamData<'model, 'msg, 'bindingModel, 'bindingMsg, 'dispatchMsg, 't> = - { BindingData: BindingData<'bindingModel, 'bindingMsg, 't> - Get: 'model -> 'bindingModel - Set: 'dispatchMsg -> 'model -> 'msg - AlterMsgStream: ('dispatchMsg -> unit) -> 'bindingMsg -> unit } + { BindingData: BindingData<'bindingModel, 'bindingMsg, 't> + Get: 'model -> 'bindingModel + Set: 'dispatchMsg -> 'model -> 'msg + AlterMsgStream: ('dispatchMsg -> unit) -> 'bindingMsg -> unit } - member this.MapDispatch - (getCurrentModel: unit -> 'model, - dispatch: 'msg -> unit) - : 'bindingMsg -> unit = - Helper.mapDispatch getCurrentModel this.Set dispatch - |> this.AlterMsgStream + member this.MapDispatch(getCurrentModel: unit -> 'model, dispatch: 'msg -> unit) : 'bindingMsg -> unit = + Helper.mapDispatch getCurrentModel this.Set dispatch |> this.AlterMsgStream and BaseBindingData<'model, 'msg, 't> = - | OneWayData of OneWayData<'model, 't> - | OneWayToSourceData of OneWayToSourceData<'model, 'msg, 't> - | OneWaySeqData of OneWaySeqData<'model, obj, 't, obj> - | TwoWayData of TwoWayData<'model, 'msg, 't> - | CmdData of CmdData<'model, 'msg> - | SubModelData of SubModelData<'model, 'msg, obj, obj, 't> - | SubModelWinData of SubModelWinData<'model, 'msg, obj, obj, 't> - | SubModelSeqUnkeyedData of SubModelSeqUnkeyedData<'model, 'msg, obj, obj, obj, 't> - | SubModelSeqKeyedData of SubModelSeqKeyedData<'model, 'msg, obj, obj, obj, 't, obj> - | SubModelSelectedItemData of SubModelSelectedItemData<'model, 'msg, obj> + | OneWayData of OneWayData<'model, 't> + | OneWayToSourceData of OneWayToSourceData<'model, 'msg, 't> + | OneWaySeqData of OneWaySeqData<'model, obj, 't, obj> + | TwoWayData of TwoWayData<'model, 'msg, 't> + | CmdData of CmdData<'model, 'msg> + | SubModelData of SubModelData<'model, 'msg, obj, obj, 't> + | SubModelWinData of SubModelWinData<'model, 'msg, obj, obj, 't> + | SubModelSeqUnkeyedData of SubModelSeqUnkeyedData<'model, 'msg, obj, obj, obj, 't> + | SubModelSeqKeyedData of SubModelSeqKeyedData<'model, 'msg, obj, obj, obj, 't, obj> + | SubModelSelectedItemData of SubModelSelectedItemData<'model, 'msg, obj> and BindingData<'model, 'msg, 't> = - | BaseBindingData of BaseBindingData<'model, 'msg, 't> - | CachingData of BindingData<'model, 'msg, 't> - | ValidationData of ValidationData<'model, 'msg, 't> - | LazyData of LazyData<'model, 'msg, obj, obj, 't> - | AlterMsgStreamData of AlterMsgStreamData<'model, 'msg, obj, obj, obj, 't> + | BaseBindingData of BaseBindingData<'model, 'msg, 't> + | CachingData of BindingData<'model, 'msg, 't> + | ValidationData of ValidationData<'model, 'msg, 't> + | LazyData of LazyData<'model, 'msg, obj, obj, 't> + | AlterMsgStreamData of AlterMsgStreamData<'model, 'msg, obj, obj, obj, 't> module BindingData = - module private MapT = - - let baseCase (fOut: 't0 -> 't1) (fIn: 't1 -> 't0) = - function - | OneWayData d -> OneWayData { - Get = d.Get >> fOut - } - | OneWayToSourceData d -> OneWayToSourceData { - Set = fIn >> d.Set - } - | OneWaySeqData d -> OneWaySeqData { - Get = d.Get - CreateCollection = d.CreateCollection >> CollectionTarget.mapCollection fOut - GetId = d.GetId - ItemEquals = d.ItemEquals - } - | TwoWayData d -> TwoWayData { - Get = d.Get >> fOut - Set = fIn >> d.Set - } - | CmdData d -> CmdData { - Exec = d.Exec - CanExec = d.CanExec - AutoRequery = d.AutoRequery - } - | SubModelData d -> SubModelData { - GetModel = d.GetModel - CreateViewModel = d.CreateViewModel >> fOut - UpdateViewModel = (fun (vm,m) -> d.UpdateViewModel (fIn vm, m)) - ToMsg = d.ToMsg - } - | SubModelWinData d -> SubModelWinData { - GetState = d.GetState - CreateViewModel = d.CreateViewModel >> fOut - UpdateViewModel = (fun (vm,m) -> d.UpdateViewModel (fIn vm, m)) - ToMsg = d.ToMsg - GetWindow = d.GetWindow - IsModal = d.IsModal - OnCloseRequested = d.OnCloseRequested - } - | SubModelSeqUnkeyedData d -> SubModelSeqUnkeyedData { - GetModels = d.GetModels - CreateViewModel = d.CreateViewModel - CreateCollection = d.CreateCollection >> CollectionTarget.mapCollection fOut - UpdateViewModel = d.UpdateViewModel - ToMsg = d.ToMsg - } - | SubModelSeqKeyedData d -> SubModelSeqKeyedData { - GetSubModels = d.GetSubModels - CreateViewModel = d.CreateViewModel - CreateCollection = d.CreateCollection >> CollectionTarget.mapCollection fOut - UpdateViewModel = d.UpdateViewModel - ToMsg = d.ToMsg - VmToId = d.VmToId - BmToId = d.BmToId - } - | SubModelSelectedItemData d -> SubModelSelectedItemData { - Get = d.Get - Set = d.Set - SubModelSeqBindingName = d.SubModelSeqBindingName - } - - let rec recursiveCase<'model, 'msg, 't0, 't1> (fOut: 't0 -> 't1) (fIn: 't1 -> 't0) - : BindingData<'model, 'msg, 't0> -> BindingData<'model, 'msg, 't1> = - function - | BaseBindingData d -> d |> baseCase fOut fIn |> BaseBindingData - | CachingData d -> d |> recursiveCase<'model, 'msg, 't0, 't1> fOut fIn |> CachingData - | ValidationData d -> ValidationData { - BindingData = recursiveCase<'model, 'msg, 't0, 't1> fOut fIn d.BindingData - Validate = d.Validate - } - | LazyData d -> LazyData { - Get = d.Get - Set = d.Set - BindingData = recursiveCase fOut fIn d.BindingData - Equals = d.Equals - } - | AlterMsgStreamData d -> AlterMsgStreamData { - BindingData = recursiveCase fOut fIn d.BindingData - AlterMsgStream = d.AlterMsgStream - Get = d.Get - Set = d.Set - } - - let boxT b = MapT.recursiveCase box unbox b - let unboxT b = MapT.recursiveCase unbox box b - - let mapModel f = - let binaryHelper binary x m = binary x (f m) - let baseCase = function - | OneWayData d -> OneWayData { - Get = f >> d.Get - } - | OneWayToSourceData d -> OneWayToSourceData { - Set = binaryHelper d.Set - } - | OneWaySeqData d -> OneWaySeqData { - Get = f >> d.Get - CreateCollection = d.CreateCollection - GetId = d.GetId - ItemEquals = d.ItemEquals - } - | TwoWayData d -> TwoWayData { - Get = f >> d.Get - Set = binaryHelper d.Set - } - | CmdData d -> CmdData { - Exec = binaryHelper d.Exec - CanExec = binaryHelper d.CanExec - AutoRequery = d.AutoRequery - } - | SubModelData d -> SubModelData { - GetModel = f >> d.GetModel - CreateViewModel = d.CreateViewModel - UpdateViewModel = d.UpdateViewModel - ToMsg = f >> d.ToMsg - } - | SubModelWinData d -> SubModelWinData { - GetState = f >> d.GetState - CreateViewModel = d.CreateViewModel - UpdateViewModel = d.UpdateViewModel - ToMsg = f >> d.ToMsg - GetWindow = f >> d.GetWindow - IsModal = d.IsModal - OnCloseRequested = f >> d.OnCloseRequested - } - | SubModelSeqUnkeyedData d -> SubModelSeqUnkeyedData { - GetModels = f >> d.GetModels - CreateViewModel = d.CreateViewModel - CreateCollection = d.CreateCollection - UpdateViewModel = d.UpdateViewModel - ToMsg = f >> d.ToMsg - } - | SubModelSeqKeyedData d -> SubModelSeqKeyedData { - GetSubModels = f >> d.GetSubModels - CreateViewModel = d.CreateViewModel - CreateCollection = d.CreateCollection - UpdateViewModel = d.UpdateViewModel - ToMsg = f >> d.ToMsg - BmToId = d.BmToId - VmToId = d.VmToId - } - | SubModelSelectedItemData d -> SubModelSelectedItemData { - Get = f >> d.Get - Set = binaryHelper d.Set - SubModelSeqBindingName = d.SubModelSeqBindingName - } - let rec recursiveCase = function - | BaseBindingData d -> d |> baseCase |> BaseBindingData - | CachingData d -> d |> recursiveCase |> CachingData - | ValidationData d -> ValidationData { - BindingData = recursiveCase d.BindingData - Validate = f >> d.Validate - } - | LazyData d -> LazyData { - BindingData = d.BindingData - Get = f >> d.Get - Set = binaryHelper d.Set - Equals = d.Equals - } - | AlterMsgStreamData d -> AlterMsgStreamData { - BindingData = d.BindingData - AlterMsgStream = d.AlterMsgStream - Get = f >> d.Get - Set = binaryHelper d.Set - } - recursiveCase - - let mapMsgWithModel (f: 'a -> 'model -> 'b) = - let baseCase = function - | OneWayData d -> d |> OneWayData - | OneWayToSourceData d -> OneWayToSourceData { - Set = fun v m -> f (d.Set v m) m - } - | OneWaySeqData d -> d |> OneWaySeqData - | TwoWayData d -> TwoWayData { - Get = d.Get - Set = fun v m -> f (d.Set v m) m - } - | CmdData d -> CmdData { - Exec = fun p m -> d.Exec p m |> ValueOption.map (fun msg -> f msg m) - CanExec = fun p m -> d.CanExec p m - AutoRequery = d.AutoRequery - } - | SubModelData d -> SubModelData { - GetModel = d.GetModel - CreateViewModel = d.CreateViewModel - UpdateViewModel = d.UpdateViewModel - ToMsg = fun m bMsg -> f (d.ToMsg m bMsg) m - } - | SubModelWinData d -> SubModelWinData { - GetState = d.GetState - CreateViewModel = d.CreateViewModel - UpdateViewModel = d.UpdateViewModel - ToMsg = fun m bMsg -> f (d.ToMsg m bMsg) m - GetWindow = fun m dispatch -> d.GetWindow m (fun msg -> f msg m |> dispatch) - IsModal = d.IsModal - OnCloseRequested = fun m -> m |> d.OnCloseRequested |> ValueOption.map (fun msg -> f msg m) - } - | SubModelSeqUnkeyedData d -> SubModelSeqUnkeyedData { - GetModels = d.GetModels - CreateViewModel = d.CreateViewModel - CreateCollection = d.CreateCollection - UpdateViewModel = d.UpdateViewModel - ToMsg = fun m bMsg -> f (d.ToMsg m bMsg) m - } - | SubModelSeqKeyedData d -> SubModelSeqKeyedData { - GetSubModels = d.GetSubModels - CreateViewModel = d.CreateViewModel - CreateCollection = d.CreateCollection - UpdateViewModel = d.UpdateViewModel - ToMsg = fun m bMsg -> f (d.ToMsg m bMsg) m - BmToId = d.BmToId - VmToId = d.VmToId - } - | SubModelSelectedItemData d -> SubModelSelectedItemData { - Get = d.Get - Set = fun v m -> f (d.Set v m) m - SubModelSeqBindingName = d.SubModelSeqBindingName - } - let rec recursiveCase = function - | BaseBindingData d -> d |> baseCase |> BaseBindingData - | CachingData d -> d |> recursiveCase |> CachingData - | ValidationData d -> ValidationData { - BindingData = recursiveCase d.BindingData - Validate = d.Validate - } - | LazyData d -> - LazyData { - BindingData = d.BindingData - Get = d.Get - Set = fun a m -> f (d.Set a m) m - Equals = d.Equals - } - | AlterMsgStreamData d -> AlterMsgStreamData { - BindingData = d.BindingData - Get = d.Get - Set = fun a m -> f (d.Set a m) m - AlterMsgStream = d.AlterMsgStream - } - recursiveCase - - let mapMsg f = mapMsgWithModel (fun a _ -> f a) - - let setMsgWithModel f = mapMsgWithModel (fun _ m -> f m) - let setMsg msg = mapMsg (fun _ -> msg) - - let addCaching b = b |> CachingData - let addValidation validate b = { BindingData = b; Validate = validate } |> ValidationData - let addLazy (equals: 'model -> 'model -> bool) b = - { BindingData = b |> mapModel unbox |> mapMsg box - Get = box - Set = fun (dMsg: obj) _ -> unbox dMsg - Equals = fun m1 m2 -> equals (unbox m1) (unbox m2) - } |> LazyData - let alterMsgStream - (alteration: ('dispatchMsg -> unit) -> 'bindingMsg -> unit) - (b: BindingData<'bindingModel, 'bindingMsg, 't>) - : BindingData<'model, 'msg, 't> = - { BindingData = b |> mapModel unbox |> mapMsg box - Get = box - Set = fun (dMsg: obj) _ -> unbox dMsg - AlterMsgStream = - fun (f: obj -> unit) -> - let f' = box >> f - let g = alteration f' - unbox >> g - } |> AlterMsgStreamData - let addSticky (predicate: 'model -> bool) (binding: BindingData<'model, 'msg, 't>) = - let mutable stickyModel = None - let f newModel = - if predicate newModel then - stickyModel <- Some newModel - newModel - else - stickyModel |> Option.defaultValue newModel - binding |> mapModel f - - - module Option = - - let box ma = ma |> Option.map box |> Option.toObj - let unbox obj = obj |> Option.ofObj |> Option.map unbox - - module ValueOption = - - let box ma = ma |> ValueOption.map box |> ValueOption.toObj - let unbox obj = obj |> ValueOption.ofObj |> ValueOption.map unbox - - - module OneWay = - - let id<'a, 'msg> : BindingData<'a, 'msg, 'a> = - { Get = id } - |> OneWayData - |> BaseBindingData - - let private mapFunctions - mGet - (d: OneWayData<'model, 'a>) = - { d with Get = mGet d.Get } - - let measureFunctions - mGet = - mapFunctions - (mGet "get") - - - module OneWayToSource = - - let id<'model, 'a> : BindingData<'model, 'a, 'a> = - { OneWayToSourceData.Set = Func2.id1 } - |> OneWayToSourceData - |> BaseBindingData - - let private mapFunctions - mSet - (d: OneWayToSourceData<'model, 'msg, 'a>) = - { d with Set = mSet d.Set } - - let measureFunctions - mSet = - mapFunctions - (mSet "set") - - - module OneWaySeq = - - let mapMinorTypes - (outMapA: 'a -> 'a0) - (outMapId: 'id -> 'id0) - (inMapA: 'a0 -> 'a) - (d: OneWaySeqData<'model, 'a, 'aCollection, 'id>) = { - Get = d.Get >> Seq.map outMapA - CreateCollection = Seq.map inMapA >> d.CreateCollection >> CollectionTarget.mapA outMapA inMapA - GetId = inMapA >> d.GetId >> outMapId - ItemEquals = fun a1 a2 -> d.ItemEquals (inMapA a1) (inMapA a2) - } - - let boxMinorTypes d = d |> mapMinorTypes box box unbox - - let create itemEquals getId = - { Get = (fun x -> upcast x) - CreateCollection = ObservableCollection >> CollectionTarget.create - ItemEquals = itemEquals - GetId = getId } - |> boxMinorTypes - |> OneWaySeqData - |> BaseBindingData - - let private mapFunctions - mGet - mGetId - mItemEquals - (d: OneWaySeqData<'model, 'a, 'aCollection, 'id>) = - { d with Get = mGet d.Get - GetId = mGetId d.GetId - ItemEquals = mItemEquals d.ItemEquals } - - let measureFunctions - mGet - mGetId - mItemEquals = - mapFunctions - (mGet "get") - (mGetId "getId") - (mItemEquals "itemEquals") - - - module TwoWay = - - let id<'a> : BindingData<'a, 'a, 'a> = - { TwoWayData.Get = id - Set = Func2.id1 } - |> TwoWayData - |> BaseBindingData - - let private mapFunctions - mGet - mSet - (d: TwoWayData<'model, 'msg, 'a>) = - { d with Get = mGet d.Get - Set = mSet d.Set } - - let measureFunctions - mGet - mSet = - mapFunctions - (mGet "get") - (mSet "set") - - - module Cmd = - - let createWithParam exec canExec autoRequery : BindingData<'model, 'msg, ICommand> = - { Exec = exec - CanExec = canExec - AutoRequery = autoRequery } - |> CmdData - |> BaseBindingData - - let private mapFunctions - mExec - mCanExec - (d: CmdData<'model, 'msg>) = - { d with Exec = mExec d.Exec - CanExec = mCanExec d.CanExec } - - let measureFunctions - mExec - mCanExec = - mapFunctions - (mExec "exec") - (mCanExec "canExec") - - - module SubModelSelectedItem = - - let mapMinorTypes - (outMapId: 'id -> 'id0) - (inMapId: 'id0 -> 'id) - (d: SubModelSelectedItemData<'model, 'msg, 'id>) = { - Get = d.Get >> ValueOption.map outMapId - Set = ValueOption.map inMapId >> d.Set - SubModelSeqBindingName = d.SubModelSeqBindingName - } - - let boxMinorTypes d = d |> mapMinorTypes box unbox - - let create subModelSeqBindingName = - { Get = id - Set = Func2.id1 - SubModelSeqBindingName = subModelSeqBindingName } - |> boxMinorTypes - |> SubModelSelectedItemData - |> BaseBindingData - - let private mapFunctions - mGet - mSet - (d: SubModelSelectedItemData<'model, 'msg, 'id>) = - { d with Get = mGet d.Get - Set = mSet d.Set } - - let measureFunctions - mGet - mSet = - mapFunctions - (mGet "get") - (mSet "set") - - - module SubModel = - - let mapMinorTypes - (outMapBindingModel: 'bindingModel -> 'bindingModel0) - (outMapBindingMsg: 'bindingMsg -> 'bindingMsg0) - (inMapBindingModel: 'bindingModel0 -> 'bindingModel) - (inMapBindingMsg: 'bindingMsg0 -> 'bindingMsg) - (d: SubModelData<'model, 'msg, 'bindingModel, 'bindingMsg, 'vm>) = { - GetModel = d.GetModel >> ValueOption.map outMapBindingModel - CreateViewModel = fun args -> d.CreateViewModel(args |> ViewModelArgs.map inMapBindingModel outMapBindingMsg) - UpdateViewModel = fun (vm, m) -> (vm, inMapBindingModel m) |> d.UpdateViewModel - ToMsg = fun m bMsg -> d.ToMsg m (inMapBindingMsg bMsg) - } - - let boxMinorTypes d = d |> mapMinorTypes box box unbox unbox - - let create createViewModel updateViewModel = - { GetModel = id - CreateViewModel = createViewModel - UpdateViewModel = updateViewModel - ToMsg = Func2.id2 } - |> boxMinorTypes - |> SubModelData - |> BaseBindingData - - let private mapFunctions - mGetModel - mGetBindings - mUpdateViewModel - mToMsg - (d: SubModelData<'model, 'msg, 'bindingModel, 'bindingMsg, 'vm>) - : SubModelData<'model,'msg,'bindingModel,'bindingMsg,'vm> = - { d with GetModel = mGetModel d.GetModel - CreateViewModel = mGetBindings d.CreateViewModel - UpdateViewModel = mUpdateViewModel d.UpdateViewModel - ToMsg = mToMsg d.ToMsg } - - let measureFunctions - mGetModel - mGetBindings - mUpdateViewModel - mToMsg = - mapFunctions - (mGetModel "getSubModel") // sic: "getModel" would be following the pattern - (mGetBindings "bindings") // sic: "getBindings" would be following the pattern - (mUpdateViewModel "updateViewModel") - (mToMsg "toMsg") - - - module SubModelWin = - - let mapMinorTypes - (outMapBindingModel: 'bindingModel -> 'bindingModel0) - (outMapBindingMsg: 'bindingMsg -> 'bindingMsg0) - (inMapBindingModel: 'bindingModel0 -> 'bindingModel) - (inMapBindingMsg: 'bindingMsg0 -> 'bindingMsg) - (d: SubModelWinData<'model, 'msg, 'bindingModel, 'bindingMsg, 'vm>) = { - GetState = d.GetState >> WindowState.map outMapBindingModel - CreateViewModel = fun args -> d.CreateViewModel(args |> ViewModelArgs.map inMapBindingModel outMapBindingMsg) - UpdateViewModel = fun (vm, m) -> d.UpdateViewModel (vm, inMapBindingModel m) - ToMsg = fun m bMsg -> d.ToMsg m (inMapBindingMsg bMsg) - GetWindow = d.GetWindow - IsModal = d.IsModal - OnCloseRequested = d.OnCloseRequested - } - - let boxMinorTypes d = d |> mapMinorTypes box box unbox unbox - - let create getState createViewModel updateViewModel toMsg getWindow isModal onCloseRequested = - { GetState = getState - CreateViewModel = createViewModel - UpdateViewModel = updateViewModel - ToMsg = toMsg - GetWindow = getWindow - IsModal = isModal - OnCloseRequested = onCloseRequested } - |> boxMinorTypes - |> SubModelWinData - |> BaseBindingData - - let private mapFunctions - mGetState - mGetBindings - mUpdateViewModel - mToMsg - mGetWindow - mOnCloseRequested - (d: SubModelWinData<'model, 'msg, 'bindingModel, 'bindingMsg, 'vm>) = - { d with GetState = mGetState d.GetState - CreateViewModel = mGetBindings d.CreateViewModel - UpdateViewModel = mUpdateViewModel d.UpdateViewModel - ToMsg = mToMsg d.ToMsg - GetWindow = mGetWindow d.GetWindow - OnCloseRequested = mOnCloseRequested d.OnCloseRequested } - - let measureFunctions - mGetState - mGetBindings - mUpdateViewModel - mToMsg = - mapFunctions - (mGetState "getState") - (mGetBindings "bindings") // sic: "getBindings" would be following the pattern - (mUpdateViewModel "updateViewModel") - (mToMsg "toMsg") - id // sic: could measure GetWindow - id // sic: could measure OnCloseRequested - - - module SubModelSeqUnkeyed = - - let mapMinorTypes - (outMapBindingModel: 'bindingModel -> 'bindingModel0) - (outMapBindingMsg: 'bindingMsg -> 'bindingMsg0) - (outMapBindingViewModel: 'vm -> 'vm0) - (inMapBindingModel: 'bindingModel0 -> 'bindingModel) - (inMapBindingMsg: 'bindingMsg0 -> 'bindingMsg) - (inMapBindingViewModel: 'vm0 -> 'vm) - (d: SubModelSeqUnkeyedData<'model, 'msg, 'bindingModel, 'bindingMsg, 'vm, 'vmCollection>) = { - GetModels = d.GetModels >> Seq.map outMapBindingModel - CreateViewModel = fun args -> d.CreateViewModel(args |> ViewModelArgs.map inMapBindingModel outMapBindingMsg) |> outMapBindingViewModel - CreateCollection = Seq.map inMapBindingViewModel >> d.CreateCollection >> CollectionTarget.mapA outMapBindingViewModel inMapBindingViewModel - UpdateViewModel = fun (vm, m) -> d.UpdateViewModel (inMapBindingViewModel vm, inMapBindingModel m) - ToMsg = fun m (idx, bMsg) -> d.ToMsg m (idx, (inMapBindingMsg bMsg)) - } - - let boxMinorTypes d = d |> mapMinorTypes box box box unbox unbox unbox - - let create createViewModel updateViewModel = - { GetModels = (fun x -> upcast x) - CreateViewModel = createViewModel - CreateCollection = ObservableCollection >> CollectionTarget.create - UpdateViewModel = updateViewModel - ToMsg = Func2.id2 } - |> boxMinorTypes - |> SubModelSeqUnkeyedData - |> BaseBindingData - - let private mapFunctions - mGetModels - mGetBindings - mCreateCollection - mUpdateViewModel - mToMsg - (d: SubModelSeqUnkeyedData<'model, 'msg, 'bindingModel, 'bindingMsg, 'vm, 'vmCollection>) = - { d with GetModels = mGetModels d.GetModels - CreateViewModel = mGetBindings d.CreateViewModel - CreateCollection = mCreateCollection d.CreateCollection - UpdateViewModel = mUpdateViewModel d.UpdateViewModel - ToMsg = mToMsg d.ToMsg } - - let measureFunctions - mGetModels - mGetBindings - mCreateCollection - mUpdateViewModel - mToMsg = - mapFunctions - (mGetModels "getSubModels") // sic: "getModels" would follow the pattern - (mGetBindings "bindings") // sic: "getBindings" would follow the pattern - (mCreateCollection "createCollection") - (mUpdateViewModel "updateViewModel") - (mToMsg "toMsg") - - - module SubModelSeqKeyed = - - let mapMinorTypes - (outMapBindingModel: 'bindingModel -> 'bindingModel0) - (outMapBindingMsg: 'bindingMsg -> 'bindingMsg0) - (outMapBindingViewModel: 'vm -> 'vm0) - (outMapId: 'id -> 'id0) - (inMapBindingModel: 'bindingModel0 -> 'bindingModel) - (inMapBindingMsg: 'bindingMsg0 -> 'bindingMsg) - (inMapBindingViewModel: 'vm0 -> 'vm) - (inMapId: 'id0 -> 'id) - (d: SubModelSeqKeyedData<'model, 'msg, 'bindingModel, 'bindingMsg, 'vm, 'vmCollection, 'id>) = { - GetSubModels = d.GetSubModels >> Seq.map outMapBindingModel - CreateViewModel = fun args -> d.CreateViewModel(args |> ViewModelArgs.map inMapBindingModel outMapBindingMsg) |> outMapBindingViewModel - CreateCollection = Seq.map inMapBindingViewModel >> d.CreateCollection >> CollectionTarget.mapA outMapBindingViewModel inMapBindingViewModel - UpdateViewModel = fun (vm, m) -> (inMapBindingViewModel vm, inMapBindingModel m) |> d.UpdateViewModel - ToMsg = fun m (id, bMsg) -> d.ToMsg m ((inMapId id), (inMapBindingMsg bMsg)) - BmToId = inMapBindingModel >> d.BmToId >> outMapId - VmToId = fun vm -> vm |> inMapBindingViewModel |> d.VmToId |> outMapId - } - - let boxMinorTypes d = d |> mapMinorTypes box box box box unbox unbox unbox unbox - - let create createViewModel updateViewModel bmToId vmToId = - { GetSubModels = (fun x -> upcast x) - CreateViewModel = createViewModel - CreateCollection = ObservableCollection >> CollectionTarget.create - UpdateViewModel = updateViewModel - ToMsg = Func2.id2 - BmToId = bmToId - VmToId = vmToId } - |> boxMinorTypes - |> SubModelSeqKeyedData - |> BaseBindingData - - let private mapFunctions - mGetSubModels - mGetBindings - mCreateCollection - mUpdateViewModel - mToMsg - mGetId - mGetVmId - (d: SubModelSeqKeyedData<'model, 'msg, 'bindingModel, 'bindingMsg, 'vm, 'vmCollection, 'id>) = - { d with GetSubModels = mGetSubModels d.GetSubModels - CreateViewModel = mGetBindings d.CreateViewModel - CreateCollection = mCreateCollection d.CreateCollection - UpdateViewModel = mUpdateViewModel d.UpdateViewModel - ToMsg = mToMsg d.ToMsg - BmToId = mGetId d.BmToId - VmToId = mGetVmId d.VmToId } - - let measureFunctions - mGetSubModels - mGetBindings - mCreateCollection - mUpdateViewModel - mToMsg - mGetId - mGetVmId = - mapFunctions - (mGetSubModels "getSubModels") - (mGetBindings "getBindings") - (mCreateCollection "createCollection") - (mUpdateViewModel "updateViewModel") - (mToMsg "toMsg") - (mGetId "getId") - (mGetVmId "getVmId") - - - module Validation = - - let private mapFunctions - mValidate - (d: ValidationData<'model, 'msg, 't>) = - { d with Validate = mValidate d.Validate } - - let measureFunctions - mValidate = - mapFunctions - (mValidate "validate") - - module Lazy = - - let private mapFunctions - mGet - mSet - mEquals - (d: LazyData<'model, 'msg, 'bindingModel, 'bindingMsg, 't>) = - { d with Get = mGet d.Get - Set = mSet d.Set - Equals = mEquals d.Equals } - - let measureFunctions - mGet - mSet - mEquals = - mapFunctions - (mGet "get") - (mSet "set") - (mEquals "equals") + module private MapT = + + let baseCase (fOut: 't0 -> 't1) (fIn: 't1 -> 't0) = + function + | OneWayData d -> OneWayData { Get = d.Get >> fOut } + | OneWayToSourceData d -> OneWayToSourceData { Set = fIn >> d.Set } + | OneWaySeqData d -> + OneWaySeqData + { Get = d.Get + CreateCollection = d.CreateCollection >> CollectionTarget.mapCollection fOut + GetId = d.GetId + ItemEquals = d.ItemEquals } + | TwoWayData d -> + TwoWayData + { Get = d.Get >> fOut + Set = fIn >> d.Set } + | CmdData d -> + CmdData + { Exec = d.Exec + CanExec = d.CanExec + AutoRequery = d.AutoRequery } + | SubModelData d -> + SubModelData + { GetModel = d.GetModel + CreateViewModel = d.CreateViewModel >> fOut + UpdateViewModel = (fun (vm, m) -> d.UpdateViewModel(fIn vm, m)) + ToMsg = d.ToMsg } + | SubModelWinData d -> + SubModelWinData + { GetState = d.GetState + CreateViewModel = d.CreateViewModel >> fOut + UpdateViewModel = (fun (vm, m) -> d.UpdateViewModel(fIn vm, m)) + ToMsg = d.ToMsg + GetWindow = d.GetWindow + IsModal = d.IsModal + OnCloseRequested = d.OnCloseRequested } + | SubModelSeqUnkeyedData d -> + SubModelSeqUnkeyedData + { GetModels = d.GetModels + CreateViewModel = d.CreateViewModel + CreateCollection = d.CreateCollection >> CollectionTarget.mapCollection fOut + UpdateViewModel = d.UpdateViewModel + ToMsg = d.ToMsg } + | SubModelSeqKeyedData d -> + SubModelSeqKeyedData + { GetSubModels = d.GetSubModels + CreateViewModel = d.CreateViewModel + CreateCollection = d.CreateCollection >> CollectionTarget.mapCollection fOut + UpdateViewModel = d.UpdateViewModel + ToMsg = d.ToMsg + VmToId = d.VmToId + BmToId = d.BmToId } + | SubModelSelectedItemData d -> + SubModelSelectedItemData + { Get = d.Get + Set = d.Set + SubModelSeqBindingName = d.SubModelSeqBindingName } + + let rec recursiveCase<'model, 'msg, 't0, 't1> + (fOut: 't0 -> 't1) + (fIn: 't1 -> 't0) + : BindingData<'model, 'msg, 't0> -> BindingData<'model, 'msg, 't1> = + function + | BaseBindingData d -> d |> baseCase fOut fIn |> BaseBindingData + | CachingData d -> d |> recursiveCase<'model, 'msg, 't0, 't1> fOut fIn |> CachingData + | ValidationData d -> + ValidationData + { BindingData = recursiveCase<'model, 'msg, 't0, 't1> fOut fIn d.BindingData + Validate = d.Validate } + | LazyData d -> + LazyData + { Get = d.Get + Set = d.Set + BindingData = recursiveCase fOut fIn d.BindingData + Equals = d.Equals } + | AlterMsgStreamData d -> + AlterMsgStreamData + { BindingData = recursiveCase fOut fIn d.BindingData + AlterMsgStream = d.AlterMsgStream + Get = d.Get + Set = d.Set } + + let boxT b = MapT.recursiveCase box unbox b + let unboxT b = MapT.recursiveCase unbox box b + + let mapModel f = + let binaryHelper binary x m = binary x (f m) + + let baseCase = + function + | OneWayData d -> OneWayData { Get = f >> d.Get } + | OneWayToSourceData d -> OneWayToSourceData { Set = binaryHelper d.Set } + | OneWaySeqData d -> + OneWaySeqData + { Get = f >> d.Get + CreateCollection = d.CreateCollection + GetId = d.GetId + ItemEquals = d.ItemEquals } + | TwoWayData d -> + TwoWayData + { Get = f >> d.Get + Set = binaryHelper d.Set } + | CmdData d -> + CmdData + { Exec = binaryHelper d.Exec + CanExec = binaryHelper d.CanExec + AutoRequery = d.AutoRequery } + | SubModelData d -> + SubModelData + { GetModel = f >> d.GetModel + CreateViewModel = d.CreateViewModel + UpdateViewModel = d.UpdateViewModel + ToMsg = f >> d.ToMsg } + | SubModelWinData d -> + SubModelWinData + { GetState = f >> d.GetState + CreateViewModel = d.CreateViewModel + UpdateViewModel = d.UpdateViewModel + ToMsg = f >> d.ToMsg + GetWindow = f >> d.GetWindow + IsModal = d.IsModal + OnCloseRequested = f >> d.OnCloseRequested } + | SubModelSeqUnkeyedData d -> + SubModelSeqUnkeyedData + { GetModels = f >> d.GetModels + CreateViewModel = d.CreateViewModel + CreateCollection = d.CreateCollection + UpdateViewModel = d.UpdateViewModel + ToMsg = f >> d.ToMsg } + | SubModelSeqKeyedData d -> + SubModelSeqKeyedData + { GetSubModels = f >> d.GetSubModels + CreateViewModel = d.CreateViewModel + CreateCollection = d.CreateCollection + UpdateViewModel = d.UpdateViewModel + ToMsg = f >> d.ToMsg + BmToId = d.BmToId + VmToId = d.VmToId } + | SubModelSelectedItemData d -> + SubModelSelectedItemData + { Get = f >> d.Get + Set = binaryHelper d.Set + SubModelSeqBindingName = d.SubModelSeqBindingName } + + let rec recursiveCase = + function + | BaseBindingData d -> d |> baseCase |> BaseBindingData + | CachingData d -> d |> recursiveCase |> CachingData + | ValidationData d -> + ValidationData + { BindingData = recursiveCase d.BindingData + Validate = f >> d.Validate } + | LazyData d -> + LazyData + { BindingData = d.BindingData + Get = f >> d.Get + Set = binaryHelper d.Set + Equals = d.Equals } + | AlterMsgStreamData d -> + AlterMsgStreamData + { BindingData = d.BindingData + AlterMsgStream = d.AlterMsgStream + Get = f >> d.Get + Set = binaryHelper d.Set } + + recursiveCase + + let mapMsgWithModel (f: 'a -> 'model -> 'b) = + let baseCase = + function + | OneWayData d -> d |> OneWayData + | OneWayToSourceData d -> OneWayToSourceData { Set = fun v m -> f (d.Set v m) m } + | OneWaySeqData d -> d |> OneWaySeqData + | TwoWayData d -> + TwoWayData + { Get = d.Get + Set = fun v m -> f (d.Set v m) m } + | CmdData d -> + CmdData + { Exec = fun p m -> d.Exec p m |> ValueOption.map (fun msg -> f msg m) + CanExec = fun p m -> d.CanExec p m + AutoRequery = d.AutoRequery } + | SubModelData d -> + SubModelData + { GetModel = d.GetModel + CreateViewModel = d.CreateViewModel + UpdateViewModel = d.UpdateViewModel + ToMsg = fun m bMsg -> f (d.ToMsg m bMsg) m } + | SubModelWinData d -> + SubModelWinData + { GetState = d.GetState + CreateViewModel = d.CreateViewModel + UpdateViewModel = d.UpdateViewModel + ToMsg = fun m bMsg -> f (d.ToMsg m bMsg) m + GetWindow = fun m dispatch -> d.GetWindow m (fun msg -> f msg m |> dispatch) + IsModal = d.IsModal + OnCloseRequested = fun m -> m |> d.OnCloseRequested |> ValueOption.map (fun msg -> f msg m) } + | SubModelSeqUnkeyedData d -> + SubModelSeqUnkeyedData + { GetModels = d.GetModels + CreateViewModel = d.CreateViewModel + CreateCollection = d.CreateCollection + UpdateViewModel = d.UpdateViewModel + ToMsg = fun m bMsg -> f (d.ToMsg m bMsg) m } + | SubModelSeqKeyedData d -> + SubModelSeqKeyedData + { GetSubModels = d.GetSubModels + CreateViewModel = d.CreateViewModel + CreateCollection = d.CreateCollection + UpdateViewModel = d.UpdateViewModel + ToMsg = fun m bMsg -> f (d.ToMsg m bMsg) m + BmToId = d.BmToId + VmToId = d.VmToId } + | SubModelSelectedItemData d -> + SubModelSelectedItemData + { Get = d.Get + Set = fun v m -> f (d.Set v m) m + SubModelSeqBindingName = d.SubModelSeqBindingName } + + let rec recursiveCase = + function + | BaseBindingData d -> d |> baseCase |> BaseBindingData + | CachingData d -> d |> recursiveCase |> CachingData + | ValidationData d -> + ValidationData + { BindingData = recursiveCase d.BindingData + Validate = d.Validate } + | LazyData d -> + LazyData + { BindingData = d.BindingData + Get = d.Get + Set = fun a m -> f (d.Set a m) m + Equals = d.Equals } + | AlterMsgStreamData d -> + AlterMsgStreamData + { BindingData = d.BindingData + Get = d.Get + Set = fun a m -> f (d.Set a m) m + AlterMsgStream = d.AlterMsgStream } + + recursiveCase + + let mapMsg f = mapMsgWithModel (fun a _ -> f a) + + let setMsgWithModel f = mapMsgWithModel (fun _ m -> f m) + let setMsg msg = mapMsg (fun _ -> msg) + + let addCaching b = b |> CachingData + + let addValidation validate b = + { BindingData = b; Validate = validate } |> ValidationData + + let addLazy (equals: 'model -> 'model -> bool) b = + { BindingData = b |> mapModel unbox |> mapMsg box + Get = box + Set = fun (dMsg: obj) _ -> unbox dMsg + Equals = fun m1 m2 -> equals (unbox m1) (unbox m2) } + |> LazyData + + let alterMsgStream + (alteration: ('dispatchMsg -> unit) -> 'bindingMsg -> unit) + (b: BindingData<'bindingModel, 'bindingMsg, 't>) + : BindingData<'model, 'msg, 't> = + { BindingData = b |> mapModel unbox |> mapMsg box + Get = box + Set = fun (dMsg: obj) _ -> unbox dMsg + AlterMsgStream = + fun (f: obj -> unit) -> + let f' = box >> f + let g = alteration f' + unbox >> g } + |> AlterMsgStreamData + + let addSticky (predicate: 'model -> bool) (binding: BindingData<'model, 'msg, 't>) = + let mutable stickyModel = None + + let f newModel = + if predicate newModel then + stickyModel <- Some newModel + newModel + else + stickyModel |> Option.defaultValue newModel + + binding |> mapModel f + + + module Option = + + let box ma = ma |> Option.map box |> Option.toObj + let unbox obj = obj |> Option.ofObj |> Option.map unbox + + module ValueOption = + + let box ma = + ma |> ValueOption.map box |> ValueOption.toObj + + let unbox obj = + obj |> ValueOption.ofObj |> ValueOption.map unbox + + + module OneWay = + + let id<'a, 'msg> : BindingData<'a, 'msg, 'a> = + { Get = id } |> OneWayData |> BaseBindingData + + let private mapFunctions mGet (d: OneWayData<'model, 'a>) = { d with Get = mGet d.Get } + + let measureFunctions mGet = mapFunctions (mGet "get") + + + module OneWayToSource = + + let id<'model, 'a> : BindingData<'model, 'a, 'a> = + { OneWayToSourceData.Set = Func2.id1 } |> OneWayToSourceData |> BaseBindingData + + let private mapFunctions mSet (d: OneWayToSourceData<'model, 'msg, 'a>) = { d with Set = mSet d.Set } + + let measureFunctions mSet = mapFunctions (mSet "set") + + + module OneWaySeq = + + let mapMinorTypes + (outMapA: 'a -> 'a0) + (outMapId: 'id -> 'id0) + (inMapA: 'a0 -> 'a) + (d: OneWaySeqData<'model, 'a, 'aCollection, 'id>) + = + { Get = d.Get >> Seq.map outMapA + CreateCollection = Seq.map inMapA >> d.CreateCollection >> CollectionTarget.mapA outMapA inMapA + GetId = inMapA >> d.GetId >> outMapId + ItemEquals = fun a1 a2 -> d.ItemEquals (inMapA a1) (inMapA a2) } + + let boxMinorTypes d = d |> mapMinorTypes box box unbox + + let create itemEquals getId = + { Get = (fun x -> upcast x) + CreateCollection = ObservableCollection >> CollectionTarget.create + ItemEquals = itemEquals + GetId = getId } + |> boxMinorTypes + |> OneWaySeqData + |> BaseBindingData + + let private mapFunctions mGet mGetId mItemEquals (d: OneWaySeqData<'model, 'a, 'aCollection, 'id>) = + { d with + Get = mGet d.Get + GetId = mGetId d.GetId + ItemEquals = mItemEquals d.ItemEquals } + + let measureFunctions mGet mGetId mItemEquals = + mapFunctions (mGet "get") (mGetId "getId") (mItemEquals "itemEquals") + + + module TwoWay = + + let id<'a> : BindingData<'a, 'a, 'a> = + { TwoWayData.Get = id; Set = Func2.id1 } |> TwoWayData |> BaseBindingData + + let private mapFunctions mGet mSet (d: TwoWayData<'model, 'msg, 'a>) = + { d with + Get = mGet d.Get + Set = mSet d.Set } + + let measureFunctions mGet mSet = mapFunctions (mGet "get") (mSet "set") + + + module Cmd = + + let createWithParam exec canExec autoRequery : BindingData<'model, 'msg, ICommand> = + { Exec = exec + CanExec = canExec + AutoRequery = autoRequery } + |> CmdData + |> BaseBindingData + + let private mapFunctions mExec mCanExec (d: CmdData<'model, 'msg>) = + { d with + Exec = mExec d.Exec + CanExec = mCanExec d.CanExec } + + let measureFunctions mExec mCanExec = + mapFunctions (mExec "exec") (mCanExec "canExec") + + + module SubModelSelectedItem = + + let mapMinorTypes + (outMapId: 'id -> 'id0) + (inMapId: 'id0 -> 'id) + (d: SubModelSelectedItemData<'model, 'msg, 'id>) + = + { Get = d.Get >> ValueOption.map outMapId + Set = ValueOption.map inMapId >> d.Set + SubModelSeqBindingName = d.SubModelSeqBindingName } + + let boxMinorTypes d = d |> mapMinorTypes box unbox + + let create subModelSeqBindingName = + { Get = id + Set = Func2.id1 + SubModelSeqBindingName = subModelSeqBindingName } + |> boxMinorTypes + |> SubModelSelectedItemData + |> BaseBindingData + + let private mapFunctions mGet mSet (d: SubModelSelectedItemData<'model, 'msg, 'id>) = + { d with + Get = mGet d.Get + Set = mSet d.Set } + + let measureFunctions mGet mSet = mapFunctions (mGet "get") (mSet "set") + + + module SubModel = + + let mapMinorTypes + (outMapBindingModel: 'bindingModel -> 'bindingModel0) + (outMapBindingMsg: 'bindingMsg -> 'bindingMsg0) + (inMapBindingModel: 'bindingModel0 -> 'bindingModel) + (inMapBindingMsg: 'bindingMsg0 -> 'bindingMsg) + (d: SubModelData<'model, 'msg, 'bindingModel, 'bindingMsg, 'vm>) + = + { GetModel = d.GetModel >> ValueOption.map outMapBindingModel + CreateViewModel = + fun args -> d.CreateViewModel(args |> ViewModelArgs.map inMapBindingModel outMapBindingMsg) + UpdateViewModel = fun (vm, m) -> (vm, inMapBindingModel m) |> d.UpdateViewModel + ToMsg = fun m bMsg -> d.ToMsg m (inMapBindingMsg bMsg) } + + let boxMinorTypes d = d |> mapMinorTypes box box unbox unbox + + let create createViewModel updateViewModel = + { GetModel = id + CreateViewModel = createViewModel + UpdateViewModel = updateViewModel + ToMsg = Func2.id2 } + |> boxMinorTypes + |> SubModelData + |> BaseBindingData + + let private mapFunctions + mGetModel + mGetBindings + mUpdateViewModel + mToMsg + (d: SubModelData<'model, 'msg, 'bindingModel, 'bindingMsg, 'vm>) + : SubModelData<'model, 'msg, 'bindingModel, 'bindingMsg, 'vm> = + { d with + GetModel = mGetModel d.GetModel + CreateViewModel = mGetBindings d.CreateViewModel + UpdateViewModel = mUpdateViewModel d.UpdateViewModel + ToMsg = mToMsg d.ToMsg } + + let measureFunctions mGetModel mGetBindings mUpdateViewModel mToMsg = + mapFunctions + (mGetModel "getSubModel") // sic: "getModel" would be following the pattern + (mGetBindings "bindings") // sic: "getBindings" would be following the pattern + (mUpdateViewModel "updateViewModel") + (mToMsg "toMsg") + + + module SubModelWin = + + let mapMinorTypes + (outMapBindingModel: 'bindingModel -> 'bindingModel0) + (outMapBindingMsg: 'bindingMsg -> 'bindingMsg0) + (inMapBindingModel: 'bindingModel0 -> 'bindingModel) + (inMapBindingMsg: 'bindingMsg0 -> 'bindingMsg) + (d: SubModelWinData<'model, 'msg, 'bindingModel, 'bindingMsg, 'vm>) + = + { GetState = d.GetState >> WindowState.map outMapBindingModel + CreateViewModel = + fun args -> d.CreateViewModel(args |> ViewModelArgs.map inMapBindingModel outMapBindingMsg) + UpdateViewModel = fun (vm, m) -> d.UpdateViewModel(vm, inMapBindingModel m) + ToMsg = fun m bMsg -> d.ToMsg m (inMapBindingMsg bMsg) + GetWindow = d.GetWindow + IsModal = d.IsModal + OnCloseRequested = d.OnCloseRequested } + + let boxMinorTypes d = d |> mapMinorTypes box box unbox unbox + + let create getState createViewModel updateViewModel toMsg getWindow isModal onCloseRequested = + { GetState = getState + CreateViewModel = createViewModel + UpdateViewModel = updateViewModel + ToMsg = toMsg + GetWindow = getWindow + IsModal = isModal + OnCloseRequested = onCloseRequested } + |> boxMinorTypes + |> SubModelWinData + |> BaseBindingData + + let private mapFunctions + mGetState + mGetBindings + mUpdateViewModel + mToMsg + mGetWindow + mOnCloseRequested + (d: SubModelWinData<'model, 'msg, 'bindingModel, 'bindingMsg, 'vm>) + = + { d with + GetState = mGetState d.GetState + CreateViewModel = mGetBindings d.CreateViewModel + UpdateViewModel = mUpdateViewModel d.UpdateViewModel + ToMsg = mToMsg d.ToMsg + GetWindow = mGetWindow d.GetWindow + OnCloseRequested = mOnCloseRequested d.OnCloseRequested } + + let measureFunctions mGetState mGetBindings mUpdateViewModel mToMsg = + mapFunctions + (mGetState "getState") + (mGetBindings "bindings") // sic: "getBindings" would be following the pattern + (mUpdateViewModel "updateViewModel") + (mToMsg "toMsg") + id // sic: could measure GetWindow + id // sic: could measure OnCloseRequested + + + module SubModelSeqUnkeyed = + + let mapMinorTypes + (outMapBindingModel: 'bindingModel -> 'bindingModel0) + (outMapBindingMsg: 'bindingMsg -> 'bindingMsg0) + (outMapBindingViewModel: 'vm -> 'vm0) + (inMapBindingModel: 'bindingModel0 -> 'bindingModel) + (inMapBindingMsg: 'bindingMsg0 -> 'bindingMsg) + (inMapBindingViewModel: 'vm0 -> 'vm) + (d: SubModelSeqUnkeyedData<'model, 'msg, 'bindingModel, 'bindingMsg, 'vm, 'vmCollection>) + = + { GetModels = d.GetModels >> Seq.map outMapBindingModel + CreateViewModel = + fun args -> + d.CreateViewModel(args |> ViewModelArgs.map inMapBindingModel outMapBindingMsg) + |> outMapBindingViewModel + CreateCollection = + Seq.map inMapBindingViewModel + >> d.CreateCollection + >> CollectionTarget.mapA outMapBindingViewModel inMapBindingViewModel + UpdateViewModel = fun (vm, m) -> d.UpdateViewModel(inMapBindingViewModel vm, inMapBindingModel m) + ToMsg = fun m (idx, bMsg) -> d.ToMsg m (idx, (inMapBindingMsg bMsg)) } + + let boxMinorTypes d = + d |> mapMinorTypes box box box unbox unbox unbox + + let create createViewModel updateViewModel = + { GetModels = (fun x -> upcast x) + CreateViewModel = createViewModel + CreateCollection = ObservableCollection >> CollectionTarget.create + UpdateViewModel = updateViewModel + ToMsg = Func2.id2 } + |> boxMinorTypes + |> SubModelSeqUnkeyedData + |> BaseBindingData + + let private mapFunctions + mGetModels + mGetBindings + mCreateCollection + mUpdateViewModel + mToMsg + (d: SubModelSeqUnkeyedData<'model, 'msg, 'bindingModel, 'bindingMsg, 'vm, 'vmCollection>) + = + { d with + GetModels = mGetModels d.GetModels + CreateViewModel = mGetBindings d.CreateViewModel + CreateCollection = mCreateCollection d.CreateCollection + UpdateViewModel = mUpdateViewModel d.UpdateViewModel + ToMsg = mToMsg d.ToMsg } + + let measureFunctions mGetModels mGetBindings mCreateCollection mUpdateViewModel mToMsg = + mapFunctions + (mGetModels "getSubModels") // sic: "getModels" would follow the pattern + (mGetBindings "bindings") // sic: "getBindings" would follow the pattern + (mCreateCollection "createCollection") + (mUpdateViewModel "updateViewModel") + (mToMsg "toMsg") + + + module SubModelSeqKeyed = + + let mapMinorTypes + (outMapBindingModel: 'bindingModel -> 'bindingModel0) + (outMapBindingMsg: 'bindingMsg -> 'bindingMsg0) + (outMapBindingViewModel: 'vm -> 'vm0) + (outMapId: 'id -> 'id0) + (inMapBindingModel: 'bindingModel0 -> 'bindingModel) + (inMapBindingMsg: 'bindingMsg0 -> 'bindingMsg) + (inMapBindingViewModel: 'vm0 -> 'vm) + (inMapId: 'id0 -> 'id) + (d: SubModelSeqKeyedData<'model, 'msg, 'bindingModel, 'bindingMsg, 'vm, 'vmCollection, 'id>) + = + { GetSubModels = d.GetSubModels >> Seq.map outMapBindingModel + CreateViewModel = + fun args -> + d.CreateViewModel(args |> ViewModelArgs.map inMapBindingModel outMapBindingMsg) + |> outMapBindingViewModel + CreateCollection = + Seq.map inMapBindingViewModel + >> d.CreateCollection + >> CollectionTarget.mapA outMapBindingViewModel inMapBindingViewModel + UpdateViewModel = fun (vm, m) -> (inMapBindingViewModel vm, inMapBindingModel m) |> d.UpdateViewModel + ToMsg = fun m (id, bMsg) -> d.ToMsg m ((inMapId id), (inMapBindingMsg bMsg)) + BmToId = inMapBindingModel >> d.BmToId >> outMapId + VmToId = fun vm -> vm |> inMapBindingViewModel |> d.VmToId |> outMapId } + + let boxMinorTypes d = + d |> mapMinorTypes box box box box unbox unbox unbox unbox + + let create createViewModel updateViewModel bmToId vmToId = + { GetSubModels = (fun x -> upcast x) + CreateViewModel = createViewModel + CreateCollection = ObservableCollection >> CollectionTarget.create + UpdateViewModel = updateViewModel + ToMsg = Func2.id2 + BmToId = bmToId + VmToId = vmToId } + |> boxMinorTypes + |> SubModelSeqKeyedData + |> BaseBindingData + + let private mapFunctions + mGetSubModels + mGetBindings + mCreateCollection + mUpdateViewModel + mToMsg + mGetId + mGetVmId + (d: SubModelSeqKeyedData<'model, 'msg, 'bindingModel, 'bindingMsg, 'vm, 'vmCollection, 'id>) + = + { d with + GetSubModels = mGetSubModels d.GetSubModels + CreateViewModel = mGetBindings d.CreateViewModel + CreateCollection = mCreateCollection d.CreateCollection + UpdateViewModel = mUpdateViewModel d.UpdateViewModel + ToMsg = mToMsg d.ToMsg + BmToId = mGetId d.BmToId + VmToId = mGetVmId d.VmToId } + + let measureFunctions mGetSubModels mGetBindings mCreateCollection mUpdateViewModel mToMsg mGetId mGetVmId = + mapFunctions + (mGetSubModels "getSubModels") + (mGetBindings "getBindings") + (mCreateCollection "createCollection") + (mUpdateViewModel "updateViewModel") + (mToMsg "toMsg") + (mGetId "getId") + (mGetVmId "getVmId") + + + module Validation = + + let private mapFunctions mValidate (d: ValidationData<'model, 'msg, 't>) = + { d with + Validate = mValidate d.Validate } + + let measureFunctions mValidate = mapFunctions (mValidate "validate") + + module Lazy = + + let private mapFunctions mGet mSet mEquals (d: LazyData<'model, 'msg, 'bindingModel, 'bindingMsg, 't>) = + { d with + Get = mGet d.Get + Set = mSet d.Set + Equals = mEquals d.Equals } + + let measureFunctions mGet mSet mEquals = + mapFunctions (mGet "get") (mSet "set") (mEquals "equals") \ No newline at end of file diff --git a/src/Elmish.WPF/BindingVmHelpers.fs b/src/Elmish.WPF/BindingVmHelpers.fs index b05517c6..e9d46550 100644 --- a/src/Elmish.WPF/BindingVmHelpers.fs +++ b/src/Elmish.WPF/BindingVmHelpers.fs @@ -8,54 +8,59 @@ open Elmish type UpdateData = - | ErrorsChanged of string - | PropertyChanged of string - | CanExecuteChanged of Command + | ErrorsChanged of string + | PropertyChanged of string + | CanExecuteChanged of Command module UpdateData = - let isPropertyChanged = function PropertyChanged _ -> true | _ -> false + let isPropertyChanged = + function + | PropertyChanged _ -> true + | _ -> false type GetErrorSubModelSelectedItem = - { NameChain: string - SubModelSeqBindingName: string - Id: string } + { NameChain: string + SubModelSeqBindingName: string + Id: string } [] type GetError = - | OneWayToSource - | SubModelSelectedItem of GetErrorSubModelSelectedItem - | ToNullError of ValueOption.ToNullError + | OneWayToSource + | SubModelSelectedItem of GetErrorSubModelSelectedItem + | ToNullError of ValueOption.ToNullError module Helpers2 = - let showNewWindow - (winRef: WeakReference) - (getWindow: 'model -> Dispatch<'msg> -> Window) - (isDialog: bool) - (onCloseRequested: 'model -> 'msg voption) - (preventClose: bool ref) - dataContext - (initialVisibility: Visibility) - (getCurrentModel: unit -> 'model) - (dispatch: 'msg -> unit) = - let win = getWindow (getCurrentModel ()) dispatch - winRef.SetTarget win - (* + let showNewWindow + (winRef: WeakReference) + (getWindow: 'model -> Dispatch<'msg> -> Window) + (isDialog: bool) + (onCloseRequested: 'model -> 'msg voption) + (preventClose: bool ref) + dataContext + (initialVisibility: Visibility) + (getCurrentModel: unit -> 'model) + (dispatch: 'msg -> unit) + = + let win = getWindow (getCurrentModel ()) dispatch + winRef.SetTarget win + (* * A different thread might own this Window, so must use its Dispatcher. * Invoking asynchronously since ShowDialog is a blocking call. Otherwise, * invoking ShowDialog synchronously blocks the Elmish dispatch loop. *) - win.Dispatcher.InvokeAsync(fun () -> - win.DataContext <- dataContext - win.Closing.Add(fun ev -> - ev.Cancel <- preventClose.Value - getCurrentModel () |> onCloseRequested |> ValueOption.iter dispatch - ) - if isDialog then - win.ShowDialog () |> ignore - else - (* + win.Dispatcher.InvokeAsync(fun () -> + win.DataContext <- dataContext + + win.Closing.Add(fun ev -> + ev.Cancel <- preventClose.Value + getCurrentModel () |> onCloseRequested |> ValueOption.iter dispatch) + + if isDialog then + win.ShowDialog() |> ignore + else + (* * Calling Show achieves the same end result as setting Visibility * property of the Window object to Visible. However, there is a * difference between the two from a timing perspective. @@ -67,759 +72,870 @@ module Helpers2 = * returns immediately * https://docs.microsoft.com/en-us/dotnet/api/system.windows.window.show *) - win.Visibility <- initialVisibility - ) |> ignore - - let measure (logPerformance: ILogger) (logLevel: LogLevel) (performanceLogThresholdMs: int) (name: string) (nameChain: string) (callName: string) f = - if not <| logPerformance.IsEnabled(logLevel) then f - else - fun a -> - let sw = System.Diagnostics.Stopwatch.StartNew () - let b = f a - sw.Stop () - if sw.ElapsedMilliseconds >= int64 performanceLogThresholdMs then - logPerformance.Log(logLevel, "[{BindingNameChain}] {CallName} ({Elapsed}ms): {MeasureName}", nameChain, callName, sw.ElapsedMilliseconds, name) - b - - let measure2 (logPerformance: ILogger) (logLevel: LogLevel) performanceLogThresholdMs name nameChain callName f = - if not <| logPerformance.IsEnabled(logLevel) - then f - else fun a -> measure logPerformance logLevel performanceLogThresholdMs name nameChain callName (f a) - - -type OneWayBinding<'model, 'a> = { - OneWayData: OneWayData<'model, 'a> -} - -type OneWayToSourceBinding<'model, 'a> = { - Set: 'a -> 'model -> unit -} - -type OneWaySeqBinding<'model, 'a, 'aCollection, 'id when 'id : equality> = { - OneWaySeqData: OneWaySeqData<'model, 'a, 'aCollection, 'id> - Values: CollectionTarget<'a, 'aCollection> -} - -type TwoWayBinding<'model, 'a> = { - Get: 'model -> 'a - Set: 'a -> 'model -> unit -} - -type SubModelBinding<'model, 'msg, 'bindingModel, 'bindingMsg, 'vm> = { - SubModelData: SubModelData<'model, 'msg, 'bindingModel, 'bindingMsg, 'vm> - Dispatch: 'msg -> unit - GetVm: unit -> 'vm voption - SetVm: 'vm voption -> unit - GetCurrentModel: unit -> 'model -} - -type SubModelWinBinding<'model, 'msg, 'bindingModel, 'bindingMsg, 'vm> = { - SubModelWinData: SubModelWinData<'model, 'msg, 'bindingModel, 'bindingMsg, 'vm> - Dispatch: 'msg -> unit - WinRef: WeakReference - PreventClose: bool ref - GetVmWinState: unit -> WindowState<'vm> - SetVmWinState: WindowState<'vm> -> unit - GetCurrentModel: unit -> 'model -} - -type SubModelSeqUnkeyedBinding<'model, 'msg, 'bindingModel, 'bindingMsg, 'vm, 'vmCollection> = { - SubModelSeqUnkeyedData: SubModelSeqUnkeyedData<'model, 'msg, 'bindingModel, 'bindingMsg, 'vm, 'vmCollection> - Dispatch: 'msg -> unit - Vms: CollectionTarget<'vm, 'vmCollection> - GetCurrentModel: unit -> 'model -} - -type SubModelSeqKeyedBinding<'model, 'msg, 'bindingModel, 'bindingMsg, 'vm, 'vmCollection, 'id when 'id : equality> = - { SubModelSeqKeyedData: SubModelSeqKeyedData<'model, 'msg, 'bindingModel, 'bindingMsg, 'vm, 'vmCollection, 'id> - Dispatch: 'msg -> unit - Vms: CollectionTarget<'vm, 'vmCollection> - GetCurrentModel: unit -> 'model - } - - member b.FromId(id: 'id) = - b.Vms.Enumerate () - |> Seq.tryFind (fun vm -> vm |> b.SubModelSeqKeyedData.VmToId |> (=) id) - -type SelectedItemBinding<'bindingModel, 'bindingMsg, 'vm, 'id> = { - FromId: 'id -> 'vm option - VmToId: 'vm -> 'id -} + win.Visibility <- initialVisibility) + |> ignore + + let measure + (logPerformance: ILogger) + (logLevel: LogLevel) + (performanceLogThresholdMs: int) + (name: string) + (nameChain: string) + (callName: string) + f + = + if not <| logPerformance.IsEnabled(logLevel) then + f + else + fun a -> + let sw = System.Diagnostics.Stopwatch.StartNew() + let b = f a + sw.Stop() + + if sw.ElapsedMilliseconds >= int64 performanceLogThresholdMs then + logPerformance.Log( + logLevel, + "[{BindingNameChain}] {CallName} ({Elapsed}ms): {MeasureName}", + nameChain, + callName, + sw.ElapsedMilliseconds, + name + ) + + b + + let measure2 (logPerformance: ILogger) (logLevel: LogLevel) performanceLogThresholdMs name nameChain callName f = + if not <| logPerformance.IsEnabled(logLevel) then + f + else + fun a -> measure logPerformance logLevel performanceLogThresholdMs name nameChain callName (f a) + + +type OneWayBinding<'model, 'a> = { OneWayData: OneWayData<'model, 'a> } + +type OneWayToSourceBinding<'model, 'a> = { Set: 'a -> 'model -> unit } + +type OneWaySeqBinding<'model, 'a, 'aCollection, 'id when 'id: equality> = + { OneWaySeqData: OneWaySeqData<'model, 'a, 'aCollection, 'id> + Values: CollectionTarget<'a, 'aCollection> } + +type TwoWayBinding<'model, 'a> = + { Get: 'model -> 'a + Set: 'a -> 'model -> unit } + +type SubModelBinding<'model, 'msg, 'bindingModel, 'bindingMsg, 'vm> = + { SubModelData: SubModelData<'model, 'msg, 'bindingModel, 'bindingMsg, 'vm> + Dispatch: 'msg -> unit + GetVm: unit -> 'vm voption + SetVm: 'vm voption -> unit + GetCurrentModel: unit -> 'model } + +type SubModelWinBinding<'model, 'msg, 'bindingModel, 'bindingMsg, 'vm> = + { SubModelWinData: SubModelWinData<'model, 'msg, 'bindingModel, 'bindingMsg, 'vm> + Dispatch: 'msg -> unit + WinRef: WeakReference + PreventClose: bool ref + GetVmWinState: unit -> WindowState<'vm> + SetVmWinState: WindowState<'vm> -> unit + GetCurrentModel: unit -> 'model } + +type SubModelSeqUnkeyedBinding<'model, 'msg, 'bindingModel, 'bindingMsg, 'vm, 'vmCollection> = + { SubModelSeqUnkeyedData: SubModelSeqUnkeyedData<'model, 'msg, 'bindingModel, 'bindingMsg, 'vm, 'vmCollection> + Dispatch: 'msg -> unit + Vms: CollectionTarget<'vm, 'vmCollection> + GetCurrentModel: unit -> 'model } + +type SubModelSeqKeyedBinding<'model, 'msg, 'bindingModel, 'bindingMsg, 'vm, 'vmCollection, 'id when 'id: equality> = + { SubModelSeqKeyedData: SubModelSeqKeyedData<'model, 'msg, 'bindingModel, 'bindingMsg, 'vm, 'vmCollection, 'id> + Dispatch: 'msg -> unit + Vms: CollectionTarget<'vm, 'vmCollection> + GetCurrentModel: unit -> 'model } + + member b.FromId(id: 'id) = + b.Vms.Enumerate() + |> Seq.tryFind (fun vm -> vm |> b.SubModelSeqKeyedData.VmToId |> (=) id) + +type SelectedItemBinding<'bindingModel, 'bindingMsg, 'vm, 'id> = + { FromId: 'id -> 'vm option + VmToId: 'vm -> 'id } type SubModelSelectedItemBinding<'model, 'msg, 'bindingModel, 'bindingMsg, 'vm, 'id> = - { Get: 'model -> 'id voption - Set: 'id voption -> 'model -> unit - SubModelSeqBindingName: string - SelectedItemBinding: SelectedItemBinding<'bindingModel, 'bindingMsg, 'vm, 'id> - } + { Get: 'model -> 'id voption + Set: 'id voption -> 'model -> unit + SubModelSeqBindingName: string + SelectedItemBinding: SelectedItemBinding<'bindingModel, 'bindingMsg, 'vm, 'id> } - member b.TypedGet(model: 'model) = - b.Get model |> ValueOption.map (fun selectedId -> selectedId, b.SelectedItemBinding.FromId selectedId) + member b.TypedGet(model: 'model) = + b.Get model + |> ValueOption.map (fun selectedId -> selectedId, b.SelectedItemBinding.FromId selectedId) - member b.TypedSet(model: 'model, vm: 'vm voption) = - let id = vm |> ValueOption.map b.SelectedItemBinding.VmToId - b.Set id model + member b.TypedSet(model: 'model, vm: 'vm voption) = + let id = vm |> ValueOption.map b.SelectedItemBinding.VmToId + b.Set id model type BaseVmBinding<'model, 'msg, 't> = - | OneWay of OneWayBinding<'model, 't> - | OneWayToSource of OneWayToSourceBinding<'model, 't> - | OneWaySeq of OneWaySeqBinding<'model, obj, 't, obj> - | TwoWay of TwoWayBinding<'model, 't> - | Cmd of cmd: Command - | SubModel of SubModelBinding<'model, 'msg, obj, obj, 't> - | SubModelWin of SubModelWinBinding<'model, 'msg, obj, obj, 't> - | SubModelSeqUnkeyed of SubModelSeqUnkeyedBinding<'model, 'msg, obj, obj, obj, 't> - | SubModelSeqKeyed of SubModelSeqKeyedBinding<'model, 'msg, obj, obj, obj, 't, obj> - | SubModelSelectedItem of SubModelSelectedItemBinding<'model, 'msg, obj, obj, 't, obj> - - -type CachedBinding<'model, 'msg, 't> = { - Binding: VmBinding<'model, 'msg, 't> - GetCache: unit -> 't option - SetCache: 't option -> unit -} - -and ValidationBinding<'model, 'msg, 't> = { - Binding: VmBinding<'model, 'msg, 't> - Validate: 'model -> string list - Errors: string list ref -} - -and LazyBinding<'model, 'msg, 'bindingModel, 'bindingMsg, 't> = { - Binding: VmBinding<'bindingModel, 'bindingMsg, 't> - Get: 'model -> 'bindingModel - Equals: 'bindingModel -> 'bindingModel -> bool -} - -and AlterMsgStreamBinding<'model, 'bindingModel, 'bindingMsg, 't> = { - Binding: VmBinding<'bindingModel, 'bindingMsg, 't> - Get: 'model -> 'bindingModel -} + | OneWay of OneWayBinding<'model, 't> + | OneWayToSource of OneWayToSourceBinding<'model, 't> + | OneWaySeq of OneWaySeqBinding<'model, obj, 't, obj> + | TwoWay of TwoWayBinding<'model, 't> + | Cmd of cmd: Command + | SubModel of SubModelBinding<'model, 'msg, obj, obj, 't> + | SubModelWin of SubModelWinBinding<'model, 'msg, obj, obj, 't> + | SubModelSeqUnkeyed of SubModelSeqUnkeyedBinding<'model, 'msg, obj, obj, obj, 't> + | SubModelSeqKeyed of SubModelSeqKeyedBinding<'model, 'msg, obj, obj, obj, 't, obj> + | SubModelSelectedItem of SubModelSelectedItemBinding<'model, 'msg, obj, obj, 't, obj> + + +type CachedBinding<'model, 'msg, 't> = + { Binding: VmBinding<'model, 'msg, 't> + GetCache: unit -> 't option + SetCache: 't option -> unit } + +and ValidationBinding<'model, 'msg, 't> = + { Binding: VmBinding<'model, 'msg, 't> + Validate: 'model -> string list + Errors: string list ref } + +and LazyBinding<'model, 'msg, 'bindingModel, 'bindingMsg, 't> = + { Binding: VmBinding<'bindingModel, 'bindingMsg, 't> + Get: 'model -> 'bindingModel + Equals: 'bindingModel -> 'bindingModel -> bool } + +and AlterMsgStreamBinding<'model, 'bindingModel, 'bindingMsg, 't> = + { Binding: VmBinding<'bindingModel, 'bindingMsg, 't> + Get: 'model -> 'bindingModel } /// Represents all necessary data used in an active binding. and VmBinding<'model, 'msg, 't> = - | BaseVmBinding of BaseVmBinding<'model, 'msg, 't> - | Cached of CachedBinding<'model, 'msg, 't> - | Validatation of ValidationBinding<'model, 'msg, 't> - | Lazy of LazyBinding<'model, 'msg, obj, obj, 't> - | AlterMsgStream of AlterMsgStreamBinding<'model, obj, obj, 't> + | BaseVmBinding of BaseVmBinding<'model, 'msg, 't> + | Cached of CachedBinding<'model, 'msg, 't> + | Validatation of ValidationBinding<'model, 'msg, 't> + | Lazy of LazyBinding<'model, 'msg, obj, obj, 't> + | AlterMsgStream of AlterMsgStreamBinding<'model, obj, obj, 't> + - with + member this.AddCaching = + let mutable cache = None in + + Cached + { Binding = this + GetCache = (fun () -> cache) + SetCache = fun c -> cache <- c } - member this.AddCaching = let mutable cache = None in Cached { Binding = this; GetCache = (fun () -> cache); SetCache = fun c -> cache <- c } member this.AddValidation currentModel validate = - { Binding = this - Validate = validate - Errors = currentModel |> validate |> ref } - |> Validatation + { Binding = this + Validate = validate + Errors = currentModel |> validate |> ref } + |> Validatation module internal MapOutputType = - let private baseCase (fOut: 'a -> 'b) (fIn: 'b -> 'a) (data: BaseVmBinding<'model, 'msg, 'a>) : BaseVmBinding<'model, 'msg, 'b> = - match data with - | OneWay b -> OneWay { OneWayData = { Get = b.OneWayData.Get >> fOut } } - | OneWayToSource b -> OneWayToSource { Set = fIn >> b.Set } - | Cmd b -> Cmd b - | TwoWay b -> TwoWay { Get = b.Get >> fOut; Set = fIn >> b.Set } - | OneWaySeq b -> OneWaySeq { - OneWaySeqData = { - Get = b.OneWaySeqData.Get - CreateCollection = b.OneWaySeqData.CreateCollection >> CollectionTarget.mapCollection fOut - GetId = b.OneWaySeqData.GetId - ItemEquals = b.OneWaySeqData.ItemEquals } - Values = b.Values |> CollectionTarget.mapCollection fOut } - | SubModel b -> SubModel { - SubModelData = { - GetModel = b.SubModelData.GetModel - CreateViewModel = b.SubModelData.CreateViewModel >> fOut - UpdateViewModel = (fun (vm,m) -> b.SubModelData.UpdateViewModel (fIn vm, m)) - ToMsg = b.SubModelData.ToMsg } - Dispatch = b.Dispatch - GetVm = b.GetVm >> ValueOption.map fOut - SetVm = ValueOption.map fIn >> b.SetVm - GetCurrentModel = b.GetCurrentModel } - | SubModelWin b -> SubModelWin { - SubModelWinData = { - GetState = b.SubModelWinData.GetState - CreateViewModel = b.SubModelWinData.CreateViewModel >> fOut - UpdateViewModel = (fun (vm,m) -> b.SubModelWinData.UpdateViewModel (fIn vm, m)) - ToMsg = b.SubModelWinData.ToMsg - GetWindow = b.SubModelWinData.GetWindow - IsModal = b.SubModelWinData.IsModal - OnCloseRequested = b.SubModelWinData.OnCloseRequested } - Dispatch = b.Dispatch - WinRef = b.WinRef - PreventClose = b.PreventClose - GetVmWinState = b.GetVmWinState >> WindowState.map fOut - SetVmWinState = WindowState.map fIn >> b.SetVmWinState - GetCurrentModel = b.GetCurrentModel } - | SubModelSeqUnkeyed b -> SubModelSeqUnkeyed { - SubModelSeqUnkeyedData = { - GetModels = b.SubModelSeqUnkeyedData.GetModels - CreateViewModel = b.SubModelSeqUnkeyedData.CreateViewModel - CreateCollection = b.SubModelSeqUnkeyedData.CreateCollection >> CollectionTarget.mapCollection fOut - UpdateViewModel = b.SubModelSeqUnkeyedData.UpdateViewModel - ToMsg = b.SubModelSeqUnkeyedData.ToMsg } - Dispatch = b.Dispatch - Vms = b.Vms |> CollectionTarget.mapCollection fOut - GetCurrentModel = b.GetCurrentModel } - | SubModelSeqKeyed b -> SubModelSeqKeyed { - SubModelSeqKeyedData = { - GetSubModels = b.SubModelSeqKeyedData.GetSubModels - CreateViewModel = b.SubModelSeqKeyedData.CreateViewModel - CreateCollection = b.SubModelSeqKeyedData.CreateCollection >> CollectionTarget.mapCollection fOut - UpdateViewModel = b.SubModelSeqKeyedData.UpdateViewModel - ToMsg = b.SubModelSeqKeyedData.ToMsg - BmToId = b.SubModelSeqKeyedData.BmToId - VmToId = b.SubModelSeqKeyedData.VmToId } - Dispatch = b.Dispatch - Vms = b.Vms |> CollectionTarget.mapCollection fOut - GetCurrentModel = b.GetCurrentModel } - | SubModelSelectedItem b -> SubModelSelectedItem { - Get = b.Get - Set = b.Set - SubModelSeqBindingName = b.SubModelSeqBindingName - SelectedItemBinding = { - VmToId = fIn >> b.SelectedItemBinding.VmToId - FromId = b.SelectedItemBinding.FromId >> Option.map fOut } } - - let rec private recursiveCase<'model, 'msg, 'a, 'b> (fOut: 'a -> 'b) (fIn: 'b -> 'a) (data: VmBinding<'model, 'msg, 'a>) : VmBinding<'model, 'msg, 'b> = - match data with - | BaseVmBinding b -> baseCase fOut fIn b |> BaseVmBinding - | Cached b -> Cached { - Binding = recursiveCase fOut fIn b.Binding - GetCache = b.GetCache >> Option.map fOut - SetCache = Option.map fIn >> b.SetCache - } - | AlterMsgStream b -> AlterMsgStream { - Binding = recursiveCase fOut fIn b.Binding - Get = b.Get - } - | Lazy b -> Lazy { - Get = b.Get - Binding = recursiveCase fOut fIn b.Binding - Equals = b.Equals - } - | Validatation b -> Validatation { - Binding = recursiveCase fOut fIn b.Binding - Errors = b.Errors - Validate = b.Validate - } - - let boxVm b = recursiveCase box unbox b - let unboxVm b = recursiveCase unbox box b + let private baseCase + (fOut: 'a -> 'b) + (fIn: 'b -> 'a) + (data: BaseVmBinding<'model, 'msg, 'a>) + : BaseVmBinding<'model, 'msg, 'b> = + match data with + | OneWay b -> OneWay { OneWayData = { Get = b.OneWayData.Get >> fOut } } + | OneWayToSource b -> OneWayToSource { Set = fIn >> b.Set } + | Cmd b -> Cmd b + | TwoWay b -> + TwoWay + { Get = b.Get >> fOut + Set = fIn >> b.Set } + | OneWaySeq b -> + OneWaySeq + { OneWaySeqData = + { Get = b.OneWaySeqData.Get + CreateCollection = b.OneWaySeqData.CreateCollection >> CollectionTarget.mapCollection fOut + GetId = b.OneWaySeqData.GetId + ItemEquals = b.OneWaySeqData.ItemEquals } + Values = b.Values |> CollectionTarget.mapCollection fOut } + | SubModel b -> + SubModel + { SubModelData = + { GetModel = b.SubModelData.GetModel + CreateViewModel = b.SubModelData.CreateViewModel >> fOut + UpdateViewModel = (fun (vm, m) -> b.SubModelData.UpdateViewModel(fIn vm, m)) + ToMsg = b.SubModelData.ToMsg } + Dispatch = b.Dispatch + GetVm = b.GetVm >> ValueOption.map fOut + SetVm = ValueOption.map fIn >> b.SetVm + GetCurrentModel = b.GetCurrentModel } + | SubModelWin b -> + SubModelWin + { SubModelWinData = + { GetState = b.SubModelWinData.GetState + CreateViewModel = b.SubModelWinData.CreateViewModel >> fOut + UpdateViewModel = (fun (vm, m) -> b.SubModelWinData.UpdateViewModel(fIn vm, m)) + ToMsg = b.SubModelWinData.ToMsg + GetWindow = b.SubModelWinData.GetWindow + IsModal = b.SubModelWinData.IsModal + OnCloseRequested = b.SubModelWinData.OnCloseRequested } + Dispatch = b.Dispatch + WinRef = b.WinRef + PreventClose = b.PreventClose + GetVmWinState = b.GetVmWinState >> WindowState.map fOut + SetVmWinState = WindowState.map fIn >> b.SetVmWinState + GetCurrentModel = b.GetCurrentModel } + | SubModelSeqUnkeyed b -> + SubModelSeqUnkeyed + { SubModelSeqUnkeyedData = + { GetModels = b.SubModelSeqUnkeyedData.GetModels + CreateViewModel = b.SubModelSeqUnkeyedData.CreateViewModel + CreateCollection = + b.SubModelSeqUnkeyedData.CreateCollection >> CollectionTarget.mapCollection fOut + UpdateViewModel = b.SubModelSeqUnkeyedData.UpdateViewModel + ToMsg = b.SubModelSeqUnkeyedData.ToMsg } + Dispatch = b.Dispatch + Vms = b.Vms |> CollectionTarget.mapCollection fOut + GetCurrentModel = b.GetCurrentModel } + | SubModelSeqKeyed b -> + SubModelSeqKeyed + { SubModelSeqKeyedData = + { GetSubModels = b.SubModelSeqKeyedData.GetSubModels + CreateViewModel = b.SubModelSeqKeyedData.CreateViewModel + CreateCollection = b.SubModelSeqKeyedData.CreateCollection >> CollectionTarget.mapCollection fOut + UpdateViewModel = b.SubModelSeqKeyedData.UpdateViewModel + ToMsg = b.SubModelSeqKeyedData.ToMsg + BmToId = b.SubModelSeqKeyedData.BmToId + VmToId = b.SubModelSeqKeyedData.VmToId } + Dispatch = b.Dispatch + Vms = b.Vms |> CollectionTarget.mapCollection fOut + GetCurrentModel = b.GetCurrentModel } + | SubModelSelectedItem b -> + SubModelSelectedItem + { Get = b.Get + Set = b.Set + SubModelSeqBindingName = b.SubModelSeqBindingName + SelectedItemBinding = + { VmToId = fIn >> b.SelectedItemBinding.VmToId + FromId = b.SelectedItemBinding.FromId >> Option.map fOut } } + + let rec private recursiveCase<'model, 'msg, 'a, 'b> + (fOut: 'a -> 'b) + (fIn: 'b -> 'a) + (data: VmBinding<'model, 'msg, 'a>) + : VmBinding<'model, 'msg, 'b> = + match data with + | BaseVmBinding b -> baseCase fOut fIn b |> BaseVmBinding + | Cached b -> + Cached + { Binding = recursiveCase fOut fIn b.Binding + GetCache = b.GetCache >> Option.map fOut + SetCache = Option.map fIn >> b.SetCache } + | AlterMsgStream b -> + AlterMsgStream + { Binding = recursiveCase fOut fIn b.Binding + Get = b.Get } + | Lazy b -> + Lazy + { Get = b.Get + Binding = recursiveCase fOut fIn b.Binding + Equals = b.Equals } + | Validatation b -> + Validatation + { Binding = recursiveCase fOut fIn b.Binding + Errors = b.Errors + Validate = b.Validate } + + let boxVm b = recursiveCase box unbox b + let unboxVm b = recursiveCase unbox box b type SubModelSelectedItemLast() = - member _.Base(data: BaseBindingData<'model, 'msg, obj>) : int = - match data with - | SubModelSelectedItemData _ -> 1 - | _ -> 0 + member _.Base(data: BaseBindingData<'model, 'msg, obj>) : int = + match data with + | SubModelSelectedItemData _ -> 1 + | _ -> 0 - member this.Recursive<'model, 'msg>(data: BindingData<'model, 'msg, obj>) : int = - match data with - | BaseBindingData d -> this.Base d - | CachingData d -> this.Recursive d - | ValidationData d -> this.Recursive d.BindingData - | LazyData d -> this.Recursive d.BindingData - | AlterMsgStreamData d -> this.Recursive d.BindingData + member this.Recursive<'model, 'msg>(data: BindingData<'model, 'msg, obj>) : int = + match data with + | BaseBindingData d -> this.Base d + | CachingData d -> this.Recursive d + | ValidationData d -> this.Recursive d.BindingData + | LazyData d -> this.Recursive d.BindingData + | AlterMsgStreamData d -> this.Recursive d.BindingData - member this.CompareBindingDatas() : BindingData<'model, 'msg, obj> -> BindingData<'model, 'msg, obj> -> int = - fun a b -> this.Recursive(a) - this.Recursive(b) + member this.CompareBindingDatas() : BindingData<'model, 'msg, obj> -> BindingData<'model, 'msg, obj> -> int = + fun a b -> this.Recursive(a) - this.Recursive(b) type FirstValidationErrors() = - member this.Recursive<'model, 'msg, 't> - (binding: VmBinding<'model, 'msg, 't>) - : string list ref option = - match binding with - | BaseVmBinding _ -> None - | Cached b -> this.Recursive b.Binding - | Lazy b -> this.Recursive b.Binding - | AlterMsgStream b -> this.Recursive b.Binding - | Validatation b -> b.Errors |> Some // TODO: what if there is more than one validation effect? + member this.Recursive<'model, 'msg, 't>(binding: VmBinding<'model, 'msg, 't>) : string list ref option = + match binding with + | BaseVmBinding _ -> None + | Cached b -> this.Recursive b.Binding + | Lazy b -> this.Recursive b.Binding + | AlterMsgStream b -> this.Recursive b.Binding + | Validatation b -> b.Errors |> Some // TODO: what if there is more than one validation effect? type FuncsFromSubModelSeqKeyed() = - member _.Base(binding: BaseVmBinding<'model, 'msg, 't>) : SelectedItemBinding<'a, 'b, 'c, obj> option = - match binding with - | SubModelSeqKeyed b -> - { VmToId = box >> b.SubModelSeqKeyedData.VmToId - FromId = b.FromId >> Option.map unbox } - |> Some - | _ -> None - - member this.Recursive<'model, 'msg, 't> - (binding: VmBinding<'model, 'msg, 't>) - : SelectedItemBinding option = - match binding with - | BaseVmBinding b -> this.Base b - | Cached b -> this.Recursive b.Binding - | Validatation b -> this.Recursive b.Binding - | Lazy b -> this.Recursive b.Binding - | AlterMsgStream b -> this.Recursive b.Binding + member _.Base(binding: BaseVmBinding<'model, 'msg, 't>) : SelectedItemBinding<'a, 'b, 'c, obj> option = + match binding with + | SubModelSeqKeyed b -> + { VmToId = box >> b.SubModelSeqKeyedData.VmToId + FromId = b.FromId >> Option.map unbox } + |> Some + | _ -> None + + member this.Recursive<'model, 'msg, 't> + (binding: VmBinding<'model, 'msg, 't>) + : SelectedItemBinding option = + match binding with + | BaseVmBinding b -> this.Base b + | Cached b -> this.Recursive b.Binding + | Validatation b -> this.Recursive b.Binding + | Lazy b -> this.Recursive b.Binding + | AlterMsgStream b -> this.Recursive b.Binding type Initialize<'t> - (loggingArgs: LoggingViewModelArgs, - name: string, - getFunctionsForSubModelSelectedItem: string -> SelectedItemBinding option) = - - let { log = log - logPerformance = logPerformance - performanceLogThresholdMs = performanceLogThresholdMs - nameChain = nameChain } = - loggingArgs - - let measure x = x |> Helpers2.measure logPerformance LogLevel.Trace performanceLogThresholdMs name nameChain - let measure2 x = x |> Helpers2.measure2 logPerformance LogLevel.Trace performanceLogThresholdMs name nameChain - - member _.Base<'model, 'msg> - (initialModel: 'model, - dispatch: 'msg -> unit, - getCurrentModel: unit -> 'model, - binding: BaseBindingData<'model, 'msg, 't>) - : BaseVmBinding<'model, 'msg, 't> option = - match binding with - | OneWayData d -> - { OneWayData = d |> BindingData.OneWay.measureFunctions measure } - |> OneWay - |> Some - | OneWayToSourceData d -> - let d = d |> BindingData.OneWayToSource.measureFunctions measure - { Set = fun obj m -> d.Set obj m |> dispatch } - |> OneWayToSource - |> Some - | OneWaySeqData d -> - { OneWaySeqData = d |> BindingData.OneWaySeq.measureFunctions measure measure measure2 - Values = d.CreateCollection (initialModel |> d.Get) } - |> OneWaySeq - |> Some - | TwoWayData d -> - let d = d |> BindingData.TwoWay.measureFunctions measure measure - { Get = d.Get - Set = fun obj m -> d.Set obj m |> dispatch } - |> TwoWay - |> Some - | CmdData d -> - let d = d |> BindingData.Cmd.measureFunctions measure2 measure2 - let execute param = d.Exec param (getCurrentModel ()) |> ValueOption.iter dispatch - let canExecute param = d.CanExec param (getCurrentModel ()) - let cmd = Command(execute, canExecute) - if d.AutoRequery then - cmd.AddRequeryHandler () - cmd - |> Cmd - |> Some - | SubModelData d -> - let d = d |> BindingData.SubModel.measureFunctions measure measure measure measure2 - let toMsg = fun msg -> d.ToMsg (getCurrentModel ()) msg - let chain = LoggingViewModelArgs.getNameChainFor nameChain name - d.GetModel initialModel - |> ValueOption.map (fun m -> ViewModelArgs.create m (toMsg >> dispatch) chain loggingArgs) - |> ValueOption.map d.CreateViewModel - |> (fun vm -> let mutable vm = vm in { SubModelData = d - Dispatch = dispatch - GetVm = (fun () -> vm) - SetVm = fun nvm -> vm <- nvm - GetCurrentModel = getCurrentModel - }) - |> SubModel - |> Some - | SubModelWinData d -> - let d = d |> BindingData.SubModelWin.measureFunctions measure measure measure measure2 - let toMsg = fun msg -> d.ToMsg (getCurrentModel ()) msg - match d.GetState initialModel with - | WindowState.Closed -> - let mutable vmWinState = WindowState.Closed - { SubModelWinData = d - Dispatch = dispatch - WinRef = WeakReference<_>(null) - PreventClose = ref true - GetVmWinState = fun () -> vmWinState - SetVmWinState = fun vmState -> vmWinState <- vmState - GetCurrentModel = getCurrentModel - } - | WindowState.Hidden m -> - let chain = LoggingViewModelArgs.getNameChainFor nameChain name - let args = ViewModelArgs.create m (toMsg >> dispatch) chain loggingArgs - let vm = d.CreateViewModel args - let winRef = WeakReference<_>(null) - let preventClose = ref true - log.LogTrace("[{BindingNameChain}] Creating hidden window", chain) - Helpers2.showNewWindow winRef d.GetWindow d.IsModal d.OnCloseRequested preventClose vm Visibility.Hidden getCurrentModel dispatch - let mutable vmWinState = WindowState.Hidden vm - { SubModelWinData = d - Dispatch = dispatch - WinRef = winRef - PreventClose = preventClose - GetVmWinState = fun () -> vmWinState - SetVmWinState = fun vm -> vmWinState <- vm - GetCurrentModel = getCurrentModel - } - | WindowState.Visible m -> - let chain = LoggingViewModelArgs.getNameChainFor nameChain name - let args = ViewModelArgs.create m (toMsg >> dispatch) chain loggingArgs - let vm = d.CreateViewModel args - let winRef = WeakReference<_>(null) - let preventClose = ref true - log.LogTrace("[{BindingNameChain}] Creating visible window", chain) - Helpers2.showNewWindow winRef d.GetWindow d.IsModal d.OnCloseRequested preventClose vm Visibility.Visible getCurrentModel dispatch - let mutable vmWinState = WindowState.Visible vm - { SubModelWinData = d - Dispatch = dispatch - WinRef = winRef - PreventClose = preventClose - GetVmWinState = fun () -> vmWinState - SetVmWinState = fun vm -> vmWinState <- vm - GetCurrentModel = getCurrentModel - } - |> SubModelWin - |> Some - | SubModelSeqUnkeyedData d -> - let d = d |> BindingData.SubModelSeqUnkeyed.measureFunctions measure measure measure measure measure2 - let toMsg = fun msg -> d.ToMsg (getCurrentModel ()) msg - let vms = - d.GetModels initialModel - |> Seq.indexed - |> Seq.map (fun (idx, m) -> - let chain = LoggingViewModelArgs.getNameChainForItem nameChain name (idx |> string) - let args = ViewModelArgs.create m (fun msg -> toMsg (idx, msg) |> dispatch) chain loggingArgs - d.CreateViewModel args) - |> d.CreateCollection - { SubModelSeqUnkeyedData = d - Dispatch = dispatch - Vms = vms - GetCurrentModel = getCurrentModel - } - |> SubModelSeqUnkeyed - |> Some - | SubModelSeqKeyedData d -> - let d = d |> BindingData.SubModelSeqKeyed.measureFunctions measure measure measure measure measure2 measure measure - let toMsg = fun msg -> d.ToMsg (getCurrentModel ()) msg - let vms = - d.GetSubModels initialModel - |> Seq.map (fun m -> - let mId = d.BmToId m - let chain = LoggingViewModelArgs.getNameChainForItem nameChain name (mId |> string) - let args = ViewModelArgs.create m (fun msg -> toMsg (mId, msg) |> dispatch) chain loggingArgs - d.CreateViewModel args) - |> d.CreateCollection - { SubModelSeqKeyedData = d - Dispatch = dispatch - Vms = vms - GetCurrentModel = getCurrentModel - } - |> SubModelSeqKeyed - |> Some - | SubModelSelectedItemData d -> - let d = d |> BindingData.SubModelSelectedItem.measureFunctions measure measure2 - d.SubModelSeqBindingName - |> getFunctionsForSubModelSelectedItem - |> Option.map (fun selectedItemBinding -> - { Get = d.Get - Set = fun obj m -> d.Set obj m |> dispatch - SubModelSeqBindingName = d.SubModelSeqBindingName - SelectedItemBinding = selectedItemBinding } - |> SubModelSelectedItem) - - member this.Recursive<'model, 'msg> - (initialModel: 'model, - dispatch: 'msg -> unit, - getCurrentModel: unit -> 'model, - binding: BindingData<'model, 'msg, 't>) - : VmBinding<'model, 'msg, 't> option = - option { - match binding with - | BaseBindingData d -> - let! b = this.Base(initialModel, dispatch, getCurrentModel, d) - return BaseVmBinding b - | CachingData d -> - let! b = this.Recursive(initialModel, dispatch, getCurrentModel, d) - return b.AddCaching - | ValidationData d -> - let d = d |> BindingData.Validation.measureFunctions measure - let! b = this.Recursive(initialModel, dispatch, getCurrentModel, d.BindingData) - return b.AddValidation initialModel d.Validate - | LazyData d -> - let initialModel' : obj = d.Get initialModel - let getCurrentModel' : unit -> obj = getCurrentModel >> d.Get - let dispatch' : obj -> unit = d.MapDispatch(getCurrentModel, dispatch) - let d = d |> BindingData.Lazy.measureFunctions measure measure2 measure2 - let! b = this.Recursive(initialModel', dispatch', getCurrentModel', d.BindingData) - return { Binding = b - Get = d.Get - Equals = d.Equals - } |> Lazy - | AlterMsgStreamData d -> - let initialModel' : obj = d.Get initialModel - let getCurrentModel' : unit -> obj = getCurrentModel >> d.Get - let dispatch' : obj -> unit = d.MapDispatch(getCurrentModel, dispatch) - let! b = this.Recursive(initialModel', dispatch', getCurrentModel', d.BindingData) - return { Binding = b - Get = d.Get - } |> AlterMsgStream - } + ( + loggingArgs: LoggingViewModelArgs, + name: string, + getFunctionsForSubModelSelectedItem: string -> SelectedItemBinding option + ) = + + let { log = log + logPerformance = logPerformance + performanceLogThresholdMs = performanceLogThresholdMs + nameChain = nameChain } = + loggingArgs + + let measure x = + x + |> Helpers2.measure logPerformance LogLevel.Trace performanceLogThresholdMs name nameChain + + let measure2 x = + x + |> Helpers2.measure2 logPerformance LogLevel.Trace performanceLogThresholdMs name nameChain + + member _.Base<'model, 'msg> + ( + initialModel: 'model, + dispatch: 'msg -> unit, + getCurrentModel: unit -> 'model, + binding: BaseBindingData<'model, 'msg, 't> + ) : BaseVmBinding<'model, 'msg, 't> option = + match binding with + | OneWayData d -> + { OneWayData = d |> BindingData.OneWay.measureFunctions measure } + |> OneWay + |> Some + | OneWayToSourceData d -> + let d = d |> BindingData.OneWayToSource.measureFunctions measure + { Set = fun obj m -> d.Set obj m |> dispatch } |> OneWayToSource |> Some + | OneWaySeqData d -> + { OneWaySeqData = d |> BindingData.OneWaySeq.measureFunctions measure measure measure2 + Values = d.CreateCollection(initialModel |> d.Get) } + |> OneWaySeq + |> Some + | TwoWayData d -> + let d = d |> BindingData.TwoWay.measureFunctions measure measure + + { Get = d.Get + Set = fun obj m -> d.Set obj m |> dispatch } + |> TwoWay + |> Some + | CmdData d -> + let d = d |> BindingData.Cmd.measureFunctions measure2 measure2 + + let execute param = + d.Exec param (getCurrentModel ()) |> ValueOption.iter dispatch + + let canExecute param = d.CanExec param (getCurrentModel ()) + let cmd = Command(execute, canExecute) + + if d.AutoRequery then + cmd.AddRequeryHandler() + + cmd |> Cmd |> Some + | SubModelData d -> + let d = d |> BindingData.SubModel.measureFunctions measure measure measure measure2 + let toMsg = fun msg -> d.ToMsg (getCurrentModel ()) msg + let chain = LoggingViewModelArgs.getNameChainFor nameChain name + + d.GetModel initialModel + |> ValueOption.map (fun m -> ViewModelArgs.create m (toMsg >> dispatch) chain loggingArgs) + |> ValueOption.map d.CreateViewModel + |> (fun vm -> + let mutable vm = vm in + + { SubModelData = d + Dispatch = dispatch + GetVm = (fun () -> vm) + SetVm = fun nvm -> vm <- nvm + GetCurrentModel = getCurrentModel }) + |> SubModel + |> Some + | SubModelWinData d -> + let d = + d |> BindingData.SubModelWin.measureFunctions measure measure measure measure2 + + let toMsg = fun msg -> d.ToMsg (getCurrentModel ()) msg + + match d.GetState initialModel with + | WindowState.Closed -> + let mutable vmWinState = WindowState.Closed + + { SubModelWinData = d + Dispatch = dispatch + WinRef = WeakReference<_>(null) + PreventClose = ref true + GetVmWinState = fun () -> vmWinState + SetVmWinState = fun vmState -> vmWinState <- vmState + GetCurrentModel = getCurrentModel } + | WindowState.Hidden m -> + let chain = LoggingViewModelArgs.getNameChainFor nameChain name + let args = ViewModelArgs.create m (toMsg >> dispatch) chain loggingArgs + let vm = d.CreateViewModel args + let winRef = WeakReference<_>(null) + let preventClose = ref true + log.LogTrace("[{BindingNameChain}] Creating hidden window", chain) + + Helpers2.showNewWindow + winRef + d.GetWindow + d.IsModal + d.OnCloseRequested + preventClose + vm + Visibility.Hidden + getCurrentModel + dispatch + + let mutable vmWinState = WindowState.Hidden vm + + { SubModelWinData = d + Dispatch = dispatch + WinRef = winRef + PreventClose = preventClose + GetVmWinState = fun () -> vmWinState + SetVmWinState = fun vm -> vmWinState <- vm + GetCurrentModel = getCurrentModel } + | WindowState.Visible m -> + let chain = LoggingViewModelArgs.getNameChainFor nameChain name + let args = ViewModelArgs.create m (toMsg >> dispatch) chain loggingArgs + let vm = d.CreateViewModel args + let winRef = WeakReference<_>(null) + let preventClose = ref true + log.LogTrace("[{BindingNameChain}] Creating visible window", chain) + + Helpers2.showNewWindow + winRef + d.GetWindow + d.IsModal + d.OnCloseRequested + preventClose + vm + Visibility.Visible + getCurrentModel + dispatch + + let mutable vmWinState = WindowState.Visible vm + + { SubModelWinData = d + Dispatch = dispatch + WinRef = winRef + PreventClose = preventClose + GetVmWinState = fun () -> vmWinState + SetVmWinState = fun vm -> vmWinState <- vm + GetCurrentModel = getCurrentModel } + |> SubModelWin + |> Some + | SubModelSeqUnkeyedData d -> + let d = + d + |> BindingData.SubModelSeqUnkeyed.measureFunctions measure measure measure measure measure2 + + let toMsg = fun msg -> d.ToMsg (getCurrentModel ()) msg + + let vms = + d.GetModels initialModel + |> Seq.indexed + |> Seq.map (fun (idx, m) -> + let chain = LoggingViewModelArgs.getNameChainForItem nameChain name (idx |> string) + + let args = + ViewModelArgs.create m (fun msg -> toMsg (idx, msg) |> dispatch) chain loggingArgs + + d.CreateViewModel args) + |> d.CreateCollection + + { SubModelSeqUnkeyedData = d + Dispatch = dispatch + Vms = vms + GetCurrentModel = getCurrentModel } + |> SubModelSeqUnkeyed + |> Some + | SubModelSeqKeyedData d -> + let d = + d + |> BindingData.SubModelSeqKeyed.measureFunctions + measure + measure + measure + measure + measure2 + measure + measure + + let toMsg = fun msg -> d.ToMsg (getCurrentModel ()) msg + + let vms = + d.GetSubModels initialModel + |> Seq.map (fun m -> + let mId = d.BmToId m + let chain = LoggingViewModelArgs.getNameChainForItem nameChain name (mId |> string) + + let args = + ViewModelArgs.create m (fun msg -> toMsg (mId, msg) |> dispatch) chain loggingArgs + + d.CreateViewModel args) + |> d.CreateCollection + + { SubModelSeqKeyedData = d + Dispatch = dispatch + Vms = vms + GetCurrentModel = getCurrentModel } + |> SubModelSeqKeyed + |> Some + | SubModelSelectedItemData d -> + let d = d |> BindingData.SubModelSelectedItem.measureFunctions measure measure2 + + d.SubModelSeqBindingName + |> getFunctionsForSubModelSelectedItem + |> Option.map (fun selectedItemBinding -> + { Get = d.Get + Set = fun obj m -> d.Set obj m |> dispatch + SubModelSeqBindingName = d.SubModelSeqBindingName + SelectedItemBinding = selectedItemBinding } + |> SubModelSelectedItem) + + member this.Recursive<'model, 'msg> + ( + initialModel: 'model, + dispatch: 'msg -> unit, + getCurrentModel: unit -> 'model, + binding: BindingData<'model, 'msg, 't> + ) : VmBinding<'model, 'msg, 't> option = + option { + match binding with + | BaseBindingData d -> + let! b = this.Base(initialModel, dispatch, getCurrentModel, d) + return BaseVmBinding b + | CachingData d -> + let! b = this.Recursive(initialModel, dispatch, getCurrentModel, d) + return b.AddCaching + | ValidationData d -> + let d = d |> BindingData.Validation.measureFunctions measure + let! b = this.Recursive(initialModel, dispatch, getCurrentModel, d.BindingData) + return b.AddValidation initialModel d.Validate + | LazyData d -> + let initialModel': obj = d.Get initialModel + let getCurrentModel': unit -> obj = getCurrentModel >> d.Get + let dispatch': obj -> unit = d.MapDispatch(getCurrentModel, dispatch) + let d = d |> BindingData.Lazy.measureFunctions measure measure2 measure2 + let! b = this.Recursive(initialModel', dispatch', getCurrentModel', d.BindingData) + + return + { Binding = b + Get = d.Get + Equals = d.Equals } + |> Lazy + | AlterMsgStreamData d -> + let initialModel': obj = d.Get initialModel + let getCurrentModel': unit -> obj = getCurrentModel >> d.Get + let dispatch': obj -> unit = d.MapDispatch(getCurrentModel, dispatch) + let! b = this.Recursive(initialModel', dispatch', getCurrentModel', d.BindingData) + return { Binding = b; Get = d.Get } |> AlterMsgStream + } /// Updates the binding and returns a list indicating what events to raise for this binding -type Update<'t> - (loggingArgs: LoggingViewModelArgs, - name: string) = - - let { log = log - nameChain = nameChain } = - loggingArgs - - member _.Base<'model, 'msg> - (newModel: 'model, - binding: BaseVmBinding<'model, 'msg, 't>) = - match binding with - | OneWay _ - | TwoWay _ - | SubModelSelectedItem _ -> [ PropertyChanged name ] - | OneWayToSource _ -> [] - | OneWaySeq b -> - b.OneWaySeqData.Merge(b.Values, newModel) - [] - | Cmd cmd -> cmd |> CanExecuteChanged |> List.singleton - | SubModel b -> - let d = b.SubModelData - match b.GetVm (), d.GetModel newModel with - | ValueNone, ValueNone -> [] - | ValueSome _, ValueNone -> - b.SetVm ValueNone - [ PropertyChanged name ] - | ValueNone, ValueSome m -> - let toMsg = fun msg -> d.ToMsg (b.GetCurrentModel ()) msg - let chain = LoggingViewModelArgs.getNameChainFor nameChain name - let args = ViewModelArgs.create m (toMsg >> b.Dispatch) chain loggingArgs - b.SetVm (ValueSome <| d.CreateViewModel(args)) - [ PropertyChanged name ] - | ValueSome vm, ValueSome m -> - d.UpdateViewModel (vm, m) +type Update<'t>(loggingArgs: LoggingViewModelArgs, name: string) = + + let { log = log; nameChain = nameChain } = loggingArgs + + member _.Base<'model, 'msg>(newModel: 'model, binding: BaseVmBinding<'model, 'msg, 't>) = + match binding with + | OneWay _ + | TwoWay _ + | SubModelSelectedItem _ -> [ PropertyChanged name ] + | OneWayToSource _ -> [] + | OneWaySeq b -> + b.OneWaySeqData.Merge(b.Values, newModel) [] - | SubModelWin b -> - let d = b.SubModelWinData - let winPropChain = LoggingViewModelArgs.getNameChainFor nameChain name - let close () = - b.PreventClose.Value <- false - match b.WinRef.TryGetTarget () with - | false, _ -> - log.LogError("[{BindingNameChain}] Attempted to close window, but did not find window reference", winPropChain) - | true, w -> - log.LogTrace("[{BindingNameChain}] Closing window", winPropChain) - b.WinRef.SetTarget null - (* + | Cmd cmd -> cmd |> CanExecuteChanged |> List.singleton + | SubModel b -> + let d = b.SubModelData + + match b.GetVm(), d.GetModel newModel with + | ValueNone, ValueNone -> [] + | ValueSome _, ValueNone -> + b.SetVm ValueNone + [ PropertyChanged name ] + | ValueNone, ValueSome m -> + let toMsg = fun msg -> d.ToMsg (b.GetCurrentModel()) msg + let chain = LoggingViewModelArgs.getNameChainFor nameChain name + let args = ViewModelArgs.create m (toMsg >> b.Dispatch) chain loggingArgs + b.SetVm(ValueSome <| d.CreateViewModel(args)) + [ PropertyChanged name ] + | ValueSome vm, ValueSome m -> + d.UpdateViewModel(vm, m) + [] + | SubModelWin b -> + let d = b.SubModelWinData + let winPropChain = LoggingViewModelArgs.getNameChainFor nameChain name + + let close () = + b.PreventClose.Value <- false + + match b.WinRef.TryGetTarget() with + | false, _ -> + log.LogError( + "[{BindingNameChain}] Attempted to close window, but did not find window reference", + winPropChain + ) + | true, w -> + log.LogTrace("[{BindingNameChain}] Closing window", winPropChain) + b.WinRef.SetTarget null + (* * The Window might be in the process of closing, * so instead of immediately executing Window.Close via Dispatcher.Invoke, * queue a call to Window.Close via Dispatcher.InvokeAsync. * https://github.com/elmish/Elmish.WPF/issues/330 *) - w.Dispatcher.InvokeAsync(w.Close) |> ignore - b.WinRef.SetTarget null - - let hide () = - match b.WinRef.TryGetTarget () with - | false, _ -> - log.LogError("[{BindingNameChain}] Attempted to hide window, but did not find window reference", winPropChain) - | true, w -> - log.LogTrace("[{BindingNameChain}] Hiding window", winPropChain) - w.Dispatcher.Invoke(fun () -> w.Visibility <- Visibility.Hidden) - - let showHidden () = - match b.WinRef.TryGetTarget () with - | false, _ -> - log.LogError("[{BindingNameChain}] Attempted to show existing hidden window, but did not find window reference", winPropChain) - | true, w -> - log.LogTrace("[{BindingNameChain}] Showing existing hidden window", winPropChain) - w.Dispatcher.Invoke(fun () -> w.Visibility <- Visibility.Visible) - - let showNew vm = - b.PreventClose.Value <- true - Helpers2.showNewWindow b.WinRef d.GetWindow d.IsModal d.OnCloseRequested b.PreventClose vm - - let newVm model = - let toMsg = fun msg -> d.ToMsg (b.GetCurrentModel ()) msg - let chain = LoggingViewModelArgs.getNameChainFor nameChain name - let args = ViewModelArgs.create model (toMsg >> b.Dispatch) chain loggingArgs - d.CreateViewModel args - - match b.GetVmWinState(), d.GetState newModel with - | WindowState.Closed, WindowState.Closed -> - [] - | WindowState.Hidden vm, WindowState.Hidden m - | WindowState.Visible vm, WindowState.Visible m -> - d.UpdateViewModel (vm, m) - [] - | WindowState.Hidden _, WindowState.Closed - | WindowState.Visible _, WindowState.Closed -> - close () - b.SetVmWinState WindowState.Closed - [ PropertyChanged name ] - | WindowState.Visible vm, WindowState.Hidden m -> - hide () - d.UpdateViewModel (vm, m) - b.SetVmWinState (WindowState.Hidden vm) - [] - | WindowState.Hidden vm, WindowState.Visible m -> - d.UpdateViewModel (vm, m) - showHidden () - b.SetVmWinState (WindowState.Visible vm) - [] - | WindowState.Closed, WindowState.Hidden m -> - let vm = newVm m - log.LogTrace("[{BindingNameChain}] Creating hidden window", winPropChain) - showNew vm Visibility.Hidden b.GetCurrentModel b.Dispatch - b.SetVmWinState (WindowState.Hidden vm) - [ PropertyChanged name ] - | WindowState.Closed, WindowState.Visible m -> - let vm = newVm m - log.LogTrace("[{BindingNameChain}] Creating visible window", winPropChain) - showNew vm Visibility.Visible b.GetCurrentModel b.Dispatch - b.SetVmWinState (WindowState.Visible vm) - [ PropertyChanged name ] - | SubModelSeqUnkeyed b -> - let d = b.SubModelSeqUnkeyedData - let create m idx = - let chain = LoggingViewModelArgs.getNameChainForItem nameChain name (idx |> string) - let args = ViewModelArgs.create m (fun msg -> d.ToMsg (b.GetCurrentModel ()) (idx, msg) |> b.Dispatch) chain loggingArgs - d.CreateViewModel args - let update vm m = d.UpdateViewModel (vm, m) - Merge.unkeyed create update b.Vms (d.GetModels newModel) - [] - | SubModelSeqKeyed b -> - let d = b.SubModelSeqKeyedData - let create m id = - let chain = LoggingViewModelArgs.getNameChainForItem nameChain name (id |> string) - let args = ViewModelArgs.create m (fun msg -> d.ToMsg (b.GetCurrentModel ()) (id, msg) |> b.Dispatch) chain loggingArgs - d.CreateViewModel args - let update vm m = d.UpdateViewModel (vm, m) - let newSubModels = newModel |> d.GetSubModels |> Seq.toArray - try - d.MergeKeyed(create, update, b.Vms, newSubModels) - with - | :? DuplicateIdException as e -> - let messageTemplate = "[{BindingNameChain}] In the {SourceOrTarget} sequence of the binding {BindingName}, the elements at indices {Index1} and {Index2} have the same ID {ID}. To avoid this problem, the elements will be merged without using IDs." - log.LogError(messageTemplate, nameChain, e.SourceOrTarget, name, e.Index1, e.Index2, e.Id) - let create m _ = create m (d.BmToId m) - Merge.unkeyed create update b.Vms newSubModels - [] - - member this.Recursive<'model, 'msg> - (currentModel: 'model, - newModel: 'model, - binding: VmBinding<'model, 'msg, 't>) - : UpdateData list = - match binding with - | BaseVmBinding b -> this.Base(newModel, b) - | Cached b -> - let updates = this.Recursive(currentModel, newModel, b.Binding) - updates - |> List.filter UpdateData.isPropertyChanged - |> List.iter (fun _ -> b.SetCache None) - updates - | Validatation b -> - let updates = this.Recursive(currentModel, newModel, b.Binding) - let newErrors = b.Validate newModel - if b.Errors.Value <> newErrors then - b.Errors.Value <- newErrors - ErrorsChanged name :: updates - else - updates - | Lazy b -> - let currentModel' = currentModel |> b.Get - let newModel' = newModel |> b.Get - if b.Equals currentModel' newModel' then + w.Dispatcher.InvokeAsync(w.Close) |> ignore + + b.WinRef.SetTarget null + + let hide () = + match b.WinRef.TryGetTarget() with + | false, _ -> + log.LogError( + "[{BindingNameChain}] Attempted to hide window, but did not find window reference", + winPropChain + ) + | true, w -> + log.LogTrace("[{BindingNameChain}] Hiding window", winPropChain) + w.Dispatcher.Invoke(fun () -> w.Visibility <- Visibility.Hidden) + + let showHidden () = + match b.WinRef.TryGetTarget() with + | false, _ -> + log.LogError( + "[{BindingNameChain}] Attempted to show existing hidden window, but did not find window reference", + winPropChain + ) + | true, w -> + log.LogTrace("[{BindingNameChain}] Showing existing hidden window", winPropChain) + w.Dispatcher.Invoke(fun () -> w.Visibility <- Visibility.Visible) + + let showNew vm = + b.PreventClose.Value <- true + Helpers2.showNewWindow b.WinRef d.GetWindow d.IsModal d.OnCloseRequested b.PreventClose vm + + let newVm model = + let toMsg = fun msg -> d.ToMsg (b.GetCurrentModel()) msg + let chain = LoggingViewModelArgs.getNameChainFor nameChain name + let args = ViewModelArgs.create model (toMsg >> b.Dispatch) chain loggingArgs + d.CreateViewModel args + + match b.GetVmWinState(), d.GetState newModel with + | WindowState.Closed, WindowState.Closed -> [] + | WindowState.Hidden vm, WindowState.Hidden m + | WindowState.Visible vm, WindowState.Visible m -> + d.UpdateViewModel(vm, m) + [] + | WindowState.Hidden _, WindowState.Closed + | WindowState.Visible _, WindowState.Closed -> + close () + b.SetVmWinState WindowState.Closed + [ PropertyChanged name ] + | WindowState.Visible vm, WindowState.Hidden m -> + hide () + d.UpdateViewModel(vm, m) + b.SetVmWinState(WindowState.Hidden vm) + [] + | WindowState.Hidden vm, WindowState.Visible m -> + d.UpdateViewModel(vm, m) + showHidden () + b.SetVmWinState(WindowState.Visible vm) + [] + | WindowState.Closed, WindowState.Hidden m -> + let vm = newVm m + log.LogTrace("[{BindingNameChain}] Creating hidden window", winPropChain) + showNew vm Visibility.Hidden b.GetCurrentModel b.Dispatch + b.SetVmWinState(WindowState.Hidden vm) + [ PropertyChanged name ] + | WindowState.Closed, WindowState.Visible m -> + let vm = newVm m + log.LogTrace("[{BindingNameChain}] Creating visible window", winPropChain) + showNew vm Visibility.Visible b.GetCurrentModel b.Dispatch + b.SetVmWinState(WindowState.Visible vm) + [ PropertyChanged name ] + | SubModelSeqUnkeyed b -> + let d = b.SubModelSeqUnkeyedData + + let create m idx = + let chain = LoggingViewModelArgs.getNameChainForItem nameChain name (idx |> string) + + let args = + ViewModelArgs.create + m + (fun msg -> d.ToMsg (b.GetCurrentModel()) (idx, msg) |> b.Dispatch) + chain + loggingArgs + + d.CreateViewModel args + + let update vm m = d.UpdateViewModel(vm, m) + Merge.unkeyed create update b.Vms (d.GetModels newModel) [] - else - this.Recursive(currentModel', newModel', b.Binding) - | AlterMsgStream b -> - this.Recursive(currentModel |> b.Get, b.Get newModel, b.Binding) + | SubModelSeqKeyed b -> + let d = b.SubModelSeqKeyedData + + let create m id = + let chain = LoggingViewModelArgs.getNameChainForItem nameChain name (id |> string) + + let args = + ViewModelArgs.create + m + (fun msg -> d.ToMsg (b.GetCurrentModel()) (id, msg) |> b.Dispatch) + chain + loggingArgs + + d.CreateViewModel args + + let update vm m = d.UpdateViewModel(vm, m) + let newSubModels = newModel |> d.GetSubModels |> Seq.toArray + + try + d.MergeKeyed(create, update, b.Vms, newSubModels) + with :? DuplicateIdException as e -> + let messageTemplate = + "[{BindingNameChain}] In the {SourceOrTarget} sequence of the binding {BindingName}, the elements at indices {Index1} and {Index2} have the same ID {ID}. To avoid this problem, the elements will be merged without using IDs." + + log.LogError(messageTemplate, nameChain, e.SourceOrTarget, name, e.Index1, e.Index2, e.Id) + let create m _ = create m (d.BmToId m) + Merge.unkeyed create update b.Vms newSubModels + + [] + + member this.Recursive<'model, 'msg> + (currentModel: 'model, newModel: 'model, binding: VmBinding<'model, 'msg, 't>) + : UpdateData list = + match binding with + | BaseVmBinding b -> this.Base(newModel, b) + | Cached b -> + let updates = this.Recursive(currentModel, newModel, b.Binding) + + updates + |> List.filter UpdateData.isPropertyChanged + |> List.iter (fun _ -> b.SetCache None) + + updates + | Validatation b -> + let updates = this.Recursive(currentModel, newModel, b.Binding) + let newErrors = b.Validate newModel + + if b.Errors.Value <> newErrors then + b.Errors.Value <- newErrors + ErrorsChanged name :: updates + else + updates + | Lazy b -> + let currentModel' = currentModel |> b.Get + let newModel' = newModel |> b.Get + + if b.Equals currentModel' newModel' then + [] + else + this.Recursive(currentModel', newModel', b.Binding) + | AlterMsgStream b -> this.Recursive(currentModel |> b.Get, b.Get newModel, b.Binding) type Get<'t>(nameChain: string) = - member _.Base (model: 'model, binding: BaseVmBinding<'model, 'msg, 't>) = - match binding with - | OneWay { OneWayData = d } -> d.Get model |> Ok - | TwoWay b -> b.Get model |> Ok - | OneWayToSource _ -> GetError.OneWayToSource |> Error - | OneWaySeq { Values = vals } -> vals.GetCollection () |> Ok - | Cmd cmd -> cmd |> unbox |> Ok - | SubModel { GetVm = getvm } -> getvm() |> ValueOption.toNull |> Result.mapError GetError.ToNullError - | SubModelWin { GetVmWinState = getvm } -> - getvm() - |> WindowState.toVOption - |> ValueOption.toNull - |> Result.mapError GetError.ToNullError - | SubModelSeqUnkeyed { Vms = vms } - | SubModelSeqKeyed { Vms = vms } -> vms.GetCollection () |> Ok - | SubModelSelectedItem b -> - b.TypedGet model - |> function - | ValueNone -> ValueNone |> Ok // deselecting successful - | ValueSome (id, mVm) -> - match mVm with - | Some vm -> vm |> ValueSome |> Ok // selecting successful - | None -> // selecting failed - { NameChain = nameChain - SubModelSeqBindingName = b.SubModelSeqBindingName - Id = id.ToString() } - |> GetError.SubModelSelectedItem - |> Error - |> Result.bind (ValueOption.toNull >> Result.mapError GetError.ToNullError) - - member this.Recursive<'model, 'msg> - (model: 'model, - binding: VmBinding<'model, 'msg, 't>) - : Result<'t, GetError> = - match binding with - | BaseVmBinding b -> this.Base(model, b) - | Cached b -> - match b.GetCache() with - | Some v -> v |> Ok - | None -> - let x = this.Recursive(model, b.Binding) - x |> Result.iter (fun v -> b.SetCache (Some v)) - x - | Validatation b -> this.Recursive(model, b.Binding) - | Lazy b -> this.Recursive(b.Get model, b.Binding) - | AlterMsgStream b -> this.Recursive(b.Get model, b.Binding) + member _.Base(model: 'model, binding: BaseVmBinding<'model, 'msg, 't>) = + match binding with + | OneWay { OneWayData = d } -> d.Get model |> Ok + | TwoWay b -> b.Get model |> Ok + | OneWayToSource _ -> GetError.OneWayToSource |> Error + | OneWaySeq { Values = vals } -> vals.GetCollection() |> Ok + | Cmd cmd -> cmd |> unbox |> Ok + | SubModel { GetVm = getvm } -> getvm () |> ValueOption.toNull |> Result.mapError GetError.ToNullError + | SubModelWin { GetVmWinState = getvm } -> + getvm () + |> WindowState.toVOption + |> ValueOption.toNull + |> Result.mapError GetError.ToNullError + | SubModelSeqUnkeyed { Vms = vms } + | SubModelSeqKeyed { Vms = vms } -> vms.GetCollection() |> Ok + | SubModelSelectedItem b -> + b.TypedGet model + |> function + | ValueNone -> ValueNone |> Ok // deselecting successful + | ValueSome(id, mVm) -> + match mVm with + | Some vm -> vm |> ValueSome |> Ok // selecting successful + | None -> // selecting failed + { NameChain = nameChain + SubModelSeqBindingName = b.SubModelSeqBindingName + Id = id.ToString() } + |> GetError.SubModelSelectedItem + |> Error + |> Result.bind (ValueOption.toNull >> Result.mapError GetError.ToNullError) + + member this.Recursive<'model, 'msg>(model: 'model, binding: VmBinding<'model, 'msg, 't>) : Result<'t, GetError> = + match binding with + | BaseVmBinding b -> this.Base(model, b) + | Cached b -> + match b.GetCache() with + | Some v -> v |> Ok + | None -> + let x = this.Recursive(model, b.Binding) + x |> Result.iter (fun v -> b.SetCache(Some v)) + x + | Validatation b -> this.Recursive(model, b.Binding) + | Lazy b -> this.Recursive(b.Get model, b.Binding) + | AlterMsgStream b -> this.Recursive(b.Get model, b.Binding) type Set<'t>(value: 't) = - member _.Base(model: 'model, binding: BaseVmBinding<'model, 'msg, 't>) = - match binding with - | TwoWay b -> - b.Set value model - true - | OneWayToSource b -> - b.Set value model - true - | SubModelSelectedItem b -> - b.TypedSet(model, ValueOption.ofNull value) - true - | OneWay _ - | OneWaySeq _ - | Cmd _ - | SubModel _ - | SubModelWin _ - | SubModelSeqUnkeyed _ - | SubModelSeqKeyed _ -> - false - - member this.Recursive<'model, 'msg>(model: 'model, binding: VmBinding<'model, 'msg, 't>) : bool = - match binding with - | BaseVmBinding b -> this.Base(model, b) - | Cached b -> - // UpdateModel changes the model, - // but Set only dispatches a message, - // so don't clear the cache here - this.Recursive<'model, 'msg>(model, b.Binding) - | Validatation b -> this.Recursive<'model, 'msg>(model, b.Binding) - | Lazy b -> this.Recursive(b.Get model, b.Binding) - | AlterMsgStream b -> this.Recursive(b.Get model, b.Binding) + member _.Base(model: 'model, binding: BaseVmBinding<'model, 'msg, 't>) = + match binding with + | TwoWay b -> + b.Set value model + true + | OneWayToSource b -> + b.Set value model + true + | SubModelSelectedItem b -> + b.TypedSet(model, ValueOption.ofNull value) + true + | OneWay _ + | OneWaySeq _ + | Cmd _ + | SubModel _ + | SubModelWin _ + | SubModelSeqUnkeyed _ + | SubModelSeqKeyed _ -> false + + member this.Recursive<'model, 'msg>(model: 'model, binding: VmBinding<'model, 'msg, 't>) : bool = + match binding with + | BaseVmBinding b -> this.Base(model, b) + | Cached b -> + // UpdateModel changes the model, + // but Set only dispatches a message, + // so don't clear the cache here + this.Recursive<'model, 'msg>(model, b.Binding) + | Validatation b -> this.Recursive<'model, 'msg>(model, b.Binding) + | Lazy b -> this.Recursive(b.Get model, b.Binding) + | AlterMsgStream b -> this.Recursive(b.Get model, b.Binding) \ No newline at end of file diff --git a/src/Elmish.WPF/Command.fs b/src/Elmish.WPF/Command.fs index c4fe663a..301d9b7f 100644 --- a/src/Elmish.WPF/Command.fs +++ b/src/Elmish.WPF/Command.fs @@ -10,22 +10,25 @@ open System.Windows.Input /// another UI control (e.g. a ListView.SelectedItem). type internal Command(execute, canExecute) = - let canExecuteChanged = Event() - - // CommandManager only keeps a weak reference to the event handler, - // so a strong reference must be maintained, - // which is achieved by this mutable let-binding. - // Can test this via the UiBoundCmdParam sample. - let mutable _handler = Unchecked.defaultof - member this.AddRequeryHandler () = - let handler = EventHandler(fun _ _ -> this.RaiseCanExecuteChanged()) - CommandManager.RequerySuggested.AddHandler handler - _handler <- handler - - member this.RaiseCanExecuteChanged () = canExecuteChanged.Trigger(this, EventArgs.Empty) - - interface ICommand with - [] - member _.CanExecuteChanged = canExecuteChanged.Publish - member _.CanExecute p = canExecute p - member _.Execute p = execute p \ No newline at end of file + let canExecuteChanged = Event() + + // CommandManager only keeps a weak reference to the event handler, + // so a strong reference must be maintained, + // which is achieved by this mutable let-binding. + // Can test this via the UiBoundCmdParam sample. + let mutable _handler = Unchecked.defaultof + + member this.AddRequeryHandler() = + let handler = EventHandler(fun _ _ -> this.RaiseCanExecuteChanged()) + CommandManager.RequerySuggested.AddHandler handler + _handler <- handler + + member this.RaiseCanExecuteChanged() = + canExecuteChanged.Trigger(this, EventArgs.Empty) + + interface ICommand with + [] + member _.CanExecuteChanged = canExecuteChanged.Publish + + member _.CanExecute p = canExecute p + member _.Execute p = execute p \ No newline at end of file diff --git a/src/Elmish.WPF/InternalUtils.fs b/src/Elmish.WPF/InternalUtils.fs index 959b6d02..ebc4c5dd 100644 --- a/src/Elmish.WPF/InternalUtils.fs +++ b/src/Elmish.WPF/InternalUtils.fs @@ -11,18 +11,14 @@ let ignore2 _ _ = () /// Deconstructs a KeyValuePair into a tuple. [] -let (|Kvp|) (kvp: KeyValuePair<_,_>) = - Kvp (kvp.Key, kvp.Value) +let (|Kvp|) (kvp: KeyValuePair<_, _>) = Kvp(kvp.Key, kvp.Value) [] type OptionalBuilder = - member _.Bind(ma, f) = - ma |> Option.bind f - member _.Return(a) = - Some a - member _.ReturnFrom(ma) = - ma + member _.Bind(ma, f) = ma |> Option.bind f + member _.Return(a) = Some a + member _.ReturnFrom(ma) = ma let option = OptionalBuilder() @@ -30,128 +26,132 @@ let option = OptionalBuilder() [] module Kvp = - let key (kvp: KeyValuePair<_,_>) = - kvp.Key + let key (kvp: KeyValuePair<_, _>) = kvp.Key - let value (kvp: KeyValuePair<_,_>) = - kvp.Value + let value (kvp: KeyValuePair<_, _>) = kvp.Value [] module Result = - let isOk = function - | Ok _ -> true - | Error _ -> false + let isOk = + function + | Ok _ -> true + | Error _ -> false - let iter f = function - | Ok x -> f x - | Error _ -> () + let iter f = + function + | Ok x -> f x + | Error _ -> () [] module ValueOption = - let ofOption = function - | Some x -> ValueSome x - | None -> ValueNone + let ofOption = + function + | Some x -> ValueSome x + | None -> ValueNone - let toOption = function - | ValueSome x -> Some x - | ValueNone -> None + let toOption = + function + | ValueSome x -> Some x + | ValueNone -> None - let ofError = function - | Ok _ -> ValueNone - | Error x -> ValueSome x + let ofError = + function + | Ok _ -> ValueNone + | Error x -> ValueSome x - let ofOk = function - | Ok x -> ValueSome x - | Error _ -> ValueNone + let ofOk = + function + | Ok x -> ValueSome x + | Error _ -> ValueNone - [] - type ToNullError = - | ValueCannotBeNull of string + [] + type ToNullError = ValueCannotBeNull of string - let ofNull<'a> (x: 'a) = - match box x with - | null -> ValueNone - | _ -> ValueSome x + let ofNull<'a> (x: 'a) = + match box x with + | null -> ValueNone + | _ -> ValueSome x - let toNull<'a> = function - | ValueSome x -> Ok x - | ValueNone -> - let default' = Unchecked.defaultof<'a> - if box default' = null then - default' |> Ok - else - typeof<'a>.Name |> ToNullError.ValueCannotBeNull |> Error + let toNull<'a> = + function + | ValueSome x -> Ok x + | ValueNone -> + let default' = Unchecked.defaultof<'a> + + if box default' = null then + default' |> Ok + else + typeof<'a>.Name |> ToNullError.ValueCannotBeNull |> Error [] module ByRefPair = - let toOption (b, a) = - if b then Some a else None + let toOption (b, a) = if b then Some a else None [] module Dictionary = - let tryFind key (d: Dictionary<_, _>) = - key |> d.TryGetValue |> ByRefPair.toOption + let tryFind key (d: Dictionary<_, _>) = + key |> d.TryGetValue |> ByRefPair.toOption [] module IReadOnlyDictionary = - let tryFind key (d: IReadOnlyDictionary<_, _>) = - key |> d.TryGetValue |> ByRefPair.toOption + let tryFind key (d: IReadOnlyDictionary<_, _>) = + key |> d.TryGetValue |> ByRefPair.toOption [] module Option = - let fromBool a b = - if b then Some a else None + let fromBool a b = if b then Some a else None [] module SeqOption = - let somes mma = mma |> Seq.choose id + let somes mma = mma |> Seq.choose id [] module Pair = - let ofKvp (kvp: KeyValuePair<_,_>) = (kvp.Key, kvp.Value) + let ofKvp (kvp: KeyValuePair<_, _>) = (kvp.Key, kvp.Value) - let mapAll f g (a, c) = (f a, g c) + let mapAll f g (a, c) = (f a, g c) - let map2 f (a, c) = (a, f c) + let map2 f (a, c) = (a, f c) [] module PairOption = - let sequence = function - | Some a, Some b -> Some (a, b) - | _ -> None + let sequence = + function + | Some a, Some b -> Some(a, b) + | _ -> None [] module Func2 = - let id1<'a, 'b> (a: 'a) (_: 'b) = a - let id2<'a, 'b> (_: 'a) (b: 'b) = b - let curry f a b = f (a, b) + let id1<'a, 'b> (a: 'a) (_: 'b) = a + let id2<'a, 'b> (_: 'a) (b: 'b) = b + let curry f a b = f (a, b) [] module Func3 = - let curry f a b c = f (a, b, c) + let curry f a b c = f (a, b, c) [] module Func5 = - let curry f a b c d e = f (a, b, c, d, e) + let curry f a b c d e = f (a, b, c, d, e) \ No newline at end of file diff --git a/src/Elmish.WPF/Merge.fs b/src/Elmish.WPF/Merge.fs index 264a810c..64321877 100644 --- a/src/Elmish.WPF/Merge.fs +++ b/src/Elmish.WPF/Merge.fs @@ -6,257 +6,283 @@ open System.Collections.ObjectModel type SourceOrTarget = - | Source - | Target - -type DuplicateIdException (sourceOrTarget: SourceOrTarget, index1: int, index2: int, id: string) = - inherit System.Exception(sprintf "In the %A sequence, the elements at indices %d and %d have the same ID %s" sourceOrTarget index1 index2 id) - member this.SourceOrTarget = sourceOrTarget - member this.Index1 = index1 - member this.Index2 = index2 - member this.Id = id + | Source + | Target + +type DuplicateIdException(sourceOrTarget: SourceOrTarget, index1: int, index2: int, id: string) = + inherit + System.Exception( + sprintf + "In the %A sequence, the elements at indices %d and %d have the same ID %s" + sourceOrTarget + index1 + index2 + id + ) + + member this.SourceOrTarget = sourceOrTarget + member this.Index1 = index1 + member this.Index2 = index2 + member this.Id = id type CollectionTarget<'a, 'aCollection> = - { GetLength: unit -> int - GetAt: int -> 'a - Append: 'a -> unit - InsertAt: int * 'a -> unit - SetAt: int * 'a -> unit - RemoveAt: int -> unit - Move: int * int -> unit - Clear: unit -> unit - Enumerate: unit -> 'a seq - GetCollection: unit -> 'aCollection } + { GetLength: unit -> int + GetAt: int -> 'a + Append: 'a -> unit + InsertAt: int * 'a -> unit + SetAt: int * 'a -> unit + RemoveAt: int -> unit + Move: int * int -> unit + Clear: unit -> unit + Enumerate: unit -> 'a seq + GetCollection: unit -> 'aCollection } module CollectionTarget = - let create (oc: ObservableCollection<'a>) = - { GetLength = fun () -> oc.Count - GetAt = fun i -> oc.[i] - Append = oc.Add - InsertAt = oc.Insert - SetAt = fun (i, a) -> oc.[i] <- a - RemoveAt = oc.RemoveAt - Move = oc.Move - Clear = oc.Clear - Enumerate = fun () -> upcast oc - GetCollection = fun () -> oc } - - let mapA (fOut: 'a0 -> 'a1) (fIn: 'a1 -> 'a0) (ct: CollectionTarget<'a0, 'aCollection>) : CollectionTarget<'a1, 'aCollection> = - { GetLength = ct.GetLength - GetAt = ct.GetAt >> fOut - Append = fIn >> ct.Append - InsertAt = Pair.map2 fIn >> ct.InsertAt - SetAt = Pair.map2 fIn >> ct.SetAt - RemoveAt = ct.RemoveAt - Move = ct.Move - Clear = ct.Clear - Enumerate = ct.Enumerate >> Seq.map fOut - GetCollection = ct.GetCollection } - - let mapCollection (fOut: 'aCollection0 -> 'aCollection1) (ct: CollectionTarget<'a, 'aCollection0>) : CollectionTarget<'a, 'aCollection1> = - { GetLength = ct.GetLength - GetAt = ct.GetAt - Append = ct.Append - InsertAt = ct.InsertAt - SetAt = ct.SetAt - RemoveAt = ct.RemoveAt - Move = ct.Move - Clear = ct.Clear - Enumerate = ct.Enumerate - GetCollection = ct.GetCollection >> fOut } + let create (oc: ObservableCollection<'a>) = + { GetLength = fun () -> oc.Count + GetAt = fun i -> oc.[i] + Append = oc.Add + InsertAt = oc.Insert + SetAt = fun (i, a) -> oc.[i] <- a + RemoveAt = oc.RemoveAt + Move = oc.Move + Clear = oc.Clear + Enumerate = fun () -> upcast oc + GetCollection = fun () -> oc } + + let mapA + (fOut: 'a0 -> 'a1) + (fIn: 'a1 -> 'a0) + (ct: CollectionTarget<'a0, 'aCollection>) + : CollectionTarget<'a1, 'aCollection> = + { GetLength = ct.GetLength + GetAt = ct.GetAt >> fOut + Append = fIn >> ct.Append + InsertAt = Pair.map2 fIn >> ct.InsertAt + SetAt = Pair.map2 fIn >> ct.SetAt + RemoveAt = ct.RemoveAt + Move = ct.Move + Clear = ct.Clear + Enumerate = ct.Enumerate >> Seq.map fOut + GetCollection = ct.GetCollection } + + let mapCollection + (fOut: 'aCollection0 -> 'aCollection1) + (ct: CollectionTarget<'a, 'aCollection0>) + : CollectionTarget<'a, 'aCollection1> = + { GetLength = ct.GetLength + GetAt = ct.GetAt + Append = ct.Append + InsertAt = ct.InsertAt + SetAt = ct.SetAt + RemoveAt = ct.RemoveAt + Move = ct.Move + Clear = ct.Clear + Enumerate = ct.Enumerate + GetCollection = ct.GetCollection >> fOut } module Merge = - let unkeyed - (create: 's -> int -> 't) - (update: 't -> 's -> unit) - (target: CollectionTarget<'t, 'tCollection>) - (source: 's seq) = - let mutable lastIdx = -1 - for (idx, s) in source |> Seq.indexed do - lastIdx <- idx - if idx < target.GetLength() then - update (target.GetAt idx) s - else // source is longer than target - create s idx |> target.Append - let mutable idx = target.GetLength() - 1 - while idx > lastIdx do // target is longer than source - target.RemoveAt idx - idx <- idx - 1 - - - let keyed - (getSourceId: 's -> 'id) - (getTargetId: 't -> 'id) - (create: 's -> 'id -> 't) - (update: 't -> 's -> int -> unit) - (target: CollectionTarget<'t, 'tCollection>) - (source: 's array) = - (* + let unkeyed + (create: 's -> int -> 't) + (update: 't -> 's -> unit) + (target: CollectionTarget<'t, 'tCollection>) + (source: 's seq) + = + let mutable lastIdx = -1 + + for (idx, s) in source |> Seq.indexed do + lastIdx <- idx + + if idx < target.GetLength() then + update (target.GetAt idx) s + else // source is longer than target + create s idx |> target.Append + + let mutable idx = target.GetLength() - 1 + + while idx > lastIdx do // target is longer than source + target.RemoveAt idx + idx <- idx - 1 + + + let keyed + (getSourceId: 's -> 'id) + (getTargetId: 't -> 'id) + (create: 's -> 'id -> 't) + (update: 't -> 's -> int -> unit) + (target: CollectionTarget<'t, 'tCollection>) + (source: 's array) + = + (* * Based on Elm's HTML.keyed * https://guide.elm-lang.org/optimization/keyed.html * https://github.com/elm/virtual-dom/blob/5a5bcf48720bc7d53461b3cd42a9f19f119c5503/src/Elm/Kernel/VirtualDom.js#L980-L1226 *) - let removals = Dictionary<_, _> () - let additions = Dictionary<_, _> () - - let recordRemoval curTargetIdx curTarget curTargetId = - if removals.ContainsKey curTargetId then - let (firstIdx, _) = removals.[curTargetId] - raise (DuplicateIdException (Target, firstIdx, curTargetIdx, curTargetId.ToString())) - else - removals.Add(curTargetId, (curTargetIdx, curTarget)) - let recordAddition curSourceIdx curSource curSourceId = - if additions.ContainsKey curSourceId then - let (firstIdx, _) = additions.[curSourceId] - raise (DuplicateIdException (Source, firstIdx, curSourceIdx, curSourceId.ToString())) - else - additions.Add(curSourceId, (curSourceIdx, curSource)) - - let mutable curSourceIdx = 0 - let mutable curTargetIdx = 0 - - let mutable shouldContinue = true - - let sourceCount = source.Length - let targetCount = target.GetLength() - - while (shouldContinue && curSourceIdx < sourceCount && curTargetIdx < targetCount) do - let curSource = source.[curSourceIdx] - let curTarget = target.GetAt curTargetIdx - - let curSourceId = getSourceId curSource - let curTargetId = getTargetId curTarget - - if curSourceId = curTargetId then - update curTarget curSource curTargetIdx - - curSourceIdx <- curSourceIdx + 1 - curTargetIdx <- curTargetIdx + 1 - else - let mNextSource = - source - |> Array.tryItem (curSourceIdx + 1) - |> Option.map (fun s -> - let id = getSourceId s - s, id, id = curTargetId) // true => need to add - - let mNextTarget = - if curTargetIdx + 1 < targetCount then target.GetAt (curTargetIdx + 1) |> Some else None - |> Option.map (fun t -> - let id = getTargetId t - t, id, id = curSourceId) // true => need to remove + let removals = Dictionary<_, _>() + let additions = Dictionary<_, _>() + + let recordRemoval curTargetIdx curTarget curTargetId = + if removals.ContainsKey curTargetId then + let (firstIdx, _) = removals.[curTargetId] + raise (DuplicateIdException(Target, firstIdx, curTargetIdx, curTargetId.ToString())) + else + removals.Add(curTargetId, (curTargetIdx, curTarget)) + + let recordAddition curSourceIdx curSource curSourceId = + if additions.ContainsKey curSourceId then + let (firstIdx, _) = additions.[curSourceId] + raise (DuplicateIdException(Source, firstIdx, curSourceIdx, curSourceId.ToString())) + else + additions.Add(curSourceId, (curSourceIdx, curSource)) + + let mutable curSourceIdx = 0 + let mutable curTargetIdx = 0 + + let mutable shouldContinue = true + + let sourceCount = source.Length + let targetCount = target.GetLength() + + while (shouldContinue && curSourceIdx < sourceCount && curTargetIdx < targetCount) do + let curSource = source.[curSourceIdx] + let curTarget = target.GetAt curTargetIdx + + let curSourceId = getSourceId curSource + let curTargetId = getTargetId curTarget + + if curSourceId = curTargetId then + update curTarget curSource curTargetIdx + + curSourceIdx <- curSourceIdx + 1 + curTargetIdx <- curTargetIdx + 1 + else + let mNextSource = + source + |> Array.tryItem (curSourceIdx + 1) + |> Option.map (fun s -> + let id = getSourceId s + s, id, id = curTargetId) // true => need to add + + let mNextTarget = + if curTargetIdx + 1 < targetCount then + target.GetAt(curTargetIdx + 1) |> Some + else + None + |> Option.map (fun t -> + let id = getTargetId t + t, id, id = curSourceId) // true => need to remove + + match mNextSource, mNextTarget with + | Some(nextSource, _, true), Some(nextTarget, _, true) -> // swap adjacent + target.SetAt(curTargetIdx, nextTarget) + target.SetAt(curTargetIdx + 1, curTarget) + + update curTarget nextSource (curTargetIdx + 1) + update nextTarget curSource curTargetIdx + + curSourceIdx <- curSourceIdx + 2 + curTargetIdx <- curTargetIdx + 2 + | None, Some(nextTarget, _, true) + | Some(_, _, false), Some(nextTarget, _, true) -> // remove + recordRemoval curTargetIdx curTarget curTargetId + + update nextTarget curSource curTargetIdx + + curSourceIdx <- curSourceIdx + 1 + curTargetIdx <- curTargetIdx + 2 + | Some(nextSource, _, true), None + | Some(nextSource, _, true), Some(_, _, false) -> // add + recordAddition curSourceIdx curSource curSourceId + + update curTarget nextSource (curTargetIdx + 1) + + curSourceIdx <- curSourceIdx + 2 + curTargetIdx <- curTargetIdx + 1 + | Some(_, _, false), None + | None, Some(_, _, false) + | None, None -> // source and target have different lengths and we have reached the end of one + shouldContinue <- false + | Some(nextSource, nextSourceId, false), Some(nextTarget, nextTargetId, false) -> + if nextSourceId = nextTargetId then // replace + recordRemoval curTargetIdx curTarget curTargetId + recordAddition curSourceIdx curSource curSourceId + + update nextTarget nextSource (curTargetIdx + 1) + + curSourceIdx <- curSourceIdx + 2 + curTargetIdx <- curTargetIdx + 2 + else // collections very different + shouldContinue <- false + + // replace many + while (curSourceIdx < sourceCount && curTargetIdx < targetCount) do + let curSource = source.[curSourceIdx] + let curTarget = target.GetAt curTargetIdx + + let curSourceId = getSourceId curSource + let curTargetId = getTargetId curTarget - match mNextSource, mNextTarget with - | Some (nextSource, _, true), Some (nextTarget, _, true) -> // swap adjacent - target.SetAt (curTargetIdx, nextTarget) - target.SetAt (curTargetIdx + 1, curTarget) - - update curTarget nextSource (curTargetIdx + 1) - update nextTarget curSource curTargetIdx - - curSourceIdx <- curSourceIdx + 2 - curTargetIdx <- curTargetIdx + 2 - | None, Some (nextTarget, _, true) - | Some (_, _, false), Some (nextTarget, _, true) -> // remove recordRemoval curTargetIdx curTarget curTargetId - - update nextTarget curSource curTargetIdx + recordAddition curSourceIdx curSource curSourceId curSourceIdx <- curSourceIdx + 1 - curTargetIdx <- curTargetIdx + 2 - | Some (nextSource, _, true), None - | Some (nextSource, _, true), Some (_, _, false) -> // add - recordAddition curSourceIdx curSource curSourceId + curTargetIdx <- curTargetIdx + 1 - update curTarget nextSource (curTargetIdx + 1) + // remove many + for i in targetCount - 1 .. -1 .. curTargetIdx do + let t = target.GetAt i + let id = getTargetId t + recordRemoval i t id - curSourceIdx <- curSourceIdx + 2 - curTargetIdx <- curTargetIdx + 1 - | Some (_, _, false), None - | None, Some (_, _, false) - | None, None -> // source and target have different lengths and we have reached the end of one - shouldContinue <- false - | Some (nextSource, nextSourceId, false), Some (nextTarget, nextTargetId, false) -> - if nextSourceId = nextTargetId then // replace - recordRemoval curTargetIdx curTarget curTargetId - recordAddition curSourceIdx curSource curSourceId - - update nextTarget nextSource (curTargetIdx + 1) - - curSourceIdx <- curSourceIdx + 2 - curTargetIdx <- curTargetIdx + 2 - else // collections very different - shouldContinue <- false - - // replace many - while (curSourceIdx < sourceCount && curTargetIdx < targetCount) do - let curSource = source.[curSourceIdx] - let curTarget = target.GetAt curTargetIdx - - let curSourceId = getSourceId curSource - let curTargetId = getTargetId curTarget - - recordRemoval curTargetIdx curTarget curTargetId - recordAddition curSourceIdx curSource curSourceId - - curSourceIdx <- curSourceIdx + 1 - curTargetIdx <- curTargetIdx + 1 - - // remove many - for i in targetCount - 1..-1..curTargetIdx do - let t = target.GetAt i - let id = getTargetId t - recordRemoval i t id - - // add many - for i in curSourceIdx..sourceCount - 1 do - let s = source.[i] - let id = getSourceId s - recordAddition i s id - - let moves = - additions - |> Seq.toList // make copy of additions so that calling Remove doesn't happen on the same data structure while enumerating - |> List.collect (fun (Kvp (id, (sIdx, s))) -> - removals - |> Dictionary.tryFind id - |> Option.map (fun (tIdx, t) -> - removals.Remove id |> ignore - additions.Remove id |> ignore - (tIdx, sIdx, t, s) |> List.singleton) - |> Option.defaultValue []) - - let actuallyRemove () = - Seq.empty - |> Seq.append (removals |> Seq.map (Kvp.value >> fst)) - |> Seq.append (moves |> Seq.map (fun (tIdx, _, _, _) -> tIdx)) - |> Seq.sortDescending // remove by index from largest to smallest - |> Seq.iter target.RemoveAt - - let actuallyAdd () = - Seq.empty - |> Seq.append (additions |> Seq.map (fun (Kvp (id, (idx, s))) -> idx, create s id)) - |> Seq.append (moves |> Seq.map (fun (_, sIdx, t, _) -> sIdx, t)) - |> Seq.sortBy fst // insert by index from smallest to largest - |> Seq.iter target.InsertAt - - match moves, removals.Count, additions.Count with - | [ (tIdx, sIdx, _, _) ], 0, 0 -> // single move - target.Move(tIdx, sIdx) - | [ (t1Idx, s1Idx, _, _); (t2Idx, s2Idx, _, _) ], 0, 0 when t1Idx = s2Idx && t2Idx = s1Idx-> // single swap - let temp = target.GetAt t1Idx - target.SetAt (t1Idx, target.GetAt t2Idx) - target.SetAt (t2Idx, temp) - | _, rc, _ when rc = targetCount && rc > 0 -> // remove everything (implies moves = []) - target.Clear () - actuallyAdd () - | _ -> - actuallyRemove () - actuallyAdd () - - // update moved elements - moves |> Seq.iter (fun (_, sIdx, t, s) -> update t s sIdx) \ No newline at end of file + // add many + for i in curSourceIdx .. sourceCount - 1 do + let s = source.[i] + let id = getSourceId s + recordAddition i s id + + let moves = + additions + |> Seq.toList // make copy of additions so that calling Remove doesn't happen on the same data structure while enumerating + |> List.collect (fun (Kvp(id, (sIdx, s))) -> + removals + |> Dictionary.tryFind id + |> Option.map (fun (tIdx, t) -> + removals.Remove id |> ignore + additions.Remove id |> ignore + (tIdx, sIdx, t, s) |> List.singleton) + |> Option.defaultValue []) + + let actuallyRemove () = + Seq.empty + |> Seq.append (removals |> Seq.map (Kvp.value >> fst)) + |> Seq.append (moves |> Seq.map (fun (tIdx, _, _, _) -> tIdx)) + |> Seq.sortDescending // remove by index from largest to smallest + |> Seq.iter target.RemoveAt + + let actuallyAdd () = + Seq.empty + |> Seq.append (additions |> Seq.map (fun (Kvp(id, (idx, s))) -> idx, create s id)) + |> Seq.append (moves |> Seq.map (fun (_, sIdx, t, _) -> sIdx, t)) + |> Seq.sortBy fst // insert by index from smallest to largest + |> Seq.iter target.InsertAt + + match moves, removals.Count, additions.Count with + | [ (tIdx, sIdx, _, _) ], 0, 0 -> // single move + target.Move(tIdx, sIdx) + | [ (t1Idx, s1Idx, _, _); (t2Idx, s2Idx, _, _) ], 0, 0 when t1Idx = s2Idx && t2Idx = s1Idx -> // single swap + let temp = target.GetAt t1Idx + target.SetAt(t1Idx, target.GetAt t2Idx) + target.SetAt(t2Idx, temp) + | _, rc, _ when rc = targetCount && rc > 0 -> // remove everything (implies moves = []) + target.Clear() + actuallyAdd () + | _ -> + actuallyRemove () + actuallyAdd () + + // update moved elements + moves |> Seq.iter (fun (_, sIdx, t, s) -> update t s sIdx) \ No newline at end of file diff --git a/src/Elmish.WPF/Utils.fs b/src/Elmish.WPF/Utils.fs index 2f6be341..28450e56 100644 --- a/src/Elmish.WPF/Utils.fs +++ b/src/Elmish.WPF/Utils.fs @@ -14,34 +14,36 @@ open System.Reflection /// Returns a fast, untyped getter for the property specified by the PropertyInfo. /// The getter takes an instance and returns a property value. let buildUntypedGetter (propertyInfo: PropertyInfo) : obj -> obj = - let method = propertyInfo.GetMethod - let objExpr = Expression.Parameter(typeof, "o") - let expr = - Expression.Lambda>( - Expression.Convert( - Expression.Call( - Expression.Convert(objExpr, method.DeclaringType), method), - typeof), - objExpr) - let action = expr.Compile() - fun target -> action.Invoke(target) + let method = propertyInfo.GetMethod + let objExpr = Expression.Parameter(typeof, "o") + + let expr = + Expression.Lambda>( + Expression.Convert(Expression.Call(Expression.Convert(objExpr, method.DeclaringType), method), typeof), + objExpr + ) + + let action = expr.Compile() + fun target -> action.Invoke(target) type private ElmEq<'a>() = - static let gettersAndEq = - typeof<'a>.GetProperties() - |> Array.map (fun pi -> - let getter = buildUntypedGetter pi - let eq = - if pi.PropertyType.IsValueType || pi.PropertyType = typeof - then (fun (a, b) -> a = b) - else obj.ReferenceEquals - getter, eq - ) + static let gettersAndEq = + typeof<'a>.GetProperties() + |> Array.map (fun pi -> + let getter = buildUntypedGetter pi + + let eq = + if pi.PropertyType.IsValueType || pi.PropertyType = typeof then + (fun (a, b) -> a = b) + else + obj.ReferenceEquals + + getter, eq) - static member Eq x1 x2 = - gettersAndEq |> Array.forall (fun (get, eq) -> eq (get (box x1), get (box x2))) + static member Eq x1 x2 = + gettersAndEq |> Array.forall (fun (get, eq) -> eq (get (box x1), get (box x2))) /// Memberwise equality where value-typed members and string members are @@ -52,5 +54,4 @@ type private ElmEq<'a>() = /// normally immutable. For a direct reference equality check (not memberwise), /// see refEq (which should be used when passing a single non-string reference /// type from the model). -let elmEq<'a> : 'a -> 'a -> bool = - ElmEq<'a>.Eq \ No newline at end of file +let elmEq<'a> : 'a -> 'a -> bool = ElmEq<'a>.Eq \ No newline at end of file diff --git a/src/Elmish.WPF/ViewModelArgs.fs b/src/Elmish.WPF/ViewModelArgs.fs index 8ddd6f96..8f2de79b 100644 --- a/src/Elmish.WPF/ViewModelArgs.fs +++ b/src/Elmish.WPF/ViewModelArgs.fs @@ -5,48 +5,48 @@ open Microsoft.Extensions.Logging.Abstractions type internal LoggingViewModelArgs = - { performanceLogThresholdMs: int - log: ILogger - logPerformance: ILogger - nameChain: string } + { performanceLogThresholdMs: int + log: ILogger + logPerformance: ILogger + nameChain: string } module internal LoggingViewModelArgs = - let getNameChainFor nameChain name = - sprintf "%s.%s" nameChain name + let getNameChainFor nameChain name = sprintf "%s.%s" nameChain name - let getNameChainForItem nameChain collectionBindingName itemId = - sprintf "%s.%s.%s" nameChain collectionBindingName itemId + let getNameChainForItem nameChain collectionBindingName itemId = + sprintf "%s.%s.%s" nameChain collectionBindingName itemId - let map nameChain v = { v with nameChain = nameChain } + let map nameChain v = { v with nameChain = nameChain } - let none = - { performanceLogThresholdMs = 1 - log = NullLogger.Instance - logPerformance = NullLogger.Instance - nameChain = "" } + let none = + { performanceLogThresholdMs = 1 + log = NullLogger.Instance + logPerformance = NullLogger.Instance + nameChain = "" } type ViewModelArgs<'model, 'msg> = - internal - { initialModel: 'model - dispatch: 'msg -> unit - loggingArgs: LoggingViewModelArgs } + internal + { initialModel: 'model + dispatch: 'msg -> unit + loggingArgs: LoggingViewModelArgs } module ViewModelArgs = - let internal create initialModel dispatch nameChain loggingArgs = - { initialModel = initialModel - dispatch = dispatch - loggingArgs = LoggingViewModelArgs.map nameChain loggingArgs } - - let map mapModel mapMsg v = - { initialModel = v.initialModel |> mapModel - dispatch = mapMsg >> v.dispatch - loggingArgs = v.loggingArgs } - - let createWithoutLogging initialModel dispatch = - { initialModel = initialModel - dispatch = dispatch - loggingArgs = LoggingViewModelArgs.none } - - let simple initialModel = createWithoutLogging initialModel ignore + let internal create initialModel dispatch nameChain loggingArgs = + { initialModel = initialModel + dispatch = dispatch + loggingArgs = LoggingViewModelArgs.map nameChain loggingArgs } + + let map mapModel mapMsg v = + { initialModel = v.initialModel |> mapModel + dispatch = mapMsg >> v.dispatch + loggingArgs = v.loggingArgs } + + let createWithoutLogging initialModel dispatch = + { initialModel = initialModel + dispatch = dispatch + loggingArgs = LoggingViewModelArgs.none } + + let simple initialModel = + createWithoutLogging initialModel ignore \ No newline at end of file diff --git a/src/Elmish.WPF/ViewModelModule.fs b/src/Elmish.WPF/ViewModelModule.fs index 761e6f12..bb63be1b 100644 --- a/src/Elmish.WPF/ViewModelModule.fs +++ b/src/Elmish.WPF/ViewModelModule.fs @@ -2,6 +2,6 @@ /// Creates a design-time view model using the given model and bindings. let designInstance (model: 'model) (bindings: Binding<'model, 'msg> list) = - let args = ViewModelArgs.simple model + let args = ViewModelArgs.simple model - DynamicViewModel(args, bindings) |> box \ No newline at end of file + DynamicViewModel(args, bindings) |> box \ No newline at end of file diff --git a/src/Elmish.WPF/ViewModels.fs b/src/Elmish.WPF/ViewModels.fs index e58c8f8a..9cdd0d29 100644 --- a/src/Elmish.WPF/ViewModels.fs +++ b/src/Elmish.WPF/ViewModels.fs @@ -10,9 +10,9 @@ open BindingVmHelpers /// Represents all necessary data used to create a binding. type Binding<'model, 'msg, 't> = - internal - { Name: string - Data: BindingData<'model, 'msg, 't> } + internal + { Name: string + Data: BindingData<'model, 'msg, 't> } type Binding<'model, 'msg> = Binding<'model, 'msg, obj> @@ -20,317 +20,444 @@ type Binding<'model, 'msg> = Binding<'model, 'msg, obj> [] module internal Helpers = - let createBinding data name = - { Name = name - Data = data |> BindingData.boxT } + let createBinding data name = + { Name = name + Data = data |> BindingData.boxT } - let createBindingT data name = - { Name = name - Data = data } + let createBindingT data name = { Name = name; Data = data } - type SubModelSelectedItemLast with - member this.CompareBindings() : Binding<'model, 'msg> -> Binding<'model, 'msg> -> int = - fun a b -> this.Recursive(a.Data) - this.Recursive(b.Data) + type SubModelSelectedItemLast with + member this.CompareBindings() : Binding<'model, 'msg> -> Binding<'model, 'msg> -> int = + fun a b -> this.Recursive(a.Data) - this.Recursive(b.Data) -type [] IViewModel<'model, 'msg> = - abstract member CurrentModel: 'model - abstract member UpdateModel: 'model -> unit +[] +type IViewModel<'model, 'msg> = + abstract member CurrentModel: 'model + abstract member UpdateModel: 'model -> unit module internal IViewModel = - let currentModel (vm: #IViewModel<'model, 'msg>) = vm.CurrentModel - let updateModel (vm: #IViewModel<'model, 'msg>, m: 'model) = vm.UpdateModel(m) + let currentModel (vm: #IViewModel<'model, 'msg>) = vm.CurrentModel + let updateModel (vm: #IViewModel<'model, 'msg>, m: 'model) = vm.UpdateModel(m) type internal ViewModelHelper<'model, 'msg> = - { GetSender: unit -> obj - LoggingArgs: LoggingViewModelArgs - Model: 'model - Bindings: Map> - ValidationErrors: Map - PropertyChanged: Event - ErrorsChanged: DelegateEvent> } - - interface INotifyPropertyChanged with - [] - member x.PropertyChanged = x.PropertyChanged.Publish - - interface INotifyDataErrorInfo with - [] - member x.ErrorsChanged = x.ErrorsChanged.Publish - member x.HasErrors = - // WPF calls this too often, so don't log https://github.com/elmish/Elmish.WPF/issues/354 - x.ValidationErrors - |> Seq.map (fun (Kvp(_, errors)) -> errors.Value) - |> Seq.filter (not << List.isEmpty) - |> (not << Seq.isEmpty) - member x.GetErrors name = - let name = name |> Option.ofObj |> Option.defaultValue "" // entity-level errors are being requested when given null or "" https://docs.microsoft.com/en-us/dotnet/api/system.componentmodel.inotifydataerrorinfo.geterrors#:~:text=null%20or%20Empty%2C%20to%20retrieve%20entity-level%20errors - x.LoggingArgs.log.LogTrace("[{BindingNameChain}] GetErrors {BindingName}", x.LoggingArgs.nameChain, name) - x.ValidationErrors - |> IReadOnlyDictionary.tryFind name - |> Option.map (fun errors -> errors.Value) - |> Option.defaultValue [] - |> (fun x -> upcast x) + { GetSender: unit -> obj + LoggingArgs: LoggingViewModelArgs + Model: 'model + Bindings: Map> + ValidationErrors: Map + PropertyChanged: Event + ErrorsChanged: DelegateEvent> } + + interface INotifyPropertyChanged with + [] + member x.PropertyChanged = x.PropertyChanged.Publish + + interface INotifyDataErrorInfo with + [] + member x.ErrorsChanged = x.ErrorsChanged.Publish + + member x.HasErrors = + // WPF calls this too often, so don't log https://github.com/elmish/Elmish.WPF/issues/354 + x.ValidationErrors + |> Seq.map (fun (Kvp(_, errors)) -> errors.Value) + |> Seq.filter (not << List.isEmpty) + |> (not << Seq.isEmpty) + + member x.GetErrors name = + let name = name |> Option.ofObj |> Option.defaultValue "" // entity-level errors are being requested when given null or "" https://docs.microsoft.com/en-us/dotnet/api/system.componentmodel.inotifydataerrorinfo.geterrors#:~:text=null%20or%20Empty%2C%20to%20retrieve%20entity-level%20errors + x.LoggingArgs.log.LogTrace("[{BindingNameChain}] GetErrors {BindingName}", x.LoggingArgs.nameChain, name) + + x.ValidationErrors + |> IReadOnlyDictionary.tryFind name + |> Option.map (fun errors -> errors.Value) + |> Option.defaultValue [] + |> (fun x -> upcast x) module internal ViewModelHelper = - let create getSender args bindings validationErrors = { - GetSender = getSender - LoggingArgs = args.loggingArgs - Model = args.initialModel - ValidationErrors = validationErrors - Bindings = bindings - PropertyChanged = Event() - ErrorsChanged = DelegateEvent>() - } - - let empty getSender args = - create getSender args Map.empty Map.empty - - let getEventsToRaise newModel helper = - helper.Bindings - |> Seq.collect (fun (Kvp (name, binding)) -> Update(helper.LoggingArgs, name).Recursive(helper.Model, newModel, binding)) - |> Seq.toList - - let raiseEvents eventsToRaise helper = - let { - log = log - nameChain = nameChain } = helper.LoggingArgs - - let raisePropertyChanged name = - log.LogTrace("[{BindingNameChain}] PropertyChanged {BindingName}", nameChain, name) - helper.PropertyChanged.Trigger(helper.GetSender (), PropertyChangedEventArgs name) - let raiseCanExecuteChanged (cmd: Command) = - cmd.RaiseCanExecuteChanged () - let raiseErrorsChanged name = - log.LogTrace("[{BindingNameChain}] ErrorsChanged {BindingName}", nameChain, name) - helper.ErrorsChanged.Trigger([| helper.GetSender (); box <| DataErrorsChangedEventArgs name |]) - - eventsToRaise - |> List.iter (function - | ErrorsChanged name -> raiseErrorsChanged name - | PropertyChanged name -> raisePropertyChanged name - | CanExecuteChanged cmd -> cmd |> raiseCanExecuteChanged) - - let getFunctionsForSubModelSelectedItem loggingArgs initializedBindings (name: string) = - let log = loggingArgs.log - initializedBindings - |> IReadOnlyDictionary.tryFind name - |> function - | Some b -> - match FuncsFromSubModelSeqKeyed().Recursive(b |> MapOutputType.unboxVm) with - | Some x -> Some x - | None -> log.LogError("SubModelSelectedItem binding referenced binding {SubModelSeqBindingName} but it is not a SubModelSeq binding", name) - None - | None -> log.LogError("SubModelSelectedItem binding referenced binding {SubModelSeqBindingName} but no binding was found with that name", name) + let create getSender args bindings validationErrors = + { GetSender = getSender + LoggingArgs = args.loggingArgs + Model = args.initialModel + ValidationErrors = validationErrors + Bindings = bindings + PropertyChanged = Event() + ErrorsChanged = DelegateEvent>() } + + let empty getSender args = + create getSender args Map.empty Map.empty + + let getEventsToRaise newModel helper = + helper.Bindings + |> Seq.collect (fun (Kvp(name, binding)) -> + Update(helper.LoggingArgs, name).Recursive(helper.Model, newModel, binding)) + |> Seq.toList + + let raiseEvents eventsToRaise helper = + let { log = log; nameChain = nameChain } = helper.LoggingArgs + + let raisePropertyChanged name = + log.LogTrace("[{BindingNameChain}] PropertyChanged {BindingName}", nameChain, name) + helper.PropertyChanged.Trigger(helper.GetSender(), PropertyChangedEventArgs name) + + let raiseCanExecuteChanged (cmd: Command) = cmd.RaiseCanExecuteChanged() + + let raiseErrorsChanged name = + log.LogTrace("[{BindingNameChain}] ErrorsChanged {BindingName}", nameChain, name) + helper.ErrorsChanged.Trigger([| helper.GetSender(); box <| DataErrorsChangedEventArgs name |]) + + eventsToRaise + |> List.iter (function + | ErrorsChanged name -> raiseErrorsChanged name + | PropertyChanged name -> raisePropertyChanged name + | CanExecuteChanged cmd -> cmd |> raiseCanExecuteChanged) + + let getFunctionsForSubModelSelectedItem loggingArgs initializedBindings (name: string) = + let log = loggingArgs.log + + initializedBindings + |> IReadOnlyDictionary.tryFind name + |> function + | Some b -> + match FuncsFromSubModelSeqKeyed().Recursive(b |> MapOutputType.unboxVm) with + | Some x -> Some x + | None -> + log.LogError( + "SubModelSelectedItem binding referenced binding {SubModelSeqBindingName} but it is not a SubModelSeq binding", + name + ) + + None + | None -> + log.LogError( + "SubModelSelectedItem binding referenced binding {SubModelSeqBindingName} but no binding was found with that name", + name + ) + None -type [] internal DynamicViewModel<'model, 'msg> - ( args: ViewModelArgs<'model, 'msg>, - bindings: Binding<'model, 'msg> list) - as this = - inherit DynamicObject() +[] +type internal DynamicViewModel<'model, 'msg>(args: ViewModelArgs<'model, 'msg>, bindings: Binding<'model, 'msg> list) as this + = + inherit DynamicObject() + + let { initialModel = initialModel + dispatch = dispatch + loggingArgs = loggingArgs } = + args + + let { log = log; nameChain = nameChain } = loggingArgs + + let (bindings, validationErrors) = + let initializeBinding initializedBindings binding = + Initialize( + loggingArgs, + binding.Name, + ViewModelHelper.getFunctionsForSubModelSelectedItem loggingArgs initializedBindings + ) + .Recursive(initialModel, dispatch, (fun () -> this |> IViewModel.currentModel), binding.Data) + + log.LogTrace("[{BindingNameChain}] Initializing bindings", nameChain) + + let bindingDict = Dictionary>(bindings.Length) + let validationDict = Dictionary() + + let sortedBindings = + bindings |> List.sortWith (SubModelSelectedItemLast().CompareBindings()) + + for b in sortedBindings do + if bindingDict.ContainsKey b.Name then + log.LogError( + "Binding name {BindingName} is duplicated. Only the first occurrence will be used.", + b.Name + ) + else + option { + let! vmBinding = initializeBinding bindingDict b + do bindingDict.Add(b.Name, vmBinding) + let! errorList = FirstValidationErrors().Recursive(vmBinding) + do validationDict.Add(b.Name, errorList) + return () + } + |> Option.defaultValue () + + (bindingDict |> Seq.map (|KeyValue|) |> Map.ofSeq, validationDict |> Seq.map (|KeyValue|) |> Map.ofSeq) + + let mutable helper = + ViewModelHelper.create (fun () -> this) args bindings validationErrors + + interface IViewModel<'model, 'msg> with + member _.CurrentModel: 'model = helper.Model + + member _.UpdateModel(newModel: 'model) : unit = + let prevHasErrors = (helper :> INotifyDataErrorInfo).HasErrors + let eventsToRaise = ViewModelHelper.getEventsToRaise newModel helper + helper <- { helper with Model = newModel } + + let eventsToRaise = + if prevHasErrors = (helper :> INotifyDataErrorInfo).HasErrors then + eventsToRaise + else + (PropertyChanged "HasErrors") :: eventsToRaise + + ViewModelHelper.raiseEvents eventsToRaise helper + + override _.TryGetMember(binder, result) = + log.LogTrace("[{BindingNameChain}] TryGetMember {BindingName}", nameChain, binder.Name) + + match bindings.TryGetValue binder.Name with + | false, _ -> + log.LogError( + "[{BindingNameChain}] TryGetMember FAILED: Property {BindingName} doesn't exist", + nameChain, + binder.Name + ) + + false + | true, binding -> + try + match Get(nameChain).Recursive(helper.Model, binding) with + | Ok v -> + result <- v + true + | Error e -> + match e with + | GetError.OneWayToSource -> + log.LogError( + "[{BindingNameChain}] TryGetMember FAILED: Binding {BindingName} is read-only", + nameChain, + binder.Name + ) + | GetError.SubModelSelectedItem d -> + log.LogError( + "[{BindingNameChain}] TryGetMember FAILED: Failed to find an element of the SubModelSeq binding {SubModelSeqBindingName} with ID {ID} in the getter for the binding {BindingName}", + d.NameChain, + d.SubModelSeqBindingName, + d.Id, + binder.Name + ) + | GetError.ToNullError(ValueOption.ToNullError.ValueCannotBeNull nonNullTypeName) -> + log.LogError( + "[{BindingNameChain}] TryGetMember FAILED: Binding {BindingName} is null, but type {Type} is non-nullable", + nameChain, + binder.Name, + nonNullTypeName + ) + + false + with e -> + log.LogError( + e, + "[{BindingNameChain}] TryGetMember FAILED: Exception thrown while processing binding {BindingName}", + nameChain, + binder.Name + ) + + reraise () + + override _.TrySetMember(binder, value) = + log.LogTrace("[{BindingNameChain}] TrySetMember {BindingName}", nameChain, binder.Name) + + match bindings.TryGetValue binder.Name with + | false, _ -> + log.LogError( + "[{BindingNameChain}] TrySetMember FAILED: Property {BindingName} doesn't exist", + nameChain, + binder.Name + ) + + false + | true, binding -> + try + let success = Set(value).Recursive(helper.Model, binding) + + if not success then + log.LogError( + "[{BindingNameChain}] TrySetMember FAILED: Binding {BindingName} is read-only", + nameChain, + binder.Name + ) + + success + with e -> + log.LogError( + e, + "[{BindingNameChain}] TrySetMember FAILED: Exception thrown while processing binding {BindingName}", + nameChain, + binder.Name + ) + + reraise () + + override _.GetDynamicMemberNames() = + log.LogTrace("[{BindingNameChain}] GetDynamicMemberNames", nameChain) + bindings.Keys + + + interface INotifyPropertyChanged with + [] + member _.PropertyChanged = (helper :> INotifyPropertyChanged).PropertyChanged + + interface INotifyDataErrorInfo with + [] + member _.ErrorsChanged = (helper :> INotifyDataErrorInfo).ErrorsChanged + + member _.HasErrors = (helper :> INotifyDataErrorInfo).HasErrors + + member _.GetErrors name = + (helper :> INotifyDataErrorInfo).GetErrors name - let { initialModel = initialModel - dispatch = dispatch - loggingArgs = loggingArgs - } = args +open System.Runtime.CompilerServices - let { log = log - nameChain = nameChain - } = loggingArgs +[] +type ViewModelBase<'model, 'msg>(args: ViewModelArgs<'model, 'msg>) as this = - let (bindings, validationErrors) = - let initializeBinding initializedBindings binding = - Initialize(loggingArgs, binding.Name, ViewModelHelper.getFunctionsForSubModelSelectedItem loggingArgs initializedBindings) - .Recursive(initialModel, dispatch, (fun () -> this |> IViewModel.currentModel), binding.Data) - - log.LogTrace("[{BindingNameChain}] Initializing bindings", nameChain) - - let bindingDict = Dictionary>(bindings.Length) - let validationDict = Dictionary() - - let sortedBindings = - bindings - |> List.sortWith (SubModelSelectedItemLast().CompareBindings()) - for b in sortedBindings do - if bindingDict.ContainsKey b.Name then - log.LogError("Binding name {BindingName} is duplicated. Only the first occurrence will be used.", b.Name) - else - option { - let! vmBinding = initializeBinding bindingDict b - do bindingDict.Add(b.Name, vmBinding) - let! errorList = FirstValidationErrors().Recursive(vmBinding) - do validationDict.Add(b.Name, errorList) - return () - } |> Option.defaultValue () - (bindingDict |> Seq.map (|KeyValue|) |> Map.ofSeq, - validationDict |> Seq.map (|KeyValue|) |> Map.ofSeq) - - let mutable helper = - ViewModelHelper.create - (fun () -> this) - args - bindings - validationErrors - - interface IViewModel<'model, 'msg> with - member _.CurrentModel : 'model = helper.Model - - member _.UpdateModel (newModel: 'model) : unit = - let prevHasErrors = (helper :> INotifyDataErrorInfo).HasErrors - let eventsToRaise = ViewModelHelper.getEventsToRaise newModel helper - helper <- { helper with Model = newModel } - let eventsToRaise = if prevHasErrors = (helper :> INotifyDataErrorInfo).HasErrors then eventsToRaise else (PropertyChanged "HasErrors") :: eventsToRaise - ViewModelHelper.raiseEvents eventsToRaise helper - - override _.TryGetMember (binder, result) = - log.LogTrace("[{BindingNameChain}] TryGetMember {BindingName}", nameChain, binder.Name) - match bindings.TryGetValue binder.Name with - | false, _ -> - log.LogError("[{BindingNameChain}] TryGetMember FAILED: Property {BindingName} doesn't exist", nameChain, binder.Name) - false - | true, binding -> - try - match Get(nameChain).Recursive(helper.Model, binding) with - | Ok v -> - result <- v - true - | Error e -> - match e with - | GetError.OneWayToSource -> log.LogError("[{BindingNameChain}] TryGetMember FAILED: Binding {BindingName} is read-only", nameChain, binder.Name) - | GetError.SubModelSelectedItem d -> log.LogError("[{BindingNameChain}] TryGetMember FAILED: Failed to find an element of the SubModelSeq binding {SubModelSeqBindingName} with ID {ID} in the getter for the binding {BindingName}", d.NameChain, d.SubModelSeqBindingName, d.Id, binder.Name) - | GetError.ToNullError (ValueOption.ToNullError.ValueCannotBeNull nonNullTypeName) -> log.LogError("[{BindingNameChain}] TryGetMember FAILED: Binding {BindingName} is null, but type {Type} is non-nullable", nameChain, binder.Name, nonNullTypeName) - false - with e -> - log.LogError(e, "[{BindingNameChain}] TryGetMember FAILED: Exception thrown while processing binding {BindingName}", nameChain, binder.Name) - reraise () - - override _.TrySetMember (binder, value) = - log.LogTrace("[{BindingNameChain}] TrySetMember {BindingName}", nameChain, binder.Name) - match bindings.TryGetValue binder.Name with - | false, _ -> - log.LogError("[{BindingNameChain}] TrySetMember FAILED: Property {BindingName} doesn't exist", nameChain, binder.Name) - false - | true, binding -> - try - let success = Set(value).Recursive(helper.Model, binding) - if not success then - log.LogError("[{BindingNameChain}] TrySetMember FAILED: Binding {BindingName} is read-only", nameChain, binder.Name) - success - with e -> - log.LogError(e, "[{BindingNameChain}] TrySetMember FAILED: Exception thrown while processing binding {BindingName}", nameChain, binder.Name) - reraise () - - override _.GetDynamicMemberNames () = - log.LogTrace("[{BindingNameChain}] GetDynamicMemberNames", nameChain) - bindings.Keys - - - interface INotifyPropertyChanged with - [] - member _.PropertyChanged = (helper :> INotifyPropertyChanged).PropertyChanged - - interface INotifyDataErrorInfo with - [] - member _.ErrorsChanged = (helper :> INotifyDataErrorInfo).ErrorsChanged - member _.HasErrors = (helper :> INotifyDataErrorInfo).HasErrors - member _.GetErrors name = (helper :> INotifyDataErrorInfo).GetErrors name + let mutable setBindings = Map.empty> -open System.Runtime.CompilerServices + let mutable helper = ViewModelHelper.empty (fun () -> this) args -type [] ViewModelBase<'model, 'msg>(args: ViewModelArgs<'model, 'msg>) - as this = - - let mutable setBindings = Map.empty> - - let mutable helper = ViewModelHelper.empty (fun () -> this) args - - let { loggingArgs = loggingArgs - initialModel = initialModel - dispatch = dispatch } = args - let { log = log; nameChain = nameChain } = loggingArgs - - let initializeBinding initializedBindings binding = - Initialize(loggingArgs, binding.Name, ViewModelHelper.getFunctionsForSubModelSelectedItem loggingArgs initializedBindings) - .Recursive(initialModel, dispatch, (fun () -> this |> IViewModel.currentModel), binding.Data) - - member _.Get<'a> ([] ?memberName: string) = - fun (binding: string -> Binding<'model, 'msg, 'a>) -> - let result = - option { - let! name = memberName - let! vmBinding = option { - match helper.Bindings.TryGetValue name with - | true, value -> - return value |> MapOutputType.unboxVm - | false, _ -> - let binding = binding name - let! vmBinding = binding |> initializeBinding helper.Bindings - let newBindings = helper.Bindings.Add (name, vmBinding |> MapOutputType.boxVm) - let newValidationErrors = - FirstValidationErrors().Recursive(vmBinding) - |> Option.map (fun errorList -> helper.ValidationErrors.Add (name, errorList)) - |> Option.defaultValue helper.ValidationErrors - helper <- - { helper with - Bindings = newBindings - ValidationErrors = newValidationErrors } - return vmBinding - } - return Get(nameChain).Recursive(helper.Model, vmBinding) - } - match result with - | None -> - log.LogError("[{BindingNameChain}] Get FAILED: Binding {BindingName} could not be constructed", nameChain, memberName) - failwithf $"[%s{nameChain}] Get FAILED: Binding {memberName} could not be constructed" - | Some (Error e) -> - match e with - | GetError.OneWayToSource -> log.LogError("[{BindingNameChain}] Get FAILED: Binding {BindingName} is read-only", nameChain, memberName) - | GetError.SubModelSelectedItem d -> log.LogError("[{BindingNameChain}] Get FAILED: Failed to find an element of the SubModelSeq binding {SubModelSeqBindingName} with ID {ID} in the getter for the binding {BindingName}", d.NameChain, d.SubModelSeqBindingName, d.Id, memberName) - | GetError.ToNullError (ValueOption.ToNullError.ValueCannotBeNull nonNullTypeName) -> log.LogError("[{BindingNameChain}] Get FAILED: Binding {BindingName} is null, but type {Type} is non-nullable", nameChain, memberName, nonNullTypeName) - failwithf $"[%s{nameChain}] Get FAILED: Binding {memberName} returned an error {e}" - | Some (Ok r) -> r - - member _.Set<'a> (value: 'a, [] ?memberName: string) = - fun (binding: string -> Binding<'model, 'msg, 'a>) -> - try - let success = - option { - let! name = memberName - let! vmBinding = option { - match setBindings.TryGetValue name with - | true, value -> - return value |> MapOutputType.unboxVm - | false, _ -> - let binding = binding name - let! vmBinding = initializeBinding helper.Bindings binding - setBindings <- setBindings.Add (name, vmBinding |> MapOutputType.boxVm) - return vmBinding - } - return Set(value).Recursive(helper.Model, vmBinding) - } - if success = Some false then - log.LogError("[{BindingNameChain}] Set FAILED: Binding {BindingName} is read-only", nameChain, memberName) - else if success = None then - log.LogError("[{BindingNameChain}] Set FAILED: Binding {BindingName} could not be constructed", nameChain, memberName) - with e -> - log.LogError(e, "[{BindingNameChain}] Set FAILED: Exception thrown while processing binding {BindingName}", nameChain, memberName) - reraise () - - interface IViewModel<'model, 'msg> with - member _.CurrentModel = helper.Model - - member _.UpdateModel(newModel: 'model) = - let eventsToRaise = ViewModelHelper.getEventsToRaise newModel helper - helper <- { helper with Model = newModel } - ViewModelHelper.raiseEvents eventsToRaise helper - - interface INotifyPropertyChanged with - [] - member _.PropertyChanged = (helper :> INotifyPropertyChanged).PropertyChanged - - interface INotifyDataErrorInfo with - [] - member _.ErrorsChanged = (helper :> INotifyDataErrorInfo).ErrorsChanged - member _.HasErrors = (helper :> INotifyDataErrorInfo).HasErrors - member _.GetErrors name = (helper :> INotifyDataErrorInfo).GetErrors name + let { loggingArgs = loggingArgs + initialModel = initialModel + dispatch = dispatch } = + args + + let { log = log; nameChain = nameChain } = loggingArgs + + let initializeBinding initializedBindings binding = + Initialize( + loggingArgs, + binding.Name, + ViewModelHelper.getFunctionsForSubModelSelectedItem loggingArgs initializedBindings + ) + .Recursive(initialModel, dispatch, (fun () -> this |> IViewModel.currentModel), binding.Data) + + member _.Get<'a>([] ?memberName: string) = + fun (binding: string -> Binding<'model, 'msg, 'a>) -> + let result = + option { + let! name = memberName + + let! vmBinding = + option { + match helper.Bindings.TryGetValue name with + | true, value -> return value |> MapOutputType.unboxVm + | false, _ -> + let binding = binding name + let! vmBinding = binding |> initializeBinding helper.Bindings + let newBindings = helper.Bindings.Add(name, vmBinding |> MapOutputType.boxVm) + + let newValidationErrors = + FirstValidationErrors().Recursive(vmBinding) + |> Option.map (fun errorList -> helper.ValidationErrors.Add(name, errorList)) + |> Option.defaultValue helper.ValidationErrors + + helper <- + { helper with + Bindings = newBindings + ValidationErrors = newValidationErrors } + + return vmBinding + } + + return Get(nameChain).Recursive(helper.Model, vmBinding) + } + + match result with + | None -> + log.LogError( + "[{BindingNameChain}] Get FAILED: Binding {BindingName} could not be constructed", + nameChain, + memberName + ) + + failwithf $"[%s{nameChain}] Get FAILED: Binding {memberName} could not be constructed" + | Some(Error e) -> + match e with + | GetError.OneWayToSource -> + log.LogError( + "[{BindingNameChain}] Get FAILED: Binding {BindingName} is read-only", + nameChain, + memberName + ) + | GetError.SubModelSelectedItem d -> + log.LogError( + "[{BindingNameChain}] Get FAILED: Failed to find an element of the SubModelSeq binding {SubModelSeqBindingName} with ID {ID} in the getter for the binding {BindingName}", + d.NameChain, + d.SubModelSeqBindingName, + d.Id, + memberName + ) + | GetError.ToNullError(ValueOption.ToNullError.ValueCannotBeNull nonNullTypeName) -> + log.LogError( + "[{BindingNameChain}] Get FAILED: Binding {BindingName} is null, but type {Type} is non-nullable", + nameChain, + memberName, + nonNullTypeName + ) + + failwithf $"[%s{nameChain}] Get FAILED: Binding {memberName} returned an error {e}" + | Some(Ok r) -> r + + member _.Set<'a>(value: 'a, [] ?memberName: string) = + fun (binding: string -> Binding<'model, 'msg, 'a>) -> + try + let success = + option { + let! name = memberName + + let! vmBinding = + option { + match setBindings.TryGetValue name with + | true, value -> return value |> MapOutputType.unboxVm + | false, _ -> + let binding = binding name + let! vmBinding = initializeBinding helper.Bindings binding + setBindings <- setBindings.Add(name, vmBinding |> MapOutputType.boxVm) + return vmBinding + } + + return Set(value).Recursive(helper.Model, vmBinding) + } + + if success = Some false then + log.LogError( + "[{BindingNameChain}] Set FAILED: Binding {BindingName} is read-only", + nameChain, + memberName + ) + else if success = None then + log.LogError( + "[{BindingNameChain}] Set FAILED: Binding {BindingName} could not be constructed", + nameChain, + memberName + ) + with e -> + log.LogError( + e, + "[{BindingNameChain}] Set FAILED: Exception thrown while processing binding {BindingName}", + nameChain, + memberName + ) + + reraise () + + interface IViewModel<'model, 'msg> with + member _.CurrentModel = helper.Model + + member _.UpdateModel(newModel: 'model) = + let eventsToRaise = ViewModelHelper.getEventsToRaise newModel helper + helper <- { helper with Model = newModel } + ViewModelHelper.raiseEvents eventsToRaise helper + + interface INotifyPropertyChanged with + [] + member _.PropertyChanged = (helper :> INotifyPropertyChanged).PropertyChanged + + interface INotifyDataErrorInfo with + [] + member _.ErrorsChanged = (helper :> INotifyDataErrorInfo).ErrorsChanged + + member _.HasErrors = (helper :> INotifyDataErrorInfo).HasErrors + + member _.GetErrors name = + (helper :> INotifyDataErrorInfo).GetErrors name \ No newline at end of file diff --git a/src/Elmish.WPF/WindowState.fs b/src/Elmish.WPF/WindowState.fs index c6cec452..4a9f9238 100644 --- a/src/Elmish.WPF/WindowState.fs +++ b/src/Elmish.WPF/WindowState.fs @@ -2,50 +2,45 @@ [] type WindowState<'model> = - | Closed - | Hidden of 'model - | Visible of 'model + | Closed + | Hidden of 'model + | Visible of 'model module WindowState = - let cata a f g = function - | WindowState.Closed -> a - | WindowState.Hidden a -> a |> f - | WindowState.Visible a -> a |> g - - let map f = - cata - WindowState.Closed - (f >> WindowState.Hidden) - (f >> WindowState.Visible) - let set a = map (fun _ -> a) - - let toHidden a = - cata - (WindowState.Hidden a) - WindowState.Hidden - WindowState.Hidden - - let toVisible a = - cata - (WindowState.Visible a) - WindowState.Visible - WindowState.Visible - - let toOption state = state |> cata None Some Some - let toVOption state = state |> cata ValueNone ValueSome ValueSome - - /// Converts None to WindowState.Closed, and Some(x) to - /// WindowState.Visible(x). - let ofOption (model: 'model option) = - match model with - | Some a -> a |> WindowState.Visible - | None -> WindowState.Closed - - /// Converts ValueNone to WindowState.Closed, and ValueSome(x) to - /// WindowState.Visible(x). - let ofVOption (model: 'model voption) = - match model with - | ValueSome a -> a |> WindowState.Visible - | ValueNone -> WindowState.Closed \ No newline at end of file + let cata a f g = + function + | WindowState.Closed -> a + | WindowState.Hidden a -> a |> f + | WindowState.Visible a -> a |> g + + let map f = + cata WindowState.Closed (f >> WindowState.Hidden) (f >> WindowState.Visible) + + let set a = map (fun _ -> a) + + let toHidden a = + cata (WindowState.Hidden a) WindowState.Hidden WindowState.Hidden + + let toVisible a = + cata (WindowState.Visible a) WindowState.Visible WindowState.Visible + + let toOption state = state |> cata None Some Some + + let toVOption state = + state |> cata ValueNone ValueSome ValueSome + + /// Converts None to WindowState.Closed, and Some(x) to + /// WindowState.Visible(x). + let ofOption (model: 'model option) = + match model with + | Some a -> a |> WindowState.Visible + | None -> WindowState.Closed + + /// Converts ValueNone to WindowState.Closed, and ValueSome(x) to + /// WindowState.Visible(x). + let ofVOption (model: 'model voption) = + match model with + | ValueSome a -> a |> WindowState.Visible + | ValueNone -> WindowState.Closed \ No newline at end of file diff --git a/src/Elmish.WPF/WpfProgram.fs b/src/Elmish.WPF/WpfProgram.fs index f1683fc6..e345aa3a 100644 --- a/src/Elmish.WPF/WpfProgram.fs +++ b/src/Elmish.WPF/WpfProgram.fs @@ -7,15 +7,16 @@ open Elmish type WpfProgram<'model, 'msg, 'viewModel> = - internal { - ElmishProgram: Program - CreateViewModel: ViewModelArgs<'model,'msg> -> 'viewModel - UpdateViewModel: 'viewModel * 'model -> unit - LoggerFactory: ILoggerFactory - ErrorHandler: string -> exn -> unit - /// Only log calls that take at least this many milliseconds. Default 1. - PerformanceLogThreshold: int - } + internal + { + ElmishProgram: Program + CreateViewModel: ViewModelArgs<'model, 'msg> -> 'viewModel + UpdateViewModel: 'viewModel * 'model -> unit + LoggerFactory: ILoggerFactory + ErrorHandler: string -> exn -> unit + /// Only log calls that take at least this many milliseconds. Default 1. + PerformanceLogThreshold: int + } type WpfProgram<'model, 'msg> = WpfProgram<'model, 'msg, obj> @@ -23,112 +24,120 @@ type WpfProgram<'model, 'msg> = WpfProgram<'model, 'msg, obj> [] module WpfProgram = - let private mapVm fOut fIn (p: WpfProgram<'model, 'msg, 'viewModel0>) : WpfProgram<'model, 'msg, 'viewModel1> = - { ElmishProgram = p.ElmishProgram - CreateViewModel = p.CreateViewModel >> fOut - UpdateViewModel = (fun (vm, m) -> p.UpdateViewModel(fIn vm, m)) - LoggerFactory = p.LoggerFactory - ErrorHandler = p.ErrorHandler - PerformanceLogThreshold = p.PerformanceLogThreshold } - - let private createWithBindings (getBindings: unit -> Binding<'model,'msg> list) program = - { ElmishProgram = program - CreateViewModel = fun args -> DynamicViewModel<'model,'msg>(args, getBindings ()) - UpdateViewModel = IViewModel.updateModel - LoggerFactory = NullLoggerFactory.Instance - ErrorHandler = fun _ _ -> () - PerformanceLogThreshold = 1 } - |> mapVm box unbox - - let private createWithVm (createVm: ViewModelArgs<'model, 'msg> -> #IViewModel<'model, 'msg>) program = - { ElmishProgram = program - CreateViewModel = createVm - UpdateViewModel = IViewModel.updateModel - LoggerFactory = NullLoggerFactory.Instance - ErrorHandler = fun _ _ -> () - PerformanceLogThreshold = 1 } - - - /// Creates a WpfProgram that does not use commands. - let mkSimple - (init: unit -> 'model) - (update: 'msg -> 'model -> 'model) - (bindings: unit -> Binding<'model, 'msg> list) = - Program.mkSimple init update (fun _ _ -> ()) - |> createWithBindings bindings - - - /// Creates a WpfProgram that uses commands - let mkProgram - (init: unit -> 'model * Cmd<'msg>) - (update: 'msg -> 'model -> 'model * Cmd<'msg>) - (bindings: unit -> Binding<'model, 'msg> list) = - Program.mkProgram init update (fun _ _ -> ()) - |> createWithBindings bindings - - /// Creates a WpfProgram that does not use commands. - let mkSimpleT - (init: unit -> 'model) - (update: 'msg -> 'model -> 'model) - (createVm: ViewModelArgs<'model, 'msg> -> 'viewModel) = - Program.mkSimple init update (fun _ _ -> ()) - |> createWithVm createVm - - - /// Creates a WpfProgram that uses commands - let mkProgramT - (init: unit -> 'model * Cmd<'msg>) - (update: 'msg -> 'model -> 'model * Cmd<'msg>) - (createVm: ViewModelArgs<'model, 'msg> -> 'viewModel) = - Program.mkProgram init update (fun _ _ -> ()) - |> createWithVm createVm - - [] - type ElmishThreaderBehavior = - | SingleThreaded - | Threaded_NoUIDispatch - | Threaded_PendingUIDispatch of pending: System.Threading.Tasks.TaskCompletionSource unit> - | Threaded_UIDispatch of active: System.Threading.Tasks.TaskCompletionSource unit> - - /// Starts an Elmish dispatch loop, setting the bindings as the DataContext for the - /// specified FrameworkElement. Non-blocking. If you have an explicit entry point where - /// you control app/window instantiation, runWindowWithConfig might be a better option. - /// - /// If you execute this from a thread other than the thread owning element.Dispatcher (UI Thread), - /// Elmish.WPF will use that background thread to run updates rather than the main UI thread. - /// Example multithreaded use: - /// - /// WpfProgram.startElmishLoop window program - /// Dispatcher.Run())) - /// elmishThread.Name <- "ElmishDispatchThread" - /// elmishThread.Run() - /// - /// mainWindow.Show() - /// let result = Application.Current.Run mainWindow - /// - /// Threading.Dispatcher.FromThread(elmishThread).InvokeShutdown() - /// elmishThread.Join() - /// ]]> - /// - /// - /// - let startElmishLoop - (element: FrameworkElement) - (program: WpfProgram<'model, 'msg, 'viewModel>) = - let mutable viewModel = None - - let updateLogger = program.LoggerFactory.CreateLogger("Elmish.WPF.Update") - let bindingsLogger = program.LoggerFactory.CreateLogger("Elmish.WPF.Bindings") - let performanceLogger = program.LoggerFactory.CreateLogger("Elmish.WPF.Performance") - - let measure callName f = BindingVmHelpers.Helpers2.measure performanceLogger LogLevel.Debug program.PerformanceLogThreshold "" "main" callName f - - let program = { program with UpdateViewModel = measure "updateViewModel" program.UpdateViewModel } - - (* + let private mapVm fOut fIn (p: WpfProgram<'model, 'msg, 'viewModel0>) : WpfProgram<'model, 'msg, 'viewModel1> = + { ElmishProgram = p.ElmishProgram + CreateViewModel = p.CreateViewModel >> fOut + UpdateViewModel = (fun (vm, m) -> p.UpdateViewModel(fIn vm, m)) + LoggerFactory = p.LoggerFactory + ErrorHandler = p.ErrorHandler + PerformanceLogThreshold = p.PerformanceLogThreshold } + + let private createWithBindings (getBindings: unit -> Binding<'model, 'msg> list) program = + { ElmishProgram = program + CreateViewModel = fun args -> DynamicViewModel<'model, 'msg>(args, getBindings ()) + UpdateViewModel = IViewModel.updateModel + LoggerFactory = NullLoggerFactory.Instance + ErrorHandler = fun _ _ -> () + PerformanceLogThreshold = 1 } + |> mapVm box unbox + + let private createWithVm (createVm: ViewModelArgs<'model, 'msg> -> #IViewModel<'model, 'msg>) program = + { ElmishProgram = program + CreateViewModel = createVm + UpdateViewModel = IViewModel.updateModel + LoggerFactory = NullLoggerFactory.Instance + ErrorHandler = fun _ _ -> () + PerformanceLogThreshold = 1 } + + + /// Creates a WpfProgram that does not use commands. + let mkSimple + (init: unit -> 'model) + (update: 'msg -> 'model -> 'model) + (bindings: unit -> Binding<'model, 'msg> list) + = + Program.mkSimple init update (fun _ _ -> ()) |> createWithBindings bindings + + + /// Creates a WpfProgram that uses commands + let mkProgram + (init: unit -> 'model * Cmd<'msg>) + (update: 'msg -> 'model -> 'model * Cmd<'msg>) + (bindings: unit -> Binding<'model, 'msg> list) + = + Program.mkProgram init update (fun _ _ -> ()) |> createWithBindings bindings + + /// Creates a WpfProgram that does not use commands. + let mkSimpleT + (init: unit -> 'model) + (update: 'msg -> 'model -> 'model) + (createVm: ViewModelArgs<'model, 'msg> -> 'viewModel) + = + Program.mkSimple init update (fun _ _ -> ()) |> createWithVm createVm + + + /// Creates a WpfProgram that uses commands + let mkProgramT + (init: unit -> 'model * Cmd<'msg>) + (update: 'msg -> 'model -> 'model * Cmd<'msg>) + (createVm: ViewModelArgs<'model, 'msg> -> 'viewModel) + = + Program.mkProgram init update (fun _ _ -> ()) |> createWithVm createVm + + [] + type ElmishThreaderBehavior = + | SingleThreaded + | Threaded_NoUIDispatch + | Threaded_PendingUIDispatch of pending: System.Threading.Tasks.TaskCompletionSource unit> + | Threaded_UIDispatch of active: System.Threading.Tasks.TaskCompletionSource unit> + + /// Starts an Elmish dispatch loop, setting the bindings as the DataContext for the + /// specified FrameworkElement. Non-blocking. If you have an explicit entry point where + /// you control app/window instantiation, runWindowWithConfig might be a better option. + /// + /// If you execute this from a thread other than the thread owning element.Dispatcher (UI Thread), + /// Elmish.WPF will use that background thread to run updates rather than the main UI thread. + /// Example multithreaded use: + /// + /// WpfProgram.startElmishLoop window program + /// Dispatcher.Run())) + /// elmishThread.Name <- "ElmishDispatchThread" + /// elmishThread.Run() + /// + /// mainWindow.Show() + /// let result = Application.Current.Run mainWindow + /// + /// Threading.Dispatcher.FromThread(elmishThread).InvokeShutdown() + /// elmishThread.Join() + /// ]]> + /// + /// + /// + let startElmishLoop (element: FrameworkElement) (program: WpfProgram<'model, 'msg, 'viewModel>) = + let mutable viewModel = None + + let updateLogger = program.LoggerFactory.CreateLogger("Elmish.WPF.Update") + let bindingsLogger = program.LoggerFactory.CreateLogger("Elmish.WPF.Bindings") + let performanceLogger = program.LoggerFactory.CreateLogger("Elmish.WPF.Performance") + + let measure callName f = + BindingVmHelpers.Helpers2.measure + performanceLogger + LogLevel.Debug + program.PerformanceLogThreshold + "" + "main" + callName + f + + let program = + { program with + UpdateViewModel = measure "updateViewModel" program.UpdateViewModel } + + (* * Capture the dispatch function before wrapping it with Dispatcher.InvokeAsync * so that the UI can synchronously dispatch messages. * In additional to being slightly more efficient, @@ -139,139 +148,146 @@ module WpfProgram = * This is definitely a hack. * Maybe something with Elmish can change so this hack can be avoided. *) - let mutable dispatch = Unchecked.defaultof> - - let elmishDispatcher = Threading.Dispatcher.CurrentDispatcher - let mutable threader = - if element.Dispatcher = elmishDispatcher then - SingleThreaded - else - Threaded_NoUIDispatch - - // Dispatch that comes in from a view model message (setter or WPF ICommand). These may come from UI thread, so must be streated specially - let dispatchFromViewModel msg = - if element.Dispatcher = Threading.Dispatcher.CurrentDispatcher then // if the message is from the UI thread - match threader with - | SingleThreaded -> dispatch msg // Dispatch directly if `elmishDispatcher` is the same as the UI thread - | Threaded_NoUIDispatch -> // If `elmishDispatcher` is different, invoke dispatch on it then wait around for it to finish executing, then execute the continuation on the current (UI) thread - let uiWaiter = System.Threading.Tasks.TaskCompletionSource unit>() - threader <- Threaded_PendingUIDispatch uiWaiter - - // This should always leave `threader` in the `Threaded_NoUIDispatch` state before leaving this thread invocation - let synchronizedUiDispatch () = - threader <- Threaded_UIDispatch uiWaiter - dispatch msg - threader <- Threaded_NoUIDispatch - - elmishDispatcher.InvokeAsync(synchronizedUiDispatch) |> ignore - // Wait on `elmishDispatcher` to get to this invocation and collect result - let continuationOnUIThread = uiWaiter.Task.Result - // Result is the `program.UpdateViewModel` call, so execute here on the UI thread - continuationOnUIThread() - | Threaded_PendingUIDispatch uiWaiter - | Threaded_UIDispatch uiWaiter -> - uiWaiter.SetException(exn("Error in core Elmish.WPF threading code. Invalid state reached!")) - else // message is not from the UI thread - elmishDispatcher.InvokeAsync(fun () -> dispatch msg) |> ignore // handle as a command message - - // Core Elmish calls this from `dispatch`, which means this is always called from `elmishDispatcher` - // (which is UI thread in single-threaded case) - let mutable pendingModel = ValueNone - let mutable ct = 0 - let setUiState model _syncDispatch = - let i = ct - ct <- ct + 1 - let scheduleJobThreadPriority = Threading.DispatcherPriority.Send - let executeJobThreadPriority = Threading.DispatcherPriority.Background - - match viewModel with - | None -> // no view model yet, so create one - let args = - { initialModel = model - dispatch = dispatchFromViewModel - loggingArgs = - { performanceLogThresholdMs = program.PerformanceLogThreshold - nameChain = "main" - log = bindingsLogger - logPerformance = performanceLogger } } - let vm = program.CreateViewModel args - element.Dispatcher.Invoke(fun () -> element.DataContext <- vm) - viewModel <- Some vm - | Some vm -> // view model exists, so update - match threader with - | Threaded_UIDispatch uiWaiter -> // We are in the specific dispatch call from the UI thread (see `synchronizedUiDispatch` in `dispatchFromViewModel`) - updateLogger.LogDebug("SetUIState {i} UIDISPATCH", i); - - let unscheduleJob () = - pendingModel <- ValueNone - updateLogger.LogDebug("Unscheduled job already completed from main thread {i}", i) - - let executeJobImmediately () = - program.UpdateViewModel (vm, model) - updateLogger.LogDebug("Update done from main thread {i}", i) - - element.Dispatcher.InvokeAsync(unscheduleJob, scheduleJobThreadPriority) |> ignore // Unschedule update (already done) - uiWaiter.SetResult(executeJobImmediately) // execute `UpdateViewModel` on UI thread - | Threaded_PendingUIDispatch _ // We are in a non-UI dispatch that updated the model before the UI got its update in, but after the user interacted - | Threaded_NoUIDispatch -> // We are in a non-UI dispatch with no pending user interactions known - updateLogger.LogDebug("SetUIState {i} NOUIDISPATCH {threader}", i, threader); - - let scheduleJob () = - pendingModel <- ValueSome model - updateLogger.LogDebug("Scheduled new job {i}", i) - - let executeJob () = - match pendingModel with - | ValueSome m -> - program.UpdateViewModel (vm, m) - pendingModel <- ValueNone - updateLogger.LogDebug("Job was full - Update done {i}", i) - | ValueNone -> - updateLogger.LogDebug("Job was empty - No update done {i}", i) - - element.Dispatcher.InvokeAsync(scheduleJob, scheduleJobThreadPriority) |> ignore // Schedule update - element.Dispatcher.InvokeAsync(executeJob, executeJobThreadPriority) |> ignore // Execute Update - | SingleThreaded -> // If we aren't using different threads, always process normally - element.Dispatcher.Invoke(fun () -> program.UpdateViewModel (vm, model)) - - let cmdDispatch (innerDispatch: Dispatch<'msg>) : Dispatch<'msg> = - let innerDispatch = measure "dispatch" innerDispatch - dispatch <- innerDispatch - (* + let mutable dispatch = Unchecked.defaultof> + + let elmishDispatcher = Threading.Dispatcher.CurrentDispatcher + + let mutable threader = + if element.Dispatcher = elmishDispatcher then + SingleThreaded + else + Threaded_NoUIDispatch + + // Dispatch that comes in from a view model message (setter or WPF ICommand). These may come from UI thread, so must be streated specially + let dispatchFromViewModel msg = + if element.Dispatcher = Threading.Dispatcher.CurrentDispatcher then // if the message is from the UI thread + match threader with + | SingleThreaded -> dispatch msg // Dispatch directly if `elmishDispatcher` is the same as the UI thread + | Threaded_NoUIDispatch -> // If `elmishDispatcher` is different, invoke dispatch on it then wait around for it to finish executing, then execute the continuation on the current (UI) thread + let uiWaiter = System.Threading.Tasks.TaskCompletionSource unit>() + threader <- Threaded_PendingUIDispatch uiWaiter + + // This should always leave `threader` in the `Threaded_NoUIDispatch` state before leaving this thread invocation + let synchronizedUiDispatch () = + threader <- Threaded_UIDispatch uiWaiter + dispatch msg + threader <- Threaded_NoUIDispatch + + elmishDispatcher.InvokeAsync(synchronizedUiDispatch) |> ignore + // Wait on `elmishDispatcher` to get to this invocation and collect result + let continuationOnUIThread = uiWaiter.Task.Result + // Result is the `program.UpdateViewModel` call, so execute here on the UI thread + continuationOnUIThread () + | Threaded_PendingUIDispatch uiWaiter + | Threaded_UIDispatch uiWaiter -> + uiWaiter.SetException(exn ("Error in core Elmish.WPF threading code. Invalid state reached!")) + else // message is not from the UI thread + elmishDispatcher.InvokeAsync(fun () -> dispatch msg) |> ignore // handle as a command message + + // Core Elmish calls this from `dispatch`, which means this is always called from `elmishDispatcher` + // (which is UI thread in single-threaded case) + let mutable pendingModel = ValueNone + let mutable ct = 0 + + let setUiState model _syncDispatch = + let i = ct + ct <- ct + 1 + let scheduleJobThreadPriority = Threading.DispatcherPriority.Send + let executeJobThreadPriority = Threading.DispatcherPriority.Background + + match viewModel with + | None -> // no view model yet, so create one + let args = + { initialModel = model + dispatch = dispatchFromViewModel + loggingArgs = + { performanceLogThresholdMs = program.PerformanceLogThreshold + nameChain = "main" + log = bindingsLogger + logPerformance = performanceLogger } } + + let vm = program.CreateViewModel args + element.Dispatcher.Invoke(fun () -> element.DataContext <- vm) + viewModel <- Some vm + | Some vm -> // view model exists, so update + match threader with + | Threaded_UIDispatch uiWaiter -> // We are in the specific dispatch call from the UI thread (see `synchronizedUiDispatch` in `dispatchFromViewModel`) + updateLogger.LogDebug("SetUIState {i} UIDISPATCH", i) + + let unscheduleJob () = + pendingModel <- ValueNone + updateLogger.LogDebug("Unscheduled job already completed from main thread {i}", i) + + let executeJobImmediately () = + program.UpdateViewModel(vm, model) + updateLogger.LogDebug("Update done from main thread {i}", i) + + element.Dispatcher.InvokeAsync(unscheduleJob, scheduleJobThreadPriority) + |> ignore // Unschedule update (already done) + + uiWaiter.SetResult(executeJobImmediately) // execute `UpdateViewModel` on UI thread + | Threaded_PendingUIDispatch _ // We are in a non-UI dispatch that updated the model before the UI got its update in, but after the user interacted + | Threaded_NoUIDispatch -> // We are in a non-UI dispatch with no pending user interactions known + updateLogger.LogDebug("SetUIState {i} NOUIDISPATCH {threader}", i, threader) + + let scheduleJob () = + pendingModel <- ValueSome model + updateLogger.LogDebug("Scheduled new job {i}", i) + + let executeJob () = + match pendingModel with + | ValueSome m -> + program.UpdateViewModel(vm, m) + pendingModel <- ValueNone + updateLogger.LogDebug("Job was full - Update done {i}", i) + | ValueNone -> updateLogger.LogDebug("Job was empty - No update done {i}", i) + + element.Dispatcher.InvokeAsync(scheduleJob, scheduleJobThreadPriority) |> ignore // Schedule update + element.Dispatcher.InvokeAsync(executeJob, executeJobThreadPriority) |> ignore // Execute Update + | SingleThreaded -> // If we aren't using different threads, always process normally + element.Dispatcher.Invoke(fun () -> program.UpdateViewModel(vm, model)) + + let cmdDispatch (innerDispatch: Dispatch<'msg>) : Dispatch<'msg> = + let innerDispatch = measure "dispatch" innerDispatch + dispatch <- innerDispatch + (* * Have commands asynchronously dispatch messages. * This avoids race conditions like those that can occur when shutting down. * https://github.com/elmish/Elmish.WPF/issues/353 *) - fun msg -> elmishDispatcher.InvokeAsync(fun () -> dispatch msg) |> ignore - - let logMsgAndModel (msg: 'msg) (model: 'model) _ = - updateLogger.LogTrace("New message: {Message}\nUpdated state:\n{Model}", msg, model) - - let errorHandler (msg: string, ex: exn) = - updateLogger.LogError(ex, msg) - program.ErrorHandler msg ex - - program.ElmishProgram - |> if updateLogger.IsEnabled LogLevel.Trace then Program.withTrace logMsgAndModel else id - |> Program.withErrorHandler errorHandler - |> Program.withSetState setUiState - |> Program.runWithDispatch cmdDispatch () - - - /// Instantiates Application and sets its MainWindow if it is not already - /// running. - let private initializeApplication window = - if isNull Application.Current then - Application () |> ignore - Application.Current.MainWindow <- window - - - /// Starts the Elmish and WPF dispatch loops. Will instantiate Application and set its - /// MainWindow if it is not already running, and then run the specified window. This is a - /// blocking function. If you are using App.xaml as an implicit entry point, see - /// startElmishLoop. - let runWindow window program = - (* + fun msg -> elmishDispatcher.InvokeAsync(fun () -> dispatch msg) |> ignore + + let logMsgAndModel (msg: 'msg) (model: 'model) _ = + updateLogger.LogTrace("New message: {Message}\nUpdated state:\n{Model}", msg, model) + + let errorHandler (msg: string, ex: exn) = + updateLogger.LogError(ex, msg) + program.ErrorHandler msg ex + + program.ElmishProgram + |> if updateLogger.IsEnabled LogLevel.Trace then + Program.withTrace logMsgAndModel + else + id + |> Program.withErrorHandler errorHandler + |> Program.withSetState setUiState + |> Program.runWithDispatch cmdDispatch () + + + /// Instantiates Application and sets its MainWindow if it is not already + /// running. + let private initializeApplication window = + if isNull Application.Current then + Application() |> ignore + Application.Current.MainWindow <- window + + + /// Starts the Elmish and WPF dispatch loops. Will instantiate Application and set its + /// MainWindow if it is not already running, and then run the specified window. This is a + /// blocking function. If you are using App.xaml as an implicit entry point, see + /// startElmishLoop. + let runWindow window program = + (* * This is the correct order for these four statements. * 1. Initialize Application.Current and set its MainWindow in case the * user code accesses either of these when initializing the bindings. @@ -280,124 +296,129 @@ module WpfProgram = * 3. Show the window now that the DataContext is set. * 4. Run the current application, which must be last because it is blocking. *) - initializeApplication window - startElmishLoop window program - window.Show () - Application.Current.Run window - - - /// Same as mkProgram, except that init and update don't return Cmd<'msg> - /// directly, but instead return a CmdMsg discriminated union that is converted - /// to Cmd<'msg> using toCmd. This means that the init and update functions - /// return only data, and thus are easier to unit test. The CmdMsg pattern is - /// general; this is just a trivial convenience function that automatically - /// converts CmdMsg to Cmd<'msg> for you in init and update. - let mkProgramWithCmdMsg - (init: unit -> 'model * 'cmdMsg list) - (update: 'msg -> 'model -> 'model * 'cmdMsg list) - (bindings: unit -> Binding<'model, 'msg> list) - (toCmd: 'cmdMsg -> Cmd<'msg>) = - let convert (model, cmdMsgs) = - model, (cmdMsgs |> List.map toCmd |> Cmd.batch) - mkProgram - (init >> convert) - (fun msg model -> update msg model |> convert) - bindings - - - /// Same as mkProgramT, except that init and update don't return Cmd<'msg> - /// directly, but instead return a CmdMsg discriminated union that is converted - /// to Cmd<'msg> using toCmd. This means that the init and update functions - /// return only data, and thus are easier to unit test. The CmdMsg pattern is - /// general; this is just a trivial convenience function that automatically - /// converts CmdMsg to Cmd<'msg> for you in init and update. - let mkProgramWithCmdMsgT - (init: unit -> 'model * 'cmdMsg list) - (update: 'msg -> 'model -> 'model * 'cmdMsg list) - (createVm: ViewModelArgs<'model, 'msg> -> 'viewModel) - (toCmd: 'cmdMsg -> Cmd<'msg>) = - let convert (model, cmdMsgs) = - model, (cmdMsgs |> List.map toCmd |> Cmd.batch) - mkProgramT - (init >> convert) - (fun msg model -> update msg model |> convert) - createVm - - - /// Uses the specified ILoggerFactory for logging. - let withLogger loggerFactory program = - { program with LoggerFactory = loggerFactory } - - - /// Calls the specified function for unhandled exceptions in the Elmish - /// dispatch loop (e.g. in commands or the update function). This essentially - /// delegates to Elmish's Program.withErrorHandler. - /// - /// The first (string) argument of onError is a message from Elmish describing - /// the context of the exception. Note that this may contain a rendered - /// message case with all data ("%A" formatting). - /// - /// Note that exceptions passed to onError are also logged to the logger - /// specified using WpfProgram.withLogger. - let withElmishErrorHandler onError program = - { program with ErrorHandler = onError } - - - /// Subscribe to external source of events, overrides existing subscription. - /// Return the subscriptions that should be active based on the current model. - /// Subscriptions will be started or stopped automatically to match. - let withSubscription (subscribe: 'model -> Sub<'msg>) program = - { program with ElmishProgram = program.ElmishProgram |> Program.withSubscription subscribe } - - - /// Map existing subscription to external source of events. - let mapSubscription map program = - { program with ElmishProgram = program.ElmishProgram |> Program.mapSubscription map } - - - /// Only logs binding performance for calls taking longer than the specified number of - /// milliseconds. The default is 1ms. - let withPerformanceLogThreshold threshold program = - { program with PerformanceLogThreshold = threshold } - - - /// Exit criteria and the handler, overrides existing. - let withTermination predicate terminate program = - { program with ElmishProgram = program.ElmishProgram |> Program.withTermination predicate terminate } - - - /// Map existing criteria and the handler. - let mapTermination map program = - { program with ElmishProgram = program.ElmishProgram |> Program.mapTermination map } + initializeApplication window + startElmishLoop window program + window.Show() + Application.Current.Run window + + + /// Same as mkProgram, except that init and update don't return Cmd<'msg> + /// directly, but instead return a CmdMsg discriminated union that is converted + /// to Cmd<'msg> using toCmd. This means that the init and update functions + /// return only data, and thus are easier to unit test. The CmdMsg pattern is + /// general; this is just a trivial convenience function that automatically + /// converts CmdMsg to Cmd<'msg> for you in init and update. + let mkProgramWithCmdMsg + (init: unit -> 'model * 'cmdMsg list) + (update: 'msg -> 'model -> 'model * 'cmdMsg list) + (bindings: unit -> Binding<'model, 'msg> list) + (toCmd: 'cmdMsg -> Cmd<'msg>) + = + let convert (model, cmdMsgs) = + model, (cmdMsgs |> List.map toCmd |> Cmd.batch) + + mkProgram (init >> convert) (fun msg model -> update msg model |> convert) bindings + + + /// Same as mkProgramT, except that init and update don't return Cmd<'msg> + /// directly, but instead return a CmdMsg discriminated union that is converted + /// to Cmd<'msg> using toCmd. This means that the init and update functions + /// return only data, and thus are easier to unit test. The CmdMsg pattern is + /// general; this is just a trivial convenience function that automatically + /// converts CmdMsg to Cmd<'msg> for you in init and update. + let mkProgramWithCmdMsgT + (init: unit -> 'model * 'cmdMsg list) + (update: 'msg -> 'model -> 'model * 'cmdMsg list) + (createVm: ViewModelArgs<'model, 'msg> -> 'viewModel) + (toCmd: 'cmdMsg -> Cmd<'msg>) + = + let convert (model, cmdMsgs) = + model, (cmdMsgs |> List.map toCmd |> Cmd.batch) + + mkProgramT (init >> convert) (fun msg model -> update msg model |> convert) createVm + + + /// Uses the specified ILoggerFactory for logging. + let withLogger loggerFactory program = + { program with + LoggerFactory = loggerFactory } + + + /// Calls the specified function for unhandled exceptions in the Elmish + /// dispatch loop (e.g. in commands or the update function). This essentially + /// delegates to Elmish's Program.withErrorHandler. + /// + /// The first (string) argument of onError is a message from Elmish describing + /// the context of the exception. Note that this may contain a rendered + /// message case with all data ("%A" formatting). + /// + /// Note that exceptions passed to onError are also logged to the logger + /// specified using WpfProgram.withLogger. + let withElmishErrorHandler onError program = { program with ErrorHandler = onError } + + + /// Subscribe to external source of events, overrides existing subscription. + /// Return the subscriptions that should be active based on the current model. + /// Subscriptions will be started or stopped automatically to match. + let withSubscription (subscribe: 'model -> Sub<'msg>) program = + { program with + ElmishProgram = program.ElmishProgram |> Program.withSubscription subscribe } + + + /// Map existing subscription to external source of events. + let mapSubscription map program = + { program with + ElmishProgram = program.ElmishProgram |> Program.mapSubscription map } + + + /// Only logs binding performance for calls taking longer than the specified number of + /// milliseconds. The default is 1ms. + let withPerformanceLogThreshold threshold program = + { program with + PerformanceLogThreshold = threshold } + + + /// Exit criteria and the handler, overrides existing. + let withTermination predicate terminate program = + { program with + ElmishProgram = program.ElmishProgram |> Program.withTermination predicate terminate } + + + /// Map existing criteria and the handler. + let mapTermination map program = + { program with + ElmishProgram = program.ElmishProgram |> Program.mapTermination map } [] module Subscribe = - /// Converts an effect to a Subscribe with a given dispose (on stop) method. - let ofEffect dispose (effect: Effect<'msg>) : Subscribe<'msg> = - fun dispatch -> - effect dispatch - { new System.IDisposable with member _.Dispose() = dispose () } + /// Converts an effect to a Subscribe with a given dispose (on stop) method. + let ofEffect dispose (effect: Effect<'msg>) : Subscribe<'msg> = + fun dispatch -> + effect dispatch + + { new System.IDisposable with + member _.Dispose() = dispose () } [] module Sub = - /// Subscribe to an external source of events. The subscribe function is called once, - /// with the initial model, but can dispatch messages at any time. - [] - let fromV3Subscription (idPrefix: string) (v3Subscription: 'model -> Cmd<'msg>) : 'model -> Sub<'msg> = - let mutable memoizedSub : Sub<'msg> voption = ValueNone - - fun model -> - match memoizedSub with - | ValueNone -> - let sub = - v3Subscription model - |> List.map (Subscribe.ofEffect id) - |> List.indexed - |> List.map (fun (i, subscribe) -> - [ idPrefix; string i ], subscribe) - memoizedSub <- ValueSome sub - sub - | ValueSome sub -> sub + /// Subscribe to an external source of events. The subscribe function is called once, + /// with the initial model, but can dispatch messages at any time. + [] + let fromV3Subscription (idPrefix: string) (v3Subscription: 'model -> Cmd<'msg>) : 'model -> Sub<'msg> = + let mutable memoizedSub: Sub<'msg> voption = ValueNone + + fun model -> + match memoizedSub with + | ValueNone -> + let sub = + v3Subscription model + |> List.map (Subscribe.ofEffect id) + |> List.indexed + |> List.map (fun (i, subscribe) -> [ idPrefix; string i ], subscribe) + + memoizedSub <- ValueSome sub + sub + | ValueSome sub -> sub \ No newline at end of file diff --git a/src/Samples/Capabilities.Core/Program.fs b/src/Samples/Capabilities.Core/Program.fs index cbbdb8db..83a45da4 100644 --- a/src/Samples/Capabilities.Core/Program.fs +++ b/src/Samples/Capabilities.Core/Program.fs @@ -10,61 +10,63 @@ open Elmish.WPF open Selection -type Screen = - SelectionScreen +type Screen = SelectionScreen type Model = - { VisibleScreen: Screen option - Selection: Selection } + { VisibleScreen: Screen option + Selection: Selection } type Msg = - | SetVisibleScreen of Screen option - | SelectionMsg of SelectionMsg + | SetVisibleScreen of Screen option + | SelectionMsg of SelectionMsg module Program = - module VisibleScreen = - let get m = m.VisibleScreen - let set v m = { m with VisibleScreen = v } - module Selection = - open Selection - let get m = m.Selection - let set v m = { m with Selection = v } - let map = map get set - let update = update >> map - - let init = - { VisibleScreen = None - Selection = Selection.init } - - let update = function - | SetVisibleScreen s -> s |> VisibleScreen.set - | SelectionMsg msg -> msg |> Selection.update - - let boolToVis = function - | true -> Visibility.Visible - | false -> Visibility.Collapsed - - let bindings () = [ - "Selection" - |> Binding.SubModel.required Selection.bindings - |> Binding.mapModel Selection.get - |> Binding.mapMsg SelectionMsg - "ShowSelection" |> Binding.cmd (SelectionScreen |> Some |> SetVisibleScreen) - "SelectionVisibility" |> Binding.oneWay (VisibleScreen.get >> (=) (Some SelectionScreen) >> boolToVis) - ] + module VisibleScreen = + let get m = m.VisibleScreen + let set v m = { m with VisibleScreen = v } + + module Selection = + open Selection + let get m = m.Selection + let set v m = { m with Selection = v } + let map = map get set + let update = update >> map + + let init = + { VisibleScreen = None + Selection = Selection.init } + + let update = + function + | SetVisibleScreen s -> s |> VisibleScreen.set + | SelectionMsg msg -> msg |> Selection.update + + let boolToVis = + function + | true -> Visibility.Visible + | false -> Visibility.Collapsed + + let bindings () = + [ "Selection" + |> Binding.SubModel.required Selection.bindings + |> Binding.mapModel Selection.get + |> Binding.mapMsg SelectionMsg + "ShowSelection" |> Binding.cmd (SelectionScreen |> Some |> SetVisibleScreen) + "SelectionVisibility" + |> Binding.oneWay (VisibleScreen.get >> (=) (Some SelectionScreen) >> boolToVis) ] let main window = - let logger = - LoggerConfiguration() - .MinimumLevel.Override("Elmish.WPF.Update", Events.LogEventLevel.Verbose) - .MinimumLevel.Override("Elmish.WPF.Bindings", Events.LogEventLevel.Verbose) - .MinimumLevel.Override("Elmish.WPF.Performance", Events.LogEventLevel.Verbose) - .WriteTo.Console() - .CreateLogger() - - WpfProgram.mkSimple (fun () -> Program.init) Program.update Program.bindings - |> WpfProgram.withLogger (new SerilogLoggerFactory(logger)) - |> WpfProgram.startElmishLoop window \ No newline at end of file + let logger = + LoggerConfiguration() + .MinimumLevel.Override("Elmish.WPF.Update", Events.LogEventLevel.Verbose) + .MinimumLevel.Override("Elmish.WPF.Bindings", Events.LogEventLevel.Verbose) + .MinimumLevel.Override("Elmish.WPF.Performance", Events.LogEventLevel.Verbose) + .WriteTo.Console() + .CreateLogger() + + WpfProgram.mkSimple (fun () -> Program.init) Program.update Program.bindings + |> WpfProgram.withLogger (new SerilogLoggerFactory(logger)) + |> WpfProgram.startElmishLoop window \ No newline at end of file diff --git a/src/Samples/Capabilities.Core/Selection.fs b/src/Samples/Capabilities.Core/Selection.fs index 22b09cf2..da249f3a 100644 --- a/src/Samples/Capabilities.Core/Selection.fs +++ b/src/Samples/Capabilities.Core/Selection.fs @@ -3,70 +3,73 @@ open Elmish.WPF -type Tree<'a> = - { Data: 'a - Children: Tree<'a> list } +type Tree<'a> = { Data: 'a; Children: Tree<'a> list } module Tree = - let create a ma = { Data = a; Children = ma } - let createLeaf a = create a [] - module Data = - let get m = m.Data - module Children = - let get m = m.Children + let create a ma = { Data = a; Children = ma } + let createLeaf a = create a [] + + module Data = + let get m = m.Data + + module Children = + let get m = m.Children type Selection = - { SelectedIndex: int option - SelectedIndexData: string list - SelectedValue: string option - SelectedValueData: Tree list } + { SelectedIndex: int option + SelectedIndexData: string list + SelectedValue: string option + SelectedValueData: Tree list } type SelectionMsg = - | SetSelectedIndex of int option - | SetSelectedValue of string option + | SetSelectedIndex of int option + | SetSelectedValue of string option module Selection = - module SelectedIndex = - let get m = m.SelectedIndex - let set v m = { m with SelectedIndex = v } - module SelectedIndexData = - let get m = m.SelectedIndexData - module SelectedValue = - let get m = m.SelectedValue - let set v m = { m with SelectedValue = v } - module SelectedValueData = - let get m = m.SelectedValueData - - let init = - { SelectedIndex = None - SelectedIndexData = ["A"; "B"] - SelectedValue = None - SelectedValueData = - [ Tree.create "A" [ Tree.createLeaf "Aa"; Tree.createLeaf "Ab" ] - Tree.create "B" [ Tree.createLeaf "Ba"; Tree.createLeaf "Bb" ] ] } - - let update = function - | SetSelectedIndex x -> x |> SelectedIndex.set - | SetSelectedValue x -> x |> SelectedValue.set - - let rec recursiveSelectedValueBindings () = [ - "Data" |> Binding.oneWay Tree.Data.get - "SelectedValueChildren" - |> Binding.subModelSeq recursiveSelectedValueBindings - |> Binding.mapModel (Tree.Children.get >> List.toSeq) - |> Binding.mapMsg snd - ] - - let bindings () = [ - "SelectedIndex" |> Binding.selectedIndex (SelectedIndex.get, SetSelectedIndex) - "DeselectIndex" |> Binding.cmdIf (SelectedIndex.get >> Option.map (fun _ -> SetSelectedIndex None)) - "SelectedIndexData" |> Binding.oneWay SelectedIndexData.get - - "SelectedValue" |> Binding.twoWayOpt (SelectedValue.get, SetSelectedValue) - "SelectedValueData" - |> Binding.subModelSeq recursiveSelectedValueBindings - |> Binding.mapModel (SelectedValueData.get >> List.toSeq) - |> Binding.mapMsg snd - ] \ No newline at end of file + module SelectedIndex = + let get m = m.SelectedIndex + let set v m = { m with SelectedIndex = v } + + module SelectedIndexData = + let get m = m.SelectedIndexData + + module SelectedValue = + let get m = m.SelectedValue + let set v m = { m with SelectedValue = v } + + module SelectedValueData = + let get m = m.SelectedValueData + + let init = + { SelectedIndex = None + SelectedIndexData = [ "A"; "B" ] + SelectedValue = None + SelectedValueData = + [ Tree.create "A" [ Tree.createLeaf "Aa"; Tree.createLeaf "Ab" ] + Tree.create "B" [ Tree.createLeaf "Ba"; Tree.createLeaf "Bb" ] ] } + + let update = + function + | SetSelectedIndex x -> x |> SelectedIndex.set + | SetSelectedValue x -> x |> SelectedValue.set + + let rec recursiveSelectedValueBindings () = + [ "Data" |> Binding.oneWay Tree.Data.get + "SelectedValueChildren" + |> Binding.subModelSeq recursiveSelectedValueBindings + |> Binding.mapModel (Tree.Children.get >> List.toSeq) + |> Binding.mapMsg snd ] + + let bindings () = + [ "SelectedIndex" |> Binding.selectedIndex (SelectedIndex.get, SetSelectedIndex) + "DeselectIndex" + |> Binding.cmdIf (SelectedIndex.get >> Option.map (fun _ -> SetSelectedIndex None)) + "SelectedIndexData" |> Binding.oneWay SelectedIndexData.get + + "SelectedValue" |> Binding.twoWayOpt (SelectedValue.get, SetSelectedValue) + "SelectedValueData" + |> Binding.subModelSeq recursiveSelectedValueBindings + |> Binding.mapModel (SelectedValueData.get >> List.toSeq) + |> Binding.mapMsg snd ] \ No newline at end of file diff --git a/src/Samples/Capabilities.Core/Utilities.fs b/src/Samples/Capabilities.Core/Utilities.fs index 1732f194..08a2ad46 100644 --- a/src/Samples/Capabilities.Core/Utilities.fs +++ b/src/Samples/Capabilities.Core/Utilities.fs @@ -3,5 +3,4 @@ module Utilities let flip f b a = f a b -let map get set f a = - a |> get |> f |> flip set a \ No newline at end of file +let map get set f a = a |> get |> f |> flip set a \ No newline at end of file diff --git a/src/Samples/EventBindingsAndBehaviors.Core/Program.fs b/src/Samples/EventBindingsAndBehaviors.Core/Program.fs index 229fd627..31f923ae 100644 --- a/src/Samples/EventBindingsAndBehaviors.Core/Program.fs +++ b/src/Samples/EventBindingsAndBehaviors.Core/Program.fs @@ -10,75 +10,80 @@ open Elmish.WPF type Position = { X: int; Y: int } type Model = - { Msg1: string - Msg2: string - ButtonText: string - Visibility: Visibility - MousePosition: Position } + { Msg1: string + Msg2: string + ButtonText: string + Visibility: Visibility + MousePosition: Position } let visibleButtonText = "Hide text box" let hiddenButtonText = "Show text box" let init () = - { Msg1 = "" - Msg2 = "" - ButtonText = visibleButtonText - Visibility = Visibility.Visible - MousePosition = { X = 0; Y = 0 } } + { Msg1 = "" + Msg2 = "" + ButtonText = visibleButtonText + Visibility = Visibility.Visible + MousePosition = { X = 0; Y = 0 } } type Msg = - | GotFocus1 - | GotFocus2 - | LostFocus1 - | LostFocus2 - | ToggleVisibility - | NewMousePosition of Position + | GotFocus1 + | GotFocus2 + | LostFocus1 + | LostFocus2 + | ToggleVisibility + | NewMousePosition of Position let update msg m = - match msg with - | GotFocus1 -> { m with Msg1 = "Focused" } - | GotFocus2 -> { m with Msg2 = "Focused" } - | LostFocus1 -> { m with Msg1 = "Not focused" } - | LostFocus2 -> { m with Msg2 = "Not focused" } - | ToggleVisibility -> - if m.Visibility = Visibility.Visible - then { m with Visibility = Visibility.Hidden; ButtonText = hiddenButtonText } - else { m with Visibility = Visibility.Visible; ButtonText = visibleButtonText } - | NewMousePosition p -> { m with MousePosition = p } + match msg with + | GotFocus1 -> { m with Msg1 = "Focused" } + | GotFocus2 -> { m with Msg2 = "Focused" } + | LostFocus1 -> { m with Msg1 = "Not focused" } + | LostFocus2 -> { m with Msg2 = "Not focused" } + | ToggleVisibility -> + if m.Visibility = Visibility.Visible then + { m with + Visibility = Visibility.Hidden + ButtonText = hiddenButtonText } + else + { m with + Visibility = Visibility.Visible + ButtonText = visibleButtonText } + | NewMousePosition p -> { m with MousePosition = p } let paramToNewMousePositionMsg (p: obj) = - let args = p :?> MouseEventArgs - let e = args.OriginalSource :?> UIElement; - let point = args.GetPosition e - NewMousePosition { X = int point.X; Y = int point.Y } + let args = p :?> MouseEventArgs + let e = args.OriginalSource :?> UIElement + let point = args.GetPosition e + NewMousePosition { X = int point.X; Y = int point.Y } -let bindings () : Binding list = [ - "Msg1" |> Binding.oneWay (fun m -> m.Msg1) - "Msg2" |> Binding.oneWay (fun m -> m.Msg2) - "GotFocus1" |> Binding.cmd GotFocus1 - "GotFocus2" |> Binding.cmd GotFocus2 - "LostFocus1" |> Binding.cmd LostFocus1 - "LostFocus2" |> Binding.cmd LostFocus2 - "ToggleVisibility" |> Binding.cmd ToggleVisibility - "ButtonText" |> Binding.oneWay (fun m -> m.ButtonText) - "TextBoxVisibility" |> Binding.oneWay (fun m -> m.Visibility) - "MouseMoveCommand" |> Binding.cmdParam paramToNewMousePositionMsg - "MousePosition" |> Binding.oneWay (fun m -> sprintf "%dx%d" m.MousePosition.X m.MousePosition.Y) -] +let bindings () : Binding list = + [ "Msg1" |> Binding.oneWay (fun m -> m.Msg1) + "Msg2" |> Binding.oneWay (fun m -> m.Msg2) + "GotFocus1" |> Binding.cmd GotFocus1 + "GotFocus2" |> Binding.cmd GotFocus2 + "LostFocus1" |> Binding.cmd LostFocus1 + "LostFocus2" |> Binding.cmd LostFocus2 + "ToggleVisibility" |> Binding.cmd ToggleVisibility + "ButtonText" |> Binding.oneWay (fun m -> m.ButtonText) + "TextBoxVisibility" |> Binding.oneWay (fun m -> m.Visibility) + "MouseMoveCommand" |> Binding.cmdParam paramToNewMousePositionMsg + "MousePosition" + |> Binding.oneWay (fun m -> sprintf "%dx%d" m.MousePosition.X m.MousePosition.Y) ] let designVm = ViewModel.designInstance (init ()) (bindings ()) let main window = - let logger = - LoggerConfiguration() - .MinimumLevel.Override("Elmish.WPF.Update", Events.LogEventLevel.Verbose) - .MinimumLevel.Override("Elmish.WPF.Bindings", Events.LogEventLevel.Verbose) - .MinimumLevel.Override("Elmish.WPF.Performance", Events.LogEventLevel.Verbose) - .WriteTo.Console() - .CreateLogger() + let logger = + LoggerConfiguration() + .MinimumLevel.Override("Elmish.WPF.Update", Events.LogEventLevel.Verbose) + .MinimumLevel.Override("Elmish.WPF.Bindings", Events.LogEventLevel.Verbose) + .MinimumLevel.Override("Elmish.WPF.Performance", Events.LogEventLevel.Verbose) + .WriteTo.Console() + .CreateLogger() - WpfProgram.mkSimple init update bindings - |> WpfProgram.withLogger (new SerilogLoggerFactory(logger)) - |> WpfProgram.startElmishLoop window + WpfProgram.mkSimple init update bindings + |> WpfProgram.withLogger (new SerilogLoggerFactory(logger)) + |> WpfProgram.startElmishLoop window \ No newline at end of file diff --git a/src/Samples/FileDialogs.Core/Program.fs b/src/Samples/FileDialogs.Core/Program.fs index 95f5347e..ebe37a97 100644 --- a/src/Samples/FileDialogs.Core/Program.fs +++ b/src/Samples/FileDialogs.Core/Program.fs @@ -9,104 +9,121 @@ open Elmish.WPF type Model = - { CurrentTime: DateTimeOffset - Text: string - StatusMsg: string } + { CurrentTime: DateTimeOffset + Text: string + StatusMsg: string } let init () = - { CurrentTime = DateTimeOffset.Now - Text = "" - StatusMsg = "" }, - [] + { CurrentTime = DateTimeOffset.Now + Text = "" + StatusMsg = "" }, + [] type Msg = - | SetTime of DateTimeOffset - | SetText of string - | RequestSave - | RequestLoad - | SaveSuccess - | LoadSuccess of string - | SaveCanceled - | LoadCanceled - | SaveFailed of exn - | LoadFailed of exn + | SetTime of DateTimeOffset + | SetText of string + | RequestSave + | RequestLoad + | SaveSuccess + | LoadSuccess of string + | SaveCanceled + | LoadCanceled + | SaveFailed of exn + | LoadFailed of exn let save text = - async { - let dlg = Microsoft.Win32.SaveFileDialog () - dlg.Filter <- "Text file (*.txt)|*.txt|Markdown file (*.md)|*.md" - let result = dlg.ShowDialog () - if result.HasValue && result.Value then - do! File.WriteAllTextAsync(dlg.FileName, text) |> Async.AwaitTask - return SaveSuccess - else return SaveCanceled - } + async { + let dlg = Microsoft.Win32.SaveFileDialog() + dlg.Filter <- "Text file (*.txt)|*.txt|Markdown file (*.md)|*.md" + let result = dlg.ShowDialog() + + if result.HasValue && result.Value then + do! File.WriteAllTextAsync(dlg.FileName, text) |> Async.AwaitTask + return SaveSuccess + else + return SaveCanceled + } let load () = - async { - let dlg = Microsoft.Win32.OpenFileDialog () - dlg.Filter <- "Text file (*.txt)|*.txt|Markdown file (*.md)|*.md" - dlg.DefaultExt <- "txt" - let result = dlg.ShowDialog () - if result.HasValue && result.Value then - let! contents = File.ReadAllTextAsync(dlg.FileName) |> Async.AwaitTask - return LoadSuccess contents - else return LoadCanceled - } + async { + let dlg = Microsoft.Win32.OpenFileDialog() + dlg.Filter <- "Text file (*.txt)|*.txt|Markdown file (*.md)|*.md" + dlg.DefaultExt <- "txt" + let result = dlg.ShowDialog() + + if result.HasValue && result.Value then + let! contents = File.ReadAllTextAsync(dlg.FileName) |> Async.AwaitTask + return LoadSuccess contents + else + return LoadCanceled + } let update msg m = - match msg with - | SetTime t -> { m with CurrentTime = t }, Cmd.none - | SetText s -> { m with Text = s}, Cmd.none - | RequestSave -> m, Cmd.OfAsync.either save m.Text id SaveFailed - | RequestLoad -> m, Cmd.OfAsync.either load () id LoadFailed - | SaveSuccess -> { m with StatusMsg = sprintf "Successfully saved at %O" DateTimeOffset.Now }, Cmd.none - | LoadSuccess s -> { m with Text = s; StatusMsg = sprintf "Successfully loaded at %O" DateTimeOffset.Now }, Cmd.none - | SaveCanceled -> { m with StatusMsg = "Saving canceled" }, Cmd.none - | LoadCanceled -> { m with StatusMsg = "Loading canceled" }, Cmd.none - | SaveFailed ex -> { m with StatusMsg = sprintf "Saving failed with exception %s: %s" (ex.GetType().Name) ex.Message }, Cmd.none - | LoadFailed ex -> { m with StatusMsg = sprintf "Loading failed with exception %s: %s" (ex.GetType().Name) ex.Message }, Cmd.none - - -let bindings () : Binding list = [ - "CurrentTime" |> Binding.oneWay (fun m -> m.CurrentTime) - "Text" |> Binding.twoWay ((fun m -> m.Text), SetText) - "StatusMsg" |> Binding.twoWay ((fun m -> m.StatusMsg), SetText) - "Save" |> Binding.cmd RequestSave - "Load" |> Binding.cmd RequestLoad -] + match msg with + | SetTime t -> { m with CurrentTime = t }, Cmd.none + | SetText s -> { m with Text = s }, Cmd.none + | RequestSave -> m, Cmd.OfAsync.either save m.Text id SaveFailed + | RequestLoad -> m, Cmd.OfAsync.either load () id LoadFailed + | SaveSuccess -> + { m with + StatusMsg = sprintf "Successfully saved at %O" DateTimeOffset.Now }, + Cmd.none + | LoadSuccess s -> + { m with + Text = s + StatusMsg = sprintf "Successfully loaded at %O" DateTimeOffset.Now }, + Cmd.none + | SaveCanceled -> { m with StatusMsg = "Saving canceled" }, Cmd.none + | LoadCanceled -> + { m with + StatusMsg = "Loading canceled" }, + Cmd.none + | SaveFailed ex -> + { m with + StatusMsg = sprintf "Saving failed with exception %s: %s" (ex.GetType().Name) ex.Message }, + Cmd.none + | LoadFailed ex -> + { m with + StatusMsg = sprintf "Loading failed with exception %s: %s" (ex.GetType().Name) ex.Message }, + Cmd.none + + +let bindings () : Binding list = + [ "CurrentTime" |> Binding.oneWay (fun m -> m.CurrentTime) + "Text" |> Binding.twoWay ((fun m -> m.Text), SetText) + "StatusMsg" |> Binding.twoWay ((fun m -> m.StatusMsg), SetText) + "Save" |> Binding.cmd RequestSave + "Load" |> Binding.cmd RequestLoad ] let designVm = ViewModel.designInstance (init () |> fst) (bindings ()) let subscriptions (model: Model) : Sub = - let timerTickSub dispatch = - let timer = new Timers.Timer(1000.) - let disp = timer.Elapsed.Subscribe(fun _ -> dispatch (SetTime DateTimeOffset.Now)) - timer.Start() - disp + let timerTickSub dispatch = + let timer = new Timers.Timer(1000.) + let disp = timer.Elapsed.Subscribe(fun _ -> dispatch (SetTime DateTimeOffset.Now)) + timer.Start() + disp - [ - [ nameof timerTickSub ], timerTickSub - ] + [ [ nameof timerTickSub ], timerTickSub ] let main window = - let logger = - LoggerConfiguration() - .MinimumLevel.Override("Elmish.WPF.Update", Events.LogEventLevel.Verbose) - .MinimumLevel.Override("Elmish.WPF.Bindings", Events.LogEventLevel.Verbose) - .MinimumLevel.Override("Elmish.WPF.Performance", Events.LogEventLevel.Verbose) - .WriteTo.Console() - .CreateLogger() + let logger = + LoggerConfiguration() + .MinimumLevel.Override("Elmish.WPF.Update", Events.LogEventLevel.Verbose) + .MinimumLevel.Override("Elmish.WPF.Bindings", Events.LogEventLevel.Verbose) + .MinimumLevel.Override("Elmish.WPF.Performance", Events.LogEventLevel.Verbose) + .WriteTo.Console() + .CreateLogger() - WpfProgram.mkProgram init update bindings - |> WpfProgram.withSubscription subscriptions - |> WpfProgram.withLogger (new SerilogLoggerFactory(logger)) - |> WpfProgram.startElmishLoop window + WpfProgram.mkProgram init update bindings + |> WpfProgram.withSubscription subscriptions + |> WpfProgram.withLogger (new SerilogLoggerFactory(logger)) + |> WpfProgram.startElmishLoop window \ No newline at end of file diff --git a/src/Samples/FileDialogsCmdMsg.Core/Program.fs b/src/Samples/FileDialogsCmdMsg.Core/Program.fs index 8f19a5ae..29ecbb20 100644 --- a/src/Samples/FileDialogsCmdMsg.Core/Program.fs +++ b/src/Samples/FileDialogsCmdMsg.Core/Program.fs @@ -10,94 +10,114 @@ open Elmish.WPF module Core = - type Model = - { CurrentTime: DateTimeOffset - Text: string - StatusMsg: string } - - - type CmdMsg = - | Save of string - | Load - - - let init () = - { CurrentTime = DateTimeOffset.Now - Text = "" - StatusMsg = "" }, - [] - - type Msg = - | SetTime of DateTimeOffset - | SetText of string - | RequestSave - | RequestLoad - | SaveSuccess - | LoadSuccess of string - | SaveCanceled - | LoadCanceled - | SaveFailed of exn - | LoadFailed of exn - - - let update msg m = - match msg with - | SetTime t -> { m with CurrentTime = t }, [] - | SetText s -> { m with Text = s}, [] - | RequestSave -> m, [Save m.Text] - | RequestLoad -> m, [Load] - | SaveSuccess -> { m with StatusMsg = sprintf "Successfully saved at %O" DateTimeOffset.Now }, [] - | LoadSuccess s -> { m with Text = s; StatusMsg = sprintf "Successfully loaded at %O" DateTimeOffset.Now }, [] - | SaveCanceled -> { m with StatusMsg = "Saving canceled" }, [] - | LoadCanceled -> { m with StatusMsg = "Loading canceled" }, [] - | SaveFailed ex -> { m with StatusMsg = sprintf "Saving failed with exception %s: %s" (ex.GetType().Name) ex.Message }, [] - | LoadFailed ex -> { m with StatusMsg = sprintf "Loading failed with exception %s: %s" (ex.GetType().Name) ex.Message }, [] + type Model = + { CurrentTime: DateTimeOffset + Text: string + StatusMsg: string } + + + type CmdMsg = + | Save of string + | Load + + + let init () = + { CurrentTime = DateTimeOffset.Now + Text = "" + StatusMsg = "" }, + [] + + type Msg = + | SetTime of DateTimeOffset + | SetText of string + | RequestSave + | RequestLoad + | SaveSuccess + | LoadSuccess of string + | SaveCanceled + | LoadCanceled + | SaveFailed of exn + | LoadFailed of exn + + + let update msg m = + match msg with + | SetTime t -> { m with CurrentTime = t }, [] + | SetText s -> { m with Text = s }, [] + | RequestSave -> m, [ Save m.Text ] + | RequestLoad -> m, [ Load ] + | SaveSuccess -> + { m with + StatusMsg = sprintf "Successfully saved at %O" DateTimeOffset.Now }, + [] + | LoadSuccess s -> + { m with + Text = s + StatusMsg = sprintf "Successfully loaded at %O" DateTimeOffset.Now }, + [] + | SaveCanceled -> { m with StatusMsg = "Saving canceled" }, [] + | LoadCanceled -> + { m with + StatusMsg = "Loading canceled" }, + [] + | SaveFailed ex -> + { m with + StatusMsg = sprintf "Saving failed with exception %s: %s" (ex.GetType().Name) ex.Message }, + [] + | LoadFailed ex -> + { m with + StatusMsg = sprintf "Loading failed with exception %s: %s" (ex.GetType().Name) ex.Message }, + [] module Platform = - open System.IO - open Core + open System.IO + open Core - let bindings () : Binding list = [ - "CurrentTime" |> Binding.oneWay (fun m -> m.CurrentTime) - "Text" |> Binding.twoWay ((fun m -> m.Text), SetText) - "StatusMsg" |> Binding.twoWay ((fun m -> m.StatusMsg), SetText) - "Save" |> Binding.cmd RequestSave - "Load" |> Binding.cmd RequestLoad - ] + let bindings () : Binding list = + [ "CurrentTime" |> Binding.oneWay (fun m -> m.CurrentTime) + "Text" |> Binding.twoWay ((fun m -> m.Text), SetText) + "StatusMsg" |> Binding.twoWay ((fun m -> m.StatusMsg), SetText) + "Save" |> Binding.cmd RequestSave + "Load" |> Binding.cmd RequestLoad ] - let save text = - async { - let dlg = Microsoft.Win32.SaveFileDialog () - dlg.Filter <- "Text file (*.txt)|*.txt|Markdown file (*.md)|*.md" - let result = dlg.ShowDialog () - if result.HasValue && result.Value then - do! File.WriteAllTextAsync(dlg.FileName, text) |> Async.AwaitTask - return SaveSuccess - else return SaveCanceled - } + let save text = + async { + let dlg = Microsoft.Win32.SaveFileDialog() + dlg.Filter <- "Text file (*.txt)|*.txt|Markdown file (*.md)|*.md" + let result = dlg.ShowDialog() + if result.HasValue && result.Value then + do! File.WriteAllTextAsync(dlg.FileName, text) |> Async.AwaitTask + return SaveSuccess + else + return SaveCanceled + } - let load () = - async { - let dlg = Microsoft.Win32.OpenFileDialog () - dlg.Filter <- "Text file (*.txt)|*.txt|Markdown file (*.md)|*.md" - dlg.DefaultExt <- "txt" - let result = dlg.ShowDialog () - if result.HasValue && result.Value then - let! contents = File.ReadAllTextAsync(dlg.FileName) |> Async.AwaitTask - return LoadSuccess contents - else return LoadCanceled - } + let load () = + async { + let dlg = Microsoft.Win32.OpenFileDialog() + dlg.Filter <- "Text file (*.txt)|*.txt|Markdown file (*.md)|*.md" + dlg.DefaultExt <- "txt" + let result = dlg.ShowDialog() - let toCmd = function - | Save text -> Cmd.OfAsync.either save text id SaveFailed - | Load -> Cmd.OfAsync.either load () id LoadFailed + if result.HasValue && result.Value then + let! contents = File.ReadAllTextAsync(dlg.FileName) |> Async.AwaitTask + return LoadSuccess contents + else + return LoadCanceled + } + + + let toCmd = + function + | Save text -> Cmd.OfAsync.either save text id SaveFailed + | Load -> Cmd.OfAsync.either load () id LoadFailed @@ -108,26 +128,24 @@ open Platform let designVm = ViewModel.designInstance (init () |> fst) (bindings ()) let subscriptions (_model: Model) : Sub = - let timerTickSub (dispatch: Msg -> unit): IDisposable = - let timer = new Timers.Timer(1000.) - let disp = timer.Elapsed.Subscribe(fun _ -> dispatch (SetTime DateTimeOffset.Now)) - timer.Start() - disp + let timerTickSub (dispatch: Msg -> unit) : IDisposable = + let timer = new Timers.Timer(1000.) + let disp = timer.Elapsed.Subscribe(fun _ -> dispatch (SetTime DateTimeOffset.Now)) + timer.Start() + disp - [ - [ nameof timerTickSub ], timerTickSub - ] + [ [ nameof timerTickSub ], timerTickSub ] let main window = - let logger = - LoggerConfiguration() - .MinimumLevel.Override("Elmish.WPF.Update", Events.LogEventLevel.Verbose) - .MinimumLevel.Override("Elmish.WPF.Bindings", Events.LogEventLevel.Verbose) - .MinimumLevel.Override("Elmish.WPF.Performance", Events.LogEventLevel.Verbose) - .WriteTo.Console() - .CreateLogger() - - WpfProgram.mkProgramWithCmdMsg init update bindings toCmd - |> WpfProgram.withSubscription subscriptions - |> WpfProgram.withLogger (new SerilogLoggerFactory(logger)) - |> WpfProgram.startElmishLoop window + let logger = + LoggerConfiguration() + .MinimumLevel.Override("Elmish.WPF.Update", Events.LogEventLevel.Verbose) + .MinimumLevel.Override("Elmish.WPF.Bindings", Events.LogEventLevel.Verbose) + .MinimumLevel.Override("Elmish.WPF.Performance", Events.LogEventLevel.Verbose) + .WriteTo.Console() + .CreateLogger() + + WpfProgram.mkProgramWithCmdMsg init update bindings toCmd + |> WpfProgram.withSubscription subscriptions + |> WpfProgram.withLogger (new SerilogLoggerFactory(logger)) + |> WpfProgram.startElmishLoop window \ No newline at end of file diff --git a/src/Samples/Multiselect.Core/Program.fs b/src/Samples/Multiselect.Core/Program.fs index 4aa7581d..9df84ca8 100644 --- a/src/Samples/Multiselect.Core/Program.fs +++ b/src/Samples/Multiselect.Core/Program.fs @@ -6,52 +6,73 @@ open Serilog.Extensions.Logging open Elmish.WPF type Entity = - { Id: int - Name: string - IsSelected: bool } + { Id: int + Name: string + IsSelected: bool } -type Model = - { Entities: Entity list } +type Model = { Entities: Entity list } let init () = - { Entities = [0 .. 10] |> List.map (fun i -> { Id = i; Name = sprintf "Entity %i" i; IsSelected = i < 5 }) } + { Entities = + [ 0..10 ] + |> List.map (fun i -> + { Id = i + Name = sprintf "Entity %i" i + IsSelected = i < 5 }) } type Msg = - | SetIsSelected of int * bool - | DeselectAll + | SetIsSelected of int * bool + | DeselectAll let update msg m = - match msg with - | SetIsSelected (entityId, isSelected) -> { m with Entities = m.Entities |> List.map (fun e -> if e.Id = entityId then { e with IsSelected = isSelected } else e) } - | DeselectAll -> { m with Entities = m.Entities |> List.map (fun e -> { e with IsSelected = false }) } - -let bindings () : Binding list = [ - "SelectRandom" |> Binding.cmd - (fun m -> m.Entities.Item(Random().Next(m.Entities.Length)).Id |> (fun id -> SetIsSelected (id, true))) - - "Deselect" |> Binding.cmd DeselectAll - - "Entities" |> Binding.subModelSeq - ( fun m -> m.Entities - , fun e -> e.Id - , fun () -> [ - "Name" |> Binding.oneWay (fun (_, e) -> e.Name) - "IsSelected" |> Binding.twoWay ((fun (_, e) -> e.IsSelected), (fun isSelected (_, e) -> SetIsSelected (e.Id, isSelected))) - "SelectedLabel" |> Binding.oneWay (fun (_, e) -> if e.IsSelected then " - SELECTED" else "") - ] ) -] + match msg with + | SetIsSelected(entityId, isSelected) -> + { m with + Entities = + m.Entities + |> List.map (fun e -> + if e.Id = entityId then + { e with IsSelected = isSelected } + else + e) } + | DeselectAll -> + { m with + Entities = m.Entities |> List.map (fun e -> { e with IsSelected = false }) } + +let bindings () : Binding list = + [ "SelectRandom" + |> Binding.cmd (fun m -> + m.Entities.Item(Random().Next(m.Entities.Length)).Id + |> (fun id -> SetIsSelected(id, true))) + + "Deselect" |> Binding.cmd DeselectAll + + "Entities" + |> Binding.subModelSeq ( + fun m -> m.Entities + , fun e -> e.Id + , fun () -> + [ "Name" |> Binding.oneWay (fun (_, e) -> e.Name) + "IsSelected" + |> Binding.twoWay ( + (fun (_, e) -> e.IsSelected), + (fun isSelected (_, e) -> SetIsSelected(e.Id, isSelected)) + ) + "SelectedLabel" + |> Binding.oneWay (fun (_, e) -> if e.IsSelected then " - SELECTED" else "") ] + ) ] let designVm = ViewModel.designInstance (init ()) (bindings ()) let main window = - let logger = - LoggerConfiguration() - .MinimumLevel.Override("Elmish.WPF.Update", Events.LogEventLevel.Verbose) - .MinimumLevel.Override("Elmish.WPF.Bindings", Events.LogEventLevel.Verbose) - .MinimumLevel.Override("Elmish.WPF.Performance", Events.LogEventLevel.Verbose) - .WriteTo.Console() - .CreateLogger() - - WpfProgram.mkSimple init update bindings - |> WpfProgram.withLogger (new SerilogLoggerFactory(logger)) - |> WpfProgram.startElmishLoop window \ No newline at end of file + let logger = + LoggerConfiguration() + .MinimumLevel.Override("Elmish.WPF.Update", Events.LogEventLevel.Verbose) + .MinimumLevel.Override("Elmish.WPF.Bindings", Events.LogEventLevel.Verbose) + .MinimumLevel.Override("Elmish.WPF.Performance", Events.LogEventLevel.Verbose) + .WriteTo.Console() + .CreateLogger() + + WpfProgram.mkSimple init update bindings + |> WpfProgram.withLogger (new SerilogLoggerFactory(logger)) + |> WpfProgram.startElmishLoop window \ No newline at end of file diff --git a/src/Samples/NewWindow.Core/App.fs b/src/Samples/NewWindow.Core/App.fs index 2a2c5c65..4ffa6678 100644 --- a/src/Samples/NewWindow.Core/App.fs +++ b/src/Samples/NewWindow.Core/App.fs @@ -9,64 +9,72 @@ open Window2Module type App = - { Window1: WindowState - Window2: Window2 option } + { Window1: WindowState + Window2: Window2 option } type AppMsg = - | Window1Show - | Window1Hide - | Window1Close - | Window1SetInput of string - | Window2Show - | Window2Close - | Window2Msg of Window2Msg + | Window1Show + | Window1Hide + | Window1Close + | Window1SetInput of string + | Window2Show + | Window2Close + | Window2Msg of Window2Msg module App = - module Window1 = - let get m = m.Window1 - let set v m = { m with Window1 = v } - let map = map get set - module Window2 = - let get m = m.Window2 - let set v m = { m with Window2 = v } - let map = map get set - let mapOutMsg = function - | Window2OutMsg.Close -> Window2Close - let mapInOutMsg = InOut.cata Window2Msg mapOutMsg + module Window1 = + let get m = m.Window1 + let set v m = { m with Window1 = v } + let map = map get set - let init = - { Window1 = WindowState.Closed - Window2 = None } + module Window2 = + let get m = m.Window2 + let set v m = { m with Window2 = v } + let map = map get set - let update = function - | Window1Show -> "" |> WindowState.toVisible |> Window1.map - | Window1Hide -> "" |> WindowState.toHidden |> Window1.map - | Window1Close -> WindowState.Closed |> Window1.set - | Window1SetInput s -> s |> WindowState.set |> Window1.map - | Window2Show -> Window2.init |> Some |> Window2.set - | Window2Close -> None |> Window2.set - | Window2Msg msg -> msg |> Window2.update |> Option.map |> Window2.map + let mapOutMsg = + function + | Window2OutMsg.Close -> Window2Close - let bindings (createWindow1: unit -> #Window) (createWindow2: unit -> #Window) () = [ - "Window1Show" |> Binding.cmd Window1Show - "Window1Hide" |> Binding.cmd Window1Hide - "Window1Close" |> Binding.cmd Window1Close - "Window2Show" |> Binding.cmd Window2Show - "Window1" |> Binding.subModelWin( - (fun m -> m.Window1), - snd, - id, - Window1.bindings >> Bindings.mapMsg Window1SetInput, - createWindow1) - "Window2" |> Binding.subModelWin( - Window2.get >> WindowState.ofOption, - snd, - Window2.mapInOutMsg, - Window2.bindings, - createWindow2, - isModal = true) - ] + let mapInOutMsg = InOut.cata Window2Msg mapOutMsg + + let init = + { Window1 = WindowState.Closed + Window2 = None } + + let update = + function + | Window1Show -> "" |> WindowState.toVisible |> Window1.map + | Window1Hide -> "" |> WindowState.toHidden |> Window1.map + | Window1Close -> WindowState.Closed |> Window1.set + | Window1SetInput s -> s |> WindowState.set |> Window1.map + | Window2Show -> Window2.init |> Some |> Window2.set + | Window2Close -> None |> Window2.set + | Window2Msg msg -> msg |> Window2.update |> Option.map |> Window2.map + + let bindings (createWindow1: unit -> #Window) (createWindow2: unit -> #Window) () = + [ "Window1Show" |> Binding.cmd Window1Show + "Window1Hide" |> Binding.cmd Window1Hide + "Window1Close" |> Binding.cmd Window1Close + "Window2Show" |> Binding.cmd Window2Show + "Window1" + |> Binding.subModelWin ( + (fun m -> m.Window1), + snd, + id, + Window1.bindings >> Bindings.mapMsg Window1SetInput, + createWindow1 + ) + "Window2" + |> Binding.subModelWin ( + Window2.get >> WindowState.ofOption, + snd, + Window2.mapInOutMsg, + Window2.bindings, + createWindow2, + isModal = true + ) ] let private fail _ = failwith "never called" let designVm = ViewModel.designInstance App.init (App.bindings fail fail ()) \ No newline at end of file diff --git a/src/Samples/NewWindow.Core/AutoOpen.fs b/src/Samples/NewWindow.Core/AutoOpen.fs index e1054f12..3253ea04 100644 --- a/src/Samples/NewWindow.Core/AutoOpen.fs +++ b/src/Samples/NewWindow.Core/AutoOpen.fs @@ -4,29 +4,31 @@ module AutoOpen let flip f b a = f a b -let map get set f a = - a |> get |> f |> flip set a +let map get set f a = a |> get |> f |> flip set a [] module Bool = - open System.Windows - let toVisibilityCollapsed = function - | true -> Visibility.Visible - | false -> Visibility.Collapsed + open System.Windows + + let toVisibilityCollapsed = + function + | true -> Visibility.Visible + | false -> Visibility.Collapsed [] module InOutModule = - [] - type InOut<'a, 'b> = - | In of 'a - | Out of 'b + [] + type InOut<'a, 'b> = + | In of 'a + | Out of 'b - [] - module InOut = + [] + module InOut = - let cata f g = function - | InOut.In msg -> msg |> f - | InOut.Out msg -> msg |> g + let cata f g = + function + | InOut.In msg -> msg |> f + | InOut.Out msg -> msg |> g \ No newline at end of file diff --git a/src/Samples/NewWindow.Core/Program.fs b/src/Samples/NewWindow.Core/Program.fs index 949342f2..a6d9a922 100644 --- a/src/Samples/NewWindow.Core/Program.fs +++ b/src/Samples/NewWindow.Core/Program.fs @@ -12,21 +12,24 @@ open AppModule let main mainWindow (createWindow1: Func<#Window>) (createWindow2: Func<#Window>) = - let logger = - LoggerConfiguration() - .MinimumLevel.Override("Elmish.WPF.Update", Events.LogEventLevel.Verbose) - .MinimumLevel.Override("Elmish.WPF.Bindings", Events.LogEventLevel.Verbose) - .MinimumLevel.Override("Elmish.WPF.Performance", Events.LogEventLevel.Verbose) - .WriteTo.Console() - .CreateLogger() - let createWindow1 () = createWindow1.Invoke() - let createWindow2 () = - let window = createWindow2.Invoke() - window.Owner <- mainWindow - window - - let init () = App.init - let bindings = App.bindings createWindow1 createWindow2 - WpfProgram.mkSimple init App.update bindings - |> WpfProgram.withLogger (new SerilogLoggerFactory(logger)) - |> WpfProgram.startElmishLoop mainWindow \ No newline at end of file + let logger = + LoggerConfiguration() + .MinimumLevel.Override("Elmish.WPF.Update", Events.LogEventLevel.Verbose) + .MinimumLevel.Override("Elmish.WPF.Bindings", Events.LogEventLevel.Verbose) + .MinimumLevel.Override("Elmish.WPF.Performance", Events.LogEventLevel.Verbose) + .WriteTo.Console() + .CreateLogger() + + let createWindow1 () = createWindow1.Invoke() + + let createWindow2 () = + let window = createWindow2.Invoke() + window.Owner <- mainWindow + window + + let init () = App.init + let bindings = App.bindings createWindow1 createWindow2 + + WpfProgram.mkSimple init App.update bindings + |> WpfProgram.withLogger (new SerilogLoggerFactory(logger)) + |> WpfProgram.startElmishLoop mainWindow \ No newline at end of file diff --git a/src/Samples/NewWindow.Core/Window1.fs b/src/Samples/NewWindow.Core/Window1.fs index 9637b7b1..d6b5494a 100644 --- a/src/Samples/NewWindow.Core/Window1.fs +++ b/src/Samples/NewWindow.Core/Window1.fs @@ -4,10 +4,8 @@ open Elmish.WPF module Window1 = - let init = "" + let init = "" - let bindings () = [ - "Input" |> Binding.twoWay (id, id) - ] + let bindings () = [ "Input" |> Binding.twoWay (id, id) ] let designVm = ViewModel.designInstance Window1.init (Window1.bindings ()) \ No newline at end of file diff --git a/src/Samples/NewWindow.Core/Window2.fs b/src/Samples/NewWindow.Core/Window2.fs index dff422f6..a91b9403 100644 --- a/src/Samples/NewWindow.Core/Window2.fs +++ b/src/Samples/NewWindow.Core/Window2.fs @@ -5,71 +5,76 @@ open Elmish.WPF [] type ConfirmState = - | Submit - | Cancel - | Close + | Submit + | Cancel + | Close type Window2 = - { Input: string - IsChecked: bool - ConfirmState: ConfirmState option } + { Input: string + IsChecked: bool + ConfirmState: ConfirmState option } type Window2Msg = - | SetInput of string - | SetChecked of bool - | Submit - | Cancel - | Close + | SetInput of string + | SetChecked of bool + | Submit + | Cancel + | Close [] -type Window2OutMsg = - | Close +type Window2OutMsg = | Close module Window2 = - module Input = - let get m = m.Input - let set v m = { m with Input = v } - module IsChecked = - let get m = m.IsChecked - let set v m = { m with IsChecked = v } - module ConfirmState = - let set v m = { m with ConfirmState = v } - - let init = - { Input = "" - IsChecked = false - ConfirmState = None } - - let update = function - | SetInput s -> s |> Input.set - | SetChecked b -> b |> IsChecked.set - | Submit -> ConfirmState.Submit |> Some |> ConfirmState.set - | Cancel -> ConfirmState.Cancel |> Some |> ConfirmState.set - | Close -> ConfirmState.Close |> Some |> ConfirmState.set - - let private confirmStateVisibilityBinding confirmState = - fun m -> m.ConfirmState = Some confirmState - >> Bool.toVisibilityCollapsed - |> Binding.oneWay - - let private confirmStateToMsg confirmState msg m = - if m.ConfirmState = Some confirmState - then InOut.Out Window2OutMsg.Close - else InOut.In msg - - let bindings () = - let inBindings = - [ "Input" |> Binding.twoWay (Input.get, SetInput) - "IsChecked" |> Binding.twoWay (IsChecked.get, SetChecked) - "SubmitMsgVisibility" |> confirmStateVisibilityBinding ConfirmState.Submit - "CancelMsgVisibility" |> confirmStateVisibilityBinding ConfirmState.Cancel - "CloseMsgVisibility" |> confirmStateVisibilityBinding ConfirmState.Close ] - |> Bindings.mapMsg InOut.In - let inOutBindings = - [ "Submit" |> Binding.cmd (confirmStateToMsg ConfirmState.Submit Submit) - "Cancel" |> Binding.cmd (confirmStateToMsg ConfirmState.Cancel Cancel) - "Close" |> Binding.cmd (confirmStateToMsg ConfirmState.Close Close) ] - inBindings @ inOutBindings + module Input = + let get m = m.Input + let set v m = { m with Input = v } + + module IsChecked = + let get m = m.IsChecked + let set v m = { m with IsChecked = v } + + module ConfirmState = + let set v m = { m with ConfirmState = v } + + let init = + { Input = "" + IsChecked = false + ConfirmState = None } + + let update = + function + | SetInput s -> s |> Input.set + | SetChecked b -> b |> IsChecked.set + | Submit -> ConfirmState.Submit |> Some |> ConfirmState.set + | Cancel -> ConfirmState.Cancel |> Some |> ConfirmState.set + | Close -> ConfirmState.Close |> Some |> ConfirmState.set + + let private confirmStateVisibilityBinding confirmState = + fun m -> m.ConfirmState = Some confirmState + >> Bool.toVisibilityCollapsed + |> Binding.oneWay + + let private confirmStateToMsg confirmState msg m = + if m.ConfirmState = Some confirmState then + InOut.Out Window2OutMsg.Close + else + InOut.In msg + + let bindings () = + let inBindings = + [ "Input" |> Binding.twoWay (Input.get, SetInput) + "IsChecked" |> Binding.twoWay (IsChecked.get, SetChecked) + "SubmitMsgVisibility" |> confirmStateVisibilityBinding ConfirmState.Submit + "CancelMsgVisibility" |> confirmStateVisibilityBinding ConfirmState.Cancel + "CloseMsgVisibility" |> confirmStateVisibilityBinding ConfirmState.Close ] + |> Bindings.mapMsg InOut.In + + let inOutBindings = + [ "Submit" |> Binding.cmd (confirmStateToMsg ConfirmState.Submit Submit) + "Cancel" |> Binding.cmd (confirmStateToMsg ConfirmState.Cancel Cancel) + "Close" |> Binding.cmd (confirmStateToMsg ConfirmState.Close Close) ] + + inBindings @ inOutBindings let designVm = ViewModel.designInstance Window2.init (Window2.bindings ()) \ No newline at end of file diff --git a/src/Samples/OneWaySeq.Core/Program.fs b/src/Samples/OneWaySeq.Core/Program.fs index eba678a1..56b8c71e 100644 --- a/src/Samples/OneWaySeq.Core/Program.fs +++ b/src/Samples/OneWaySeq.Core/Program.fs @@ -6,41 +6,44 @@ open Elmish.WPF type Model = - { OneWaySeqNumbers: int list - OneWayNumbers: int list } + { OneWaySeqNumbers: int list + OneWayNumbers: int list } let init () = - { OneWaySeqNumbers = [ 1000..-1..1 ] - OneWayNumbers = [ 1000..-1..1 ] } + { OneWaySeqNumbers = [ 1000..-1..1 ] + OneWayNumbers = [ 1000..-1..1 ] } type Msg = - | AddOneWaySeqNumber - | AddOneWayNumber + | AddOneWaySeqNumber + | AddOneWayNumber let update msg m = - match msg with - | AddOneWaySeqNumber -> { m with OneWaySeqNumbers = m.OneWaySeqNumbers.Head + 1 :: m.OneWaySeqNumbers } - | AddOneWayNumber -> { m with OneWayNumbers = m.OneWayNumbers.Head + 1 :: m.OneWayNumbers } - -let bindings () : Binding list = [ - "OneWaySeqNumbers" |> Binding.oneWaySeq((fun m -> m.OneWaySeqNumbers), (=), id) - "OneWayNumbers" |> Binding.oneWay (fun m -> m.OneWayNumbers) - "AddOneWaySeqNumber" |> Binding.cmd AddOneWaySeqNumber - "AddOneWayNumber" |> Binding.cmd AddOneWayNumber -] + match msg with + | AddOneWaySeqNumber -> + { m with + OneWaySeqNumbers = m.OneWaySeqNumbers.Head + 1 :: m.OneWaySeqNumbers } + | AddOneWayNumber -> + { m with + OneWayNumbers = m.OneWayNumbers.Head + 1 :: m.OneWayNumbers } + +let bindings () : Binding list = + [ "OneWaySeqNumbers" |> Binding.oneWaySeq ((fun m -> m.OneWaySeqNumbers), (=), id) + "OneWayNumbers" |> Binding.oneWay (fun m -> m.OneWayNumbers) + "AddOneWaySeqNumber" |> Binding.cmd AddOneWaySeqNumber + "AddOneWayNumber" |> Binding.cmd AddOneWayNumber ] let designVm = ViewModel.designInstance (init ()) (bindings ()) let main window = - let logger = - LoggerConfiguration() - .MinimumLevel.Override("Elmish.WPF.Update", Events.LogEventLevel.Verbose) - .MinimumLevel.Override("Elmish.WPF.Bindings", Events.LogEventLevel.Verbose) - .MinimumLevel.Override("Elmish.WPF.Performance", Events.LogEventLevel.Verbose) - .WriteTo.Console() - .CreateLogger() - - WpfProgram.mkSimple init update bindings - |> WpfProgram.withLogger (new SerilogLoggerFactory(logger)) - |> WpfProgram.startElmishLoop window + let logger = + LoggerConfiguration() + .MinimumLevel.Override("Elmish.WPF.Update", Events.LogEventLevel.Verbose) + .MinimumLevel.Override("Elmish.WPF.Bindings", Events.LogEventLevel.Verbose) + .MinimumLevel.Override("Elmish.WPF.Performance", Events.LogEventLevel.Verbose) + .WriteTo.Console() + .CreateLogger() + + WpfProgram.mkSimple init update bindings + |> WpfProgram.withLogger (new SerilogLoggerFactory(logger)) + |> WpfProgram.startElmishLoop window \ No newline at end of file diff --git a/src/Samples/SingleCounter.Core/Program.fs b/src/Samples/SingleCounter.Core/Program.fs index 3033ae91..059a272a 100644 --- a/src/Samples/SingleCounter.Core/Program.fs +++ b/src/Samples/SingleCounter.Core/Program.fs @@ -4,51 +4,44 @@ open Serilog open Serilog.Extensions.Logging open Elmish.WPF -type Model = - { Count: int - StepSize: int } +type Model = { Count: int; StepSize: int } type Msg = - | Increment - | Decrement - | SetStepSize of int - | Reset + | Increment + | Decrement + | SetStepSize of int + | Reset -let init = - { Count = 0 - StepSize = 1 } +let init = { Count = 0; StepSize = 1 } let canReset = (<>) init let update msg m = - match msg with - | Increment -> { m with Count = m.Count + m.StepSize } - | Decrement -> { m with Count = m.Count - m.StepSize } - | SetStepSize x -> { m with StepSize = x } - | Reset -> init - -let bindings () : Binding list = [ - "CounterValue" |> Binding.oneWay (fun m -> m.Count) - "Increment" |> Binding.cmd Increment - "Decrement" |> Binding.cmd Decrement - "StepSize" |> Binding.twoWay( - (fun m -> float m.StepSize), - int >> SetStepSize) - "Reset" |> Binding.cmdIf(Reset, canReset) -] + match msg with + | Increment -> { m with Count = m.Count + m.StepSize } + | Decrement -> { m with Count = m.Count - m.StepSize } + | SetStepSize x -> { m with StepSize = x } + | Reset -> init + +let bindings () : Binding list = + [ "CounterValue" |> Binding.oneWay (fun m -> m.Count) + "Increment" |> Binding.cmd Increment + "Decrement" |> Binding.cmd Decrement + "StepSize" |> Binding.twoWay ((fun m -> float m.StepSize), int >> SetStepSize) + "Reset" |> Binding.cmdIf (Reset, canReset) ] let designVm = ViewModel.designInstance init (bindings ()) let main window = - let logger = - LoggerConfiguration() - .MinimumLevel.Override("Elmish.WPF.Update", Events.LogEventLevel.Verbose) - .MinimumLevel.Override("Elmish.WPF.Bindings", Events.LogEventLevel.Verbose) - .MinimumLevel.Override("Elmish.WPF.Performance", Events.LogEventLevel.Verbose) - .WriteTo.Console() - .CreateLogger() - - WpfProgram.mkSimple (fun () -> init) update bindings - |> WpfProgram.withLogger (new SerilogLoggerFactory(logger)) - |> WpfProgram.startElmishLoop window + let logger = + LoggerConfiguration() + .MinimumLevel.Override("Elmish.WPF.Update", Events.LogEventLevel.Verbose) + .MinimumLevel.Override("Elmish.WPF.Bindings", Events.LogEventLevel.Verbose) + .MinimumLevel.Override("Elmish.WPF.Performance", Events.LogEventLevel.Verbose) + .WriteTo.Console() + .CreateLogger() + + WpfProgram.mkSimple (fun () -> init) update bindings + |> WpfProgram.withLogger (new SerilogLoggerFactory(logger)) + |> WpfProgram.startElmishLoop window \ No newline at end of file diff --git a/src/Samples/Sticky.Core/Program.fs b/src/Samples/Sticky.Core/Program.fs index 5bed469e..727bcf6b 100644 --- a/src/Samples/Sticky.Core/Program.fs +++ b/src/Samples/Sticky.Core/Program.fs @@ -4,54 +4,47 @@ open Serilog open Serilog.Extensions.Logging open Elmish.WPF -type Model = - { Count: int - StepSize: int } +type Model = { Count: int; StepSize: int } type Msg = - | Increment - | Decrement - | SetStepSize of int - | Reset + | Increment + | Decrement + | SetStepSize of int + | Reset -let init = - { Count = 0 - StepSize = 1 } +let init = { Count = 0; StepSize = 1 } let canReset = (<>) init let update msg m = - match msg with - | Increment -> { m with Count = m.Count + m.StepSize } - | Decrement -> { m with Count = m.Count - m.StepSize } - | SetStepSize x -> { m with StepSize = x } - | Reset -> init - -let bindings () : Binding list = [ - "CounterValue" - |> Binding.oneWay id - |> Binding.addSticky (fun v -> v % 2 = 0) - |> Binding.mapModel (fun m -> m.Count) - "Increment" |> Binding.cmd Increment - "Decrement" |> Binding.cmd Decrement - "StepSize" |> Binding.twoWay( - (fun m -> float m.StepSize), - int >> SetStepSize) - "Reset" |> Binding.cmdIf(Reset, canReset) -] + match msg with + | Increment -> { m with Count = m.Count + m.StepSize } + | Decrement -> { m with Count = m.Count - m.StepSize } + | SetStepSize x -> { m with StepSize = x } + | Reset -> init + +let bindings () : Binding list = + [ "CounterValue" + |> Binding.oneWay id + |> Binding.addSticky (fun v -> v % 2 = 0) + |> Binding.mapModel (fun m -> m.Count) + "Increment" |> Binding.cmd Increment + "Decrement" |> Binding.cmd Decrement + "StepSize" |> Binding.twoWay ((fun m -> float m.StepSize), int >> SetStepSize) + "Reset" |> Binding.cmdIf (Reset, canReset) ] let designVm = ViewModel.designInstance init (bindings ()) let main window = - let logger = - LoggerConfiguration() - .MinimumLevel.Override("Elmish.WPF.Update", Events.LogEventLevel.Verbose) - .MinimumLevel.Override("Elmish.WPF.Bindings", Events.LogEventLevel.Verbose) - .MinimumLevel.Override("Elmish.WPF.Performance", Events.LogEventLevel.Verbose) - .WriteTo.Console() - .CreateLogger() - - WpfProgram.mkSimple (fun () -> init) update bindings - |> WpfProgram.withLogger (new SerilogLoggerFactory(logger)) - |> WpfProgram.startElmishLoop window + let logger = + LoggerConfiguration() + .MinimumLevel.Override("Elmish.WPF.Update", Events.LogEventLevel.Verbose) + .MinimumLevel.Override("Elmish.WPF.Bindings", Events.LogEventLevel.Verbose) + .MinimumLevel.Override("Elmish.WPF.Performance", Events.LogEventLevel.Verbose) + .WriteTo.Console() + .CreateLogger() + + WpfProgram.mkSimple (fun () -> init) update bindings + |> WpfProgram.withLogger (new SerilogLoggerFactory(logger)) + |> WpfProgram.startElmishLoop window \ No newline at end of file diff --git a/src/Samples/SubModel.Core/Program.fs b/src/Samples/SubModel.Core/Program.fs index 0529da51..adf21687 100644 --- a/src/Samples/SubModel.Core/Program.fs +++ b/src/Samples/SubModel.Core/Program.fs @@ -8,177 +8,173 @@ open Elmish.WPF module Counter = - type Model = - { Count: int - StepSize: int } - - type Msg = - | Increment - | Decrement - | SetStepSize of int - | Reset - - let init = - { Count = 0 - StepSize = 1 } - - let canReset = (<>) init - - let update msg m = - match msg with - | Increment -> { m with Count = m.Count + m.StepSize } - | Decrement -> { m with Count = m.Count - m.StepSize } - | SetStepSize x -> { m with StepSize = x } - | Reset -> init - - let bindings () : Binding list = [ - "CounterValue" |> Binding.oneWay (fun m -> m.Count) - "Increment" |> Binding.cmd Increment - "Decrement" |> Binding.cmd Decrement - "StepSize" |> Binding.twoWay( - (fun m -> float m.StepSize), - int >> SetStepSize) - "Reset" |> Binding.cmdIf(Reset, canReset) - ] + type Model = { Count: int; StepSize: int } + type Msg = + | Increment + | Decrement + | SetStepSize of int + | Reset -module Clock = + let init = { Count = 0; StepSize = 1 } - type TimeType = - | Utc - | Local + let canReset = (<>) init - type Model = - { Time: DateTimeOffset - TimeType: TimeType } + let update msg m = + match msg with + | Increment -> { m with Count = m.Count + m.StepSize } + | Decrement -> { m with Count = m.Count - m.StepSize } + | SetStepSize x -> { m with StepSize = x } + | Reset -> init - let init () = - { Time = DateTimeOffset.Now - TimeType = Local } + let bindings () : Binding list = + [ "CounterValue" |> Binding.oneWay (fun m -> m.Count) + "Increment" |> Binding.cmd Increment + "Decrement" |> Binding.cmd Decrement + "StepSize" |> Binding.twoWay ((fun m -> float m.StepSize), int >> SetStepSize) + "Reset" |> Binding.cmdIf (Reset, canReset) ] - let getTime m = - match m.TimeType with - | Utc -> m.Time.UtcDateTime - | Local -> m.Time.LocalDateTime - type Msg = - | Tick of DateTimeOffset - | SetTimeType of TimeType +module Clock = - let update msg m = - match msg with - | Tick t -> { m with Time = t } - | SetTimeType t -> { m with TimeType = t } + type TimeType = + | Utc + | Local - let bindings () : Binding list = [ - "Time" |> Binding.oneWay getTime - "IsLocal" |> Binding.oneWay (fun m -> m.TimeType = Local) - "SetLocal" |> Binding.cmd (SetTimeType Local) - "IsUtc" |> Binding.oneWay (fun m -> m.TimeType = Utc) - "SetUtc" |> Binding.cmd (SetTimeType Utc) - ] + type Model = + { Time: DateTimeOffset + TimeType: TimeType } + let init () = + { Time = DateTimeOffset.Now + TimeType = Local } -module CounterWithClock = + let getTime m = + match m.TimeType with + | Utc -> m.Time.UtcDateTime + | Local -> m.Time.LocalDateTime - type Model = - { Counter: Counter.Model - Clock: Clock.Model } + type Msg = + | Tick of DateTimeOffset + | SetTimeType of TimeType - let init () = - { Counter = Counter.init - Clock = Clock.init () } + let update msg m = + match msg with + | Tick t -> { m with Time = t } + | SetTimeType t -> { m with TimeType = t } - type Msg = - | CounterMsg of Counter.Msg - | ClockMsg of Clock.Msg + let bindings () : Binding list = + [ "Time" |> Binding.oneWay getTime + "IsLocal" |> Binding.oneWay (fun m -> m.TimeType = Local) + "SetLocal" |> Binding.cmd (SetTimeType Local) + "IsUtc" |> Binding.oneWay (fun m -> m.TimeType = Utc) + "SetUtc" |> Binding.cmd (SetTimeType Utc) ] - let update msg m = - match msg with - | CounterMsg msg -> { m with Counter = Counter.update msg m.Counter } - | ClockMsg msg -> { m with Clock = Clock.update msg m.Clock } - let bindings () : Binding list = [ - "Counter" - |> Binding.SubModel.required Counter.bindings - |> Binding.mapModel (fun m -> m.Counter) - |> Binding.mapMsg CounterMsg - "Clock" - |> Binding.SubModel.required Clock.bindings - |> Binding.mapModel (fun m -> m.Clock) - |> Binding.mapMsg ClockMsg - ] +module CounterWithClock = + + type Model = + { Counter: Counter.Model + Clock: Clock.Model } + + let init () = + { Counter = Counter.init + Clock = Clock.init () } + + type Msg = + | CounterMsg of Counter.Msg + | ClockMsg of Clock.Msg + + let update msg m = + match msg with + | CounterMsg msg -> + { m with + Counter = Counter.update msg m.Counter } + | ClockMsg msg -> + { m with + Clock = Clock.update msg m.Clock } + + let bindings () : Binding list = + [ "Counter" + |> Binding.SubModel.required Counter.bindings + |> Binding.mapModel (fun m -> m.Counter) + |> Binding.mapMsg CounterMsg + "Clock" + |> Binding.SubModel.required Clock.bindings + |> Binding.mapModel (fun m -> m.Clock) + |> Binding.mapMsg ClockMsg ] module App = - type Model = - { ClockCounter1: CounterWithClock.Model - ClockCounter2: CounterWithClock.Model } + type Model = + { ClockCounter1: CounterWithClock.Model + ClockCounter2: CounterWithClock.Model } - let init () = - { ClockCounter1 = CounterWithClock.init () - ClockCounter2 = CounterWithClock.init () } + let init () = + { ClockCounter1 = CounterWithClock.init () + ClockCounter2 = CounterWithClock.init () } - type Msg = - | ClockCounter1Msg of CounterWithClock.Msg - | ClockCounter2Msg of CounterWithClock.Msg + type Msg = + | ClockCounter1Msg of CounterWithClock.Msg + | ClockCounter2Msg of CounterWithClock.Msg - let update msg m = - match msg with - | ClockCounter1Msg msg -> - { m with ClockCounter1 = CounterWithClock.update msg m.ClockCounter1 } - | ClockCounter2Msg msg -> - { m with ClockCounter2 = CounterWithClock.update msg m.ClockCounter2 } + let update msg m = + match msg with + | ClockCounter1Msg msg -> + { m with + ClockCounter1 = CounterWithClock.update msg m.ClockCounter1 } + | ClockCounter2Msg msg -> + { m with + ClockCounter2 = CounterWithClock.update msg m.ClockCounter2 } - let bindings () : Binding list = [ - "ClockCounter1" - |> Binding.SubModel.required CounterWithClock.bindings - |> Binding.mapModel (fun m -> m.ClockCounter1) - |> Binding.mapMsg ClockCounter1Msg + let bindings () : Binding list = + [ "ClockCounter1" + |> Binding.SubModel.required CounterWithClock.bindings + |> Binding.mapModel (fun m -> m.ClockCounter1) + |> Binding.mapMsg ClockCounter1Msg - "ClockCounter2" - |> Binding.SubModel.required CounterWithClock.bindings - |> Binding.mapModel (fun m -> m.ClockCounter2) - |> Binding.mapMsg ClockCounter2Msg - ] + "ClockCounter2" + |> Binding.SubModel.required CounterWithClock.bindings + |> Binding.mapModel (fun m -> m.ClockCounter2) + |> Binding.mapMsg ClockCounter2Msg ] let counterDesignVm = ViewModel.designInstance Counter.init (Counter.bindings ()) let clockDesignVm = ViewModel.designInstance (Clock.init ()) (Clock.bindings ()) -let counterWithClockDesignVm = ViewModel.designInstance (CounterWithClock.init ()) (CounterWithClock.bindings ()) + +let counterWithClockDesignVm = + ViewModel.designInstance (CounterWithClock.init ()) (CounterWithClock.bindings ()) + let mainDesignVm = ViewModel.designInstance (App.init ()) (App.bindings ()) let subscriptions (model: App.Model) : Sub = - let timerTickSub dispatch = - let timer = new System.Timers.Timer(1000.) - let disp = timer.Elapsed.Subscribe(fun _ -> - let clockMsg = - DateTimeOffset.Now - |> Clock.Tick - |> CounterWithClock.ClockMsg - dispatch <| App.ClockCounter1Msg clockMsg - dispatch <| App.ClockCounter2Msg clockMsg - ) - timer.Start() - disp - - [ - [ nameof timerTickSub ], timerTickSub - ] + let timerTickSub dispatch = + let timer = new System.Timers.Timer(1000.) + + let disp = + timer.Elapsed.Subscribe(fun _ -> + let clockMsg = DateTimeOffset.Now |> Clock.Tick |> CounterWithClock.ClockMsg + dispatch <| App.ClockCounter1Msg clockMsg + dispatch <| App.ClockCounter2Msg clockMsg) + + timer.Start() + disp + + [ [ nameof timerTickSub ], timerTickSub ] let main window = - let logger = - LoggerConfiguration() - .MinimumLevel.Override("Elmish.WPF.Update", Events.LogEventLevel.Verbose) - .MinimumLevel.Override("Elmish.WPF.Bindings", Events.LogEventLevel.Verbose) - .MinimumLevel.Override("Elmish.WPF.Performance", Events.LogEventLevel.Verbose) - .WriteTo.Console() - .CreateLogger() - - WpfProgram.mkSimple App.init App.update App.bindings - |> WpfProgram.withSubscription subscriptions - |> WpfProgram.withLogger (new SerilogLoggerFactory(logger)) - |> WpfProgram.startElmishLoop window + let logger = + LoggerConfiguration() + .MinimumLevel.Override("Elmish.WPF.Update", Events.LogEventLevel.Verbose) + .MinimumLevel.Override("Elmish.WPF.Bindings", Events.LogEventLevel.Verbose) + .MinimumLevel.Override("Elmish.WPF.Performance", Events.LogEventLevel.Verbose) + .WriteTo.Console() + .CreateLogger() + + WpfProgram.mkSimple App.init App.update App.bindings + |> WpfProgram.withSubscription subscriptions + |> WpfProgram.withLogger (new SerilogLoggerFactory(logger)) + |> WpfProgram.startElmishLoop window \ No newline at end of file diff --git a/src/Samples/SubModelOpt.Core/Program.fs b/src/Samples/SubModelOpt.Core/Program.fs index 3a8dfe50..9dbb6628 100644 --- a/src/Samples/SubModelOpt.Core/Program.fs +++ b/src/Samples/SubModelOpt.Core/Program.fs @@ -7,111 +7,120 @@ open Elmish.WPF module Form1 = - type Model = - { Text: string } + type Model = { Text: string } - type Msg = - | SetText of string - | Submit + type Msg = + | SetText of string + | Submit - let init = - { Text = "" } + let init = { Text = "" } - let update msg m = - match msg with - | SetText s -> { m with Text = s } - | Submit -> m // handled by parent + let update msg m = + match msg with + | SetText s -> { m with Text = s } + | Submit -> m // handled by parent - let bindings () : Binding list = [ - "Text" |> Binding.twoWay ((fun m -> m.Text), SetText) - "Submit" |> Binding.cmd Submit - ] + let bindings () : Binding list = + [ "Text" |> Binding.twoWay ((fun m -> m.Text), SetText) + "Submit" |> Binding.cmd Submit ] module Form2 = - type Model = - { Text1: string - Text2: string } + type Model = { Text1: string; Text2: string } - type Msg = - | SetText1 of string - | SetText2 of string - | Submit + type Msg = + | SetText1 of string + | SetText2 of string + | Submit - let init = - { Text1 = "" - Text2 = "" } + let init = { Text1 = ""; Text2 = "" } - let update msg m = - match msg with - | SetText1 s -> { m with Text1 = s } - | SetText2 s -> { m with Text2 = s } - | Submit -> m // handled by parent + let update msg m = + match msg with + | SetText1 s -> { m with Text1 = s } + | SetText2 s -> { m with Text2 = s } + | Submit -> m // handled by parent - let bindings () : Binding list = [ - "Text1" |> Binding.twoWay ((fun m -> m.Text1), SetText1) - "Text2" |> Binding.twoWay ((fun m -> m.Text2), SetText2) - "Submit" |> Binding.cmd Submit - ] + let bindings () : Binding list = + [ "Text1" |> Binding.twoWay ((fun m -> m.Text1), SetText1) + "Text2" |> Binding.twoWay ((fun m -> m.Text2), SetText2) + "Submit" |> Binding.cmd Submit ] module App = - type Dialog = - | Form1 of Form1.Model - | Form2 of Form2.Model - - type Model = - { Dialog: Dialog option } - - let init () = - { Dialog = None } - - type Msg = - | ShowForm1 - | ShowForm2 - | Form1Msg of Form1.Msg - | Form2Msg of Form2.Msg - - let update msg m = - match msg with - | ShowForm1 -> { m with Dialog = Some <| Form1 Form1.init } - | ShowForm2 -> { m with Dialog = Some <| Form2 Form2.init } - | Form1Msg Form1.Submit -> { m with Dialog = None } - | Form1Msg msg' -> - match m.Dialog with - | Some (Form1 m') -> { m with Dialog = Form1.update msg' m' |> Form1 |> Some } - | _ -> m - | Form2Msg Form2.Submit -> { m with Dialog = None } - | Form2Msg msg' -> - match m.Dialog with - | Some (Form2 m') -> { m with Dialog = Form2.update msg' m' |> Form2 |> Some } - | _ -> m - - let bindings () : Binding list = [ - "ShowForm1" |> Binding.cmd ShowForm1 - - "ShowForm2" |> Binding.cmd ShowForm2 - - "DialogVisible" |> Binding.oneWay (fun m -> m.Dialog.IsSome) - - "Form1Visible" |> Binding.oneWay - (fun m -> match m.Dialog with Some (Form1 _) -> true | _ -> false) - - "Form2Visible" |> Binding.oneWay - (fun m -> match m.Dialog with Some (Form2 _) -> true | _ -> false) - - "Form1" - |> Binding.SubModel.opt Form1.bindings - |> Binding.mapModel (fun m -> match m.Dialog with Some (Form1 m') -> Some m' | _ -> None) - |> Binding.mapMsg Form1Msg - - "Form2" - |> Binding.SubModel.opt Form2.bindings - |> Binding.mapModel (fun m -> match m.Dialog with Some (Form2 m') -> Some m' | _ -> None) - |> Binding.mapMsg Form2Msg - ] + type Dialog = + | Form1 of Form1.Model + | Form2 of Form2.Model + + type Model = { Dialog: Dialog option } + + let init () = { Dialog = None } + + type Msg = + | ShowForm1 + | ShowForm2 + | Form1Msg of Form1.Msg + | Form2Msg of Form2.Msg + + let update msg m = + match msg with + | ShowForm1 -> + { m with + Dialog = Some <| Form1 Form1.init } + | ShowForm2 -> + { m with + Dialog = Some <| Form2 Form2.init } + | Form1Msg Form1.Submit -> { m with Dialog = None } + | Form1Msg msg' -> + match m.Dialog with + | Some(Form1 m') -> + { m with + Dialog = Form1.update msg' m' |> Form1 |> Some } + | _ -> m + | Form2Msg Form2.Submit -> { m with Dialog = None } + | Form2Msg msg' -> + match m.Dialog with + | Some(Form2 m') -> + { m with + Dialog = Form2.update msg' m' |> Form2 |> Some } + | _ -> m + + let bindings () : Binding list = + [ "ShowForm1" |> Binding.cmd ShowForm1 + + "ShowForm2" |> Binding.cmd ShowForm2 + + "DialogVisible" |> Binding.oneWay (fun m -> m.Dialog.IsSome) + + "Form1Visible" + |> Binding.oneWay (fun m -> + match m.Dialog with + | Some(Form1 _) -> true + | _ -> false) + + "Form2Visible" + |> Binding.oneWay (fun m -> + match m.Dialog with + | Some(Form2 _) -> true + | _ -> false) + + "Form1" + |> Binding.SubModel.opt Form1.bindings + |> Binding.mapModel (fun m -> + match m.Dialog with + | Some(Form1 m') -> Some m' + | _ -> None) + |> Binding.mapMsg Form1Msg + + "Form2" + |> Binding.SubModel.opt Form2.bindings + |> Binding.mapModel (fun m -> + match m.Dialog with + | Some(Form2 m') -> Some m' + | _ -> None) + |> Binding.mapMsg Form2Msg ] let form1DesignVm = ViewModel.designInstance Form1.init (Form1.bindings ()) @@ -121,14 +130,14 @@ let mainDesignVm = ViewModel.designInstance (App.init ()) (App.bindings ()) let main window = - let logger = - LoggerConfiguration() - .MinimumLevel.Override("Elmish.WPF.Update", Events.LogEventLevel.Verbose) - .MinimumLevel.Override("Elmish.WPF.Bindings", Events.LogEventLevel.Verbose) - .MinimumLevel.Override("Elmish.WPF.Performance", Events.LogEventLevel.Verbose) - .WriteTo.Console() - .CreateLogger() - - WpfProgram.mkSimple App.init App.update App.bindings - |> WpfProgram.withLogger (new SerilogLoggerFactory(logger)) - |> WpfProgram.startElmishLoop window + let logger = + LoggerConfiguration() + .MinimumLevel.Override("Elmish.WPF.Update", Events.LogEventLevel.Verbose) + .MinimumLevel.Override("Elmish.WPF.Bindings", Events.LogEventLevel.Verbose) + .MinimumLevel.Override("Elmish.WPF.Performance", Events.LogEventLevel.Verbose) + .WriteTo.Console() + .CreateLogger() + + WpfProgram.mkSimple App.init App.update App.bindings + |> WpfProgram.withLogger (new SerilogLoggerFactory(logger)) + |> WpfProgram.startElmishLoop window \ No newline at end of file diff --git a/src/Samples/SubModelSelectedItem.Core/Program.fs b/src/Samples/SubModelSelectedItem.Core/Program.fs index c8f970af..843f4582 100644 --- a/src/Samples/SubModelSelectedItem.Core/Program.fs +++ b/src/Samples/SubModelSelectedItem.Core/Program.fs @@ -5,53 +5,52 @@ open Serilog open Serilog.Extensions.Logging open Elmish.WPF -type Entity = - { Id: int - Name: string } +type Entity = { Id: int; Name: string } type Model = - { Entities: Entity list - Selected: int option } + { Entities: Entity list + Selected: int option } let init () = - { Entities = [0 .. 10] |> List.map (fun i -> { Id = i; Name = sprintf "Entity %i" i}) - Selected = Some 4 } + { Entities = [ 0..10 ] |> List.map (fun i -> { Id = i; Name = sprintf "Entity %i" i }) + Selected = Some 4 } -type Msg = - | Select of int option +type Msg = Select of int option let update msg m = - match msg with - | Select entityId -> { m with Selected = entityId } + match msg with + | Select entityId -> { m with Selected = entityId } -let bindings () : Binding list = [ - "SelectRandom" |> Binding.cmd - (fun m -> m.Entities.Item(Random().Next(m.Entities.Length)).Id |> Some |> Select) +let bindings () : Binding list = + [ "SelectRandom" + |> Binding.cmd (fun m -> m.Entities.Item(Random().Next(m.Entities.Length)).Id |> Some |> Select) - "Deselect" |> Binding.cmd(Select None) + "Deselect" |> Binding.cmd (Select None) - "Entities" |> Binding.subModelSeq( - (fun m -> m.Entities), - (fun e -> e.Id), - (fun () -> [ - "Name" |> Binding.oneWay (fun (_, e) -> e.Name) - "SelectedLabel" |> Binding.oneWay (fun (m, e) -> if m.Selected = Some e.Id then " - SELECTED" else "") - ])) + "Entities" + |> Binding.subModelSeq ( + (fun m -> m.Entities), + (fun e -> e.Id), + (fun () -> + [ "Name" |> Binding.oneWay (fun (_, e) -> e.Name) + "SelectedLabel" + |> Binding.oneWay (fun (m, e) -> if m.Selected = Some e.Id then " - SELECTED" else "") ]) + ) - "SelectedEntity" |> Binding.subModelSelectedItem("Entities", (fun m -> m.Selected), Select) -] + "SelectedEntity" + |> Binding.subModelSelectedItem ("Entities", (fun m -> m.Selected), Select) ] let designVm = ViewModel.designInstance (init ()) (bindings ()) let main window = - let logger = - LoggerConfiguration() - .MinimumLevel.Override("Elmish.WPF.Update", Events.LogEventLevel.Verbose) - .MinimumLevel.Override("Elmish.WPF.Bindings", Events.LogEventLevel.Verbose) - .MinimumLevel.Override("Elmish.WPF.Performance", Events.LogEventLevel.Verbose) - .WriteTo.Console() - .CreateLogger() - - WpfProgram.mkSimple init update bindings - |> WpfProgram.withLogger (new SerilogLoggerFactory(logger)) - |> WpfProgram.startElmishLoop window + let logger = + LoggerConfiguration() + .MinimumLevel.Override("Elmish.WPF.Update", Events.LogEventLevel.Verbose) + .MinimumLevel.Override("Elmish.WPF.Bindings", Events.LogEventLevel.Verbose) + .MinimumLevel.Override("Elmish.WPF.Performance", Events.LogEventLevel.Verbose) + .WriteTo.Console() + .CreateLogger() + + WpfProgram.mkSimple init update bindings + |> WpfProgram.withLogger (new SerilogLoggerFactory(logger)) + |> WpfProgram.startElmishLoop window \ No newline at end of file diff --git a/src/Samples/SubModelSeq.Core/Program.fs b/src/Samples/SubModelSeq.Core/Program.fs index 1f0a6f5c..5ad6297b 100644 --- a/src/Samples/SubModelSeq.Core/Program.fs +++ b/src/Samples/SubModelSeq.Core/Program.fs @@ -7,308 +7,302 @@ open Elmish.WPF type InOutMsg<'a, 'b> = - | InMsg of 'a - | OutMsg of 'b + | InMsg of 'a + | OutMsg of 'b module Option = - let set a = Option.map (fun _ -> a) + let set a = Option.map (fun _ -> a) module Func = - let flip f b a = f a b + let flip f b a = f a b module FuncOption = - let inputIfNone f a = a |> f |> Option.defaultValue a + let inputIfNone f a = a |> f |> Option.defaultValue a - let map (f: 'b -> 'c) (mb: 'a -> 'b option) = - mb >> Option.map f + let map (f: 'b -> 'c) (mb: 'a -> 'b option) = mb >> Option.map f - let bind (f: 'b -> 'a -> 'c) (mb: 'a -> 'b option) a = - mb a |> Option.bind (fun b -> Some(f b a)) + let bind (f: 'b -> 'a -> 'c) (mb: 'a -> 'b option) a = + mb a |> Option.bind (fun b -> Some(f b a)) -let map get set f a = - a |> get |> f |> Func.flip set a +let map get set f a = a |> get |> f |> Func.flip set a module List = - let swap i j = - List.permute - (function - | a when a = i -> j - | a when a = j -> i - | a -> a) + let swap i j = + List.permute (function + | a when a = i -> j + | a when a = j -> i + | a -> a) - let swapWithNext i = swap i (i + 1) - let swapWithPrev i = swap i (i - 1) + let swapWithNext i = swap i (i + 1) + let swapWithPrev i = swap i (i - 1) - let cons head tail = head :: tail + let cons head tail = head :: tail - let mapFirst p f input = - let rec mapFirstRec reverseFront back = - match back with - | [] -> - (* + let mapFirst p f input = + let rec mapFirstRec reverseFront back = + match back with + | [] -> + (* * Conceptually, the correct value to return is * reverseFront |> List.rev * but this is the same as * input * so returning that instead. *) - input - | a :: ma -> - if p a then - (reverseFront |> List.rev) @ (f a :: ma) - else - mapFirstRec (a :: reverseFront) ma - mapFirstRec [] input + input + | a :: ma -> + if p a then + (reverseFront |> List.rev) @ (f a :: ma) + else + mapFirstRec (a :: reverseFront) ma + + mapFirstRec [] input [] module Identifiable = - type Identifiable<'a> = - { Id: Guid - Value: 'a } + type Identifiable<'a> = { Id: Guid; Value: 'a } - module Identifiable = + module Identifiable = - let getId m = m.Id - let get m = m.Value - let set v m = { m with Value = v } - let map f = f |> map get set + let getId m = m.Id + let get m = m.Value + let set v m = { m with Value = v } + let map f = f |> map get set [] module Counter = - type Counter = - { Count: int - StepSize: int } + type Counter = { Count: int; StepSize: int } - type CounterMsg = - | Increment - | Decrement - | SetStepSize of int - | Reset + type CounterMsg = + | Increment + | Decrement + | SetStepSize of int + | Reset - module Counter = + module Counter = - let init = - { Count = 0 - StepSize = 1 } + let init = { Count = 0; StepSize = 1 } - let canReset = (<>) init + let canReset = (<>) init - let update msg m = - match msg with - | Increment -> { m with Count = m.Count + m.StepSize } - | Decrement -> { m with Count = m.Count - m.StepSize } - | SetStepSize x -> { m with StepSize = x } - | Reset -> init + let update msg m = + match msg with + | Increment -> { m with Count = m.Count + m.StepSize } + | Decrement -> { m with Count = m.Count - m.StepSize } + | SetStepSize x -> { m with StepSize = x } + | Reset -> init - let bindings () : Binding list = [ - "CounterValue" |> Binding.oneWay (fun m -> m.Count) - "Increment" |> Binding.cmd Increment - "Decrement" |> Binding.cmd Decrement - "StepSize" |> Binding.twoWay( - (fun m -> float m.StepSize), - int >> SetStepSize) - "Reset" |> Binding.cmdIf(Reset, canReset) - ] + let bindings () : Binding list = + [ "CounterValue" |> Binding.oneWay (fun m -> m.Count) + "Increment" |> Binding.cmd Increment + "Decrement" |> Binding.cmd Decrement + "StepSize" |> Binding.twoWay ((fun m -> float m.StepSize), int >> SetStepSize) + "Reset" |> Binding.cmdIf (Reset, canReset) ] [] module RoseTree = - type RoseTree<'model> = - { Data: 'model - Children: RoseTree<'model> list } + type RoseTree<'model> = + { Data: 'model + Children: RoseTree<'model> list } + + type RoseTreeMsg<'a, 'msg> = + | BranchMsg of 'a * RoseTreeMsg<'a, 'msg> + | LeafMsg of 'msg - type RoseTreeMsg<'a, 'msg> = - | BranchMsg of 'a * RoseTreeMsg<'a, 'msg> - | LeafMsg of 'msg + module RoseTree = - module RoseTree = + let create data children = { Data = data; Children = children } + let createLeaf a = create a [] - let create data children = - { Data = data - Children = children } - let createLeaf a = create a [] + let getData t = t.Data + let setData (d: 'a) (t: RoseTree<'a>) = { t with Data = d } + let mapData f = map getData setData f - let getData t = t.Data - let setData (d: 'a) (t: RoseTree<'a>) = { t with Data = d } - let mapData f = map getData setData f + let getChildren t = t.Children + let setChildren c t = { t with Children = c } + let mapChildren f = map getChildren setChildren f - let getChildren t = t.Children - let setChildren c t = { t with Children = c } - let mapChildren f = map getChildren setChildren f + let addSubtree t = t |> List.cons |> mapChildren + let addChildData a = a |> createLeaf |> addSubtree - let addSubtree t = t |> List.cons |> mapChildren - let addChildData a = a |> createLeaf |> addSubtree + let update p (f: 'msg -> RoseTree<'model> -> RoseTree<'model>) = + let rec updateRec = + function + | BranchMsg(a, msg) -> msg |> updateRec |> List.mapFirst (p a) |> mapChildren + | LeafMsg msg -> msg |> f - let update p (f: 'msg -> RoseTree<'model> -> RoseTree<'model>) = - let rec updateRec = function - | BranchMsg (a, msg) -> msg |> updateRec |> List.mapFirst (p a) |> mapChildren - | LeafMsg msg -> msg |> f - updateRec + updateRec module App = - type Model = - { SomeGlobalState: bool - DummyRoot: RoseTree> } - - type SubtreeMsg = - | CounterMsg of CounterMsg - | AddChild - | Remove of Guid - | MoveUp of Guid - | MoveDown of Guid - - type SubtreeOutMsg = - | OutRemove - | OutMoveUp - | OutMoveDown - - type Msg = - | ToggleGlobalState - | SubtreeMsg of RoseTreeMsg - - - let getSomeGlobalState m = m.SomeGlobalState - let setSomeGlobalState v m = { m with SomeGlobalState = v } - let mapSomeGlobalState f = f |> map getSomeGlobalState setSomeGlobalState - - let getDummyRoot m = m.DummyRoot - let setDummyRoot v m = { m with DummyRoot = v } - let mapDummyRoot f = f |> map getDummyRoot setDummyRoot - - let createNewIdentifiableCounter () = - { Id = Guid.NewGuid () - Value = Counter.init } - - let createNewLeaf () = - createNewIdentifiableCounter () - |> RoseTree.createLeaf - - let init () = - let dummyRootData = createNewIdentifiableCounter () // Placeholder data to satisfy type system. User never sees this. - { SomeGlobalState = false - DummyRoot = - createNewLeaf () - |> List.singleton - |> RoseTree.create dummyRootData } - - let hasId id t = t.Data.Id = id - - let swapCounters swap nId = - nId - |> hasId - |> List.tryFindIndex - |> FuncOption.bind swap - |> FuncOption.inputIfNone - - let updateSubtree = function - | CounterMsg msg -> msg |> Counter.update |> Identifiable.map |> RoseTree.mapData - | AddChild -> createNewLeaf () |> List.cons |> RoseTree.mapChildren - | Remove cId -> cId |> hasId >> not |> List.filter |> RoseTree.mapChildren - | MoveUp cId -> cId |> swapCounters List.swapWithPrev |> RoseTree.mapChildren - | MoveDown cId -> cId |> swapCounters List.swapWithNext |> RoseTree.mapChildren - - let update = function - | ToggleGlobalState -> mapSomeGlobalState not - | SubtreeMsg msg -> msg |> RoseTree.update hasId updateSubtree |> mapDummyRoot - - let mapOutMsg = function - | OutRemove -> Remove - | OutMoveUp -> MoveUp - | OutMoveDown -> MoveDown + type Model = + { SomeGlobalState: bool + DummyRoot: RoseTree> } + type SubtreeMsg = + | CounterMsg of CounterMsg + | AddChild + | Remove of Guid + | MoveUp of Guid + | MoveDown of Guid -module Bindings = + type SubtreeOutMsg = + | OutRemove + | OutMoveUp + | OutMoveDown - open App - - type SelfWithParent<'a> = - { Self: 'a - Parent: 'a } - - let moveUpMsg (_, { Parent = p; Self = s }) = - match p.Children |> List.tryHead with - | Some c when c.Data.Id <> s.Data.Id -> - OutMoveUp |> Some - | _ -> None - - let moveDownMsg (_, { Parent = p; Self = s }) = - match p.Children |> List.tryLast with - | Some c when c.Data.Id <> s.Data.Id -> - OutMoveDown |> Some - | _ -> None - - let rec subtreeBindings () : Binding>>, InOutMsg, SubtreeOutMsg>> list = - let counterBindings = - Counter.bindings () - |> Bindings.mapModel (fun (_, { Self = s }) -> s.Data.Value) - |> Bindings.mapMsg (CounterMsg >> LeafMsg) - - let inMsgBindings = - [ "CounterIdText" |> Binding.oneWay(fun (_, { Self = s }) -> s.Data.Id) - "AddChild" |> Binding.cmd(AddChild |> LeafMsg) - "GlobalState" |> Binding.oneWay(fun (m, _) -> m.SomeGlobalState) - "ChildCounters" - |> Binding.subModelSeq (subtreeBindings, (fun (_, { Self = c }) -> c.Data.Id)) - |> Binding.mapModel (fun (m, { Self = p }) -> p.Children |> Seq.map (fun c -> m, { Self = c; Parent = p })) - |> Binding.mapMsg (fun (cId, inOutMsg) -> - match inOutMsg with - | InMsg msg -> (cId, msg) |> BranchMsg - | OutMsg msg -> cId |> mapOutMsg msg |> LeafMsg) - ] @ counterBindings - |> Bindings.mapMsg InMsg + type Msg = + | ToggleGlobalState + | SubtreeMsg of RoseTreeMsg + + + let getSomeGlobalState m = m.SomeGlobalState + let setSomeGlobalState v m = { m with SomeGlobalState = v } + + let mapSomeGlobalState f = + f |> map getSomeGlobalState setSomeGlobalState + + let getDummyRoot m = m.DummyRoot + let setDummyRoot v m = { m with DummyRoot = v } + let mapDummyRoot f = f |> map getDummyRoot setDummyRoot + + let createNewIdentifiableCounter () = + { Id = Guid.NewGuid() + Value = Counter.init } - let outMsgBindings = - [ "Remove" |> Binding.cmd OutRemove - "MoveUp" |> Binding.cmdIf moveUpMsg - "MoveDown" |> Binding.cmdIf moveDownMsg - ] |> Bindings.mapMsg OutMsg + let createNewLeaf () = + createNewIdentifiableCounter () |> RoseTree.createLeaf - outMsgBindings @ inMsgBindings + let init () = + let dummyRootData = createNewIdentifiableCounter () // Placeholder data to satisfy type system. User never sees this. + { SomeGlobalState = false + DummyRoot = createNewLeaf () |> List.singleton |> RoseTree.create dummyRootData } - let rootBindings () : Binding list = [ - "Counters" - |> Binding.subModelSeq (subtreeBindings, (fun (_, { Self = c }) -> c.Data.Id)) - |> Binding.mapModel (fun m -> m.DummyRoot.Children |> Seq.map (fun c -> m, { Self = c; Parent = m.DummyRoot })) - |> Binding.mapMsg (fun (cId, inOutMsg) -> - match inOutMsg with - | InMsg msg -> (cId, msg) |> BranchMsg - | OutMsg msg -> cId |> mapOutMsg msg |> LeafMsg - |> SubtreeMsg) + let hasId id t = t.Data.Id = id + + let swapCounters swap nId = + nId + |> hasId + |> List.tryFindIndex + |> FuncOption.bind swap + |> FuncOption.inputIfNone + + let updateSubtree = + function + | CounterMsg msg -> msg |> Counter.update |> Identifiable.map |> RoseTree.mapData + | AddChild -> createNewLeaf () |> List.cons |> RoseTree.mapChildren + | Remove cId -> cId |> hasId >> not |> List.filter |> RoseTree.mapChildren + | MoveUp cId -> cId |> swapCounters List.swapWithPrev |> RoseTree.mapChildren + | MoveDown cId -> cId |> swapCounters List.swapWithNext |> RoseTree.mapChildren + + let update = + function + | ToggleGlobalState -> mapSomeGlobalState not + | SubtreeMsg msg -> msg |> RoseTree.update hasId updateSubtree |> mapDummyRoot + + let mapOutMsg = + function + | OutRemove -> Remove + | OutMoveUp -> MoveUp + | OutMoveDown -> MoveDown + + +module Bindings = + + open App + + type SelfWithParent<'a> = { Self: 'a; Parent: 'a } + + let moveUpMsg (_, { Parent = p; Self = s }) = + match p.Children |> List.tryHead with + | Some c when c.Data.Id <> s.Data.Id -> OutMoveUp |> Some + | _ -> None + + let moveDownMsg (_, { Parent = p; Self = s }) = + match p.Children |> List.tryLast with + | Some c when c.Data.Id <> s.Data.Id -> OutMoveDown |> Some + | _ -> None + + let rec subtreeBindings + () + : Binding< + Model * SelfWithParent>>, + InOutMsg, SubtreeOutMsg> + > list + = + let counterBindings = + Counter.bindings () + |> Bindings.mapModel (fun (_, { Self = s }) -> s.Data.Value) + |> Bindings.mapMsg (CounterMsg >> LeafMsg) + + let inMsgBindings = + [ "CounterIdText" |> Binding.oneWay (fun (_, { Self = s }) -> s.Data.Id) + "AddChild" |> Binding.cmd (AddChild |> LeafMsg) + "GlobalState" |> Binding.oneWay (fun (m, _) -> m.SomeGlobalState) + "ChildCounters" + |> Binding.subModelSeq (subtreeBindings, (fun (_, { Self = c }) -> c.Data.Id)) + |> Binding.mapModel (fun (m, { Self = p }) -> + p.Children |> Seq.map (fun c -> m, { Self = c; Parent = p })) + |> Binding.mapMsg (fun (cId, inOutMsg) -> + match inOutMsg with + | InMsg msg -> (cId, msg) |> BranchMsg + | OutMsg msg -> cId |> mapOutMsg msg |> LeafMsg) ] + @ counterBindings + |> Bindings.mapMsg InMsg + + let outMsgBindings = + [ "Remove" |> Binding.cmd OutRemove + "MoveUp" |> Binding.cmdIf moveUpMsg + "MoveDown" |> Binding.cmdIf moveDownMsg ] + |> Bindings.mapMsg OutMsg + + outMsgBindings @ inMsgBindings + + + let rootBindings () : Binding list = + [ "Counters" + |> Binding.subModelSeq (subtreeBindings, (fun (_, { Self = c }) -> c.Data.Id)) + |> Binding.mapModel (fun m -> + m.DummyRoot.Children |> Seq.map (fun c -> m, { Self = c; Parent = m.DummyRoot })) + |> Binding.mapMsg (fun (cId, inOutMsg) -> + match inOutMsg with + | InMsg msg -> (cId, msg) |> BranchMsg + | OutMsg msg -> cId |> mapOutMsg msg |> LeafMsg + |> SubtreeMsg) - "ToggleGlobalState" |> Binding.cmd ToggleGlobalState + "ToggleGlobalState" |> Binding.cmd ToggleGlobalState - "AddCounter" |> Binding.cmd (AddChild |> LeafMsg |> SubtreeMsg) - ] + "AddCounter" |> Binding.cmd (AddChild |> LeafMsg |> SubtreeMsg) ] let counterDesignVm = ViewModel.designInstance Counter.init (Counter.bindings ()) let mainDesignVm = ViewModel.designInstance (App.init ()) (Bindings.rootBindings ()) let main window = - let logger = - LoggerConfiguration() - .MinimumLevel.Override("Elmish.WPF.Update", Events.LogEventLevel.Verbose) - .MinimumLevel.Override("Elmish.WPF.Bindings", Events.LogEventLevel.Verbose) - .MinimumLevel.Override("Elmish.WPF.Performance", Events.LogEventLevel.Verbose) - .WriteTo.Console() - .CreateLogger() - - WpfProgram.mkSimple App.init App.update Bindings.rootBindings - |> WpfProgram.withLogger (new SerilogLoggerFactory(logger)) - |> WpfProgram.startElmishLoop window + let logger = + LoggerConfiguration() + .MinimumLevel.Override("Elmish.WPF.Update", Events.LogEventLevel.Verbose) + .MinimumLevel.Override("Elmish.WPF.Bindings", Events.LogEventLevel.Verbose) + .MinimumLevel.Override("Elmish.WPF.Performance", Events.LogEventLevel.Verbose) + .WriteTo.Console() + .CreateLogger() + + WpfProgram.mkSimple App.init App.update Bindings.rootBindings + |> WpfProgram.withLogger (new SerilogLoggerFactory(logger)) + |> WpfProgram.startElmishLoop window \ No newline at end of file diff --git a/src/Samples/SubModelStatic.Core/Program.fs b/src/Samples/SubModelStatic.Core/Program.fs index c983a4eb..8bbd1f46 100644 --- a/src/Samples/SubModelStatic.Core/Program.fs +++ b/src/Samples/SubModelStatic.Core/Program.fs @@ -1,4 +1,5 @@ namespace Elmish.WPF.Samples.SubModelStatic + #nowarn "44" open System @@ -9,187 +10,251 @@ open Elmish.WPF module Counter = - type Model = - { Count: int - StepSize: int - History: (int * int) list } + type Model = + { Count: int + StepSize: int + History: (int * int) list } + + type Msg = + | Increment + | Decrement + | SetStepSize of int + | Reset + + let init = + { Count = 0 + StepSize = 1 + History = [] } + + let canReset = (<>) init + + let update msg m = + match msg with + | Increment -> + { m with + Count = m.Count + m.StepSize + History = (m.Count, m.History.Length) :: m.History } + | Decrement -> + { m with + Count = m.Count - m.StepSize + History = (m.Count, m.History.Length) :: m.History } + | SetStepSize x -> { m with StepSize = x } + | Reset -> init + +[] +type CounterViewModel(args) = + inherit ViewModelBase(args) + + let stepSizeBinding = + Binding.TwoWayT.id + >> Binding.addLazy (=) + >> Binding.mapModel (fun (m: Counter.Model) -> m.StepSize) + >> Binding.mapMsg Counter.Msg.SetStepSize + + new() = + CounterViewModel( + { Counter.init with + History = [ (3, 1); (0, 0) ] } + |> ViewModelArgs.simple + ) + + member _.StepSize + with get () = base.Get () stepSizeBinding + and set (v) = base.Set (v) stepSizeBinding + + member _.CounterValue = + base.Get () (Binding.OneWayT.id >> Binding.addLazy (=) >> 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 _.Reset = base.Get () (Binding.CmdT.set Counter.canReset Counter.Reset) + + member _.History = + base.Get () (Binding.OneWaySeqT.id (=) snd >> Binding.mapModel (fun m -> m.History)) - type Msg = - | Increment - | Decrement - | SetStepSize of int - | Reset - let init = - { Count = 0 - StepSize = 1 - History = [] } +module Clock = - let canReset = (<>) init + type TimeType = + | Utc + | Local - let update msg m = - match msg with - | Increment -> { m with Count = m.Count + m.StepSize; History = (m.Count, m.History.Length) :: m.History } - | Decrement -> { m with Count = m.Count - m.StepSize; History = (m.Count, m.History.Length) :: m.History } - | SetStepSize x -> { m with StepSize = x } - | Reset -> init + type Model = + { Time: DateTimeOffset + TimeType: TimeType } -type [] CounterViewModel (args) = - inherit ViewModelBase(args) + let init () = + { Time = DateTimeOffset.Now + TimeType = Local } - let stepSizeBinding = - Binding.TwoWayT.id - >> Binding.addLazy (=) - >> Binding.mapModel (fun (m: Counter.Model) -> m.StepSize) - >> Binding.mapMsg Counter.Msg.SetStepSize + let getTime m = + match m.TimeType with + | Utc -> m.Time.UtcDateTime + | Local -> m.Time.LocalDateTime - new() = CounterViewModel({ Counter.init with History = [ (3,1); (0,0) ] } |> ViewModelArgs.simple) + type Msg = + | Tick of DateTimeOffset + | SetTimeType of TimeType - member _.StepSize - with get() = base.Get() stepSizeBinding - and set(v) = base.Set(v) stepSizeBinding - member _.CounterValue = base.Get() (Binding.OneWayT.id >> Binding.addLazy (=) >> 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 _.Reset = base.Get() (Binding.CmdT.set Counter.canReset Counter.Reset) - member _.History = base.Get() (Binding.OneWaySeqT.id (=) snd >> Binding.mapModel (fun m -> m.History)) + let update msg m = + match msg with + | Tick t -> { m with Time = t } + | SetTimeType t -> { m with TimeType = t } +[] +type ClockViewModel(args) = + inherit ViewModelBase(args) -module Clock = + new() = ClockViewModel(Clock.init () |> ViewModelArgs.simple) - type TimeType = - | Utc - | Local + member _.Time = + base.Get () (Binding.OneWayT.id >> Binding.addLazy (=) >> Binding.mapModel Clock.getTime) - type Model = - { Time: DateTimeOffset - TimeType: TimeType } + member _.IsLocal = + base.Get + () + (Binding.OneWayT.id + >> Binding.addLazy (=) + >> Binding.mapModel (fun m -> m.TimeType = Clock.Local)) - let init () = - { Time = DateTimeOffset.Now - TimeType = Local } + member _.SetLocal = + base.Get () (Binding.CmdT.setAlways (Clock.SetTimeType Clock.Local)) - let getTime m = - match m.TimeType with - | Utc -> m.Time.UtcDateTime - | Local -> m.Time.LocalDateTime + member _.IsUtc = + base.Get + () + (Binding.OneWayT.id + >> Binding.addLazy (=) + >> Binding.mapModel (fun m -> m.TimeType = Clock.Utc)) - type Msg = - | Tick of DateTimeOffset - | SetTimeType of TimeType + member _.SetUtc = base.Get () (Binding.CmdT.setAlways (Clock.SetTimeType Clock.Utc)) - let update msg m = - match msg with - | Tick t -> { m with Time = t } - | SetTimeType t -> { m with TimeType = t } -type [] ClockViewModel (args) = - inherit ViewModelBase(args) - - new() = ClockViewModel(Clock.init () |> ViewModelArgs.simple) +module CounterWithClock = - member _.Time = base.Get() (Binding.OneWayT.id >> Binding.addLazy (=) >> Binding.mapModel Clock.getTime) - member _.IsLocal = base.Get() (Binding.OneWayT.id >> Binding.addLazy (=) >> Binding.mapModel (fun m -> m.TimeType = Clock.Local)) - member _.SetLocal = base.Get() (Binding.CmdT.setAlways (Clock.SetTimeType Clock.Local)) - member _.IsUtc = base.Get() (Binding.OneWayT.id >> Binding.addLazy (=) >> Binding.mapModel (fun m -> m.TimeType = Clock.Utc)) - member _.SetUtc = base.Get() (Binding.CmdT.setAlways (Clock.SetTimeType Clock.Utc)) + type Model = + { Counter: Counter.Model + Clock: Clock.Model } + module ModelM = + module Counter = + let get m = m.Counter -module CounterWithClock = + module Clock = + let get m = m.Clock - type Model = - { Counter: Counter.Model - Clock: Clock.Model } + let init () = + { Counter = Counter.init + Clock = Clock.init () } - module ModelM = - module Counter = - let get m = m.Counter - module Clock = - let get m = m.Clock + type Msg = + | CounterMsg of Counter.Msg + | ClockMsg of Clock.Msg - let init () = - { Counter = Counter.init - Clock = Clock.init () } + let update msg m = + match msg with + | CounterMsg msg -> + { m with + Counter = Counter.update msg m.Counter } + | ClockMsg msg -> + { m with + Clock = Clock.update msg m.Clock } - type Msg = - | CounterMsg of Counter.Msg - | ClockMsg of Clock.Msg +[] +type CounterWithClockViewModel(args) = + inherit ViewModelBase(args) - let update msg m = - match msg with - | CounterMsg msg -> { m with Counter = Counter.update msg m.Counter } - | ClockMsg msg -> { m with Clock = Clock.update msg m.Clock } + new() = CounterWithClockViewModel(CounterWithClock.init () |> ViewModelArgs.simple) -type [] CounterWithClockViewModel (args) = - inherit ViewModelBase(args) - - new() = CounterWithClockViewModel(CounterWithClock.init () |> ViewModelArgs.simple) + member _.Counter = + base.Get + () + (Binding.SubModelT.req CounterViewModel + >> Binding.mapModel CounterWithClock.ModelM.Counter.get + >> Binding.mapMsg CounterWithClock.CounterMsg) - member _.Counter = base.Get() (Binding.SubModelT.req CounterViewModel >> Binding.mapModel CounterWithClock.ModelM.Counter.get >> Binding.mapMsg CounterWithClock.CounterMsg) - member _.Clock = base.Get() (Binding.SubModelT.req ClockViewModel >> Binding.mapModel CounterWithClock.ModelM.Clock.get >> Binding.mapMsg CounterWithClock.ClockMsg) + member _.Clock = + base.Get + () + (Binding.SubModelT.req ClockViewModel + >> Binding.mapModel CounterWithClock.ModelM.Clock.get + >> Binding.mapMsg CounterWithClock.ClockMsg) module App2 = - type Model = - { ClockCounter1: CounterWithClock.Model - ClockCounter2: CounterWithClock.Model } + type Model = + { ClockCounter1: CounterWithClock.Model + ClockCounter2: CounterWithClock.Model } + + module ModelM = + module ClockCounter1 = + let get m = m.ClockCounter1 + + module ClockCounter2 = + let get m = m.ClockCounter2 + + let init () = + { ClockCounter1 = CounterWithClock.init () + ClockCounter2 = CounterWithClock.init () } + + type Msg = + | ClockCounter1Msg of CounterWithClock.Msg + | ClockCounter2Msg of CounterWithClock.Msg + + let update msg m = + match msg with + | ClockCounter1Msg msg -> + { m with + ClockCounter1 = CounterWithClock.update msg m.ClockCounter1 } + | ClockCounter2Msg msg -> + { m with + ClockCounter2 = CounterWithClock.update msg m.ClockCounter2 } + +[] +type AppViewModel(args) = + inherit ViewModelBase(args) + + new() = AppViewModel(App2.init () |> ViewModelArgs.simple) + + member _.ClockCounter1 = + base.Get + () + (Binding.SubModelT.req CounterWithClockViewModel + >> Binding.mapModel App2.ModelM.ClockCounter1.get + >> Binding.mapMsg App2.ClockCounter1Msg) + + member _.ClockCounter2 = + base.Get + () + (Binding.SubModelT.req CounterWithClockViewModel + >> Binding.mapModel App2.ModelM.ClockCounter2.get + >> Binding.mapMsg App2.ClockCounter2Msg) - module ModelM = - module ClockCounter1 = - let get m = m.ClockCounter1 - module ClockCounter2 = - let get m = m.ClockCounter2 +module Program = - let init () = - { ClockCounter1 = CounterWithClock.init () - ClockCounter2 = CounterWithClock.init () } + let timerTick dispatch = + let timer = new System.Timers.Timer(1000.) - type Msg = - | ClockCounter1Msg of CounterWithClock.Msg - | ClockCounter2Msg of CounterWithClock.Msg + timer.Elapsed.Add(fun _ -> + let clockMsg = DateTimeOffset.Now |> Clock.Tick |> CounterWithClock.ClockMsg + dispatch <| App2.ClockCounter1Msg clockMsg + dispatch <| App2.ClockCounter2Msg clockMsg) - let update msg m = - match msg with - | ClockCounter1Msg msg -> - { m with ClockCounter1 = CounterWithClock.update msg m.ClockCounter1 } - | ClockCounter2Msg msg -> - { m with ClockCounter2 = CounterWithClock.update msg m.ClockCounter2 } + timer.Start() -type [] AppViewModel (args) = - inherit ViewModelBase(args) - - new() = AppViewModel(App2.init () |> ViewModelArgs.simple) - member _.ClockCounter1 = base.Get() (Binding.SubModelT.req CounterWithClockViewModel >> Binding.mapModel App2.ModelM.ClockCounter1.get >> Binding.mapMsg App2.ClockCounter1Msg) - member _.ClockCounter2 = base.Get() (Binding.SubModelT.req CounterWithClockViewModel >> Binding.mapModel App2.ModelM.ClockCounter2.get >> Binding.mapMsg App2.ClockCounter2Msg) + let main window = -module Program = + let logger = + LoggerConfiguration() + .MinimumLevel.Override("Elmish.WPF.Update", Events.LogEventLevel.Verbose) + .MinimumLevel.Override("Elmish.WPF.Bindings", Events.LogEventLevel.Verbose) + .MinimumLevel.Override("Elmish.WPF.Performance", Events.LogEventLevel.Verbose) + .WriteTo.Console() + .CreateLogger() - let timerTick dispatch = - let timer = new System.Timers.Timer(1000.) - timer.Elapsed.Add (fun _ -> - let clockMsg = - DateTimeOffset.Now - |> Clock.Tick - |> CounterWithClock.ClockMsg - dispatch <| App2.ClockCounter1Msg clockMsg - dispatch <| App2.ClockCounter2Msg clockMsg - ) - timer.Start() - - - let main window = - - let logger = - LoggerConfiguration() - .MinimumLevel.Override("Elmish.WPF.Update", Events.LogEventLevel.Verbose) - .MinimumLevel.Override("Elmish.WPF.Bindings", Events.LogEventLevel.Verbose) - .MinimumLevel.Override("Elmish.WPF.Performance", Events.LogEventLevel.Verbose) - .WriteTo.Console() - .CreateLogger() - - WpfProgram.mkSimpleT App2.init App2.update AppViewModel - |> WpfProgram.withSubscription (Sub.fromV3Subscription "sub" (fun _ -> Cmd.ofEffect timerTick)) - |> WpfProgram.withLogger (new SerilogLoggerFactory(logger)) - |> WpfProgram.startElmishLoop window + WpfProgram.mkSimpleT App2.init App2.update AppViewModel + |> WpfProgram.withSubscription (Sub.fromV3Subscription "sub" (fun _ -> Cmd.ofEffect timerTick)) + |> WpfProgram.withLogger (new SerilogLoggerFactory(logger)) + |> WpfProgram.startElmishLoop window \ No newline at end of file diff --git a/src/Samples/Threading.Core/Program.fs b/src/Samples/Threading.Core/Program.fs index 1e34fa94..92461c21 100644 --- a/src/Samples/Threading.Core/Program.fs +++ b/src/Samples/Threading.Core/Program.fs @@ -10,68 +10,66 @@ open Elmish.WPF -type Model = - { Pings: int - Message: string } +type Model = { Pings: int; Message: string } type Msg = - | IncrementPings - | UpdateMessage of string + | IncrementPings + | UpdateMessage of string -type Cmd = - | DelayThenIncrementPings +type Cmd = | DelayThenIncrementPings module Program = - module Pings = - let get m = m.Pings - let set v m = { m with Pings = v } - let map f m = m |> get |> f |> set <| m + module Pings = + let get m = m.Pings + let set v m = { m with Pings = v } + let map f m = m |> get |> f |> set <| m - module Message = - let get m = m.Message - let set v m = { m with Message = v } + module Message = + let get m = m.Message + let set v m = { m with Message = v } - let init = - { Pings = 0; Message = "" }, [ DelayThenIncrementPings ] + let init = { Pings = 0; Message = "" }, [ DelayThenIncrementPings ] - let update msg m = - match msg with - | IncrementPings -> m |> Pings.map ((+) 1), [ DelayThenIncrementPings ] - | UpdateMessage message -> m |> Message.set message, [ ] + let update msg m = + match msg with + | IncrementPings -> m |> Pings.map ((+) 1), [ DelayThenIncrementPings ] + | UpdateMessage message -> m |> Message.set message, [] - let bindings () = [ - "Pings" |> Binding.oneWay Pings.get - "Message" |> Binding.twoWay (Message.get, UpdateMessage) - ] + let bindings () = + [ "Pings" |> Binding.oneWay Pings.get + "Message" |> Binding.twoWay (Message.get, UpdateMessage) ] - let toCmd = - function - | DelayThenIncrementPings -> - Elmish.Cmd.OfAsyncImmediate.perform (fun () -> Async.Sleep 1000) () (fun () -> IncrementPings) + let toCmd = + function + | DelayThenIncrementPings -> + Elmish.Cmd.OfAsyncImmediate.perform (fun () -> Async.Sleep 1000) () (fun () -> IncrementPings) -let designVm = ViewModel.designInstance { Pings = 2; Message = "Hello" } (Program.bindings ()) +let designVm = + ViewModel.designInstance { Pings = 2; Message = "Hello" } (Program.bindings ()) let main window = - let logger = - LoggerConfiguration() - .MinimumLevel.Override("Elmish.WPF.Update", Events.LogEventLevel.Verbose) - .MinimumLevel.Override("Elmish.WPF.Bindings", Events.LogEventLevel.Verbose) - .MinimumLevel.Override("Elmish.WPF.Performance", Events.LogEventLevel.Verbose) - .WriteTo.Console() - .CreateLogger() - - let program = - WpfProgram.mkProgramWithCmdMsg (fun () -> Program.init) Program.update Program.bindings Program.toCmd - |> WpfProgram.withLogger (new SerilogLoggerFactory(logger)) - - let elmishThread = - Thread( - ThreadStart(fun () -> - WpfProgram.startElmishLoop window program - Dispatcher.Run())) - elmishThread.Name <- "ElmishDispatchThread" - elmishThread.Start() - - elmishThread + let logger = + LoggerConfiguration() + .MinimumLevel.Override("Elmish.WPF.Update", Events.LogEventLevel.Verbose) + .MinimumLevel.Override("Elmish.WPF.Bindings", Events.LogEventLevel.Verbose) + .MinimumLevel.Override("Elmish.WPF.Performance", Events.LogEventLevel.Verbose) + .WriteTo.Console() + .CreateLogger() + + let program = + WpfProgram.mkProgramWithCmdMsg (fun () -> Program.init) Program.update Program.bindings Program.toCmd + |> WpfProgram.withLogger (new SerilogLoggerFactory(logger)) + + let elmishThread = + Thread( + ThreadStart(fun () -> + WpfProgram.startElmishLoop window program + Dispatcher.Run()) + ) + + elmishThread.Name <- "ElmishDispatchThread" + elmishThread.Start() + + elmishThread \ No newline at end of file diff --git a/src/Samples/UiBoundCmdParam.Core/Program.fs b/src/Samples/UiBoundCmdParam.Core/Program.fs index ce87dc6b..13878e38 100644 --- a/src/Samples/UiBoundCmdParam.Core/Program.fs +++ b/src/Samples/UiBoundCmdParam.Core/Program.fs @@ -6,42 +6,43 @@ open Elmish.WPF type Model = - { Numbers: int list - EnabledMaxLimit: int } + { Numbers: int list + EnabledMaxLimit: int } let init () = - { Numbers = [0 .. 10] - EnabledMaxLimit = 5 } + { Numbers = [ 0..10 ] + EnabledMaxLimit = 5 } type Msg = - | SetLimit of int - | Command + | SetLimit of int + | Command let update msg m = - match msg with - | SetLimit x -> { m with EnabledMaxLimit = x } - | Command -> m - -let bindings () : Binding list = [ - "Numbers" |> Binding.oneWay(fun m -> m.Numbers) - "Limit" |> Binding.twoWay((fun m -> float m.EnabledMaxLimit), int >> SetLimit) - "Command" |> Binding.cmdParamIf( - (fun p m -> Command), - (fun (p: obj) m -> not (isNull p) && p :?> int <= m.EnabledMaxLimit), - true) -] + match msg with + | SetLimit x -> { m with EnabledMaxLimit = x } + | Command -> m + +let bindings () : Binding list = + [ "Numbers" |> Binding.oneWay (fun m -> m.Numbers) + "Limit" |> Binding.twoWay ((fun m -> float m.EnabledMaxLimit), int >> SetLimit) + "Command" + |> Binding.cmdParamIf ( + (fun p m -> Command), + (fun (p: obj) m -> not (isNull p) && p :?> int <= m.EnabledMaxLimit), + true + ) ] let designVm = ViewModel.designInstance (init ()) (bindings ()) let main window = - let logger = - LoggerConfiguration() - .MinimumLevel.Override("Elmish.WPF.Update", Events.LogEventLevel.Verbose) - .MinimumLevel.Override("Elmish.WPF.Bindings", Events.LogEventLevel.Verbose) - .MinimumLevel.Override("Elmish.WPF.Performance", Events.LogEventLevel.Verbose) - .WriteTo.Console() - .CreateLogger() - - WpfProgram.mkSimple init update bindings - |> WpfProgram.withLogger (new SerilogLoggerFactory(logger)) - |> WpfProgram.startElmishLoop window + let logger = + LoggerConfiguration() + .MinimumLevel.Override("Elmish.WPF.Update", Events.LogEventLevel.Verbose) + .MinimumLevel.Override("Elmish.WPF.Bindings", Events.LogEventLevel.Verbose) + .MinimumLevel.Override("Elmish.WPF.Performance", Events.LogEventLevel.Verbose) + .WriteTo.Console() + .CreateLogger() + + WpfProgram.mkSimple init update bindings + |> WpfProgram.withLogger (new SerilogLoggerFactory(logger)) + |> WpfProgram.startElmishLoop window \ No newline at end of file diff --git a/src/Samples/Validation.Core/Program.fs b/src/Samples/Validation.Core/Program.fs index 8d3da7cd..a333fc8f 100644 --- a/src/Samples/Validation.Core/Program.fs +++ b/src/Samples/Validation.Core/Program.fs @@ -9,98 +9,105 @@ open Elmish.WPF module Result = - module Error = + module Error = - let toList = function - | Ok _ -> [] - | Error e -> [ e ] + let toList = + function + | Ok _ -> [] + | Error e -> [ e ] let requireNotEmpty s = - if String.IsNullOrEmpty s then Error "This field is required" else Ok s + if String.IsNullOrEmpty s then + Error "This field is required" + else + Ok s let parseInt (s: string) = - match Int32.TryParse s with - | true, i -> Ok i - | false, _ -> Error "Please enter a valid integer" + match Int32.TryParse s with + | true, i -> Ok i + | false, _ -> Error "Please enter a valid integer" let requireExactly y x = - if x = y then Ok x else Error <| sprintf "Please enter %A" y + if x = y then Ok x else Error <| sprintf "Please enter %A" y let validateInt42 = - requireNotEmpty - >> Result.bind parseInt - >> Result.bind (requireExactly 42) + requireNotEmpty >> Result.bind parseInt >> Result.bind (requireExactly 42) let validatePassword (s: string) = - [ - if s.All(fun c -> Char.IsDigit c |> not) then - "Must contain a digit" - if s.All(fun c -> Char.IsLower c |> not) then - "Must contain a lowercase letter" - if s.All(fun c -> Char.IsUpper c |> not) then - "Must contain an uppercase letter" - ] + [ if s.All(fun c -> Char.IsDigit c |> not) then + "Must contain a digit" + if s.All(fun c -> Char.IsLower c |> not) then + "Must contain a lowercase letter" + if s.All(fun c -> Char.IsUpper c |> not) then + "Must contain an uppercase letter" ] type Model = - { UpdateCount: int - Value: string - Password: string } + { UpdateCount: int + Value: string + Password: string } let init () = - { UpdateCount = 0 - Value = "" - Password = "" } + { UpdateCount = 0 + Value = "" + Password = "" } type Msg = - | NewValue of string - | NewPassword of string - | Submit + | NewValue of string + | NewPassword of string + | Submit let increaseUpdateCount m = - { m with UpdateCount = m.UpdateCount + 1 } + { m with + UpdateCount = m.UpdateCount + 1 } let update msg m = - let m = increaseUpdateCount m - match msg with - | NewValue x -> { m with Value = x } - | NewPassword x -> { m with Password = x } - | Submit -> m + let m = increaseUpdateCount m + + match msg with + | NewValue x -> { m with Value = x } + | NewPassword x -> { m with Password = x } + | Submit -> m let errorOnEven m = - if m.UpdateCount % 2 = 0 then - [ "Even counts have this error" ] - else - [] - -let bindings () : Binding list = [ - "UpdateCount" - |> Binding.oneWay(fun m -> m.UpdateCount) - |> Binding.addValidation errorOnEven - "Value" - |> Binding.twoWay((fun m -> m.Value), NewValue) - |> Binding.addValidation(fun m -> m.Value |> validateInt42 |> Result.Error.toList) - "Password" - |> Binding.twoWay((fun m -> m.Password), NewPassword) - |> Binding.addValidation(fun m -> m.Password |> validatePassword) - "Submit" |> Binding.cmdIf( - (fun _ -> Submit), - (fun m -> (match validateInt42 m.Value with Ok _ -> true | Error _ -> false) && (validatePassword m.Password |> List.isEmpty))) -] + if m.UpdateCount % 2 = 0 then + [ "Even counts have this error" ] + else + [] + +let bindings () : Binding list = + [ "UpdateCount" + |> Binding.oneWay (fun m -> m.UpdateCount) + |> Binding.addValidation errorOnEven + "Value" + |> Binding.twoWay ((fun m -> m.Value), NewValue) + |> Binding.addValidation (fun m -> m.Value |> validateInt42 |> Result.Error.toList) + "Password" + |> Binding.twoWay ((fun m -> m.Password), NewPassword) + |> Binding.addValidation (fun m -> m.Password |> validatePassword) + "Submit" + |> Binding.cmdIf ( + (fun _ -> Submit), + (fun m -> + (match validateInt42 m.Value with + | Ok _ -> true + | Error _ -> false) + && (validatePassword m.Password |> List.isEmpty)) + ) ] let designVm = ViewModel.designInstance (init ()) (bindings ()) let main window = - let logger = - LoggerConfiguration() - .MinimumLevel.Override("Elmish.WPF.Update", Events.LogEventLevel.Verbose) - .MinimumLevel.Override("Elmish.WPF.Bindings", Events.LogEventLevel.Verbose) - .MinimumLevel.Override("Elmish.WPF.Performance", Events.LogEventLevel.Verbose) - .WriteTo.Console() - .CreateLogger() - - WpfProgram.mkSimple init update bindings - |> WpfProgram.withLogger (new SerilogLoggerFactory(logger)) - |> WpfProgram.startElmishLoop window + let logger = + LoggerConfiguration() + .MinimumLevel.Override("Elmish.WPF.Update", Events.LogEventLevel.Verbose) + .MinimumLevel.Override("Elmish.WPF.Bindings", Events.LogEventLevel.Verbose) + .MinimumLevel.Override("Elmish.WPF.Performance", Events.LogEventLevel.Verbose) + .WriteTo.Console() + .CreateLogger() + + WpfProgram.mkSimple init update bindings + |> WpfProgram.withLogger (new SerilogLoggerFactory(logger)) + |> WpfProgram.startElmishLoop window \ No newline at end of file From 78bc7b16df257566489a35d72c7099a50d3e4730 Mon Sep 17 00:00:00 2001 From: Tim Rudat Date: Sun, 20 Jul 2025 17:15:51 +0200 Subject: [PATCH 05/18] Improve XML documentation in code Improve code documentation comments of Elmish.WPF project. --- src/Elmish.WPF/Binding.fs | 185 ++++++++++++++++++++++++++++- src/Elmish.WPF/BindingData.fs | 115 ++++++++++++++++++ src/Elmish.WPF/BindingVmHelpers.fs | 103 ++++++++++++++++ src/Elmish.WPF/Command.fs | 10 ++ src/Elmish.WPF/InternalUtils.fs | 90 ++++++++++++++ src/Elmish.WPF/Merge.fs | 51 ++++++++ src/Elmish.WPF/Utils.fs | 15 +++ src/Elmish.WPF/ViewModelArgs.fs | 24 ++++ src/Elmish.WPF/ViewModelModule.fs | 6 + src/Elmish.WPF/ViewModels.fs | 37 ++++++ src/Elmish.WPF/WindowState.fs | 58 ++++++++- src/Elmish.WPF/WpfProgram.fs | 106 +++++++++++++++-- 12 files changed, 783 insertions(+), 17 deletions(-) diff --git a/src/Elmish.WPF/Binding.fs b/src/Elmish.WPF/Binding.fs index 1a71758e..3077b11e 100644 --- a/src/Elmish.WPF/Binding.fs +++ b/src/Elmish.WPF/Binding.fs @@ -14,34 +14,72 @@ module Binding = { Name = binding.Name Data = binding.Data |> f } + /// /// Boxes the output parameter. /// Allows using a strongly-typed submodel binding (from a module ending in "T") /// in a binding list (rather than in a view model class member as normal). + /// + /// The strongly-typed binding to box. + /// A boxed binding suitable for use in binding lists. let boxT (binding: Binding<'b, 'msg, 't>) = BindingData.boxT |> mapData <| binding - /// Unboxes the output parameter + /// + /// Unboxes the output parameter. + /// + /// The boxed binding to unbox. + /// A strongly-typed binding with the output parameter unboxed. let unboxT (binding: Binding<'b, 'msg>) : Binding<'b, 'msg, 't> = BindingData.unboxT |> mapData <| binding + /// /// Maps the model of a binding via a contravariant mapping. + /// + /// The mapping function from 'a to 'b. + /// The binding whose model is to be mapped. + /// A binding with the model mapped. let mapModel (f: 'a -> 'b) (binding: Binding<'b, 'msg, 't>) = f |> mapModel |> mapData <| binding + /// /// Maps the message of a binding with access to the model via a covariant mapping. + /// + /// The mapping function that takes a message and the model to produce a new message. + /// The binding whose message is to be mapped. + /// A binding with the message mapped. let mapMsgWithModel (f: 'a -> 'model -> 'b) (binding: Binding<'model, 'a, 't>) = f |> mapMsgWithModel |> mapData <| binding + /// /// Maps the message of a binding via a covariant mapping. + /// + /// The mapping function from message 'a to message 'b. + /// The binding whose message is to be mapped. + /// A binding with the message mapped. let mapMsg (f: 'a -> 'b) (binding: Binding<'model, 'a, 't>) = f |> mapMsg |> mapData <| binding + /// /// Sets the message of a binding with access to the model. + /// + /// The function that takes the model and produces the message. + /// The binding whose message is to be set. + /// A binding with the message set based on the model. let setMsgWithModel (f: 'model -> 'b) (binding: Binding<'model, 'a, 't>) = f |> setMsgWithModel |> mapData <| binding + /// /// Sets the message of a binding. + /// + /// The message to set. + /// The binding whose message is to be set. + /// A binding with the specified message. let setMsg (msg: 'b) (binding: Binding<'model, 'a, 't>) = msg |> setMsg |> mapData <| binding + /// /// Restricts the binding to models that satisfy the predicate after some model satisfies the predicate. + /// + /// The predicate that determines which models are valid. + /// The binding to which the sticky behavior is added. + /// A binding that remains active only for models satisfying the predicate once triggered. let addSticky (predicate: 'model -> bool) (binding: Binding<'model, 'msg, 't>) = predicate |> addSticky |> mapData <| binding @@ -106,29 +144,49 @@ module Binding = /// module OneWayT = + /// /// Elemental instance of a one-way binding. + /// + /// The name of the binding. + /// A one-way binding. let id<'a, 'msg> : string -> Binding<'a, 'msg, 'a> = OneWay.id |> createBindingT + /// /// Creates a one-way binding to an optional value. The binding /// automatically converts between a missing value in the model and /// a null value in the view. + /// + /// The name of the binding. + /// A one-way binding for optional values. let opt x : Binding<'a option, 'msg, System.Nullable<'a>> = x |> id |> mapModel Option.toNullable + /// /// Creates a one-way binding to an optional value. The binding /// automatically converts between a missing value in the model and /// a null value in the view. + /// + /// The name of the binding. + /// A one-way binding for value optional values. let vopt x : Binding<'a voption, 'msg, System.Nullable<'a>> = x |> id |> mapModel ValueOption.toNullable + /// /// Creates a one-way binding to an optional value. The binding /// automatically converts between a missing value in the model and /// a null value in the view. + /// + /// The name of the binding. + /// A one-way binding for optional reference types. let optobj<'a, 'msg when 'a: null> : string -> Binding<'a option, 'msg, 'a> = id<'a, 'msg> >> mapModel Option.toObj + /// /// Creates a one-way binding to an optional value. The binding /// automatically converts between a missing value in the model and /// a null value in the view. + /// + /// The name of the binding. + /// A one-way binding for value optional reference types. let voptobj<'a, 'msg when 'a: null> : string -> Binding<'a voption, 'msg, 'a> = id<'a, 'msg> >> mapModel ValueOption.toObj @@ -137,30 +195,50 @@ module Binding = /// module OneWayToSourceT = + /// /// Elemental instance of a one-way-to-source binding. + /// + /// The name of the binding. + /// A one-way-to-source binding. let id<'model, 'a> : string -> Binding<'model, 'a, 'a> = OneWayToSource.id |> createBindingT + /// /// Creates a one-way-to-source binding to an optional value. The binding /// automatically converts between a missing value in the model and /// a null value in the view. + /// + /// The name of the binding. + /// A one-way-to-source binding for optional reference types. let optobj<'a, 'model when 'a: null> : string -> Binding<'model, 'a option, 'a> = id<'model, 'a> >> mapMsg Option.ofObj + /// /// Creates a one-way-to-source binding to an optional value. The binding /// automatically converts between a missing value in the model and /// a null value in the view. + /// + /// The name of the binding. + /// A one-way-to-source binding for value optional reference types. let voptobj<'a, 'model when 'a: null> : string -> Binding<'model, 'a voption, 'a> = id<'model, 'a> >> mapMsg ValueOption.ofObj + /// /// Creates a one-way-to-source binding to an optional value. The binding /// automatically converts between a missing value in the model and /// a null value in the view. + /// + /// The name of the binding. + /// A one-way-to-source binding for optional nullable values. let opt x : Binding<'model, 'a option, System.Nullable<'a>> = x |> id |> mapMsg Option.ofNullable + /// /// Creates a one-way-to-source binding to an optional value. The binding /// automatically converts between a missing value in the model and /// a null value in the view. + /// + /// The name of the binding. + /// A one-way-to-source binding for value optional nullable values. let vopt x : Binding<'model, 'a voption, System.Nullable<'a>> = x |> id |> mapMsg ValueOption.ofNullable @@ -169,30 +247,50 @@ module Binding = /// module TwoWayT = + /// /// Elemental instance of a two-way binding. + /// + /// The name of the binding. + /// A two-way binding. let id<'a> : string -> Binding<'a, 'a, 'a> = TwoWay.id |> createBindingT + /// /// Creates a two-way binding to an optional value. The binding /// automatically converts between a missing value in the model and /// a null value in the view. + /// + /// The name of the binding. + /// A two-way binding for optional nullable values. let opt x : Binding<'a option, 'a option, System.Nullable<'a>> = x |> id |> mapMsg Option.ofNullable |> mapModel Option.toNullable + /// /// Creates a two-way binding to an optional value. The binding /// automatically converts between a missing value in the model and /// a null value in the view. + /// + /// The name of the binding. + /// A two-way binding for value optional nullable values. let vopt x : Binding<'a voption, 'a voption, System.Nullable<'a>> = x |> id |> mapMsg ValueOption.ofNullable |> mapModel ValueOption.toNullable + /// /// Creates a two-way binding to an optional value. The binding /// automatically converts between a missing value in the model and /// a null value in the view. + /// + /// The name of the binding. + /// A two-way binding for optional reference types. let optobj<'a when 'a: null> : string -> Binding<'a option, 'a option, 'a> = id<'a> >> mapModel Option.toObj >> mapMsg Option.ofObj + /// /// Creates a two-way binding to an optional value. The binding /// automatically converts between a missing value in the model and /// a null value in the view. + /// + /// The name of the binding. + /// A two-way binding for value optional reference types. let voptobj<'a when 'a: null> : string -> Binding<'a voption, 'a voption, 'a> = id<'a> >> mapMsg ValueOption.ofObj >> mapModel ValueOption.toObj @@ -268,67 +366,105 @@ module Binding = module OneWay = + /// /// Elemental instance of a one-way binding. + /// + /// The name of the binding. + /// A one-way binding. let id<'a, 'msg> : string -> Binding<'a, 'msg> = OneWay.id |> createBinding + /// /// Creates a one-way binding to an optional value. The binding /// automatically converts between a missing value in the model and /// a null value in the view. + /// + /// The name of the binding. + /// A one-way binding for optional values. let opt<'a, 'msg> : string -> Binding<'a option, 'msg> = id >> mapModel Option.box + /// /// Creates a one-way binding to an optional value. The binding /// automatically converts between a missing value in the model and /// a null value in the view. + /// + /// The name of the binding. + /// A one-way binding for value optional values. let vopt<'a, 'msg> : string -> Binding<'a voption, 'msg> = id >> mapModel ValueOption.box module OneWayToSource = + /// /// Elemental instance of a one-way-to-source binding. + /// + /// The name of the binding. + /// A one-way-to-source binding. let id<'model, 'a> : string -> Binding<'model, 'a> = OneWayToSource.id |> createBinding + /// /// Creates a one-way-to-source binding to an optional value. The binding /// automatically converts between a missing value in the model and /// a null value in the view. + /// + /// The name of the binding. + /// A one-way-to-source binding for value optional values. let vopt<'model, 'a> : string -> Binding<'model, 'a voption> = id<'model, obj> >> mapMsg ValueOption.unbox + /// /// Creates a one-way-to-source binding to an optional value. The binding /// automatically converts between a missing value in the model and /// a null value in the view. + /// + /// The name of the binding. + /// A one-way-to-source binding for optional values. let opt<'model, 'a> : string -> Binding<'model, 'a option> = id<'model, obj> >> mapMsg Option.unbox module TwoWay = + /// /// Elemental instance of a two-way binding. + /// + /// The name of the binding. + /// A two-way binding. let id<'a> : string -> Binding<'a, 'a> = TwoWay.id |> createBinding + /// /// Creates a two-way binding to an optional value. The binding /// automatically converts between a missing value in the model and /// a null value in the view. + /// + /// The name of the binding. + /// A two-way binding for value optional values. let vopt<'a> : string -> Binding<'a voption, 'a voption> = id >> mapModel ValueOption.box >> mapMsg ValueOption.unbox + /// /// Creates a two-way binding to an optional value. The binding /// automatically converts between a missing value in the model and /// a null value in the view. + /// + /// The name of the binding. + /// A two-way binding for optional values. let opt<'a> : string -> Binding<'a option, 'a option> = id >> mapModel Option.box >> mapMsg Option.unbox module SubModelSelectedItem = + /// /// Creates a two-way binding to a SelectedItem-like property where /// the ItemsSource-like property is a /// binding. Automatically converts the dynamically created Elmish.WPF view /// models to/from their corresponding IDs, so the Elmish user code only has /// to work with the IDs. - /// + /// + /// /// Only use this if you are unable to use some kind of SelectedValue /// or SelectedIndex property with a normal /// binding. This binding is less type-safe. It will throw when initializing @@ -336,18 +472,24 @@ module Binding = /// does not correspond to a binding, and it will /// throw at runtime if the inferred 'id type does not match the /// actual ID type used in that binding. + /// + /// The name of the subModelSeq binding. + /// The name of the binding. + /// A two-way binding for the selected item ID. let vopt subModelSeqBindingName : string -> Binding<'id voption, 'id voption> = SubModelSelectedItem.create subModelSeqBindingName |> createBinding >> mapModel (ValueOption.map box) >> mapMsg (ValueOption.map unbox) + /// /// Creates a two-way binding to a SelectedItem-like property where /// the ItemsSource-like property is a /// binding. Automatically converts the dynamically created Elmish.WPF view /// models to/from their corresponding IDs, so the Elmish user code only has /// to work with the IDs. - /// + /// + /// /// Only use this if you are unable to use some kind of SelectedValue /// or SelectedIndex property with a normal /// binding. This binding is less type-safe. It will throw when initializing @@ -355,6 +497,10 @@ module Binding = /// does not correspond to a binding, and it will /// throw at runtime if the inferred 'id type does not match the /// actual ID type used in that binding. + /// + /// The name of the subModelSeq binding. + /// The name of the binding. + /// A two-way binding for the selected item ID. let opt subModelSeqBindingName : string -> Binding<'id option, 'id option> = vopt subModelSeqBindingName >> mapModel ValueOption.ofOption @@ -412,13 +558,21 @@ module Binding = /// module SubModelT = + /// /// Exposes an optional view model member for binding. + /// + /// Function to create the view model. + /// A binding function for optional view models. let opt (createVm: ViewModelArgs<'bindingModel, 'msg> -> #IViewModel<'bindingModel, 'msg>) : (string -> Binding<'bindingModel voption, 'msg, #IViewModel<'bindingModel, 'msg>>) = SubModel.create createVm IViewModel.updateModel |> createBindingT + /// /// Exposes a non-optional view model member for binding. + /// + /// Function to create the view model. + /// A binding function for required view models. let req (createVm: ViewModelArgs<'bindingModel, 'msg> -> #IViewModel<'bindingModel, 'msg>) : (string -> Binding<'bindingModel, 'msg, #IViewModel<'bindingModel, 'msg>>) = @@ -455,6 +609,8 @@ module Binding = /// The function applied to every element of the bound ObservableCollection /// to create a child view model. /// + /// The name of the binding. + /// A binding for a collection of view models identified by index. let id (createVm: ViewModelArgs<'bindingModel, 'msg> -> #IViewModel<'bindingModel, 'msg>) : (string @@ -547,13 +703,21 @@ module Binding = module SelectedIndex = + /// /// Prebuilt binding intended for use with Selector.SelectedIndex. + /// + /// The name of the binding. + /// A two-way binding for nullable selected index. let vopt = TwoWay.id >> mapModel (ValueOption.defaultValue -1) >> mapMsg (fun i -> if i < 0 then ValueNone else ValueSome i) + /// /// Prebuilt binding intended for use with Selector.SelectedIndex. + /// + /// The name of the binding. + /// A two-way binding for optional selected index. let opt = vopt >> mapModel ValueOption.ofOption >> mapMsg ValueOption.toOption @@ -579,15 +743,30 @@ module Binding = module Bindings = + /// /// Maps the model of a list of bindings via a contravariant mapping. + /// + /// The mapping function from 'a to 'b. + /// The list of bindings to map. + /// A list of bindings with the model mapped. let mapModel (f: 'a -> 'b) (bindings: Binding<'b, 'msg> list) = f |> Binding.mapModel |> List.map <| bindings + /// /// Maps the message of a list of bindings with access to the model via a covariant mapping. + /// + /// The mapping function that takes a message and the model to produce a new message. + /// The list of bindings to map. + /// A list of bindings with the message mapped. let mapMsgWithModel (f: 'a -> 'model -> 'b) (bindings: Binding<'model, 'a> list) = f |> Binding.mapMsgWithModel |> List.map <| bindings + /// /// Maps the message of a list of bindings via a covariant mapping. + /// + /// The mapping function from message 'a to message 'b. + /// The list of bindings to map. + /// A list of bindings with the message mapped. let mapMsg (f: 'a -> 'b) (bindings: Binding<'model, 'a> list) = f |> Binding.mapMsg |> List.map <| bindings diff --git a/src/Elmish.WPF/BindingData.fs b/src/Elmish.WPF/BindingData.fs index d06e2245..a128e2e1 100644 --- a/src/Elmish.WPF/BindingData.fs +++ b/src/Elmish.WPF/BindingData.fs @@ -10,6 +10,14 @@ open Elmish module Helper = + /// + /// Maps dispatch function to handle binding messages by getting the current model, + /// applying a message transformation, and dispatching the result. + /// + /// Function to get the current model state. + /// Function to transform binding message and model into a message. + /// The dispatch function to send messages. + /// A function that takes a binding message and dispatches the transformed message. let mapDispatch (getCurrentModel: unit -> 'model) (set: 'bindingMsg -> 'model -> 'msg) @@ -18,12 +26,21 @@ module Helper = fun bMsg -> getCurrentModel () |> set bMsg |> dispatch +/// +/// Represents a one-way binding that flows data from the model to the view. +/// type OneWayData<'model, 'a> = { Get: 'model -> 'a } +/// +/// Represents a one-way binding that flows data from the view to the model. +/// type OneWayToSourceData<'model, 'msg, 'a> = { Set: 'a -> 'model -> 'msg } +/// +/// Represents a one-way binding for sequences/collections with efficient updating. +/// type OneWaySeqData<'model, 'a, 'aCollection, 'id when 'id: equality> = { Get: 'model -> 'a seq CreateCollection: 'a seq -> CollectionTarget<'a, 'aCollection> @@ -41,23 +58,35 @@ type OneWaySeqData<'model, 'a, 'aCollection, 'id when 'id: equality> = Merge.keyed d.GetId d.GetId create update values newVals +/// +/// Represents a two-way binding that flows data between the model and view in both directions. +/// type TwoWayData<'model, 'msg, 'a> = { Get: 'model -> 'a Set: 'a -> 'model -> 'msg } +/// +/// Represents a command binding following the WPF ICommand pattern. +/// type CmdData<'model, 'msg> = { Exec: obj -> 'model -> 'msg voption CanExec: obj -> 'model -> bool AutoRequery: bool } +/// +/// Represents a binding for selected items in a sub-model sequence. +/// type SubModelSelectedItemData<'model, 'msg, 'id> = { Get: 'model -> 'id voption Set: 'id voption -> 'model -> 'msg SubModelSeqBindingName: string } +/// +/// Represents a binding to a sub-model/child view model. +/// type SubModelData<'model, 'msg, 'bindingModel, 'bindingMsg, 'vm> = { GetModel: 'model -> 'bindingModel voption CreateViewModel: ViewModelArgs<'bindingModel, 'bindingMsg> -> 'vm @@ -65,6 +94,9 @@ type SubModelData<'model, 'msg, 'bindingModel, 'bindingMsg, 'vm> = ToMsg: 'model -> 'bindingMsg -> 'msg } +/// +/// Represents a binding to a sub-model displayed in a separate window. +/// and SubModelWinData<'model, 'msg, 'bindingModel, 'bindingMsg, 'vm> = { GetState: 'model -> WindowState<'bindingModel> CreateViewModel: ViewModelArgs<'bindingModel, 'bindingMsg> -> 'vm @@ -75,6 +107,9 @@ and SubModelWinData<'model, 'msg, 'bindingModel, 'bindingMsg, 'vm> = OnCloseRequested: 'model -> 'msg voption } +/// +/// Represents a binding to a sequence of sub-models without unique identifiers. +/// and SubModelSeqUnkeyedData<'model, 'msg, 'bindingModel, 'bindingMsg, 'vm, 'vmCollection> = { GetModels: 'model -> 'bindingModel seq CreateViewModel: ViewModelArgs<'bindingModel, 'bindingMsg> -> 'vm @@ -83,6 +118,9 @@ and SubModelSeqUnkeyedData<'model, 'msg, 'bindingModel, 'bindingMsg, 'vm, 'vmCol ToMsg: 'model -> int * 'bindingMsg -> 'msg } +/// +/// Represents a binding to a sequence of sub-models with unique identifiers for efficient updates. +/// and SubModelSeqKeyedData<'model, 'msg, 'bindingModel, 'bindingMsg, 'vm, 'vmCollection, 'id when 'id: equality> = { GetSubModels: 'model -> 'bindingModel seq CreateViewModel: ViewModelArgs<'bindingModel, 'bindingMsg> -> 'vm @@ -103,11 +141,17 @@ and SubModelSeqKeyedData<'model, 'msg, 'bindingModel, 'bindingMsg, 'vm, 'vmColle Merge.keyed d.BmToId d.VmToId create update values newSubModels +/// +/// Wraps a binding with validation capabilities using INotifyDataErrorInfo. +/// and ValidationData<'model, 'msg, 't> = { BindingData: BindingData<'model, 'msg, 't> Validate: 'model -> string list } +/// +/// Wraps a binding with lazy evaluation to skip updates when models are equal. +/// and LazyData<'model, 'msg, 'bindingModel, 'bindingMsg, 't> = { BindingData: BindingData<'bindingModel, 'bindingMsg, 't> Get: 'model -> 'bindingModel @@ -118,6 +162,9 @@ and LazyData<'model, 'msg, 'bindingModel, 'bindingMsg, 't> = Helper.mapDispatch getCurrentModel this.Set dispatch +/// +/// Wraps a binding allowing transformation of the message stream (e.g., throttling, debouncing). +/// and AlterMsgStreamData<'model, 'msg, 'bindingModel, 'bindingMsg, 'dispatchMsg, 't> = { BindingData: BindingData<'bindingModel, 'bindingMsg, 't> Get: 'model -> 'bindingModel @@ -128,6 +175,9 @@ and AlterMsgStreamData<'model, 'msg, 'bindingModel, 'bindingMsg, 'dispatchMsg, ' Helper.mapDispatch getCurrentModel this.Set dispatch |> this.AlterMsgStream +/// +/// Discriminated union representing all base binding types. +/// and BaseBindingData<'model, 'msg, 't> = | OneWayData of OneWayData<'model, 't> | OneWayToSourceData of OneWayToSourceData<'model, 'msg, 't> @@ -141,6 +191,9 @@ and BaseBindingData<'model, 'msg, 't> = | SubModelSelectedItemData of SubModelSelectedItemData<'model, 'msg, obj> +/// +/// Main discriminated union representing binding data with possible decorators. +/// and BindingData<'model, 'msg, 't> = | BaseBindingData of BaseBindingData<'model, 'msg, 't> | CachingData of BindingData<'model, 'msg, 't> @@ -234,9 +287,25 @@ module BindingData = Get = d.Get Set = d.Set } + /// + /// Boxes the type parameter of a binding for generic handling. + /// + /// The binding to box. + /// A binding with boxed type parameter. let boxT b = MapT.recursiveCase box unbox b + + /// + /// Unboxes the type parameter of a binding. + /// + /// The binding to unbox. + /// A binding with unboxed type parameter. let unboxT b = MapT.recursiveCase unbox box b + /// + /// Maps the model of a binding via a contravariant mapping. + /// + /// The mapping function. + /// A function that transforms the binding's model type. let mapModel f = let binaryHelper binary x m = binary x (f m) @@ -319,6 +388,11 @@ module BindingData = recursiveCase + /// + /// Maps the message of a binding with access to the model via a covariant mapping. + /// + /// The mapping function that takes a message and the model. + /// A function that transforms the binding's message type. let mapMsgWithModel (f: 'a -> 'model -> 'b) = let baseCase = function @@ -394,16 +468,51 @@ module BindingData = recursiveCase + /// + /// Maps the message of a binding via a covariant mapping. + /// + /// The mapping function. + /// A function that transforms the binding's message type. let mapMsg f = mapMsgWithModel (fun a _ -> f a) + /// + /// Sets the message of a binding with access to the model. + /// + /// Function that produces the message from the model. + /// A function that sets the binding's message based on the model. let setMsgWithModel f = mapMsgWithModel (fun _ m -> f m) + + /// + /// Sets the message of a binding to a constant value. + /// + /// The message to set. + /// A function that sets the binding's message. let setMsg msg = mapMsg (fun _ -> msg) + /// + /// Adds caching to the given binding. The cache holds a single value and + /// is invalidated after the binding raises the PropertyChanged event. + /// + /// The binding to add caching to. + /// A cached binding. let addCaching b = b |> CachingData + /// + /// Adds validation to the given binding using INotifyDataErrorInfo. + /// + /// Function that returns validation errors for the model. + /// The binding to add validation to. + /// A binding with validation. let addValidation validate b = { BindingData = b; Validate = validate } |> ValidationData + /// + /// Adds laziness to the updating of the given binding. If the models are considered equal, + /// then updating of the given binding is skipped. + /// + /// Function to determine if two models are equal. + /// The binding to add laziness to. + /// A lazy binding. let addLazy (equals: 'model -> 'model -> bool) b = { BindingData = b |> mapModel unbox |> mapMsg box Get = box @@ -425,6 +534,12 @@ module BindingData = unbox >> g } |> AlterMsgStreamData + /// + /// Restricts the binding to models that satisfy the predicate after some model satisfies the predicate. + /// + /// The predicate that determines which models are valid. + /// The binding to which the sticky behavior is added. + /// A binding that remains active only for models satisfying the predicate once triggered. let addSticky (predicate: 'model -> bool) (binding: BindingData<'model, 'msg, 't>) = let mutable stickyModel = None diff --git a/src/Elmish.WPF/BindingVmHelpers.fs b/src/Elmish.WPF/BindingVmHelpers.fs index e9d46550..8df76512 100644 --- a/src/Elmish.WPF/BindingVmHelpers.fs +++ b/src/Elmish.WPF/BindingVmHelpers.fs @@ -7,9 +7,15 @@ open Microsoft.Extensions.Logging open Elmish +/// +/// Discriminated union representing different types of UI update events +/// type UpdateData = + /// Validation errors changed for the specified property | ErrorsChanged of string + /// Property value changed for the specified property | PropertyChanged of string + /// Command's CanExecute state changed | CanExecuteChanged of Command module UpdateData = @@ -24,14 +30,32 @@ type GetErrorSubModelSelectedItem = SubModelSeqBindingName: string Id: string } +/// +/// Errors that can occur when getting a binding value +/// [] type GetError = + /// Attempted to get value from a one-way-to-source binding | OneWayToSource + /// Failed to find selected item in sub-model sequence | SubModelSelectedItem of GetErrorSubModelSelectedItem + /// Failed to convert value option to null | ToNullError of ValueOption.ToNullError module Helpers2 = + /// + /// Shows a new window with the specified configuration + /// + /// Weak reference to store the window + /// Function to create the window + /// Whether to show as modal dialog + /// Handler for close requests + /// Reference to control close prevention + /// Data context for the window + /// Initial visibility state + /// Function to get current model + /// Message dispatch function let showNewWindow (winRef: WeakReference) (getWindow: 'model -> Dispatch<'msg> -> Window) @@ -75,6 +99,17 @@ module Helpers2 = win.Visibility <- initialVisibility) |> ignore + /// + /// Measures execution time of a function and logs if it exceeds threshold + /// + /// Logger for performance metrics + /// Log level to use + /// Threshold in milliseconds + /// Name of the measurement + /// Binding name chain for context + /// Name of the call being measured + /// Function to measure + /// The measured function let measure (logPerformance: ILogger) (logLevel: LogLevel) @@ -206,7 +241,13 @@ and AlterMsgStreamBinding<'model, 'bindingModel, 'bindingMsg, 't> = { Binding: VmBinding<'bindingModel, 'bindingMsg, 't> Get: 'model -> 'bindingModel } +/// /// Represents all necessary data used in an active binding. +/// This is the core type that encapsulates different binding behaviors. +/// +/// The type of the model +/// The type of messages +/// The type of the binding value and VmBinding<'model, 'msg, 't> = | BaseVmBinding of BaseVmBinding<'model, 'msg, 't> | Cached of CachedBinding<'model, 'msg, 't> @@ -215,6 +256,10 @@ and VmBinding<'model, 'msg, 't> = | AlterMsgStream of AlterMsgStreamBinding<'model, obj, obj, 't> + /// + /// Adds caching behavior to this binding + /// + /// A new binding with caching enabled member this.AddCaching = let mutable cache = None in @@ -223,6 +268,12 @@ and VmBinding<'model, 'msg, 't> = GetCache = (fun () -> cache) SetCache = fun c -> cache <- c } + /// + /// Adds validation behavior to this binding + /// + /// The current model state + /// Validation function + /// A new binding with validation enabled member this.AddValidation currentModel validate = { Binding = this Validate = validate @@ -656,11 +707,22 @@ type Initialize<'t> } +/// /// Updates the binding and returns a list indicating what events to raise for this binding +/// +/// The type of the binding value +/// Logging configuration +/// The binding name type Update<'t>(loggingArgs: LoggingViewModelArgs, name: string) = let { log = log; nameChain = nameChain } = loggingArgs + /// + /// Updates a base binding and returns events to raise + /// + /// The new model state + /// The binding to update + /// List of update events to raise member _.Base<'model, 'msg>(newModel: 'model, binding: BaseVmBinding<'model, 'msg, 't>) = match binding with | OneWay _ @@ -827,6 +889,13 @@ type Update<'t>(loggingArgs: LoggingViewModelArgs, name: string) = [] + /// + /// Recursively updates a binding and its wrappers + /// + /// The current model state + /// The new model state + /// The binding to update + /// List of update events to raise member this.Recursive<'model, 'msg> (currentModel: 'model, newModel: 'model, binding: VmBinding<'model, 'msg, 't>) : UpdateData list = @@ -860,8 +929,19 @@ type Update<'t>(loggingArgs: LoggingViewModelArgs, name: string) = | AlterMsgStream b -> this.Recursive(currentModel |> b.Get, b.Get newModel, b.Binding) +/// +/// Gets the value from a binding +/// +/// The type of the binding value +/// The binding name chain for logging type Get<'t>(nameChain: string) = + /// + /// Gets the value from a base binding + /// + /// The current model state + /// The binding to get value from + /// Result containing the value or an error member _.Base(model: 'model, binding: BaseVmBinding<'model, 'msg, 't>) = match binding with | OneWay { OneWayData = d } -> d.Get model |> Ok @@ -892,6 +972,12 @@ type Get<'t>(nameChain: string) = |> Error |> Result.bind (ValueOption.toNull >> Result.mapError GetError.ToNullError) + /// + /// Recursively gets the value from a binding and its wrappers + /// + /// The current model state + /// The binding to get value from + /// Result containing the value or an error member this.Recursive<'model, 'msg>(model: 'model, binding: VmBinding<'model, 'msg, 't>) : Result<'t, GetError> = match binding with | BaseVmBinding b -> this.Base(model, b) @@ -907,8 +993,19 @@ type Get<'t>(nameChain: string) = | AlterMsgStream b -> this.Recursive(b.Get model, b.Binding) +/// +/// Sets a value in a binding +/// +/// The type of the binding value +/// The value to set type Set<'t>(value: 't) = + /// + /// Sets the value in a base binding + /// + /// The current model state + /// The binding to set value in + /// True if the value was set, false if binding is read-only member _.Base(model: 'model, binding: BaseVmBinding<'model, 'msg, 't>) = match binding with | TwoWay b -> @@ -928,6 +1025,12 @@ type Set<'t>(value: 't) = | SubModelSeqUnkeyed _ | SubModelSeqKeyed _ -> false + /// + /// Recursively sets the value in a binding and its wrappers + /// + /// The current model state + /// The binding to set value in + /// True if the value was set, false if binding is read-only member this.Recursive<'model, 'msg>(model: 'model, binding: VmBinding<'model, 'msg, 't>) : bool = match binding with | BaseVmBinding b -> this.Base(model, b) diff --git a/src/Elmish.WPF/Command.fs b/src/Elmish.WPF/Command.fs index 301d9b7f..46b8344f 100644 --- a/src/Elmish.WPF/Command.fs +++ b/src/Elmish.WPF/Command.fs @@ -3,11 +3,15 @@ open System open System.Windows.Input +/// /// A command that optionally hooks into CommandManager.RequerySuggested to /// automatically trigger CanExecuteChanged whenever the CommandManager detects /// conditions that might change the output of canExecute. It's necessary to use /// this feature for command bindings where the CommandParameter is bound to /// another UI control (e.g. a ListView.SelectedItem). +/// +/// The function to execute when the command is invoked. +/// The function that determines whether the command can execute. type internal Command(execute, canExecute) = let canExecuteChanged = Event() @@ -18,11 +22,17 @@ type internal Command(execute, canExecute) = // Can test this via the UiBoundCmdParam sample. let mutable _handler = Unchecked.defaultof + /// + /// Adds a handler to CommandManager.RequerySuggested for automatic CanExecuteChanged updates. + /// member this.AddRequeryHandler() = let handler = EventHandler(fun _ _ -> this.RaiseCanExecuteChanged()) CommandManager.RequerySuggested.AddHandler handler _handler <- handler + /// + /// Raises the CanExecuteChanged event to notify that the command's ability to execute may have changed. + /// member this.RaiseCanExecuteChanged() = canExecuteChanged.Trigger(this, EventArgs.Empty) diff --git a/src/Elmish.WPF/InternalUtils.fs b/src/Elmish.WPF/InternalUtils.fs index ebc4c5dd..8cd632c4 100644 --- a/src/Elmish.WPF/InternalUtils.fs +++ b/src/Elmish.WPF/InternalUtils.fs @@ -5,64 +5,127 @@ open System.Collections.Generic open System.Diagnostics +/// +/// Flips the order of arguments for a two-argument function. +/// +/// The function to flip. +/// The second argument. +/// The first argument. +/// The result of f a b. let flip f b a = f a b +/// +/// Ignores two arguments and returns unit. +/// +/// First ignored argument. +/// Second ignored argument. let ignore2 _ _ = () +/// /// Deconstructs a KeyValuePair into a tuple. +/// +/// The KeyValuePair to deconstruct. +/// A tuple of (key, value). [] let (|Kvp|) (kvp: KeyValuePair<_, _>) = Kvp(kvp.Key, kvp.Value) +/// +/// Computation expression builder for Option monad. +/// [] type OptionalBuilder = member _.Bind(ma, f) = ma |> Option.bind f member _.Return(a) = Some a member _.ReturnFrom(ma) = ma +/// +/// Instance of the optional computation expression builder. +/// let option = OptionalBuilder() +/// +/// Utility functions for KeyValuePair. +/// [] module Kvp = + /// + /// Gets the key from a KeyValuePair. + /// + /// The KeyValuePair. + /// The key. let key (kvp: KeyValuePair<_, _>) = kvp.Key + /// + /// Gets the value from a KeyValuePair. + /// + /// The KeyValuePair. + /// The value. let value (kvp: KeyValuePair<_, _>) = kvp.Value +/// +/// Utility functions for Result type. +/// [] module Result = + /// + /// Checks if a Result is Ok. + /// + /// The Result to check. + /// True if Ok, false if Error. let isOk = function | Ok _ -> true | Error _ -> false + /// + /// Applies a function to the Ok value if present, otherwise does nothing. + /// + /// The function to apply. + /// The Result value. let iter f = function | Ok x -> f x | Error _ -> () +/// +/// Utility functions for ValueOption type. +/// [] module ValueOption = + /// + /// Converts an Option to a ValueOption. + /// let ofOption = function | Some x -> ValueSome x | None -> ValueNone + /// + /// Converts a ValueOption to an Option. + /// let toOption = function | ValueSome x -> Some x | ValueNone -> None + /// + /// Extracts the Error value as a ValueOption, returning ValueNone for Ok. + /// let ofError = function | Ok _ -> ValueNone | Error x -> ValueSome x + /// + /// Extracts the Ok value as a ValueOption, returning ValueNone for Error. + /// let ofOk = function | Ok x -> ValueSome x @@ -71,11 +134,20 @@ module ValueOption = [] type ToNullError = ValueCannotBeNull of string + /// + /// Converts a nullable value to a ValueOption. + /// + /// The value to convert. + /// ValueSome if not null, ValueNone otherwise. let ofNull<'a> (x: 'a) = match box x with | null -> ValueNone | _ -> ValueSome x + /// + /// Converts a ValueOption to a nullable value. + /// + /// Ok with the value or null, Error if the type cannot be null. let toNull<'a> = function | ValueSome x -> Ok x @@ -88,15 +160,33 @@ module ValueOption = typeof<'a>.Name |> ToNullError.ValueCannotBeNull |> Error +/// +/// Utility functions for by-reference pairs. +/// [] module ByRefPair = + /// + /// Converts a bool * 'a pair to an Option. + /// + /// The boolean flag. + /// The value. + /// Some a if b is true, None otherwise. let toOption (b, a) = if b then Some a else None +/// +/// Utility functions for Dictionary. +/// [] module Dictionary = + /// + /// Tries to find a value in the dictionary. + /// + /// The key to search for. + /// The dictionary. + /// Some value if found, None otherwise. let tryFind key (d: Dictionary<_, _>) = key |> d.TryGetValue |> ByRefPair.toOption diff --git a/src/Elmish.WPF/Merge.fs b/src/Elmish.WPF/Merge.fs index 64321877..f13b2a73 100644 --- a/src/Elmish.WPF/Merge.fs +++ b/src/Elmish.WPF/Merge.fs @@ -5,10 +5,20 @@ open System.Collections.Generic open System.Collections.ObjectModel +/// +/// Discriminated union indicating whether an error occurred in the source or target sequence +/// type SourceOrTarget = | Source | Target +/// +/// Exception thrown when duplicate IDs are found in a keyed collection merge operation +/// +/// Whether the duplicate was found in the source or target sequence +/// Index of the first occurrence of the duplicate ID +/// Index of the second occurrence of the duplicate ID +/// The duplicate ID value type DuplicateIdException(sourceOrTarget: SourceOrTarget, index1: int, index2: int, id: string) = inherit System.Exception( @@ -25,6 +35,11 @@ type DuplicateIdException(sourceOrTarget: SourceOrTarget, index1: int, index2: i member this.Index2 = index2 member this.Id = id +/// +/// Abstraction over mutable collections that allows for efficient updates during merge operations +/// +/// The type of elements in the collection +/// The type of the collection itself type CollectionTarget<'a, 'aCollection> = { GetLength: unit -> int GetAt: int -> 'a @@ -39,6 +54,11 @@ type CollectionTarget<'a, 'aCollection> = module CollectionTarget = + /// + /// Creates a CollectionTarget from an ObservableCollection + /// + /// The ObservableCollection to wrap + /// A CollectionTarget that manipulates the ObservableCollection let create (oc: ObservableCollection<'a>) = { GetLength = fun () -> oc.Count GetAt = fun i -> oc.[i] @@ -51,6 +71,13 @@ module CollectionTarget = Enumerate = fun () -> upcast oc GetCollection = fun () -> oc } + /// + /// Maps the element type of a CollectionTarget using bidirectional transformation functions + /// + /// Function to transform elements when reading from the collection + /// Function to transform elements when writing to the collection + /// The CollectionTarget to map + /// A new CollectionTarget with transformed element type let mapA (fOut: 'a0 -> 'a1) (fIn: 'a1 -> 'a0) @@ -67,6 +94,12 @@ module CollectionTarget = Enumerate = ct.Enumerate >> Seq.map fOut GetCollection = ct.GetCollection } + /// + /// Maps the collection type of a CollectionTarget + /// + /// Function to transform the collection type + /// The CollectionTarget to map + /// A new CollectionTarget with transformed collection type let mapCollection (fOut: 'aCollection0 -> 'aCollection1) (ct: CollectionTarget<'a, 'aCollection0>) @@ -86,6 +119,13 @@ module CollectionTarget = module Merge = + /// + /// Merges a source sequence into a target collection without using keys, updating by position + /// + /// Function to create a new target element from a source element and index + /// Function to update an existing target element with data from a source element + /// The target collection to merge into + /// The source sequence to merge from let unkeyed (create: 's -> int -> 't) (update: 't -> 's -> unit) @@ -109,6 +149,17 @@ module Merge = idx <- idx - 1 + /// + /// Merges a source array into a target collection using keys for efficient updates. + /// Based on Elm's HTML.keyed algorithm for efficient virtual DOM updates. + /// + /// Function to extract ID from source elements + /// Function to extract ID from target elements + /// Function to create a new target element from source element and ID + /// Function to update an existing target element with data from source element and index + /// The target collection to merge into + /// The source array to merge from + /// Thrown when duplicate IDs are found in either sequence let keyed (getSourceId: 's -> 'id) (getTargetId: 't -> 'id) diff --git a/src/Elmish.WPF/Utils.fs b/src/Elmish.WPF/Utils.fs index 28450e56..8b22ca9f 100644 --- a/src/Elmish.WPF/Utils.fs +++ b/src/Elmish.WPF/Utils.fs @@ -2,8 +2,13 @@ module Elmish.WPF.Utils +/// /// Reference/physical equality for reference types. Alias for /// LanguagePrimitives.PhysicalEquality. Also see elmEq. +/// +/// First object to compare +/// Second object to compare +/// True if both references point to the same object let refEq = LanguagePrimitives.PhysicalEquality @@ -11,8 +16,12 @@ open System open System.Linq.Expressions open System.Reflection +/// /// Returns a fast, untyped getter for the property specified by the PropertyInfo. /// The getter takes an instance and returns a property value. +/// +/// The property to create a getter for +/// A function that takes an object instance and returns the property value let buildUntypedGetter (propertyInfo: PropertyInfo) : obj -> obj = let method = propertyInfo.GetMethod let objExpr = Expression.Parameter(typeof, "o") @@ -46,6 +55,7 @@ type private ElmEq<'a>() = gettersAndEq |> Array.forall (fun (get, eq) -> eq (get (box x1), get (box x2))) +/// /// Memberwise equality where value-typed members and string members are /// compared using structural comparison (the standard F# (=) operator), /// and reference-typed members (except strings) are compared using reference @@ -54,4 +64,9 @@ type private ElmEq<'a>() = /// normally immutable. For a direct reference equality check (not memberwise), /// see refEq (which should be used when passing a single non-string reference /// type from the model). +/// +/// The type to compare +/// First value to compare +/// Second value to compare +/// True if all members are equal according to the rules described let elmEq<'a> : 'a -> 'a -> bool = ElmEq<'a>.Eq \ No newline at end of file diff --git a/src/Elmish.WPF/ViewModelArgs.fs b/src/Elmish.WPF/ViewModelArgs.fs index 8f2de79b..52fb2daf 100644 --- a/src/Elmish.WPF/ViewModelArgs.fs +++ b/src/Elmish.WPF/ViewModelArgs.fs @@ -26,6 +26,12 @@ module internal LoggingViewModelArgs = nameChain = "" } +/// +/// Arguments required to create a view model, including the initial model state, +/// dispatch function for messages, and logging configuration +/// +/// The type of the model +/// The type of messages type ViewModelArgs<'model, 'msg> = internal { initialModel: 'model @@ -38,15 +44,33 @@ module ViewModelArgs = dispatch = dispatch loggingArgs = LoggingViewModelArgs.map nameChain loggingArgs } + /// + /// Maps both the model and message types of ViewModelArgs + /// + /// Function to transform the model type + /// Function to transform the message type + /// The ViewModelArgs to map + /// New ViewModelArgs with transformed types let map mapModel mapMsg v = { initialModel = v.initialModel |> mapModel dispatch = mapMsg >> v.dispatch loggingArgs = v.loggingArgs } + /// + /// Creates ViewModelArgs without logging enabled + /// + /// The initial model state + /// The message dispatch function + /// ViewModelArgs configured without logging let createWithoutLogging initialModel dispatch = { initialModel = initialModel dispatch = dispatch loggingArgs = LoggingViewModelArgs.none } + /// + /// Creates simple ViewModelArgs with no logging and no dispatch handling + /// + /// The initial model state + /// ViewModelArgs with no-op dispatch and no logging let simple initialModel = createWithoutLogging initialModel ignore \ No newline at end of file diff --git a/src/Elmish.WPF/ViewModelModule.fs b/src/Elmish.WPF/ViewModelModule.fs index bb63be1b..30984fa3 100644 --- a/src/Elmish.WPF/ViewModelModule.fs +++ b/src/Elmish.WPF/ViewModelModule.fs @@ -1,6 +1,12 @@ module Elmish.WPF.ViewModel +/// /// Creates a design-time view model using the given model and bindings. +/// This is useful for XAML design-time data support in WPF designers. +/// +/// The model instance to use at design time +/// The list of bindings to configure +/// A boxed DynamicViewModel suitable for design-time use let designInstance (model: 'model) (bindings: Binding<'model, 'msg> list) = let args = ViewModelArgs.simple model diff --git a/src/Elmish.WPF/ViewModels.fs b/src/Elmish.WPF/ViewModels.fs index 9cdd0d29..66e9afc1 100644 --- a/src/Elmish.WPF/ViewModels.fs +++ b/src/Elmish.WPF/ViewModels.fs @@ -8,7 +8,12 @@ open Microsoft.Extensions.Logging open BindingVmHelpers +/// /// Represents all necessary data used to create a binding. +/// +/// The type of the model +/// The type of messages +/// The type of the binding value type Binding<'model, 'msg, 't> = internal { Name: string @@ -30,9 +35,16 @@ module internal Helpers = member this.CompareBindings() : Binding<'model, 'msg> -> Binding<'model, 'msg> -> int = fun a b -> this.Recursive(a.Data) - this.Recursive(b.Data) +/// +/// Interface for view models that can be updated with new model states +/// +/// The type of the model +/// The type of messages [] type IViewModel<'model, 'msg> = + /// Gets the current model state abstract member CurrentModel: 'model + /// Updates the view model with a new model state abstract member UpdateModel: 'model -> unit module internal IViewModel = @@ -136,6 +148,12 @@ module internal ViewModelHelper = None +/// +/// Dynamic view model that uses DynamicObject to expose bindings as properties. +/// This is the primary view model implementation for dynamic binding scenarios. +/// +/// The type of the model +/// The type of messages [] type internal DynamicViewModel<'model, 'msg>(args: ViewModelArgs<'model, 'msg>, bindings: Binding<'model, 'msg> list) as this = @@ -309,6 +327,12 @@ type internal DynamicViewModel<'model, 'msg>(args: ViewModelArgs<'model, 'msg>, open System.Runtime.CompilerServices +/// +/// Base class for strongly-typed view models that provides Get and Set helper methods +/// for accessing bindings in a type-safe manner. +/// +/// The type of the model +/// The type of messages [] type ViewModelBase<'model, 'msg>(args: ViewModelArgs<'model, 'msg>) as this = @@ -331,6 +355,12 @@ type ViewModelBase<'model, 'msg>(args: ViewModelArgs<'model, 'msg>) as this = ) .Recursive(initialModel, dispatch, (fun () -> this |> IViewModel.currentModel), binding.Data) + /// + /// Gets a binding value using the caller member name as the binding name + /// + /// The type of the binding value + /// Automatically captured member name + /// Function that takes a binding factory and returns the value member _.Get<'a>([] ?memberName: string) = fun (binding: string -> Binding<'model, 'msg, 'a>) -> let result = @@ -398,6 +428,13 @@ type ViewModelBase<'model, 'msg>(args: ViewModelArgs<'model, 'msg>) as this = failwithf $"[%s{nameChain}] Get FAILED: Binding {memberName} returned an error {e}" | Some(Ok r) -> r + /// + /// Sets a binding value using the caller member name as the binding name + /// + /// The type of the binding value + /// The value to set + /// Automatically captured member name + /// Function that takes a binding factory member _.Set<'a>(value: 'a, [] ?memberName: string) = fun (binding: string -> Binding<'model, 'msg, 'a>) -> try diff --git a/src/Elmish.WPF/WindowState.fs b/src/Elmish.WPF/WindowState.fs index 4a9f9238..e14a5d4c 100644 --- a/src/Elmish.WPF/WindowState.fs +++ b/src/Elmish.WPF/WindowState.fs @@ -1,45 +1,95 @@ namespace Elmish.WPF +/// +/// Represents the state of a window with an associated model +/// +/// The type of model associated with the window [] type WindowState<'model> = + /// Window is closed and has no model | Closed + /// Window exists but is hidden with the given model | Hidden of 'model + /// Window is visible with the given model | Visible of 'model module WindowState = + /// + /// Catamorphism for WindowState. Applies one of three functions based on the state. + /// + /// Value to return for Closed state + /// Function to apply to Hidden state's model + /// Function to apply to Visible state's model + /// Result of applying the appropriate function let cata a f g = function | WindowState.Closed -> a | WindowState.Hidden a -> a |> f | WindowState.Visible a -> a |> g + /// + /// Maps the model inside a WindowState + /// + /// Function to transform the model + /// New WindowState with transformed model let map f = cata WindowState.Closed (f >> WindowState.Hidden) (f >> WindowState.Visible) + /// + /// Sets the model in a WindowState, preserving the window visibility state + /// + /// The new model value + /// WindowState with the new model let set a = map (fun _ -> a) + /// + /// Converts any WindowState to Hidden with the given model + /// + /// The model for the hidden state + /// WindowState.Hidden with the given model let toHidden a = cata (WindowState.Hidden a) WindowState.Hidden WindowState.Hidden + /// + /// Converts any WindowState to Visible with the given model + /// + /// The model for the visible state + /// WindowState.Visible with the given model let toVisible a = cata (WindowState.Visible a) WindowState.Visible WindowState.Visible + /// + /// Converts WindowState to Option, where Closed becomes None + /// + /// The WindowState to convert + /// Some(model) for Hidden/Visible states, None for Closed let toOption state = state |> cata None Some Some + /// + /// Converts WindowState to ValueOption, where Closed becomes ValueNone + /// + /// The WindowState to convert + /// ValueSome(model) for Hidden/Visible states, ValueNone for Closed let toVOption state = state |> cata ValueNone ValueSome ValueSome - /// Converts None to WindowState.Closed, and Some(x) to - /// WindowState.Visible(x). + /// + /// Converts None to WindowState.Closed, and Some(x) to WindowState.Visible(x) + /// + /// The optional model + /// WindowState.Closed for None, WindowState.Visible for Some let ofOption (model: 'model option) = match model with | Some a -> a |> WindowState.Visible | None -> WindowState.Closed - /// Converts ValueNone to WindowState.Closed, and ValueSome(x) to - /// WindowState.Visible(x). + /// + /// Converts ValueNone to WindowState.Closed, and ValueSome(x) to WindowState.Visible(x) + /// + /// The value optional model + /// WindowState.Closed for ValueNone, WindowState.Visible for ValueSome let ofVOption (model: 'model voption) = match model with | ValueSome a -> a |> WindowState.Visible diff --git a/src/Elmish.WPF/WpfProgram.fs b/src/Elmish.WPF/WpfProgram.fs index e345aa3a..e9c27566 100644 --- a/src/Elmish.WPF/WpfProgram.fs +++ b/src/Elmish.WPF/WpfProgram.fs @@ -6,6 +6,12 @@ open Microsoft.Extensions.Logging.Abstractions open Elmish +/// +/// Represents a WPF program configuration that combines Elmish architecture with WPF data binding +/// +/// The type of the model +/// The type of messages +/// The type of the view model type WpfProgram<'model, 'msg, 'viewModel> = internal { @@ -50,7 +56,13 @@ module WpfProgram = PerformanceLogThreshold = 1 } + /// /// Creates a WpfProgram that does not use commands. + /// + /// Function to initialize the model + /// Function to update the model based on messages + /// Function that returns the list of bindings + /// A configured WpfProgram let mkSimple (init: unit -> 'model) (update: 'msg -> 'model -> 'model) @@ -59,7 +71,13 @@ module WpfProgram = Program.mkSimple init update (fun _ _ -> ()) |> createWithBindings bindings - /// Creates a WpfProgram that uses commands + /// + /// Creates a WpfProgram that uses commands for side effects + /// + /// Function to initialize the model and commands + /// Function to update the model and produce commands + /// Function that returns the list of bindings + /// A configured WpfProgram let mkProgram (init: unit -> 'model * Cmd<'msg>) (update: 'msg -> 'model -> 'model * Cmd<'msg>) @@ -67,7 +85,13 @@ module WpfProgram = = Program.mkProgram init update (fun _ _ -> ()) |> createWithBindings bindings - /// Creates a WpfProgram that does not use commands. + /// + /// Creates a WpfProgram with a typed view model that does not use commands + /// + /// Function to initialize the model + /// Function to update the model based on messages + /// Function to create the typed view model + /// A configured WpfProgram let mkSimpleT (init: unit -> 'model) (update: 'msg -> 'model -> 'model) @@ -76,7 +100,13 @@ module WpfProgram = Program.mkSimple init update (fun _ _ -> ()) |> createWithVm createVm - /// Creates a WpfProgram that uses commands + /// + /// Creates a WpfProgram with a typed view model that uses commands + /// + /// Function to initialize the model and commands + /// Function to update the model and produce commands + /// Function to create the typed view model + /// A configured WpfProgram let mkProgramT (init: unit -> 'model * Cmd<'msg>) (update: 'msg -> 'model -> 'model * Cmd<'msg>) @@ -282,10 +312,15 @@ module WpfProgram = Application.Current.MainWindow <- window + /// /// Starts the Elmish and WPF dispatch loops. Will instantiate Application and set its /// MainWindow if it is not already running, and then run the specified window. This is a /// blocking function. If you are using App.xaml as an implicit entry point, see /// startElmishLoop. + /// + /// The main window to run + /// The WpfProgram configuration + /// The application run result let runWindow window program = (* * This is the correct order for these four statements. @@ -302,12 +337,19 @@ module WpfProgram = Application.Current.Run window + /// /// Same as mkProgram, except that init and update don't return Cmd<'msg> /// directly, but instead return a CmdMsg discriminated union that is converted /// to Cmd<'msg> using toCmd. This means that the init and update functions /// return only data, and thus are easier to unit test. The CmdMsg pattern is /// general; this is just a trivial convenience function that automatically /// converts CmdMsg to Cmd<'msg> for you in init and update. + /// + /// Function to initialize model and command messages + /// Function to update model and produce command messages + /// Function that returns the list of bindings + /// Function to convert command messages to Cmd + /// A configured WpfProgram let mkProgramWithCmdMsg (init: unit -> 'model * 'cmdMsg list) (update: 'msg -> 'model -> 'model * 'cmdMsg list) @@ -338,53 +380,87 @@ module WpfProgram = mkProgramT (init >> convert) (fun msg model -> update msg model |> convert) createVm - /// Uses the specified ILoggerFactory for logging. + /// + /// Uses the specified ILoggerFactory for logging + /// + /// The logger factory to use + /// The program to configure + /// Program with logging configured let withLogger loggerFactory program = { program with LoggerFactory = loggerFactory } + /// /// Calls the specified function for unhandled exceptions in the Elmish /// dispatch loop (e.g. in commands or the update function). This essentially /// delegates to Elmish's Program.withErrorHandler. - /// /// The first (string) argument of onError is a message from Elmish describing /// the context of the exception. Note that this may contain a rendered /// message case with all data ("%A" formatting). - /// /// Note that exceptions passed to onError are also logged to the logger /// specified using WpfProgram.withLogger. + /// + /// Function to handle errors + /// The program to configure + /// Program with error handler configured let withElmishErrorHandler onError program = { program with ErrorHandler = onError } + /// /// Subscribe to external source of events, overrides existing subscription. /// Return the subscriptions that should be active based on the current model. /// Subscriptions will be started or stopped automatically to match. + /// + /// Function that returns active subscriptions based on model + /// The program to configure + /// Program with subscription configured let withSubscription (subscribe: 'model -> Sub<'msg>) program = { program with ElmishProgram = program.ElmishProgram |> Program.withSubscription subscribe } - /// Map existing subscription to external source of events. + /// + /// Map existing subscription to external source of events + /// + /// Function to transform the subscription + /// The program to configure + /// Program with mapped subscription let mapSubscription map program = { program with ElmishProgram = program.ElmishProgram |> Program.mapSubscription map } + /// /// Only logs binding performance for calls taking longer than the specified number of /// milliseconds. The default is 1ms. + /// + /// Minimum milliseconds before logging performance + /// The program to configure + /// Program with performance threshold configured let withPerformanceLogThreshold threshold program = { program with PerformanceLogThreshold = threshold } - /// Exit criteria and the handler, overrides existing. + /// + /// Exit criteria and the handler, overrides existing + /// + /// Function to determine if program should terminate + /// Function to handle termination + /// The program to configure + /// Program with termination configured let withTermination predicate terminate program = { program with ElmishProgram = program.ElmishProgram |> Program.withTermination predicate terminate } - /// Map existing criteria and the handler. + /// + /// Map existing termination criteria and handler + /// + /// Function to transform the termination configuration + /// The program to configure + /// Program with mapped termination let mapTermination map program = { program with ElmishProgram = program.ElmishProgram |> Program.mapTermination map } @@ -393,7 +469,12 @@ module WpfProgram = [] module Subscribe = - /// Converts an effect to a Subscribe with a given dispose (on stop) method. + /// + /// Converts an effect to a Subscribe with a given dispose (on stop) method + /// + /// Function to call when subscription is stopped + /// The effect to convert + /// A subscription that can be managed by Elmish let ofEffect dispose (effect: Effect<'msg>) : Subscribe<'msg> = fun dispatch -> effect dispatch @@ -404,8 +485,13 @@ module Subscribe = [] module Sub = + /// /// Subscribe to an external source of events. The subscribe function is called once, /// with the initial model, but can dispatch messages at any time. + /// + /// Prefix for subscription IDs + /// Legacy v3 subscription function + /// Function that creates subscriptions from model [] let fromV3Subscription (idPrefix: string) (v3Subscription: 'model -> Cmd<'msg>) : 'model -> Sub<'msg> = let mutable memoizedSub: Sub<'msg> voption = ValueNone From 2df060ae2b2be004ea0fa17ea99047b1227bedce Mon Sep 17 00:00:00 2001 From: Tim Rudat Date: Sun, 20 Jul 2025 17:57:56 +0200 Subject: [PATCH 06/18] Reorganize project structure Move all dynamic binding samples to src/Samples/Dynamic/ Move typed ViewModel samples to src/Samples/Typed/ Relocate test projects to tests/ folder Update .slnx file to reflect new project structure Add README explaining both binding approaches This reorganization makes it clearer for users to understand and choose between the dynamic bindings() approach and the typed ViewModelBase approach. --- .claude/settings.local.json | 10 + Elmish.WPF.slnx | 100 ++--- README.md | 188 ++++++--- REFERENCE.md | 367 ++++++++++++------ RELEASE_NOTES.md | 195 +++++++--- TUTORIAL.md | 261 +++++++++---- jetbrains.svg | 71 ++-- src/Elmish.WPF/BindingData.fs | 4 +- src/Elmish.WPF/Elmish.WPF.fsproj | 32 +- src/Samples/Capabilities/App.xaml.cs | 16 - src/Samples/Capabilities/MainWindow.xaml.cs | 9 - .../Selection/BindableSelectedValue.cs | 31 -- .../Selection/SelectionScreen.xaml.cs | 17 - .../Capabilities.Core.fsproj | 14 +- .../Capabilities.Core/Program.fs | 0 .../Capabilities.Core/Selection.fs | 0 .../Capabilities.Core/Utilities.fs | 0 .../{ => Dynamic}/Capabilities/App.xaml | 2 +- src/Samples/Dynamic/Capabilities/App.xaml.cs | 19 + .../Capabilities/Capabilities.csproj | 4 +- .../Capabilities/MainWindow.xaml | 8 +- .../Dynamic/Capabilities/MainWindow.xaml.cs | 11 + .../Selection/BindableSelectedValue.cs | 40 ++ .../Selection/SelectionScreen.xaml | 33 +- .../Selection/SelectionScreen.xaml.cs | 19 + .../EventBindingsAndBehaviors.Core.fsproj | 10 +- .../EventBindingsAndBehaviors.Core/Program.fs | 0 .../EventBindingsAndBehaviors/App.xaml | 7 + .../EventBindingsAndBehaviors/App.xaml.cs | 18 + .../EventBindingsAndBehaviors.csproj | 4 +- .../FocusWhenVisibleBehavior.cs | 24 ++ .../EventBindingsAndBehaviors/MainWindow.xaml | 34 +- .../MainWindow.xaml.cs | 11 + .../FileDialogs.Core/FileDialogs.Core.fsproj | 10 +- .../{ => Dynamic}/FileDialogs.Core/Program.fs | 0 src/Samples/Dynamic/FileDialogs/App.xaml | 7 + src/Samples/Dynamic/FileDialogs/App.xaml.cs | 18 + .../FileDialogs/FileDialogs.csproj | 2 +- .../{ => Dynamic}/FileDialogs/MainWindow.xaml | 5 +- .../Dynamic/FileDialogs/MainWindow.xaml.cs | 11 + .../FileDialogsCmdMsg.Core.fsproj | 10 +- .../FileDialogsCmdMsg.Core/Program.fs | 0 .../Dynamic/FileDialogsCmdMsg/App.xaml | 7 + .../Dynamic/FileDialogsCmdMsg/App.xaml.cs | 18 + .../FileDialogsCmdMsg.csproj | 2 +- .../FileDialogsCmdMsg/MainWindow.xaml | 5 +- .../FileDialogsCmdMsg/MainWindow.xaml.cs | 11 + .../Multiselect.Core/Multiselect.Core.fsproj | 10 +- .../{ => Dynamic}/Multiselect.Core/Program.fs | 0 .../{ => Dynamic}/Multiselect/App.xaml | 2 +- src/Samples/Dynamic/Multiselect/App.xaml.cs | 18 + .../{ => Dynamic}/Multiselect/MainWindow.xaml | 13 +- .../Dynamic/Multiselect/MainWindow.xaml.cs | 11 + .../Multiselect/Multiselect.csproj | 2 +- .../{ => Dynamic}/NewWindow.Core/App.fs | 0 .../{ => Dynamic}/NewWindow.Core/AutoOpen.fs | 0 .../NewWindow.Core/NewWindow.Core.fsproj | 29 ++ .../{ => Dynamic}/NewWindow.Core/Program.fs | 0 .../{ => Dynamic}/NewWindow.Core/Window1.fs | 0 .../{ => Dynamic}/NewWindow.Core/Window2.fs | 0 src/Samples/Dynamic/NewWindow/App.xaml | 8 + src/Samples/Dynamic/NewWindow/App.xaml.cs | 18 + .../{ => Dynamic}/NewWindow/MainWindow.xaml | 5 +- .../Dynamic/NewWindow/MainWindow.xaml.cs | 15 + .../{ => Dynamic}/NewWindow/NewWindow.csproj | 4 +- .../{ => Dynamic}/NewWindow/Window1.xaml | 0 src/Samples/Dynamic/NewWindow/Window1.xaml.cs | 11 + .../{ => Dynamic}/NewWindow/Window2.xaml | 6 +- src/Samples/Dynamic/NewWindow/Window2.xaml.cs | 11 + .../OneWaySeq.Core/OneWaySeq.Core.fsproj | 10 +- .../{ => Dynamic}/OneWaySeq.Core/Program.fs | 0 src/Samples/Dynamic/OneWaySeq/App.xaml | 7 + src/Samples/Dynamic/OneWaySeq/App.xaml.cs | 18 + .../{ => Dynamic}/OneWaySeq/MainWindow.xaml | 11 +- .../Dynamic/OneWaySeq/MainWindow.xaml.cs | 11 + .../{ => Dynamic}/OneWaySeq/OneWaySeq.csproj | 2 +- .../SingleCounter.Core/Program.fs | 0 .../SingleCounter.Core.fsproj | 10 +- src/Samples/Dynamic/SingleCounter/App.xaml | 7 + src/Samples/Dynamic/SingleCounter/App.xaml.cs | 18 + .../SingleCounter/MainWindow.xaml | 5 +- .../Dynamic/SingleCounter/MainWindow.xaml.cs | 11 + .../SingleCounter/SingleCounter.csproj | 2 +- .../{ => Dynamic}/Sticky.Core/Program.fs | 0 .../Sticky.Core/Sticky.Core.fsproj | 10 +- src/Samples/{ => Dynamic}/Sticky/App.xaml | 0 src/Samples/Dynamic/Sticky/App.xaml.cs | 18 + .../{ => Dynamic}/Sticky/MainWindow.xaml | 7 +- src/Samples/Dynamic/Sticky/MainWindow.xaml.cs | 11 + .../{ => Dynamic}/Sticky/Sticky.csproj | 2 +- .../{ => Dynamic}/SubModel.Core/Program.fs | 0 .../SubModel.Core/SubModel.Core.fsproj | 10 +- src/Samples/Dynamic/SubModel/App.xaml | 7 + src/Samples/Dynamic/SubModel/App.xaml.cs | 18 + src/Samples/{ => Dynamic}/SubModel/Clock.xaml | 10 +- src/Samples/Dynamic/SubModel/Clock.xaml.cs | 11 + .../{ => Dynamic}/SubModel/Counter.xaml | 5 +- src/Samples/Dynamic/SubModel/Counter.xaml.cs | 11 + .../SubModel/CounterWithClock.xaml | 10 +- .../Dynamic/SubModel/CounterWithClock.xaml.cs | 11 + .../{ => Dynamic}/SubModel/MainWindow.xaml | 14 +- .../Dynamic/SubModel/MainWindow.xaml.cs | 11 + .../{ => Dynamic}/SubModel/SubModel.csproj | 2 +- .../{ => Dynamic}/SubModelOpt.Core/Program.fs | 0 .../SubModelOpt.Core/SubModelOpt.Core.fsproj | 10 +- src/Samples/Dynamic/SubModelOpt/App.xaml | 7 + src/Samples/Dynamic/SubModelOpt/App.xaml.cs | 18 + .../{ => Dynamic}/SubModelOpt/Form1.xaml | 5 +- src/Samples/Dynamic/SubModelOpt/Form1.xaml.cs | 11 + .../{ => Dynamic}/SubModelOpt/Form2.xaml | 2 +- src/Samples/Dynamic/SubModelOpt/Form2.xaml.cs | 11 + .../{ => Dynamic}/SubModelOpt/MainWindow.xaml | 30 +- .../Dynamic/SubModelOpt/MainWindow.xaml.cs | 15 + .../SubModelOpt/SubModelOpt.csproj | 2 +- .../SubModelSelectedItem.Core/Program.fs | 0 .../SubModelSelectedItem.Core.fsproj | 10 +- .../Dynamic/SubModelSelectedItem/App.xaml | 7 + .../Dynamic/SubModelSelectedItem/App.xaml.cs | 18 + .../SubModelSelectedItem/MainWindow.xaml | 5 +- .../SubModelSelectedItem/MainWindow.xaml.cs | 11 + .../SubModelSelectedItem.csproj | 2 +- .../{ => Dynamic}/SubModelSeq.Core/Program.fs | 0 .../SubModelSeq.Core/SubModelSeq.Core.fsproj | 10 +- src/Samples/Dynamic/SubModelSeq/App.xaml | 7 + src/Samples/Dynamic/SubModelSeq/App.xaml.cs | 18 + .../{ => Dynamic}/SubModelSeq/Counter.xaml | 5 +- .../Dynamic/SubModelSeq/Counter.xaml.cs | 11 + .../{ => Dynamic}/SubModelSeq/MainWindow.xaml | 12 +- .../Dynamic/SubModelSeq/MainWindow.xaml.cs | 11 + .../SubModelSeq/SubModelSeq.csproj | 2 +- .../{ => Dynamic}/Threading.Core/Program.fs | 0 .../Threading.Core/Threading.Core.fsproj | 10 +- src/Samples/{ => Dynamic}/Threading/App.xaml | 2 +- src/Samples/Dynamic/Threading/App.xaml.cs | 31 ++ .../{ => Dynamic}/Threading/MainWindow.xaml | 9 +- .../Dynamic/Threading/MainWindow.xaml.cs | 23 ++ .../{ => Dynamic}/Threading/Threading.csproj | 4 +- .../UiBoundCmdParam.Core/Program.fs | 0 .../UiBoundCmdParam.Core.fsproj | 10 +- src/Samples/Dynamic/UiBoundCmdParam/App.xaml | 7 + .../Dynamic/UiBoundCmdParam/App.xaml.cs | 18 + .../UiBoundCmdParam/MainWindow.xaml | 5 +- .../UiBoundCmdParam/MainWindow.xaml.cs | 11 + .../UiBoundCmdParam/UiBoundCmdParam.csproj | 2 +- .../{ => Dynamic}/Validation.Core/Program.fs | 0 .../Validation.Core/Validation.Core.fsproj | 10 +- src/Samples/Dynamic/Validation/App.xaml | 7 + src/Samples/Dynamic/Validation/App.xaml.cs | 18 + .../{ => Dynamic}/Validation/MainWindow.xaml | 24 +- .../Dynamic/Validation/MainWindow.xaml.cs | 11 + .../Validation/Validation.csproj | 2 +- .../EventBindingsAndBehaviors/App.xaml | 7 - .../EventBindingsAndBehaviors/App.xaml.cs | 20 - .../FocusWhenVisibleBehavior.cs | 29 -- .../MainWindow.xaml.cs | 12 - src/Samples/FileDialogs/App.xaml | 7 - src/Samples/FileDialogs/App.xaml.cs | 20 - src/Samples/FileDialogs/MainWindow.xaml.cs | 12 - src/Samples/FileDialogsCmdMsg/App.xaml | 7 - src/Samples/FileDialogsCmdMsg/App.xaml.cs | 20 - .../FileDialogsCmdMsg/MainWindow.xaml.cs | 12 - src/Samples/Multiselect/App.xaml.cs | 15 - src/Samples/Multiselect/MainWindow.xaml.cs | 9 - .../NewWindow.Core/NewWindow.Core.fsproj | 29 -- src/Samples/NewWindow/App.xaml | 8 - src/Samples/NewWindow/App.xaml.cs | 20 - src/Samples/NewWindow/MainWindow.xaml.cs | 17 - src/Samples/NewWindow/Window1.xaml.cs | 12 - src/Samples/NewWindow/Window2.xaml.cs | 12 - src/Samples/OneWaySeq/App.xaml | 7 - src/Samples/OneWaySeq/App.xaml.cs | 20 - src/Samples/OneWaySeq/MainWindow.xaml.cs | 12 - src/Samples/README.md | 93 +++++ src/Samples/SingleCounter/App.xaml | 7 - src/Samples/SingleCounter/App.xaml.cs | 20 - src/Samples/SingleCounter/MainWindow.xaml.cs | 12 - src/Samples/Sticky/App.xaml.cs | 20 - src/Samples/Sticky/MainWindow.xaml.cs | 11 - src/Samples/SubModel/App.xaml | 7 - src/Samples/SubModel/App.xaml.cs | 20 - src/Samples/SubModel/Clock.xaml.cs | 12 - src/Samples/SubModel/Counter.xaml.cs | 12 - src/Samples/SubModel/CounterWithClock.xaml.cs | 12 - src/Samples/SubModel/MainWindow.xaml.cs | 12 - src/Samples/SubModelOpt/App.xaml | 7 - src/Samples/SubModelOpt/App.xaml.cs | 20 - src/Samples/SubModelOpt/Form1.xaml.cs | 12 - src/Samples/SubModelOpt/Form2.xaml.cs | 12 - src/Samples/SubModelOpt/MainWindow.xaml.cs | 17 - src/Samples/SubModelSelectedItem/App.xaml | 7 - src/Samples/SubModelSelectedItem/App.xaml.cs | 20 - .../SubModelSelectedItem/MainWindow.xaml.cs | 12 - src/Samples/SubModelSeq/App.xaml | 7 - src/Samples/SubModelSeq/App.xaml.cs | 20 - src/Samples/SubModelSeq/Counter.xaml.cs | 9 - src/Samples/SubModelSeq/MainWindow.xaml.cs | 12 - src/Samples/SubModelStatic/App.xaml | 7 - src/Samples/SubModelStatic/App.xaml.cs | 20 - src/Samples/SubModelStatic/Clock.xaml.cs | 12 - src/Samples/SubModelStatic/Counter.xaml.cs | 12 - .../SubModelStatic/CounterWithClock.xaml.cs | 12 - src/Samples/SubModelStatic/MainWindow.xaml.cs | 12 - src/Samples/Threading/App.xaml.cs | 28 -- src/Samples/Threading/MainWindow.xaml.cs | 23 -- .../SubModelStatic.Core/Program.fs | 0 .../SubModelStatic.Core.fsproj | 10 +- src/Samples/Typed/SubModelStatic/App.xaml | 7 + src/Samples/Typed/SubModelStatic/App.xaml.cs | 18 + .../{ => Typed}/SubModelStatic/Clock.xaml | 10 +- .../Typed/SubModelStatic/Clock.xaml.cs | 11 + .../{ => Typed}/SubModelStatic/Counter.xaml | 29 +- .../Typed/SubModelStatic/Counter.xaml.cs | 11 + .../SubModelStatic/CounterWithClock.xaml | 2 +- .../SubModelStatic/CounterWithClock.xaml.cs | 11 + .../SubModelStatic/MainWindow.xaml | 8 +- .../Typed/SubModelStatic/MainWindow.xaml.cs | 11 + .../SubModelStatic/SubModelStatic.csproj | 2 +- src/Samples/UiBoundCmdParam/App.xaml | 7 - src/Samples/UiBoundCmdParam/App.xaml.cs | 20 - .../UiBoundCmdParam/MainWindow.xaml.cs | 12 - src/Samples/Validation/App.xaml | 7 - src/Samples/Validation/App.xaml.cs | 20 - src/Samples/Validation/MainWindow.xaml.cs | 12 - .../Elmish.WPF.Benchmarks.fsproj | 6 +- .../Elmish.WPF.Benchmarks/Program.fs | 0 {src => tests}/Elmish.WPF.Tests/AutoOpen.fs | 0 .../Elmish.WPF.Tests/BindingTests.fs | 0 .../Elmish.WPF.Tests/BindingVmHelpersTests.fs | 0 .../Elmish.WPF.Tests/DynamicViewModelTests.fs | 0 .../Elmish.WPF.Tests/Elmish.WPF.Tests.fsproj | 28 +- {src => tests}/Elmish.WPF.Tests/MergeTests.fs | 0 .../Elmish.WPF.Tests/StaticViewModelTests.fs | 2 - {src => tests}/Elmish.WPF.Tests/UtilsTests.fs | 0 233 files changed, 2082 insertions(+), 1540 deletions(-) create mode 100644 .claude/settings.local.json delete mode 100644 src/Samples/Capabilities/App.xaml.cs delete mode 100644 src/Samples/Capabilities/MainWindow.xaml.cs delete mode 100644 src/Samples/Capabilities/Selection/BindableSelectedValue.cs delete mode 100644 src/Samples/Capabilities/Selection/SelectionScreen.xaml.cs rename src/Samples/{ => Dynamic}/Capabilities.Core/Capabilities.Core.fsproj (64%) rename src/Samples/{ => Dynamic}/Capabilities.Core/Program.fs (100%) rename src/Samples/{ => Dynamic}/Capabilities.Core/Selection.fs (100%) rename src/Samples/{ => Dynamic}/Capabilities.Core/Utilities.fs (100%) rename src/Samples/{ => Dynamic}/Capabilities/App.xaml (89%) create mode 100644 src/Samples/Dynamic/Capabilities/App.xaml.cs rename src/Samples/{ => Dynamic}/Capabilities/Capabilities.csproj (93%) rename src/Samples/{ => Dynamic}/Capabilities/MainWindow.xaml (91%) create mode 100644 src/Samples/Dynamic/Capabilities/MainWindow.xaml.cs create mode 100644 src/Samples/Dynamic/Capabilities/Selection/BindableSelectedValue.cs rename src/Samples/{ => Dynamic}/Capabilities/Selection/SelectionScreen.xaml (68%) create mode 100644 src/Samples/Dynamic/Capabilities/Selection/SelectionScreen.xaml.cs rename src/Samples/{ => Dynamic}/EventBindingsAndBehaviors.Core/EventBindingsAndBehaviors.Core.fsproj (72%) rename src/Samples/{ => Dynamic}/EventBindingsAndBehaviors.Core/Program.fs (100%) create mode 100644 src/Samples/Dynamic/EventBindingsAndBehaviors/App.xaml create mode 100644 src/Samples/Dynamic/EventBindingsAndBehaviors/App.xaml.cs rename src/Samples/{ => Dynamic}/EventBindingsAndBehaviors/EventBindingsAndBehaviors.csproj (88%) create mode 100644 src/Samples/Dynamic/EventBindingsAndBehaviors/FocusWhenVisibleBehavior.cs rename src/Samples/{ => Dynamic}/EventBindingsAndBehaviors/MainWindow.xaml (83%) create mode 100644 src/Samples/Dynamic/EventBindingsAndBehaviors/MainWindow.xaml.cs rename src/Samples/{ => Dynamic}/FileDialogs.Core/FileDialogs.Core.fsproj (72%) rename src/Samples/{ => Dynamic}/FileDialogs.Core/Program.fs (100%) create mode 100644 src/Samples/Dynamic/FileDialogs/App.xaml create mode 100644 src/Samples/Dynamic/FileDialogs/App.xaml.cs rename src/Samples/{ => Dynamic}/FileDialogs/FileDialogs.csproj (96%) rename src/Samples/{ => Dynamic}/FileDialogs/MainWindow.xaml (91%) create mode 100644 src/Samples/Dynamic/FileDialogs/MainWindow.xaml.cs rename src/Samples/{ => Dynamic}/FileDialogsCmdMsg.Core/FileDialogsCmdMsg.Core.fsproj (72%) rename src/Samples/{ => Dynamic}/FileDialogsCmdMsg.Core/Program.fs (100%) create mode 100644 src/Samples/Dynamic/FileDialogsCmdMsg/App.xaml create mode 100644 src/Samples/Dynamic/FileDialogsCmdMsg/App.xaml.cs rename src/Samples/{ => Dynamic}/FileDialogsCmdMsg/FileDialogsCmdMsg.csproj (93%) rename src/Samples/{ => Dynamic}/FileDialogsCmdMsg/MainWindow.xaml (91%) create mode 100644 src/Samples/Dynamic/FileDialogsCmdMsg/MainWindow.xaml.cs rename src/Samples/{ => Dynamic}/Multiselect.Core/Multiselect.Core.fsproj (72%) rename src/Samples/{ => Dynamic}/Multiselect.Core/Program.fs (100%) rename src/Samples/{ => Dynamic}/Multiselect/App.xaml (95%) create mode 100644 src/Samples/Dynamic/Multiselect/App.xaml.cs rename src/Samples/{ => Dynamic}/Multiselect/MainWindow.xaml (83%) create mode 100644 src/Samples/Dynamic/Multiselect/MainWindow.xaml.cs rename src/Samples/{ => Dynamic}/Multiselect/Multiselect.csproj (96%) rename src/Samples/{ => Dynamic}/NewWindow.Core/App.fs (100%) rename src/Samples/{ => Dynamic}/NewWindow.Core/AutoOpen.fs (100%) create mode 100644 src/Samples/Dynamic/NewWindow.Core/NewWindow.Core.fsproj rename src/Samples/{ => Dynamic}/NewWindow.Core/Program.fs (100%) rename src/Samples/{ => Dynamic}/NewWindow.Core/Window1.fs (100%) rename src/Samples/{ => Dynamic}/NewWindow.Core/Window2.fs (100%) create mode 100644 src/Samples/Dynamic/NewWindow/App.xaml create mode 100644 src/Samples/Dynamic/NewWindow/App.xaml.cs rename src/Samples/{ => Dynamic}/NewWindow/MainWindow.xaml (86%) create mode 100644 src/Samples/Dynamic/NewWindow/MainWindow.xaml.cs rename src/Samples/{ => Dynamic}/NewWindow/NewWindow.csproj (94%) rename src/Samples/{ => Dynamic}/NewWindow/Window1.xaml (100%) create mode 100644 src/Samples/Dynamic/NewWindow/Window1.xaml.cs rename src/Samples/{ => Dynamic}/NewWindow/Window2.xaml (94%) create mode 100644 src/Samples/Dynamic/NewWindow/Window2.xaml.cs rename src/Samples/{ => Dynamic}/OneWaySeq.Core/OneWaySeq.Core.fsproj (72%) rename src/Samples/{ => Dynamic}/OneWaySeq.Core/Program.fs (100%) create mode 100644 src/Samples/Dynamic/OneWaySeq/App.xaml create mode 100644 src/Samples/Dynamic/OneWaySeq/App.xaml.cs rename src/Samples/{ => Dynamic}/OneWaySeq/MainWindow.xaml (79%) create mode 100644 src/Samples/Dynamic/OneWaySeq/MainWindow.xaml.cs rename src/Samples/{ => Dynamic}/OneWaySeq/OneWaySeq.csproj (97%) rename src/Samples/{ => Dynamic}/SingleCounter.Core/Program.fs (100%) rename src/Samples/{ => Dynamic}/SingleCounter.Core/SingleCounter.Core.fsproj (72%) create mode 100644 src/Samples/Dynamic/SingleCounter/App.xaml create mode 100644 src/Samples/Dynamic/SingleCounter/App.xaml.cs rename src/Samples/{ => Dynamic}/SingleCounter/MainWindow.xaml (92%) create mode 100644 src/Samples/Dynamic/SingleCounter/MainWindow.xaml.cs rename src/Samples/{ => Dynamic}/SingleCounter/SingleCounter.csproj (95%) rename src/Samples/{ => Dynamic}/Sticky.Core/Program.fs (100%) rename src/Samples/{ => Dynamic}/Sticky.Core/Sticky.Core.fsproj (72%) rename src/Samples/{ => Dynamic}/Sticky/App.xaml (100%) create mode 100644 src/Samples/Dynamic/Sticky/App.xaml.cs rename src/Samples/{ => Dynamic}/Sticky/MainWindow.xaml (92%) create mode 100644 src/Samples/Dynamic/Sticky/MainWindow.xaml.cs rename src/Samples/{ => Dynamic}/Sticky/Sticky.csproj (98%) rename src/Samples/{ => Dynamic}/SubModel.Core/Program.fs (100%) rename src/Samples/{ => Dynamic}/SubModel.Core/SubModel.Core.fsproj (72%) create mode 100644 src/Samples/Dynamic/SubModel/App.xaml create mode 100644 src/Samples/Dynamic/SubModel/App.xaml.cs rename src/Samples/{ => Dynamic}/SubModel/Clock.xaml (72%) create mode 100644 src/Samples/Dynamic/SubModel/Clock.xaml.cs rename src/Samples/{ => Dynamic}/SubModel/Counter.xaml (92%) create mode 100644 src/Samples/Dynamic/SubModel/Counter.xaml.cs rename src/Samples/{ => Dynamic}/SubModel/CounterWithClock.xaml (59%) create mode 100644 src/Samples/Dynamic/SubModel/CounterWithClock.xaml.cs rename src/Samples/{ => Dynamic}/SubModel/MainWindow.xaml (68%) create mode 100644 src/Samples/Dynamic/SubModel/MainWindow.xaml.cs rename src/Samples/{ => Dynamic}/SubModel/SubModel.csproj (97%) rename src/Samples/{ => Dynamic}/SubModelOpt.Core/Program.fs (100%) rename src/Samples/{ => Dynamic}/SubModelOpt.Core/SubModelOpt.Core.fsproj (72%) create mode 100644 src/Samples/Dynamic/SubModelOpt/App.xaml create mode 100644 src/Samples/Dynamic/SubModelOpt/App.xaml.cs rename src/Samples/{ => Dynamic}/SubModelOpt/Form1.xaml (88%) create mode 100644 src/Samples/Dynamic/SubModelOpt/Form1.xaml.cs rename src/Samples/{ => Dynamic}/SubModelOpt/Form2.xaml (98%) create mode 100644 src/Samples/Dynamic/SubModelOpt/Form2.xaml.cs rename src/Samples/{ => Dynamic}/SubModelOpt/MainWindow.xaml (68%) create mode 100644 src/Samples/Dynamic/SubModelOpt/MainWindow.xaml.cs rename src/Samples/{ => Dynamic}/SubModelOpt/SubModelOpt.csproj (96%) rename src/Samples/{ => Dynamic}/SubModelSelectedItem.Core/Program.fs (100%) rename src/Samples/{ => Dynamic}/SubModelSelectedItem.Core/SubModelSelectedItem.Core.fsproj (72%) create mode 100644 src/Samples/Dynamic/SubModelSelectedItem/App.xaml create mode 100644 src/Samples/Dynamic/SubModelSelectedItem/App.xaml.cs rename src/Samples/{ => Dynamic}/SubModelSelectedItem/MainWindow.xaml (94%) create mode 100644 src/Samples/Dynamic/SubModelSelectedItem/MainWindow.xaml.cs rename src/Samples/{ => Dynamic}/SubModelSelectedItem/SubModelSelectedItem.csproj (91%) rename src/Samples/{ => Dynamic}/SubModelSeq.Core/Program.fs (100%) rename src/Samples/{ => Dynamic}/SubModelSeq.Core/SubModelSeq.Core.fsproj (72%) create mode 100644 src/Samples/Dynamic/SubModelSeq/App.xaml create mode 100644 src/Samples/Dynamic/SubModelSeq/App.xaml.cs rename src/Samples/{ => Dynamic}/SubModelSeq/Counter.xaml (92%) create mode 100644 src/Samples/Dynamic/SubModelSeq/Counter.xaml.cs rename src/Samples/{ => Dynamic}/SubModelSeq/MainWindow.xaml (91%) create mode 100644 src/Samples/Dynamic/SubModelSeq/MainWindow.xaml.cs rename src/Samples/{ => Dynamic}/SubModelSeq/SubModelSeq.csproj (96%) rename src/Samples/{ => Dynamic}/Threading.Core/Program.fs (100%) rename src/Samples/{ => Dynamic}/Threading.Core/Threading.Core.fsproj (72%) rename src/Samples/{ => Dynamic}/Threading/App.xaml (89%) create mode 100644 src/Samples/Dynamic/Threading/App.xaml.cs rename src/Samples/{ => Dynamic}/Threading/MainWindow.xaml (87%) create mode 100644 src/Samples/Dynamic/Threading/MainWindow.xaml.cs rename src/Samples/{ => Dynamic}/Threading/Threading.csproj (94%) rename src/Samples/{ => Dynamic}/UiBoundCmdParam.Core/Program.fs (100%) rename src/Samples/{ => Dynamic}/UiBoundCmdParam.Core/UiBoundCmdParam.Core.fsproj (72%) create mode 100644 src/Samples/Dynamic/UiBoundCmdParam/App.xaml create mode 100644 src/Samples/Dynamic/UiBoundCmdParam/App.xaml.cs rename src/Samples/{ => Dynamic}/UiBoundCmdParam/MainWindow.xaml (93%) create mode 100644 src/Samples/Dynamic/UiBoundCmdParam/MainWindow.xaml.cs rename src/Samples/{ => Dynamic}/UiBoundCmdParam/UiBoundCmdParam.csproj (94%) rename src/Samples/{ => Dynamic}/Validation.Core/Program.fs (100%) rename src/Samples/{ => Dynamic}/Validation.Core/Validation.Core.fsproj (72%) create mode 100644 src/Samples/Dynamic/Validation/App.xaml create mode 100644 src/Samples/Dynamic/Validation/App.xaml.cs rename src/Samples/{ => Dynamic}/Validation/MainWindow.xaml (73%) create mode 100644 src/Samples/Dynamic/Validation/MainWindow.xaml.cs rename src/Samples/{ => Dynamic}/Validation/Validation.csproj (96%) delete mode 100644 src/Samples/EventBindingsAndBehaviors/App.xaml delete mode 100644 src/Samples/EventBindingsAndBehaviors/App.xaml.cs delete mode 100644 src/Samples/EventBindingsAndBehaviors/FocusWhenVisibleBehavior.cs delete mode 100644 src/Samples/EventBindingsAndBehaviors/MainWindow.xaml.cs delete mode 100644 src/Samples/FileDialogs/App.xaml delete mode 100644 src/Samples/FileDialogs/App.xaml.cs delete mode 100644 src/Samples/FileDialogs/MainWindow.xaml.cs delete mode 100644 src/Samples/FileDialogsCmdMsg/App.xaml delete mode 100644 src/Samples/FileDialogsCmdMsg/App.xaml.cs delete mode 100644 src/Samples/FileDialogsCmdMsg/MainWindow.xaml.cs delete mode 100644 src/Samples/Multiselect/App.xaml.cs delete mode 100644 src/Samples/Multiselect/MainWindow.xaml.cs delete mode 100644 src/Samples/NewWindow.Core/NewWindow.Core.fsproj delete mode 100644 src/Samples/NewWindow/App.xaml delete mode 100644 src/Samples/NewWindow/App.xaml.cs delete mode 100644 src/Samples/NewWindow/MainWindow.xaml.cs delete mode 100644 src/Samples/NewWindow/Window1.xaml.cs delete mode 100644 src/Samples/NewWindow/Window2.xaml.cs delete mode 100644 src/Samples/OneWaySeq/App.xaml delete mode 100644 src/Samples/OneWaySeq/App.xaml.cs delete mode 100644 src/Samples/OneWaySeq/MainWindow.xaml.cs create mode 100644 src/Samples/README.md delete mode 100644 src/Samples/SingleCounter/App.xaml delete mode 100644 src/Samples/SingleCounter/App.xaml.cs delete mode 100644 src/Samples/SingleCounter/MainWindow.xaml.cs delete mode 100644 src/Samples/Sticky/App.xaml.cs delete mode 100644 src/Samples/Sticky/MainWindow.xaml.cs delete mode 100644 src/Samples/SubModel/App.xaml delete mode 100644 src/Samples/SubModel/App.xaml.cs delete mode 100644 src/Samples/SubModel/Clock.xaml.cs delete mode 100644 src/Samples/SubModel/Counter.xaml.cs delete mode 100644 src/Samples/SubModel/CounterWithClock.xaml.cs delete mode 100644 src/Samples/SubModel/MainWindow.xaml.cs delete mode 100644 src/Samples/SubModelOpt/App.xaml delete mode 100644 src/Samples/SubModelOpt/App.xaml.cs delete mode 100644 src/Samples/SubModelOpt/Form1.xaml.cs delete mode 100644 src/Samples/SubModelOpt/Form2.xaml.cs delete mode 100644 src/Samples/SubModelOpt/MainWindow.xaml.cs delete mode 100644 src/Samples/SubModelSelectedItem/App.xaml delete mode 100644 src/Samples/SubModelSelectedItem/App.xaml.cs delete mode 100644 src/Samples/SubModelSelectedItem/MainWindow.xaml.cs delete mode 100644 src/Samples/SubModelSeq/App.xaml delete mode 100644 src/Samples/SubModelSeq/App.xaml.cs delete mode 100644 src/Samples/SubModelSeq/Counter.xaml.cs delete mode 100644 src/Samples/SubModelSeq/MainWindow.xaml.cs delete mode 100644 src/Samples/SubModelStatic/App.xaml delete mode 100644 src/Samples/SubModelStatic/App.xaml.cs delete mode 100644 src/Samples/SubModelStatic/Clock.xaml.cs delete mode 100644 src/Samples/SubModelStatic/Counter.xaml.cs delete mode 100644 src/Samples/SubModelStatic/CounterWithClock.xaml.cs delete mode 100644 src/Samples/SubModelStatic/MainWindow.xaml.cs delete mode 100644 src/Samples/Threading/App.xaml.cs delete mode 100644 src/Samples/Threading/MainWindow.xaml.cs rename src/Samples/{ => Typed}/SubModelStatic.Core/Program.fs (100%) rename src/Samples/{ => Typed}/SubModelStatic.Core/SubModelStatic.Core.fsproj (72%) create mode 100644 src/Samples/Typed/SubModelStatic/App.xaml create mode 100644 src/Samples/Typed/SubModelStatic/App.xaml.cs rename src/Samples/{ => Typed}/SubModelStatic/Clock.xaml (73%) create mode 100644 src/Samples/Typed/SubModelStatic/Clock.xaml.cs rename src/Samples/{ => Typed}/SubModelStatic/Counter.xaml (91%) create mode 100644 src/Samples/Typed/SubModelStatic/Counter.xaml.cs rename src/Samples/{ => Typed}/SubModelStatic/CounterWithClock.xaml (98%) create mode 100644 src/Samples/Typed/SubModelStatic/CounterWithClock.xaml.cs rename src/Samples/{ => Typed}/SubModelStatic/MainWindow.xaml (86%) create mode 100644 src/Samples/Typed/SubModelStatic/MainWindow.xaml.cs rename src/Samples/{ => Typed}/SubModelStatic/SubModelStatic.csproj (94%) delete mode 100644 src/Samples/UiBoundCmdParam/App.xaml delete mode 100644 src/Samples/UiBoundCmdParam/App.xaml.cs delete mode 100644 src/Samples/UiBoundCmdParam/MainWindow.xaml.cs delete mode 100644 src/Samples/Validation/App.xaml delete mode 100644 src/Samples/Validation/App.xaml.cs delete mode 100644 src/Samples/Validation/MainWindow.xaml.cs rename {src => tests}/Elmish.WPF.Benchmarks/Elmish.WPF.Benchmarks.fsproj (76%) rename {src => tests}/Elmish.WPF.Benchmarks/Program.fs (100%) rename {src => tests}/Elmish.WPF.Tests/AutoOpen.fs (100%) rename {src => tests}/Elmish.WPF.Tests/BindingTests.fs (100%) rename {src => tests}/Elmish.WPF.Tests/BindingVmHelpersTests.fs (100%) rename {src => tests}/Elmish.WPF.Tests/DynamicViewModelTests.fs (100%) rename {src => tests}/Elmish.WPF.Tests/Elmish.WPF.Tests.fsproj (64%) rename {src => tests}/Elmish.WPF.Tests/MergeTests.fs (100%) rename {src => tests}/Elmish.WPF.Tests/StaticViewModelTests.fs (99%) rename {src => tests}/Elmish.WPF.Tests/UtilsTests.fs (100%) diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 00000000..6d280cc4 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,10 @@ +{ + "permissions": { + "allow": [ + "Bash(mv:*)", + "Bash(mkdir:*)", + "Bash(cp:*)" + ], + "deny": [] + } +} \ No newline at end of file diff --git a/Elmish.WPF.slnx b/Elmish.WPF.slnx index 94d7e7b5..51df4b22 100644 --- a/Elmish.WPF.slnx +++ b/Elmish.WPF.slnx @@ -1,50 +1,58 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - \ No newline at end of file diff --git a/README.md b/README.md index e3497c09..04041f2a 100644 --- a/README.md +++ b/README.md @@ -5,11 +5,13 @@ WPF done the Elmish Way [![NuGet version](https://img.shields.io/nuget/v/Elmish.WPF.svg)](https://www.nuget.org/packages/Elmish.WPF) [![NuGet downloads](https://img.shields.io/nuget/dt/Elmish.WPF.svg)](https://www.nuget.org/packages/Elmish.WPF) [![Build status](https://github.com/elmish/Elmish.WPF/actions/workflows/continuous_integration.yml/badge.svg)](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. Never write an overly-complex ViewModel class again!** ### 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: @@ -22,7 +24,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 +39,40 @@ Big thanks to [@MrMattSim](https://github.com/MrMattSim) for the wonderful logo! [![JetBrains logo](jetbrains.svg)](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. * 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): +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): 1. Create an F# Class Library. If targeting .NET 5 or .NET Core, the project file should look like this: @@ -111,7 +128,10 @@ See the [SingleCounter](https://github.com/elmish/Elmish.WPF/tree/master/src/Sam 6. 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,10 +147,10 @@ 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. - - Alternatively, use statically-typed view models in order to get better IDE support in the XAML. +Alternatively, use statically-typed view models in order to get better IDE support in the XAML. ```f# open Elmish.WPF @@ -146,7 +166,8 @@ See the [SingleCounter](https://github.com/elmish/Elmish.WPF/tree/master/src/Sam and set(v) = base.Set(v) (Binding.OneWayToSourceT.id >> Binding.mapMsg Counter.Msg.SetStepSize) ``` -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 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`: ```F# open Elmish.WPF @@ -166,13 +187,18 @@ See the [SingleCounter](https://github.com/elmish/Elmish.WPF/tree/master/src/Sam |> Program.runElmishLoop 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 the code above, `Program.runElmishLoop` will set the window’s `DataContext` to the specified bindings and start + the Elmish dispatch loop for the window. -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,9 +240,12 @@ 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. +* 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 --- @@ -225,47 +254,75 @@ FAQ 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) 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 +343,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 +369,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 +388,28 @@ 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). -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). +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 +417,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..88248ac1 100644 --- a/REFERENCE.md +++ b/REFERENCE.md @@ -5,39 +5,38 @@ 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) * [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) + + [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) The Elmish.WPF bindings ---------------------------- @@ -45,19 +44,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 - ([XAML views](src/Samples/SingleCounter) +and [F# core](src/Samples/SingleCounter.Core))* One-way bindings are used when you want to bind to a simple value. @@ -77,13 +82,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 - ([XAML views](src/Samples/SingleCounter) +and [F# core](src/Samples/SingleCounter.Core))* -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 +119,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 +140,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))* -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 - ([XAML views](src/Samples/SingleCounter) +and [F# core](src/Samples/SingleCounter.Core))* 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 - + @@ -30,4 +31,4 @@ - + \ No newline at end of file diff --git a/src/Samples/Dynamic/UiBoundCmdParam/MainWindow.xaml.cs b/src/Samples/Dynamic/UiBoundCmdParam/MainWindow.xaml.cs new file mode 100644 index 00000000..260d1b51 --- /dev/null +++ b/src/Samples/Dynamic/UiBoundCmdParam/MainWindow.xaml.cs @@ -0,0 +1,11 @@ +using System.Windows; + +namespace Elmish.WPF.Samples.UiBoundCmdParam; + +public partial class MainWindow : Window +{ + public MainWindow() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/src/Samples/UiBoundCmdParam/UiBoundCmdParam.csproj b/src/Samples/Dynamic/UiBoundCmdParam/UiBoundCmdParam.csproj similarity index 94% rename from src/Samples/UiBoundCmdParam/UiBoundCmdParam.csproj rename to src/Samples/Dynamic/UiBoundCmdParam/UiBoundCmdParam.csproj index 7ed472e8..1396605d 100644 --- a/src/Samples/UiBoundCmdParam/UiBoundCmdParam.csproj +++ b/src/Samples/Dynamic/UiBoundCmdParam/UiBoundCmdParam.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/Samples/Validation.Core/Program.fs b/src/Samples/Dynamic/Validation.Core/Program.fs similarity index 100% rename from src/Samples/Validation.Core/Program.fs rename to src/Samples/Dynamic/Validation.Core/Program.fs diff --git a/src/Samples/Validation.Core/Validation.Core.fsproj b/src/Samples/Dynamic/Validation.Core/Validation.Core.fsproj similarity index 72% rename from src/Samples/Validation.Core/Validation.Core.fsproj rename to src/Samples/Dynamic/Validation.Core/Validation.Core.fsproj index 28983b0f..c406ba6d 100644 --- a/src/Samples/Validation.Core/Validation.Core.fsproj +++ b/src/Samples/Dynamic/Validation.Core/Validation.Core.fsproj @@ -9,17 +9,17 @@ - + - - - + + + - + diff --git a/src/Samples/Dynamic/Validation/App.xaml b/src/Samples/Dynamic/Validation/App.xaml new file mode 100644 index 00000000..8493d19c --- /dev/null +++ b/src/Samples/Dynamic/Validation/App.xaml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/src/Samples/Dynamic/Validation/App.xaml.cs b/src/Samples/Dynamic/Validation/App.xaml.cs new file mode 100644 index 00000000..5c0e560b --- /dev/null +++ b/src/Samples/Dynamic/Validation/App.xaml.cs @@ -0,0 +1,18 @@ +using System; +using System.Windows; + +namespace Elmish.WPF.Samples.Validation; + +public partial class App : Application +{ + public App() + { + Activated += StartElmish; + } + + private void StartElmish(object sender, EventArgs e) + { + Activated -= StartElmish; + Program.main(MainWindow); + } +} \ No newline at end of file diff --git a/src/Samples/Validation/MainWindow.xaml b/src/Samples/Dynamic/Validation/MainWindow.xaml similarity index 73% rename from src/Samples/Validation/MainWindow.xaml rename to src/Samples/Dynamic/Validation/MainWindow.xaml index 67b0115f..d10811a3 100644 --- a/src/Samples/Validation/MainWindow.xaml +++ b/src/Samples/Dynamic/Validation/MainWindow.xaml @@ -17,15 +17,16 @@ * - + - + - + @@ -35,17 +36,20 @@ - - + + - - + + - - + +