├── .gitignore
├── AvaloniaSamples.sln
├── Readme.md
└── Samples
├── LineDemo
├── LineDemo.sln
└── LineDemo
│ ├── App.axaml
│ ├── App.axaml.cs
│ ├── Assets
│ ├── aaa.png
│ ├── avalonia-logo.ico
│ └── avalonia.png
│ ├── LineDemo.csproj
│ ├── Program.cs
│ ├── ViewLocator.cs
│ ├── ViewModels
│ ├── ChartViewModel.cs
│ ├── MainWindowViewModel.cs
│ └── ViewModelBase.cs
│ ├── Views
│ ├── ChartView.axaml
│ ├── ChartView.axaml.cs
│ ├── MainWindow.axaml
│ └── MainWindow.axaml.cs
│ └── app.manifest
├── MVSCameraDemo
├── CameraDemo.sln
├── CameraDemo
│ ├── App.axaml
│ ├── App.axaml.cs
│ ├── Assets
│ │ ├── avalonia-logo.ico
│ │ └── msyh.ttc
│ ├── CameraDemo.csproj
│ ├── Controls
│ │ └── HandleControl.cs
│ ├── FontManager.cs
│ ├── Program.cs
│ ├── ViewLocator.cs
│ ├── ViewModels
│ │ ├── MainWindowViewModel.cs
│ │ └── ViewModelBase.cs
│ ├── Views
│ │ ├── MainWindow.axaml
│ │ └── MainWindow.axaml.cs
│ └── app.manifest
├── HKSDK
│ ├── Enums
│ │ ├── DeviceLayerType.cs
│ │ ├── ImageType.cs
│ │ └── PixelFormatType.cs
│ ├── HKCameraControl.cs
│ ├── HKSDK.csproj
│ ├── Lib
│ │ ├── libFormatConversion.so
│ │ ├── libMVGigEVisionSDK.so
│ │ ├── libMVRender.so
│ │ ├── libMediaProcess.so
│ │ ├── libMvCameraControl.so
│ │ ├── libMvCameraControlWrapper.so
│ │ └── libMvUsb3vTL.so
│ └── Models
│ │ ├── DeviceInfo.cs
│ │ ├── DeviceInfos.cs
│ │ ├── FloatSettingModel.cs
│ │ ├── FrameOutInfo.cs
│ │ ├── GigeDeviceInfo.cs
│ │ └── SaveImageParams.cs
└── Readme.md
└── OpcUADemo
├── OpcUAClient.Repository
├── IRepository.cs
├── Models
│ └── HistoryDataModel.cs
├── OpcUAClient.Repository.csproj
└── Services
│ └── DapperRepository.cs
├── OpcUAClient
├── App.axaml
├── App.axaml.cs
├── Assets
│ ├── avalonia-logo.ico
│ └── msyh.ttc
├── FodyWeavers.xml
├── FodyWeavers.xsd
├── FontManager.cs
├── Models
│ ├── DataRecordSettingModel.cs
│ ├── NodeInfo.cs
│ ├── NodeListenModel.cs
│ └── NodeModel.cs
├── OPC
│ └── pki
│ │ ├── own
│ │ ├── certs
│ │ │ └── OpcUa Client [49AC5D9DC29D3C0AC0885E6B77901913BDB340CF].der
│ │ └── private
│ │ │ └── OpcUa Client [49AC5D9DC29D3C0AC0885E6B77901913BDB340CF].pfx
│ │ └── trusted
│ │ └── certs
│ │ └── OPCUAServer@sii-ret [B2B70C26F4DEB5D7A4817E73F56DFC8D38EC107D].der
├── OpcUAClient.Config.xml
├── OpcUAClient.csproj
├── Program.cs
├── Readme.md
├── UAClient.cs
├── ViewLocator.cs
├── ViewModels
│ ├── LineListenViewModel.cs
│ ├── MainWindowViewModel.cs
│ ├── SettingWindowViewModel.cs
│ └── ViewModelBase.cs
├── Views
│ ├── LineListenView.axaml
│ ├── LineListenView.axaml.cs
│ ├── MainWindow.axaml
│ ├── MainWindow.axaml.cs
│ ├── SettingWindowView.axaml
│ └── SettingWindowView.axaml.cs
├── app.config
└── app.manifest
└── OpcUADemo.sln
/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Rider ignored files
5 | /projectSettingsUpdater.xml
6 | /modules.xml
7 | /contentModel.xml
8 | /.idea.ZKSD.RTE.iml
9 | # Editor-based HTTP Client requests
10 | /httpRequests/
11 | # Datasource local storage ignored files
12 | /dataSources/
13 | /dataSources.local.xml
14 |
15 | .idea/
16 | .vs/
17 |
18 |
19 | *.suo
20 | *.user
21 | *.userosscache
22 | *.sln.docstates
23 |
24 | bin
25 | obj
--------------------------------------------------------------------------------
/AvaloniaSamples.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.0.31903.59
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{B93D2B5F-7D17-4919-B363-02E363AE408A}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MVSCameraDemo", "MVSCameraDemo", "{8382B803-6D74-40AA-A909-094028E06B26}"
9 | ProjectSection(SolutionItems) = preProject
10 | Samples\MVSCameraDemo\Readme.md = Samples\MVSCameraDemo\Readme.md
11 | EndProjectSection
12 | EndProject
13 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CameraDemo", "Samples\MVSCameraDemo\CameraDemo\CameraDemo.csproj", "{7778DEF8-09B0-4B32-A714-AAB237793F75}"
14 | EndProject
15 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HKSDK", "Samples\MVSCameraDemo\HKSDK\HKSDK.csproj", "{1502CD99-72A4-4894-8981-834DD9FA3036}"
16 | EndProject
17 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "LineDemo", "LineDemo", "{26EE2E3B-A005-42C6-AC79-4F09325E49E0}"
18 | EndProject
19 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LineDemo", "Samples\LineDemo\LineDemo\LineDemo.csproj", "{1F5D1719-65A4-48AB-BAA8-485EBB8AC4BA}"
20 | EndProject
21 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "OpcUADemo", "OpcUADemo", "{06A3D0BE-7497-451E-AD92-F92F8B5557B7}"
22 | EndProject
23 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpcUAClient", "Samples\OpcUADemo\OpcUAClient\OpcUAClient.csproj", "{9EBC3A71-EA2E-4B2D-9A01-CF68E90F7078}"
24 | EndProject
25 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpcUAClient.Repository", "Samples\OpcUADemo\OpcUAClient.Repository\OpcUAClient.Repository.csproj", "{82FE4236-E9E2-4578-87A2-7B1BECED019D}"
26 | EndProject
27 | Global
28 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
29 | Debug|Any CPU = Debug|Any CPU
30 | Release|Any CPU = Release|Any CPU
31 | EndGlobalSection
32 | GlobalSection(SolutionProperties) = preSolution
33 | HideSolutionNode = FALSE
34 | EndGlobalSection
35 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
36 | {7778DEF8-09B0-4B32-A714-AAB237793F75}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
37 | {7778DEF8-09B0-4B32-A714-AAB237793F75}.Debug|Any CPU.Build.0 = Debug|Any CPU
38 | {7778DEF8-09B0-4B32-A714-AAB237793F75}.Release|Any CPU.ActiveCfg = Release|Any CPU
39 | {7778DEF8-09B0-4B32-A714-AAB237793F75}.Release|Any CPU.Build.0 = Release|Any CPU
40 | {1502CD99-72A4-4894-8981-834DD9FA3036}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
41 | {1502CD99-72A4-4894-8981-834DD9FA3036}.Debug|Any CPU.Build.0 = Debug|Any CPU
42 | {1502CD99-72A4-4894-8981-834DD9FA3036}.Release|Any CPU.ActiveCfg = Release|Any CPU
43 | {1502CD99-72A4-4894-8981-834DD9FA3036}.Release|Any CPU.Build.0 = Release|Any CPU
44 | {1F5D1719-65A4-48AB-BAA8-485EBB8AC4BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
45 | {1F5D1719-65A4-48AB-BAA8-485EBB8AC4BA}.Debug|Any CPU.Build.0 = Debug|Any CPU
46 | {1F5D1719-65A4-48AB-BAA8-485EBB8AC4BA}.Release|Any CPU.ActiveCfg = Release|Any CPU
47 | {1F5D1719-65A4-48AB-BAA8-485EBB8AC4BA}.Release|Any CPU.Build.0 = Release|Any CPU
48 | {259728FA-5373-4D95-94A2-3E40420EC291}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
49 | {259728FA-5373-4D95-94A2-3E40420EC291}.Debug|Any CPU.Build.0 = Debug|Any CPU
50 | {259728FA-5373-4D95-94A2-3E40420EC291}.Release|Any CPU.ActiveCfg = Release|Any CPU
51 | {259728FA-5373-4D95-94A2-3E40420EC291}.Release|Any CPU.Build.0 = Release|Any CPU
52 | {9EBC3A71-EA2E-4B2D-9A01-CF68E90F7078}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
53 | {9EBC3A71-EA2E-4B2D-9A01-CF68E90F7078}.Debug|Any CPU.Build.0 = Debug|Any CPU
54 | {9EBC3A71-EA2E-4B2D-9A01-CF68E90F7078}.Release|Any CPU.ActiveCfg = Release|Any CPU
55 | {9EBC3A71-EA2E-4B2D-9A01-CF68E90F7078}.Release|Any CPU.Build.0 = Release|Any CPU
56 | {82FE4236-E9E2-4578-87A2-7B1BECED019D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
57 | {82FE4236-E9E2-4578-87A2-7B1BECED019D}.Debug|Any CPU.Build.0 = Debug|Any CPU
58 | {82FE4236-E9E2-4578-87A2-7B1BECED019D}.Release|Any CPU.ActiveCfg = Release|Any CPU
59 | {82FE4236-E9E2-4578-87A2-7B1BECED019D}.Release|Any CPU.Build.0 = Release|Any CPU
60 | EndGlobalSection
61 | GlobalSection(NestedProjects) = preSolution
62 | {8382B803-6D74-40AA-A909-094028E06B26} = {B93D2B5F-7D17-4919-B363-02E363AE408A}
63 | {7778DEF8-09B0-4B32-A714-AAB237793F75} = {8382B803-6D74-40AA-A909-094028E06B26}
64 | {1502CD99-72A4-4894-8981-834DD9FA3036} = {8382B803-6D74-40AA-A909-094028E06B26}
65 | {26EE2E3B-A005-42C6-AC79-4F09325E49E0} = {B93D2B5F-7D17-4919-B363-02E363AE408A}
66 | {1F5D1719-65A4-48AB-BAA8-485EBB8AC4BA} = {26EE2E3B-A005-42C6-AC79-4F09325E49E0}
67 | {06A3D0BE-7497-451E-AD92-F92F8B5557B7} = {B93D2B5F-7D17-4919-B363-02E363AE408A}
68 | {9EBC3A71-EA2E-4B2D-9A01-CF68E90F7078} = {06A3D0BE-7497-451E-AD92-F92F8B5557B7}
69 | {82FE4236-E9E2-4578-87A2-7B1BECED019D} = {06A3D0BE-7497-451E-AD92-F92F8B5557B7}
70 | EndGlobalSection
71 | EndGlobal
72 |
--------------------------------------------------------------------------------
/Readme.md:
--------------------------------------------------------------------------------
1 | # 中科时代Avalonia开发样例
2 |
3 | ## 说明
4 | 此资料为中科时代(深圳)计算机系统有限公司提供,主要目的在于指导C#程序员进行跨平台桌面程序开发(主要为Linux),所涉及的主要技术为C#和[Avalonia](https://www.avaloniaui.net/),如有任何疑问请联系中科时代(深圳)计算机系统有限公司。
5 |
6 | ## 目录介绍
7 | 本资料包含两部分内容:
8 | * [Samples](./Samples):此目录下包含使用Avalonia开发的一些常用的Demo,可以下载进行参阅。
9 |
10 |
11 | ## Demo列表
12 |
13 | | 名称 | 介绍 |
14 | |---|---|
15 | |[LineDemo](./Samples/LineDemo)|曲线绘制相关Demo|
16 | |[MVSCameraDemo](./Samples/MVSCameraDemo)|海康工业相机连接Demo,提供C#调用海康C语言SDK示例以及调用海康相机显示实时视频流、存储图片的功能|
17 | |[OpcUADemo](./Samples/OpcUADemo/OpcUAClient)|连接OPC UA服务端的Demo,提供OPC UA客户端,具有曲线绘制等相关功能|
18 | ### 更多Avalonia开发资料及相应问题,请参阅[知识文档](https://sinsegye-csharp.github.io/avalonia-doc)
19 |
--------------------------------------------------------------------------------
/Samples/LineDemo/LineDemo.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.5.33424.131
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LineDemo", "LineDemo\LineDemo.csproj", "{AF8156C7-D27B-4D2F-9BA8-DFF968E375AE}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {AF8156C7-D27B-4D2F-9BA8-DFF968E375AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {AF8156C7-D27B-4D2F-9BA8-DFF968E375AE}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {AF8156C7-D27B-4D2F-9BA8-DFF968E375AE}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {AF8156C7-D27B-4D2F-9BA8-DFF968E375AE}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ExtensibilityGlobals) = postSolution
23 | SolutionGuid = {E8196FA2-733E-4561-AB23-8C13E978C077}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/Samples/LineDemo/LineDemo/App.axaml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/Samples/LineDemo/LineDemo/App.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Controls.ApplicationLifetimes;
3 | using Avalonia.Markup.Xaml;
4 | using AvaloniaDemo.ViewModels;
5 | using AvaloniaDemo.Views;
6 |
7 | namespace AvaloniaDemo;
8 |
9 | public partial class App : Application
10 | {
11 | public override void Initialize()
12 | {
13 | AvaloniaXamlLoader.Load(this);
14 | }
15 |
16 | public override void OnFrameworkInitializationCompleted()
17 | {
18 | if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
19 | {
20 | desktop.MainWindow = new MainWindow
21 | {
22 | DataContext = new MainWindowViewModel(),
23 | };
24 | }
25 |
26 | base.OnFrameworkInitializationCompleted();
27 | }
28 | }
--------------------------------------------------------------------------------
/Samples/LineDemo/LineDemo/Assets/aaa.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sinsegye-CSharp/AvaloniaSamples/57bdbf48c0eb86b05ae12e239984a27e9291b8c4/Samples/LineDemo/LineDemo/Assets/aaa.png
--------------------------------------------------------------------------------
/Samples/LineDemo/LineDemo/Assets/avalonia-logo.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sinsegye-CSharp/AvaloniaSamples/57bdbf48c0eb86b05ae12e239984a27e9291b8c4/Samples/LineDemo/LineDemo/Assets/avalonia-logo.ico
--------------------------------------------------------------------------------
/Samples/LineDemo/LineDemo/Assets/avalonia.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sinsegye-CSharp/AvaloniaSamples/57bdbf48c0eb86b05ae12e239984a27e9291b8c4/Samples/LineDemo/LineDemo/Assets/avalonia.png
--------------------------------------------------------------------------------
/Samples/LineDemo/LineDemo/LineDemo.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | WinExe
4 | net6.0
5 | enable
6 | true
7 | app.manifest
8 | AvaloniaDemo
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/Samples/LineDemo/LineDemo/Program.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.ReactiveUI;
3 | using System;
4 |
5 | namespace AvaloniaDemo;
6 |
7 | class Program
8 | {
9 | // Initialization code. Don't use any Avalonia, third-party APIs or any
10 | // SynchronizationContext-reliant code before AppMain is called: things aren't initialized
11 | // yet and stuff might break.
12 | [STAThread]
13 | public static void Main(string[] args) => BuildAvaloniaApp()
14 | .StartWithClassicDesktopLifetime(args);
15 |
16 | // Avalonia configuration, don't remove; also used by visual designer.
17 | public static AppBuilder BuildAvaloniaApp()
18 | => AppBuilder.Configure()
19 | .UsePlatformDetect()
20 | .LogToTrace()
21 | .UseReactiveUI();
22 | }
23 |
--------------------------------------------------------------------------------
/Samples/LineDemo/LineDemo/ViewLocator.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Avalonia.Controls;
3 | using Avalonia.Controls.Templates;
4 | using AvaloniaDemo.ViewModels;
5 |
6 | namespace AvaloniaDemo;
7 |
8 | public class ViewLocator : IDataTemplate
9 | {
10 | public IControl Build(object data)
11 | {
12 | var viewModelName = data.GetType().FullName;
13 | if (string.IsNullOrWhiteSpace(viewModelName))
14 | {
15 | return new TextBlock { Text = "Not Found: " };
16 | }
17 |
18 | var viewTypeName = viewModelName.TrimEnd("Model".ToCharArray()).Replace("ViewModels","Views");
19 | var type = Type.GetType(viewTypeName);
20 |
21 | if (type != null)
22 | {
23 | return (Control)Activator.CreateInstance(type)!;
24 | }
25 |
26 | return new TextBlock { Text = "Not Found: " + viewTypeName };
27 | }
28 |
29 | public bool Match(object data)
30 | {
31 | return data is ViewModelBase;
32 | }
33 | }
--------------------------------------------------------------------------------
/Samples/LineDemo/LineDemo/ViewModels/ChartViewModel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.ObjectModel;
3 | using Avalonia.Threading;
4 | using LiveChartsCore;
5 | using LiveChartsCore.Defaults;
6 | using LiveChartsCore.SkiaSharpView;
7 | using LiveChartsCore.SkiaSharpView.Painting;
8 | using SkiaSharp;
9 |
10 | namespace AvaloniaDemo.ViewModels;
11 |
12 | public class ChartViewModel : ViewModelBase
13 | {
14 | #region Private Fileds
15 |
16 | // 动态折线1数据
17 | private readonly ObservableCollection _observableValues1;
18 |
19 | // 动态折线2数据
20 | private readonly ObservableCollection _observableValues2;
21 |
22 | // 生成随机数据的函数
23 | private readonly Random _random = new();
24 |
25 | #endregion
26 |
27 | ///
28 | /// 构造函数
29 | ///
30 | public ChartViewModel()
31 | {
32 | _observableValues1 = new ObservableCollection
33 | {
34 | new(DateTime.Now.AddSeconds(-1), 0),
35 | };
36 | _observableValues2 = new ObservableCollection
37 | {
38 | new(DateTime.Now.AddSeconds(-1), 0),
39 | };
40 |
41 | CartesianLineChartXAxis = new[]
42 | {
43 | new Axis
44 | {
45 | MinLimit = DateTime.Now.Ticks,
46 | MinStep = TimeSpan.FromSeconds(1).Ticks,
47 | UnitWidth = TimeSpan.FromSeconds(1).Ticks,
48 | Labeler = value=>new DateTime((long)value).ToString("HH:mm:ss"),
49 | TicksPaint = new SolidColorPaint(SKColors.Black)
50 | }
51 | };
52 | CartesianChartYAxis = new[]
53 | {
54 | new Axis
55 | {
56 | MinLimit = 0,
57 | MinStep = 1,
58 | }
59 | };
60 |
61 | CartesianChartLineSeries = new ObservableCollection
62 | {
63 | new LineSeries
64 | {
65 | Name = "城市规模",
66 | Values = _observableValues1,
67 | },
68 | new LineSeries
69 | {
70 | Name = "城市经济",
71 | Values = _observableValues2,
72 | },
73 | };
74 |
75 | UpdateLine();
76 | }
77 |
78 | #region Public Properties
79 |
80 | ///
81 | /// 折线图X轴设置
82 | ///
83 | public Axis[] CartesianLineChartXAxis { get; set; }
84 |
85 | ///
86 | /// 折线图Y轴设置
87 | ///
88 | public Axis[] CartesianChartYAxis { get; set; }
89 |
90 | ///
91 | /// 折线图曲线
92 | ///
93 | public ObservableCollection CartesianChartLineSeries { get; set; }
94 |
95 | ///
96 | /// 提示框绘制设置
97 | ///
98 | public SolidColorPaint TooltipTextPaint { get; set; } =
99 | new()
100 | {
101 | Color = SKColors.Black,
102 | SKTypeface = SKFontManager.Default.MatchCharacter('汉'),
103 | };
104 |
105 | #endregion
106 |
107 | #region Private Methods
108 |
109 | // 更新曲线
110 | private void UpdateLine()
111 | {
112 | var timer = new DispatcherTimer
113 | {
114 | Interval = TimeSpan.FromSeconds(1)
115 | };
116 | timer.Tick += (_, _) =>
117 | {
118 | if (_observableValues1.Count > 10)
119 | {
120 | CartesianLineChartXAxis[0].MinLimit = _observableValues1[^10].DateTime.Ticks;
121 | }
122 | _observableValues1.Add(new DateTimePoint(DateTime.Now, _random.Next(1, 10)));
123 | _observableValues2.Add(new DateTimePoint(DateTime.Now, _random.Next(1, 10)));
124 | };
125 | timer.Start();
126 | }
127 |
128 | #endregion
129 | }
--------------------------------------------------------------------------------
/Samples/LineDemo/LineDemo/ViewModels/MainWindowViewModel.cs:
--------------------------------------------------------------------------------
1 | using ReactiveUI;
2 |
3 | namespace AvaloniaDemo.ViewModels;
4 |
5 | public class MainWindowViewModel : ViewModelBase
6 | {
7 | private string _title = "曲线图演示Demo";
8 |
9 | ///
10 | /// 程序标题
11 | ///
12 | public string Title
13 | {
14 | get => _title;
15 | set => this.RaiseAndSetIfChanged(ref _title, value);
16 | }
17 |
18 | private ChartViewModel _chartViewModel=new();
19 |
20 | public ChartViewModel ChartViewModel
21 | {
22 | get => _chartViewModel;
23 | set => this.RaiseAndSetIfChanged(ref _chartViewModel, value);
24 | }
25 | }
--------------------------------------------------------------------------------
/Samples/LineDemo/LineDemo/ViewModels/ViewModelBase.cs:
--------------------------------------------------------------------------------
1 | using ReactiveUI;
2 |
3 | namespace AvaloniaDemo.ViewModels;
4 |
5 | public class ViewModelBase : ReactiveObject
6 | {
7 | }
8 |
--------------------------------------------------------------------------------
/Samples/LineDemo/LineDemo/Views/ChartView.axaml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
20 |
21 |
--------------------------------------------------------------------------------
/Samples/LineDemo/LineDemo/Views/ChartView.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Markup.Xaml;
2 | using Avalonia.ReactiveUI;
3 | using AvaloniaDemo.ViewModels;
4 | using ReactiveUI;
5 |
6 | namespace AvaloniaDemo.Views;
7 |
8 | public partial class ChartView : ReactiveUserControl
9 | {
10 | public ChartView()
11 | {
12 | InitializeComponent();
13 | }
14 |
15 | private void InitializeComponent()
16 | {
17 | this.WhenActivated(disposable =>
18 | {
19 |
20 | });
21 | AvaloniaXamlLoader.Load(this);
22 | }
23 | }
--------------------------------------------------------------------------------
/Samples/LineDemo/LineDemo/Views/MainWindow.axaml:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/Samples/LineDemo/LineDemo/Views/MainWindow.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Markup.Xaml;
3 | using Avalonia.ReactiveUI;
4 | using AvaloniaDemo.ViewModels;
5 | using ReactiveUI;
6 |
7 | namespace AvaloniaDemo.Views;
8 |
9 | public partial class MainWindow : ReactiveWindow
10 | {
11 | public MainWindow()
12 | {
13 | this.WhenActivated(disposables =>
14 | {
15 | });
16 | AvaloniaXamlLoader.Load(this);
17 |
18 | #if DEBUG
19 | this.AttachDevTools();
20 | #endif
21 | }
22 | }
--------------------------------------------------------------------------------
/Samples/LineDemo/LineDemo/app.manifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
10 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/Samples/MVSCameraDemo/CameraDemo.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CameraDemo", "CameraDemo\CameraDemo.csproj", "{82D7DB28-F1D0-400F-8CFC-7F8559444FC8}"
4 | EndProject
5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HKSDK", "HKSDK\HKSDK.csproj", "{E0C3B0D0-085B-4F07-B162-80209B655640}"
6 | EndProject
7 | Global
8 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
9 | Debug|Any CPU = Debug|Any CPU
10 | Release|Any CPU = Release|Any CPU
11 | EndGlobalSection
12 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
13 | {82D7DB28-F1D0-400F-8CFC-7F8559444FC8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
14 | {82D7DB28-F1D0-400F-8CFC-7F8559444FC8}.Debug|Any CPU.Build.0 = Debug|Any CPU
15 | {82D7DB28-F1D0-400F-8CFC-7F8559444FC8}.Release|Any CPU.ActiveCfg = Release|Any CPU
16 | {82D7DB28-F1D0-400F-8CFC-7F8559444FC8}.Release|Any CPU.Build.0 = Release|Any CPU
17 | {E0C3B0D0-085B-4F07-B162-80209B655640}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
18 | {E0C3B0D0-085B-4F07-B162-80209B655640}.Debug|Any CPU.Build.0 = Debug|Any CPU
19 | {E0C3B0D0-085B-4F07-B162-80209B655640}.Release|Any CPU.ActiveCfg = Release|Any CPU
20 | {E0C3B0D0-085B-4F07-B162-80209B655640}.Release|Any CPU.Build.0 = Release|Any CPU
21 | EndGlobalSection
22 | EndGlobal
23 |
--------------------------------------------------------------------------------
/Samples/MVSCameraDemo/CameraDemo/App.axaml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/Samples/MVSCameraDemo/CameraDemo/App.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Controls.ApplicationLifetimes;
3 | using Avalonia.Markup.Xaml;
4 | using Avalonia.Platform;
5 | using CameraDemo.ViewModels;
6 | using CameraDemo.Views;
7 |
8 | namespace CameraDemo;
9 |
10 | public partial class App : Application
11 | {
12 | public override void Initialize()
13 | {
14 | AvaloniaXamlLoader.Load(this);
15 | }
16 |
17 | public override void OnFrameworkInitializationCompleted()
18 | {
19 | if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
20 | {
21 | desktop.MainWindow = new MainWindow
22 | {
23 | DataContext = new MainWindowViewModel(),
24 | };
25 | }
26 |
27 | base.OnFrameworkInitializationCompleted();
28 | }
29 |
30 | public override void RegisterServices()
31 | {
32 | AvaloniaLocator.CurrentMutable.Bind().ToConstant(new FontManager());
33 | base.RegisterServices();
34 | }
35 | }
--------------------------------------------------------------------------------
/Samples/MVSCameraDemo/CameraDemo/Assets/avalonia-logo.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sinsegye-CSharp/AvaloniaSamples/57bdbf48c0eb86b05ae12e239984a27e9291b8c4/Samples/MVSCameraDemo/CameraDemo/Assets/avalonia-logo.ico
--------------------------------------------------------------------------------
/Samples/MVSCameraDemo/CameraDemo/Assets/msyh.ttc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sinsegye-CSharp/AvaloniaSamples/57bdbf48c0eb86b05ae12e239984a27e9291b8c4/Samples/MVSCameraDemo/CameraDemo/Assets/msyh.ttc
--------------------------------------------------------------------------------
/Samples/MVSCameraDemo/CameraDemo/CameraDemo.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | WinExe
4 | net6.0
5 | enable
6 | true
7 | app.manifest
8 | true
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 |
--------------------------------------------------------------------------------
/Samples/MVSCameraDemo/CameraDemo/Controls/HandleControl.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Avalonia;
3 | using Avalonia.Controls;
4 | using Avalonia.Data;
5 | using Avalonia.Platform;
6 |
7 | namespace CameraDemo.Controls;
8 |
9 | ///
10 | /// 海康相机控件
11 | ///
12 | public class HkCameraControl : NativeControlHost
13 | {
14 | public static readonly StyledProperty HandleProperty = AvaloniaProperty.Register(
15 | "Handle", default, false, BindingMode.OneWayToSource);
16 |
17 | ///
18 | /// 窗口句柄
19 | ///
20 | public IntPtr Handle
21 | {
22 | get => GetValue(HandleProperty);
23 | set => SetValue(HandleProperty, value);
24 | }
25 |
26 | protected override IPlatformHandle CreateNativeControlCore(IPlatformHandle parent)
27 | {
28 | var handle = base.CreateNativeControlCore(parent);
29 | this.Handle = handle.Handle;
30 | return handle;
31 | }
32 | }
--------------------------------------------------------------------------------
/Samples/MVSCameraDemo/CameraDemo/FontManager.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Globalization;
3 | using System.Linq;
4 | using Avalonia.Media;
5 | using Avalonia.Platform;
6 | using Avalonia.Skia;
7 | using SkiaSharp;
8 |
9 | namespace CameraDemo;
10 |
11 | public class FontManager : IFontManagerImpl
12 | {
13 | private readonly Typeface[] _customTypefaces;
14 | private readonly string _defaultFamilyName;
15 |
16 | private readonly Typeface _defaultTypeface = new("avares://CameraDemo/Assets/msyh.ttc#微软雅黑");
17 |
18 | private readonly string[] _bcp47 =
19 | { CultureInfo.CurrentCulture.ThreeLetterISOLanguageName, CultureInfo.CurrentCulture.TwoLetterISOLanguageName };
20 |
21 | public FontManager()
22 | {
23 | _customTypefaces = new[] { _defaultTypeface };
24 | _defaultFamilyName = _defaultTypeface.FontFamily.FamilyNames.PrimaryFamilyName;
25 | }
26 |
27 | public string GetDefaultFontFamilyName() => _defaultFamilyName;
28 |
29 | public IEnumerable GetInstalledFontFamilyNames(bool checkForUpdates = false) =>
30 | _customTypefaces.Select(x => x.FontFamily.Name);
31 |
32 | public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, FontFamily fontFamily,
33 | CultureInfo culture, out Typeface typeface)
34 | {
35 | foreach (var customTypeface in _customTypefaces)
36 | {
37 | if (customTypeface.GlyphTypeface.GetGlyph((uint)codepoint) == 0)
38 | {
39 | continue;
40 | }
41 |
42 | typeface = new Typeface(customTypeface.FontFamily.Name, fontStyle, fontWeight);
43 |
44 | return true;
45 | }
46 |
47 | var fallback = SKFontManager.Default.MatchCharacter(fontFamily?.Name, (SKFontStyleWeight)fontWeight,
48 | SKFontStyleWidth.Normal, (SKFontStyleSlant)fontStyle, _bcp47, codepoint);
49 |
50 | typeface = new Typeface(fallback?.FamilyName ?? _defaultFamilyName, fontStyle, fontWeight);
51 |
52 | return true;
53 | }
54 |
55 | public IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface)
56 | {
57 | var skTypeface = typeface.FontFamily.Name switch
58 | {
59 | FontFamily.DefaultFontFamilyName => SKTypeface.FromFamilyName(_defaultTypeface.FontFamily.Name),
60 | "微软雅黑" => SKTypeface.FromFamilyName(_defaultTypeface.FontFamily.Name),
61 | _ => SKTypeface.FromFamilyName(typeface.FontFamily.Name,
62 | (SKFontStyleWeight)typeface.Weight, SKFontStyleWidth.Normal, (SKFontStyleSlant)typeface.Style)
63 | };
64 |
65 | return new GlyphTypefaceImpl(skTypeface);
66 | }
67 | }
--------------------------------------------------------------------------------
/Samples/MVSCameraDemo/CameraDemo/Program.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.ReactiveUI;
3 | using System;
4 |
5 | namespace CameraDemo;
6 |
7 | class Program
8 | {
9 | // Initialization code. Don't use any Avalonia, third-party APIs or any
10 | // SynchronizationContext-reliant code before AppMain is called: things aren't initialized
11 | // yet and stuff might break.
12 | [STAThread]
13 | public static void Main(string[] args) => BuildAvaloniaApp()
14 | .StartWithClassicDesktopLifetime(args);
15 |
16 | // Avalonia configuration, don't remove; also used by visual designer.
17 | public static AppBuilder BuildAvaloniaApp()
18 | => AppBuilder.Configure()
19 | .UsePlatformDetect()
20 | .LogToTrace()
21 | .UseReactiveUI();
22 | }
--------------------------------------------------------------------------------
/Samples/MVSCameraDemo/CameraDemo/ViewLocator.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Avalonia.Controls;
3 | using Avalonia.Controls.Templates;
4 | using CameraDemo.ViewModels;
5 |
6 | namespace CameraDemo;
7 |
8 | public class ViewLocator : IDataTemplate
9 | {
10 | public IControl Build(object data)
11 | {
12 | var name = data.GetType().FullName!.Replace("ViewModel", "View");
13 | var type = Type.GetType(name);
14 |
15 | if (type != null)
16 | {
17 | return (Control)Activator.CreateInstance(type)!;
18 | }
19 |
20 | return new TextBlock { Text = "Not Found: " + name };
21 | }
22 |
23 | public bool Match(object data)
24 | {
25 | return data is ViewModelBase;
26 | }
27 | }
--------------------------------------------------------------------------------
/Samples/MVSCameraDemo/CameraDemo/ViewModels/MainWindowViewModel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.ObjectModel;
4 | using System.Drawing.Imaging;
5 | using System.IO;
6 | using System.Runtime.InteropServices;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 | using Avalonia.Controls;
10 | using Avalonia.Controls.ApplicationLifetimes;
11 | using Avalonia.Metadata;
12 | using HKSDK;
13 | using HKSDK.Enums;
14 | using HKSDK.Models;
15 | using ReactiveUI;
16 | using Image = System.Drawing.Image;
17 |
18 | namespace CameraDemo.ViewModels;
19 |
20 | public class MainWindowViewModel : ViewModelBase
21 | {
22 | #region Private Fields
23 |
24 | // 图像接收回调函数
25 | private readonly ReceiveImageDataDelegate _receiveImageData;
26 |
27 | // 相机句柄
28 | private IntPtr _cameraHandel;
29 |
30 | // 是否保存图片
31 | private bool _isSaveImage;
32 |
33 | private readonly IDictionary _cameraIntPtrDictionary;
34 |
35 | #endregion
36 |
37 | #region Public Properties
38 |
39 | private IntPtr _handle;
40 |
41 | ///
42 | /// 播放视频的窗口句柄
43 | ///
44 | public IntPtr Handle
45 | {
46 | get => _handle;
47 | set => this.RaiseAndSetIfChanged(ref _handle, value);
48 | }
49 |
50 | ///
51 | /// 相机列表
52 | ///
53 | public ObservableCollection CameraList { get; }
54 |
55 | private string _selectedCamera = "";
56 |
57 | ///
58 | /// 选中的相机
59 | ///
60 | public string SelectedCamera
61 | {
62 | get => _selectedCamera;
63 | set => this.RaiseAndSetIfChanged(ref _selectedCamera, value);
64 | }
65 |
66 | private string _currentFps = "";
67 |
68 | ///
69 | /// 当前帧率
70 | ///
71 | public string CurrentFps
72 | {
73 | get => _currentFps;
74 | set => this.RaiseAndSetIfChanged(ref _currentFps, value);
75 | }
76 |
77 | private string _resultingFrameRate = "";
78 |
79 | ///
80 | /// 实际帧速率帧率
81 | ///
82 | public string ResultingFrameRate
83 | {
84 | get => _resultingFrameRate;
85 | set => this.RaiseAndSetIfChanged(ref _resultingFrameRate, value);
86 | }
87 |
88 | private string _settingFps = "";
89 |
90 | ///
91 | /// 要设置的帧率
92 | ///
93 | public string SettingFps
94 | {
95 | get => _settingFps;
96 | set => this.RaiseAndSetIfChanged(ref _settingFps, value);
97 | }
98 |
99 | private bool _isPlaying;
100 |
101 | ///
102 | /// 是否正在播放
103 | ///
104 | public bool IsPlaying
105 | {
106 | get => _isPlaying;
107 | set => this.RaiseAndSetIfChanged(ref _isPlaying, value);
108 | }
109 |
110 | private string _imageSavePath =
111 | Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "CameraTest");
112 |
113 | ///
114 | /// 图片保存路径
115 | ///
116 | public string ImageSavePath
117 | {
118 | get => _imageSavePath;
119 | set => this.RaiseAndSetIfChanged(ref _imageSavePath, value);
120 | }
121 |
122 | #endregion
123 |
124 | #region Ctor
125 |
126 | ///
127 | /// 构造函数
128 | ///
129 | public MainWindowViewModel()
130 | {
131 | _receiveImageData = OnReceiveImage;
132 | CameraList = new ObservableCollection();
133 | _cameraIntPtrDictionary = new Dictionary();
134 |
135 | Init();
136 | }
137 |
138 | #endregion
139 |
140 | #region Public Methods
141 |
142 | ///
143 | /// 开始播放
144 | ///
145 | public void StartPlay()
146 | {
147 | var isOk = HkCameraControl.MV_CC_CreateHandle(out _cameraHandel, _cameraIntPtrDictionary[SelectedCamera]);
148 | Console.WriteLine($"创建句柄{isOk:x8}");
149 |
150 | isOk = HkCameraControl.MV_CC_OpenDevice(_cameraHandel, 1, 0);
151 | Console.WriteLine($"打开设备{isOk:x8}");
152 |
153 | isOk = HkCameraControl.MV_CC_RegisterImageCallBackEx(_cameraHandel, _receiveImageData, new IntPtr());
154 | Console.WriteLine($"注册取图回调{isOk:x8}");
155 |
156 | isOk = HkCameraControl.MV_CC_StartGrabbing(_cameraHandel);
157 | Console.WriteLine($"开始采集{isOk:x8}");
158 |
159 | isOk = HkCameraControl.MV_CC_Display(_cameraHandel, Handle);
160 | Console.WriteLine($"开始播放{isOk:x8}");
161 |
162 | this.IsPlaying = true;
163 |
164 | GetFpsValue();
165 | }
166 |
167 | ///
168 | /// 是否可以开始播放
169 | ///
170 | ///
171 | [DependsOn(nameof(SelectedCamera))]
172 | public bool CanStartPlay(object _) => !string.IsNullOrWhiteSpace(SelectedCamera);
173 |
174 | ///
175 | /// 停止播放
176 | ///
177 | public void Stop()
178 | {
179 | var b = HkCameraControl.MV_CC_StopGrabbing(_cameraHandel);
180 | Console.WriteLine($"停止采集{b:x8}");
181 |
182 | b = HkCameraControl.MV_CC_CloseDevice(_cameraHandel);
183 | Console.WriteLine($"关闭设备{b:x8}");
184 |
185 | b = HkCameraControl.MV_CC_DestroyHandle(_cameraHandel);
186 | Console.WriteLine($"释放句柄{b:x8}");
187 |
188 | this.IsPlaying = false;
189 | this.CurrentFps = "";
190 | this.SettingFps = "";
191 | this.ResultingFrameRate = "";
192 | }
193 |
194 | ///
195 | /// 抓取图片
196 | ///
197 | public void GrabPicture()
198 | {
199 | _isSaveImage = true;
200 | }
201 |
202 | ///
203 | /// 修改帧率
204 | ///
205 | public void ModifyFps()
206 | {
207 | var isOk = HkCameraControl.MV_CC_SetFloatValue(_cameraHandel, "AcquisitionFrameRate", float.Parse(SettingFps));
208 | if (isOk != 0)
209 | {
210 | Console.WriteLine($"设置帧率失败,错误码{isOk:x8}");
211 | return;
212 | }
213 |
214 | GetFpsValue();
215 | }
216 |
217 | [DependsOn(nameof(SettingFps))]
218 | public bool CanModifyFps(object _)
219 | {
220 | if (SettingFps == CurrentFps) return false;
221 | return float.TryParse(SettingFps, out var _);
222 | }
223 |
224 | ///
225 | /// 更改保存位置
226 | ///
227 | public async Task ChangeSavePath(Window window)
228 | {
229 | OpenFolderDialog dialog = new OpenFolderDialog();
230 | var result = await dialog.ShowAsync(window);
231 | if(string.IsNullOrWhiteSpace(result)) return;
232 |
233 | this.ImageSavePath = result;
234 | }
235 |
236 | #endregion
237 |
238 | #region Private Methods
239 |
240 | // 初始化,获取相机列表等
241 | private void Init()
242 | {
243 | var isOk = HkCameraControl.MV_CC_EnumDevices((uint)DeviceLayerType.Gige, out var deviceList);
244 |
245 | if (isOk != 0)
246 | {
247 | Console.WriteLine($"请求出错,错误码{isOk:x8}");
248 | return;
249 | }
250 |
251 | if (deviceList.DeviceNumber == 0)
252 | {
253 | Console.WriteLine($"没有可用设备");
254 | return;
255 | }
256 |
257 | Console.WriteLine($"获取到{deviceList.DeviceNumber}个设备");
258 | for (var i = 0; i < deviceList.DeviceNumber; i++)
259 | {
260 | Console.WriteLine($" 设备{i + 1}:");
261 |
262 | var devicePtr = deviceList.DeviceInfos[i];
263 | var device = Marshal.PtrToStructure(devicePtr);
264 |
265 | var deviceModel = Encoding.UTF8.GetString(device.GigEInfo.ModelName).Trim('\0');
266 | Console.WriteLine($" 名称:{deviceModel}");
267 |
268 | var deviceSerialNumber = Encoding.UTF8.GetString(device.GigEInfo.SerialNumber).Trim('\0');
269 | Console.WriteLine($" 序列号:{deviceSerialNumber}");
270 |
271 | var deviceVersion = Encoding.UTF8.GetString(device.GigEInfo.DeviceVersion);
272 | Console.WriteLine($" 版本:{deviceVersion}");
273 |
274 | var ip1 = (device.GigEInfo.CurrentIp & 0xff000000) >> 24;
275 | var ip2 = (device.GigEInfo.CurrentIp & 0x00ff0000) >> 16;
276 | var ip3 = (device.GigEInfo.CurrentIp & 0x0000ff00) >> 8;
277 | var ip4 = device.GigEInfo.CurrentIp & 0x000000ff;
278 | Console.WriteLine($" 设备IP:{ip1}:{ip2}:{ip3}:{ip4}");
279 |
280 | _cameraIntPtrDictionary.Add($"{deviceModel}({deviceSerialNumber})", devicePtr);
281 | CameraList.Add($"{deviceModel}({deviceSerialNumber})");
282 | }
283 |
284 | // ReSharper disable once AccessToStaticMemberViaDerivedType
285 | if (App.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime lifetime)
286 | {
287 | lifetime.Exit += (_, _) => { Stop(); };
288 | }
289 | }
290 |
291 | // 接收到图片,进行处理,保存
292 | private void OnReceiveImage(IntPtr dataPtr, IntPtr imageFrameInfoPtr, IntPtr userCustom)
293 | {
294 | if (!_isSaveImage) return;
295 | var frameInfo = Marshal.PtrToStructure(imageFrameInfoPtr);
296 | Console.WriteLine(
297 | $"{frameInfo.HostTimeStamp}获取到图像:{frameInfo.Width}x{frameInfo.Height},格式:{frameInfo.PixelFormatType}");
298 | _isSaveImage = false;
299 |
300 | var bufferSize = frameInfo.Width * frameInfo.Height * 4 + 2048;
301 |
302 | // 创建Byte数组内存指针,用于接收图片数据
303 | var imageDataPtr = Marshal.AllocHGlobal(bufferSize);
304 | Marshal.WriteByte(imageDataPtr, 0);
305 |
306 | // 调用SDK进行图片转换
307 | var saveImageParams = new SaveImageParams
308 | {
309 | ImageData = dataPtr,
310 | DataLength = frameInfo.FrameLength,
311 | PixelFormatType = frameInfo.PixelFormatType,
312 | Width = frameInfo.Width,
313 | Height = frameInfo.Height,
314 | ImageType = ImageType.Jpeg,
315 | BufferSize = (uint)bufferSize,
316 | JpgQuality = 80,
317 | ImageBuffer = imageDataPtr
318 | };
319 | var b = HkCameraControl.MV_CC_SaveImageEx2(_cameraHandel, ref saveImageParams);
320 | Console.WriteLine($"图片转换{b:x8}");
321 |
322 | // 将内存指针中的图片数据拷贝到托管数组中
323 | var imageBytes = new byte[bufferSize];
324 | Marshal.Copy(imageDataPtr, imageBytes, 0, bufferSize);
325 |
326 | // 释放自己创建的内存指针
327 | Marshal.FreeHGlobal(imageDataPtr);
328 |
329 | // 保存图片
330 | using var stream = new MemoryStream(imageBytes);
331 | var bitmap = Image.FromStream(stream);
332 |
333 | try
334 | {
335 | if (!Directory.Exists(ImageSavePath))
336 | {
337 | Directory.CreateDirectory(ImageSavePath);
338 | }
339 |
340 | var dateTime = DateTimeOffset.FromUnixTimeMilliseconds(frameInfo.HostTimeStamp).LocalDateTime;
341 | bitmap?.Save(Path.Combine(ImageSavePath, $"{dateTime:yyyy-MM-dd HH:mm:ss}.jpg"), ImageFormat.Jpeg);
342 |
343 | Console.WriteLine($"保存完成");
344 | }
345 | catch (Exception e)
346 | {
347 | Console.WriteLine(e);
348 | }
349 | }
350 |
351 | // 获取当前帧率
352 | private void GetFpsValue()
353 | {
354 | var isOk = HkCameraControl.MV_CC_GetFloatValue(_cameraHandel, "AcquisitionFrameRate", out var fpsSetting);
355 | if (isOk != 0)
356 | {
357 | Console.WriteLine($"获取失败,错误码{isOk:x8}");
358 | return;
359 | }
360 |
361 | isOk = HkCameraControl.MV_CC_GetFloatValue(_cameraHandel, "ResultingFrameRate", out var resultingFrameRate);
362 | if (isOk != 0)
363 | {
364 | Console.WriteLine($"获取失败,错误码{isOk:x8}");
365 | return;
366 | }
367 |
368 | this.CurrentFps = fpsSetting.CurrentValue.ToString("0.00");
369 | this.SettingFps = fpsSetting.CurrentValue.ToString("0.00");
370 | this.ResultingFrameRate = resultingFrameRate.CurrentValue.ToString("0.00");
371 | }
372 |
373 | #endregion
374 | }
--------------------------------------------------------------------------------
/Samples/MVSCameraDemo/CameraDemo/ViewModels/ViewModelBase.cs:
--------------------------------------------------------------------------------
1 | using ReactiveUI;
2 |
3 | namespace CameraDemo.ViewModels;
4 |
5 | public class ViewModelBase : ReactiveObject
6 | {
7 | }
--------------------------------------------------------------------------------
/Samples/MVSCameraDemo/CameraDemo/Views/MainWindow.axaml:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
33 |
35 |
36 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
46 |
48 |
49 |
50 |
51 |
54 |
55 |
56 |
58 |
62 |
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/Samples/MVSCameraDemo/CameraDemo/Views/MainWindow.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls;
2 |
3 | namespace CameraDemo.Views;
4 |
5 | public partial class MainWindow : Window
6 | {
7 | public MainWindow()
8 | {
9 | InitializeComponent();
10 | }
11 | }
--------------------------------------------------------------------------------
/Samples/MVSCameraDemo/CameraDemo/app.manifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
10 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/Samples/MVSCameraDemo/HKSDK/Enums/DeviceLayerType.cs:
--------------------------------------------------------------------------------
1 | namespace HKSDK.Enums;
2 |
3 | ///
4 | /// 设备传输层协议类型
5 | ///
6 | public enum DeviceLayerType : uint
7 | {
8 | ///
9 | /// 未知类型
10 | ///
11 | Unknown = 0x00000000,
12 |
13 | ///
14 | /// GIGE设备
15 | ///
16 | Gige = 0x00000001,
17 |
18 | ///
19 | /// 1394-a/b设备
20 | ///
21 | M1394 = 0x00000002,
22 |
23 | ///
24 | /// USB3.0设备
25 | ///
26 | Usb = 0x00000004,
27 |
28 | ///
29 | /// CameraLink设备
30 | ///
31 | CameraLink = 0x00000008
32 | }
--------------------------------------------------------------------------------
/Samples/MVSCameraDemo/HKSDK/Enums/ImageType.cs:
--------------------------------------------------------------------------------
1 | namespace HKSDK.Enums;
2 |
3 | ///
4 | /// 图片类型
5 | ///
6 | public enum ImageType
7 | {
8 | Bmp = 1,
9 |
10 | Jpeg = 2,
11 |
12 | Png = 3
13 | }
--------------------------------------------------------------------------------
/Samples/MVSCameraDemo/HKSDK/Enums/PixelFormatType.cs:
--------------------------------------------------------------------------------
1 | namespace HKSDK.Enums;
2 |
3 | ///
4 | /// 图像像素格式
5 | ///
6 | public enum PixelFormatType: long
7 | {
8 | Mono8 = 0x01080001,
9 | Mono10 = 0x01100003,
10 | Mono10Packed = 0x010C0004,
11 | Mono12 = 0x01100005,
12 | Mono12Packed = 0x010C0006,
13 | Mono16 = 0x01100007,
14 | RGB8Packed = 0x02180014,
15 | YUV422_8 = 0x02100032,
16 | YUV422_8_UYVY = 0x0210001F,
17 | BayerGR8 = 0x01080008,
18 | BayerRG8 = 0x01080009,
19 | BayerGB8 = 0x0108000A,
20 | BayerBG8 = 0x0108000B,
21 | BayerGB10 = 0x0110000e,
22 | BayerGB12 = 0x01100012,
23 | BayerGB12Packed = 0x010C002C
24 | }
--------------------------------------------------------------------------------
/Samples/MVSCameraDemo/HKSDK/HKCameraControl.cs:
--------------------------------------------------------------------------------
1 | global using System.Runtime.InteropServices;
2 | using HKSDK.Models;
3 |
4 | namespace HKSDK;
5 |
6 | ///
7 | /// 接收图片数据委托
8 | ///
9 | public delegate void ReceiveImageDataDelegate(IntPtr dataPtr, IntPtr imageFrameInfoPtr, IntPtr userCustom);
10 |
11 | public static class HkCameraControl
12 | {
13 | ///
14 | /// 枚举网络中的设备
15 | ///
16 | /// 传输协议类型,按位传入,可传多个
17 | /// 设备列表
18 | /// 接口是否运行成功
19 | [DllImport("Lib/libMvCameraControl")]
20 | public static extern int MV_CC_EnumDevices(uint layerType, out DeviceInfoList pstDevList);
21 |
22 | ///
23 | /// 创建句柄
24 | ///
25 | /// 设备句柄
26 | /// 设备信息
27 | /// 接口是否运行成功
28 | [DllImport("Lib/libMvCameraControl")]
29 | public static extern int MV_CC_CreateHandle(out IntPtr handle, IntPtr pstDevInfo);
30 |
31 | ///
32 | /// 销毁句柄
33 | ///
34 | /// 设备句柄
35 | /// 接口是否运行成功
36 | [DllImport("Lib/libMvCameraControl")]
37 | public static extern int MV_CC_DestroyHandle(IntPtr handle);
38 |
39 | ///
40 | /// 打开设备
41 | ///
42 | /// 设备句柄
43 | /// 访问模式
44 | /// 访问秘钥
45 | /// 接口是否运行成功
46 | [DllImport("Lib/libMvCameraControl")]
47 | public static extern int MV_CC_OpenDevice(IntPtr handle, uint nAccessMode, ushort nSwitchoverKey);
48 |
49 | ///
50 | /// 关闭设备
51 | ///
52 | /// 设备句柄
53 | /// 接口是否运行成功
54 | [DllImport("Lib/libMvCameraControl")]
55 | public static extern int MV_CC_CloseDevice(IntPtr handle);
56 |
57 | ///
58 | /// 开始采集
59 | ///
60 | /// 设备句柄
61 | /// 接口是否运行成功
62 | [DllImport("Lib/libMvCameraControl")]
63 | public static extern int MV_CC_StartGrabbing(IntPtr handle);
64 |
65 | ///
66 | /// 停止采集
67 | ///
68 | /// 设备句柄
69 | /// 接口是否运行成功
70 | [DllImport("Lib/libMvCameraControl")]
71 | public static extern int MV_CC_StopGrabbing(IntPtr handle);
72 |
73 | ///
74 | /// 显示图像
75 | ///
76 | /// 设备句柄
77 | /// 显示图像的窗口句柄
78 | /// 接口是否运行成功
79 | [DllImport("Lib/libMvCameraControl")]
80 | public static extern int MV_CC_Display(IntPtr handle, IntPtr windowHandle);
81 |
82 | ///
83 | /// 注册回调函数,接收图像数据
84 | ///
85 | /// 设备句柄
86 | /// 接收图片数据的回调
87 | /// 用户自定义变量
88 | /// 接口是否运行成功
89 | [DllImport("Lib/libMvCameraControl")]
90 | public static extern int MV_CC_RegisterImageCallBackEx(IntPtr handle,
91 | ReceiveImageDataDelegate receiveImageCallback, IntPtr userCustom);
92 |
93 | ///
94 | /// 存储图像数据
95 | ///
96 | /// 设备句柄
97 | /// 保存图像参数
98 | /// 接口是否运行成功
99 | [DllImport("Lib/libMvCameraControl")]
100 | public static extern int MV_CC_SaveImageEx2(IntPtr handle, ref SaveImageParams saveImageParams);
101 |
102 | ///
103 | /// 获取Float类型的设置项
104 | ///
105 | /// 设备句柄
106 | /// 设置项字符串名称
107 | /// 设置项结构
108 | /// 操作结果
109 | [DllImport("Lib/libMvCameraControl")]
110 | public static extern int MV_CC_GetFloatValue(IntPtr handle, string strKey, out FloatSettingModel floatSetting);
111 |
112 | ///
113 | /// 设置Float类型的设置项
114 | ///
115 | /// 设备句柄
116 | /// 设置项字符串名称
117 | /// 要设置的值
118 | /// 操作结果
119 | [DllImport("Lib/libMvCameraControl")]
120 | public static extern int MV_CC_SetFloatValue(IntPtr handle, string strKey, float value);
121 | }
--------------------------------------------------------------------------------
/Samples/MVSCameraDemo/HKSDK/HKSDK.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 | enable
6 | enable
7 |
8 |
9 |
10 | true
11 |
12 |
13 |
14 |
15 | PreserveNewest
16 |
17 |
18 | PreserveNewest
19 |
20 |
21 | PreserveNewest
22 |
23 |
24 | PreserveNewest
25 |
26 |
27 | PreserveNewest
28 |
29 |
30 | PreserveNewest
31 |
32 |
33 | PreserveNewest
34 |
35 |
36 | PreserveNewest
37 |
38 |
39 | PreserveNewest
40 |
41 |
42 | PreserveNewest
43 |
44 |
45 | PreserveNewest
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/Samples/MVSCameraDemo/HKSDK/Lib/libFormatConversion.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sinsegye-CSharp/AvaloniaSamples/57bdbf48c0eb86b05ae12e239984a27e9291b8c4/Samples/MVSCameraDemo/HKSDK/Lib/libFormatConversion.so
--------------------------------------------------------------------------------
/Samples/MVSCameraDemo/HKSDK/Lib/libMVGigEVisionSDK.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sinsegye-CSharp/AvaloniaSamples/57bdbf48c0eb86b05ae12e239984a27e9291b8c4/Samples/MVSCameraDemo/HKSDK/Lib/libMVGigEVisionSDK.so
--------------------------------------------------------------------------------
/Samples/MVSCameraDemo/HKSDK/Lib/libMVRender.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sinsegye-CSharp/AvaloniaSamples/57bdbf48c0eb86b05ae12e239984a27e9291b8c4/Samples/MVSCameraDemo/HKSDK/Lib/libMVRender.so
--------------------------------------------------------------------------------
/Samples/MVSCameraDemo/HKSDK/Lib/libMediaProcess.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sinsegye-CSharp/AvaloniaSamples/57bdbf48c0eb86b05ae12e239984a27e9291b8c4/Samples/MVSCameraDemo/HKSDK/Lib/libMediaProcess.so
--------------------------------------------------------------------------------
/Samples/MVSCameraDemo/HKSDK/Lib/libMvCameraControl.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sinsegye-CSharp/AvaloniaSamples/57bdbf48c0eb86b05ae12e239984a27e9291b8c4/Samples/MVSCameraDemo/HKSDK/Lib/libMvCameraControl.so
--------------------------------------------------------------------------------
/Samples/MVSCameraDemo/HKSDK/Lib/libMvCameraControlWrapper.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sinsegye-CSharp/AvaloniaSamples/57bdbf48c0eb86b05ae12e239984a27e9291b8c4/Samples/MVSCameraDemo/HKSDK/Lib/libMvCameraControlWrapper.so
--------------------------------------------------------------------------------
/Samples/MVSCameraDemo/HKSDK/Lib/libMvUsb3vTL.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sinsegye-CSharp/AvaloniaSamples/57bdbf48c0eb86b05ae12e239984a27e9291b8c4/Samples/MVSCameraDemo/HKSDK/Lib/libMvUsb3vTL.so
--------------------------------------------------------------------------------
/Samples/MVSCameraDemo/HKSDK/Models/DeviceInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.InteropServices;
2 |
3 | namespace HKSDK.Models;
4 |
5 | ///
6 | /// 设备信息
7 | ///
8 | [StructLayout(LayoutKind.Sequential)]
9 | public struct DeviceInfo
10 | {
11 | ///
12 | /// 设备主版本
13 | ///
14 | public ushort MajorVer;
15 |
16 | ///
17 | /// 设备次版本
18 | ///
19 | public ushort MinorVer;
20 |
21 | ///
22 | /// 高MAC地址
23 | ///
24 | public uint MacAddrHigh;
25 |
26 | ///
27 | /// 低MAC地址
28 | ///
29 | public uint MacAddrLow;
30 |
31 | ///
32 | /// 设备传输的协议层类型
33 | ///
34 | public uint LayerType;
35 |
36 | ///
37 | /// 预留字段
38 | ///
39 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
40 | public uint[] Reserved;
41 |
42 | ///
43 | /// Gige设备信息
44 | ///
45 | public GigeDeviceInfo GigEInfo;
46 | }
--------------------------------------------------------------------------------
/Samples/MVSCameraDemo/HKSDK/Models/DeviceInfos.cs:
--------------------------------------------------------------------------------
1 | namespace HKSDK.Models;
2 |
3 | ///
4 | /// 设备信息列表
5 | ///
6 | [StructLayout(LayoutKind.Sequential)]
7 | public struct DeviceInfoList
8 | {
9 | ///
10 | /// 设备数量
11 | ///
12 | public uint DeviceNumber;
13 |
14 | ///
15 | /// 设备信息数组(最大256个设备)
16 | ///
17 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)]
18 | public IntPtr[] DeviceInfos;
19 | }
--------------------------------------------------------------------------------
/Samples/MVSCameraDemo/HKSDK/Models/FloatSettingModel.cs:
--------------------------------------------------------------------------------
1 | namespace HKSDK.Models;
2 |
3 | public struct FloatSettingModel
4 | {
5 | public float CurrentValue;
6 |
7 | public float MaxValue;
8 |
9 | public float MinValue;
10 | }
--------------------------------------------------------------------------------
/Samples/MVSCameraDemo/HKSDK/Models/FrameOutInfo.cs:
--------------------------------------------------------------------------------
1 | using HKSDK.Enums;
2 |
3 | namespace HKSDK.Models;
4 |
5 | ///
6 | /// 输出的帧信息
7 | ///
8 | public struct FrameOutInfo
9 | {
10 | ///
11 | /// 图像宽
12 | ///
13 | public ushort Width;
14 |
15 | ///
16 | /// 图像高
17 | ///
18 | public ushort Height;
19 |
20 | ///
21 | /// 像素格式
22 | ///
23 | public PixelFormatType PixelFormatType;
24 |
25 | ///
26 | /// 帧号
27 | ///
28 | public uint FrameNumber;
29 |
30 | ///
31 | /// 时间戳高32位
32 | ///
33 | public uint DevTimeStampHeight;
34 |
35 | ///
36 | /// 时间戳低32位
37 | ///
38 | public uint DevTimeStampLow;
39 |
40 | ///
41 | /// 保留,8字节对齐
42 | ///
43 | public uint Reserved;
44 |
45 | ///
46 | /// 主机生成的时间戳
47 | ///
48 | public long HostTimeStamp;
49 |
50 | ///
51 | /// 帧长度
52 | ///
53 | public uint FrameLength;
54 |
55 | ///
56 | /// 设备水印时标
57 | ///
58 | public uint SecondCount;
59 |
60 | ///
61 | /// 周期数
62 | ///
63 | public uint CycleCount;
64 |
65 | ///
66 | /// 周期偏移量
67 | ///
68 | public uint CycleOffset;
69 |
70 | ///
71 | /// 增益
72 | ///
73 | public float Gain;
74 |
75 | ///
76 | /// 曝光时间
77 | ///
78 | public float ExposureTime;
79 |
80 | ///
81 | /// 平均亮度
82 | ///
83 | public uint AverageBrightness;
84 |
85 | ///
86 | /// 白平衡相关
87 | ///
88 | public uint Red;
89 |
90 | ///
91 | /// 绿色
92 | ///
93 | public uint Green;
94 |
95 | ///
96 | /// 蓝色
97 | ///
98 | public uint Blue;
99 |
100 | ///
101 | /// 总帧数
102 | ///
103 | public uint FrameCounter;
104 |
105 | ///
106 | /// 触发计数
107 | ///
108 | public uint TriggerIndex;
109 |
110 | ///
111 | /// 输入
112 | ///
113 | public uint Input;
114 |
115 | ///
116 | /// 输出
117 | ///
118 | public uint Output;
119 |
120 | ///
121 | /// ROI区域
122 | ///
123 | public ushort OffsetX;
124 |
125 | ///
126 | /// 垂直偏移量
127 | ///
128 | public ushort OffsetY;
129 |
130 | ///
131 | /// Chunk宽
132 | ///
133 | public ushort ChunkWidth;
134 |
135 | ///
136 | /// Chunk高
137 | ///
138 | public ushort ChunkHeight;
139 |
140 | ///
141 | /// 本帧丢包数
142 | ///
143 | public uint LostPacket;
144 |
145 | ///
146 | /// 未解析的ChunkData个数
147 | ///
148 | public uint UnparsedChunkNumber;
149 | }
--------------------------------------------------------------------------------
/Samples/MVSCameraDemo/HKSDK/Models/GigeDeviceInfo.cs:
--------------------------------------------------------------------------------
1 | namespace HKSDK.Models;
2 |
3 | ///
4 | /// GIGE设备信息
5 | ///
6 | [StructLayout(LayoutKind.Sequential)]
7 | public struct GigeDeviceInfo
8 | {
9 | ///
10 | /// IP配置选项
11 | ///
12 | public uint CfgOption;
13 |
14 | ///
15 | /// 当前IP配置
16 | ///
17 | public uint IpCfgCurrent;
18 |
19 | ///
20 | /// 当前IP地址
21 | ///
22 | public uint CurrentIp;
23 |
24 | ///
25 | /// 当前子网掩码
26 | ///
27 | public uint CurrentSubNetMask;
28 |
29 | ///
30 | /// 当前网关
31 | ///
32 | public uint DefaultGateWay;
33 |
34 | ///
35 | /// 制造商名称
36 | ///
37 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
38 | public byte[] ManufacturerName;
39 |
40 | ///
41 | /// 设备型号
42 | ///
43 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
44 | public byte[] ModelName;
45 |
46 | ///
47 | /// 设备版本
48 | ///
49 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
50 | public byte[] DeviceVersion;
51 |
52 | ///
53 | /// 制造商的具体信息
54 | ///
55 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 48)]
56 | public byte[] ManufacturerSpecificInfo;
57 |
58 | ///
59 | /// 序列号
60 | ///
61 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
62 | public byte[] SerialNumber;
63 |
64 | ///
65 | /// 用户自定义名称
66 | ///
67 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
68 | public byte[] UserDefinedName;
69 |
70 | ///
71 | /// 网口IP地址
72 | ///
73 | public uint NetExport;
74 |
75 | ///
76 | /// 预留字段
77 | ///
78 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
79 | public uint[] Reserved;
80 | }
--------------------------------------------------------------------------------
/Samples/MVSCameraDemo/HKSDK/Models/SaveImageParams.cs:
--------------------------------------------------------------------------------
1 | using HKSDK.Enums;
2 |
3 | namespace HKSDK.Models;
4 |
5 | ///
6 | /// 保存图像参数
7 | ///
8 | [StructLayout(LayoutKind.Sequential)]
9 | public struct SaveImageParams
10 | {
11 | ///
12 | /// 指向图像数据的指针,类型为char
13 | ///
14 | public IntPtr ImageData;
15 |
16 | ///
17 | /// 数据长度
18 | ///
19 | public uint DataLength;
20 |
21 | ///
22 | /// 输入数据的像素格式
23 | ///
24 | public PixelFormatType PixelFormatType;
25 |
26 | ///
27 | /// 图像宽
28 | ///
29 | public ushort Width;
30 |
31 | ///
32 | /// 图像高
33 | ///
34 | public ushort Height;
35 |
36 | ///
37 | /// 输出图片缓存的指针,unsigned char类型
38 | ///
39 | public IntPtr ImageBuffer;
40 |
41 | ///
42 | /// 输出图片的大小
43 | ///
44 | public uint ImageLength;
45 |
46 | ///
47 | /// 提供的输出缓冲区大小
48 | ///
49 | public uint BufferSize;
50 |
51 | ///
52 | /// 输出的图片类型
53 | ///
54 | public ImageType ImageType;
55 |
56 | ///
57 | /// JPG编码质量(50-99),其他格式无效
58 | ///
59 | public uint JpgQuality;
60 |
61 | ///
62 | /// 插值方法
63 | ///
64 | public uint MethodValue;
65 |
66 | ///
67 | /// 预留字段
68 | ///
69 | [MarshalAs(UnmanagedType.ByValArray,SizeConst = 3)]
70 | public uint[] Reserved;
71 | }
--------------------------------------------------------------------------------
/Samples/MVSCameraDemo/Readme.md:
--------------------------------------------------------------------------------
1 | ## MVSCameraDemo项目简介
2 |
3 | 此Demo是连接海康工业相机MVS的演示Demo,开发依赖为.net 6,运行环境为Linux 64位,引用了海康MVS的Linux SDK(C语言版)。
4 |
5 | 运行此项目还需要安装特殊依赖:`sudo apt install libgdiplus`,此库是.net进行Bitmap保存为图片的必须库,否则保存图片会失败。
6 |
7 | 注意:此项目中有图片保存功能,代码中默认位置为桌面,如自己修改保存位置,需特别注意权限问题,Linux权限较严,很可能保存失败。
8 |
9 | ### 项目目录介绍
10 |
11 | * HKSDK:引用的MVS的Linux SDK,转为C#的相应方法
12 | * CameraDemo:使用Avalonia UI进行的桌面程序开发,引用了HKSDK项目,具有扫描网络中相机、相机视频播放、相机图片保存以及帧率设置等功能。
13 |
14 | ### 特别注意
15 |
16 | 虽然HKSDK中,添加了对MVS的Linux SDK的直接引用(Lib文件夹下),但某些Linux分发版中,扔无法加载相应依赖,需要启动的时候手动进行环境变量的添加:`LD_LIBRARY_PATH=/Lib`,其中键不可更改,值修改为自己对应目录
17 |
--------------------------------------------------------------------------------
/Samples/OpcUADemo/OpcUAClient.Repository/IRepository.cs:
--------------------------------------------------------------------------------
1 | using OpcUAClient.Repository.Models;
2 |
3 | namespace OpcUAClient.Repository;
4 |
5 | public interface IRepository
6 | {
7 | ///
8 | /// 连接字符串
9 | ///
10 | public string ConnectionString { get; set; }
11 |
12 | ///
13 | /// 添加一条数据
14 | ///
15 | /// 要添加的数据
16 | Task AddData(HistoryDataModel historyData);
17 |
18 | ///
19 | /// 使用指定的数据源
20 | ///
21 | /// 数据源的连接字符串
22 | /// 数据库实体
23 | IRepository UseSource(string connectionString);
24 | }
--------------------------------------------------------------------------------
/Samples/OpcUADemo/OpcUAClient.Repository/Models/HistoryDataModel.cs:
--------------------------------------------------------------------------------
1 | namespace OpcUAClient.Repository.Models;
2 |
3 | [Dapper.Contrib.Extensions.Table("history_data")]
4 | public class HistoryDataModel
5 | {
6 | ///
7 | /// 节点ID
8 | ///
9 | public string NodeId { get; set; } = string.Empty;
10 |
11 | ///
12 | /// 节点名称
13 | ///
14 | public string NodeName { get; set; } = string.Empty;
15 |
16 | ///
17 | /// 节点的值
18 | ///
19 | public float NodeValue { get; set; }
20 |
21 | ///
22 | /// 记录时间
23 | ///
24 | public DateTime RecordTime { get; set; }
25 | }
--------------------------------------------------------------------------------
/Samples/OpcUADemo/OpcUAClient.Repository/OpcUAClient.Repository.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 | enable
6 | enable
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/Samples/OpcUADemo/OpcUAClient.Repository/Services/DapperRepository.cs:
--------------------------------------------------------------------------------
1 | using Dapper.Contrib.Extensions;
2 | using MySqlConnector;
3 | using OpcUAClient.Repository.Models;
4 |
5 | namespace OpcUAClient.Repository.Services;
6 |
7 | public class DapperRepository : IRepository
8 | {
9 | public string ConnectionString { get; set; } = string.Empty;
10 |
11 | public async Task AddData(HistoryDataModel historyData)
12 | {
13 | await using var connection = new MySqlConnection(ConnectionString);
14 | await connection.OpenAsync();
15 |
16 | await connection.InsertAsync(historyData);
17 | }
18 |
19 | public IRepository UseSource(string connectionString)
20 | {
21 | this.ConnectionString = connectionString;
22 | return this;
23 | }
24 | }
--------------------------------------------------------------------------------
/Samples/OpcUADemo/OpcUAClient/App.axaml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/Samples/OpcUADemo/OpcUAClient/App.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Controls.ApplicationLifetimes;
3 | using Avalonia.Markup.Xaml;
4 | using Avalonia.Platform;
5 | using OpcUAClient.Repository;
6 | using OpcUAClient.Repository.Services;
7 | using OpcUAClient.ViewModels;
8 | using OpcUAClient.Views;
9 |
10 | namespace OpcUAClient;
11 |
12 | public partial class App : Application
13 | {
14 | public override void Initialize()
15 | {
16 | AvaloniaXamlLoader.Load(this);
17 | }
18 |
19 | public override void OnFrameworkInitializationCompleted()
20 | {
21 | if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
22 | {
23 | desktop.MainWindow = new MainWindow
24 | {
25 | DataContext = new MainWindowViewModel(),
26 | };
27 | }
28 |
29 | base.OnFrameworkInitializationCompleted();
30 | }
31 |
32 | public override void RegisterServices()
33 | {
34 | AvaloniaLocator.CurrentMutable.Bind().ToConstant(new FontManager());
35 | AvaloniaLocator.CurrentMutable.Bind().ToTransient();
36 | base.RegisterServices();
37 | }
38 | }
--------------------------------------------------------------------------------
/Samples/OpcUADemo/OpcUAClient/Assets/avalonia-logo.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sinsegye-CSharp/AvaloniaSamples/57bdbf48c0eb86b05ae12e239984a27e9291b8c4/Samples/OpcUADemo/OpcUAClient/Assets/avalonia-logo.ico
--------------------------------------------------------------------------------
/Samples/OpcUADemo/OpcUAClient/Assets/msyh.ttc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sinsegye-CSharp/AvaloniaSamples/57bdbf48c0eb86b05ae12e239984a27e9291b8c4/Samples/OpcUADemo/OpcUAClient/Assets/msyh.ttc
--------------------------------------------------------------------------------
/Samples/OpcUADemo/OpcUAClient/FodyWeavers.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/Samples/OpcUADemo/OpcUAClient/FodyWeavers.xsd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.
12 |
13 |
14 |
15 |
16 | A comma-separated list of error codes that can be safely ignored in assembly verification.
17 |
18 |
19 |
20 |
21 | 'false' to turn off automatic generation of the XML Schema file.
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/Samples/OpcUADemo/OpcUAClient/FontManager.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Globalization;
3 | using System.Linq;
4 | using Avalonia.Media;
5 | using Avalonia.Platform;
6 | using Avalonia.Skia;
7 | using SkiaSharp;
8 |
9 | namespace OpcUAClient;
10 |
11 | public class FontManager : IFontManagerImpl
12 | {
13 | private readonly Typeface[] _customTypefaces;
14 | private readonly string _defaultFamilyName;
15 |
16 | private readonly Typeface _defaultTypeface = new("avares://CameraDemo/Assets/msyh.ttc#微软雅黑");
17 |
18 | private readonly string[] _bcp47 =
19 | { CultureInfo.CurrentCulture.ThreeLetterISOLanguageName, CultureInfo.CurrentCulture.TwoLetterISOLanguageName };
20 |
21 | public FontManager()
22 | {
23 | _customTypefaces = new[] { _defaultTypeface };
24 | _defaultFamilyName = _defaultTypeface.FontFamily.FamilyNames.PrimaryFamilyName;
25 | }
26 |
27 | public string GetDefaultFontFamilyName() => _defaultFamilyName;
28 |
29 | public IEnumerable GetInstalledFontFamilyNames(bool checkForUpdates = false) =>
30 | _customTypefaces.Select(x => x.FontFamily.Name);
31 |
32 | public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, FontFamily fontFamily,
33 | CultureInfo culture, out Typeface typeface)
34 | {
35 | foreach (var customTypeface in _customTypefaces)
36 | {
37 | if (customTypeface.GlyphTypeface.GetGlyph((uint)codepoint) == 0)
38 | {
39 | continue;
40 | }
41 |
42 | typeface = new Typeface(customTypeface.FontFamily.Name, fontStyle, fontWeight);
43 |
44 | return true;
45 | }
46 |
47 | var fallback = SKFontManager.Default.MatchCharacter(fontFamily?.Name, (SKFontStyleWeight)fontWeight,
48 | SKFontStyleWidth.Normal, (SKFontStyleSlant)fontStyle, _bcp47, codepoint);
49 |
50 | typeface = new Typeface(fallback?.FamilyName ?? _defaultFamilyName, fontStyle, fontWeight);
51 |
52 | return true;
53 | }
54 |
55 | public IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface)
56 | {
57 | var skTypeface = typeface.FontFamily.Name switch
58 | {
59 | FontFamily.DefaultFontFamilyName => SKTypeface.FromFamilyName(_defaultTypeface.FontFamily.Name),
60 | "微软雅黑" => SKTypeface.FromFamilyName(_defaultTypeface.FontFamily.Name),
61 | _ => SKTypeface.FromFamilyName(_defaultTypeface.FontFamily.Name)
62 | };
63 |
64 | return new GlyphTypefaceImpl(skTypeface);
65 | }
66 | }
--------------------------------------------------------------------------------
/Samples/OpcUADemo/OpcUAClient/Models/DataRecordSettingModel.cs:
--------------------------------------------------------------------------------
1 | namespace OpcUAClient.Models;
2 |
3 | ///
4 | /// 数据记录设置Model
5 | ///
6 | public class DataRecordSettingModel
7 | {
8 | ///
9 | /// 数据采集周期(单位:s)
10 | ///
11 | public int AcquisitionCycle { get; set; } = 1;
12 |
13 | ///
14 | /// 是否存储到数据库
15 | ///
16 | public bool IsSaveToDatabase { get; set; }
17 |
18 | ///
19 | /// 数据库地址
20 | ///
21 | public string DatabasePath { get; set; } = string.Empty;
22 |
23 | ///
24 | /// 数据库名称
25 | ///
26 | public string DatabaseName { get; set; } = string.Empty;
27 |
28 | ///
29 | /// 用户名
30 | ///
31 | public string User { get; set; } = string.Empty;
32 |
33 | ///
34 | /// 密码
35 | ///
36 | public string Password { get; set; } = string.Empty;
37 |
38 | ///
39 | /// 数据库端口
40 | ///
41 | public int Port { get; set; } = 3306;
42 |
43 | ///
44 | /// 数据库连接字符串
45 | ///
46 | public string DatabaseConnectionString =>
47 | $"server={this.DatabasePath};port={this.Port};uid={this.User};password={this.Password}; database={DatabaseName};sslMode=none;CharSet=utf8;";
48 | }
--------------------------------------------------------------------------------
/Samples/OpcUADemo/OpcUAClient/Models/NodeInfo.cs:
--------------------------------------------------------------------------------
1 | namespace OpcUAClient.Models;
2 |
3 | public class NodeInfo
4 | {
5 | ///
6 | /// 节点Id
7 | ///
8 | public string NodeId { get; set; } = string.Empty;
9 |
10 | ///
11 | /// 节点名称
12 | ///
13 | public string Name { get; set; } = string.Empty;
14 |
15 | ///
16 | /// 节点的值
17 | ///
18 | public string Value { get; set; } = string.Empty;
19 |
20 | ///
21 | /// 节点的类型
22 | ///
23 | public string Type { get; set; } = string.Empty;
24 |
25 | ///
26 | /// 节点描述
27 | ///
28 | public string Description { get; set; } = string.Empty;
29 |
30 | ///
31 | /// 克隆
32 | ///
33 | public NodeInfo Clone()
34 | {
35 | return new NodeInfo
36 | {
37 | NodeId = this.NodeId,
38 | Type = this.Type,
39 | Name = this.Name,
40 | Description = this.Description,
41 | Value = this.Value
42 | };
43 | }
44 | }
--------------------------------------------------------------------------------
/Samples/OpcUADemo/OpcUAClient/Models/NodeListenModel.cs:
--------------------------------------------------------------------------------
1 | using Opc.Ua;
2 | using ReactiveUI;
3 | using ReactiveUI.Fody.Helpers;
4 |
5 | namespace OpcUAClient.Models;
6 |
7 | ///
8 | /// 节点监听Model
9 | ///
10 | public class NodeListenModel: ReactiveObject
11 | {
12 | ///
13 | /// 节点名称
14 | ///
15 | public string Name { get; set; } = "";
16 |
17 | ///
18 | /// 节点Id
19 | ///
20 | public string NodeId { get; set; } = "";
21 |
22 | ///
23 | /// 节点的值,以字符串形式标识
24 | ///
25 | [Reactive]
26 | public string Value { get; set; } = "";
27 |
28 | ///
29 | /// 节点在Opc Ua中的类型
30 | ///
31 | public BuiltInType BuiltInType { get; set; }
32 | }
--------------------------------------------------------------------------------
/Samples/OpcUADemo/OpcUAClient/Models/NodeModel.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.ObjectModel;
2 |
3 | namespace OpcUAClient.Models;
4 |
5 | ///
6 | /// 节点Model
7 | ///
8 | public class NodeModel
9 | {
10 | ///
11 | /// 节点名称
12 | ///
13 | public string Name { get; set; } = "";
14 |
15 | ///
16 | /// 节点Id
17 | ///
18 | public string NodeId { get; set; } = "";
19 |
20 | ///
21 | /// 是否含有子节点
22 | ///
23 | public bool HasChild { get; set; }
24 |
25 | ///
26 | /// 子节点集合
27 | ///
28 | public ObservableCollection Child { get; set; } = new();
29 | }
--------------------------------------------------------------------------------
/Samples/OpcUADemo/OpcUAClient/OPC/pki/own/certs/OpcUa Client [49AC5D9DC29D3C0AC0885E6B77901913BDB340CF].der:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sinsegye-CSharp/AvaloniaSamples/57bdbf48c0eb86b05ae12e239984a27e9291b8c4/Samples/OpcUADemo/OpcUAClient/OPC/pki/own/certs/OpcUa Client [49AC5D9DC29D3C0AC0885E6B77901913BDB340CF].der
--------------------------------------------------------------------------------
/Samples/OpcUADemo/OpcUAClient/OPC/pki/own/private/OpcUa Client [49AC5D9DC29D3C0AC0885E6B77901913BDB340CF].pfx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sinsegye-CSharp/AvaloniaSamples/57bdbf48c0eb86b05ae12e239984a27e9291b8c4/Samples/OpcUADemo/OpcUAClient/OPC/pki/own/private/OpcUa Client [49AC5D9DC29D3C0AC0885E6B77901913BDB340CF].pfx
--------------------------------------------------------------------------------
/Samples/OpcUADemo/OpcUAClient/OPC/pki/trusted/certs/OPCUAServer@sii-ret [B2B70C26F4DEB5D7A4817E73F56DFC8D38EC107D].der:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sinsegye-CSharp/AvaloniaSamples/57bdbf48c0eb86b05ae12e239984a27e9291b8c4/Samples/OpcUADemo/OpcUAClient/OPC/pki/trusted/certs/OPCUAServer@sii-ret [B2B70C26F4DEB5D7A4817E73F56DFC8D38EC107D].der
--------------------------------------------------------------------------------
/Samples/OpcUADemo/OpcUAClient/OpcUAClient.Config.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | OpcUa Client
6 | urn:localhost:UA:OpcUADemo:OpcUAClient
7 | uri:opcfoundation.org:OpcUADemo:OpcUAClient
8 | Client_1
9 |
10 |
11 |
12 |
13 |
14 | Directory
15 | OPC/pki/own
16 | CN=OpcUa Client, C=US, S=Arizona, O=ZKSD, DC=localhost
17 |
18 |
19 |
20 |
21 | Directory
22 | OPC/pki/issuer
23 |
24 |
25 |
26 |
27 | Directory
28 | OPC/pki/trusted
29 |
30 |
31 |
32 |
33 | Directory
34 | OPC/pki/rejected
35 |
36 |
37 |
39 | false
40 |
41 |
43 | true
44 | true
45 | 2048
46 | false
47 | true
48 |
49 |
50 |
51 | Directory
52 | OPC/pki/trustedUser
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | 120000
61 | 4194304
62 | 4194304
63 | 65535
64 | 4194304
65 | 65535
66 | 300000
67 | 3600000
68 |
69 |
70 |
71 | 60000
72 |
73 | opc.tcp://{0}:4840
74 | http://{0}:52601/UADiscovery
75 | http://{0}/UADiscovery/Default.svc
76 |
77 |
78 | 10000
79 |
80 |
81 | 2500
82 | 1000
83 | 1000
84 | 2500
85 | 1000
86 | 1000
87 | 2500
88 | 2500
89 | 2500
90 | 2500
91 | 2500
92 | 2500
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 | OPC/Logs/demo.log.txt
102 | true
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
--------------------------------------------------------------------------------
/Samples/OpcUADemo/OpcUAClient/OpcUAClient.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | WinExe
4 | net6.0
5 | enable
6 | true
7 | app.manifest
8 | true
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 | Always
38 |
39 |
40 | PreserveNewest
41 |
42 |
43 | Always
44 |
45 |
46 | Always
47 |
48 |
49 | Always
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/Samples/OpcUADemo/OpcUAClient/Program.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.ReactiveUI;
3 | using System;
4 |
5 | namespace OpcUAClient;
6 |
7 | class Program
8 | {
9 | // Initialization code. Don't use any Avalonia, third-party APIs or any
10 | // SynchronizationContext-reliant code before AppMain is called: things aren't initialized
11 | // yet and stuff might break.
12 | [STAThread]
13 | public static void Main(string[] args) => BuildAvaloniaApp()
14 | .StartWithClassicDesktopLifetime(args);
15 |
16 | // Avalonia configuration, don't remove; also used by visual designer.
17 | public static AppBuilder BuildAvaloniaApp()
18 | => AppBuilder.Configure()
19 | .UsePlatformDetect()
20 | .LogToTrace()
21 | .UseReactiveUI();
22 | }
--------------------------------------------------------------------------------
/Samples/OpcUADemo/OpcUAClient/Readme.md:
--------------------------------------------------------------------------------
1 | ## OpcUADemo项目简介
2 |
3 | 此Demo是一个OPC UA客户端Demo,使用需要配合已有的OPC UA服务器,此Demo具有以下功能:
4 |
5 | * 服务器变量浏览
6 | * 服务器变量具体数值读取
7 | * 监测服务器数值型数据,并进行曲线绘制
8 | * 监听特殊变量,并提供修改功能。
9 |
10 | ### Demo使用说明
11 |
12 | ##### 1、与服务器连接
13 |
14 | 使用此Demo首先需要连接OPC UA服务器,服务器登录方式为用户名、密码方式,在软件指定区域内输入服务器地址、用户名、密码,点击连接按钮既可登录
15 |
16 | 软件登录后,会自动加载服务端中的变量,并以树形图展示出来,如果点击登录后,左侧没有出现变量树,则登录出现问题。
17 |
18 | 注意:变量树采用了分级加载方式,点击每级展开按钮时,会加载子节点,如没有展开按钮,则代表此节点没有子节点,不可展开
19 |
20 | ##### 2、监听变量并进行曲线绘制
21 |
22 | 连接服务端成功,并加载出变量树后,可以对``数值``型的变量进行订阅,订阅后的数值,会显示在右侧``曲线监听值列表``中,展示形式为``变量名称(变量的节点ID)``,将需要监听的值都添加到列表后,点击``开始监听``按钮,既可绘制曲线,具体操作步骤如下:
23 | 1. 输入服务器地址、用户名以及密码,点击``连接``按钮
24 | 2. 在左侧变量树中,选取需要绘制的变量,选中并点击右键,在右键菜单中点击``添加订阅``按钮
25 | 3. 在右侧``曲线监听值列表``核对需要绘制的值是否正确
26 | 4. 点击``开始监听``按钮,曲线开始绘制
27 |
28 | ##### 3、监听特殊变量并进行值修改
29 |
30 | 连接服务端成功,并加载出变量树后,可以对变量(不包括数组)进行重点监听,订阅后的变量,会显示在右侧``重点值监视区``中,并提供修改数值的输入框和按钮,点击``开始监听``按钮,既可实时读取相应的值,并进行修改,具体操作步骤如下:
31 | 1. 输入服务器地址、用户名以及密码,点击``连接``按钮
32 | 2. 在左侧变量树中,选取需要绘制的变量,选中并点击右键,在右键菜单中点击``添加到重点监听``按钮
33 | 3. 在右侧``重点值监视区``核对需要绘制的值是否正确
34 | 4. 点击``开始监听``按钮,进行数据监听
35 |
36 | ##### 4、依赖说明
37 |
38 | 此Demo使用了一些第三方组件,可根据需求选用:
39 |
40 | * ``LiveChartsCore.SkiaSharpView.Avalonia``:用于曲线图的绘制
41 | * ``OPCFoundation.NetStandard.Opc.Ua.Client``:OPC协会发布的.net使用OPC UA协议的包
42 | * ``ReactiveUI.Fody``:ReactiveUI的功能扩展包,用于快速编写ViewModel中的属性
43 |
44 | ### Q&A
45 |
46 | 1、为什么变量树右键菜单中,添加订阅按钮是灰的?
47 |
48 |
49 | 订阅绘制曲线只可以订阅数值型变量,如果所选变量树节点有子节点或者是对象、字符串以及数组时,不可进行订阅
50 |
51 |
52 | 2、为什么变量树右键菜单中,添加到重点监听是灰的?
53 |
54 |
55 | 订阅绘制曲线只可以订阅变量,如果所选变量树节点有子节点或者是对象、数组时,不可进行监听
56 |
--------------------------------------------------------------------------------
/Samples/OpcUADemo/OpcUAClient/UAClient.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics;
4 | using System.Linq;
5 | using System.Threading;
6 | using System.Threading.Tasks;
7 | using DynamicData;
8 | using Opc.Ua;
9 | using Opc.Ua.Client;
10 | using OpcUAClient.Models;
11 |
12 | namespace OpcUAClient;
13 |
14 | public class UaClient : IDisposable
15 | {
16 | #region Private Fields
17 |
18 | // OPC UA配置
19 | private readonly ApplicationConfiguration _configuration;
20 |
21 | private readonly IDictionary _subscriptionsDictionary;
22 |
23 | #endregion
24 |
25 | #region Public Properties
26 |
27 | ///
28 | /// OPC UA会话
29 | ///
30 | public ISession? Session { get; set; }
31 |
32 | ///
33 | /// The session keepalive interval to be used in ms.
34 | ///
35 | public int KeepAliveInterval { get; set; } = 5000;
36 |
37 | ///
38 | /// 和Server连接Session的LifeTime
39 | ///
40 | public uint SessionLifeTime { get; set; } = 30 * 1000;
41 |
42 | ///
43 | /// 用户连接Server的凭据
44 | ///
45 | public IUserIdentity UserIdentity { get; set; }
46 |
47 | ///
48 | /// 请求时每个节点下最大的子节点数量
49 | ///
50 | public int RequestMaxNodeCountPerNode { get; set; } = 200;
51 |
52 | #endregion
53 |
54 | #region Constructors
55 |
56 | ///
57 | /// 初始化一个OPC UA客户端
58 | ///
59 | /// OPC UA客户端的配置
60 | /// 连接服务端的凭据
61 | public UaClient(ApplicationConfiguration configuration, UserIdentity? userIdentity)
62 | {
63 | _configuration = configuration;
64 | UserIdentity = userIdentity ?? new UserIdentity();
65 | _subscriptionsDictionary = new Dictionary();
66 | }
67 |
68 | #endregion
69 |
70 | #region Connect Disconnect
71 |
72 | ///
73 | /// 连接OPC UA Server
74 | ///
75 | /// 服务器地址
76 | /// 是否使用安全登录
77 | /// 登录是成功
78 | /// 参数为NUll错误,指服务器地址为空
79 | public async Task ConnectAsync(string serverUrl, bool useSecurity = true)
80 | {
81 | if (string.IsNullOrWhiteSpace(serverUrl)) throw new ArgumentNullException(nameof(serverUrl));
82 |
83 | try
84 | {
85 | if (Session is { Connected: true })
86 | {
87 | Debug.WriteLine("Session already connected!");
88 | return false;
89 | }
90 |
91 | Debug.WriteLine($"Connecting to... {serverUrl}");
92 |
93 | var endpointDescription = CoreClientUtils.SelectEndpoint(_configuration, serverUrl, useSecurity);
94 | var endpointConfiguration = EndpointConfiguration.Create(_configuration);
95 | var endpoint = new ConfiguredEndpoint(null, endpointDescription, endpointConfiguration);
96 |
97 | var session = await Opc.Ua.Client.Session.Create(_configuration, endpoint, false, false,
98 | _configuration.ApplicationName, SessionLifeTime, UserIdentity, null).ConfigureAwait(false);
99 |
100 | if (session is not { Connected: true }) return false;
101 |
102 | Session = session;
103 |
104 | Session.KeepAliveInterval = KeepAliveInterval;
105 |
106 | Debug.WriteLine($"New Session Created with SessionName = {Session.SessionName}");
107 |
108 | return true;
109 | }
110 | catch (Exception ex)
111 | {
112 | Debug.WriteLine($"Create Session Error : {ex.Message}");
113 | throw;
114 | }
115 | }
116 |
117 | ///
118 | /// 断开连接
119 | ///
120 | public async Task DisconnectAsync()
121 | {
122 | if (Session == null) return true;
123 |
124 | Debug.WriteLine("Disconnecting...");
125 |
126 | await Session.RemoveSubscriptionsAsync(_subscriptionsDictionary.Values);
127 | await Session.CloseAsync();
128 | Session.Dispose();
129 | Session = null;
130 |
131 | Debug.WriteLine("Session Disconnected.");
132 |
133 | return true;
134 | }
135 |
136 | public void Dispose()
137 | {
138 | Utils.SilentDispose(Session);
139 | }
140 |
141 | #endregion
142 |
143 | #region Get Node Info
144 |
145 | ///
146 | /// 获取指定节点下的子节点
147 | ///
148 | /// 指定的节点
149 | ///
150 | public IEnumerable GetChildNodes(NodeId? nodeId)
151 | {
152 | if (Session is null) throw new NullReferenceException("Session is Null");
153 |
154 | var referenceDescriptionCollection = GetReferenceDescriptionCollection(nodeId);
155 | var result = new List();
156 |
157 | foreach (var referenceDescription in referenceDescriptionCollection)
158 | {
159 | var treeNode = new NodeModel
160 | {
161 | Name = referenceDescription.DisplayName.Text,
162 | NodeId = referenceDescription.NodeId.Format(),
163 | HasChild = GetReferenceDescriptionCollection(referenceDescription.NodeId.Format()).Count > 0
164 | };
165 | result.Add(treeNode);
166 | }
167 |
168 | return result;
169 | }
170 |
171 | ///
172 | /// 获取节点某个属性的信息
173 | ///
174 | /// 节点的ID
175 | /// 属性ID,值参考Attributes枚举
176 | /// 获取的结果
177 | ///
178 | public DataValueCollection GetNodeAttributesInfo(NodeId nodeId, List attributeIds)
179 | {
180 | if (Session is null) throw new NullReferenceException("Session is Null");
181 |
182 | var nodesToRead = new ReadValueIdCollection();
183 | nodesToRead.AddRange(attributeIds.Select(attributeId =>
184 | new ReadValueId { NodeId = nodeId, AttributeId = attributeId }));
185 |
186 | // read all values.
187 | Session.Read(null, 0, TimestampsToReturn.Neither, nodesToRead, out var results,
188 | out var diagnosticInfos);
189 | ClientBase.ValidateResponse(results, nodesToRead);
190 | ClientBase.ValidateDiagnosticInfos(diagnosticInfos, nodesToRead);
191 |
192 | return results;
193 | }
194 |
195 | ///
196 | /// 获取节点某个属性的信息
197 | ///
198 | /// 节点的ID
199 | /// 属性ID,值参考Attributes枚举
200 | /// 获取的结果
201 | ///
202 | public async Task GetNodeAttributesInfoAsync(NodeId nodeId, uint attributeId)
203 | {
204 | if (Session is null) throw new NullReferenceException("Session is Null");
205 |
206 | var nodesToRead = new ReadValueIdCollection
207 | {
208 | new ReadValueId
209 | {
210 | NodeId = nodeId,
211 | AttributeId = attributeId
212 | }
213 | };
214 |
215 | // read all values.
216 | var response =
217 | await Session.ReadAsync(null, 0, TimestampsToReturn.Neither, nodesToRead, CancellationToken.None);
218 | ClientBase.ValidateResponse(response.Results, nodesToRead);
219 | ClientBase.ValidateDiagnosticInfos(response.DiagnosticInfos, nodesToRead);
220 |
221 | return response.Results.First();
222 | }
223 |
224 | ///
225 | /// 获取节点信息
226 | ///
227 | /// 节点ID
228 | /// 返回的结果
229 | public async Task GetNodeInfoAsync(NodeId nodeId)
230 | {
231 | var result = await GetNodesInfoAsync(new List { nodeId });
232 | return result.First();
233 | }
234 |
235 | ///
236 | /// 获取节点列表信息
237 | ///
238 | /// 节点列表
239 | /// 节点信息列表
240 | /// 输入参数为空
241 | public async Task> GetNodesInfoAsync(List nodeIds)
242 | {
243 | if (Session is null) throw new NullReferenceException("Session is Null");
244 |
245 | var nodesToRead = new ReadValueIdCollection();
246 |
247 | foreach (var nodeId in nodeIds)
248 | {
249 | nodesToRead.Add(new ReadValueId
250 | {
251 | NodeId = nodeId,
252 | AttributeId = Attributes.NodeClass
253 | });
254 | nodesToRead.Add(new ReadValueId
255 | {
256 | NodeId = nodeId,
257 | AttributeId = Attributes.Value
258 | });
259 | nodesToRead.Add(new ReadValueId
260 | {
261 | NodeId = nodeId,
262 | AttributeId = Attributes.DisplayName
263 | });
264 | nodesToRead.Add(new ReadValueId
265 | {
266 | NodeId = nodeId,
267 | AttributeId = Attributes.Description,
268 | });
269 | }
270 |
271 | // read all values.
272 | var response =
273 | await Session.ReadAsync(null, 0, TimestampsToReturn.Both, nodesToRead, CancellationToken.None);
274 | ClientBase.ValidateResponse(response.Results, nodesToRead);
275 | ClientBase.ValidateDiagnosticInfos(response.DiagnosticInfos, nodesToRead);
276 |
277 | // 对结果进行分割
278 | var infoArray = response.Results.Chunk(4).ToList();
279 |
280 | var result = new List();
281 |
282 | for (int i = 0; i < infoArray.Count; i++)
283 | {
284 | var info = infoArray[i];
285 | var nodeInfo = new NodeInfo
286 | {
287 | NodeId = nodeIds[i].Format()
288 | };
289 | if ((NodeClass)info[0].WrappedValue.Value == NodeClass.Variable)
290 | {
291 | if (info[1] is not null)
292 | {
293 | nodeInfo.Type = info[1]?.WrappedValue.TypeInfo.BuiltInType.ToString() ?? "";
294 | nodeInfo.Value = info[1]?.WrappedValue.Value?.ToString() ?? "";
295 | nodeInfo.Name = info[2]?.WrappedValue.Value?.ToString() ?? "";
296 | nodeInfo.Description = info[3]?.WrappedValue.Value?.ToString() ?? "";
297 | }
298 | }
299 | else if ((NodeClass)info[0].WrappedValue.Value == NodeClass.Object)
300 | {
301 | nodeInfo.Type = ((NodeClass)info[0].WrappedValue.Value).ToString();
302 | nodeInfo.Value = "";
303 | nodeInfo.Name = info[2].WrappedValue.Value.ToString() ?? "";
304 | nodeInfo.Description = info[3]?.WrappedValue.Value?.ToString() ?? "";
305 | }
306 |
307 | result.Add(nodeInfo);
308 | }
309 |
310 | return result;
311 | }
312 |
313 | #endregion
314 |
315 | #region Read Write
316 |
317 | ///
318 | /// 读取某个节点的值
319 | ///
320 | /// 节点的id
321 | /// 期望的节点的值类型
322 | /// 读取的值
323 | /// 参数为空
324 | public async Task ReadValueAsync(NodeId nodeId)
325 | {
326 | if (Session is null) throw new NullReferenceException("Session is Null");
327 |
328 | var result = await Session.ReadValueAsync(nodeId);
329 | if (result is null) return default;
330 |
331 | return (T)result.Value;
332 | }
333 |
334 | ///
335 | /// 读取一系列节点的值
336 | ///
337 | /// 节点集合
338 | /// 值的类型
339 | /// 值结果
340 | /// 参数为空
341 | public async IAsyncEnumerable ReadValuesAsync(List nodeIds)
342 | {
343 | if (Session is null) throw new NullReferenceException("Session is Null");
344 |
345 | var (dataValueCollection, _) = await Session.ReadValuesAsync(nodeIds);
346 |
347 | if (dataValueCollection is null) yield break;
348 |
349 | foreach (var dataValue in dataValueCollection.Where(dataValue => dataValue is not null))
350 | {
351 | yield return (T)dataValue.Value;
352 | }
353 | }
354 |
355 | ///
356 | /// 写入一个值
357 | ///
358 | /// 节点的ID
359 | /// 节点的值
360 | /// 值类型
361 | public async void WriteValue(NodeId nodeId, T value)
362 | {
363 | if (Session is null) throw new NullReferenceException("Session is Null");
364 |
365 | var valueToWrite = new WriteValue
366 | {
367 | NodeId = nodeId,
368 | AttributeId = Attributes.Value,
369 | Value =
370 | {
371 | Value = value,
372 | StatusCode = StatusCodes.Good,
373 | ServerTimestamp = DateTime.MinValue,
374 | SourceTimestamp = DateTime.MinValue
375 | }
376 | };
377 | var valuesToWrite = new WriteValueCollection
378 | {
379 | valueToWrite
380 | };
381 |
382 | var _ = await Session.WriteAsync(null, valuesToWrite, CancellationToken.None);
383 | }
384 |
385 | #endregion
386 |
387 | #region PubSub Subscribe
388 |
389 | ///
390 | /// 订阅数据改变
391 | ///
392 | /// 订阅的组ID
393 | /// 节点模型
394 | /// 数据改变回调
395 | /// 参数为空
396 | public void SubscribeToDataChanges(Guid key, IList nodeModels,
397 | Action changeCallBack)
398 | {
399 | if (Session is null) throw new NullReferenceException("Session is Null");
400 |
401 | var subscription = new Subscription(Session.DefaultSubscription)
402 | {
403 | DisplayName = key.ToString(),
404 | PublishingEnabled = true,
405 | PublishingInterval = 0,
406 | LifetimeCount = uint.MaxValue,
407 | MaxNotificationsPerPublish = uint.MaxValue,
408 | Priority = 100
409 | };
410 |
411 | Session.AddSubscription(subscription);
412 | subscription.Create();
413 |
414 | foreach (var nodeModel in nodeModels)
415 | {
416 | var item = new MonitoredItem(subscription.DefaultItem)
417 | {
418 | StartNodeId = nodeModel.NodeId,
419 | AttributeId = Attributes.Value,
420 | DisplayName = nodeModel.Name,
421 | SamplingInterval = 100,
422 | QueueSize = 10,
423 | DiscardOldest = true
424 | };
425 | item.Notification += changeCallBack.Invoke;
426 |
427 | subscription.AddItem(item);
428 | }
429 |
430 | subscription.ApplyChanges();
431 | _subscriptionsDictionary.Add(key, subscription);
432 | }
433 |
434 | #endregion
435 |
436 | #region Private Methods
437 |
438 | // 获取节点引用的描述
439 | private ReferenceDescriptionCollection GetReferenceDescriptionCollection(NodeId? sourceId)
440 | {
441 | var result = new ReferenceDescriptionCollection();
442 |
443 | if (Session is null) return result;
444 |
445 | var browseTemplate = new BrowseDescription
446 | {
447 | NodeId = sourceId ?? ObjectIds.ObjectsFolder,
448 | BrowseDirection = BrowseDirection.Forward,
449 | ReferenceTypeId = ReferenceTypeIds.HierarchicalReferences,
450 | IncludeSubtypes = true,
451 | NodeClassMask = (uint)NodeClass.Unspecified,
452 | ResultMask = (uint)BrowseResultMask.All
453 | };
454 | var browseDescriptionCollection = new BrowseDescriptionCollection { browseTemplate };
455 |
456 | Session.Browse(null, null, (uint)RequestMaxNodeCountPerNode, browseDescriptionCollection,
457 | out var browseResultCollection, out var diagnosticsInfoCollection);
458 | ClientBase.ValidateResponse(browseResultCollection, browseDescriptionCollection);
459 | ClientBase.ValidateDiagnosticInfos(diagnosticsInfoCollection, browseDescriptionCollection);
460 |
461 | if (browseResultCollection is { Count: 0 }) return result;
462 |
463 | var continuationPoints = new ByteStringCollection();
464 |
465 | for (var i = 0; i < browseResultCollection.Count; i++)
466 | {
467 | if (StatusCode.IsBad(browseResultCollection[i].StatusCode))
468 | {
469 | // this error indicates that the server does not have enough simultaneously active
470 | // continuation points. This request will need to be resent after the other operations
471 | // have been completed and their continuation points released.
472 | // if (browseResultCollection[ii].StatusCode == StatusCodes.BadNoContinuationPoints)
473 | // {
474 | // unprocessedOperations.Add( nodesToBrowse[ii] );
475 | // }
476 |
477 | continue;
478 | }
479 |
480 | if (browseResultCollection[i].References is { Count: 0 }) continue;
481 |
482 | result.Add(browseResultCollection[i].References);
483 |
484 | if (browseResultCollection[i].ContinuationPoint is not null)
485 | {
486 | continuationPoints.Add(browseResultCollection[i].ContinuationPoint);
487 | }
488 | }
489 |
490 | var revisedContinuationPoint = new ByteStringCollection();
491 | while (continuationPoints.Count > 0)
492 | {
493 | // continue browse operation.
494 | Session.BrowseNext(null, true, continuationPoints, out var results, out var diagnosticInfos);
495 |
496 | ClientBase.ValidateResponse(results, continuationPoints);
497 | ClientBase.ValidateDiagnosticInfos(diagnosticInfos, continuationPoints);
498 |
499 | for (var i = 0; i < continuationPoints.Count; i++)
500 | {
501 | if (StatusCode.IsBad(results[i].StatusCode))
502 | {
503 | continue;
504 | }
505 |
506 | if (results[i].References.Count == 0)
507 | {
508 | continue;
509 | }
510 |
511 | // save results.
512 | result.AddRange(results[i].References);
513 |
514 | // check for continuation point.
515 | if (results[i].ContinuationPoint != null)
516 | {
517 | revisedContinuationPoint.Add(results[i].ContinuationPoint);
518 | }
519 | }
520 |
521 | // check if browsing must continue;
522 | continuationPoints = revisedContinuationPoint;
523 | }
524 |
525 | return result;
526 | }
527 |
528 | #endregion
529 | }
--------------------------------------------------------------------------------
/Samples/OpcUADemo/OpcUAClient/ViewLocator.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Avalonia.Controls;
3 | using Avalonia.Controls.Templates;
4 | using OpcUAClient.ViewModels;
5 |
6 | namespace OpcUAClient;
7 |
8 | public class ViewLocator : IDataTemplate
9 | {
10 | public IControl Build(object data)
11 | {
12 | var name = data.GetType().FullName!.Replace("ViewModel", "View");
13 | var type = Type.GetType(name);
14 |
15 | if (type != null)
16 | {
17 | return (Control)Activator.CreateInstance(type)!;
18 | }
19 |
20 | return new TextBlock { Text = "Not Found: " + name };
21 | }
22 |
23 | public bool Match(object data)
24 | {
25 | return data is ViewModelBase;
26 | }
27 | }
--------------------------------------------------------------------------------
/Samples/OpcUADemo/OpcUAClient/ViewModels/LineListenViewModel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.ObjectModel;
4 | using System.Diagnostics;
5 | using System.Globalization;
6 | using System.Linq;
7 | using System.Reactive.Linq;
8 | using System.Threading.Tasks;
9 | using Avalonia;
10 | using Avalonia.Metadata;
11 | using Avalonia.Threading;
12 | using DynamicData;
13 | using LiveChartsCore;
14 | using LiveChartsCore.Defaults;
15 | using LiveChartsCore.Drawing;
16 | using LiveChartsCore.SkiaSharpView;
17 | using LiveChartsCore.SkiaSharpView.Painting;
18 | using Opc.Ua;
19 | using Opc.Ua.Configuration;
20 | using OpcUAClient.Models;
21 | using OpcUAClient.Repository;
22 | using OpcUAClient.Repository.Models;
23 | using ReactiveUI;
24 | using ReactiveUI.Fody.Helpers;
25 | using SkiaSharp;
26 |
27 | namespace OpcUAClient.ViewModels;
28 |
29 | public class LineListenViewModel : ViewModelBase
30 | {
31 | #region Private Fields
32 |
33 | // 应用名称
34 | private readonly string _applicationName = "OpcUa Client";
35 |
36 | // OPC UA客户端
37 | private readonly UaClient? _client;
38 |
39 | // 图表中绘制的曲线
40 | private readonly List> _observableCollections = new();
41 |
42 | // 是否第一次绘制图表
43 | private bool _isFirst;
44 |
45 | // 图表计时器,用来控制请求Server数据的时间间隔
46 | private readonly DispatcherTimer _chartTimer;
47 |
48 | // 数值计时器,用来控制请求Server数据的时间间隔
49 | private readonly DispatcherTimer _valueTimer;
50 |
51 | // 数据记录设置
52 | private DataRecordSettingModel _recordSetting = new();
53 |
54 | #endregion
55 |
56 | #region Public Properties
57 |
58 | ///
59 | /// 服务器地址
60 | ///
61 | [Reactive]
62 | public string ServerPath { get; set; } = "opc.tcp://192.168.110.100:4840";
63 |
64 | ///
65 | /// 用户名
66 | ///
67 | [Reactive]
68 | public string UserName { get; set; } = "sii";
69 |
70 | ///
71 | /// 密码
72 | ///
73 | [Reactive]
74 | public string Password { get; set; } = "1";
75 |
76 | ///
77 | /// 节点树
78 | ///
79 | public ObservableCollection NodeTree { get; set; } = new();
80 |
81 | ///
82 | /// 监听的曲线列表
83 | ///
84 | public ObservableCollection ListenModeCollection { get; set; } = new();
85 |
86 | ///
87 | /// 折线图X轴设置
88 | ///
89 | public Axis[] CartesianLineChartXAxis { get; set; }
90 |
91 | ///
92 | /// 折线图Y轴设置
93 | ///
94 | public Axis[] CartesianChartYAxis { get; set; }
95 |
96 | ///
97 | /// 折线图曲线
98 | ///
99 | public ObservableCollection CartesianChartLineSeries { get; set; } = new();
100 |
101 | ///
102 | /// 提示框绘制设置
103 | ///
104 | public SolidColorPaint TooltipTextPaint { get; set; } = new()
105 | {
106 | Color = SKColors.Black,
107 | SKTypeface = SKFontManager.Default.MatchCharacter('汉'),
108 | };
109 |
110 | ///
111 | /// 重点监视的值
112 | ///
113 | public ObservableCollection ImportantListenModelCollection { get; set; } = new();
114 |
115 | ///
116 | /// 打开设置窗体
117 | ///
118 | public Interaction OpenSettingDialog { get; } = new();
119 |
120 | #endregion
121 |
122 | #region Ctor
123 |
124 | ///
125 | /// 构造函数
126 | ///
127 | public LineListenViewModel()
128 | {
129 | var app = new ApplicationInstance
130 | {
131 | ApplicationName = _applicationName,
132 | ApplicationType = ApplicationType.Client,
133 | ConfigSectionName = "OpcUAClient"
134 | };
135 |
136 | var configuration = app.LoadApplicationConfiguration(false).Result;
137 |
138 | var _ = app.CheckApplicationInstanceCertificate(false, 0).Result;
139 | _client = new UaClient(configuration, null);
140 |
141 | _chartTimer = new DispatcherTimer
142 | {
143 | Interval = TimeSpan.FromSeconds(1)
144 | };
145 | _chartTimer.Tick += GetChartValue;
146 |
147 | _valueTimer = new DispatcherTimer
148 | {
149 | Interval = TimeSpan.FromSeconds(1)
150 | };
151 | _valueTimer.Tick += GetImportantListenValue;
152 |
153 | CartesianLineChartXAxis = new[]
154 | {
155 | new Axis
156 | {
157 | MinLimit = DateTime.Now.Ticks,
158 | MinStep = TimeSpan.FromSeconds(1).Ticks,
159 | UnitWidth = TimeSpan.FromSeconds(1).Ticks,
160 | SeparatorsPaint = new SolidColorPaint(SKColors.LightSlateGray) { StrokeThickness = 2 },
161 | Labeler = value => new DateTime((long)value).ToString("HH:mm:ss"),
162 | TicksPaint = new SolidColorPaint(SKColors.Black),
163 | SeparatorsAtCenter = true,
164 | LabelsPaint = new SolidColorPaint()
165 | {
166 | Color = SKColors.Black,
167 | SKTypeface = SKFontManager.Default.MatchCharacter('汉'),
168 | }
169 | }
170 | };
171 |
172 | CartesianChartYAxis = new[]
173 | {
174 | new Axis
175 | {
176 | LabelsPaint = new SolidColorPaint()
177 | {
178 | Color = SKColors.Black,
179 | SKTypeface = SKFontManager.Default.MatchCharacter('汉'),
180 | },
181 | Labeler = value => value.ToString(CultureInfo.InvariantCulture)
182 | }
183 | };
184 | }
185 |
186 | #endregion
187 |
188 | #region Public Methods
189 |
190 | ///
191 | /// 连接服务器
192 | ///
193 | public async Task ConnectionServerAsync()
194 | {
195 | if (_client is null) return;
196 |
197 | _client.UserIdentity = new UserIdentity(UserName, Password);
198 |
199 | var isConnected = await _client.ConnectAsync(ServerPath);
200 |
201 | Debug.WriteLine($"Server连接结果:{isConnected}");
202 |
203 | LoadNodeTree();
204 | }
205 |
206 | ///
207 | /// 断开连接
208 | ///
209 | public async Task DisconnectAsync()
210 | {
211 | if (_client is null) return;
212 | await _client.DisconnectAsync();
213 | }
214 |
215 | ///
216 | /// 打开设置窗体
217 | ///
218 | public async void OpenSettingWindow()
219 | {
220 | var model = new SettingWindowViewModel
221 | {
222 | AcquisitionCycle = _recordSetting.AcquisitionCycle,
223 | IsSaveToDatabase = _recordSetting.IsSaveToDatabase,
224 | DatabasePath = _recordSetting.DatabasePath,
225 | DatabaseName = _recordSetting.DatabaseName,
226 | Port = _recordSetting.Port,
227 | User = _recordSetting.User,
228 | Password = _recordSetting.Password
229 | };
230 |
231 | var result = await OpenSettingDialog.Handle(model);
232 | if (result is null) return;
233 |
234 | this._recordSetting = result;
235 | }
236 |
237 | ///
238 | /// 展开节点
239 | ///
240 | /// 展开的节点
241 | public void Expand(NodeModel node)
242 | {
243 | if (_client is null) return;
244 | if (node.Child.Count is not 0) return;
245 |
246 | var childNodes = _client.GetChildNodes(node.NodeId);
247 |
248 | node.Child.AddRange(childNodes);
249 | }
250 |
251 | #region Context Menu Command
252 |
253 | ///
254 | /// 添加订阅
255 | ///
256 | public void AddSubscribe(NodeModel nodeModel)
257 | {
258 | this.ListenModeCollection.Add(nodeModel);
259 | }
260 |
261 | [DependsOn(nameof(ListenModeCollection))]
262 | public bool CanAddSubscribe(object parameter)
263 | {
264 | if (parameter is not NodeModel nodeModel || ListenModeCollection.Contains(nodeModel)) return false;
265 | if (_client is null) return false;
266 | var dataValue = _client.GetNodeAttributesInfo(nodeModel.NodeId, new List()
267 | {
268 | Attributes.NodeClass,
269 | Attributes.Value
270 | });
271 |
272 | if ((NodeClass)dataValue[0].WrappedValue.Value != NodeClass.Variable) return false;
273 | if (dataValue[1].WrappedValue.TypeInfo.BuiltInType is BuiltInType.Int16 or BuiltInType.Int32
274 | or BuiltInType.Int64 or BuiltInType.Integer or BuiltInType.UInt16 or BuiltInType.UInt32
275 | or BuiltInType.UInt64 or BuiltInType.UInteger or BuiltInType.Float or BuiltInType.Double) return true;
276 |
277 | return false;
278 | }
279 |
280 | ///
281 | /// 移除订阅
282 | ///
283 | /// 要订阅的节点
284 | public void RemoveSubscribe(NodeModel nodeModel)
285 | {
286 | this.ListenModeCollection.Remove(nodeModel);
287 | }
288 |
289 | [DependsOn(nameof(ListenModeCollection))]
290 | public bool CanRemoveSubscribe(object parameter) =>
291 | parameter is NodeModel nodeModel && ListenModeCollection.Contains(nodeModel);
292 |
293 | ///
294 | /// 添加到重点监听
295 | ///
296 | /// 要监听的节点
297 | public void AddToImportantListen(NodeModel nodeModel)
298 | {
299 | if (this.ImportantListenModelCollection.Any(n => n.NodeId == nodeModel.NodeId))
300 | {
301 | return;
302 | }
303 |
304 | if (_client?.Session is null) return;
305 | var dataValue = _client.Session.ReadValue(nodeModel.NodeId);
306 |
307 | var node = new NodeListenModel
308 | {
309 | Name = nodeModel.Name,
310 | NodeId = nodeModel.NodeId,
311 | BuiltInType = dataValue.WrappedValue.TypeInfo.BuiltInType,
312 | Value = dataValue.WrappedValue.Value.ToString() ?? ""
313 | };
314 | this.ImportantListenModelCollection.Add(node);
315 | }
316 |
317 | [DependsOn(nameof(ImportantListenModelCollection))]
318 | public bool CanAddToImportantListen(object parameter)
319 | {
320 | if (parameter is not NodeModel nodeModel ||
321 | ImportantListenModelCollection.Any(n => n.NodeId == nodeModel.NodeId)) return false;
322 | if (_client is null) return false;
323 | var dataValue = _client.GetNodeAttributesInfo(nodeModel.NodeId, new List()
324 | {
325 | Attributes.NodeClass,
326 | Attributes.Value
327 | });
328 |
329 | if ((NodeClass)dataValue[0].WrappedValue.Value != NodeClass.Variable) return false;
330 | return dataValue[1].WrappedValue.TypeInfo.ValueRank is ValueRanks.Scalar;
331 | }
332 |
333 | ///
334 | /// 移除到重点监听
335 | ///
336 | /// 要监听的节点
337 | public void RemoveImportantListen(NodeModel nodeModel)
338 | {
339 | var node = this.ImportantListenModelCollection.FirstOrDefault(n => n.NodeId == nodeModel.NodeId);
340 | if (node is not null)
341 | {
342 | this.ImportantListenModelCollection.Remove(node);
343 | }
344 | }
345 |
346 | public bool CanRemoveImportantListen(object parameter) =>
347 | parameter is NodeModel nodeModel && ImportantListenModelCollection.Any(n => n.NodeId == nodeModel.NodeId);
348 |
349 | #endregion
350 |
351 | ///
352 | /// 开始绘制图表
353 | ///
354 | public void StartDraw()
355 | {
356 | CartesianChartLineSeries.Clear();
357 | _observableCollections.Clear();
358 | foreach (var nodeModel in ListenModeCollection)
359 | {
360 | var seriesValues = new ObservableCollection();
361 | var series = new LineSeries()
362 | {
363 | Name = nodeModel.Name,
364 | Values = seriesValues,
365 | Fill = null,
366 | GeometryFill = null,
367 | GeometrySize = 2,
368 | DataPadding = new LvcPoint(0, 0),
369 | };
370 | _observableCollections.Add(seriesValues);
371 | CartesianChartLineSeries.Add(series);
372 | }
373 |
374 | _isFirst = true;
375 | if (ListenModeCollection is not { Count: 0 })
376 | {
377 | _chartTimer.Start();
378 | }
379 |
380 | if (ImportantListenModelCollection is not { Count: 0 })
381 | {
382 | _valueTimer.Start();
383 | }
384 | }
385 |
386 | ///
387 | /// 停止绘制图表
388 | ///
389 | public void StopDraw()
390 | {
391 | _chartTimer.Stop();
392 | _valueTimer.Start();
393 | _isFirst = true;
394 | }
395 |
396 | ///
397 | /// 修改值
398 | ///
399 | public void ModifyValue(NodeListenModel nodeListenModel)
400 | {
401 | switch (nodeListenModel.BuiltInType)
402 | {
403 | case BuiltInType.Boolean:
404 | _client?.WriteValue(nodeListenModel.NodeId, bool.Parse(nodeListenModel.Value));
405 | break;
406 | case BuiltInType.SByte:
407 | break;
408 | case BuiltInType.Byte:
409 | _client?.WriteValue(nodeListenModel.NodeId, byte.Parse(nodeListenModel.Value));
410 | break;
411 | case BuiltInType.Int16:
412 | _client?.WriteValue(nodeListenModel.NodeId, short.Parse(nodeListenModel.Value));
413 | break;
414 | case BuiltInType.UInt16:
415 | _client?.WriteValue(nodeListenModel.NodeId, ushort.Parse(nodeListenModel.Value));
416 | break;
417 | case BuiltInType.Int32:
418 | _client?.WriteValue(nodeListenModel.NodeId, int.Parse(nodeListenModel.Value));
419 | break;
420 | case BuiltInType.UInt32:
421 | _client?.WriteValue(nodeListenModel.NodeId, uint.Parse(nodeListenModel.Value));
422 | break;
423 | case BuiltInType.Int64:
424 | _client?.WriteValue(nodeListenModel.NodeId, long.Parse(nodeListenModel.Value));
425 | break;
426 | case BuiltInType.UInt64:
427 | _client?.WriteValue(nodeListenModel.NodeId, ulong.Parse(nodeListenModel.Value));
428 | break;
429 | case BuiltInType.Float:
430 | _client?.WriteValue(nodeListenModel.NodeId, float.Parse(nodeListenModel.Value));
431 | break;
432 | case BuiltInType.Double:
433 | _client?.WriteValue(nodeListenModel.NodeId, double.Parse(nodeListenModel.Value));
434 | break;
435 | case BuiltInType.String:
436 | _client?.WriteValue(nodeListenModel.NodeId, nodeListenModel.Value);
437 | break;
438 | case BuiltInType.DateTime:
439 | _client?.WriteValue(nodeListenModel.NodeId, DateTime.Parse(nodeListenModel.Value));
440 | break;
441 | case BuiltInType.Guid:
442 | _client?.WriteValue(nodeListenModel.NodeId, Guid.Parse(nodeListenModel.Value));
443 | break;
444 | case BuiltInType.LocalizedText:
445 | _client?.WriteValue(nodeListenModel.NodeId, nodeListenModel.Value);
446 | break;
447 | case BuiltInType.Number:
448 | _client?.WriteValue(nodeListenModel.NodeId, int.Parse(nodeListenModel.Value));
449 | break;
450 | case BuiltInType.Integer:
451 | _client?.WriteValue(nodeListenModel.NodeId, int.Parse(nodeListenModel.Value));
452 | break;
453 | case BuiltInType.UInteger:
454 | _client?.WriteValue(nodeListenModel.NodeId, uint.Parse(nodeListenModel.Value));
455 | break;
456 | }
457 | }
458 |
459 | #endregion
460 |
461 | #region Private Methods
462 |
463 | // 加载节点树
464 | private void LoadNodeTree()
465 | {
466 | if (_client is null) return;
467 |
468 | var nodes = _client.GetChildNodes(null);
469 | NodeTree.AddRange(nodes);
470 | }
471 |
472 | // 按照时间获取图表需要绘制的值
473 | private async void GetChartValue(object? sender, EventArgs args)
474 | {
475 | if (_client?.Session is null) return;
476 | if (ListenModeCollection is { Count: 0 }) return;
477 |
478 | var (dataValueCollection, _) =
479 | await _client.Session.ReadValuesAsync(ListenModeCollection.Select(n => NodeId.Parse(n.NodeId)).ToList());
480 |
481 | for (var i = 0; i < dataValueCollection.Count; i++)
482 | {
483 | var value = double.Parse(dataValueCollection[i].WrappedValue.Value.ToString() ?? "0");
484 | var time = dataValueCollection[i].ServerTimestamp
485 | .AddMilliseconds(-dataValueCollection[i].ServerTimestamp.Millisecond).ToLocalTime();
486 | Console.WriteLine($"{time}:{value}");
487 |
488 | if (_isFirst)
489 | {
490 | _isFirst = false;
491 | CartesianLineChartXAxis[0].MinLimit = time.Ticks;
492 | }
493 |
494 | _observableCollections[i].Add(new DateTimePoint(time, value));
495 | }
496 | }
497 |
498 | // 按照时间读取重点监视的值
499 | private async void GetImportantListenValue(object? sender, EventArgs args)
500 | {
501 | if (_client?.Session is null) return;
502 | if (ImportantListenModelCollection is { Count: 0 }) return;
503 |
504 | var (dataValueCollection, _) =
505 | await _client.Session.ReadValuesAsync(ImportantListenModelCollection.Select(n => NodeId.Parse(n.NodeId))
506 | .ToList());
507 |
508 | for (var i = 0; i < dataValueCollection.Count; i++)
509 | {
510 | var value = dataValueCollection[i].WrappedValue.Value.ToString() ?? "0";
511 |
512 | ImportantListenModelCollection[i].Value = value;
513 | }
514 |
515 | if (!_recordSetting.IsSaveToDatabase) return;
516 | var repository = AvaloniaLocator.Current.GetService();
517 | if (repository is null) return;
518 |
519 | for (var i = 0; i < dataValueCollection.Count; i++)
520 | {
521 | var value = dataValueCollection[i].WrappedValue.Value.ToString() ?? "0";
522 |
523 | var data = new HistoryDataModel
524 | {
525 | NodeId = ImportantListenModelCollection[i].NodeId,
526 | NodeName = ImportantListenModelCollection[i].Name,
527 | NodeValue = float.Parse(value)
528 | };
529 | await repository.UseSource(_recordSetting.DatabaseConnectionString).AddData(data);
530 | }
531 | }
532 |
533 | #endregion
534 | }
--------------------------------------------------------------------------------
/Samples/OpcUADemo/OpcUAClient/ViewModels/MainWindowViewModel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.ObjectModel;
4 | using System.Diagnostics;
5 | using System.Threading.Tasks;
6 | using Avalonia.Controls;
7 | using DynamicData;
8 | using Opc.Ua;
9 | using Opc.Ua.Client;
10 | using Opc.Ua.Configuration;
11 | using OpcUAClient.Models;
12 | using ReactiveUI.Fody.Helpers;
13 |
14 | namespace OpcUAClient.ViewModels;
15 |
16 | public class MainWindowViewModel : ViewModelBase
17 | {
18 | [Reactive] public LineListenViewModel LineListenViewModel { get; set; }
19 |
20 | public MainWindowViewModel()
21 | {
22 | LineListenViewModel = new LineListenViewModel();
23 | }
24 | }
--------------------------------------------------------------------------------
/Samples/OpcUADemo/OpcUAClient/ViewModels/SettingWindowViewModel.cs:
--------------------------------------------------------------------------------
1 | using System.Reactive;
2 | using OpcUAClient.Models;
3 | using ReactiveUI;
4 | using ReactiveUI.Fody.Helpers;
5 |
6 | namespace OpcUAClient.ViewModels;
7 |
8 | public class SettingWindowViewModel : ViewModelBase
9 | {
10 | ///
11 | /// 采集周期
12 | ///
13 | [Reactive]
14 | public int AcquisitionCycle { get; set; }
15 |
16 | ///
17 | /// 是否存储到数据库
18 | ///
19 | [Reactive]
20 | public bool IsSaveToDatabase { get; set; }
21 |
22 | ///
23 | /// 数据库地址
24 | ///
25 | [Reactive]
26 | public string DatabasePath { get; set; } = string.Empty;
27 |
28 | ///
29 | /// 数据库名称
30 | ///
31 | [Reactive]
32 | public string DatabaseName { get; set; } = string.Empty;
33 |
34 | ///
35 | /// 用户名
36 | ///
37 | [Reactive]
38 | public string User { get; set; } = string.Empty;
39 |
40 | ///
41 | /// 密码
42 | ///
43 | [Reactive]
44 | public string Password { get; set; } = string.Empty;
45 |
46 | ///
47 | /// 数据库端口
48 | ///
49 | [Reactive]
50 | public int Port { get; set; }
51 |
52 | ///
53 | /// 确定命令
54 | ///
55 | public ReactiveCommand OkCommand { get; }
56 |
57 | ///
58 | /// 构造函数
59 | ///
60 | public SettingWindowViewModel()
61 | {
62 | OkCommand = ReactiveCommand.Create(Ok, this.WhenAnyValue(x => x.DatabasePath,
63 | x => x.DatabaseName, x => x.User,
64 | x => x.Password,x=>x.IsSaveToDatabase,
65 | (databasePath, databaseName, user, password,isSave) =>
66 | {
67 | if (!isSave)
68 | {
69 | return true;
70 | }
71 | return !string.IsNullOrWhiteSpace(databasePath) &&
72 | !string.IsNullOrWhiteSpace(databaseName) &&
73 | !string.IsNullOrWhiteSpace(user) &&
74 | !string.IsNullOrWhiteSpace(password);
75 | }));
76 | }
77 |
78 |
79 | private DataRecordSettingModel Ok()
80 | {
81 | return new DataRecordSettingModel()
82 | {
83 | AcquisitionCycle = this.AcquisitionCycle,
84 | IsSaveToDatabase = this.IsSaveToDatabase,
85 | DatabasePath = this.DatabasePath,
86 | DatabaseName = this.DatabaseName,
87 | Port = this.Port,
88 | User = this.User,
89 | Password = this.Password
90 | };
91 | }
92 | }
--------------------------------------------------------------------------------
/Samples/OpcUADemo/OpcUAClient/ViewModels/ViewModelBase.cs:
--------------------------------------------------------------------------------
1 | using ReactiveUI;
2 |
3 | namespace OpcUAClient.ViewModels;
4 |
5 | public class ViewModelBase : ReactiveObject
6 | {
7 | }
--------------------------------------------------------------------------------
/Samples/OpcUADemo/OpcUAClient/Views/LineListenView.axaml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
25 |
26 |
27 |
29 |
30 |
31 |
33 |
34 |
36 |
38 |
40 |
41 |
42 |
43 |
44 |
62 |
63 |
64 |
65 |
66 |
67 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
81 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
101 |
104 |
105 |
106 |
107 |
108 |
109 |
111 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
124 |
125 |
126 |
127 |
128 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
--------------------------------------------------------------------------------
/Samples/OpcUADemo/OpcUAClient/Views/LineListenView.axaml.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using Avalonia;
3 | using Avalonia.Controls.ApplicationLifetimes;
4 | using Avalonia.Markup.Xaml;
5 | using Avalonia.ReactiveUI;
6 | using OpcUAClient.Models;
7 | using OpcUAClient.ViewModels;
8 | using ReactiveUI;
9 |
10 | namespace OpcUAClient.Views;
11 |
12 | public partial class LineListenView : ReactiveUserControl
13 | {
14 | public LineListenView()
15 | {
16 | InitializeComponent();
17 | }
18 |
19 | private void InitializeComponent()
20 | {
21 | AvaloniaXamlLoader.Load(this);
22 |
23 | this.WhenActivated(d => d(ViewModel!.OpenSettingDialog.RegisterHandler(DoShowDialogAsync)));
24 | }
25 |
26 | private async Task DoShowDialogAsync(
27 | InteractionContext interaction)
28 | {
29 | var dialog = new SettingWindowView
30 | {
31 | DataContext = interaction.Input
32 | };
33 |
34 | var mainWindow = ((IClassicDesktopStyleApplicationLifetime)Application.Current!.ApplicationLifetime!).MainWindow;
35 | var result = await dialog.ShowDialog(mainWindow);
36 | interaction.SetOutput(result);
37 | }
38 | }
--------------------------------------------------------------------------------
/Samples/OpcUADemo/OpcUAClient/Views/MainWindow.axaml:
--------------------------------------------------------------------------------
1 |
11 |
12 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/Samples/OpcUADemo/OpcUAClient/Views/MainWindow.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls;
2 |
3 | namespace OpcUAClient.Views;
4 |
5 | public partial class MainWindow : Window
6 | {
7 | public MainWindow()
8 | {
9 | InitializeComponent();
10 | }
11 | }
--------------------------------------------------------------------------------
/Samples/OpcUADemo/OpcUAClient/Views/SettingWindowView.axaml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
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 |
--------------------------------------------------------------------------------
/Samples/OpcUADemo/OpcUAClient/Views/SettingWindowView.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Markup.Xaml;
3 | using Avalonia.ReactiveUI;
4 | using OpcUAClient.ViewModels;
5 | using ReactiveUI;
6 | using System;
7 |
8 | namespace OpcUAClient.Views;
9 |
10 | public partial class SettingWindowView : ReactiveWindow
11 | {
12 | public SettingWindowView()
13 | {
14 | InitializeComponent();
15 | #if DEBUG
16 | this.AttachDevTools();
17 | #endif
18 | }
19 |
20 | private void InitializeComponent()
21 | {
22 | AvaloniaXamlLoader.Load(this);
23 |
24 | // 订阅窗体关闭事件,执行OKCommand时,会执行关闭事件
25 | this.WhenActivated(d => d(ViewModel!.OkCommand.Subscribe(Close)));
26 | }
27 | }
--------------------------------------------------------------------------------
/Samples/OpcUADemo/OpcUAClient/app.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | OpcUAClient.Config.xml
8 |
9 |
10 |
--------------------------------------------------------------------------------
/Samples/OpcUADemo/OpcUAClient/app.manifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
10 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/Samples/OpcUADemo/OpcUADemo.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpcUAClient", "OpcUAClient\OpcUAClient.csproj", "{584D2F9C-9CFF-4029-841E-7DF7A30B627B}"
4 | EndProject
5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpcUAClient.Repository", "OpcUAClient.Repository\OpcUAClient.Repository.csproj", "{0151222F-3808-479F-BDE9-30544958D0D0}"
6 | EndProject
7 | Global
8 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
9 | Debug|Any CPU = Debug|Any CPU
10 | Release|Any CPU = Release|Any CPU
11 | EndGlobalSection
12 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
13 | {584D2F9C-9CFF-4029-841E-7DF7A30B627B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
14 | {584D2F9C-9CFF-4029-841E-7DF7A30B627B}.Debug|Any CPU.Build.0 = Debug|Any CPU
15 | {584D2F9C-9CFF-4029-841E-7DF7A30B627B}.Release|Any CPU.ActiveCfg = Release|Any CPU
16 | {584D2F9C-9CFF-4029-841E-7DF7A30B627B}.Release|Any CPU.Build.0 = Release|Any CPU
17 | {0151222F-3808-479F-BDE9-30544958D0D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
18 | {0151222F-3808-479F-BDE9-30544958D0D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
19 | {0151222F-3808-479F-BDE9-30544958D0D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
20 | {0151222F-3808-479F-BDE9-30544958D0D0}.Release|Any CPU.Build.0 = Release|Any CPU
21 | EndGlobalSection
22 | EndGlobal
23 |
--------------------------------------------------------------------------------