添加项目文件。
This commit is contained in:
16
App.xaml
Normal file
16
App.xaml
Normal file
@@ -0,0 +1,16 @@
|
||||
<Application x:Class="WpfApp1.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:local="clr-namespace:WpfApp1"
|
||||
StartupUri="MainWindow.xaml">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<materialDesign:BundledTheme BaseTheme="Light" PrimaryColor="DeepPurple" SecondaryColor="Lime" />
|
||||
|
||||
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesign2.Defaults.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
14
App.xaml.cs
Normal file
14
App.xaml.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System.Configuration;
|
||||
using System.Data;
|
||||
using System.Windows;
|
||||
|
||||
namespace WpfApp1
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for App.xaml
|
||||
/// </summary>
|
||||
public partial class App : Application
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
10
AssemblyInfo.cs
Normal file
10
AssemblyInfo.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System.Windows;
|
||||
|
||||
[assembly: ThemeInfo(
|
||||
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
|
||||
//(used if a resource is not found in the page,
|
||||
// or application resource dictionaries)
|
||||
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
|
||||
//(used if a resource is not found in the page,
|
||||
// app, or any theme specific resource dictionaries)
|
||||
)]
|
||||
31
FolderHelper.cs
Normal file
31
FolderHelper.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using Microsoft.WindowsAPICodePack.Dialogs;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace WpfApp1
|
||||
{
|
||||
public static class FolderHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 打开目录选择对话框,返回选择的路径。取消则返回 null。
|
||||
/// </summary>
|
||||
public static string? SelectFolder(string title = "请选择保存目录")
|
||||
{
|
||||
var dialog = new CommonOpenFileDialog
|
||||
{
|
||||
IsFolderPicker = true,
|
||||
Title = title
|
||||
};
|
||||
|
||||
if (dialog.ShowDialog() == CommonFileDialogResult.Ok)
|
||||
{
|
||||
return dialog.FileName;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
49
MainWindow.xaml
Normal file
49
MainWindow.xaml
Normal file
@@ -0,0 +1,49 @@
|
||||
<Window x:Class="WpfApp1.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:wv2="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"
|
||||
xmlns:local="clr-namespace:WpfApp1"
|
||||
Style="{StaticResource MaterialDesignWindow}"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
WindowStyle="None"
|
||||
ResizeMode="NoResize"
|
||||
mc:Ignorable="d"
|
||||
Title="MainWindow" Height="600" Width="800">
|
||||
<GroupBox>
|
||||
<GroupBox.Header>
|
||||
<TextBlock Text="欢迎使用" FontSize="20" Foreground="White"/>
|
||||
</GroupBox.Header>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 相机图像显示区域 -->
|
||||
<Border Grid.Row="0" BorderBrush="Gray" BorderThickness="1" Margin="10">
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
|
||||
<Image Name="CameraImage" Stretch="Uniform" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
|
||||
<!-- 状态显示 -->
|
||||
<TextBlock Grid.Row="1" x:Name="StatusText" Margin="10" FontSize="14"
|
||||
Text="正在初始化相机..." HorizontalAlignment="Center"
|
||||
Foreground="DarkBlue"/>
|
||||
|
||||
<!-- 控制按钮 -->
|
||||
<StackPanel x:Name="ButtonPanel1" Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Center"
|
||||
Margin="10" >
|
||||
<Button x:Name="SelectCameraButton" Content="选择相机" Width="100" Height="30" Click="SelectCameraButton_Click" Margin="0,0,10,0"/>
|
||||
<Button x:Name="StartCameraButton" Content="开启推流" Width="100" Height="30" Click="StartCameraButton_Click" Margin="0,0,10,0"/>
|
||||
<Button x:Name="CaptureButton" Content="捕获图像" Width="100" Height="30" Click="CaptureButton_Click" Margin="0,0,10,0"/>
|
||||
<Button x:Name="ChangeFolderButton" Content="更改保存文件夹" Width="150" Height="30" Click="ChangeFolderButton_Click" Margin="0,0,10,0"/>
|
||||
<Button x:Name="TimedCaptureButton" Content="定时拍摄设置" Width="120" Height="30" Click="TimedCaptureButton_Click" Margin="0,0,10,0"/>
|
||||
<Button x:Name="ExitButton" Content="退出" Width="100" Height="30" Click="ExitButton_Click"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
</Window>
|
||||
280
MainWindow.xaml.cs
Normal file
280
MainWindow.xaml.cs
Normal file
@@ -0,0 +1,280 @@
|
||||
using Basler.Pylon;
|
||||
using Microsoft.Win32;
|
||||
using Microsoft.WindowsAPICodePack.Dialogs;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using System.Windows;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
namespace WpfApp1
|
||||
{
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
private Camera _camera;
|
||||
private string _saveFolder = @"C:\CapturedImages";
|
||||
private ICameraInfo? _selectedCameraInfo;
|
||||
private bool _isCameraStarted = false;
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
WindowStyle = WindowStyle.None;
|
||||
ResizeMode = ResizeMode.NoResize;
|
||||
WindowState = WindowState.Maximized;
|
||||
|
||||
ButtonPanel1.IsEnabled = false;
|
||||
StatusText.Text = "正在初始化相机...请稍后";
|
||||
Task.Run(() => InitializeCameraAsync());
|
||||
}
|
||||
|
||||
private async Task InitializeCameraAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var cameras = CameraFinder.Enumerate();
|
||||
if (cameras.Count == 0)
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
ButtonPanel1.IsEnabled = true;
|
||||
StatusText.Text = "未检测到相机";
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
_selectedCameraInfo = cameras[0];
|
||||
|
||||
await Task.CompletedTask;
|
||||
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
ButtonPanel1.IsEnabled = true;
|
||||
StatusText.Text = "相机初始化完成,请选择相机并随后‘开启推流’开始取流";
|
||||
StartCameraButton.Content = "开启推流";
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Dispatcher.Invoke(() => MessageBox.Show("初始化失败: " + ex.Message));
|
||||
}
|
||||
}
|
||||
|
||||
private void StartCameraButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 确保相机已打开
|
||||
if ((_camera == null || !_camera.IsOpen) && _selectedCameraInfo != null)
|
||||
{
|
||||
if (_camera != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_camera.StreamGrabber.Stop();
|
||||
_camera.StreamGrabber.ImageGrabbed -= OnImageGrabbed;
|
||||
_camera.Close();
|
||||
}
|
||||
catch { }
|
||||
finally { _camera = null; }
|
||||
}
|
||||
|
||||
_camera = new Camera(_selectedCameraInfo);
|
||||
_camera.Open();
|
||||
_camera.Parameters[PLCamera.AcquisitionMode].SetValue(PLCamera.AcquisitionMode.Continuous);
|
||||
}
|
||||
|
||||
if (_camera == null || !_camera.IsOpen)
|
||||
{
|
||||
StatusText.Text = "请先选择一个相机";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_isCameraStarted)
|
||||
{
|
||||
_camera.StreamGrabber.ImageGrabbed += OnImageGrabbed;
|
||||
_camera.StreamGrabber.Start(GrabStrategy.LatestImages, GrabLoop.ProvidedByStreamGrabber);
|
||||
_isCameraStarted = true;
|
||||
StatusText.Text = "推流已开启";
|
||||
StartCameraButton.Content = "关闭推流";
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
_camera.StreamGrabber.Stop();
|
||||
_camera.StreamGrabber.ImageGrabbed -= OnImageGrabbed;
|
||||
_isCameraStarted = false;
|
||||
StatusText.Text = "推流已关闭";
|
||||
StartCameraButton.Content = "开启推流";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
StatusText.Text = "关闭推流失败:" + ex.Message;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
StatusText.Text = "推流切换失败:" + ex.Message;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnImageGrabbed(object sender, ImageGrabbedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (IGrabResult result = e.GrabResult)
|
||||
{
|
||||
if (!result.GrabSucceeded) return;
|
||||
|
||||
var converter = new PixelDataConverter();
|
||||
converter.OutputPixelFormat = PixelType.BGRA8packed;
|
||||
|
||||
byte[] buffer = new byte[result.Width * result.Height * 4];
|
||||
converter.Convert(buffer, result);
|
||||
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
CameraImage.Source = BitmapSource.Create(
|
||||
result.Width, result.Height, 96, 96,
|
||||
System.Windows.Media.PixelFormats.Bgra32,
|
||||
null, buffer, result.Width * 4);
|
||||
});
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 忽略异常
|
||||
}
|
||||
}
|
||||
|
||||
private void CaptureButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (CameraImage.Source == null)
|
||||
{
|
||||
StatusText.Text = "没有图像可保存";
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
string filePath = Path.Combine(_saveFolder,
|
||||
$"Capture_{DateTime.Now:yyyyMMdd_HHmmss}.png");
|
||||
|
||||
SaveImage(CameraImage.Source as BitmapSource, filePath);
|
||||
|
||||
StatusText.Text = $"图像已保存到:{filePath}";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
StatusText.Text = "保存失败:" + ex.Message;
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveImage(BitmapSource bitmap, string filePath)
|
||||
{
|
||||
var encoder = new PngBitmapEncoder();
|
||||
encoder.Frames.Add(BitmapFrame.Create(bitmap));
|
||||
|
||||
using (FileStream stream = new FileStream(filePath, FileMode.Create))
|
||||
{
|
||||
encoder.Save(stream);
|
||||
}
|
||||
}
|
||||
|
||||
private void ChangeFolderButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var selected = FolderHelper.SelectFolder();
|
||||
|
||||
if (selected != null)
|
||||
{
|
||||
_saveFolder = selected;
|
||||
StatusText.Text = $"保存目录已切换为:{_saveFolder}";
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
|
||||
{
|
||||
base.OnClosing(e);
|
||||
|
||||
if (_camera != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_camera.StreamGrabber.Stop();
|
||||
_camera.StreamGrabber.ImageGrabbed -= OnImageGrabbed;
|
||||
_camera.Close();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
StatusText.Text = "关闭相机时出错:" + ex.Message;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_camera = null;
|
||||
_isCameraStarted = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SelectCameraButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var dlg = new SelectCamera();
|
||||
dlg.Owner = this;
|
||||
|
||||
if (dlg.ShowDialog() == true)
|
||||
{
|
||||
var selectedInfo = dlg.SelectedCameraInfo;
|
||||
_selectedCameraInfo = selectedInfo;
|
||||
|
||||
// 关闭旧相机,打开新相机,但不取流
|
||||
try
|
||||
{
|
||||
if (_camera != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_camera.StreamGrabber.Stop();
|
||||
_camera.StreamGrabber.ImageGrabbed -= OnImageGrabbed;
|
||||
_camera.Close();
|
||||
}
|
||||
catch { }
|
||||
finally { _camera = null; _isCameraStarted = false; }
|
||||
}
|
||||
|
||||
_camera = new Camera(_selectedCameraInfo);
|
||||
_camera.Open();
|
||||
_camera.Parameters[PLCamera.AcquisitionMode].SetValue(PLCamera.AcquisitionMode.Continuous);
|
||||
|
||||
_isCameraStarted = false;
|
||||
StatusText.Text = $"已选择并打开相机:{selectedInfo[CameraInfoKey.SerialNumber]}(未取流)。点击‘开启推流’开始/停止推流";
|
||||
StartCameraButton.Content = "开启推流";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
StatusText.Text = "打开相机失败:" + ex.Message;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ExitButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
private void TimedCaptureButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// 定时拍摄只要求相机已打开,无需取流
|
||||
if (_camera == null || !_camera.IsOpen)
|
||||
{
|
||||
MessageBox.Show("请先选择相机(会自动打开但不取流)");
|
||||
return;
|
||||
}
|
||||
var dlg = new TimedCapture(_camera);
|
||||
dlg.Owner = this;
|
||||
dlg.ShowDialog();
|
||||
}
|
||||
}
|
||||
}
|
||||
34
SelectCamera.xaml
Normal file
34
SelectCamera.xaml
Normal file
@@ -0,0 +1,34 @@
|
||||
<Window x:Class="WpfApp1.SelectCamera"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:WpfApp1"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
mc:Ignorable="d"
|
||||
Title="选择相机" Height="450" Width="800">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Text="检测到的摄像头:" FontWeight="Bold" Margin="10" FontSize="18" Foreground="Purple"/>
|
||||
|
||||
<ListView x:Name="CameraList" Grid.Row="1" Margin="0 10 0 10">
|
||||
<ListView.View>
|
||||
<GridView>
|
||||
<GridViewColumn Header="序列号" DisplayMemberBinding="{Binding Serial}"/>
|
||||
<GridViewColumn Header="型号" DisplayMemberBinding="{Binding Model}"/>
|
||||
<GridViewColumn Header="接口类型" DisplayMemberBinding="{Binding Interface}"/>
|
||||
</GridView>
|
||||
</ListView.View>
|
||||
</ListView>
|
||||
|
||||
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right" Margin="10">
|
||||
<Button Content="确认" Width="80" Margin="0 0 10 0" Click="BtnOK_Click"/>
|
||||
<Button Content="取消" Width="80" Click="BtnCancel_Click"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Window>
|
||||
73
SelectCamera.xaml.cs
Normal file
73
SelectCamera.xaml.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
using Basler.Pylon;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace WpfApp1
|
||||
{
|
||||
/// <summary>
|
||||
/// SelectCamera.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class SelectCamera : Window
|
||||
{
|
||||
public ICameraInfo SelectedCameraInfo { get; private set; }
|
||||
public SelectCamera()
|
||||
{
|
||||
InitializeComponent();
|
||||
LoadCameras();
|
||||
}
|
||||
private void LoadCameras()
|
||||
{
|
||||
CameraList.Items.Clear();
|
||||
|
||||
var list = CameraFinder.Enumerate();
|
||||
|
||||
if (list.Count == 0)
|
||||
{
|
||||
MessageBox.Show("未检测到任何摄像头。");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var info in list)
|
||||
{
|
||||
CameraList.Items.Add(new
|
||||
{
|
||||
Raw = info,
|
||||
Serial = info[CameraInfoKey.SerialNumber],
|
||||
Model = info[CameraInfoKey.ModelName]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void BtnOK_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (CameraList.SelectedItem == null)
|
||||
{
|
||||
MessageBox.Show("请先选择一个相机");
|
||||
return;
|
||||
}
|
||||
|
||||
dynamic item = CameraList.SelectedItem;
|
||||
|
||||
SelectedCameraInfo = item.Raw;
|
||||
this.DialogResult = true;
|
||||
this.Close();
|
||||
}
|
||||
|
||||
private void BtnCancel_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
this.DialogResult = false;
|
||||
this.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
44
TimedCapture.xaml
Normal file
44
TimedCapture.xaml
Normal file
@@ -0,0 +1,44 @@
|
||||
<Window x:Class="WpfApp1.TimedCapture"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:WpfApp1"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
mc:Ignorable="d"
|
||||
Title="定时拍摄" Height="450" Width="800">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<StackPanel Grid.Row="0" Margin="10">
|
||||
|
||||
<TextBlock Text="定时拍摄" FontSize="20" FontWeight="Bold" Margin="0,0,0,10"/>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,0,0,10">
|
||||
<TextBlock Text="正在使用的相机:" VerticalAlignment="Center" Margin="0,0,10,0"/>
|
||||
<TextBlock x:Name="CameraInfoTextBlock" Text="相机信息" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,0,0,10">
|
||||
<TextBlock Text="拍摄间隔(毫秒):" VerticalAlignment="Center" Margin="0,0,10,0"/>
|
||||
<TextBox x:Name="IntervalTextBox" Width="100"/>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Margin="0,0,0,10">
|
||||
<TextBlock Text="保存文件夹:" VerticalAlignment="Center" Margin="0,0,10,0"/>
|
||||
<TextBox x:Name="FolderTextBox" Width="400"/>
|
||||
<Button x:Name="BrowseButton" Content="浏览" Width="80" Margin="10,0,0,0" Click="BrowseButton_Click" />
|
||||
</StackPanel>
|
||||
<TextBlock x:Name="StatusTextBlock" Text="状态:未开始" FontSize="14" Foreground="DarkGreen" Margin="0,10,0,0"/>
|
||||
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Right" Margin="10">
|
||||
<Button x:Name="TimedCaptureStart" Content="开始拍摄" Width="100" Margin="0,0,20,0" Click="TimedCaptureStart_Click"/>
|
||||
<Button x:Name="TimedCaptureStop" Content="停止拍摄" Width="100" Click="TimedCaptureStop_Click"/>
|
||||
</StackPanel>
|
||||
|
||||
</Grid>
|
||||
</Window>
|
||||
133
TimedCapture.xaml.cs
Normal file
133
TimedCapture.xaml.cs
Normal file
@@ -0,0 +1,133 @@
|
||||
using Basler.Pylon;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace WpfApp1
|
||||
{
|
||||
/// <summary>
|
||||
/// TimedCapture.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class TimedCapture : Window
|
||||
{
|
||||
private readonly Camera _camera;
|
||||
private readonly DispatcherTimer _timer = new DispatcherTimer();
|
||||
private string _saveFolder = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures);
|
||||
private int _intervalMs = 1000;
|
||||
|
||||
public TimedCapture(Camera camera)
|
||||
{
|
||||
InitializeComponent();
|
||||
_camera = camera;
|
||||
|
||||
var name = _camera?.CameraInfo?[CameraInfoKey.FriendlyName];
|
||||
CameraInfoTextBlock.Text = string.IsNullOrWhiteSpace(name) ? "未知相机" : name;
|
||||
FolderTextBox.Text = _saveFolder;
|
||||
IntervalTextBox.Text = _intervalMs.ToString();
|
||||
|
||||
_timer.Tick += OnTimerTick;
|
||||
_timer.IsEnabled = false;
|
||||
}
|
||||
|
||||
private void BrowseButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var selected = FolderHelper.SelectFolder("请选择保存目录");
|
||||
if (!string.IsNullOrWhiteSpace(selected))
|
||||
{
|
||||
_saveFolder = selected;
|
||||
FolderTextBox.Text = _saveFolder;
|
||||
}
|
||||
}
|
||||
|
||||
private void TimedCaptureStart_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_camera == null || !_camera.IsOpen)
|
||||
{
|
||||
MessageBox.Show("相机未打开");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!int.TryParse(IntervalTextBox.Text, out var ms) || ms <= 0)
|
||||
{
|
||||
MessageBox.Show("请输入有效的拍摄间隔(毫秒)");
|
||||
return;
|
||||
}
|
||||
_intervalMs = ms;
|
||||
_timer.Interval = TimeSpan.FromMilliseconds(_intervalMs);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(FolderTextBox.Text))
|
||||
{
|
||||
MessageBox.Show("请选择保存文件夹");
|
||||
return;
|
||||
}
|
||||
_saveFolder = FolderTextBox.Text.Trim();
|
||||
if (!Directory.Exists(_saveFolder))
|
||||
{
|
||||
try { Directory.CreateDirectory(_saveFolder); } catch (Exception ex) { MessageBox.Show("创建文件夹失败:" + ex.Message); return; }
|
||||
}
|
||||
|
||||
StatusTextBlock.Text = "状态:拍摄中";
|
||||
TimedCaptureStart.IsEnabled = false;
|
||||
TimedCaptureStop.IsEnabled = true;
|
||||
_timer.Start();
|
||||
}
|
||||
|
||||
private void TimedCaptureStop_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_timer.Stop();
|
||||
StatusTextBlock.Text = "状态:已停止";
|
||||
TimedCaptureStart.IsEnabled = true;
|
||||
TimedCaptureStop.IsEnabled = false;
|
||||
}
|
||||
|
||||
private void OnTimerTick(object? sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 单次抓拍
|
||||
using (IGrabResult grabResult = _camera.StreamGrabber.GrabOne(_intervalMs))
|
||||
{
|
||||
if (grabResult != null && grabResult.GrabSucceeded)
|
||||
{
|
||||
var converter = new PixelDataConverter { OutputPixelFormat = PixelType.BGRA8packed };
|
||||
byte[] buffer = new byte[grabResult.Width * grabResult.Height * 4];
|
||||
converter.Convert(buffer, grabResult);
|
||||
|
||||
var bitmap = BitmapSource.Create(
|
||||
grabResult.Width,
|
||||
grabResult.Height,
|
||||
96, 96,
|
||||
System.Windows.Media.PixelFormats.Bgra32,
|
||||
null,
|
||||
buffer,
|
||||
grabResult.Width * 4);
|
||||
|
||||
string file = Path.Combine(_saveFolder, $"Timed_{DateTime.Now:yyyyMMdd_HHmmss_fff}.png");
|
||||
SaveImage(bitmap, file);
|
||||
StatusTextBlock.Text = "状态:已保存 " + System.IO.Path.GetFileName(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
StatusTextBlock.Text = "状态:抓拍失败 - " + ex.Message;
|
||||
}
|
||||
}
|
||||
|
||||
private static void SaveImage(BitmapSource bitmap, string filePath)
|
||||
{
|
||||
var encoder = new PngBitmapEncoder();
|
||||
encoder.Frames.Add(BitmapFrame.Create(bitmap));
|
||||
using var stream = new FileStream(filePath, FileMode.Create, FileAccess.Write);
|
||||
encoder.Save(stream);
|
||||
}
|
||||
|
||||
protected override void OnClosed(EventArgs e)
|
||||
{
|
||||
_timer.Stop();
|
||||
base.OnClosed(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
25
WpfApp1.csproj
Normal file
25
WpfApp1.csproj
Normal file
@@ -0,0 +1,25 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<UseWPF>true</UseWPF>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Basler.Pylon.NET.x64" Version="10.3.2.636" />
|
||||
<PackageReference Include="MaterialDesignThemes" Version="5.3.0" />
|
||||
<PackageReference Include="Microsoft-WindowsAPICodePack-Shell" Version="1.1.5" />
|
||||
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.3595.46" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Basler.Pylon">
|
||||
<HintPath>..\..\..\Pylon\Development\Assemblies\Basler.Pylon\net8.0\x64\Basler.Pylon.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
9
WpfApp1.slnx
Normal file
9
WpfApp1.slnx
Normal file
@@ -0,0 +1,9 @@
|
||||
<Solution>
|
||||
<Configurations>
|
||||
<Platform Name="Any CPU" />
|
||||
<Platform Name="x64" />
|
||||
</Configurations>
|
||||
<Project Path="WpfApp1.csproj">
|
||||
<Platform Solution="*|x64" Project="x64" />
|
||||
</Project>
|
||||
</Solution>
|
||||
Reference in New Issue
Block a user