├── .editorconfig
├── .gitattributes
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
└── workflows
│ ├── dotnet-desktop.yml
│ └── release.yml
├── .gitignore
├── Directory.Build.props
├── LICENSE.txt
├── Lemon.ModuleNavigation.sln
├── README.md
├── breakdownchanges.md
├── lemon-100.png
├── samples
├── Lemon.ModuleNavigation.Sample.Browser
│ ├── Lemon.ModuleNavigation.Sample.Browser.csproj
│ ├── Program.cs
│ ├── Properties
│ │ └── launchSettings.json
│ ├── runtimeconfig.template.json
│ └── wwwroot
│ │ ├── app.css
│ │ ├── favicon.ico
│ │ ├── index.html
│ │ └── main.js
├── Lemon.ModuleNavigation.Sample.Desktop
│ ├── Lemon.ModuleNavigation.Sample.Desktop.csproj
│ ├── Program.cs
│ ├── Properties
│ │ └── launchSettings.json
│ └── app.manifest
├── Lemon.ModuleNavigation.Sample.DesktopHosting
│ ├── Lemon.ModuleNavigation.Sample.DesktopHosting.csproj
│ ├── Program.cs
│ ├── Properties
│ │ └── launchSettings.json
│ ├── app.manifest
│ └── rd.xml
├── Lemon.ModuleNavigation.Sample
│ ├── App.axaml
│ ├── App.axaml.cs
│ ├── AppWithDI.axaml.cs
│ ├── AppWithDi.axaml
│ ├── Assets
│ │ ├── avalonia-logo.ico
│ │ ├── lemon-100.ico
│ │ ├── lemon-100.png
│ │ ├── lemon-28.png
│ │ └── lemon-50.png
│ ├── DesignDatas
│ │ └── MainWindowViewModel.cs
│ ├── DialogWindows
│ │ ├── CustomDialogWindow.axaml
│ │ └── CustomDialogWindow.axaml.cs
│ ├── Lemon.ModuleNavigation.Sample.csproj
│ ├── ModuleAs
│ │ ├── ModuleA.cs
│ │ ├── ViewA.axaml
│ │ ├── ViewA.axaml.cs
│ │ └── ViewModelA.cs
│ ├── ModuleBs
│ │ ├── ModuleB.cs
│ │ ├── ViewB.axaml
│ │ ├── ViewB.axaml.cs
│ │ └── ViewModelB.cs
│ ├── ModuleCs
│ │ ├── ModuleC.cs
│ │ ├── SubModules
│ │ │ ├── SubModule01.cs
│ │ │ └── SubModule02.cs
│ │ ├── ViewC.axaml
│ │ ├── ViewC.axaml.cs
│ │ ├── ViewModelC.cs
│ │ ├── ViewModels
│ │ │ ├── SubViewModel01.cs
│ │ │ └── SubViewModel02.cs
│ │ └── Views
│ │ │ ├── SubView01.axaml
│ │ │ ├── SubView01.axaml.cs
│ │ │ ├── SubView02.axaml
│ │ │ └── SubView02.axaml.cs
│ ├── ViewModels
│ │ └── MainViewModel.cs
│ └── Views
│ │ ├── MainView.axaml
│ │ ├── MainView.axaml.cs
│ │ ├── MainWindow.axaml
│ │ ├── MainWindow.axaml.cs
│ │ ├── ViewAlpha.axaml
│ │ ├── ViewAlpha.axaml.cs
│ │ ├── ViewBeta.axaml
│ │ └── ViewBeta.axaml.cs
├── Lemon.ModuleNavigation.SampleViewModel
│ ├── BaseNavigationViewModel.cs
│ ├── Lemon.ModuleNavigation.SampleViewModel.csproj
│ ├── MainWindowViewModel.cs
│ ├── ViewAlphaViewModel.cs
│ └── ViewBetaViewModel.cs
└── Lemon.ModuleNavigation.WpfSample
│ ├── App.xaml
│ ├── App.xaml.cs
│ ├── AssemblyInfo.cs
│ ├── BoolToVisibilityConverter.cs
│ ├── Lemon.ModuleNavigation.WpfSample.csproj
│ ├── MainWindow.xaml
│ ├── MainWindow.xaml.cs
│ └── Views
│ ├── ViewAlpha.xaml
│ ├── ViewAlpha.xaml.cs
│ ├── ViewBeta.xaml
│ └── ViewBeta.xaml.cs
└── src
├── Lemon.ModuleNavigation.Avaloniaui
├── AssemblyInfo.cs
├── AvaModule{TView,TViewModel}.cs
├── DefaultDialogWindow.axaml
├── DefaultDialogWindow.axaml.cs
├── DialogService.cs
├── Extensions
│ ├── AvaloniauiExtensions.cs
│ └── ServiceCollectionExtensions.cs
├── IDialogWindow.cs
├── Lemon.ModuleNavigation.Avaloniaui.csproj
├── NavigationExtension.cs
├── Regions
│ ├── ContentRegion.cs
│ ├── ItemsRegion.cs
│ ├── Region.cs
│ └── TabRegion.cs
├── RegionsExtension.cs
└── RegionsOld
│ ├── ContentRegion.cs
│ ├── ItemsRegion.cs
│ ├── RegionBak.cs
│ └── TabRegion.cs
├── Lemon.ModuleNavigation.Wpf
├── AssemblyInfo.cs
├── DefaultDialogWindow.xaml
├── DefaultDialogWindow.xaml.cs
├── DialogService.cs
├── Extensions
│ ├── ServiceCollectionExtensions.cs
│ └── WpfExtensions.cs
├── IDialogWindow.cs
├── Lemon.ModuleNavigation.Wpf.csproj
├── NavigationExtension.cs
└── Regions
│ ├── ContentRegion.cs
│ ├── ItemsRegion.cs
│ ├── Region.cs
│ └── TabRegion.cs
├── Lemon.ModuleNavigation
├── Abstractions
│ ├── BaseParameters.cs
│ ├── IContentRegionContext{TDataTemplate}.cs
│ ├── IDialogAware.cs
│ ├── IDialogParameters.cs
│ ├── IDialogResult.cs
│ ├── IDialogService.cs
│ ├── IDialogWindow.cs
│ ├── IItemsRegionDataContext{TDataTemplate}.cs
│ ├── IModule.cs
│ ├── IModuleManager.cs
│ ├── IModuleNavigationAware.cs
│ ├── IModuleNavigationHandler.cs
│ ├── IModuleNavigationHandler{T}.cs
│ ├── IModuleNavigationService.cs
│ ├── IModuleNavigationService{T}.cs
│ ├── IModuleScope.cs
│ ├── IModuleServiceProvider.cs
│ ├── INavigationAware.cs
│ ├── INavigationHandler.cs
│ ├── INavigationService.cs
│ ├── IRegion.cs
│ ├── IRegionManager.cs
│ ├── IServiceAware.cs
│ ├── IServiceProviderDecorator.cs
│ ├── IView.cs
│ ├── IViewManager.cs
│ ├── IViewNavigationHandler.cs
│ └── IViewNavigationService.cs
├── Core
│ ├── ButtonResult.cs
│ ├── ConcurrentItem.cs
│ ├── DialogParameters.cs
│ ├── DialogResult.cs
│ ├── NavigationParameters.cs
│ └── RegionNameNotFoundException.cs
├── Extensions
│ ├── ListKeyValuePairExtensions.cs
│ ├── ObservableExtension.cs
│ └── ServiceCollectionExtensions.cs
├── Internal
│ ├── ConcurrentSet.cs
│ ├── DisposableAction.cs
│ └── ModuleServiceProvider.cs
├── Lemon.ModuleNavigation.csproj
├── ModuleManager.cs
├── Module{TView,TViewModel}.cs
├── NavigationContext.cs
├── NavigationHandler.cs
├── NavigationService.cs
├── RegionManager.cs
├── ServiceProviderDecorator.cs
├── ViewDiscription.cs
├── ViewId.cs
└── ViewManager.cs
└── Package.props
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.cs]
2 |
3 | # IDE0290: Use primary constructor
4 | dotnet_diagnostic.IDE0290.severity = none
5 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Environment(please complete the following information):**
27 | - OS: [e.g. Windows 11 ,linux ubuntu 20.04.6]
28 | - Nuget:[e.g. 1.0.0]
29 |
30 | **Additional context**
31 | Add any other context about the problem here.
32 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/workflows/dotnet-desktop.yml:
--------------------------------------------------------------------------------
1 | name: .NET
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | paths-ignore:
8 | - 'README.md'
9 | pull_request:
10 | branches:
11 | - master
12 | paths-ignore:
13 | - 'README.md'
14 |
15 | jobs:
16 | build-avaloniaui:
17 | runs-on: ${{ matrix.os }}
18 | strategy:
19 | matrix:
20 | os: [windows-2022, ubuntu-22.04, macos-13]
21 | steps:
22 | - uses: actions/checkout@v3
23 | - name: Setup .NET 8
24 | uses: actions/setup-dotnet@v3
25 | with:
26 | dotnet-version: 8.0.x
27 | - name: Install dependencies
28 | run: dotnet restore
29 | working-directory: samples/Lemon.ModuleNavigation.Sample.Desktop
30 | - name: Build
31 | run: dotnet build --configuration Release --no-restore
32 | working-directory: samples/Lemon.ModuleNavigation.Sample.Desktop
33 | build-wpf:
34 | runs-on: windows-2022
35 | steps:
36 | - uses: actions/checkout@v3
37 | - name: Setup .NET 8
38 | uses: actions/setup-dotnet@v3
39 | with:
40 | dotnet-version: 8.0.x
41 | - name: Install dependencies
42 | run: dotnet restore
43 | working-directory: samples/Lemon.ModuleNavigation.WpfSample
44 | - name: Build WPF Sample
45 | run: dotnet build --configuration Release --no-restore
46 | working-directory: samples/Lemon.ModuleNavigation.WpfSample
47 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Pack Nuget
2 |
3 | on:
4 | push:
5 | branches: [ "action/release" ]
6 | pull_request:
7 | branches: [ "action/release" ]
8 |
9 | jobs:
10 | nuget-abstraction:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - name: Checkout
14 | uses: actions/checkout@v4.1.1
15 |
16 | - name: Build the Project
17 | run: dotnet build ./src/Lemon.ModuleNavigation --configuration Release
18 |
19 | - name: Pack Nuget
20 | run: dotnet pack ./src/Lemon.ModuleNavigation --configuration Release --no-build -o ./nugets
21 |
22 | - name: Publish NuGet package
23 | run: |
24 | if [ -n "$(find ./nugets -name '*.nupkg')" ]; then
25 | dotnet nuget push "./nugets/*.nupkg" --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate
26 | fi
27 |
28 | - name: Upload a Build Artifact
29 | uses: actions/upload-artifact@v4.3.1
30 | with:
31 | name: nugets-abstraction
32 | path: ./nugets
33 |
34 | nuget-avaloniaui:
35 | runs-on: ubuntu-latest
36 | steps:
37 | - name: Checkout
38 | uses: actions/checkout@v4.1.1
39 |
40 | - name: Build the Project
41 | run: dotnet build ./src/Lemon.ModuleNavigation.Avaloniaui --configuration Release
42 |
43 | - name: Pack Nuget
44 | run: dotnet pack ./src/Lemon.ModuleNavigation.Avaloniaui --configuration Release --no-build -o ./nugets
45 |
46 | - name: Publish NuGet package
47 | run: |
48 | if [ -n "$(find ./nugets -name '*.nupkg')" ]; then
49 | dotnet nuget push "./nugets/*.nupkg" --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate
50 | fi
51 |
52 | - name: Upload a Build Artifact
53 | uses: actions/upload-artifact@v4.3.1
54 | with:
55 | name: nugets-avaloniaui
56 | path: ./nugets
57 |
58 | nuget-wpf:
59 | runs-on: windows-latest
60 | steps:
61 | - name: Checkout
62 | uses: actions/checkout@v4.1.1
63 |
64 | - name: Setup .NET 8
65 | uses: actions/setup-dotnet@v3
66 | with:
67 | dotnet-version: 8.0.x
68 |
69 | - name: Build the WPF Project
70 | run: dotnet build ./src/Lemon.ModuleNavigation.Wpf --configuration Release
71 |
72 | - name: Pack WPF Nuget
73 | run: dotnet pack ./src/Lemon.ModuleNavigation.Wpf --configuration Release --no-build -o ./nugets
74 |
75 | - name: Publish WPF NuGet package
76 | run: |
77 | if (Test-Path "./nugets/*.nupkg") {
78 | dotnet nuget push ".\nugets\*.nupkg" --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate
79 | }
80 |
81 | - name: Upload WPF NuGet Artifact
82 | uses: actions/upload-artifact@v4.3.1
83 | with:
84 | name: nugets-wpf
85 | path: ./nugets
86 |
--------------------------------------------------------------------------------
/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 | enable
4 | 11.1.0
5 |
6 |
7 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) [year] [fullname]
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Lemon.ModuleNavigation.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.11.35312.102
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lemon.ModuleNavigation", "src\Lemon.ModuleNavigation\Lemon.ModuleNavigation.csproj", "{9E271C3B-0D09-4318-BF49-4BE2952759D8}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{6E1EE4BE-93AB-4A7D-B080-E054855DDEA6}"
9 | ProjectSection(SolutionItems) = preProject
10 | .editorconfig = .editorconfig
11 | EndProjectSection
12 | EndProject
13 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lemon.ModuleNavigation.Avaloniaui", "src\Lemon.ModuleNavigation.Avaloniaui\Lemon.ModuleNavigation.Avaloniaui.csproj", "{3EDBF55F-6D3E-4BD0-A726-4BA84461C687}"
14 | EndProject
15 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lemon.ModuleNavigation.Sample", "samples\Lemon.ModuleNavigation.Sample\Lemon.ModuleNavigation.Sample.csproj", "{0CCB72E9-102B-41E2-80A4-67D580041508}"
16 | EndProject
17 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lemon.ModuleNavigation.Sample.DesktopHosting", "samples\Lemon.ModuleNavigation.Sample.DesktopHosting\Lemon.ModuleNavigation.Sample.DesktopHosting.csproj", "{48509E81-7077-402B-A208-2134A7476857}"
18 | EndProject
19 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lemon.ModuleNavigation.Sample.Desktop", "samples\Lemon.ModuleNavigation.Sample.Desktop\Lemon.ModuleNavigation.Sample.Desktop.csproj", "{AF18E2B0-7ED3-4BDA-81BA-6A23B3C89F39}"
20 | EndProject
21 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lemon.ModuleNavigation.Sample.Browser", "samples\Lemon.ModuleNavigation.Sample.Browser\Lemon.ModuleNavigation.Sample.Browser.csproj", "{FDD2C508-3201-4659-8962-886C2CBF41BD}"
22 | EndProject
23 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lemon.ModuleNavigation.Wpf", "src\Lemon.ModuleNavigation.Wpf\Lemon.ModuleNavigation.Wpf.csproj", "{2AE59E5E-900D-5CCB-0C30-F8DFAD324C9E}"
24 | EndProject
25 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lemon.ModuleNavigation.WpfSample", "samples\Lemon.ModuleNavigation.WpfSample\Lemon.ModuleNavigation.WpfSample.csproj", "{8EC545BD-E076-A2DF-A7A4-2F11221AF297}"
26 | EndProject
27 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lemon.ModuleNavigation.SampleViewModel", "samples\Lemon.ModuleNavigation.SampleViewModel\Lemon.ModuleNavigation.SampleViewModel.csproj", "{99600F69-5901-4CEA-8C54-BFDC0751E207}"
28 | EndProject
29 | Global
30 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
31 | Debug|Any CPU = Debug|Any CPU
32 | Release|Any CPU = Release|Any CPU
33 | EndGlobalSection
34 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
35 | {9E271C3B-0D09-4318-BF49-4BE2952759D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
36 | {9E271C3B-0D09-4318-BF49-4BE2952759D8}.Debug|Any CPU.Build.0 = Debug|Any CPU
37 | {9E271C3B-0D09-4318-BF49-4BE2952759D8}.Release|Any CPU.ActiveCfg = Release|Any CPU
38 | {9E271C3B-0D09-4318-BF49-4BE2952759D8}.Release|Any CPU.Build.0 = Release|Any CPU
39 | {3EDBF55F-6D3E-4BD0-A726-4BA84461C687}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
40 | {3EDBF55F-6D3E-4BD0-A726-4BA84461C687}.Debug|Any CPU.Build.0 = Debug|Any CPU
41 | {3EDBF55F-6D3E-4BD0-A726-4BA84461C687}.Release|Any CPU.ActiveCfg = Release|Any CPU
42 | {3EDBF55F-6D3E-4BD0-A726-4BA84461C687}.Release|Any CPU.Build.0 = Release|Any CPU
43 | {0CCB72E9-102B-41E2-80A4-67D580041508}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
44 | {0CCB72E9-102B-41E2-80A4-67D580041508}.Debug|Any CPU.Build.0 = Debug|Any CPU
45 | {0CCB72E9-102B-41E2-80A4-67D580041508}.Release|Any CPU.ActiveCfg = Release|Any CPU
46 | {0CCB72E9-102B-41E2-80A4-67D580041508}.Release|Any CPU.Build.0 = Release|Any CPU
47 | {48509E81-7077-402B-A208-2134A7476857}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
48 | {48509E81-7077-402B-A208-2134A7476857}.Debug|Any CPU.Build.0 = Debug|Any CPU
49 | {48509E81-7077-402B-A208-2134A7476857}.Release|Any CPU.ActiveCfg = Release|Any CPU
50 | {48509E81-7077-402B-A208-2134A7476857}.Release|Any CPU.Build.0 = Release|Any CPU
51 | {AF18E2B0-7ED3-4BDA-81BA-6A23B3C89F39}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
52 | {AF18E2B0-7ED3-4BDA-81BA-6A23B3C89F39}.Debug|Any CPU.Build.0 = Debug|Any CPU
53 | {AF18E2B0-7ED3-4BDA-81BA-6A23B3C89F39}.Release|Any CPU.ActiveCfg = Release|Any CPU
54 | {AF18E2B0-7ED3-4BDA-81BA-6A23B3C89F39}.Release|Any CPU.Build.0 = Release|Any CPU
55 | {FDD2C508-3201-4659-8962-886C2CBF41BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
56 | {FDD2C508-3201-4659-8962-886C2CBF41BD}.Debug|Any CPU.Build.0 = Debug|Any CPU
57 | {FDD2C508-3201-4659-8962-886C2CBF41BD}.Release|Any CPU.ActiveCfg = Release|Any CPU
58 | {FDD2C508-3201-4659-8962-886C2CBF41BD}.Release|Any CPU.Build.0 = Release|Any CPU
59 | {2AE59E5E-900D-5CCB-0C30-F8DFAD324C9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
60 | {2AE59E5E-900D-5CCB-0C30-F8DFAD324C9E}.Debug|Any CPU.Build.0 = Debug|Any CPU
61 | {2AE59E5E-900D-5CCB-0C30-F8DFAD324C9E}.Release|Any CPU.ActiveCfg = Release|Any CPU
62 | {2AE59E5E-900D-5CCB-0C30-F8DFAD324C9E}.Release|Any CPU.Build.0 = Release|Any CPU
63 | {8EC545BD-E076-A2DF-A7A4-2F11221AF297}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
64 | {8EC545BD-E076-A2DF-A7A4-2F11221AF297}.Debug|Any CPU.Build.0 = Debug|Any CPU
65 | {8EC545BD-E076-A2DF-A7A4-2F11221AF297}.Release|Any CPU.ActiveCfg = Release|Any CPU
66 | {8EC545BD-E076-A2DF-A7A4-2F11221AF297}.Release|Any CPU.Build.0 = Release|Any CPU
67 | {99600F69-5901-4CEA-8C54-BFDC0751E207}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
68 | {99600F69-5901-4CEA-8C54-BFDC0751E207}.Debug|Any CPU.Build.0 = Debug|Any CPU
69 | {99600F69-5901-4CEA-8C54-BFDC0751E207}.Release|Any CPU.ActiveCfg = Release|Any CPU
70 | {99600F69-5901-4CEA-8C54-BFDC0751E207}.Release|Any CPU.Build.0 = Release|Any CPU
71 | EndGlobalSection
72 | GlobalSection(SolutionProperties) = preSolution
73 | HideSolutionNode = FALSE
74 | EndGlobalSection
75 | GlobalSection(ExtensibilityGlobals) = postSolution
76 | SolutionGuid = {D8F466EB-6501-49CC-94ED-77E9348E1831}
77 | EndGlobalSection
78 | EndGlobal
79 |
--------------------------------------------------------------------------------
/breakdownchanges.md:
--------------------------------------------------------------------------------
1 | ### remove namespaces:
2 | - Lemon.ModuleNavigation.Avaloniaui.Extensions;
3 | - Lemon.ModuleNavigation.Avaloniaui.Dialogs;
4 | original members were pulled to Lemon.ModuleNavigation.Avaloniaui.Core;
5 |
6 | ### rename namespaces:
7 | - Lemon.ModuleNavigation.Abstracts to Lemon.ModuleNavigation.Abstractions;
--------------------------------------------------------------------------------
/lemon-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NeverMorewd/Lemon.ModuleNavigation/7bab73f18a8a70db3b7686c45cd0aa1647d983dc/lemon-100.png
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.Sample.Browser/Lemon.ModuleNavigation.Sample.Browser.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net8.0-browser
4 | Exe
5 | true
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.Sample.Browser/Program.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Browser;
3 | using Avalonia.ReactiveUI;
4 | using Lemon.ModuleNavigation.Sample;
5 | using System.Runtime.Versioning;
6 | using System.Threading.Tasks;
7 |
8 | [assembly: SupportedOSPlatform("browser")]
9 |
10 | internal sealed partial class Program
11 | {
12 | private static Task Main(string[] args) => BuildAvaloniaApp()
13 | .WithInterFont()
14 | .UseReactiveUI()
15 | .StartBrowserAppAsync("out");
16 |
17 | public static AppBuilder BuildAvaloniaApp()
18 | => AppBuilder.Configure();
19 | }
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.Sample.Browser/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "Lemon.ModuleNavigation.Sample.Browser": {
4 | "commandName": "Project",
5 | "launchBrowser": true,
6 | "environmentVariables": {
7 | "ASPNETCORE_ENVIRONMENT": "Development"
8 | },
9 | "applicationUrl": "https://localhost:9991;http://localhost:9990",
10 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}"
11 | }
12 | }
13 | }
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.Sample.Browser/runtimeconfig.template.json:
--------------------------------------------------------------------------------
1 | {
2 | "wasmHostProperties": {
3 | "perHostConfig": [
4 | {
5 | "name": "browser",
6 | "html-path": "index.html",
7 | "Host": "browser"
8 | }
9 | ]
10 | }
11 | }
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.Sample.Browser/wwwroot/app.css:
--------------------------------------------------------------------------------
1 | /* HTML styles for the splash screen */
2 | .avalonia-splash {
3 | position: absolute;
4 | height: 100%;
5 | width: 100%;
6 | background: white;
7 | font-family: 'Outfit', sans-serif;
8 | justify-content: center;
9 | align-items: center;
10 | display: flex;
11 | pointer-events: none;
12 | }
13 |
14 | /* Light theme styles */
15 | @media (prefers-color-scheme: light) {
16 | .avalonia-splash {
17 | background: white;
18 | }
19 |
20 | .avalonia-splash h2 {
21 | color: #1b2a4e;
22 | }
23 |
24 | .avalonia-splash a {
25 | color: #0D6EFD;
26 | }
27 | }
28 |
29 | @media (prefers-color-scheme: dark) {
30 | .avalonia-splash {
31 | background: #1b2a4e;
32 | }
33 |
34 | .avalonia-splash h2 {
35 | color: white;
36 | }
37 |
38 | .avalonia-splash a {
39 | color: white;
40 | }
41 | }
42 |
43 | .avalonia-splash h2 {
44 | font-weight: 400;
45 | font-size: 1.5rem;
46 | }
47 |
48 | .avalonia-splash a {
49 | text-decoration: none;
50 | font-size: 2.5rem;
51 | display: block;
52 | }
53 |
54 | .avalonia-splash.splash-close {
55 | transition: opacity 200ms, display 200ms;
56 | display: none;
57 | opacity: 0;
58 | }
59 |
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.Sample.Browser/wwwroot/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NeverMorewd/Lemon.ModuleNavigation/7bab73f18a8a70db3b7686c45cd0aa1647d983dc/samples/Lemon.ModuleNavigation.Sample.Browser/wwwroot/favicon.ico
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.Sample.Browser/wwwroot/main.js:
--------------------------------------------------------------------------------
1 | import { dotnet } from './_framework/dotnet.js'
2 |
3 | const is_browser = typeof window != "undefined";
4 | if (!is_browser) throw new Error(`Expected to be running in a browser`);
5 |
6 | const dotnetRuntime = await dotnet
7 | .withDiagnosticTracing(false)
8 | .withApplicationArgumentsFromQuery()
9 | .create();
10 |
11 | const config = dotnetRuntime.getConfig();
12 |
13 | await dotnetRuntime.runMain(config.mainAssemblyName, [globalThis.location.href]);
14 |
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.Sample.Desktop/Lemon.ModuleNavigation.Sample.Desktop.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | WinExe
4 |
6 | net8.0
7 | enable
8 | true
9 | app.manifest
10 |
12 |
13 |
14 | true
15 | true
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.Sample.Desktop/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Avalonia;
3 | using Avalonia.ReactiveUI;
4 |
5 | namespace Lemon.ModuleNavigation.Sample.Desktop;
6 |
7 | public static class Program
8 | {
9 | [STAThread]
10 | public static void Main(string[] args) => BuildAvaloniaApp()
11 | .StartWithClassicDesktopLifetime(args);
12 |
13 | // Avalonia configuration, don't remove; also used by visual designer.
14 | private static AppBuilder BuildAvaloniaApp()
15 | => AppBuilder.Configure()
16 | .UsePlatformDetect()
17 | .WithInterFont()
18 | .LogToTrace()
19 | .UseReactiveUI();
20 | }
21 |
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.Sample.Desktop/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "Lemon.ModuleNavigation.Sample.Desktop": {
4 | "commandName": "Project"
5 | },
6 | "WSL": {
7 | "commandName": "WSL2",
8 | "distributionName": ""
9 | }
10 | }
11 | }
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.Sample.Desktop/app.manifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
10 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.Sample.DesktopHosting/Lemon.ModuleNavigation.Sample.DesktopHosting.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | WinExe
4 |
6 | net8.0
7 | enable
8 | true
9 | app.manifest
10 |
12 |
13 |
14 | true
15 | true
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.Sample.DesktopHosting/Program.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.ReactiveUI;
3 | using Lemon.Hosting.AvaloniauiDesktop;
4 | using Lemon.ModuleNavigation.Sample.ModuleAs;
5 | using Lemon.ModuleNavigation.Sample.ModuleBs;
6 | using Lemon.ModuleNavigation.Sample.ModuleCs;
7 | using Lemon.ModuleNavigation.Sample.ViewModels;
8 | using Lemon.ModuleNavigation.Sample.Views;
9 | using Lemon.ModuleNavigation.SampleViewModel;
10 | using Microsoft.Extensions.DependencyInjection;
11 | using Microsoft.Extensions.Hosting;
12 | using Microsoft.Extensions.Logging;
13 | using System;
14 | using System.Runtime.Versioning;
15 |
16 | namespace Lemon.ModuleNavigation.Sample.DesktopHosting;
17 |
18 | internal static class Program
19 | {
20 | [STAThread]
21 | [SupportedOSPlatform("windows")]
22 | [SupportedOSPlatform("linux")]
23 | [SupportedOSPlatform("macos")]
24 | public static void Main(string[] args)
25 | {
26 | var hostBuilder = Host.CreateApplicationBuilder();
27 |
28 | // module navigation
29 | hostBuilder.Services.AddAvaNavigationSupport();
30 | hostBuilder.Logging.ClearProviders();
31 | hostBuilder.Logging.AddConsole();
32 | hostBuilder.Logging.SetMinimumLevel(LogLevel.Debug);
33 | // modules
34 | hostBuilder.Services.AddModule();
35 | hostBuilder.Services.AddModule();
36 | hostBuilder.Services.AddModule();
37 | // views
38 | hostBuilder.Services.AddView(nameof(ViewAlpha));
39 | hostBuilder.Services.AddView(nameof(ViewBeta));
40 | hostBuilder.Services.AddAvaDialogWindow(nameof(CustomDialogWindow));
41 |
42 | hostBuilder.Services.AddAvaloniauiDesktopApplication(BuildAvaloniaApp);
43 | hostBuilder.Services.AddMainWindow();
44 | var appHost = hostBuilder.Build();
45 | appHost.RunAvaloniauiApplication(args);
46 | }
47 |
48 | private static AppBuilder BuildAvaloniaApp(AppBuilder appBuilder)
49 | {
50 | return appBuilder
51 | .UsePlatformDetect()
52 | .WithInterFont()
53 | .LogToTrace()
54 | .UseReactiveUI();
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.Sample.DesktopHosting/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "Lemon.ModuleNavigation.Sample.DesktopHosting": {
4 | "commandName": "Project"
5 | },
6 | "WSL": {
7 | "commandName": "WSL2",
8 | "distributionName": ""
9 | }
10 | }
11 | }
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.Sample.DesktopHosting/app.manifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
10 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.Sample.DesktopHosting/rd.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.Sample/App.axaml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.Sample/App.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Markup.Xaml;
3 |
4 | namespace Lemon.ModuleNavigation.Sample;
5 |
6 | public partial class App : Application
7 | {
8 | public override void Initialize()
9 | {
10 | AvaloniaXamlLoader.Load(this);
11 | }
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.Sample/AppWithDI.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Controls.ApplicationLifetimes;
3 | using Avalonia.Markup.Xaml;
4 | using Lemon.ModuleNavigation.Sample.ModuleAs;
5 | using Lemon.ModuleNavigation.Sample.ModuleBs;
6 | using Lemon.ModuleNavigation.Sample.ModuleCs;
7 | using Lemon.ModuleNavigation.Sample.ViewModels;
8 | using Lemon.ModuleNavigation.Sample.Views;
9 | using Lemon.ModuleNavigation.SampleViewModel;
10 | using Microsoft.Extensions.DependencyInjection;
11 | using System;
12 |
13 | namespace Lemon.ModuleNavigation.Sample;
14 |
15 | public partial class AppWithDi : Application
16 | {
17 | public IServiceProvider? AppServiceProvider { get; private set; }
18 |
19 | public override void Initialize()
20 | {
21 | AvaloniaXamlLoader.Load(this);
22 | }
23 |
24 | public override void OnFrameworkInitializationCompleted()
25 | {
26 | var services = new ServiceCollection();
27 | services.AddLogging();
28 | services.AddAvaNavigationSupport()
29 | .AddModule()
30 | .AddModule()
31 | .AddModule()
32 | .AddSingleton()
33 | .AddSingleton()
34 | .AddSingleton()
35 | .AddAvaDialogWindow(nameof(CustomDialogWindow))
36 | .AddView(nameof(ViewAlpha))
37 | .AddView(nameof(ViewBeta));
38 | AppServiceProvider = services.BuildServiceProvider();
39 |
40 | var viewModel = AppServiceProvider.GetRequiredService();
41 |
42 | switch (ApplicationLifetime)
43 | {
44 | case IClassicDesktopStyleApplicationLifetime desktop:
45 | var window = AppServiceProvider.GetRequiredService();
46 | window.DataContext = viewModel;
47 | desktop.MainWindow = window;
48 | break;
49 | case ISingleViewApplicationLifetime singleView:
50 | var view = AppServiceProvider.GetRequiredService();
51 | view.DataContext = viewModel;
52 | singleView.MainView = view;
53 | break;
54 | }
55 | base.OnFrameworkInitializationCompleted();
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.Sample/AppWithDi.axaml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.Sample/Assets/avalonia-logo.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NeverMorewd/Lemon.ModuleNavigation/7bab73f18a8a70db3b7686c45cd0aa1647d983dc/samples/Lemon.ModuleNavigation.Sample/Assets/avalonia-logo.ico
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.Sample/Assets/lemon-100.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NeverMorewd/Lemon.ModuleNavigation/7bab73f18a8a70db3b7686c45cd0aa1647d983dc/samples/Lemon.ModuleNavigation.Sample/Assets/lemon-100.ico
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.Sample/Assets/lemon-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NeverMorewd/Lemon.ModuleNavigation/7bab73f18a8a70db3b7686c45cd0aa1647d983dc/samples/Lemon.ModuleNavigation.Sample/Assets/lemon-100.png
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.Sample/Assets/lemon-28.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NeverMorewd/Lemon.ModuleNavigation/7bab73f18a8a70db3b7686c45cd0aa1647d983dc/samples/Lemon.ModuleNavigation.Sample/Assets/lemon-28.png
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.Sample/Assets/lemon-50.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NeverMorewd/Lemon.ModuleNavigation/7bab73f18a8a70db3b7686c45cd0aa1647d983dc/samples/Lemon.ModuleNavigation.Sample/Assets/lemon-50.png
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.Sample/DesignDatas/MainWindowViewModel.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Lemon.ModuleNavigation.Sample.ViewModels;
3 | using Microsoft.Extensions.DependencyInjection;
4 |
5 | namespace Lemon.ModuleNavigation.Sample.DesignDatas;
6 |
7 | public static class DesignData
8 | {
9 | public static MainViewModel MainWindowViewModel { get; } =
10 | ((AppWithDi)Application.Current!).AppServiceProvider!.GetRequiredService();
11 | }
12 |
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.Sample/DialogWindows/CustomDialogWindow.axaml:
--------------------------------------------------------------------------------
1 |
8 | Welcome to Avalonia!
9 |
10 |
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.Sample/DialogWindows/CustomDialogWindow.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls;
2 | using Lemon.ModuleNavigation.Avaloniaui;
3 |
4 | namespace Lemon.ModuleNavigation.Sample;
5 |
6 | public partial class CustomDialogWindow : Window, IDialogWindow
7 | {
8 | public CustomDialogWindow()
9 | {
10 | InitializeComponent();
11 | }
12 | }
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.Sample/Lemon.ModuleNavigation.Sample.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net8.0
4 | enable
5 | latest
6 | Assets\avalonia-logo.ico
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | SubView01.axaml
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.Sample/ModuleAs/ModuleA.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Lemon.ModuleNavigation.Sample.ModuleAs;
4 |
5 | public class ModuleA : Module
6 | {
7 | public ModuleA(IServiceProvider serviceProvider) : base(serviceProvider)
8 | {
9 |
10 | }
11 |
12 | ///
13 | /// Specifies whether this module needs to be loaded on demand
14 | /// Default value is True
15 | ///
16 | public override bool LoadOnDemand => false;
17 |
18 | ///
19 | /// Alias of module for displaying usually
20 | /// Default value is class name of Module
21 | ///
22 | public override string? Alias => base.Alias;
23 |
24 | ///
25 | /// Specifies whether this module allow multiple instances
26 | /// If true,every navigation to this module will generate a new instance.
27 | /// Default value is false.
28 | ///
29 | public override bool ForceNew => base.ForceNew;
30 |
31 | ///
32 | /// Specifies whether this module can be unloaded.
33 | /// Default value is false.
34 | ///
35 | public override bool CanUnload => base.CanUnload;
36 |
37 | ///
38 | /// Initialize
39 | ///
40 | public override void Initialize()
41 | {
42 | base.Initialize();
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.Sample/ModuleAs/ViewA.axaml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
13 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.Sample/ModuleAs/ViewA.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls;
2 | using Lemon.ModuleNavigation.Abstractions;
3 |
4 | namespace Lemon.ModuleNavigation.Sample.ModuleAs;
5 |
6 | public partial class ViewA : UserControl, IView
7 | {
8 | public ViewA()
9 | {
10 | InitializeComponent();
11 | }
12 | }
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.Sample/ModuleAs/ViewModelA.cs:
--------------------------------------------------------------------------------
1 | using Lemon.ModuleNavigation.Abstractions;
2 | using Lemon.ModuleNavigation.SampleViewModel;
3 | using ReactiveUI;
4 | using System.Reactive;
5 |
6 | namespace Lemon.ModuleNavigation.Sample.ModuleAs;
7 |
8 | public class ViewModelA : BaseNavigationViewModel, IModuleNavigationAware
9 | {
10 | private readonly NavigationService _navigationService;
11 | public ViewModelA(NavigationService navigationService)
12 | {
13 | _navigationService = navigationService;
14 | NavigateCommand = ReactiveCommand.Create(target =>
15 | {
16 | _navigationService.RequestModuleNavigate(target, null);
17 | });
18 | }
19 | public ReactiveCommand NavigateCommand
20 | {
21 | get;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.Sample/ModuleBs/ModuleB.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Lemon.ModuleNavigation.Sample.ModuleBs;
4 |
5 | public class ModuleB : Module
6 | {
7 | public ModuleB(IServiceProvider serviceProvider) : base(serviceProvider)
8 | {
9 | }
10 |
11 | public override bool LoadOnDemand => true;
12 | }
13 |
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.Sample/ModuleBs/ViewB.axaml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
13 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.Sample/ModuleBs/ViewB.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls;
2 | using Lemon.ModuleNavigation.Abstractions;
3 |
4 | namespace Lemon.ModuleNavigation.Sample.ModuleBs;
5 |
6 | public partial class ViewB : UserControl, IView
7 | {
8 | public ViewB()
9 | {
10 | InitializeComponent();
11 | }
12 | }
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.Sample/ModuleBs/ViewModelB.cs:
--------------------------------------------------------------------------------
1 | using Lemon.ModuleNavigation.Abstractions;
2 | using Lemon.ModuleNavigation.SampleViewModel;
3 | using ReactiveUI;
4 | using System.Reactive;
5 |
6 | namespace Lemon.ModuleNavigation.Sample.ModuleBs;
7 |
8 | public class ViewModelB : BaseNavigationViewModel, IModuleNavigationAware
9 | {
10 | private readonly NavigationService _navigationService;
11 | public ViewModelB(NavigationService navigationService)
12 | {
13 | _navigationService = navigationService;
14 | NavigateCommand = ReactiveCommand.Create(target =>
15 | {
16 | _navigationService.RequestModuleNavigate(target, null);
17 | });
18 | }
19 | public ReactiveCommand NavigateCommand
20 | {
21 | get;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.Sample/ModuleCs/ModuleC.cs:
--------------------------------------------------------------------------------
1 | using Lemon.ModuleNavigation.Abstractions;
2 | using Lemon.ModuleNavigation.Sample.ModuleCs.SubModules;
3 | using Microsoft.Extensions.DependencyInjection;
4 | using Microsoft.Extensions.Logging;
5 | using System;
6 | using System.Collections.Generic;
7 | using ILogger = Microsoft.Extensions.Logging.ILogger;
8 |
9 | namespace Lemon.ModuleNavigation.Sample.ModuleCs;
10 |
11 | public class ModuleC : Module, IModuleScope
12 | {
13 | private readonly IServiceProvider _subServiceProvider;
14 | private readonly ILogger _logger;
15 | public ModuleC(IServiceProvider serviceProvider, ILogger logger) : base(serviceProvider)
16 | {
17 | _logger = logger;
18 |
19 | ScopeServiceCollection = new ServiceCollection();
20 | ScopeServiceCollection.AddAppServiceProvider(serviceProvider);
21 | ScopeServiceCollection.AddModule();
22 | ScopeServiceCollection.AddModule();
23 | ScopeServiceCollection.AddAvaNavigationSupport();
24 |
25 | _subServiceProvider = ScopeServiceCollection.BuildServiceProvider();
26 | ScopeServiceProvider = _subServiceProvider;
27 | }
28 | public IServiceCollection ScopeServiceCollection
29 | {
30 | get;
31 | }
32 | public IServiceProvider ScopeServiceProvider
33 | {
34 | get;
35 | }
36 | public override bool LoadOnDemand => true;
37 | public override bool ForceNew => true;
38 | public override string Alias => $"{base.Alias}:{nameof(ForceNew)}";
39 |
40 | public override void Initialize()
41 | {
42 | base.Initialize();
43 | Console.WriteLine($"Initialize:{nameof(ModuleC)}");
44 | var subModules = _subServiceProvider.GetRequiredService>();
45 | foreach (var subModule in subModules)
46 | {
47 | _logger.LogInformation(subModule.Key);
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.Sample/ModuleCs/SubModules/SubModule01.cs:
--------------------------------------------------------------------------------
1 | using Lemon.ModuleNavigation.Sample.ModuleCs.ViewModels;
2 | using Lemon.ModuleNavigation.Sample.ModuleCs.Views;
3 | using System;
4 |
5 | namespace Lemon.ModuleNavigation.Sample.ModuleCs.SubModules;
6 |
7 | public class SubModule01 : Module
8 | {
9 | public SubModule01(IServiceProvider serviceProvider) : base(serviceProvider)
10 | {
11 |
12 | }
13 |
14 | public override bool LoadOnDemand => false;
15 | }
16 |
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.Sample/ModuleCs/SubModules/SubModule02.cs:
--------------------------------------------------------------------------------
1 | using Lemon.ModuleNavigation.Sample.ModuleCs.ViewModels;
2 | using Lemon.ModuleNavigation.Sample.ModuleCs.Views;
3 | using System;
4 |
5 | namespace Lemon.ModuleNavigation.Sample.ModuleCs.SubModules
6 | {
7 | public class SubModule02 : Module
8 | {
9 | public SubModule02(IServiceProvider serviceProvider) : base(serviceProvider)
10 | {
11 |
12 | }
13 |
14 | public override bool LoadOnDemand => false;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.Sample/ModuleCs/ViewC.axaml:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
17 |
21 |
22 |
23 |
24 |
25 |
26 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.Sample/ModuleCs/ViewC.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls;
2 | using Lemon.ModuleNavigation.Abstractions;
3 |
4 | namespace Lemon.ModuleNavigation.Sample.ModuleCs;
5 |
6 | public partial class ViewC : UserControl, IView
7 | {
8 | public ViewC()
9 | {
10 | InitializeComponent();
11 | }
12 | }
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.Sample/ModuleCs/ViewModelC.cs:
--------------------------------------------------------------------------------
1 | using Lemon.ModuleNavigation.Abstractions;
2 | using Lemon.ModuleNavigation.SampleViewModel;
3 | using ReactiveUI;
4 | using System;
5 | using System.Reactive;
6 |
7 | namespace Lemon.ModuleNavigation.Sample.ModuleCs
8 | {
9 | public class ViewModelC : BaseNavigationViewModel, IModuleNavigationAware, IServiceAware
10 | {
11 | private readonly IModuleNavigationService _navigationService;
12 | private readonly IServiceProvider _moduleServiceProvider;
13 | public ViewModelC(IModuleNavigationService navigationService,
14 | IModuleServiceProvider moduleServiceProvider)
15 | {
16 | _navigationService = navigationService;
17 | _moduleServiceProvider = moduleServiceProvider;
18 | NavigateCommand = ReactiveCommand.Create(target =>
19 | {
20 | _navigationService.RequestModuleNavigate(target, null);
21 | });
22 | }
23 |
24 | public ReactiveCommand NavigateCommand
25 | {
26 | get;
27 | }
28 |
29 | public IServiceProvider ServiceProvider => _moduleServiceProvider;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.Sample/ModuleCs/ViewModels/SubViewModel01.cs:
--------------------------------------------------------------------------------
1 | using Lemon.ModuleNavigation.Abstractions;
2 | using Lemon.ModuleNavigation.SampleViewModel;
3 | using Microsoft.Extensions.DependencyInjection;
4 | using Microsoft.Extensions.Logging;
5 | using System;
6 |
7 | namespace Lemon.ModuleNavigation.Sample.ModuleCs.ViewModels;
8 |
9 | public class SubViewModel01 : BaseNavigationViewModel, IModuleNavigationAware
10 | {
11 | private readonly ILogger _logger;
12 | public SubViewModel01(IServiceProvider serviceProvider, IServiceProviderDecorator appServiceProvider)
13 | {
14 | _logger = appServiceProvider.GetRequiredService>();
15 | _logger.LogDebug("SubViewModel01");
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.Sample/ModuleCs/ViewModels/SubViewModel02.cs:
--------------------------------------------------------------------------------
1 | using Lemon.ModuleNavigation.Abstractions;
2 | using Lemon.ModuleNavigation.SampleViewModel;
3 | using System;
4 |
5 | namespace Lemon.ModuleNavigation.Sample.ModuleCs.ViewModels;
6 |
7 | public class SubViewModel02 : BaseNavigationViewModel, IModuleNavigationAware
8 | {
9 | public SubViewModel02(IServiceProvider serviceProvider)
10 | {
11 |
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.Sample/ModuleCs/Views/SubView01.axaml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.Sample/ModuleCs/Views/SubView01.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Controls;
3 | using Avalonia.Markup.Xaml;
4 | using Lemon.ModuleNavigation.Abstractions;
5 |
6 | namespace Lemon.ModuleNavigation.Sample.ModuleCs.Views;
7 |
8 | public partial class SubView01 : UserControl, IView
9 | {
10 | public SubView01()
11 | {
12 | InitializeComponent();
13 | }
14 | }
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.Sample/ModuleCs/Views/SubView02.axaml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.Sample/ModuleCs/Views/SubView02.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls;
2 | using Lemon.ModuleNavigation.Abstractions;
3 |
4 | namespace Lemon.ModuleNavigation.Sample.ModuleCs.Views;
5 |
6 | public partial class SubView02 : UserControl, IView
7 | {
8 | public SubView02()
9 | {
10 | InitializeComponent();
11 | }
12 | }
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.Sample/ViewModels/MainViewModel.cs:
--------------------------------------------------------------------------------
1 | using Lemon.ModuleNavigation.Abstractions;
2 | using Lemon.ModuleNavigation.Core;
3 | using Lemon.ModuleNavigation.Extensions;
4 | using Microsoft.Extensions.Logging;
5 | using ReactiveUI;
6 | using System;
7 | using System.Collections.Generic;
8 | using System.Collections.ObjectModel;
9 | using System.Reactive;
10 |
11 | namespace Lemon.ModuleNavigation.Sample.ViewModels;
12 |
13 | public class MainViewModel : ReactiveObject, IServiceAware
14 | {
15 | private readonly NavigationService _navigationService;
16 | private readonly IServiceProvider _serviceProvider;
17 | private readonly IDialogService _dialogService;
18 | private readonly IRegionManager _regionManager;
19 | private readonly ILogger _logger;
20 | public MainViewModel(IEnumerable modules,
21 | IServiceProvider serviceProvider,
22 | NavigationService navigationService,
23 | IDialogService dialogService,
24 | IRegionManager regionManager,
25 | ILogger logger)
26 | {
27 | _logger = logger;
28 | _navigationService = navigationService;
29 | _serviceProvider = serviceProvider;
30 | _dialogService = dialogService;
31 | _regionManager = regionManager;
32 | // default views for different regions
33 | _navigationService.RequestViewNavigation("ContentRegion", "ViewAlpha");
34 | _navigationService.RequestViewNavigation("TransitioningContentRegion", "ViewAlpha");
35 | Modules = [.. modules];
36 | ToViewCommand = ReactiveCommand.Create(content =>
37 | {
38 | var viewName = content;
39 | var requestNew = false;
40 | if (content.EndsWith(".RequestNew"))
41 | {
42 | viewName = content.Replace(".RequestNew",string.Empty);
43 | requestNew = true;
44 | }
45 | _navigationService.RequestViewNavigation("ContentRegion",
46 | viewName,
47 | new NavigationParameters { { "requestNew", requestNew } });
48 | _navigationService.RequestViewNavigation("TabRegion",
49 | viewName,
50 | new NavigationParameters { { "requestNew", requestNew } },
51 | $"alias-{viewName}");
52 | _navigationService.RequestViewNavigation("ItemsRegion",
53 | viewName,
54 | new NavigationParameters { { "requestNew", requestNew } });
55 | _navigationService.RequestViewNavigation("TransitioningContentRegion",
56 | viewName,
57 | new NavigationParameters { { "requestNew", requestNew } });
58 | });
59 | ShowCommand = ReactiveCommand.Create(content =>
60 | {
61 | var param = new DialogParameters
62 | {
63 | { "parent", nameof(MainViewModel) }
64 | };
65 | _dialogService.Show(content, null, param, result =>
66 | {
67 | _logger.LogDebug($"Call back:{result}");
68 | });
69 | _logger.LogDebug($"Show over!");
70 | });
71 |
72 | ShowDialogCommand = ReactiveCommand.CreateFromTask(async content =>
73 | {
74 | var param = new DialogParameters
75 | {
76 | { "parent", nameof(MainViewModel) }
77 | };
78 | await _dialogService.ShowDialog(content,
79 | nameof(CustomDialogWindow),
80 | param,
81 | result =>
82 | {
83 | _logger.LogDebug($"Call back:{result}");
84 | });
85 | _logger.LogDebug($"ShowDialog over!");
86 | });
87 |
88 | ShowDialogSyncCommand = ReactiveCommand.Create(content =>
89 | {
90 | var param = new DialogParameters
91 | {
92 | { "parent", nameof(MainViewModel) }
93 | };
94 | var result = _dialogService.WaitShowDialog(content,
95 | nameof(CustomDialogWindow),
96 | param);
97 | _logger.LogDebug($"ShowDialog over:{result}");
98 | });
99 |
100 |
101 | UnloadViewCommand = ReactiveCommand.Create((context) =>
102 | {
103 | _regionManager.RequestViewUnload(context);
104 | });
105 |
106 | _regionManager.NavigationSubscribe(n =>
107 | {
108 | _logger.LogDebug($"Request to : {n.RegionName}.{n.ViewName}");
109 | });
110 | _regionManager.NavigationSubscribe(r =>
111 | {
112 | _logger.LogDebug($"New region : {r.Name}");
113 | });
114 | }
115 | public ReactiveCommand UnloadViewCommand
116 | {
117 | get;
118 | }
119 | public ReactiveCommand ToViewCommand
120 | {
121 | get;
122 | set;
123 | }
124 | public ReactiveCommand ShowCommand
125 | {
126 | get;
127 | set;
128 | }
129 | public ReactiveCommand ShowDialogCommand
130 | {
131 | get;
132 | set;
133 | }
134 | public ReactiveCommand ShowDialogSyncCommand
135 | {
136 | get;
137 | set;
138 | }
139 | public ObservableCollection Modules
140 | {
141 | get;
142 | set;
143 | }
144 |
145 | private IModule? _selectedModule;
146 | public IModule? SelectedModule
147 | {
148 | get => _selectedModule;
149 | set
150 | {
151 | this.RaiseAndSetIfChanged(ref _selectedModule, value);
152 | _navigationService.RequestModuleNavigate(_selectedModule!, null);
153 | }
154 | }
155 | public IServiceProvider ServiceProvider => _serviceProvider;
156 | }
157 |
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.Sample/Views/MainView.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls;
2 |
3 | namespace Lemon.ModuleNavigation.Sample.Views;
4 |
5 | public partial class MainView : UserControl
6 | {
7 | public MainView()
8 | {
9 | InitializeComponent();
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.Sample/Views/MainWindow.axaml:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.Sample/Views/MainWindow.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls;
2 |
3 | namespace Lemon.ModuleNavigation.Sample.Views;
4 |
5 | public partial class MainWindow : Window
6 | {
7 | public MainWindow()
8 | {
9 | InitializeComponent();
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.Sample/Views/ViewAlpha.axaml:
--------------------------------------------------------------------------------
1 |
11 |
16 |
17 |
18 |
24 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.Sample/Views/ViewAlpha.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls;
2 | using Lemon.ModuleNavigation.Abstractions;
3 |
4 | namespace Lemon.ModuleNavigation.Sample.Views;
5 |
6 | public partial class ViewAlpha : UserControl, IView
7 | {
8 | public ViewAlpha()
9 | {
10 | InitializeComponent();
11 | }
12 | }
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.Sample/Views/ViewBeta.axaml:
--------------------------------------------------------------------------------
1 |
10 |
15 |
16 |
17 |
23 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.Sample/Views/ViewBeta.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls;
2 | using Lemon.ModuleNavigation.Abstractions;
3 |
4 | namespace Lemon.ModuleNavigation.Sample.Views;
5 |
6 | public partial class ViewBeta : UserControl, IView
7 | {
8 | public ViewBeta()
9 | {
10 | InitializeComponent();
11 | }
12 | }
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.SampleViewModel/BaseNavigationViewModel.cs:
--------------------------------------------------------------------------------
1 | using Lemon.ModuleNavigation.Abstractions;
2 | using ReactiveUI;
3 | using System.Diagnostics;
4 | using System.Reactive;
5 |
6 | namespace Lemon.ModuleNavigation.SampleViewModel;
7 |
8 | public class BaseNavigationViewModel : ReactiveObject, INavigationAware
9 | {
10 | public virtual string Greeting => $"Welcome to {GetType().Name}[{Environment.ProcessId}][{Environment.CurrentManagedThreadId}]{Environment.NewLine}{DateTime.Now:yyyy-MM-dd HH-mm-ss.ffff}";
11 |
12 | public BaseNavigationViewModel()
13 | {
14 | UnloadViewCommand = ReactiveCommand.Create(() =>
15 | {
16 | var code = this.GetHashCode();
17 | Debug.WriteLine(code);
18 | RequestUnload?.Invoke();
19 | });
20 | }
21 | public ReactiveCommand UnloadViewCommand
22 | {
23 | get;
24 | }
25 |
26 | public event Action? RequestUnload;
27 |
28 | public virtual bool IsNavigationTarget(NavigationContext navigationContext)
29 | {
30 | return true;
31 | }
32 |
33 | public virtual void OnNavigatedFrom(NavigationContext navigationContext)
34 | {
35 | //throw new NotImplementedException();
36 | }
37 |
38 | public virtual void OnNavigatedTo(NavigationContext navigationContext)
39 | {
40 | //throw new NotImplementedException();
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.SampleViewModel/Lemon.ModuleNavigation.SampleViewModel.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0;net6.0
5 | enable
6 | enable
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.SampleViewModel/MainWindowViewModel.cs:
--------------------------------------------------------------------------------
1 | using Lemon.ModuleNavigation.Abstractions;
2 | using Lemon.ModuleNavigation.Core;
3 | using ReactiveUI;
4 | using System.Diagnostics;
5 | using System.Reactive;
6 | using System.Reflection;
7 |
8 | namespace Lemon.ModuleNavigation.SampleViewModel;
9 |
10 | public class MainWindowViewModel : ReactiveObject, IServiceAware
11 | {
12 | private readonly INavigationService _navigationService;
13 | private readonly IDialogService _dialogService;
14 | private readonly IRegionManager _regionManager;
15 | public MainWindowViewModel(INavigationService navigationService,
16 | IDialogService dialogService,
17 | IRegionManager regionManager,
18 | IServiceProvider serviceProvider)
19 | {
20 | _dialogService = dialogService;
21 | _regionManager = regionManager;
22 | _navigationService = navigationService;
23 | _navigationService.RequestViewNavigation("ContentRegion", "ViewAlpha");
24 | ServiceProvider = serviceProvider;
25 | NavigateToViewCommand = ReactiveCommand.Create(content =>
26 | {
27 | var viewName = content;
28 | var requestNew = false;
29 | if (content.EndsWith(".RequestNew"))
30 | {
31 | viewName = content.Replace(".RequestNew", string.Empty);
32 | requestNew = true;
33 |
34 | }
35 | _navigationService.RequestViewNavigation("ContentRegion", viewName, new NavigationParameters { { "requestNew", requestNew } });
36 | _navigationService.RequestViewNavigation("TabRegion", viewName, new NavigationParameters { { "requestNew", requestNew } }, $"alias-{viewName}");
37 | _navigationService.RequestViewNavigation("ItemsRegion", viewName, new NavigationParameters { { "requestNew", requestNew } });
38 | });
39 |
40 | ShowCommand = ReactiveCommand.Create(content =>
41 | {
42 | var param = new DialogParameters
43 | {
44 | { "parent", nameof(MainWindowViewModel) }
45 | };
46 | _dialogService.Show(content, null, param, result =>
47 | {
48 | Debug.WriteLine($"Call back:{result}");
49 | });
50 | Debug.WriteLine($"Show over!");
51 | });
52 |
53 | ShowDialogCommand = ReactiveCommand.CreateFromTask(async content =>
54 | {
55 | var param = new DialogParameters
56 | {
57 | { "parent", nameof(MainWindowViewModel) }
58 | };
59 | await _dialogService.ShowDialog(content,
60 | null,
61 | param,
62 | result =>
63 | {
64 | Debug.WriteLine($"Call back:{result}");
65 | });
66 | Debug.WriteLine($"ShowDialog over!");
67 | });
68 |
69 | ShowDialogSyncCommand = ReactiveCommand.Create(content =>
70 | {
71 | var param = new DialogParameters
72 | {
73 | { "parent", nameof(MainWindowViewModel) }
74 | };
75 | var result = _dialogService.WaitShowDialog(content,
76 | null,
77 | param);
78 | Debug.WriteLine($"ShowDialog over:{result}");
79 | });
80 | UnloadViewCommand = ReactiveCommand.Create((context) =>
81 | {
82 | _regionManager.RequestViewUnload(context);
83 | });
84 | }
85 |
86 | public IServiceProvider ServiceProvider
87 | {
88 | get;
89 | }
90 | public ReactiveCommand UnloadViewCommand
91 | {
92 | get;
93 | }
94 | public ReactiveCommand NavigateToViewCommand
95 | {
96 | get;
97 | }
98 | public ReactiveCommand ShowCommand
99 | {
100 | get;
101 | }
102 | public ReactiveCommand ShowDialogCommand
103 | {
104 | get;
105 | }
106 | public ReactiveCommand ShowDialogSyncCommand
107 | {
108 | get;
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.SampleViewModel/ViewAlphaViewModel.cs:
--------------------------------------------------------------------------------
1 | using Lemon.ModuleNavigation.Abstractions;
2 | using Lemon.ModuleNavigation.Core;
3 | using ReactiveUI;
4 | using System.Reactive;
5 |
6 | namespace Lemon.ModuleNavigation.SampleViewModel;
7 |
8 | public class ViewAlphaViewModel : BaseNavigationViewModel, IDialogAware
9 | {
10 | public ViewAlphaViewModel()
11 | {
12 | }
13 |
14 | public string Title => nameof(ViewAlphaViewModel);
15 |
16 | public event Action? RequestClose;
17 | public ReactiveCommand CloseCommand => ReactiveCommand.Create(() =>
18 | {
19 | var param = new DialogParameters
20 | {
21 | { "from", nameof(ViewAlphaViewModel) }
22 | };
23 | RequestClose?.Invoke(new DialogResult(ButtonResult.OK, param));
24 | });
25 |
26 | private bool _isDialog = false;
27 | public bool IsDialog
28 | {
29 | get => _isDialog;
30 | set
31 | {
32 | this.RaiseAndSetIfChanged(ref _isDialog, value);
33 | }
34 | }
35 |
36 | public void OnDialogClosed()
37 | {
38 |
39 | }
40 |
41 | public void OnDialogOpened(IDialogParameters? parameters)
42 | {
43 | IsDialog = true;
44 | }
45 |
46 | public override bool IsNavigationTarget(NavigationContext navigationContext)
47 | {
48 | if (navigationContext.Parameters is not null)
49 | {
50 | if (navigationContext.Parameters.TryGetValue("requestNew", out bool requestNew))
51 | {
52 | return !requestNew;
53 | }
54 | }
55 | return true;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.SampleViewModel/ViewBetaViewModel.cs:
--------------------------------------------------------------------------------
1 | using Lemon.ModuleNavigation.Abstractions;
2 | using Lemon.ModuleNavigation.Core;
3 | using ReactiveUI;
4 | using System.Reactive;
5 |
6 | namespace Lemon.ModuleNavigation.SampleViewModel;
7 |
8 | public class ViewBetaViewModel : BaseNavigationViewModel, IDialogAware
9 | {
10 | public string Title => nameof(ViewBetaViewModel);
11 |
12 | public event Action? RequestClose;
13 | public ReactiveCommand CloseCommand => ReactiveCommand.Create(() =>
14 | {
15 | var param = new DialogParameters
16 | {
17 | { "from", nameof(ViewBetaViewModel) }
18 | };
19 | RequestClose?.Invoke(new DialogResult(ButtonResult.OK, param));
20 | });
21 |
22 | private bool _isDialog = false;
23 | public bool IsDialog
24 | {
25 | get => _isDialog;
26 | set
27 | {
28 | this.RaiseAndSetIfChanged(ref _isDialog, value);
29 | }
30 | }
31 |
32 | public void OnDialogClosed()
33 | {
34 |
35 | }
36 |
37 | public void OnDialogOpened(IDialogParameters? parameters)
38 | {
39 | IsDialog = true;
40 | }
41 | public override bool IsNavigationTarget(NavigationContext navigationContext)
42 | {
43 | if (navigationContext.Parameters is not null)
44 | {
45 | if (navigationContext.Parameters.TryGetValue("requestNew", out bool requestNew))
46 | {
47 | return !requestNew;
48 | }
49 | }
50 | return true;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.WpfSample/App.xaml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.WpfSample/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using Lemon.ModuleNavigation.SampleViewModel;
2 | using Lemon.ModuleNavigation.WpfSample.Views;
3 | using Microsoft.Extensions.DependencyInjection;
4 | using System.Windows;
5 |
6 | namespace Lemon.ModuleNavigation.WpfSample;
7 |
8 | ///
9 | /// Interaction logic for App.xaml
10 | ///
11 | public partial class App : Application
12 | {
13 | public IServiceProvider? AppServiceProvider { get; private set; }
14 | protected override void OnStartup(StartupEventArgs e)
15 | {
16 | var services = new ServiceCollection();
17 | services.AddWpfNavigationSupport()
18 | .AddSingleton()
19 | .AddSingleton()
20 | .AddView(nameof(ViewAlpha))
21 | .AddView(nameof(ViewBeta));
22 |
23 | AppServiceProvider = services.BuildServiceProvider();
24 | var window = AppServiceProvider.GetRequiredService();
25 | var mainViewModel = AppServiceProvider.GetRequiredService();
26 | window.DataContext = mainViewModel;
27 | base.OnStartup(e);
28 |
29 | window.Show();
30 | Application.Current.MainWindow = window;
31 | }
32 | }
33 |
34 |
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.WpfSample/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 |
3 | [assembly:ThemeInfo(
4 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
5 | //(used if a resource is not found in the page,
6 | // or application resource dictionaries)
7 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
8 | //(used if a resource is not found in the page,
9 | // app, or any theme specific resource dictionaries)
10 | )]
11 |
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.WpfSample/BoolToVisibilityConverter.cs:
--------------------------------------------------------------------------------
1 | using System.Globalization;
2 | using System.Windows;
3 | using System.Windows.Data;
4 |
5 | namespace Lemon.ModuleNavigation.WpfSample;
6 |
7 | public class BoolToVisibilityConverter : IValueConverter
8 | {
9 | public bool Inverse { get; set; } = false;
10 |
11 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
12 | {
13 | if (value is bool boolValue)
14 | {
15 | if (Inverse)
16 | return boolValue ? Visibility.Collapsed : Visibility.Visible;
17 | else
18 | return boolValue ? Visibility.Visible : Visibility.Collapsed;
19 | }
20 | return Visibility.Collapsed;
21 | }
22 |
23 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
24 | {
25 | if (value is Visibility visibility)
26 | {
27 | if (Inverse)
28 | return visibility != Visibility.Visible;
29 | else
30 | return visibility == Visibility.Visible;
31 | }
32 | return false;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.WpfSample/Lemon.ModuleNavigation.WpfSample.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | WinExe
5 | net8.0-windows7.0;net6.0-windows7.0
6 | enable
7 | enable
8 | true
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.WpfSample/MainWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 |
3 | namespace Lemon.ModuleNavigation.WpfSample;
4 |
5 | ///
6 | /// Interaction logic for MainWindow.xaml
7 | ///
8 | public partial class MainWindow : Window
9 | {
10 | public MainWindow()
11 | {
12 | InitializeComponent();
13 | }
14 | }
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.WpfSample/Views/ViewAlpha.xaml:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |
16 |
21 |
22 |
23 |
29 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.WpfSample/Views/ViewAlpha.xaml.cs:
--------------------------------------------------------------------------------
1 | using Lemon.ModuleNavigation.Abstractions;
2 | using System.Windows.Controls;
3 |
4 | namespace Lemon.ModuleNavigation.WpfSample.Views;
5 |
6 | ///
7 | /// Interaction logic for ViewAlpha.xaml
8 | ///
9 | public partial class ViewAlpha : UserControl, IView
10 | {
11 | public ViewAlpha()
12 | {
13 | InitializeComponent();
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.WpfSample/Views/ViewBeta.xaml:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |
16 |
21 |
22 |
23 |
29 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/samples/Lemon.ModuleNavigation.WpfSample/Views/ViewBeta.xaml.cs:
--------------------------------------------------------------------------------
1 | using Lemon.ModuleNavigation.Abstractions;
2 | using System.Windows.Controls;
3 |
4 | namespace Lemon.ModuleNavigation.WpfSample.Views;
5 |
6 | ///
7 | /// Interaction logic for ViewBeta.xaml
8 | ///
9 | public partial class ViewBeta : UserControl, IView
10 | {
11 | public ViewBeta()
12 | {
13 | InitializeComponent();
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation.Avaloniaui/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Metadata;
2 |
3 | [assembly: XmlnsPrefix("https://github.com/NeverMorewd/Lemon.ModuleNavigation", "lm")]
4 | [assembly: XmlnsDefinition("https://github.com/NeverMorewd/Lemon.ModuleNavigation", "Lemon.ModuleNavigation")]
5 | [assembly: XmlnsDefinition("https://github.com/NeverMorewd/Lemon.ModuleNavigation", "Lemon.ModuleNavigation.Avaloniaui")]
6 | [assembly: XmlnsDefinition("https://github.com/NeverMorewd/Lemon.ModuleNavigation", "Lemon.ModuleNavigation.Avaloniaui.Regions")]
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation.Avaloniaui/AvaModule{TView,TViewModel}.cs:
--------------------------------------------------------------------------------
1 | using Lemon.ModuleNavigation.Abstractions;
2 | using System.Diagnostics.CodeAnalysis;
3 |
4 | namespace Lemon.ModuleNavigation.Avaloniaui;
5 |
6 | [Obsolete($"Use Module instead, they are equivalent.")]
7 | public abstract class AvaModule<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TView,
8 | [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TViewModel>
9 | : Module
10 | where TViewModel : IModuleNavigationAware
11 | where TView : IView
12 | {
13 | public AvaModule(IServiceProvider serviceProvider) : base(serviceProvider)
14 | {
15 |
16 | }
17 | }
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation.Avaloniaui/DefaultDialogWindow.axaml:
--------------------------------------------------------------------------------
1 |
12 |
13 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation.Avaloniaui/DefaultDialogWindow.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls;
2 |
3 | namespace Lemon.ModuleNavigation.Avaloniaui;
4 |
5 | public partial class DefaultDialogWindow : Window, IDialogWindow
6 | {
7 | public readonly static string Key = nameof(DefaultDialogWindow);
8 | public DefaultDialogWindow()
9 | {
10 | InitializeComponent();
11 | }
12 | }
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation.Avaloniaui/DialogService.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Controls.ApplicationLifetimes;
3 | using Lemon.ModuleNavigation.Abstractions;
4 | using Lemon.ModuleNavigation.Core;
5 | using Microsoft.Extensions.DependencyInjection;
6 |
7 | namespace Lemon.ModuleNavigation.Avaloniaui;
8 |
9 | public class DialogService : IDialogService
10 | {
11 | private readonly IServiceProvider _serviceProvider;
12 | public DialogService(IServiceProvider serviceProvider)
13 | {
14 | _serviceProvider = serviceProvider;
15 | }
16 |
17 | public void Show(string name,
18 | string? windowName = null,
19 | IDialogParameters? parameters = null,
20 | Action? callback = null)
21 | {
22 | ShowCore(name, windowName, false, parameters, callback).Wait();
23 | }
24 |
25 | public async Task ShowDialog(string name,
26 | string? windowName = null,
27 | IDialogParameters? parameters = null,
28 | Action? callback = null)
29 | {
30 | await ShowCore(name, windowName, true, parameters, callback);
31 | }
32 |
33 | public IDialogResult WaitShowDialog(string name,
34 | string? windowName = null,
35 | IDialogParameters? parameters = null)
36 | {
37 | return ShowDialogCoreSync(name, windowName, parameters);
38 | }
39 |
40 |
41 | private IDialogResult ShowDialogCoreSync(string name,
42 | string? windowName,
43 | IDialogParameters? parameters = null,
44 | Action? callback = null)
45 | {
46 | var tcs = new TaskCompletionSource();
47 | var task = ShowCore(name,
48 | windowName,
49 | true,
50 | parameters,
51 | result =>
52 | {
53 | callback?.Invoke(result);
54 | tcs.TrySetResult(result);
55 | });
56 | return tcs.Task.WaitOnDispatcherFrame();
57 | }
58 |
59 | private async Task ShowCore(string name,
60 | string? windowName,
61 | bool showDialog,
62 | IDialogParameters? parameters = null,
63 | Action? callback = null)
64 | {
65 | IDialogWindow dialogWindow;
66 | if (string.IsNullOrEmpty(windowName))
67 | {
68 | dialogWindow = _serviceProvider.GetRequiredKeyedService(DefaultDialogWindow.Key);
69 | }
70 | else
71 | {
72 | dialogWindow = _serviceProvider.GetRequiredKeyedService(windowName);
73 | }
74 |
75 | var dialogViewModel = _serviceProvider.GetRequiredKeyedService(name);
76 | dialogWindow.Title = dialogViewModel.Title;
77 | dialogWindow.Content = _serviceProvider.GetRequiredKeyedService(name);
78 | dialogWindow.DataContext = dialogViewModel;
79 | dialogViewModel.OnDialogOpened(parameters);
80 |
81 | bool handled = false;
82 | void RequestCloseHandler(IDialogResult dialogResult)
83 | {
84 | callback?.Invoke(dialogResult);
85 | handled = true;
86 | dialogWindow.Close();
87 | }
88 |
89 | dialogViewModel.RequestClose += RequestCloseHandler;
90 | dialogWindow.Closed += (s, e) =>
91 | {
92 | dialogViewModel.RequestClose -= RequestCloseHandler;
93 | if (!handled)
94 | {
95 | callback?.Invoke(new DialogResult(ButtonResult.None));
96 | }
97 | dialogViewModel.OnDialogClosed();
98 | };
99 | if (showDialog)
100 | {
101 | if (Application.Current!.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime classicDesktop)
102 | {
103 | var owner = classicDesktop.MainWindow!;
104 | await dialogWindow.ShowDialog(owner);
105 | }
106 | }
107 | else
108 | {
109 | dialogWindow.Show();
110 | }
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation.Avaloniaui/Extensions/AvaloniauiExtensions.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Threading;
2 |
3 | namespace Avalonia;
4 |
5 | public static class AvaloniauiExtensions
6 | {
7 | public static T WaitOnDispatcherFrame(this Task task)
8 | {
9 | if (!task.IsCompleted)
10 | {
11 | var frame = new DispatcherFrame();
12 | task.ContinueWith(static (_, s) => ((DispatcherFrame)s!).Continue = false, frame);
13 | Dispatcher.UIThread.PushFrame(frame);
14 | }
15 |
16 | return task.GetAwaiter().GetResult();
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation.Avaloniaui/Extensions/ServiceCollectionExtensions.cs:
--------------------------------------------------------------------------------
1 | using Lemon.ModuleNavigation.Abstractions;
2 | using Lemon.ModuleNavigation.Avaloniaui;
3 |
4 | namespace Microsoft.Extensions.DependencyInjection;
5 |
6 | public static class ServiceCollectionExtensions
7 | {
8 | public static IServiceCollection AddAvaNavigationSupport(this IServiceCollection serviceDescriptors)
9 | {
10 | return serviceDescriptors
11 | .AddNavigationSupport()
12 | .AddSingleton(sp => sp.GetKeyedServices(typeof(IView)))
13 | .AddSingleton()
14 | .AddKeyedTransient(DefaultDialogWindow.Key);
15 | }
16 |
17 | public static IServiceCollection AddAvaDialogWindow(this IServiceCollection serviceDescriptors, string windowKey)
18 | where TDialogWindow : class, IDialogWindow
19 | {
20 | return serviceDescriptors.AddKeyedTransient(windowKey);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation.Avaloniaui/IDialogWindow.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls;
2 | using Lemon.ModuleNavigation.Abstractions;
3 |
4 | namespace Lemon.ModuleNavigation.Avaloniaui;
5 |
6 | public interface IDialogWindow : IDialogWindowBase
7 | {
8 | Task ShowDialog(Window owner);
9 | Task ShowDialog(Window owner);
10 | }
11 |
12 | [Obsolete("Use IDialogWindow instead, they are equivalent.")]
13 | public interface IAvaDialogWindow : IDialogWindowBase
14 | {
15 | Task ShowDialog(Window owner);
16 | Task ShowDialog(Window owner);
17 | }
18 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation.Avaloniaui/Lemon.ModuleNavigation.Avaloniaui.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0;net6.0
5 | enable
6 | enable
7 | latest
8 | True
9 | Lemon.ModuleNavigation.Avaloniaui
10 | Extending Lemon.ModuleNavigation, this package provides NavigationContainers specifically designed for AvaloniaUI.
11 | lemon-100.png
12 | README.md
13 | ../output
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | True
34 | \
35 |
36 |
37 | True
38 | \
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation.Avaloniaui/Regions/ContentRegion.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls;
2 | using Avalonia.Controls.Templates;
3 | using Avalonia.Data;
4 | using Avalonia.Markup.Xaml.Templates;
5 | using Lemon.ModuleNavigation.Abstractions;
6 | using System.ComponentModel;
7 | using System.Runtime.CompilerServices;
8 |
9 | namespace Lemon.ModuleNavigation.Avaloniaui.Regions;
10 |
11 | public class ContentRegion : Region, IContentRegionContext
12 | {
13 | private readonly ContentControl _contentControl;
14 | public ContentRegion(string name, ContentControl contentControl) : base(name)
15 | {
16 | _contentControl = contentControl;
17 | SetBindingContentTemplate();
18 | SetBindingContent();
19 | }
20 |
21 | private object? _content;
22 | public object? Content
23 | {
24 | get => _content;
25 | set
26 | {
27 | _content = value;
28 | OnPropertyChanged();
29 | }
30 | }
31 |
32 | private IDataTemplate? _contentTemplate;
33 | public IDataTemplate? ContentTemplate
34 | {
35 | get => _contentTemplate;
36 | set
37 | {
38 | _contentTemplate = value;
39 | OnPropertyChanged();
40 | }
41 | }
42 |
43 | public event PropertyChangedEventHandler? PropertyChanged;
44 |
45 | ///
46 | /// When Views with same ViewName were found, the latest one will be picked.
47 | ///
48 | ///
49 | public override void Activate(NavigationContext target)
50 | {
51 | if (ViewCache.TryGetValue(target, out IView? accurateView))
52 | {
53 | target.View = accurateView;
54 | Content = target;
55 | }
56 | else if (ViewNameCache.TryGetValue(target.ViewName, out IView? view)
57 | && view.DataContext is INavigationAware navigationAware
58 | && navigationAware.IsNavigationTarget(target))
59 | {
60 | var context = Contexts.First(c => c.ViewName == target.ViewName);
61 | context.View = view;
62 | Content = context;
63 | }
64 | else
65 | {
66 | Contexts.Add(target);
67 | Content = target;
68 | }
69 | }
70 |
71 | public override void DeActivate(string regionName)
72 | {
73 | if (Content is NavigationContext current)
74 | {
75 | if (current.ViewName == regionName)
76 | {
77 | Contexts.Remove(current);
78 | Content = null;
79 | }
80 | }
81 | }
82 | public override void DeActivate(NavigationContext navigationContext)
83 | {
84 | if (Content is NavigationContext current)
85 | {
86 | if (NavigationContext.ViewNameComparer.Equals(current, navigationContext))
87 | {
88 | Contexts.Remove(current);
89 | Content = null;
90 | }
91 | }
92 | }
93 |
94 | protected virtual void SetBindingContentTemplate()
95 | {
96 | ContentTemplate = RegionContentTemplate;
97 | _contentControl.Bind(ContentControl.ContentTemplateProperty,
98 | new Binding(nameof(ContentTemplate))
99 | {
100 | Source = this,
101 | Mode = BindingMode.TwoWay
102 | });
103 | }
104 | protected virtual void SetBindingContent()
105 | {
106 | _contentControl.Bind(ContentControl.ContentProperty,
107 | new Binding(nameof(Content))
108 | {
109 | Source = this,
110 | Mode = BindingMode.TwoWay
111 | });
112 | }
113 | protected void OnPropertyChanged([CallerMemberName] string? propertyName = null)
114 | {
115 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation.Avaloniaui/Regions/ItemsRegion.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls;
2 | using Avalonia.Controls.Primitives;
3 | using Avalonia.Controls.Templates;
4 | using Avalonia.Data;
5 | using Avalonia.Markup.Xaml.Templates;
6 | using Lemon.ModuleNavigation.Abstractions;
7 | using System.ComponentModel;
8 | using System.Runtime.CompilerServices;
9 |
10 | namespace Lemon.ModuleNavigation.Avaloniaui.Regions;
11 |
12 | public class ItemsRegion : Region, IItemsRegionDataContext
13 | {
14 | private readonly ItemsControl _itemsControl;
15 | public ItemsRegion(string name, ItemsControl itemsControl) : base(name)
16 | {
17 | _itemsControl = itemsControl;
18 | SetBindingItemTemplate();
19 | SetBindingSelectedItem();
20 | SetBindingItemsSource();
21 | }
22 | private object? _selectItem;
23 | public object? SelectedItem
24 | {
25 | get
26 | {
27 | return _selectItem;
28 | }
29 | set
30 | {
31 | _selectItem = value;
32 | OnPropertyChanged();
33 | }
34 | }
35 |
36 | private IDataTemplate? _itemsTemplate;
37 | public IDataTemplate? ItemTemplate
38 | {
39 | get => _itemsTemplate;
40 | set
41 | {
42 | _itemsTemplate = value;
43 | OnPropertyChanged();
44 | }
45 | }
46 | public event PropertyChangedEventHandler? PropertyChanged;
47 |
48 | public override void ScrollIntoView(int index)
49 | {
50 | _itemsControl.ScrollIntoView(index);
51 | }
52 | public override void ScrollIntoView(NavigationContext item)
53 | {
54 | _itemsControl.ScrollIntoView(item);
55 | }
56 |
57 | ///
58 | /// When Views with same ViewName were found, the earliest one will be picked.
59 | ///
60 | ///
61 | public override void Activate(NavigationContext target)
62 | {
63 | try
64 | {
65 | if (ViewCache.TryGetValue(target, out IView? accurateView))
66 | {
67 | target.View = accurateView;
68 | SelectedItem = target;
69 | return;
70 | }
71 | var context = Contexts.FirstOrDefault(c => c.ViewName == target.ViewName);
72 | if (context is not null
73 | && context.View is not null
74 | && context.View.DataContext is INavigationAware navigationAware
75 | && navigationAware.IsNavigationTarget(target))
76 | {
77 | SelectedItem = context;
78 | return;
79 | }
80 | Contexts.Add(target);
81 | SelectedItem = target;
82 | }
83 | finally
84 | {
85 | ScrollIntoView((SelectedItem as NavigationContext)!);
86 | }
87 | }
88 | public override void DeActivate(string viewName)
89 | {
90 | Contexts.Remove(Contexts.Last(c => c.ViewName == viewName));
91 | }
92 | public override void DeActivate(NavigationContext navigationContext)
93 | {
94 | Contexts.Remove(navigationContext);
95 | }
96 | public void Add(NavigationContext item)
97 | {
98 | Contexts.Add(item);
99 | }
100 |
101 | protected virtual void SetBindingItemsSource()
102 | {
103 | _itemsControl.Bind(ItemsControl.ItemsSourceProperty,
104 | new Binding(nameof(Contexts))
105 | {
106 | Source = this
107 | });
108 | }
109 | protected virtual void SetBindingItemTemplate()
110 | {
111 | ItemTemplate = RegionContentTemplate;
112 | _itemsControl.Bind(ItemsControl.ItemTemplateProperty,
113 | new Binding(nameof(ItemTemplate))
114 | {
115 | Source = this
116 | });
117 | }
118 | protected virtual void SetBindingSelectedItem()
119 | {
120 | if (_itemsControl is SelectingItemsControl selector)
121 | {
122 | selector.Bind(SelectingItemsControl.SelectedItemProperty,
123 | new Binding(nameof(SelectedItem))
124 | {
125 | Source = this,
126 | Mode = BindingMode.TwoWay
127 | });
128 | }
129 | }
130 | protected void OnPropertyChanged([CallerMemberName] string? propertyName = null)
131 | {
132 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation.Avaloniaui/Regions/Region.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls;
2 | using Avalonia.Controls.Templates;
3 | using Lemon.ModuleNavigation.Abstractions;
4 | using Lemon.ModuleNavigation.Core;
5 | using Microsoft.Extensions.DependencyInjection;
6 | using System.Collections.Concurrent;
7 | using System.Collections.ObjectModel;
8 | using System.Collections.Specialized;
9 |
10 | namespace Lemon.ModuleNavigation.Avaloniaui.Regions;
11 |
12 | public abstract class Region : IRegion
13 | {
14 | public Region(string name)
15 | {
16 | Name = name;
17 | Current = new();
18 | ViewCache = [];
19 | Contexts = [];
20 | RegionContentTemplate = CreateRegionDataTemplate();
21 | Contexts.CollectionChanged += Contexts_CollectionChanged;
22 | }
23 | protected ConcurrentItem<(IView View, INavigationAware NavigationAware)> Current
24 | {
25 | get;
26 | }
27 | public string Name
28 | {
29 | get;
30 | }
31 | protected ConcurrentDictionary ViewCache
32 | {
33 | get;
34 | }
35 |
36 | protected ConcurrentDictionary ViewNameCache
37 | {
38 | get
39 | {
40 | return new(Contexts
41 | .GroupBy(kv => kv.ViewName)
42 | .Select(group => new KeyValuePair(
43 | group.Key,
44 | group.Last().View!)));
45 | }
46 | }
47 | public ObservableCollection Contexts
48 | {
49 | get;
50 | }
51 |
52 | public IDataTemplate? RegionContentTemplate
53 | {
54 | get;
55 | }
56 | public virtual void ScrollIntoView(int index)
57 | {
58 | throw new NotImplementedException();
59 | }
60 | public virtual void ScrollIntoView(NavigationContext item)
61 | {
62 | throw new NotImplementedException();
63 | }
64 | public abstract void Activate(NavigationContext target);
65 | public abstract void DeActivate(string viewName);
66 | public abstract void DeActivate(NavigationContext target);
67 |
68 | protected IView? ResolveView(NavigationContext context)
69 | {
70 | var view = context.View;
71 | if (view is null)
72 | {
73 | view = context.ServiceProvider.GetRequiredKeyedService(context.ViewName);
74 | var navigationAware = context.ServiceProvider.GetRequiredKeyedService(context.ViewName);
75 |
76 | if (Current.TryTakeData(out var previousData))
77 | {
78 | previousData.NavigationAware.OnNavigatedFrom(context);
79 | }
80 |
81 | view.DataContext = navigationAware;
82 | navigationAware.OnNavigatedTo(context);
83 | navigationAware.RequestUnload += () =>
84 | {
85 | DeActivate(context);
86 | };
87 | Current.SetData((view, navigationAware));
88 | context.View = view;
89 | ViewCache.AddOrUpdate(context, view, (key, value) => view);
90 | }
91 | return view;
92 | }
93 |
94 | protected virtual void WhenContextsAdded(IEnumerable contexts)
95 | {
96 |
97 | }
98 | protected virtual void WhenContextsRemoved(IEnumerable contexts)
99 | {
100 |
101 | }
102 | private void Contexts_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
103 | {
104 | if (e.Action == NotifyCollectionChangedAction.Add)
105 | {
106 | if (e.NewItems is not null)
107 | {
108 | WhenContextsAdded(e.NewItems.Cast());
109 | }
110 | }
111 | if (e.Action == NotifyCollectionChangedAction.Remove)
112 | {
113 | if (e.OldItems is not null)
114 | {
115 | WhenContextsRemoved(e.OldItems.Cast());
116 | }
117 | }
118 | }
119 | private IDataTemplate CreateRegionDataTemplate()
120 | {
121 | return new FuncDataTemplate((context, np) =>
122 | {
123 | if (context is null)
124 | {
125 | return null;
126 | }
127 | var view = context.View;
128 | if (view is null)
129 | {
130 | view = context.ServiceProvider.GetRequiredKeyedService(context.ViewName);
131 | var navigationAware = context.ServiceProvider.GetRequiredKeyedService(context.ViewName);
132 |
133 | if (Current.TryTakeData(out var previousData))
134 | {
135 | previousData.NavigationAware.OnNavigatedFrom(context);
136 | }
137 |
138 | view.DataContext = navigationAware;
139 | navigationAware.OnNavigatedTo(context);
140 | navigationAware.RequestUnload += () =>
141 | {
142 | DeActivate(context);
143 | };
144 | Current.SetData((view, navigationAware));
145 | context.View = view;
146 | ViewCache.AddOrUpdate(context, view, (key, value) => view);
147 | }
148 | return view as Control;
149 | });
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation.Avaloniaui/Regions/TabRegion.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls;
2 | using Avalonia.Controls.Templates;
3 | using Avalonia.Data;
4 | using Avalonia.Markup.Xaml.Templates;
5 | using Lemon.ModuleNavigation.Abstractions;
6 |
7 | namespace Lemon.ModuleNavigation.Avaloniaui.Regions;
8 |
9 | public class TabRegion : ItemsRegion, IContentRegionContext
10 | {
11 | private readonly TabControl _tabControl;
12 | public TabRegion(string name, TabControl tabControl) : base(name, tabControl)
13 | {
14 | _tabControl = tabControl;
15 | SetBindingContentTemplate();
16 | }
17 |
18 | public object? Content
19 | {
20 | get => throw new NotImplementedException();
21 | set => throw new NotImplementedException();
22 | }
23 |
24 | private IDataTemplate? _contentTemplate;
25 | public IDataTemplate? ContentTemplate
26 | {
27 | get => _contentTemplate;
28 | set
29 | {
30 | _contentTemplate = value;
31 | OnPropertyChanged();
32 | }
33 | }
34 | protected override void SetBindingItemTemplate()
35 | {
36 | //base.SetBindingItemTemplate();
37 | }
38 | protected virtual void SetBindingContentTemplate()
39 | {
40 | ContentTemplate = RegionContentTemplate;
41 | _tabControl.Bind(TabControl.ContentTemplateProperty,
42 | new Binding(nameof(ContentTemplate))
43 | {
44 | Mode = BindingMode.TwoWay,
45 | Source = this
46 | });
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation.Avaloniaui/RegionsExtension.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls;
2 | using Lemon.ModuleNavigation.Abstractions;
3 | using Lemon.ModuleNavigation.Avaloniaui.Regions;
4 |
5 | namespace Lemon.ModuleNavigation.Avaloniaui;
6 |
7 | public static class RegionsExtension
8 | {
9 | public static IRegion ToRegion(this Control control, string name)
10 | {
11 | return control switch
12 | {
13 | TabControl tabControl => new TabRegion(name, tabControl),
14 | ItemsControl itemsControl => new ItemsRegion(name, itemsControl),
15 | ContentControl contentControl => new ContentRegion(name, contentControl),
16 | _ => throw new NotSupportedException($"Unsupported control:{control.GetType()}"),
17 | };
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation.Avaloniaui/RegionsOld/ContentRegion.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls;
2 | using Lemon.ModuleNavigation.Abstractions;
3 | using Lemon.ModuleNavigation.Core;
4 | using System.Collections.Concurrent;
5 | using System.Collections.ObjectModel;
6 | using System.Collections.Specialized;
7 |
8 | namespace Lemon.ModuleNavigation.Avaloniaui.RegionsOld;
9 |
10 | public class ContentRegion : RegionBak
11 | {
12 | private readonly ConcurrentDictionary _viewCache = new();
13 | private readonly ConcurrentItem<(IView View, INavigationAware NavigationAware)> _current = new();
14 | private readonly ContentControl _contentControl;
15 | public ContentRegion(ContentControl contentControl, string name) : base()
16 | {
17 | _contentControl = contentControl;
18 | _contentControl.ContentTemplate = RegionTemplate;
19 | Contexts = [];
20 | Contexts.CollectionChanged += ViewContents_CollectionChanged;
21 | Name = name;
22 | }
23 |
24 | public override string Name
25 | {
26 | get;
27 | }
28 | public object? Content
29 | {
30 | get => _contentControl.Content;
31 | set
32 | {
33 | _contentControl.Content = value;
34 | }
35 | }
36 | public override ObservableCollection Contexts
37 | {
38 | get;
39 | }
40 |
41 | public override void Activate(NavigationContext target)
42 | {
43 | if(Content is NavigationContext current)
44 | {
45 | if (target.ViewName == current.ViewName
46 | && !target.RequestNew)
47 | {
48 | return;
49 | }
50 | }
51 | Content = target;
52 | Contexts.Add(target);
53 | }
54 |
55 | public override void DeActivate(string regionName)
56 | {
57 | if (Content is NavigationContext current)
58 | {
59 | if (current.ViewName == regionName)
60 | {
61 | Contexts.Remove(current);
62 | Content = null;
63 | }
64 | }
65 | }
66 | public override void DeActivate(NavigationContext navigationContext)
67 | {
68 | if (Content is NavigationContext current)
69 | {
70 | if (current == navigationContext)
71 | {
72 | Contexts.Remove(current);
73 | Content = null;
74 | }
75 | }
76 | }
77 | private void ViewContents_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
78 | {
79 | if (e.Action == NotifyCollectionChangedAction.Add)
80 | {
81 | //
82 | }
83 | if (e.Action == NotifyCollectionChangedAction.Remove)
84 | {
85 | //
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation.Avaloniaui/RegionsOld/ItemsRegion.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls;
2 | using Avalonia.Controls.Primitives;
3 | using System.Collections.ObjectModel;
4 | using System.Collections.Specialized;
5 |
6 | namespace Lemon.ModuleNavigation.Avaloniaui.RegionsOld;
7 |
8 | public class ItemsRegion : RegionBak
9 | {
10 | private readonly ItemsControl _itemsControl;
11 | public ItemsRegion(ItemsControl itemsControl, string name)
12 | {
13 | _itemsControl = itemsControl;
14 | _itemsControl.ItemTemplate = RegionTemplate;
15 | Contexts = [];
16 | Contexts.CollectionChanged += ViewContents_CollectionChanged;
17 | Name = name;
18 | }
19 | public override string Name
20 | {
21 | get;
22 | }
23 | public object? SelectedItem
24 | {
25 | get
26 | {
27 | if (_itemsControl is SelectingItemsControl selecting)
28 | {
29 | return selecting.SelectedItem;
30 | }
31 | return null;
32 | }
33 | set
34 | {
35 | if (value != null)
36 | {
37 | if (_itemsControl is SelectingItemsControl selecting)
38 | {
39 | selecting.SelectedItem = value;
40 | }
41 | _itemsControl.ScrollIntoView(value);
42 | }
43 | }
44 | }
45 |
46 | public override ObservableCollection Contexts
47 | {
48 | get;
49 | }
50 |
51 | public void ScrollIntoView(int index)
52 | {
53 | _itemsControl.ScrollIntoView(index);
54 | }
55 | public void ScrollIntoView(NavigationContext item)
56 | {
57 | _itemsControl.ScrollIntoView(item);
58 | }
59 | public override void Activate(NavigationContext target)
60 | {
61 | if (!target.RequestNew)
62 | {
63 | var targetContext = Contexts.FirstOrDefault(context =>
64 | {
65 | if (NavigationContext.ViewNameComparer.Equals(target, context))
66 | {
67 | return true;
68 | }
69 | return false;
70 | });
71 | if (targetContext == null)
72 | {
73 | Contexts.Add(target);
74 | SelectedItem = target;
75 | }
76 | else
77 | {
78 | SelectedItem = targetContext;
79 | }
80 | }
81 | else
82 | {
83 | Contexts.Add(target);
84 | SelectedItem = target;
85 | }
86 | }
87 | public override void DeActivate(string viewName)
88 | {
89 | Contexts.Remove(Contexts.Last(c => c.ViewName == viewName));
90 | }
91 | public override void DeActivate(NavigationContext navigationContext)
92 | {
93 | Contexts.Remove(navigationContext);
94 | }
95 | public void Add(NavigationContext item)
96 | {
97 | Contexts.Add(item);
98 | }
99 | private void ViewContents_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
100 | {
101 | if (e.Action == NotifyCollectionChangedAction.Add)
102 | {
103 | if (e.NewItems != null)
104 | {
105 | foreach (var item in e.NewItems)
106 | {
107 | _itemsControl.Items.Add(item);
108 | }
109 | }
110 | }
111 | if (e.Action == NotifyCollectionChangedAction.Remove)
112 | {
113 | if (e.OldItems != null)
114 | {
115 | foreach (var item in e.OldItems)
116 | {
117 | _itemsControl.Items.Remove(item);
118 | }
119 | }
120 | }
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation.Avaloniaui/RegionsOld/RegionBak.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls;
2 | using Avalonia.Controls.Templates;
3 | using Lemon.ModuleNavigation.Abstractions;
4 | using Lemon.ModuleNavigation.Core;
5 | using Microsoft.Extensions.DependencyInjection;
6 | using System.Collections.Concurrent;
7 | using System.Collections.ObjectModel;
8 |
9 | namespace Lemon.ModuleNavigation.Avaloniaui.RegionsOld;
10 |
11 | public abstract class RegionBak : IRegion
12 | {
13 | private readonly ConcurrentDictionary _viewCache = new();
14 | private readonly ConcurrentItem<(IView View, INavigationAware NavigationAware)> _current = new();
15 |
16 | public RegionBak()
17 | {
18 | RegionTemplate = CreateRegionDataTemplate();
19 | }
20 |
21 | public abstract string Name { get; }
22 | public abstract ObservableCollection Contexts { get; }
23 |
24 | public IDataTemplate? RegionTemplate { get; set; }
25 |
26 | public abstract void Activate(NavigationContext target);
27 | public abstract void DeActivate(string viewName);
28 | public abstract void DeActivate(NavigationContext target);
29 |
30 | private IDataTemplate CreateRegionDataTemplate()
31 | {
32 | return new FuncDataTemplate((context, np) =>
33 | {
34 | if (context == null)
35 | return null;
36 |
37 | bool needNewView = context.RequestNew ||
38 | !_viewCache.TryGetValue(context.ViewName, out IView? view);
39 |
40 | if (needNewView)
41 | {
42 | view = context.ServiceProvider.GetRequiredKeyedService(context.ViewName);
43 | var navigationAware = context.ServiceProvider.GetRequiredKeyedService(context.ViewName);
44 |
45 | if (_current.TryTakeData(out var previousData))
46 | {
47 | previousData.NavigationAware.OnNavigatedFrom(context);
48 | }
49 | if (!navigationAware.IsNavigationTarget(context))
50 | return null;
51 |
52 | view.DataContext = navigationAware;
53 | navigationAware.OnNavigatedTo(context);
54 | _current.SetData((view, navigationAware));
55 | _viewCache.TryAdd(context.ViewName, view);
56 | }
57 | else
58 | {
59 | view = _viewCache[context.ViewName];
60 | }
61 |
62 | return view as Control;
63 | });
64 | }
65 | }
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation.Avaloniaui/RegionsOld/TabRegion.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls;
2 | using Avalonia.Controls.Primitives;
3 | using Avalonia.Data;
4 | using System.Collections.ObjectModel;
5 | using System.Collections.Specialized;
6 | using System.ComponentModel;
7 | using System.Runtime.CompilerServices;
8 |
9 | namespace Lemon.ModuleNavigation.Avaloniaui.RegionsOld;
10 |
11 | public class TabRegion : RegionBak, INotifyPropertyChanged
12 | {
13 | private readonly TabControl _tabControl;
14 | public TabRegion(TabControl tabControl, string name)
15 | {
16 | _tabControl = tabControl;
17 | Contexts = [];
18 | //Contexts.CollectionChanged += ViewContents_CollectionChanged;
19 |
20 |
21 | _tabControl.Bind(SelectingItemsControl.SelectedItemProperty,
22 | new Binding(nameof(SelectedItem))
23 | {
24 | Mode = BindingMode.TwoWay,
25 | Source = this
26 | });
27 | _tabControl.Bind(ItemsControl.ItemsSourceProperty,
28 | new Binding(nameof(Contexts))
29 | {
30 | Source = this
31 | });
32 | _tabControl.ContentTemplate = RegionTemplate;
33 |
34 | Name = name;
35 | }
36 | public override string Name
37 | {
38 | get;
39 | }
40 | private NavigationContext? _selectItem;
41 | public NavigationContext? SelectedItem
42 | {
43 | get
44 | {
45 | return _selectItem;
46 | }
47 | set
48 | {
49 | _selectItem = value;
50 | OnPropertyChanged();
51 | }
52 | }
53 | public override ObservableCollection Contexts
54 | {
55 | get;
56 | }
57 | public event PropertyChangedEventHandler? PropertyChanged;
58 | protected void OnPropertyChanged([CallerMemberName] string? propertyName = null)
59 | {
60 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
61 | }
62 | public override void Activate(NavigationContext target)
63 | {
64 | if (!target.RequestNew)
65 | {
66 | var targetContext = Contexts.FirstOrDefault(context =>
67 | {
68 | if (NavigationContext.ViewNameComparer.Equals(target, context))
69 | {
70 | return true;
71 | }
72 | return false;
73 | });
74 | if (targetContext == null)
75 | {
76 | Contexts.Add(target);
77 | SelectedItem = target;
78 | }
79 | else
80 | {
81 | SelectedItem = targetContext;
82 | }
83 | }
84 | else
85 | {
86 | Contexts.Add(target);
87 | SelectedItem = target;
88 | }
89 | }
90 | public override void DeActivate(string viewName)
91 | {
92 | Contexts.Remove(Contexts.Last(c => c.ViewName == viewName));
93 | }
94 | public override void DeActivate(NavigationContext navigationContext)
95 | {
96 | Contexts.Remove(navigationContext);
97 | }
98 | public void Add(NavigationContext item)
99 | {
100 | Contexts.Add(item);
101 | }
102 | private void ViewContents_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
103 | {
104 | if (e.Action == NotifyCollectionChangedAction.Add)
105 | {
106 | if (e.NewItems != null)
107 | {
108 | foreach (var item in e.NewItems)
109 | {
110 | _tabControl.Items.Add(item);
111 | }
112 | }
113 | }
114 | if (e.Action == NotifyCollectionChangedAction.Remove)
115 | {
116 | if (e.OldItems != null)
117 | {
118 | foreach (var item in e.OldItems)
119 | {
120 | _tabControl.Items.Remove(item);
121 | }
122 | }
123 | }
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation.Wpf/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 | using System.Windows.Markup;
3 |
4 | [assembly: XmlnsPrefix("https://github.com/NeverMorewd/Lemon.ModuleNavigation", "lm")]
5 | [assembly: XmlnsDefinition("https://github.com/NeverMorewd/Lemon.ModuleNavigation", "Lemon.ModuleNavigation")]
6 | [assembly: XmlnsDefinition("https://github.com/NeverMorewd/Lemon.ModuleNavigation", "Lemon.ModuleNavigation.Wpf")]
7 | [assembly: XmlnsDefinition("https://github.com/NeverMorewd/Lemon.ModuleNavigation", "Lemon.ModuleNavigation.Wpf.Regions")]
8 | [assembly:ThemeInfo(
9 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
10 | //(used if a resource is not found in the page,
11 | // or application resource dictionaries)
12 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
13 | //(used if a resource is not found in the page,
14 | // app, or any theme specific resource dictionaries)
15 | )]
16 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation.Wpf/DefaultDialogWindow.xaml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation.Wpf/DefaultDialogWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 |
3 | namespace Lemon.ModuleNavigation.Wpf;
4 |
5 | ///
6 | /// Interaction logic for DefaultDialogWindow.xaml
7 | ///
8 | public partial class DefaultDialogWindow : Window, IDialogWindow
9 | {
10 | public readonly static string Key = nameof(DefaultDialogWindow);
11 | public DefaultDialogWindow()
12 | {
13 | InitializeComponent();
14 | }
15 |
16 | public async Task ShowDialogAsync(Window? owner = null)
17 | {
18 | TaskCompletionSource taskCompletionSource = new();
19 | if (Owner != null)
20 | {
21 | Owner = owner;
22 | }
23 | var result = ShowDialog();
24 | taskCompletionSource.SetResult(result);
25 | return await taskCompletionSource.Task;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation.Wpf/DialogService.cs:
--------------------------------------------------------------------------------
1 | using Lemon.ModuleNavigation.Abstractions;
2 | using Lemon.ModuleNavigation.Core;
3 | using Microsoft.Extensions.DependencyInjection;
4 | using System.Windows;
5 |
6 | namespace Lemon.ModuleNavigation.Wpf;
7 |
8 | public class DialogService : IDialogService
9 | {
10 | private readonly IServiceProvider _serviceProvider;
11 | public DialogService(IServiceProvider serviceProvider)
12 | {
13 | _serviceProvider = serviceProvider;
14 | }
15 |
16 | public void Show(string name,
17 | string? windowName = null,
18 | IDialogParameters? parameters = null,
19 | Action? callback = null)
20 | {
21 | ShowCore(name, windowName, false, parameters, callback).Wait();
22 | }
23 |
24 | public async Task ShowDialog(string name,
25 | string? windowName = null,
26 | IDialogParameters? parameters = null,
27 | Action? callback = null)
28 | {
29 | await ShowCore(name, windowName, true, parameters, callback);
30 | }
31 |
32 | public IDialogResult WaitShowDialog(string name,
33 | string? windowName = null,
34 | IDialogParameters? parameters = null)
35 | {
36 | return ShowDialogCoreSync(name, windowName, parameters);
37 | }
38 |
39 |
40 | private IDialogResult ShowDialogCoreSync(string name,
41 | string? windowName,
42 | IDialogParameters? parameters = null,
43 | Action? callback = null)
44 | {
45 | var tcs = new TaskCompletionSource();
46 | var task = ShowCore(name,
47 | windowName,
48 | true,
49 | parameters,
50 | result =>
51 | {
52 | callback?.Invoke(result);
53 | tcs.TrySetResult(result);
54 | });
55 | return tcs.Task.WaitOnDispatcherFrame();
56 | }
57 |
58 | private async Task ShowCore(string name,
59 | string? windowName,
60 | bool showDialog,
61 | IDialogParameters? parameters = null,
62 | Action? callback = null)
63 | {
64 | IDialogWindow dialogWindow;
65 | if (string.IsNullOrEmpty(windowName))
66 | {
67 | dialogWindow = _serviceProvider.GetRequiredKeyedService(DefaultDialogWindow.Key);
68 | }
69 | else
70 | {
71 | dialogWindow = _serviceProvider.GetRequiredKeyedService(windowName);
72 | }
73 |
74 | var dialogViewModel = _serviceProvider.GetRequiredKeyedService(name);
75 | dialogWindow.Title = dialogViewModel.Title;
76 | dialogWindow.Content = _serviceProvider.GetRequiredKeyedService(name);
77 | dialogWindow.DataContext = dialogViewModel;
78 | dialogViewModel.OnDialogOpened(parameters);
79 |
80 | bool handled = false;
81 | void RequestCloseHandler(IDialogResult dialogResult)
82 | {
83 | callback?.Invoke(dialogResult);
84 | handled = true;
85 | dialogWindow.Close();
86 | }
87 |
88 | dialogViewModel.RequestClose += RequestCloseHandler;
89 | dialogWindow.Closed += (s, e) =>
90 | {
91 | dialogViewModel.RequestClose -= RequestCloseHandler;
92 | if (!handled)
93 | {
94 | callback?.Invoke(new DialogResult(ButtonResult.None));
95 | }
96 | dialogViewModel.OnDialogClosed();
97 | };
98 | if (showDialog)
99 | {
100 | var parent = Application.Current.MainWindow ?? null;
101 | await dialogWindow.ShowDialogAsync(parent);
102 | }
103 | else
104 | {
105 | dialogWindow.Show();
106 | }
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation.Wpf/Extensions/ServiceCollectionExtensions.cs:
--------------------------------------------------------------------------------
1 | using Lemon.ModuleNavigation.Abstractions;
2 | using Lemon.ModuleNavigation.Wpf;
3 |
4 | namespace Microsoft.Extensions.DependencyInjection;
5 |
6 | public static class ServiceCollectionExtensions
7 | {
8 | public static IServiceCollection AddWpfNavigationSupport(this IServiceCollection serviceDescriptors)
9 | {
10 | return serviceDescriptors
11 | .AddNavigationSupport()
12 | .AddSingleton(sp => sp.GetKeyedServices(typeof(IView)))
13 | .AddSingleton()
14 | .AddKeyedTransient(DefaultDialogWindow.Key);
15 | }
16 |
17 | public static IServiceCollection AddDialogWindow(this IServiceCollection serviceDescriptors, string windowKey)
18 | where TDialogWindow : class, IDialogWindow
19 | {
20 | return serviceDescriptors.AddKeyedTransient(windowKey);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation.Wpf/Extensions/WpfExtensions.cs:
--------------------------------------------------------------------------------
1 | using Lemon.ModuleNavigation.Abstractions;
2 | using Lemon.ModuleNavigation.Wpf.Regions;
3 | using System.Windows.Controls;
4 | using System.Windows.Threading;
5 |
6 | namespace System.Windows;
7 |
8 | public static class WpfExtensions
9 | {
10 | public static T WaitOnDispatcherFrame(this Task task)
11 | {
12 | if (!task.IsCompleted)
13 | {
14 | var frame = new DispatcherFrame();
15 | task.ContinueWith(static (_, s) => ((DispatcherFrame)s!).Continue = false, frame);
16 | Dispatcher.PushFrame(frame);
17 | }
18 |
19 | return task.GetAwaiter().GetResult();
20 | }
21 |
22 | public static T? FindLogicalAncestorOfType(this DependencyObject obj, bool includeSelf = false) where T : DependencyObject
23 | {
24 | if (obj == null) throw new ArgumentNullException(nameof(obj));
25 |
26 | if (includeSelf && obj is T self)
27 | return self;
28 |
29 | DependencyObject? parent = LogicalTreeHelper.GetParent(obj);
30 | while (parent != null)
31 | {
32 | if (parent is T ancestor)
33 | return ancestor;
34 |
35 | parent = LogicalTreeHelper.GetParent(parent);
36 | }
37 |
38 | return null;
39 | }
40 |
41 | public static IRegion ToRegion(this Control control, string name)
42 | {
43 | return control switch
44 | {
45 | TabControl tabControl => new TabRegion(name, tabControl),
46 | ItemsControl itemsControl => new ItemsRegion(name, itemsControl),
47 | ContentControl contentControl => new ContentRegion(name, contentControl),
48 | _ => throw new NotSupportedException($"Unsupported control:{control.GetType()}"),
49 | };
50 | }
51 |
52 | public static void ScrollIntoView(this ItemsControl itemsControl, object item)
53 | {
54 | if (itemsControl == null || item == null)
55 | return;
56 | if (itemsControl is ListBox listBox)
57 | {
58 | listBox.Dispatcher.InvokeAsync(() => listBox.ScrollIntoView(item));
59 | }
60 | else
61 | {
62 | var container = itemsControl.ItemContainerGenerator.ContainerFromItem(item) as FrameworkElement;
63 | if (container != null)
64 | {
65 | container.BringIntoView();
66 | return;
67 | }
68 | itemsControl.ItemContainerGenerator.StatusChanged += (s, e) =>
69 | {
70 | if (itemsControl.ItemContainerGenerator.Status == System.Windows.Controls.Primitives.GeneratorStatus.ContainersGenerated)
71 | {
72 | container = itemsControl.ItemContainerGenerator.ContainerFromItem(item) as FrameworkElement;
73 | container?.BringIntoView();
74 | }
75 | };
76 | }
77 | }
78 |
79 | public static void ScrollIntoView(this ItemsControl itemsControl, int index)
80 | {
81 | if (itemsControl == null || index < 0 || index >= itemsControl.Items.Count)
82 | return;
83 |
84 | var item = itemsControl.Items[index];
85 | itemsControl.ScrollIntoView(item);
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation.Wpf/IDialogWindow.cs:
--------------------------------------------------------------------------------
1 | using Lemon.ModuleNavigation.Abstractions;
2 | using System.Windows;
3 |
4 | namespace Lemon.ModuleNavigation.Wpf;
5 |
6 | public interface IDialogWindow : IDialogWindowBase
7 | {
8 | Task ShowDialogAsync(Window? owner = null);
9 | bool? ShowDialog();
10 | }
11 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation.Wpf/Lemon.ModuleNavigation.Wpf.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0-windows;net6.0-windows
5 | enable
6 | true
7 | latest
8 | enable
9 | True
10 | Lemon.ModuleNavigation.Wpf
11 | Extending Lemon.ModuleNavigation, this package provides NavigationContainers specifically designed for Wpf.
12 | lemon-100.png
13 | README.md
14 | ../output
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | True
25 | \
26 |
27 |
28 | True
29 | \
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation.Wpf/NavigationExtension.cs:
--------------------------------------------------------------------------------
1 | using Lemon.ModuleNavigation.Abstractions;
2 | using Microsoft.Extensions.DependencyInjection;
3 | using System.Windows;
4 | using System.Windows.Controls;
5 |
6 | namespace Lemon.ModuleNavigation.Wpf;
7 |
8 | public class NavigationExtension
9 | {
10 | //private static readonly ConcurrentDictionary _moduleContainerNamesCaches = [];
11 | private static readonly HashSet _targets = [];
12 | #region RegionNameProperty
13 | public static readonly DependencyProperty RegionNameProperty =
14 | DependencyProperty.RegisterAttached(
15 | "RegionName",
16 | typeof(string),
17 | typeof(NavigationExtension),
18 | new PropertyMetadata(null, new PropertyChangedCallback(RegionNameChangedCallback)));
19 |
20 | private static void RegionNameChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
21 | {
22 | if (d is Control control)
23 | {
24 | if (control is ContentControl || control is ItemsControl)
25 | {
26 | void LoadedHandler(object? sender, RoutedEventArgs e)
27 | {
28 | if (control.DataContext is IServiceAware navigationProvider)
29 | {
30 | var serviceProvider = navigationProvider!.ServiceProvider;
31 | var handler = serviceProvider.GetRequiredService();
32 | var value = GetRegionName(control);
33 | handler.RegionManager.AddRegion(value, control.ToRegion(value));
34 | }
35 | control.Loaded -= LoadedHandler;
36 | }
37 | control.Loaded += LoadedHandler;
38 | }
39 | else
40 | {
41 | throw new InvalidOperationException($"{nameof(RegionNameProperty)} supports {nameof(ContentControl)} and {nameof(ItemsControl)} Only");
42 | }
43 | }
44 | }
45 |
46 | public static void SetRegionName(Control element, string value)
47 | {
48 | element.SetValue(RegionNameProperty, value);
49 | }
50 |
51 | public static string GetRegionName(Control element)
52 | {
53 | return (string)element.GetValue(RegionNameProperty);
54 | }
55 | #endregion
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation.Wpf/Regions/ContentRegion.cs:
--------------------------------------------------------------------------------
1 | using Lemon.ModuleNavigation.Abstractions;
2 | using System.ComponentModel;
3 | using System.Runtime.CompilerServices;
4 | using System.Windows;
5 | using System.Windows.Controls;
6 | using System.Windows.Data;
7 |
8 | namespace Lemon.ModuleNavigation.Wpf.Regions;
9 |
10 | public class ContentRegion : Region, IContentRegionContext
11 | {
12 | private readonly ContentControl _contentControl;
13 | public ContentRegion(string name, ContentControl contentControl) : base(name)
14 | {
15 | _contentControl = contentControl;
16 | SetBindingContentTemplate();
17 | SetBindingContent();
18 | }
19 |
20 | private object? _content;
21 | public object? Content
22 | {
23 | get => _content;
24 | set
25 | {
26 | _content = value;
27 | OnPropertyChanged();
28 | }
29 | }
30 |
31 | private DataTemplate? _contentTemplate;
32 | public DataTemplate? ContentTemplate
33 | {
34 | get => _contentTemplate;
35 | set
36 | {
37 | _contentTemplate = value;
38 | OnPropertyChanged();
39 | }
40 | }
41 |
42 | public event PropertyChangedEventHandler? PropertyChanged;
43 |
44 | ///
45 | /// When Views with same ViewName were found, the latest one will be picked.
46 | ///
47 | ///
48 | public override void Activate(NavigationContext target)
49 | {
50 | if (ViewCache.TryGetValue(target, out IView? accurateView))
51 | {
52 | target.View = accurateView;
53 | Content = target;
54 | }
55 | else if (ViewNameCache.TryGetValue(target.ViewName, out IView? view)
56 | && view.DataContext is INavigationAware navigationAware
57 | && navigationAware.IsNavigationTarget(target))
58 | {
59 | var context = Contexts.First(c => c.ViewName == target.ViewName);
60 | context.View = view;
61 | Content = context;
62 | }
63 | else
64 | {
65 | Contexts.Add(target);
66 | Content = target;
67 | }
68 | }
69 |
70 | public override void DeActivate(string regionName)
71 | {
72 | if (Content is NavigationContext current)
73 | {
74 | if (current.ViewName == regionName)
75 | {
76 | Contexts.Remove(current);
77 | Content = null;
78 | }
79 | }
80 | }
81 | public override void DeActivate(NavigationContext navigationContext)
82 | {
83 | if (Content is NavigationContext current)
84 | {
85 | if (NavigationContext.ViewNameComparer.Equals(current, navigationContext))
86 | {
87 | Contexts.Remove(current);
88 | Content = null;
89 | }
90 | }
91 | }
92 |
93 | protected virtual void SetBindingContentTemplate()
94 | {
95 | ContentTemplate = RegionContentTemplate;
96 | BindingOperations.SetBinding(_contentControl,
97 | ContentControl.ContentTemplateProperty,
98 | new Binding
99 | {
100 | Source = this,
101 | Path = new PropertyPath(nameof(ContentTemplate)),
102 | });
103 | }
104 | protected virtual void SetBindingContent()
105 | {
106 | BindingOperations.SetBinding(_contentControl,
107 | ContentControl.ContentProperty,
108 | new Binding
109 | {
110 | Source = this,
111 | Path = new PropertyPath(nameof(Content)),
112 | Mode = BindingMode.TwoWay
113 | });
114 | }
115 | protected void OnPropertyChanged([CallerMemberName] string? propertyName = null)
116 | {
117 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation.Wpf/Regions/ItemsRegion.cs:
--------------------------------------------------------------------------------
1 | using Lemon.ModuleNavigation.Abstractions;
2 | using System.ComponentModel;
3 | using System.Runtime.CompilerServices;
4 | using System.Windows;
5 | using System.Windows.Controls;
6 | using System.Windows.Controls.Primitives;
7 | using System.Windows.Data;
8 |
9 | namespace Lemon.ModuleNavigation.Wpf.Regions;
10 |
11 | public class ItemsRegion : Region, IItemsRegionDataContext
12 | {
13 | private readonly ItemsControl _itemsControl;
14 | public ItemsRegion(string name, ItemsControl itemsControl) : base(name)
15 | {
16 | _itemsControl = itemsControl;
17 | SetBindingItemTemplate();
18 | SetBindingSelectedItem();
19 | SetBindingItemsSource();
20 | }
21 | private object? _selectItem;
22 | public object? SelectedItem
23 | {
24 | get
25 | {
26 | return _selectItem;
27 | }
28 | set
29 | {
30 | _selectItem = value;
31 | OnPropertyChanged();
32 | }
33 | }
34 |
35 | private DataTemplate? _itemsTemplate;
36 | public DataTemplate? ItemTemplate
37 | {
38 | get => _itemsTemplate;
39 | set
40 | {
41 | _itemsTemplate = value;
42 | OnPropertyChanged();
43 | }
44 | }
45 | public event PropertyChangedEventHandler? PropertyChanged;
46 |
47 | public override void ScrollIntoView(int index)
48 | {
49 | _itemsControl.ScrollIntoView(index);
50 | }
51 | public override void ScrollIntoView(NavigationContext item)
52 | {
53 | _itemsControl.ScrollIntoView(item);
54 | }
55 |
56 | ///
57 | /// When Views with same ViewName were found, the earliest one will be picked.
58 | ///
59 | ///
60 | public override void Activate(NavigationContext target)
61 | {
62 | try
63 | {
64 | if (ViewCache.TryGetValue(target, out IView? accurateView))
65 | {
66 | target.View = accurateView;
67 | SelectedItem = target;
68 | return;
69 | }
70 | var context = Contexts.FirstOrDefault(c => c.ViewName == target.ViewName);
71 | if (context is not null
72 | && context.View is not null
73 | && context.View.DataContext is INavigationAware navigationAware
74 | && navigationAware.IsNavigationTarget(target))
75 | {
76 | SelectedItem = context;
77 | return;
78 | }
79 | Contexts.Add(target);
80 | SelectedItem = target;
81 | }
82 | finally
83 | {
84 | ScrollIntoView((SelectedItem as NavigationContext)!);
85 | }
86 | }
87 | public override void DeActivate(string viewName)
88 | {
89 | Contexts.Remove(Contexts.Last(c => c.ViewName == viewName));
90 | }
91 | public override void DeActivate(NavigationContext navigationContext)
92 | {
93 | Contexts.Remove(navigationContext);
94 | }
95 | public void Add(NavigationContext item)
96 | {
97 | Contexts.Add(item);
98 | }
99 |
100 | protected virtual void SetBindingItemsSource()
101 | {
102 | BindingOperations.SetBinding(_itemsControl, ItemsControl.ItemsSourceProperty, new Binding
103 | {
104 | Source = this,
105 | Path = new PropertyPath(nameof(Contexts)),
106 | });
107 | }
108 | protected virtual void SetBindingItemTemplate()
109 | {
110 | ItemTemplate = RegionContentTemplate;
111 | BindingOperations.SetBinding(_itemsControl, ItemsControl.ItemTemplateProperty, new Binding
112 | {
113 | Source = this,
114 | Path = new PropertyPath(nameof(ItemTemplate)),
115 | });
116 | }
117 | protected virtual void SetBindingSelectedItem()
118 | {
119 | if (_itemsControl is Selector selector)
120 | {
121 | BindingOperations.SetBinding(selector, Selector.SelectedItemProperty, new Binding
122 | {
123 | Source = this,
124 | Path = new PropertyPath(nameof(SelectedItem)),
125 | Mode = BindingMode.TwoWay
126 | });
127 | }
128 | }
129 | protected void OnPropertyChanged([CallerMemberName] string? propertyName = null)
130 | {
131 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation.Wpf/Regions/Region.cs:
--------------------------------------------------------------------------------
1 | using Lemon.ModuleNavigation.Abstractions;
2 | using Lemon.ModuleNavigation.Core;
3 | using Microsoft.Extensions.DependencyInjection;
4 | using System.Collections.Concurrent;
5 | using System.Collections.ObjectModel;
6 | using System.Collections.Specialized;
7 | using System.Windows;
8 | using System.Windows.Controls;
9 |
10 | namespace Lemon.ModuleNavigation.Wpf.Regions;
11 |
12 | public abstract class Region : IRegion
13 | {
14 | public Region(string name)
15 | {
16 | Name = name;
17 | Current = new();
18 | ViewCache = [];
19 | Contexts = [];
20 | RegionContentTemplate = CreateRegionDataTemplate();
21 | Contexts.CollectionChanged += Contexts_CollectionChanged;
22 | }
23 | protected ConcurrentItem<(IView View, INavigationAware NavigationAware)> Current
24 | {
25 | get;
26 | }
27 | public string Name
28 | {
29 | get;
30 | }
31 | protected ConcurrentDictionary ViewCache
32 | {
33 | get;
34 | }
35 |
36 | protected ConcurrentDictionary ViewNameCache
37 | {
38 | get
39 | {
40 | return new(Contexts
41 | .GroupBy(kv => kv.ViewName)
42 | .Select(group => new KeyValuePair(
43 | group.Key,
44 | group.Last().View!)));
45 | }
46 | }
47 | public ObservableCollection Contexts
48 | {
49 | get;
50 | }
51 |
52 | public DataTemplate? RegionContentTemplate
53 | {
54 | get;
55 | }
56 | public virtual void ScrollIntoView(int index)
57 | {
58 | throw new NotImplementedException();
59 | }
60 | public virtual void ScrollIntoView(NavigationContext item)
61 | {
62 | throw new NotImplementedException();
63 | }
64 | public abstract void Activate(NavigationContext target);
65 | public abstract void DeActivate(string viewName);
66 | public abstract void DeActivate(NavigationContext target);
67 |
68 | protected IView? ResolveView(NavigationContext context)
69 | {
70 | var view = context.View;
71 | if (view is null)
72 | {
73 | view = context.ServiceProvider.GetRequiredKeyedService(context.ViewName);
74 | var navigationAware = context.ServiceProvider.GetRequiredKeyedService(context.ViewName);
75 |
76 | if (Current.TryTakeData(out var previousData))
77 | {
78 | previousData.NavigationAware.OnNavigatedFrom(context);
79 | }
80 |
81 | view.DataContext = navigationAware;
82 | navigationAware.OnNavigatedTo(context);
83 | navigationAware.RequestUnload += () =>
84 | {
85 | DeActivate(context);
86 | };
87 | Current.SetData((view, navigationAware));
88 | context.View = view;
89 | ViewCache.AddOrUpdate(context, view, (key, value) => view);
90 | }
91 | return view;
92 | }
93 |
94 | protected virtual void WhenContextsAdded(IEnumerable contexts)
95 | {
96 |
97 | }
98 | protected virtual void WhenContextsRemoved(IEnumerable contexts)
99 | {
100 |
101 | }
102 | private void Contexts_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
103 | {
104 | if (e.Action == NotifyCollectionChangedAction.Add)
105 | {
106 | if (e.NewItems is not null)
107 | {
108 | WhenContextsAdded(e.NewItems.Cast());
109 | }
110 | }
111 | if (e.Action == NotifyCollectionChangedAction.Remove)
112 | {
113 | if (e.OldItems is not null)
114 | {
115 | WhenContextsRemoved(e.OldItems.Cast());
116 | }
117 | }
118 | }
119 | private DataTemplate CreateRegionDataTemplate()
120 | {
121 | var dataTemplate = new DataTemplate(typeof(NavigationContext));
122 | FrameworkElementFactory factory = new(typeof(ContentPresenter));
123 | factory.SetBinding(ContentPresenter.ContentProperty, new System.Windows.Data.Binding()
124 | {
125 | Converter = new NavigationContextToViewConverter(this)
126 | });
127 | dataTemplate.VisualTree = factory;
128 | return dataTemplate;
129 | }
130 |
131 | private class NavigationContextToViewConverter : System.Windows.Data.IValueConverter
132 | {
133 | private readonly Region _region;
134 | public NavigationContextToViewConverter(Region region)
135 | {
136 | _region = region;
137 | }
138 |
139 | public object? Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
140 | {
141 | if (value is NavigationContext context)
142 | {
143 | return _region.ResolveView(context);
144 | }
145 | return null;
146 | }
147 |
148 | public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
149 | {
150 | throw new NotImplementedException();
151 | }
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation.Wpf/Regions/TabRegion.cs:
--------------------------------------------------------------------------------
1 | using Lemon.ModuleNavigation.Abstractions;
2 | using System.Windows;
3 | using System.Windows.Controls;
4 | using System.Windows.Data;
5 |
6 | namespace Lemon.ModuleNavigation.Wpf.Regions;
7 |
8 | public class TabRegion : ItemsRegion, IContentRegionContext
9 | {
10 | private readonly TabControl _tabControl;
11 | public TabRegion(string name, TabControl tabControl) : base(name, tabControl)
12 | {
13 | _tabControl = tabControl;
14 | ContentTemplate = RegionContentTemplate;
15 | SetBindingContentTemplate();
16 | }
17 |
18 | public object? Content
19 | {
20 | get => throw new NotImplementedException();
21 | set => throw new NotImplementedException();
22 | }
23 |
24 | private DataTemplate? _contentTemplate;
25 | public DataTemplate? ContentTemplate
26 | {
27 | get => _contentTemplate;
28 | set
29 | {
30 | _contentTemplate = value;
31 | OnPropertyChanged();
32 | }
33 | }
34 | protected override void SetBindingItemTemplate()
35 | {
36 | //base.SetBindingItemTemplate();
37 | }
38 | protected virtual void SetBindingContentTemplate()
39 | {
40 | BindingOperations.SetBinding(_tabControl,
41 | TabControl.ContentTemplateProperty,
42 | new Binding
43 | {
44 | Source = this,
45 | Path = new PropertyPath(nameof(ContentTemplate)),
46 | });
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation/Abstractions/BaseParameters.cs:
--------------------------------------------------------------------------------
1 | using Lemon.ModuleNavigation.Extensions;
2 | using System.Collections;
3 | using System.ComponentModel;
4 | using System.Text;
5 |
6 | namespace Lemon.ModuleNavigation.Abstractions;
7 |
8 | public abstract class BaseParameters : IEnumerable>, IEnumerable
9 | {
10 | private readonly List> _entries = [];
11 | public BaseParameters()
12 | {
13 |
14 | }
15 | public object? this[string key]
16 | {
17 | get
18 | {
19 | foreach (KeyValuePair entry in _entries)
20 | {
21 | if (string.Compare(entry.Key, key, StringComparison.Ordinal) == 0)
22 | {
23 | return entry.Value;
24 | }
25 | }
26 |
27 | return null;
28 | }
29 | }
30 | public int Count => _entries.Count;
31 | public IEnumerable Keys => _entries.Select((x) => x.Key);
32 |
33 | public void Add(string key, object value)
34 | {
35 | _entries.Add(new KeyValuePair(key, value));
36 | }
37 | public bool ContainsKey(string key)
38 | {
39 | return _entries.ContainsKey(key);
40 | }
41 | public IEnumerator> GetEnumerator()
42 | {
43 | return _entries.GetEnumerator();
44 | }
45 | public T? GetValue(string key) where T : notnull
46 | {
47 | return _entries.GetValue(key);
48 | }
49 | public IEnumerable GetValues(string key) where T : notnull
50 | {
51 | return _entries.GetValues(key);
52 | }
53 | public bool TryGetValue(string key, out T? value) where T : notnull
54 | {
55 | return _entries.TryGetValue(key, out value);
56 | }
57 | IEnumerator IEnumerable.GetEnumerator()
58 | {
59 | return GetEnumerator();
60 | }
61 | public override string ToString()
62 | {
63 | StringBuilder stringBuilder = new();
64 | if (_entries.Count > 0)
65 | {
66 | stringBuilder.Append('?');
67 | bool flag = true;
68 | foreach (KeyValuePair entry in _entries)
69 | {
70 | if (!flag)
71 | {
72 | stringBuilder.Append('&');
73 | }
74 | else
75 | {
76 | flag = false;
77 | }
78 |
79 | stringBuilder.Append(Uri.EscapeDataString(entry.Key));
80 | stringBuilder.Append('=');
81 | stringBuilder.Append(Uri.EscapeDataString(entry.Value != null ? entry.Value.ToString()! : ""));
82 | }
83 | }
84 |
85 | return stringBuilder.ToString();
86 | }
87 |
88 | [EditorBrowsable(EditorBrowsableState.Never)]
89 | public void FromParameters(IEnumerable> parameters)
90 | {
91 | _entries.AddRange(parameters);
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation/Abstractions/IContentRegionContext{TDataTemplate}.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 |
3 | namespace Lemon.ModuleNavigation.Abstractions;
4 |
5 | public interface IContentRegionContext : INotifyPropertyChanged
6 | {
7 | object? Content { get; set; }
8 | TDataTemplate? ContentTemplate { get; set; }
9 | }
10 |
11 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation/Abstractions/IDialogAware.cs:
--------------------------------------------------------------------------------
1 | namespace Lemon.ModuleNavigation.Abstractions;
2 |
3 | public interface IDialogAware
4 | {
5 | string Title { get; }
6 | event Action? RequestClose;
7 | void OnDialogClosed();
8 | void OnDialogOpened(IDialogParameters? parameters);
9 | }
10 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation/Abstractions/IDialogParameters.cs:
--------------------------------------------------------------------------------
1 | namespace Lemon.ModuleNavigation.Abstractions;
2 |
3 | public interface IDialogParameters
4 | {
5 | ///
6 | /// The number of parameters in the collection.
7 | ///
8 | int Count { get; }
9 |
10 | ///
11 | /// The keys in the collection.
12 | ///
13 | IEnumerable Keys { get; }
14 |
15 | ///
16 | /// Adds the key and value to the collection.
17 | ///
18 | ///
19 | ///
20 | void Add(string key, object value);
21 |
22 | ///
23 | /// Checks the collection for the presence of a key.
24 | ///
25 | ///
26 | ///
27 | bool ContainsKey(string key);
28 |
29 | ///
30 | /// Gets the parameter value referenced by a key.
31 | ///
32 | ///
33 | ///
34 | ///
35 | T? GetValue(string key) where T : notnull;
36 |
37 | ///
38 | /// Gets all parameter values referenced by a key.
39 | ///
40 | ///
41 | ///
42 | ///
43 | IEnumerable GetValues(string key) where T : notnull;
44 |
45 | ///
46 | /// Gets the parameter value if the referenced key exists.
47 | ///
48 | ///
49 | ///
50 | ///
51 | ///
52 | bool TryGetValue(string key, out T? value) where T : notnull;
53 | }
54 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation/Abstractions/IDialogResult.cs:
--------------------------------------------------------------------------------
1 | using Lemon.ModuleNavigation.Core;
2 |
3 | namespace Lemon.ModuleNavigation.Abstractions;
4 |
5 | public interface IDialogResult
6 | {
7 | ///
8 | /// The parameters from the dialog.
9 | ///
10 | IDialogParameters Parameters { get; }
11 |
12 | ///
13 | /// The result of the dialog.
14 | ///
15 | ButtonResult Result { get; }
16 | }
17 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation/Abstractions/IDialogService.cs:
--------------------------------------------------------------------------------
1 | namespace Lemon.ModuleNavigation.Abstractions;
2 |
3 | public interface IDialogService
4 | {
5 | void Show(string name,
6 | string? windowName = null,
7 | IDialogParameters? parameters = null,
8 | Action? callback = null);
9 |
10 | Task ShowDialog(string name,
11 | string? windowName = null,
12 | IDialogParameters? parameters = null,
13 | Action? callback = null);
14 |
15 | IDialogResult WaitShowDialog(string name,
16 | string? windowName = null,
17 | IDialogParameters? parameters = null);
18 | }
19 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation/Abstractions/IDialogWindow.cs:
--------------------------------------------------------------------------------
1 | namespace Lemon.ModuleNavigation.Abstractions;
2 |
3 | public interface IDialogWindowBase
4 | {
5 | string? Title { get; set; }
6 | object? Content { get; set; }
7 | object? DataContext { get; set; }
8 | event EventHandler Closed;
9 | void Close();
10 | void Show();
11 | }
12 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation/Abstractions/IItemsRegionDataContext{TDataTemplate}.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 |
3 | namespace Lemon.ModuleNavigation.Abstractions;
4 |
5 | public interface IItemsRegionDataContext : INotifyPropertyChanged
6 | {
7 | object? SelectedItem
8 | {
9 | get;
10 | set;
11 | }
12 | TDataTemplate? ItemTemplate
13 | {
14 | get;
15 | set;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation/Abstractions/IModule.cs:
--------------------------------------------------------------------------------
1 | namespace Lemon.ModuleNavigation.Abstractions;
2 |
3 | public interface IModule
4 | {
5 | public string Key
6 | {
7 | get;
8 | }
9 | public string? Alias
10 | {
11 | get;
12 | }
13 | public bool LoadOnDemand
14 | {
15 | get;
16 | }
17 | public bool IsInitialized
18 | {
19 | get;
20 | }
21 | public bool CanUnload
22 | {
23 | get;
24 | }
25 | public bool ForceNew
26 | {
27 | get;
28 | }
29 | public bool IsActivated
30 | {
31 | get;
32 | set;
33 | }
34 | public Type ViewType
35 | {
36 | get;
37 | }
38 | public Type ViewModelType
39 | {
40 | get;
41 | }
42 | public IView? View
43 | {
44 | get;
45 | }
46 | public IModuleNavigationAware? ViewModel
47 | {
48 | get;
49 | }
50 | public void Initialize();
51 | }
52 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation/Abstractions/IModuleManager.cs:
--------------------------------------------------------------------------------
1 | using Lemon.ModuleNavigation.Core;
2 | using System.Collections.ObjectModel;
3 |
4 | namespace Lemon.ModuleNavigation.Abstractions;
5 |
6 | public interface IModuleManager
7 | {
8 | IModule? CurrentModule { get; }
9 | IEnumerable Modules
10 | {
11 | get;
12 | }
13 | ObservableCollection ActiveModules
14 | {
15 | get;
16 | }
17 | IView CreateView(IModule module);
18 | IView GetOrCreateView(IModule module, string regionName);
19 | void RequestNavigate(string moduleName, NavigationParameters? parameters);
20 | void RequestNavigate(IModule module, NavigationParameters? parameters);
21 | }
22 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation/Abstractions/IModuleNavigationAware.cs:
--------------------------------------------------------------------------------
1 | namespace Lemon.ModuleNavigation.Abstractions
2 | {
3 | public interface IModuleNavigationAware
4 | {
5 |
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation/Abstractions/IModuleNavigationHandler.cs:
--------------------------------------------------------------------------------
1 | using Lemon.ModuleNavigation.Core;
2 |
3 | namespace Lemon.ModuleNavigation.Abstractions;
4 |
5 | public interface IModuleNavigationHandler
6 | {
7 | void OnNavigateTo(string moduleKey, NavigationParameters? parameters);
8 | }
9 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation/Abstractions/IModuleNavigationHandler{T}.cs:
--------------------------------------------------------------------------------
1 | using Lemon.ModuleNavigation.Core;
2 |
3 | namespace Lemon.ModuleNavigation.Abstractions;
4 |
5 | public interface IModuleNavigationHandler :IModuleNavigationHandler where T : IModule
6 | {
7 | void OnNavigateTo(T module, NavigationParameters? parameter);
8 | }
9 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation/Abstractions/IModuleNavigationService.cs:
--------------------------------------------------------------------------------
1 | using Lemon.ModuleNavigation.Core;
2 |
3 | namespace Lemon.ModuleNavigation.Abstractions;
4 |
5 | public interface IModuleNavigationService
6 | {
7 | IDisposable BindingNavigationHandler(IModuleNavigationHandler handler);
8 | void RequestModuleNavigate(string moduleKey, NavigationParameters? parameters);
9 | }
10 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation/Abstractions/IModuleNavigationService{T}.cs:
--------------------------------------------------------------------------------
1 | using Lemon.ModuleNavigation.Core;
2 |
3 | namespace Lemon.ModuleNavigation.Abstractions;
4 |
5 | public interface IModuleNavigationService : IModuleNavigationService where T : IModule
6 | {
7 | IDisposable BindingNavigationHandler(IModuleNavigationHandler handler);
8 | void RequestModuleNavigate(T module, NavigationParameters? parameters);
9 | }
10 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation/Abstractions/IModuleScope.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.DependencyInjection;
2 |
3 | namespace Lemon.ModuleNavigation.Abstractions;
4 |
5 | public interface IModuleScope
6 | {
7 | IServiceCollection ScopeServiceCollection { get; }
8 | IServiceProvider ScopeServiceProvider { get; }
9 | }
10 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation/Abstractions/IModuleServiceProvider.cs:
--------------------------------------------------------------------------------
1 | namespace Lemon.ModuleNavigation.Abstractions;
2 |
3 | public interface IModuleServiceProvider : IServiceProvider
4 | {
5 |
6 | }
7 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation/Abstractions/INavigationAware.cs:
--------------------------------------------------------------------------------
1 | namespace Lemon.ModuleNavigation.Abstractions;
2 |
3 | public interface INavigationAware
4 | {
5 | event Action? RequestUnload;
6 | void OnNavigatedTo(NavigationContext navigationContext);
7 | bool IsNavigationTarget(NavigationContext navigationContext);
8 | void OnNavigatedFrom(NavigationContext navigationContext);
9 | }
10 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation/Abstractions/INavigationHandler.cs:
--------------------------------------------------------------------------------
1 | namespace Lemon.ModuleNavigation.Abstractions;
2 |
3 | public interface INavigationHandler : IModuleNavigationHandler, IViewNavigationHandler
4 | {
5 | IRegionManager RegionManager { get; }
6 | IModuleManager ModuleManager { get; }
7 | }
8 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation/Abstractions/INavigationService.cs:
--------------------------------------------------------------------------------
1 | namespace Lemon.ModuleNavigation.Abstractions;
2 |
3 | public interface INavigationService: IModuleNavigationService, IViewNavigationService
4 | {
5 |
6 | }
7 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation/Abstractions/IRegion.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.ObjectModel;
2 |
3 | namespace Lemon.ModuleNavigation.Abstractions;
4 |
5 | public interface IRegion
6 | {
7 | string Name { get; }
8 | ObservableCollection Contexts
9 | {
10 | get;
11 | }
12 | void Activate(NavigationContext target);
13 | void DeActivate(NavigationContext target);
14 | void DeActivate(string viewName);
15 |
16 | ///ongoing: last view
17 | }
18 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation/Abstractions/IRegionManager.cs:
--------------------------------------------------------------------------------
1 | using Lemon.ModuleNavigation.Core;
2 |
3 | namespace Lemon.ModuleNavigation.Abstractions;
4 |
5 | public interface IRegionManager : IObservable, IObservable
6 | {
7 | void AddRegion(string regionName, IRegion region);
8 | IRegion? GetRegion(string regionName);
9 | void RequestViewNavigate(string regionName, string viewName, NavigationParameters? parameters = null, string? alias = null);
10 | [Obsolete("requestNew was obsolete.Consider IsNavigationTarget() in INavigationAware instead.")]
11 | void RequestNavigate(string regionName, string viewName, bool requestNew, NavigationParameters? parameters = null);
12 | void RequestViewUnload(string regionName, string viewName);
13 | void RequestViewUnload(NavigationContext context);
14 | void RequestModuleNavigate(string regionName, string moduleName, NavigationParameters? parameters);
15 | void RequestModuleNavigate(string regionName, IModule module, NavigationParameters? parameters);
16 | void RequestModuleUnload(string moduleName, string viewName);
17 | void RequestModuleUnload(IModule module);
18 | }
19 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation/Abstractions/IServiceAware.cs:
--------------------------------------------------------------------------------
1 | namespace Lemon.ModuleNavigation.Abstractions;
2 |
3 | public interface IServiceAware
4 | {
5 | IServiceProvider ServiceProvider { get; }
6 | }
7 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation/Abstractions/IServiceProviderDecorator.cs:
--------------------------------------------------------------------------------
1 | namespace Lemon.ModuleNavigation.Abstractions;
2 |
3 | public interface IServiceProviderDecorator : IServiceProvider
4 | {
5 |
6 | }
7 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation/Abstractions/IView.cs:
--------------------------------------------------------------------------------
1 | namespace Lemon.ModuleNavigation.Abstractions;
2 |
3 | public interface IView
4 | {
5 | object? DataContext
6 | {
7 | get;
8 | set;
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation/Abstractions/IViewManager.cs:
--------------------------------------------------------------------------------
1 | namespace Lemon.ModuleNavigation.Abstractions;
2 |
3 | public interface IViewManager : IObservable
4 | {
5 | IEnumerable Views { get; }
6 | }
7 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation/Abstractions/IViewNavigationHandler.cs:
--------------------------------------------------------------------------------
1 | using Lemon.ModuleNavigation.Core;
2 |
3 | namespace Lemon.ModuleNavigation.Abstractions;
4 |
5 | public interface IViewNavigationHandler
6 | {
7 | void OnNavigateTo(string regionName,
8 | string viewName,
9 | string? alias = null);
10 | void OnNavigateTo(string regionName,
11 | string viewName,
12 | NavigationParameters parameters,
13 | string? alias = null);
14 | void OnViewUnload(string regionName,
15 | string viewName);
16 | }
17 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation/Abstractions/IViewNavigationService.cs:
--------------------------------------------------------------------------------
1 | using Lemon.ModuleNavigation.Core;
2 |
3 | namespace Lemon.ModuleNavigation.Abstractions;
4 |
5 | public interface IViewNavigationService
6 | {
7 | IDisposable BindingViewNavigationHandler(IViewNavigationHandler handler);
8 | ///
9 | /// RequestViewNavigation
10 | ///
11 | ///
12 | ///
13 | ///
14 | /// requestNew can not decide wether to get a new instance of View,
15 | /// It is controlled by IsNavigationTarget() in INavigationAware.
16 | ///
17 | [Obsolete("requestNew was obsolete.Consider IsNavigationTarget() in INavigationAware instead.")]
18 | void RequestViewNavigation(string regionName,
19 | string viewName,
20 | bool requestNew);
21 |
22 | void RequestViewNavigation(string regionName,
23 | string viewName,
24 | string? alias = null);
25 |
26 | ///
27 | /// RequestViewNavigation
28 | ///
29 | ///
30 | ///
31 | ///
32 | ///
33 | /// requestNew can not decide wether to get a new instance of View,
34 | /// It is controlled by IsNavigationTarget() in INavigationAware.
35 | ///
36 | [Obsolete("requestNew was obsolete.Consider IsNavigationTarget() in INavigationAware instead.")]
37 | void RequestViewNavigation(string regionName,
38 | string viewName,
39 | NavigationParameters parameters,
40 | bool requestNew);
41 |
42 | void RequestViewNavigation(string regionName,
43 | string viewName,
44 | NavigationParameters parameters,
45 | string? alias = null);
46 |
47 | ///
48 | /// RequestViewUnload
49 | ///
50 | ///
51 | ///
52 | void RequestViewUnload(string regionName,
53 | string viewName);
54 | }
55 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation/Core/ButtonResult.cs:
--------------------------------------------------------------------------------
1 | namespace Lemon.ModuleNavigation.Core;
2 |
3 | public enum ButtonResult
4 | {
5 | Abort = 3,
6 | Cancel = 2,
7 | Ignore = 5,
8 | No = 7,
9 | None = 0,
10 | OK = 1,
11 | Retry = 4,
12 | Yes = 6
13 | }
14 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation/Core/ConcurrentItem.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Concurrent;
2 |
3 | namespace Lemon.ModuleNavigation.Core;
4 |
5 | public class ConcurrentItem
6 | {
7 | private const int SIZE = 1;
8 | private readonly BlockingCollection _collection = new(SIZE);
9 | public ConcurrentItem() { }
10 |
11 | public void SetData(T data)
12 | {
13 | if (_collection.Count == SIZE)
14 | {
15 | _collection.Take();
16 | }
17 | _collection.Add(data);
18 | }
19 |
20 | public T TakeData()
21 | {
22 | return _collection.Take();
23 | }
24 |
25 | public bool TryTakeData(out T? data)
26 | {
27 | return _collection.TryTake(out data);
28 | }
29 |
30 | public bool WaitForData(TimeSpan timeSpan, out T? data)
31 | {
32 | return _collection.TryTake(out data, timeSpan);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation/Core/DialogParameters.cs:
--------------------------------------------------------------------------------
1 | using Lemon.ModuleNavigation.Abstractions;
2 |
3 | namespace Lemon.ModuleNavigation.Core;
4 |
5 | public class DialogParameters : BaseParameters, IDialogParameters
6 | {
7 |
8 | }
9 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation/Core/DialogResult.cs:
--------------------------------------------------------------------------------
1 | using Lemon.ModuleNavigation.Abstractions;
2 |
3 | namespace Lemon.ModuleNavigation.Core;
4 |
5 | ///
6 | /// An that contains from the dialog
7 | /// and the of the dialog.
8 | ///
9 | public class DialogResult : IDialogResult
10 | {
11 | ///
12 | /// The parameters from the dialog.
13 | ///
14 | public IDialogParameters Parameters
15 | {
16 | get;
17 | private set;
18 | } = new DialogParameters();
19 |
20 | ///
21 | /// The result of the dialog.
22 | ///
23 | public ButtonResult Result
24 | {
25 | get;
26 | private set;
27 | } = ButtonResult.None;
28 |
29 | ///
30 | /// Initializes a new instance of the class.
31 | ///
32 | public DialogResult()
33 | {
34 | }
35 |
36 | ///
37 | /// Initializes a new instance of the class.
38 | ///
39 | /// The result of the dialog.
40 | public DialogResult(ButtonResult result)
41 | {
42 | Result = result;
43 | }
44 |
45 | ///
46 | /// Initializes a new instance of the class.
47 | ///
48 | /// The result of the dialog.
49 | /// The parameters from the dialog.
50 | public DialogResult(ButtonResult result, IDialogParameters parameters)
51 | {
52 | Result = result;
53 | Parameters = parameters;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation/Core/NavigationParameters.cs:
--------------------------------------------------------------------------------
1 | using Lemon.ModuleNavigation.Abstractions;
2 |
3 | namespace Lemon.ModuleNavigation.Core;
4 |
5 | public class NavigationParameters : BaseParameters
6 | {
7 |
8 | }
9 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation/Core/RegionNameNotFoundException.cs:
--------------------------------------------------------------------------------
1 | namespace Lemon.ModuleNavigation.Core;
2 |
3 | public class RegionNameNotFoundException : Exception
4 | {
5 | public RegionNameNotFoundException(string regionName) : base($"{regionName} was not found!")
6 | {
7 |
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation/Extensions/ListKeyValuePairExtensions.cs:
--------------------------------------------------------------------------------
1 | namespace Lemon.ModuleNavigation.Extensions;
2 |
3 | public static class ListKeyValuePairExtensions
4 | {
5 | public static bool ContainsKey(this List> list, string key)
6 | => !string.IsNullOrEmpty(key) && list.Any(kvp => string.Equals(kvp.Key, key, StringComparison.OrdinalIgnoreCase));
7 |
8 | public static T? GetValue(this List> list, string key)
9 | {
10 | if (string.IsNullOrEmpty(key))
11 | {
12 | throw new ArgumentNullException(nameof(key));
13 | }
14 |
15 | var kvp = list.FirstOrDefault(x => string.Equals(x.Key, key, StringComparison.OrdinalIgnoreCase));
16 | if (kvp.Key is null)
17 | throw new KeyNotFoundException($"Key was not found: {key}");
18 |
19 | try
20 | {
21 | return kvp.Value switch
22 | {
23 | null => default,
24 | string s when typeof(T).IsEnum => (T)Enum.Parse(typeof(T), s, true),
25 | _ when typeof(T) == typeof(string) => (T)(object)(kvp.Value.ToString() ?? string.Empty),
26 | IConvertible convertible => (T)Convert.ChangeType(convertible, typeof(T)),
27 | T typedValue => typedValue,
28 | _ => throw new InvalidCastException($"Cannot convert value of type '{kvp.Value.GetType().Name}' to {typeof(T).Name}")
29 | };
30 | }
31 | catch (Exception ex) when (ex is not InvalidCastException)
32 | {
33 | throw new InvalidCastException(
34 | $"Cannot convert the value '{kvp.Value}' of '{key}' to {typeof(T).Name}", ex);
35 | }
36 | }
37 |
38 | public static IEnumerable GetValues(this List> list, string key) where T : notnull
39 | {
40 | if (string.IsNullOrEmpty(key))
41 | return [];
42 |
43 | return list.Where(x => string.Equals(x.Key, key, StringComparison.OrdinalIgnoreCase))
44 | .Select(x => x.Value switch
45 | {
46 | null => throw new InvalidDataException($"The value of '{x.Key}' is null!"),
47 | string s when typeof(T).IsEnum => (T)Enum.Parse(typeof(T), s, true),
48 | _ when typeof(T) == typeof(string) => (T)(object)(x.Value.ToString() ?? string.Empty),
49 | IConvertible convertible => (T)Convert.ChangeType(convertible, typeof(T)),
50 | T typedValue => typedValue,
51 | _ => throw new InvalidCastException($"Cannot convert value of type '{x.Value.GetType().Name}' to {typeof(T).Name}")
52 | });
53 | }
54 |
55 | public static bool TryGetValue(this List> list, string key, out T? value)
56 | {
57 | value = default;
58 |
59 | if (string.IsNullOrEmpty(key))
60 | return false;
61 |
62 | var kvp = list.FirstOrDefault(x => string.Equals(x.Key, key, StringComparison.OrdinalIgnoreCase));
63 | if (kvp.Key is null)
64 | return false;
65 |
66 | try
67 | {
68 | value = kvp.Value switch
69 | {
70 | null => default,
71 | string s when typeof(T).IsEnum => (T)Enum.Parse(typeof(T), s, true),
72 | _ when typeof(T) == typeof(string) => (T)(object)(kvp.Value.ToString() ?? string.Empty),
73 | IConvertible convertible => (T)Convert.ChangeType(convertible, typeof(T)),
74 | T typedValue => typedValue,
75 | _ => default
76 | };
77 | return true;
78 | }
79 | catch
80 | {
81 | value = default;
82 | return false;
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation/Extensions/ObservableExtension.cs:
--------------------------------------------------------------------------------
1 | namespace Lemon.ModuleNavigation.Extensions;
2 |
3 | public static class ObservableExtension
4 | {
5 | public static IDisposable NavigationSubscribe(this IObservable observable, Action onNext)
6 | {
7 | var observer = new NavigationObserver(onNext);
8 | return observable.Subscribe(observer);
9 | }
10 | private class NavigationObserver : IObserver
11 | {
12 | private readonly Action _onNext;
13 |
14 | public NavigationObserver(Action onNext)
15 | {
16 | _onNext = onNext ?? throw new ArgumentNullException(nameof(onNext));
17 | }
18 |
19 | public void OnCompleted()
20 | {
21 |
22 | }
23 |
24 | public void OnError(Exception error)
25 | {
26 |
27 | }
28 |
29 | public void OnNext(T value)
30 | {
31 | _onNext(value);
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation/Extensions/ServiceCollectionExtensions.cs:
--------------------------------------------------------------------------------
1 | using Lemon.ModuleNavigation;
2 | using Lemon.ModuleNavigation.Abstractions;
3 | using Lemon.ModuleNavigation.Internal;
4 | using System.Diagnostics.CodeAnalysis;
5 |
6 | namespace Microsoft.Extensions.DependencyInjection;
7 |
8 | public static class ServiceCollectionExtensions
9 | {
10 | public static IServiceCollection AddView(this IServiceCollection serviceDescriptors, string viewKey)
11 | where TView : class, IView
12 | where TViewModel : class, INavigationAware
13 | {
14 | if (!ViewManager.InternalViewDiscriptions.TryAdd(key: viewKey,
15 | new ViewDiscription
16 | {
17 | ViewKey = viewKey,
18 | ViewClassName = typeof(TView).Name,
19 | ViewModelType = typeof(TViewModel),
20 | ViewType = typeof(TView)
21 | }))
22 | {
23 | throw new InvalidOperationException($"Duplicated key is not allowed:{viewKey}!");
24 | }
25 |
26 | serviceDescriptors
27 | .AddTransient()
28 | .AddTransient()
29 | .AddKeyedTransient(viewKey, (sp, key) =>
30 | {
31 | var view = sp.GetRequiredService();
32 | return view;
33 | })
34 | .AddKeyedTransient(viewKey, (sp, key) =>
35 | {
36 | var viewModel = sp.GetRequiredService();
37 | return viewModel;
38 | });
39 | if (typeof(TViewModel).IsAssignableTo(typeof(IDialogAware)))
40 | {
41 | serviceDescriptors.AddKeyedTransient(viewKey, (sp, key) =>
42 | {
43 | var viewModel = sp.GetRequiredService();
44 | return (IDialogAware)viewModel;
45 | });
46 | }
47 | return serviceDescriptors;
48 | }
49 | public static IServiceCollection AddModule<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TModule>(this IServiceCollection serviceDescriptors)
50 | where TModule : class, IModule
51 | {
52 | serviceDescriptors = serviceDescriptors
53 | .AddTransient()
54 | .AddKeyedTransient(typeof(TModule).Name, (sp, key) => sp.GetRequiredService())
55 | .AddKeyedTransient(nameof(IModule), (sp, key) => sp.GetRequiredService())
56 | .AddKeyedTransient(typeof(TModule).Name, (sp, key) =>
57 | {
58 | var module = sp.GetRequiredService();
59 | var view = ActivatorUtilities.CreateInstance(sp, module.ViewType);
60 | return (view as IView)!;
61 | })
62 | .AddKeyedTransient(typeof(TModule).Name, (sp, key) =>
63 | {
64 | var module = sp.GetRequiredService();
65 | var moduleSp = sp.GetKeyedService(typeof(TModule).Name);
66 | if (moduleSp == null)
67 | {
68 | var viewModel = ActivatorUtilities.CreateInstance(sp, module.ViewModelType);
69 | return (viewModel as IModuleNavigationAware)!;
70 | }
71 | else
72 | {
73 | var viewModel = ActivatorUtilities.CreateInstance(sp, module.ViewModelType, moduleSp);
74 | return (viewModel as IModuleNavigationAware)!;
75 | }
76 | })
77 | .AddKeyedTransient(typeof(TModule).Name, (sp, key) =>
78 | {
79 | var module = sp.GetRequiredService();
80 | if (module is IModuleScope moduleScope)
81 | {
82 | return new ModuleServiceProvider(moduleScope.ScopeServiceProvider!) as IModuleServiceProvider;
83 | }
84 | return default!;
85 | });
86 | return serviceDescriptors;
87 | }
88 |
89 | private static IServiceCollection AddModulesBuilder(this IServiceCollection serviceDescriptors)
90 | {
91 | serviceDescriptors = serviceDescriptors.AddSingleton(sp => sp.GetKeyedServices(nameof(IModule)));
92 | return serviceDescriptors;
93 | }
94 |
95 | ///
96 | /// AddNavigationSupport
97 | /// todo: auto generate with source generator
98 | ///
99 | ///
100 | ///
101 | public static IServiceCollection AddNavigationSupport(this IServiceCollection serviceDescriptors)
102 | {
103 | return serviceDescriptors
104 | .AddModulesBuilder()
105 | .AddSingleton()
106 | .AddSingleton>(sp => sp.GetRequiredService())
107 | .AddSingleton(sp => sp.GetRequiredService())
108 | .AddSingleton()
109 | .AddSingleton()
110 | .AddSingleton();
111 | }
112 |
113 | public static IServiceCollection AddAppServiceProvider(this IServiceCollection serviceDescriptors,
114 | IServiceProvider serviceProvider)
115 | {
116 | return serviceDescriptors.AddSingleton(_ => new ServiceProviderDecorator(serviceProvider));
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation/Internal/ConcurrentSet.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Concurrent;
3 |
4 | namespace Lemon.ModuleNavigation.Internal;
5 |
6 | internal sealed class ConcurrentSet : IEnumerable where T : notnull
7 | {
8 | private readonly ConcurrentDictionary _dict = new();
9 |
10 | public bool Add(T item)
11 | {
12 | return _dict.TryAdd(item, 0);
13 | }
14 |
15 | public bool Remove(T item)
16 | {
17 | return _dict.TryRemove(item, out _);
18 | }
19 |
20 | public bool Contains(T item)
21 | {
22 | return _dict.ContainsKey(item);
23 | }
24 |
25 | public IEnumerator GetEnumerator()
26 | {
27 | return _dict.Select(kv => kv.Key).GetEnumerator();
28 | }
29 |
30 | IEnumerator IEnumerable.GetEnumerator()
31 | {
32 | return GetEnumerator();
33 | }
34 |
35 | public void Clear()
36 | {
37 | _dict.Clear();
38 | }
39 |
40 | public int Count => _dict.Count;
41 | }
42 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation/Internal/DisposableAction.cs:
--------------------------------------------------------------------------------
1 | namespace Lemon.ModuleNavigation.Internal;
2 |
3 | internal sealed class DisposableAction : IDisposable
4 | {
5 | private readonly Action _action;
6 | private int _disposed;
7 |
8 | public DisposableAction(Action action)
9 | {
10 | _action = action;
11 | }
12 |
13 | public void Dispose()
14 | {
15 | if (Interlocked.Exchange(ref _disposed, 1) == 0)
16 | {
17 | _action();
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation/Internal/ModuleServiceProvider.cs:
--------------------------------------------------------------------------------
1 | using Lemon.ModuleNavigation.Abstractions;
2 |
3 | namespace Lemon.ModuleNavigation.Internal;
4 |
5 | internal sealed class ModuleServiceProvider : IModuleServiceProvider
6 | {
7 | private readonly IServiceProvider _serviceProvider;
8 | public ModuleServiceProvider(IServiceProvider serviceProvider)
9 | {
10 | _serviceProvider = serviceProvider;
11 | }
12 |
13 | public object? GetService(Type serviceType)
14 | {
15 | return GetServiceInternal(serviceType);
16 | }
17 |
18 | private object? GetServiceInternal(Type serviceType)
19 | {
20 | return _serviceProvider.GetService(serviceType);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation/Lemon.ModuleNavigation.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | net8.0;net6.0
6 | enable
7 | enable
8 | latest
9 | True
10 | Lemon.ModuleNavigation
11 | lemon-100.png
12 | A lightweight module navigation framework built on top of the Microsoft Dependency Injection (MSDI) for AvaloniaUI,WPF and Xaml-platform else. Support nativeAot
13 | README.md
14 | ../output
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | True
30 | \
31 |
32 |
33 | True
34 | \
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation/ModuleManager.cs:
--------------------------------------------------------------------------------
1 | using Lemon.ModuleNavigation.Abstractions;
2 | using Lemon.ModuleNavigation.Core;
3 | using Microsoft.Extensions.DependencyInjection;
4 | using System.Collections.Concurrent;
5 | using System.Collections.ObjectModel;
6 | using System.ComponentModel;
7 | using System.Runtime.CompilerServices;
8 |
9 | namespace Lemon.ModuleNavigation;
10 |
11 | public class ModuleManager : IModuleManager, INotifyPropertyChanged
12 | {
13 | private readonly ConcurrentDictionary _modulesCache;
14 | private readonly ConcurrentDictionary<(string RegionName, string ModuleKey), IView> _regionCache;
15 | private readonly IServiceProvider _serviceProvider;
16 | private readonly IRegionManager _regionManager;
17 | public ModuleManager(IEnumerable modules,
18 | IRegionManager regionManager,
19 | IServiceProvider serviceProvider)
20 | {
21 | _serviceProvider = serviceProvider;
22 | _regionManager = regionManager;
23 | _regionCache = [];
24 | _modulesCache = new ConcurrentDictionary(modules.ToDictionary(m => m.Key, m => m));
25 | Modules = _modulesCache.Values;
26 | ActiveModules = [.. _modulesCache
27 | .Where(m =>
28 | {
29 | return !m.Value.LoadOnDemand;
30 | })
31 | .Select(m =>
32 | {
33 | m.Value.Initialize();
34 | return m.Value;
35 | })];
36 | }
37 | public ObservableCollection ActiveModules
38 | {
39 | get;
40 | }
41 |
42 | public IEnumerable Modules
43 | {
44 | get;
45 | }
46 |
47 | private IModule? _currentModule;
48 | public IModule? CurrentModule
49 | {
50 | get => _currentModule;
51 | set
52 | {
53 | if (_currentModule != value)
54 | {
55 | _currentModule = value;
56 | OnPropertyChanged();
57 | }
58 | }
59 | }
60 | public event PropertyChangedEventHandler? PropertyChanged;
61 | protected void OnPropertyChanged([CallerMemberName] string? propertyName = null)
62 | {
63 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
64 | }
65 |
66 | public void RequestNavigate(string moduleName, NavigationParameters? parameters)
67 | {
68 | if (_modulesCache.TryGetValue(moduleName, out var module))
69 | {
70 | RequestNavigate(module, parameters);
71 | }
72 | else
73 | {
74 | throw new KeyNotFoundException(moduleName);
75 | }
76 | }
77 |
78 | public void RequestNavigate(IModule module, NavigationParameters? parameters)
79 | {
80 | if (module.ForceNew)
81 | {
82 | module = _serviceProvider.GetKeyedService(module.Key)!;
83 | ActiveModules.Add(module);
84 | }
85 | else
86 | {
87 | if (!ActiveModules.Contains(module))
88 | {
89 | ActiveModules.Add(module);
90 | }
91 | }
92 | ///TODO:Consider an async implementation
93 | module.Initialize();
94 | module.IsActivated = true;
95 | CurrentModule = module;
96 | }
97 |
98 | public IView CreateView(IModule module)
99 | {
100 | var view = _serviceProvider.GetRequiredKeyedService(module.Key);
101 | var viewmodel = _serviceProvider.GetRequiredKeyedService(module.Key);
102 | view.DataContext = viewmodel;
103 | return view;
104 | }
105 | public IView GetOrCreateView(IModule module, string regionName)
106 | {
107 | if (module.ForceNew)
108 | {
109 | return CreateView(module);
110 | }
111 | if (_regionCache.TryGetValue((regionName, module.Key), out IView? cache))
112 | {
113 | return cache;
114 | }
115 | else
116 | {
117 | IView view;
118 | if (!IsRenderedOnAnyRegion(module.Key))
119 | {
120 | view = module.View!;
121 | }
122 | else
123 | {
124 | view = CreateView(module);
125 | }
126 | _regionCache.TryAdd((regionName, module.Key), view);
127 | return view;
128 | }
129 | }
130 |
131 | private bool IsRenderedOnAnyRegion(string moduleKey)
132 | {
133 | if (!_regionCache.IsEmpty)
134 | {
135 | return _regionCache.Select(r => r.Key.ModuleKey).Contains(moduleKey);
136 | }
137 | return false;
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/src/Lemon.ModuleNavigation/Module{TView,TViewModel}.cs:
--------------------------------------------------------------------------------
1 | using Lemon.ModuleNavigation.Abstractions;
2 | using Microsoft.Extensions.DependencyInjection;
3 | using System.Diagnostics.CodeAnalysis;
4 |
5 | namespace Lemon.ModuleNavigation;
6 |
7 | public abstract class Module<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TView,
8 | [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TViewModel>
9 | : IModule
10 | where TViewModel : IModuleNavigationAware
11 | where TView : IView
12 | {
13 | protected readonly IServiceProvider ServiceProvider;
14 | public Module(IServiceProvider serviceProvider)
15 | {
16 | ServiceProvider = serviceProvider;
17 | }
18 | public virtual void Initialize()
19 | {
20 | lock (this)
21 | {
22 | if (IsInitialized) return;
23 | View = ServiceProvider.GetRequiredKeyedService(Key);
24 | ViewModel = ServiceProvider.GetRequiredKeyedService