├── .github ├── FUNDING.yml └── workflows │ └── dotnet-core.yml ├── .gitignore ├── .template.config └── template.json ├── CreateNugetPackage.bat ├── LICENSE ├── README.md ├── TwincatWpfHMI.template.nuspec ├── WpfApp.Gui ├── App.xaml ├── App.xaml.cs ├── Contents.Designer.cs ├── Contents.de.Designer.cs ├── Contents.de.resx ├── Contents.resx ├── Converters │ ├── BoolToBrushConverter.cs │ ├── BoolToStyleConverter.cs │ ├── BoolToVisibilityConverter.cs │ ├── ConnectionStateToVisibilityConverter.cs │ ├── MinimalRoleToVisibilityConverter.cs │ └── RoleToVisibilityConverter.cs ├── Design │ ├── DesignPlcEventService.cs │ ├── DesignPlcProvider.cs │ └── DesignSettingsProvider.cs ├── GuiModuleCatalog.cs ├── Resources │ └── icon.png ├── Services │ └── PresentationService.cs ├── ViewModelLocator.cs ├── ViewModels │ ├── Basics │ │ ├── PlcErrorBarViewModel.cs │ │ ├── PlcErrorDetailsViewModel.cs │ │ └── PlcVariableViewModel.cs │ ├── GraphViewModel.cs │ ├── MainViewModel.cs │ ├── MainWindowViewModel.cs │ ├── SettingsViewModel.cs │ └── ViewModelBase.cs ├── Views │ ├── Basics │ │ ├── PlcButton.xaml │ │ ├── PlcButton.xaml.cs │ │ ├── PlcErrorBar.xaml │ │ ├── PlcErrorBar.xaml.cs │ │ ├── PlcErrorDetails.xaml │ │ ├── PlcErrorDetails.xaml.cs │ │ ├── PlcSignalOkNok.xaml │ │ ├── PlcSignalOkNok.xaml.cs │ │ ├── PlcSignalOnOff.xaml │ │ ├── PlcSignalOnOff.xaml.cs │ │ ├── PlcUserControl.cs │ │ ├── PlcVariable.xaml │ │ └── PlcVariable.xaml.cs │ ├── MainWindow.xaml │ ├── MainWindow.xaml.cs │ └── Pages │ │ ├── GraphView.xaml │ │ ├── GraphView.xaml.cs │ │ ├── MainView.xaml │ │ ├── MainView.xaml.cs │ │ ├── SettingsView.xaml │ │ └── SettingsView.xaml.cs └── WpfApp.Gui.csproj ├── WpfApp.Interfaces ├── Commons │ ├── IInitializable.cs │ ├── IInstanceCreator.cs │ └── IViewModelFactory.cs ├── Constants.cs ├── Enums │ ├── Role.cs │ └── Severity.cs ├── Exceptions │ └── LoginFailedException.cs ├── Extensions │ ├── DisposableExtensions.cs │ └── RandomExtensions.cs ├── Hardware │ ├── IPlc.cs │ └── MockPlc.cs ├── Models │ ├── DatabaseObject.cs │ ├── PlcEvent.cs │ └── User.cs ├── Services │ ├── IDatabaseService.cs │ ├── IDirectoryService.cs │ ├── IPlcEventLogService.cs │ ├── IPlcEventService.cs │ ├── IPlcProvider.cs │ ├── ISelectionService.cs │ ├── ISettingsProvider.cs │ └── IUserService.cs ├── Settings │ ├── ApplicationSetting.cs │ ├── CultureSetting.cs │ ├── ErrorCodeDescription.cs │ ├── ErrorCodeSetting.cs │ ├── ErrorSetting.cs │ ├── HardwareSetting.cs │ ├── PlcSetting.cs │ └── SettingRoot.cs ├── Ui │ ├── IPresentationService.cs │ └── IViewModel.cs └── WpfApp.Interfaces.csproj ├── WpfApp.Logic ├── Hardware │ ├── BeckhoffConversions.cs │ └── BeckhoffPlc.cs ├── LogicModuleCatalog.cs ├── Services │ ├── DatabaseService.cs │ ├── DirectoryService.cs │ ├── PlcEventLogService.cs │ ├── PlcEventService.cs │ ├── PlcProvider.cs │ ├── SelectionService.cs │ ├── SettingsService.cs │ └── UserService.cs └── WpfApp.Logic.csproj ├── WpfApp.sln ├── WpfApp ├── Program.cs ├── WpfApp.csproj └── icon.ico └── docs └── example1.gif /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: fbarresi 4 | -------------------------------------------------------------------------------- /.github/workflows/dotnet-core.yml: -------------------------------------------------------------------------------- 1 | name: .NET Core Build 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: windows-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Setup .NET Core 17 | uses: actions/setup-dotnet@v1 18 | with: 19 | dotnet-version: 3.1.101 20 | - name: Install dependencies 21 | run: dotnet restore 22 | - name: Build 23 | run: dotnet build --configuration Release --no-restore 24 | - name: Test 25 | run: dotnet test --no-restore --verbosity normal 26 | 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015/2017 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # Visual Studio 2017 auto generated files 33 | Generated\ Files/ 34 | 35 | # MSTest test Results 36 | [Tt]est[Rr]esult*/ 37 | [Bb]uild[Ll]og.* 38 | 39 | # NUNIT 40 | *.VisualState.xml 41 | TestResult.xml 42 | 43 | # Build Results of an ATL Project 44 | [Dd]ebugPS/ 45 | [Rr]eleasePS/ 46 | dlldata.c 47 | 48 | # Benchmark Results 49 | BenchmarkDotNet.Artifacts/ 50 | 51 | # .NET Core 52 | project.lock.json 53 | project.fragment.lock.json 54 | artifacts/ 55 | **/Properties/launchSettings.json 56 | 57 | # StyleCop 58 | StyleCopReport.xml 59 | 60 | # Files built by Visual Studio 61 | *_i.c 62 | *_p.c 63 | *_i.h 64 | *.ilk 65 | *.meta 66 | *.obj 67 | *.iobj 68 | *.pch 69 | *.pdb 70 | *.ipdb 71 | *.pgc 72 | *.pgd 73 | *.rsp 74 | *.sbr 75 | *.tlb 76 | *.tli 77 | *.tlh 78 | *.tmp 79 | *.tmp_proj 80 | *.log 81 | *.vspscc 82 | *.vssscc 83 | .builds 84 | *.pidb 85 | *.svclog 86 | *.scc 87 | 88 | # Chutzpah Test files 89 | _Chutzpah* 90 | 91 | # Visual C++ cache files 92 | ipch/ 93 | *.aps 94 | *.ncb 95 | *.opendb 96 | *.opensdf 97 | *.sdf 98 | *.cachefile 99 | *.VC.db 100 | *.VC.VC.opendb 101 | 102 | # Visual Studio profiler 103 | *.psess 104 | *.vsp 105 | *.vspx 106 | *.sap 107 | 108 | # Visual Studio Trace Files 109 | *.e2e 110 | 111 | # TFS 2012 Local Workspace 112 | $tf/ 113 | 114 | # Guidance Automation Toolkit 115 | *.gpState 116 | 117 | # ReSharper is a .NET coding add-in 118 | _ReSharper*/ 119 | *.[Rr]e[Ss]harper 120 | *.DotSettings.user 121 | 122 | # JustCode is a .NET coding add-in 123 | .JustCode 124 | 125 | # TeamCity is a build add-in 126 | _TeamCity* 127 | 128 | # DotCover is a Code Coverage Tool 129 | *.dotCover 130 | 131 | # AxoCover is a Code Coverage Tool 132 | .axoCover/* 133 | !.axoCover/settings.json 134 | 135 | # Visual Studio code coverage results 136 | *.coverage 137 | *.coveragexml 138 | 139 | # NCrunch 140 | _NCrunch_* 141 | .*crunch*.local.xml 142 | nCrunchTemp_* 143 | 144 | # MightyMoose 145 | *.mm.* 146 | AutoTest.Net/ 147 | 148 | # Web workbench (sass) 149 | .sass-cache/ 150 | 151 | # Installshield output folder 152 | [Ee]xpress/ 153 | 154 | # DocProject is a documentation generator add-in 155 | DocProject/buildhelp/ 156 | DocProject/Help/*.HxT 157 | DocProject/Help/*.HxC 158 | DocProject/Help/*.hhc 159 | DocProject/Help/*.hhk 160 | DocProject/Help/*.hhp 161 | DocProject/Help/Html2 162 | DocProject/Help/html 163 | 164 | # Click-Once directory 165 | publish/ 166 | 167 | # Publish Web Output 168 | *.[Pp]ublish.xml 169 | *.azurePubxml 170 | # Note: Comment the next line if you want to checkin your web deploy settings, 171 | # but database connection strings (with potential passwords) will be unencrypted 172 | *.pubxml 173 | *.publishproj 174 | 175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 176 | # checkin your Azure Web App publish settings, but sensitive information contained 177 | # in these scripts will be unencrypted 178 | PublishScripts/ 179 | 180 | # NuGet Packages 181 | *.nupkg 182 | # The packages folder can be ignored because of Package Restore 183 | **/[Pp]ackages/* 184 | # except build/, which is used as an MSBuild target. 185 | !**/[Pp]ackages/build/ 186 | # Uncomment if necessary however generally it will be regenerated when needed 187 | #!**/[Pp]ackages/repositories.config 188 | # NuGet v3's project.json files produces more ignorable files 189 | *.nuget.props 190 | *.nuget.targets 191 | 192 | # Microsoft Azure Build Output 193 | csx/ 194 | *.build.csdef 195 | 196 | # Microsoft Azure Emulator 197 | ecf/ 198 | rcf/ 199 | 200 | # Windows Store app package directories and files 201 | AppPackages/ 202 | BundleArtifacts/ 203 | Package.StoreAssociation.xml 204 | _pkginfo.txt 205 | *.appx 206 | 207 | # Visual Studio cache files 208 | # files ending in .cache can be ignored 209 | *.[Cc]ache 210 | # but keep track of directories ending in .cache 211 | !*.[Cc]ache/ 212 | 213 | # Others 214 | ClientBin/ 215 | ~$* 216 | *~ 217 | *.dbmdl 218 | *.dbproj.schemaview 219 | *.jfm 220 | *.pfx 221 | *.publishsettings 222 | orleans.codegen.cs 223 | 224 | # Including strong name files can present a security risk 225 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 226 | #*.snk 227 | 228 | # Since there are multiple workflows, uncomment next line to ignore bower_components 229 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 230 | #bower_components/ 231 | 232 | # RIA/Silverlight projects 233 | Generated_Code/ 234 | 235 | # Backup & report files from converting an old project file 236 | # to a newer Visual Studio version. Backup files are not needed, 237 | # because we have git ;-) 238 | _UpgradeReport_Files/ 239 | Backup*/ 240 | UpgradeLog*.XML 241 | UpgradeLog*.htm 242 | ServiceFabricBackup/ 243 | *.rptproj.bak 244 | 245 | # SQL Server files 246 | *.mdf 247 | *.ldf 248 | *.ndf 249 | 250 | # Business Intelligence projects 251 | *.rdl.data 252 | *.bim.layout 253 | *.bim_*.settings 254 | *.rptproj.rsuser 255 | 256 | # Microsoft Fakes 257 | FakesAssemblies/ 258 | 259 | # GhostDoc plugin setting file 260 | *.GhostDoc.xml 261 | 262 | # Node.js Tools for Visual Studio 263 | .ntvs_analysis.dat 264 | node_modules/ 265 | 266 | # Visual Studio 6 build log 267 | *.plg 268 | 269 | # Visual Studio 6 workspace options file 270 | *.opt 271 | 272 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 273 | *.vbw 274 | 275 | # Visual Studio LightSwitch build output 276 | **/*.HTMLClient/GeneratedArtifacts 277 | **/*.DesktopClient/GeneratedArtifacts 278 | **/*.DesktopClient/ModelManifest.xml 279 | **/*.Server/GeneratedArtifacts 280 | **/*.Server/ModelManifest.xml 281 | _Pvt_Extensions 282 | 283 | # Paket dependency manager 284 | .paket/paket.exe 285 | paket-files/ 286 | 287 | # FAKE - F# Make 288 | .fake/ 289 | 290 | # JetBrains Rider 291 | .idea/ 292 | *.sln.iml 293 | 294 | # CodeRush 295 | .cr/ 296 | 297 | # Python Tools for Visual Studio (PTVS) 298 | __pycache__/ 299 | *.pyc 300 | 301 | # Cake - Uncomment if you are using it 302 | # tools/** 303 | # !tools/packages.config 304 | 305 | # Tabs Studio 306 | *.tss 307 | 308 | # Telerik's JustMock configuration file 309 | *.jmconfig 310 | 311 | # BizTalk build output 312 | *.btp.cs 313 | *.btm.cs 314 | *.odx.cs 315 | *.xsd.cs 316 | 317 | # OpenCover UI analysis results 318 | OpenCover/ 319 | 320 | # Azure Stream Analytics local run output 321 | ASALocalRun/ 322 | 323 | # MSBuild Binary and Structured Log 324 | *.binlog 325 | 326 | # NVidia Nsight GPU debugger configuration file 327 | *.nvuser 328 | 329 | # MFractors (Xamarin productivity tool) working folder 330 | .mfractor/ 331 | -------------------------------------------------------------------------------- /.template.config/template.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/template", 3 | "author": "Federico Barresi", 4 | "classifications": [ 5 | "Common", "WPF" 6 | ], 7 | "preferNameDirectory": "true", 8 | "sourceName": "WpfApp", 9 | "identity": "TwincatWpfHMI.template", 10 | "name": "Twincat WPF HMI", 11 | "shortName": "tchmi", 12 | "tags": { 13 | "language": "C#", 14 | "type": "project" 15 | } 16 | } -------------------------------------------------------------------------------- /CreateNugetPackage.bat: -------------------------------------------------------------------------------- 1 | nuget pack TwincatWpfHMI.template.nuspec -NoDefaultExcludes -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Federico Barresi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # twincat-wpf-boilerplate 2 | an hackable template for building fabulous wpf hmi with twincat 3 | 4 | ![.NET Core Build](https://github.com/fbarresi/twincat-wpf-boilerplate/workflows/.NET%20Core%20Build/badge.svg) 5 | 6 | ## Main features 7 | 8 | - :heavy_check_mark: An easy-to-use alternative to other Beckhoff HMI engines (web or plc based) 9 | - :heavy_check_mark: runs on every network connected device 10 | - :heavy_check_mark: Professional application (Embedded Database, cleaned architecture, open source) 11 | - :heavy_check_mark: Low code and XAML based: get nice usable dashboards in seconds only with XAML design 12 | - :heavy_check_mark: Fully hackable, customizable and extendable with c# 13 | 14 | 15 | ## Example 16 | 17 | ![](docs/example1.gif) 18 | 19 | 20 | ## Quickstart 21 | 22 | ### Create your application 23 | 24 | 1. Install the template via console or powershell 25 | ``` 26 | dotnet new --install TwincatWpfHMI.template 27 | ``` 28 | 29 | 2. Create your application with name `MyFancyHMI` 30 | 31 | ``` 32 | dotnet new tchmi -n MyFancyHMI -o MyFancyWorkingDirectory 33 | ``` 34 | 35 | ### Design your HMI in XAML 36 | 37 | #### With included controls 38 | 39 | `todo` 40 | 41 | #### With your custom controls 42 | 43 | Create new user controls by: 44 | 45 | - creating your custom controls and view models in `WpfApp.Gui\View` and `WpfApp.Gui\ViewModels` 46 | - implementing `PlcUserControl` in XAML and in code-behind 47 | - adding one of the included DataContext (`DataContext="{Binding PlcVariableViewModel, Source={StaticResource Locator}}"`) 48 | 49 | XAML (.xaml) 50 | ```xml 51 | 61 | 62 | 63 | 66 | 67 | 68 | ``` 69 | 70 | Code-behind (xaml.cs) 71 | ```csharp 72 | public partial class PlcVariableView : PlcUserControl 73 | { 74 | public PlcVariableView() 75 | { 76 | InitializeComponent(); 77 | } 78 | } 79 | ``` 80 | 81 | ### Run the application 82 | 83 | `todo` 84 | 85 | -------------------------------------------------------------------------------- /TwincatWpfHMI.template.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | TwincatWpfHMI.template 5 | 1.0.10 6 | 7 | A TwinCAT HMI as a WPF application. 8 | 9 | https://github.com/fbarresi/twincat-wpf-boilerplate 10 | Federico Barresi 11 | 12 | 13 | 14 | MIT 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /WpfApp.Gui/App.xaml: -------------------------------------------------------------------------------- 1 |  7 | 8 | 9 | 10 | True 11 | False 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 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /WpfApp.Gui/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | namespace WpfApp.Gui 4 | { 5 | /// 6 | /// Interaction logic for App.xaml 7 | /// 8 | public partial class App : Application 9 | { 10 | } 11 | } -------------------------------------------------------------------------------- /WpfApp.Gui/Contents.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace WpfApp.Gui.Resources { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Contents { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Contents() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("WpfApp.Gui.Contents", typeof(Contents).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | 63 | /// 64 | /// Looks up a localized string similar to Save. 65 | /// 66 | internal static string Save { 67 | get { 68 | return ResourceManager.GetString("Save", resourceCulture); 69 | } 70 | } 71 | 72 | /// 73 | /// Looks up a localized string similar to Settings. 74 | /// 75 | internal static string Settings { 76 | get { 77 | return ResourceManager.GetString("Settings", resourceCulture); 78 | } 79 | } 80 | 81 | /// 82 | /// Looks up a localized string similar to Button. 83 | /// 84 | internal static string TestButton { 85 | get { 86 | return ResourceManager.GetString("TestButton", resourceCulture); 87 | } 88 | } 89 | 90 | /// 91 | /// Looks up a localized string similar to Description. 92 | /// 93 | internal static string TestSignalDescription { 94 | get { 95 | return ResourceManager.GetString("TestSignalDescription", resourceCulture); 96 | } 97 | } 98 | 99 | /// 100 | /// Looks up a localized string similar to Lamp. 101 | /// 102 | internal static string TestSignalLabel { 103 | get { 104 | return ResourceManager.GetString("TestSignalLabel", resourceCulture); 105 | } 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /WpfApp.Gui/Contents.de.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace WpfApp.Gui { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Contents_de { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Contents_de() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("WpfApp.Gui.Contents.de", typeof(Contents_de).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | 63 | /// 64 | /// Looks up a localized string similar to Speichern. 65 | /// 66 | internal static string Save { 67 | get { 68 | return ResourceManager.GetString("Save", resourceCulture); 69 | } 70 | } 71 | 72 | /// 73 | /// Looks up a localized string similar to Einstellungen. 74 | /// 75 | internal static string Settings { 76 | get { 77 | return ResourceManager.GetString("Settings", resourceCulture); 78 | } 79 | } 80 | 81 | /// 82 | /// Looks up a localized string similar to Knopf. 83 | /// 84 | internal static string TestButton { 85 | get { 86 | return ResourceManager.GetString("TestButton", resourceCulture); 87 | } 88 | } 89 | 90 | /// 91 | /// Looks up a localized string similar to Beschreibung. 92 | /// 93 | internal static string TestSignalDescription { 94 | get { 95 | return ResourceManager.GetString("TestSignalDescription", resourceCulture); 96 | } 97 | } 98 | 99 | /// 100 | /// Looks up a localized string similar to Lampe. 101 | /// 102 | internal static string TestSignalLabel { 103 | get { 104 | return ResourceManager.GetString("TestSignalLabel", resourceCulture); 105 | } 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /WpfApp.Gui/Contents.de.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | text/microsoft-resx 11 | 12 | 13 | 1.3 14 | 15 | 16 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 17 | 18 | 19 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 20 | 21 | 22 | Beschreibung 23 | 24 | 25 | Lampe 26 | 27 | 28 | Speichern 29 | 30 | 31 | Einstellungen 32 | 33 | 34 | Knopf 35 | 36 | -------------------------------------------------------------------------------- /WpfApp.Gui/Contents.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | text/microsoft-resx 11 | 12 | 13 | 1.3 14 | 15 | 16 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 17 | 18 | 19 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 20 | 21 | 22 | Lamp 23 | 24 | 25 | Description 26 | 27 | 28 | Save 29 | 30 | 31 | Settings 32 | 33 | 34 | Button 35 | 36 | -------------------------------------------------------------------------------- /WpfApp.Gui/Converters/BoolToBrushConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows; 4 | using System.Windows.Data; 5 | using System.Windows.Media; 6 | 7 | namespace WpfApp.Gui.Converters 8 | { 9 | public class BoolToBrushConverter : IValueConverter 10 | { 11 | public Brush IfTrue { get; set; } = Brushes.Green; 12 | public Brush IfFalse { get; set; } = Brushes.Red; 13 | 14 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 15 | { 16 | if (value is bool) 17 | { 18 | return (bool) value ? IfTrue : IfFalse; 19 | } 20 | 21 | return DependencyProperty.UnsetValue; 22 | 23 | } 24 | 25 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 26 | { 27 | return DependencyProperty.UnsetValue; 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /WpfApp.Gui/Converters/BoolToStyleConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows; 4 | using System.Windows.Data; 5 | using System.Windows.Media; 6 | 7 | namespace WpfApp.Gui.Converters 8 | { 9 | public class BoolToStyleConverter : IValueConverter 10 | { 11 | public Style IfTrue { get; set; } 12 | public Style IfFalse { get; set; } 13 | 14 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 15 | { 16 | if (value is bool) 17 | { 18 | return (bool) value ? IfTrue : IfFalse; 19 | } 20 | 21 | return DependencyProperty.UnsetValue; 22 | 23 | } 24 | 25 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 26 | { 27 | return DependencyProperty.UnsetValue; 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /WpfApp.Gui/Converters/BoolToVisibilityConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows; 4 | using System.Windows.Data; 5 | 6 | namespace WpfApp.Gui.Converters 7 | { 8 | public class BoolToVisibilityConverter : IValueConverter 9 | { 10 | public Visibility IfTrue { get; set; } = Visibility.Visible; 11 | public Visibility IfFalse { get; set; } = Visibility.Collapsed; 12 | 13 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 14 | { 15 | if (value is bool) 16 | { 17 | return (bool) value ? IfTrue : IfFalse; 18 | } 19 | 20 | return DependencyProperty.UnsetValue; 21 | 22 | } 23 | 24 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 25 | { 26 | return DependencyProperty.UnsetValue; 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /WpfApp.Gui/Converters/ConnectionStateToVisibilityConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows; 4 | using System.Windows.Data; 5 | using TwinCAT; 6 | 7 | namespace WpfApp.Gui.Converters 8 | { 9 | public class ConnectionStateToVisibilityConverter : IValueConverter 10 | { 11 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 12 | { 13 | if (value is ConnectionState) 14 | { 15 | return (ConnectionState) value == ConnectionState.Connected ? Visibility.Collapsed : Visibility.Visible; 16 | } 17 | 18 | return DependencyProperty.UnsetValue; 19 | 20 | } 21 | 22 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 23 | { 24 | return DependencyProperty.UnsetValue; 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /WpfApp.Gui/Converters/MinimalRoleToVisibilityConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Linq; 5 | using System.Windows; 6 | using System.Windows.Data; 7 | using WpfApp.Interfaces.Enums; 8 | 9 | namespace WpfApp.Gui.Converters 10 | { 11 | public class MinimalRoleToVisibilityConverter : IValueConverter 12 | { 13 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 14 | { 15 | var parsed = Enum.TryParse((string)parameter, out Role parsedRole); 16 | 17 | if (value is List && parsed) 18 | { 19 | return ((List) value).Any(r => r <= parsedRole) ? Visibility.Visible : Visibility.Collapsed; 20 | } 21 | 22 | return Visibility.Collapsed; 23 | 24 | } 25 | 26 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 27 | { 28 | return DependencyProperty.UnsetValue; 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /WpfApp.Gui/Converters/RoleToVisibilityConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Windows; 5 | using System.Windows.Data; 6 | using WpfApp.Interfaces.Enums; 7 | 8 | namespace WpfApp.Gui.Converters 9 | { 10 | public class RoleToVisibilityConverter : IValueConverter 11 | { 12 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 13 | { 14 | var parsed = Enum.TryParse((string)parameter, out Role parsedRole); 15 | 16 | if (value is List && parsed) 17 | { 18 | return ((List) value).Contains(parsedRole) ? Visibility.Visible : Visibility.Collapsed; 19 | } 20 | 21 | return Visibility.Collapsed; 22 | 23 | } 24 | 25 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 26 | { 27 | return DependencyProperty.UnsetValue; 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /WpfApp.Gui/Design/DesignPlcEventService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reactive.Linq; 3 | using WpfApp.Interfaces.Models; 4 | using WpfApp.Interfaces.Services; 5 | 6 | namespace WpfApp.Gui.Design 7 | { 8 | internal class DesignPlcEventService : IPlcEventService 9 | { 10 | public IObservable ActiveEvents => Observable.Never(); 11 | public IObservable LatestEvent => Observable.Never(); 12 | } 13 | } -------------------------------------------------------------------------------- /WpfApp.Gui/Design/DesignPlcProvider.cs: -------------------------------------------------------------------------------- 1 | using WpfApp.Interfaces.Hardware; 2 | using WpfApp.Interfaces.Services; 3 | 4 | namespace WpfApp.Gui.Design 5 | { 6 | internal class DesignPlcProvider : IPlcProvider 7 | { 8 | public IPlc GetHardware(string name) 9 | { 10 | return new MockPlc(); 11 | } 12 | 13 | public IPlc GetHardware() 14 | { 15 | return new MockPlc(); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /WpfApp.Gui/Design/DesignSettingsProvider.cs: -------------------------------------------------------------------------------- 1 | using WpfApp.Interfaces.Services; 2 | using WpfApp.Interfaces.Settings; 3 | 4 | namespace WpfApp.Gui.Design 5 | { 6 | internal class DesignSettingsProvider : ISettingsProvider 7 | { 8 | public SettingRoot SettingRoot => new SettingRoot(); 9 | public void SaveSettings() 10 | { 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /WpfApp.Gui/GuiModuleCatalog.cs: -------------------------------------------------------------------------------- 1 | using MahApps.Metro.Controls.Dialogs; 2 | using Ninject.Modules; 3 | using WpfApp.Gui.Services; 4 | using WpfApp.Interfaces.Ui; 5 | 6 | namespace WpfApp.Gui 7 | { 8 | public class GuiModuleCatalog : NinjectModule 9 | { 10 | public override void Load() 11 | { 12 | Bind().To().InSingletonScope(); 13 | Bind().To().InSingletonScope(); 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /WpfApp.Gui/Resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fbarresi/twincat-wpf-boilerplate/8f013ee26513634c483efe762ca8fcd3670c87c5/WpfApp.Gui/Resources/icon.png -------------------------------------------------------------------------------- /WpfApp.Gui/Services/PresentationService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reactive.Linq; 3 | using System.Reactive.Subjects; 4 | using WpfApp.Interfaces.Commons; 5 | using WpfApp.Interfaces.Ui; 6 | 7 | namespace WpfApp.Gui.Services 8 | { 9 | public class PresentationService : IPresentationService, IDisposable 10 | { 11 | private readonly IViewModelFactory viewModelFactory; 12 | 13 | private readonly BehaviorSubject activeViewModelSubject = new BehaviorSubject(null); 14 | 15 | public PresentationService(IViewModelFactory viewModelFactory) 16 | { 17 | this.viewModelFactory = viewModelFactory; 18 | } 19 | 20 | public void SwitchActiveViewModel(IViewModel viewModel) 21 | { 22 | activeViewModelSubject.Value?.Dispose(); 23 | activeViewModelSubject.OnNext(viewModel); 24 | } 25 | 26 | public void SwitchActiveViewModel(Type viewModelType) 27 | { 28 | var viewModel = viewModelFactory.CreateViewModel(viewModelType); 29 | SwitchActiveViewModel(viewModel); 30 | } 31 | 32 | public void SwitchActiveViewModel() where T : IViewModel 33 | { 34 | var viewModel = viewModelFactory.CreateViewModel(); 35 | SwitchActiveViewModel(viewModel); 36 | } 37 | 38 | public IObservable ActiveViewModel => activeViewModelSubject.AsObservable(); 39 | 40 | public void Dispose() 41 | { 42 | activeViewModelSubject.Value?.Dispose(); 43 | activeViewModelSubject.OnCompleted(); 44 | activeViewModelSubject.Dispose(); 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /WpfApp.Gui/ViewModelLocator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Ninject; 3 | using Ninject.Parameters; 4 | using WpfApp.Gui.ViewModels; 5 | using WpfApp.Gui.ViewModels.Basics; 6 | using WpfApp.Interfaces; 7 | using WpfApp.Interfaces.Commons; 8 | using WpfApp.Interfaces.Ui; 9 | using IInitializable = WpfApp.Interfaces.Commons.IInitializable; 10 | 11 | namespace WpfApp.Gui 12 | { 13 | public class ViewModelLocator : IViewModelFactory, IInstanceCreator 14 | { 15 | protected readonly IKernel Kernel; 16 | 17 | private static ViewModelLocator s_Instance; 18 | 19 | public ViewModelLocator() 20 | { 21 | BindServices(); 22 | } 23 | 24 | private void BindServices() 25 | { 26 | Kernel.Bind().To(); 27 | } 28 | 29 | public ViewModelLocator(IKernel kernel) 30 | { 31 | Kernel = kernel; 32 | kernel.Bind().ToConstant(this); 33 | BindServices(); 34 | } 35 | 36 | public static IInstanceCreator DesignInstanceCreator => s_Instance ?? (s_Instance = new ViewModelLocator()); 37 | 38 | public static IViewModelFactory DesignViewModelFactory => s_Instance ?? (s_Instance = new ViewModelLocator()); 39 | 40 | public MainWindowViewModel MainWindowViewModel => Kernel.Get(); 41 | 42 | public PlcVariableViewModel PlcVariableViewModel => CreateViewModel(); 43 | 44 | public T Create() 45 | { 46 | var newObject = Kernel.Get(); 47 | return newObject; 48 | } 49 | 50 | public T CreateInstance(ConstructorArgument[] arguments) 51 | { 52 | var vm = Kernel.Get(arguments); 53 | InitializeInitialziable(vm as IInitializable); 54 | return vm; 55 | } 56 | 57 | public TVm CreateViewModel(T model) 58 | { 59 | var vm = Kernel.Get(new ConstructorArgument(@"model", model)); 60 | InitializeInitialziable(vm as IInitializable); 61 | return vm; 62 | } 63 | 64 | public TVm CreateViewModel() 65 | { 66 | var vm = Kernel.Get(); 67 | InitializeInitialziable(vm as IInitializable); 68 | return vm; 69 | } 70 | 71 | public IViewModel CreateViewModel(Type viewModelType) 72 | { 73 | var vm = Kernel.Get(viewModelType) as IViewModel; 74 | InitializeInitialziable(vm); 75 | return vm; 76 | } 77 | 78 | private static void InitializeInitialziable(IInitializable initializable) 79 | { 80 | initializable?.Init(); 81 | } 82 | } 83 | } -------------------------------------------------------------------------------- /WpfApp.Gui/ViewModels/Basics/PlcErrorBarViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reactive.Linq; 3 | using WpfApp.Gui.Design; 4 | using WpfApp.Interfaces.Extensions; 5 | using WpfApp.Interfaces.Models; 6 | using WpfApp.Interfaces.Services; 7 | 8 | namespace WpfApp.Gui.ViewModels.Basics 9 | { 10 | public class PlcErrorBarViewModel : ViewModelBase 11 | { 12 | private readonly IPlcEventService eventService; 13 | private bool hasErrors; 14 | private PlcEvent error; 15 | 16 | public PlcErrorBarViewModel(IPlcEventService eventService) 17 | { 18 | this.eventService = eventService; 19 | } 20 | protected override void Initialize() 21 | { 22 | eventService.LatestEvent 23 | .ObserveOnDispatcher() 24 | .Do(b => HasErrors = (b == null)) 25 | .Where(e => e != null) 26 | .Do(e => Error = e) 27 | .Subscribe() 28 | .AddDisposableTo(Disposables); 29 | 30 | } 31 | 32 | public PlcEvent Error 33 | { 34 | get => error; 35 | set 36 | { 37 | if (Equals(value, error)) return; 38 | error = value; 39 | raisePropertyChanged(); 40 | } 41 | } 42 | 43 | public bool HasErrors 44 | { 45 | get => hasErrors; 46 | set 47 | { 48 | if (value == hasErrors) return; 49 | hasErrors = value; 50 | raisePropertyChanged(); 51 | } 52 | } 53 | } 54 | 55 | internal class DesignPlcErrorBarViewModel : PlcErrorBarViewModel 56 | { 57 | public DesignPlcErrorBarViewModel() : base(new DesignPlcEventService()) 58 | { 59 | Init(); 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /WpfApp.Gui/ViewModels/Basics/PlcErrorDetailsViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.ObjectModel; 3 | using System.Reactive.Linq; 4 | using DynamicData; 5 | using WpfApp.Gui.Design; 6 | using WpfApp.Interfaces.Extensions; 7 | using WpfApp.Interfaces.Models; 8 | using WpfApp.Interfaces.Services; 9 | 10 | namespace WpfApp.Gui.ViewModels.Basics 11 | { 12 | public class PlcErrorDetailsViewModel : ViewModelBase 13 | { 14 | private readonly IPlcEventService plcEventService; 15 | private ObservableCollection activeErrors = new ObservableCollection(); 16 | 17 | public PlcErrorDetailsViewModel(IPlcEventService plcEventService) 18 | { 19 | this.plcEventService = plcEventService; 20 | } 21 | protected override void Initialize() 22 | { 23 | plcEventService.ActiveEvents 24 | .Do(_ => ActiveErrors.Clear()) 25 | .Do(events => ActiveErrors.AddRange(events)) 26 | .Subscribe() 27 | .AddDisposableTo(Disposables) 28 | ; 29 | } 30 | 31 | public ObservableCollection ActiveErrors 32 | { 33 | get => activeErrors; 34 | set 35 | { 36 | if (Equals(value, activeErrors)) return; 37 | activeErrors = value; 38 | raisePropertyChanged(); 39 | } 40 | } 41 | } 42 | 43 | internal class DesignPlcErrorDetailsViewModel : PlcErrorDetailsViewModel 44 | { 45 | public DesignPlcErrorDetailsViewModel() : base(new DesignPlcEventService()) 46 | { 47 | Init(); 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /WpfApp.Gui/ViewModels/Basics/PlcVariableViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reactive; 3 | using System.Reactive.Disposables; 4 | using System.Reactive.Linq; 5 | using System.Reactive.Subjects; 6 | using System.Threading.Tasks; 7 | using ReactiveUI; 8 | using TwinCAT; 9 | using WpfApp.Interfaces.Extensions; 10 | using WpfApp.Interfaces.Hardware; 11 | using WpfApp.Interfaces.Services; 12 | 13 | namespace WpfApp.Gui.ViewModels.Basics 14 | { 15 | public class PlcVariableViewModel : ViewModelBase 16 | { 17 | private readonly IPlcProvider provider; 18 | private string variablePath = string.Empty; 19 | private string plcName; 20 | private string label; 21 | private string description; 22 | private readonly SerialDisposable variableSubscriptions = new SerialDisposable(); 23 | private readonly BehaviorSubject canSetVariable = new BehaviorSubject(false); 24 | private ConnectionState connectionState; 25 | private object rawValue; 26 | private IPlc plc; 27 | private object setParameter; 28 | private string valueFormat; 29 | 30 | public ReactiveCommand SetVariable { get; set; } 31 | public PlcVariableViewModel(IPlcProvider provider) 32 | { 33 | this.provider = provider; 34 | } 35 | protected override void Initialize() 36 | { 37 | variableSubscriptions.AddDisposableTo(Disposables); 38 | 39 | SetVariable = ReactiveCommand 40 | .CreateFromTask(WriteVariable) 41 | .AddDisposableTo(Disposables) 42 | ; 43 | 44 | Logger.Debug("PlcVariableViewModel view model initialized!"); 45 | } 46 | 47 | private async Task WriteVariable(Unit arg) 48 | { 49 | if (plc == null) return Unit.Default; 50 | await plc.Write(VariablePath, SetParameter); 51 | return Unit.Default; 52 | } 53 | 54 | public string VariablePath 55 | { 56 | get => variablePath; 57 | set 58 | { 59 | if (value == variablePath) return; 60 | variablePath = value; 61 | raisePropertyChanged(); 62 | } 63 | } 64 | 65 | public string PlcName 66 | { 67 | get => plcName; 68 | set 69 | { 70 | if (value == plcName) return; 71 | plcName = value; 72 | raisePropertyChanged(); 73 | } 74 | } 75 | 76 | public string Label 77 | { 78 | get => label; 79 | set 80 | { 81 | if (value == label) return; 82 | label = value; 83 | raisePropertyChanged(); 84 | } 85 | } 86 | 87 | public string Description 88 | { 89 | get => description; 90 | set 91 | { 92 | if (value == description) return; 93 | description = value; 94 | raisePropertyChanged(); 95 | } 96 | } 97 | 98 | public object RawValue 99 | { 100 | get => rawValue; 101 | set 102 | { 103 | if (value == rawValue) return; 104 | rawValue = value; 105 | raisePropertyChanged(); 106 | } 107 | } 108 | 109 | public ConnectionState ConnectionState 110 | { 111 | get => connectionState; 112 | set 113 | { 114 | if (value == connectionState) return; 115 | connectionState = value; 116 | raisePropertyChanged(); 117 | } 118 | } 119 | 120 | public object SetParameter 121 | { 122 | get => setParameter; 123 | set 124 | { 125 | if (Equals(value, setParameter)) return; 126 | setParameter = value; 127 | raisePropertyChanged(); 128 | } 129 | } 130 | 131 | public string ValueFormat 132 | { 133 | get => valueFormat; 134 | set 135 | { 136 | if (value == valueFormat) return; 137 | valueFormat = value; 138 | raisePropertyChanged(); 139 | } 140 | } 141 | 142 | public void SetupVariableViewModel(string plcName, string variablePath, string label, string description, 143 | object setParameter, string valueFormat) 144 | { 145 | var subscriptions = new CompositeDisposable(); 146 | 147 | Logger.Debug($"Setting up variable Plc Variable for {variablePath}"); 148 | 149 | plc = plcName == null ? provider.GetHardware() : provider.GetHardware(plcName); 150 | PlcName = plcName; 151 | VariablePath = variablePath; 152 | Label = label; 153 | Description = description; 154 | SetParameter = setParameter; 155 | ValueFormat = valueFormat; 156 | 157 | plc.ConnectionState 158 | .DistinctUntilChanged() 159 | .ObserveOnDispatcher() 160 | .Do(cs => ConnectionState = cs) 161 | .Subscribe() 162 | .AddDisposableTo(subscriptions) 163 | ; 164 | 165 | plc.CreateNotification(variablePath) 166 | .ObserveOnDispatcher() 167 | .Do(r => RawValue = r) 168 | .Subscribe() 169 | .AddDisposableTo(subscriptions) 170 | ; 171 | 172 | plc.ConnectionState 173 | .DistinctUntilChanged() 174 | .ObserveOnDispatcher() 175 | .Select(state => state == ConnectionState.Connected) 176 | .Subscribe(canSetVariable.OnNext) 177 | .AddDisposableTo(Disposables) 178 | ; 179 | 180 | variableSubscriptions.Disposable = subscriptions; 181 | } 182 | } 183 | } -------------------------------------------------------------------------------- /WpfApp.Gui/ViewModels/GraphViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.ObjectModel; 3 | using System.Reactive.Linq; 4 | using System.Windows; 5 | using OxyPlot; 6 | using OxyPlot.Series; 7 | using WpfApp.Gui.Design; 8 | using WpfApp.Interfaces.Extensions; 9 | using WpfApp.Interfaces.Hardware; 10 | using WpfApp.Interfaces.Services; 11 | using WpfApp.Interfaces.Settings; 12 | 13 | namespace WpfApp.Gui.ViewModels 14 | { 15 | public class GraphViewModel: ViewModelBase 16 | { 17 | private readonly IPlcProvider plcProvider; 18 | private readonly ApplicationSetting setting; 19 | private PlotModel plotModel; 20 | 21 | public GraphViewModel(IPlcProvider plcProvider, ApplicationSetting setting) 22 | { 23 | this.plcProvider = plcProvider; 24 | this.setting = setting; 25 | Title = "Graph View"; 26 | } 27 | 28 | protected override void Initialize() 29 | { 30 | PlotModel = new PlotModel(); 31 | PlotModel.Series.Add(new LineSeries()); 32 | 33 | var plc = plcProvider.GetHardware(); 34 | 35 | plc.CreateNotification(setting.DoubleSignalName) 36 | .ObserveOnDispatcher() 37 | .Do(d => 38 | { 39 | var plotModelSeries = PlotModel.Series[0] as LineSeries; 40 | plotModelSeries?.Points.Add(new DataPoint(plotModelSeries.Points.Count, d)); 41 | if(plotModelSeries?.Points.Count > 100) plotModelSeries?.Points.RemoveAt(0); 42 | PlotModel.InvalidatePlot(true); 43 | }) 44 | .Subscribe() 45 | .AddDisposableTo(Disposables) 46 | ; 47 | 48 | Logger?.Debug("Initialized Graph view"); 49 | } 50 | 51 | public PlotModel PlotModel 52 | { 53 | get => plotModel; 54 | set 55 | { 56 | if(plotModel == value) return; 57 | raisePropertyChanged(); 58 | plotModel = value; 59 | } 60 | } 61 | } 62 | 63 | internal class DesignGraphViewModel : GraphViewModel 64 | { 65 | public DesignGraphViewModel() : base(new DesignPlcProvider(), new ApplicationSetting()) 66 | { 67 | Init(); 68 | } 69 | 70 | 71 | } 72 | } -------------------------------------------------------------------------------- /WpfApp.Gui/ViewModels/MainViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Reactive.Linq; 2 | using ReactiveUI; 3 | using TwinCAT; 4 | using WpfApp.Gui.Design; 5 | using WpfApp.Interfaces.Services; 6 | using WpfApp.Interfaces.Settings; 7 | 8 | namespace WpfApp.Gui.ViewModels 9 | { 10 | public class MainViewModel : ViewModelBase 11 | { 12 | 13 | public MainViewModel() 14 | { 15 | Title = "Main View"; 16 | } 17 | protected override void Initialize() 18 | { 19 | Logger.Debug("Main view initialized!"); 20 | } 21 | 22 | } 23 | 24 | internal class DesignMainViewModel : MainViewModel 25 | { 26 | public DesignMainViewModel() : base() 27 | { 28 | Init(); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /WpfApp.Gui/ViewModels/MainWindowViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Reactive; 4 | using System.Reactive.Linq; 5 | using System.Threading.Tasks; 6 | using MahApps.Metro.Controls.Dialogs; 7 | using ReactiveUI; 8 | using TwinCAT; 9 | using WpfApp.Gui.ViewModels.Basics; 10 | using WpfApp.Interfaces.Commons; 11 | using WpfApp.Interfaces.Exceptions; 12 | using WpfApp.Interfaces.Extensions; 13 | using WpfApp.Interfaces.Services; 14 | using WpfApp.Interfaces.Settings; 15 | using WpfApp.Interfaces.Ui; 16 | using WPFLocalizeExtension.Engine; 17 | 18 | namespace WpfApp.Gui.ViewModels 19 | { 20 | public class MainWindowViewModel : ViewModelBase 21 | { 22 | private readonly IDialogCoordinator dialogCoordinator; 23 | private readonly IPlcProvider provider; 24 | private readonly IPresentationService presentationService; 25 | private readonly SettingRoot settingRoot; 26 | private readonly IViewModelFactory viewModelFactory; 27 | private bool sideMenuOpen; 28 | private ObservableAsPropertyHelper helper; 29 | private ViewModelBase activeViewModel; 30 | private CultureInfo selectedLanguage; 31 | 32 | public ReactiveCommand ToggleMenu { get; set; } 33 | public ReactiveCommand Login { get; set; } 34 | public ReactiveCommand Logout { get; set; } 35 | public ReactiveCommand SwitchToViewModel { get; set; } 36 | public ReactiveCommand ChangeLanguageCmd { get; set; } 37 | 38 | public CultureInfo SelectedLanguage 39 | { 40 | get => selectedLanguage; 41 | set 42 | { 43 | if (value == selectedLanguage) return; 44 | selectedLanguage = value; 45 | raisePropertyChanged(); 46 | } 47 | } 48 | 49 | public CultureInfo[] SupportedCultures => settingRoot.CultureSettings.SupportedCultures; 50 | 51 | public bool SideMenuOpen 52 | { 53 | get => sideMenuOpen; 54 | set 55 | { 56 | if(value == sideMenuOpen) return; 57 | sideMenuOpen = value; 58 | raisePropertyChanged(); 59 | } 60 | } 61 | 62 | public ConnectionState ConnectionState => helper?.Value ?? ConnectionState.None; 63 | 64 | public MainWindowViewModel(IDialogCoordinator dialogCoordinator, 65 | IPresentationService presentationService, 66 | IPlcProvider provider, 67 | SettingRoot settingRoot, 68 | IViewModelFactory viewModelFactory) 69 | { 70 | this.dialogCoordinator = dialogCoordinator; 71 | this.presentationService = presentationService; 72 | this.provider = provider; 73 | this.settingRoot = settingRoot; 74 | this.viewModelFactory = viewModelFactory; 75 | } 76 | protected override void Initialize() 77 | { 78 | InitializeLocalization(); 79 | SelectedLanguage = settingRoot.CultureSettings.SelectedCulture; 80 | this.WhenAnyValue(vm => vm.SelectedLanguage) 81 | .ObserveOnDispatcher() 82 | .Do(ChangeCurrentCulture) 83 | .Subscribe() 84 | .AddDisposableTo(Disposables); 85 | 86 | ChangeLanguageCmd = ReactiveCommand.CreateFromTask(ChangeCurrentCulture) 87 | .AddDisposableTo(Disposables); 88 | 89 | var plc = provider.GetHardware(); 90 | 91 | helper = plc.ConnectionState.ToProperty(this, vm => vm.ConnectionState, ConnectionState.None); 92 | 93 | Logger.Debug("Main view model initialized!"); 94 | 95 | SetInitialViewModel(); 96 | 97 | SetupContentPresenter(); 98 | 99 | SwitchToViewModel = ReactiveCommand.CreateFromTask(SwitchTo) 100 | .AddDisposableTo(Disposables); 101 | 102 | ToggleMenu = ReactiveCommand.CreateFromTask(ToggleSideMenuOpen) 103 | .AddDisposableTo(Disposables); 104 | 105 | Login = ReactiveCommand.CreateFromTask(ShowLoginDialog) 106 | .AddDisposableTo(Disposables); 107 | Logout = ReactiveCommand.CreateFromTask(ExecuteLogout) 108 | .AddDisposableTo(Disposables); 109 | 110 | PlcErrorBarViewModel = viewModelFactory.CreateViewModel(); 111 | PlcErrorBarViewModel.AddDisposableTo(Disposables); 112 | } 113 | 114 | private Task ExecuteLogout() 115 | { 116 | UserService.Logout(); 117 | return Task.FromResult(Unit.Default); 118 | } 119 | 120 | private async Task ShowLoginDialog() 121 | { 122 | var result = await dialogCoordinator 123 | .ShowLoginAsync(this, 124 | "Login", 125 | "Enter your credentials", 126 | new LoginDialogSettings{EnablePasswordPreview = true}); 127 | 128 | if(result == null) return Unit.Default; 129 | 130 | try 131 | { 132 | UserService.Login(result.Username, result.Password); 133 | } 134 | catch (LoginFailedException e) 135 | { 136 | Logger.Error(e, "Error while login"); 137 | await dialogCoordinator.ShowMessageAsync(this, "Error", e.Message, MessageDialogStyle.Affirmative, 138 | new MetroDialogSettings() { }); 139 | } 140 | 141 | return Unit.Default; 142 | } 143 | 144 | private Task ToggleSideMenuOpen() 145 | { 146 | SideMenuOpen = !SideMenuOpen; 147 | return Task.FromResult(Unit.Default); 148 | } 149 | 150 | private Task ChangeCurrentCulture(string culture) 151 | { 152 | settingRoot.CultureSettings.SelectedCulture = CultureInfo.GetCultureInfo(culture); 153 | LocalizeDictionary.Instance.Culture = CultureInfo.GetCultureInfo(culture); 154 | return Task.FromResult(Unit.Default); 155 | } 156 | 157 | private void ChangeCurrentCulture(CultureInfo culture) 158 | { 159 | Logger.Debug("Setting current culture to {culture}", culture); 160 | settingRoot.CultureSettings.SelectedCulture = culture; 161 | LocalizeDictionary.Instance.Culture = culture; 162 | } 163 | 164 | private void InitializeLocalization() 165 | { 166 | LocalizeDictionary.Instance.Culture = settingRoot.CultureSettings.SelectedCulture; 167 | } 168 | 169 | private Task SwitchTo(Type type) 170 | { 171 | presentationService.SwitchActiveViewModel(type); 172 | SideMenuOpen = false; 173 | return Task.FromResult(Unit.Default); 174 | } 175 | 176 | private void SetupContentPresenter() 177 | { 178 | presentationService.ActiveViewModel 179 | .Where(vm => vm != null) 180 | .ObserveOnDispatcher() 181 | .Do(vm => ActiveViewModel = vm as ViewModelBase) 182 | .Subscribe() 183 | .AddDisposableTo(Disposables); 184 | } 185 | 186 | private void SetInitialViewModel() 187 | { 188 | presentationService.SwitchActiveViewModel(); 189 | } 190 | 191 | public ViewModelBase ActiveViewModel 192 | { 193 | get => activeViewModel; 194 | set 195 | { 196 | if (value == activeViewModel) return; 197 | activeViewModel = value; 198 | raisePropertyChanged(); 199 | } 200 | } 201 | 202 | public PlcErrorBarViewModel PlcErrorBarViewModel { get; set; } 203 | } 204 | } -------------------------------------------------------------------------------- /WpfApp.Gui/ViewModels/SettingsViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Reactive; 2 | using System.Threading.Tasks; 3 | using ReactiveUI; 4 | using WpfApp.Gui.Design; 5 | using WpfApp.Interfaces.Extensions; 6 | using WpfApp.Interfaces.Services; 7 | using WpfApp.Interfaces.Settings; 8 | 9 | namespace WpfApp.Gui.ViewModels 10 | { 11 | public class SettingsViewModel : ViewModelBase 12 | { 13 | private readonly ISettingsProvider settingsProvider; 14 | public ApplicationSetting Setting { get; } 15 | 16 | public ReactiveCommand SaveSettings { get; set; } 17 | 18 | public SettingsViewModel(ApplicationSetting setting, ISettingsProvider settingsProvider) 19 | { 20 | this.settingsProvider = settingsProvider; 21 | Setting = setting; 22 | } 23 | protected override void Initialize() 24 | { 25 | SaveSettings = ReactiveCommand.CreateFromTask(Save).AddDisposableTo(Disposables); 26 | } 27 | 28 | private Task Save() 29 | { 30 | settingsProvider.SaveSettings(); 31 | return Task.FromResult(Unit.Default); 32 | } 33 | } 34 | 35 | internal class DesignSettingsViewModel : SettingsViewModel 36 | { 37 | public DesignSettingsViewModel() : base(new ApplicationSetting(), new DesignSettingsProvider()) 38 | { 39 | Init(); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /WpfApp.Gui/ViewModels/ViewModelBase.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Diagnostics.CodeAnalysis; 3 | using System.Reactive.Disposables; 4 | using System.Reactive.Linq; 5 | using System.Runtime.CompilerServices; 6 | using JetBrains.Annotations; 7 | using Ninject; 8 | using ReactiveUI; 9 | using Serilog; 10 | using WpfApp.Interfaces.Enums; 11 | using WpfApp.Interfaces.Extensions; 12 | using WpfApp.Interfaces.Models; 13 | using WpfApp.Interfaces.Services; 14 | using WpfApp.Interfaces.Ui; 15 | 16 | namespace WpfApp.Gui.ViewModels 17 | { 18 | public abstract class ViewModelBase : ReactiveObject, IViewModel 19 | { 20 | protected CompositeDisposable Disposables = new CompositeDisposable(); 21 | private bool disposed; 22 | private string title; 23 | private ObservableAsPropertyHelper currentUserHelper; 24 | private ObservableAsPropertyHelper loggedInHelper; 25 | private ObservableAsPropertyHelper> rolesInHelper; 26 | 27 | [Inject] 28 | public IUserService UserService { get; set; } 29 | 30 | public string Title 31 | { 32 | get => title; 33 | set 34 | { 35 | if (value == title) return; 36 | title = value; 37 | raisePropertyChanged(); 38 | } 39 | } 40 | 41 | protected ILogger Logger { get; } = Log.Logger; 42 | 43 | public virtual void Dispose() 44 | { 45 | Dispose(true); 46 | } 47 | 48 | public void Init() 49 | { 50 | Initialize(); 51 | InitializeBaseElements(); 52 | } 53 | 54 | protected abstract void Initialize(); 55 | 56 | [SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed", MessageId = "Disposables")] 57 | protected virtual void Dispose(bool disposing) 58 | { 59 | if (disposed) 60 | { 61 | return; 62 | } 63 | 64 | Disposables?.Dispose(); 65 | Disposables = null; 66 | Logger?.Debug("Disposed {type}", this.GetType().Name); 67 | 68 | disposed = true; 69 | } 70 | 71 | [NotifyPropertyChangedInvocator] 72 | // ReSharper disable once InconsistentNaming 73 | protected void raisePropertyChanged([CallerMemberName] string propertyName = "") 74 | { 75 | this.RaisePropertyChanged(propertyName); 76 | } 77 | 78 | ~ViewModelBase() 79 | { 80 | Dispose(false); 81 | } 82 | 83 | private void InitializeBaseElements() 84 | { 85 | currentUserHelper = UserService?.CurrentUser.ToProperty(this, vm => vm.CurrentUser); 86 | currentUserHelper.AddDisposableTo(Disposables); 87 | 88 | loggedInHelper = UserService?.CurrentUser.Select(u => u != null).ToProperty(this, vm => vm.LoggedIn); 89 | loggedInHelper.AddDisposableTo(Disposables); 90 | 91 | rolesInHelper = UserService?.CurrentUser.Select(u => u?.Roles ?? new List()).ToProperty(this, vm => vm.CurrentRoles); 92 | rolesInHelper.AddDisposableTo(Disposables); 93 | } 94 | 95 | public List CurrentRoles => rolesInHelper.Value; 96 | 97 | public bool LoggedIn => loggedInHelper.Value; 98 | 99 | public User CurrentUser => currentUserHelper.Value; 100 | } 101 | } -------------------------------------------------------------------------------- /WpfApp.Gui/Views/Basics/PlcButton.xaml: -------------------------------------------------------------------------------- 1 |  13 | 14 | 17 | 18 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /WpfApp.Gui/Views/Basics/PlcErrorBar.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | 3 | namespace WpfApp.Gui.Views.Basics 4 | { 5 | public partial class PlcErrorBar : UserControl 6 | { 7 | public PlcErrorBar() 8 | { 9 | InitializeComponent(); 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /WpfApp.Gui/Views/Basics/PlcErrorDetails.xaml: -------------------------------------------------------------------------------- 1 |  11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /WpfApp.Gui/Views/Basics/PlcErrorDetails.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | 3 | namespace WpfApp.Gui.Views.Basics 4 | { 5 | public partial class PlcErrorDetails : UserControl 6 | { 7 | public PlcErrorDetails() 8 | { 9 | InitializeComponent(); 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /WpfApp.Gui/Views/Basics/PlcSignalOkNok.xaml: -------------------------------------------------------------------------------- 1 |  13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 30 | 31 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /WpfApp.Gui/Views/Basics/PlcSignalOkNok.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace WpfApp.Gui.Views.Basics 2 | { 3 | public partial class PlcSignalOkNok : PlcUserControl 4 | { 5 | public PlcSignalOkNok() 6 | { 7 | InitializeComponent(); 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /WpfApp.Gui/Views/Basics/PlcSignalOnOff.xaml: -------------------------------------------------------------------------------- 1 |  13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 30 | 31 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /WpfApp.Gui/Views/Basics/PlcSignalOnOff.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace WpfApp.Gui.Views.Basics 2 | { 3 | public partial class PlcSignalOnOff : PlcUserControl 4 | { 5 | public PlcSignalOnOff() 6 | { 7 | InitializeComponent(); 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /WpfApp.Gui/Views/Basics/PlcUserControl.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reactive.Disposables; 3 | using System.Windows; 4 | using System.Windows.Controls; 5 | using ReactiveUI; 6 | using WpfApp.Gui.ViewModels; 7 | using WpfApp.Gui.ViewModels.Basics; 8 | 9 | namespace WpfApp.Gui.Views.Basics 10 | { 11 | public class PlcUserControl : UserControl, IViewFor 12 | { 13 | public PlcUserControl() 14 | { 15 | this.WhenActivated(disposables => 16 | { 17 | if (ViewModel != null) 18 | { 19 | ViewModel.SetupVariableViewModel(PlcName, VariablePath, Label, Description, SetParameter, ValueFormat); 20 | ViewModel.DisposeWith(disposables); 21 | } 22 | }); 23 | } 24 | 25 | public readonly DependencyProperty VariablePathProperty = DependencyProperty.Register(nameof(VariablePath)+Guid.NewGuid(), typeof(string), typeof(PlcUserControl)); 26 | public readonly DependencyProperty SetParameterProperty = DependencyProperty.Register(nameof(SetParameter)+Guid.NewGuid(), typeof(object), typeof(PlcUserControl)); 27 | public readonly DependencyProperty ValueFormatProperty = DependencyProperty.Register(nameof(ValueFormat)+Guid.NewGuid(), typeof(string), typeof(PlcUserControl)); 28 | public readonly DependencyProperty PlcNameProperty = DependencyProperty.Register(nameof(PlcNameProperty)+Guid.NewGuid(), typeof(string), typeof(PlcUserControl)); 29 | public readonly DependencyProperty LabelProperty = DependencyProperty.Register(nameof(LabelProperty)+Guid.NewGuid(), typeof(string), typeof(PlcUserControl)); 30 | public readonly DependencyProperty DescriptionProperty = DependencyProperty.Register(nameof(DescriptionProperty)+Guid.NewGuid(), typeof(string), typeof(PlcUserControl)); 31 | 32 | public string VariablePath 33 | { 34 | get => (string) GetValue(VariablePathProperty); 35 | set => SetValue(VariablePathProperty, value); 36 | } 37 | 38 | public object SetParameter 39 | { 40 | get => GetValue(SetParameterProperty); 41 | set => SetValue(SetParameterProperty, value); 42 | } 43 | 44 | public string ValueFormat 45 | { 46 | get => (string) GetValue(ValueFormatProperty); 47 | set => SetValue(ValueFormatProperty, value); 48 | } 49 | 50 | public string PlcName 51 | { 52 | get => (string) GetValue(PlcNameProperty); 53 | set => SetValue(PlcNameProperty, value); 54 | } 55 | public string Label 56 | { 57 | get => (string) GetValue(LabelProperty); 58 | set => SetValue(LabelProperty, value); 59 | } 60 | public string Description 61 | { 62 | get => (string) GetValue(DescriptionProperty); 63 | set => SetValue(DescriptionProperty, value); 64 | } 65 | 66 | object? IViewFor.ViewModel 67 | { 68 | get => ViewModel; 69 | set => ViewModel = value as PlcVariableViewModel; 70 | } 71 | 72 | public PlcVariableViewModel? ViewModel 73 | { 74 | get => DataContext as PlcVariableViewModel; 75 | set => DataContext = value; 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /WpfApp.Gui/Views/Basics/PlcVariable.xaml: -------------------------------------------------------------------------------- 1 |  13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /WpfApp.Gui/Views/Basics/PlcVariable.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace WpfApp.Gui.Views.Basics 2 | { 3 | public partial class PlcVariable : PlcUserControl 4 | { 5 | public PlcVariable() 6 | { 7 | InitializeComponent(); 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /WpfApp.Gui/Views/MainWindow.xaml: -------------------------------------------------------------------------------- 1 |  23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 50 | 51 | 57 | 58 | 63 | 64 | 65 | 66 | 80 | 81 | 87 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 118 | 119 | 120 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | -------------------------------------------------------------------------------- /WpfApp.Gui/Views/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using ControlzEx.Theming; 3 | using MahApps.Metro.Controls; 4 | 5 | namespace WpfApp.Gui.Views 6 | { 7 | /// 8 | /// Interaction logic for MainWindow.xaml 9 | /// 10 | public partial class MainWindow : MetroWindow 11 | { 12 | public MainWindow() 13 | { 14 | InitializeComponent(); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /WpfApp.Gui/Views/Pages/GraphView.xaml: -------------------------------------------------------------------------------- 1 |  12 | 13 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /WpfApp.Gui/Views/Pages/GraphView.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | 3 | namespace WpfApp.Gui.Views.Pages 4 | { 5 | public partial class GraphView : UserControl 6 | { 7 | public GraphView() 8 | { 9 | InitializeComponent(); 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /WpfApp.Gui/Views/Pages/MainView.xaml: -------------------------------------------------------------------------------- 1 |  16 | 17 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /WpfApp.Gui/Views/Pages/MainView.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | 3 | namespace WpfApp.Gui.Views.Pages 4 | { 5 | public partial class MainView : UserControl 6 | { 7 | public MainView() 8 | { 9 | InitializeComponent(); 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /WpfApp.Gui/Views/Pages/SettingsView.xaml: -------------------------------------------------------------------------------- 1 |  13 | 14 | 15 | 16 | 17 | 18 | 25 |