I'm trying to create an SVG file from some shape objects(path geometries, ellipses etc.) drawn on XAML canvas controls (the canvases are rendered on top of each other inside grid controls). It looks like Win2D can provide the classes to generate the SVG file, but I'm struggling to figure out how to populate the CanvasSvgDocument class with the shapes.
This is the only partial example I've found, but the answer seems to include a conversion to XML strings to load into the CanvasSvgDocument which seems like doing the same task twice (as SVG files are XML). Is anybody able to provide an example of how I might do this?
My current best guess of what the resulting code might look like is:
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Svg;
using Microsoft.Graphics.Canvas.UI.Xaml;
using System;
using System.Threading.Tasks;
using Windows.Storage.Streams;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Shapes;
namespace MyApp
{
public class ExportSVG
{
private CanvasSvgDocument SVG { get; } = new(new CanvasDevice());
public async Task SaveASync(IRandomAccessStream stream) => await SVG.SaveAsync(stream);
public void AddCanvases(UIElement element)
{
if (element is Grid grid)
{
foreach (UIElement child in grid.Children)
{
AddCanvases(child);
}
}
else if (element is Canvas canvas)
{
AddCanvas(canvas);
}
}
public void AddCanvas(Canvas canvas)
{
foreach (UIElement element in canvas.Children)
{
if (element is Path path)
{
if (path.Data is PathGeometry pathGeometry)
{
foreach (PathFigure pathFigure in pathGeometry.Figures)
{
// Add path to SVG
}
}
else if (path.Data is EllipseGeometry ellipseGeometry)
{
// Add ellipse to SVG
}
}
else if (element is TextBlock textBlock)
{
// add text to SVG
}
}
}
}
}
You could use CanvasGeometry.CreateInk convert the ink strokes into geometry and use the relevant methods under the CanvasGeometry namespace to get the path, then write a custom class to read the parse path. Finally, the generated CanvasSvgDocument object is used to save the stream containing the svg content.
Please refer to the following sample to do these steps. (Note: download Win2D.uwp package)
XAML code:
<Page
x:Class="CanvasToSVG.MainPage"
…
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<StackPanel>
<InkCanvas x:Name="MyInkConntrol" Height="500">
</InkCanvas>
<InkToolbar Grid.Row="1" TargetInkCanvas="{x:Bind MyInkConntrol}" HorizontalAlignment="Left">
<InkToolbarCustomToolButton Click="save">
<SymbolIcon Symbol="Save" />
</InkToolbarCustomToolButton>
</InkToolbar>
<Line Stroke="Black"/>
<Image Name="ImageControl"></Image>
</StackPanel>
</Grid>
</Page>
Code behind:
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
MyInkConntrol.InkPresenter.InputDeviceTypes= CoreInputDeviceTypes.Mouse |CoreInputDeviceTypes.Pen |
CoreInputDeviceTypes.Touch;
MyInkConntrol.InkPresenter.StrokesCollected += InkPresenter_StrokesCollected;
MyInkConntrol.InkPresenter.StrokesErased += InkPresenter_StrokesErased;
}
private async void InkPresenter_StrokesErased(Windows.UI.Input.Inking.InkPresenter sender, Windows.UI.Input.Inking.InkStrokesErasedEventArgs args)
{
await RenderSvg();
}
private async void InkPresenter_StrokesCollected(Windows.UI.Input.Inking.InkPresenter sender, Windows.UI.Input.Inking.InkStrokesCollectedEventArgs args)
{
await RenderSvg();
}
public async Task RenderSvg()
{
using (var stream=new InMemoryRandomAccessStream())
{
await RenderSvg(stream);
var image= new SvgImageSource();
await image.SetSourceAsync(stream);
ImageControl.Source = image;
}
}
public async Task RenderSvg(IRandomAccessStream randomAccessStream)
{
var sharedDevice = CanvasDevice.GetSharedDevice();
using (var offscreen = new CanvasRenderTarget(sharedDevice, (float)MyInkConntrol.RenderSize.Width, (float)MyInkConntrol.RenderSize.Height, 96))
{
using (var session = offscreen.CreateDrawingSession())
{
var svgDocument = new CanvasSvgDocument(sharedDevice);
svgDocument.Root.SetStringAttribute("viewBox", $"0 0 {MyInkConntrol.RenderSize.Width} {MyInkConntrol.RenderSize.Height}");
foreach (var stroke in MyInkConntrol.InkPresenter.StrokeContainer.GetStrokes())
{
var canvasGeometry = CanvasGeometry.CreateInk(session, new[] { stroke }).Outline();
var pathReceiver = new CanvasGeometryToSvgPathReader();
canvasGeometry.SendPathTo(pathReceiver);
var element = svgDocument.Root.CreateAndAppendNamedChildElement("path");
element.SetStringAttribute("d", pathReceiver.Path);
var color = stroke.DrawingAttributes.Color;
element.SetColorAttribute("fill", color);
}
await svgDocument.SaveAsync(randomAccessStream);
}
}
}
private async void save(object sender, RoutedEventArgs e)
{
FileSavePicker savePicker = new FileSavePicker();
savePicker.SuggestedStartLocation = PickerLocationId.DocumentsLibrary;
savePicker.FileTypeChoices.Add("svg file", new List<string>() { ".svg" });
savePicker.SuggestedFileName = "NewSvgfile1";
var file = await savePicker.PickSaveFileAsync();
if (file != null)
{
using (var writeStream = (await file.OpenStreamForWriteAsync()).AsRandomAccessStream())
{
await RenderSvg(writeStream);
await writeStream.FlushAsync();
}
}
}
}
Custom class:
public class CanvasGeometryToSvgPathReader: ICanvasPathReceiver
{
private readonly Vector2 _ratio;
private List<string> Parts { get; }
public string Path => string.Join(" ", Parts);
public CanvasGeometryToSvgPathReader() : this(Vector2.One)
{ }
public CanvasGeometryToSvgPathReader(Vector2 ratio)
{
_ratio = ratio;
Parts = new List<string>();
}
public void BeginFigure(Vector2 startPoint, CanvasFigureFill figureFill)
{
Parts.Add($"M{startPoint.X / _ratio.X} {startPoint.Y / _ratio.Y}");
}
public void AddArc(Vector2 endPoint, float radiusX, float radiusY, float rotationAngle, CanvasSweepDirection sweepDirection, CanvasArcSize arcSize)
{
}
public void AddCubicBezier(Vector2 controlPoint1, Vector2 controlPoint2, Vector2 endPoint)
{
Parts.Add($"C{controlPoint1.X / _ratio.X},{controlPoint1.Y / _ratio.Y} {controlPoint2.X / _ratio.X},{controlPoint2.Y / _ratio.Y} {endPoint.X / _ratio.X},{endPoint.Y / _ratio.Y}");
}
public void AddLine(Vector2 endPoint)
{
Parts.Add($"L {endPoint.X / _ratio.X} {endPoint.Y / _ratio.Y}");
}
public void AddQuadraticBezier(Vector2 controlPoint, Vector2 endPoint)
{
//
}
public void SetFilledRegionDetermination(CanvasFilledRegionDetermination filledRegionDetermination)
{
//
}
public void SetSegmentOptions(CanvasFigureSegmentOptions figureSegmentOptions)
{
//
}
public void EndFigure(CanvasFigureLoop figureLoop)
{
Parts.Add("Z");
}
}
In the end I was able to use the XmlWriter class to write my own canvas-to-svg converter. Using the example in the question:
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Svg;
using Microsoft.Graphics.Canvas.UI.Xaml;
using System;
using System.Xml;
using System.Threading.Tasks;
using Windows.Storage.Streams;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Shapes;
namespace MyApp
{
public class ExportSVG
{
private XmlWriter Writer { get; }
public SVGWriter(System.IO.Stream stream)
{
Writer = XmlWriter.Create(stream, new XmlWriterSettings()
{
Indent = true,
});
Writer.WriteStartElement("svg", "http://www.w3.org/2000/svg");
Write("version", "1.1");
}
public void AddCanvases(UIElement element)
{
if (element is Grid grid)
{
foreach (UIElement child in grid.Children)
{
AddCanvases(child);
}
}
else if (element is Canvas canvas)
{
AddCanvas(canvas);
}
}
public void AddCanvas(Canvas canvas)
{
foreach (UIElement element in canvas.Children)
{
if (element is Path path)
{
else if (path.Data is EllipseGeometry ellipseGeometry)
{
Writer.WriteStartElement("ellipse");
Write("stroke", ellipseGeometry.Stroke);
Write("stroke-width", ellipseGeometry.StrokeThickness);
Write("cx", ellipseGeometry.Center.X);
Write("cy", ellipseGeometry.Center.Y);
Write("rx", ellipseGeometry.RadiusX);
Write("ry", ellipseGeometry.RadiusY);
Writer.WriteEndElement();
}
}
else if (element is TextBlock textBlock)
{
Writer.WriteStartElement("text");
Write("x", Canvas.GetLeft(textBlock));
Write("y", Canvas.GetTop(textBlock) + textBlock.ActualHeight);
Write("font-family", textBlock.FontFamily.Source);
Write("font-size", $"{textBlock.FontSize}px");
Writer.WriteString(textBlock.Text);
Writer.WriteEndElement();
}
}
}
private void Write(string name, string value)
{
Writer.WriteAttributeString(name, value);
}
private void Write(string name, double value)
{
Write(name, ((float)value).ToString());
}
public void Dispose()
{
Writer.WriteEndElement();
Writer.Close();
Writer.Dispose();
}
}
}
Related
I've some hard time trying to display images in my UWP App.
My images are located in the Images library.
The question is:
How to show jpg in the Image Xaml control on a UWP, that is not in the projects "Assets"?
So far using Binding, I’ve
Try to set Image.Source to the full path with no success, image control remains blank. (for file access retriction a guess)
Try using setting BitMapImage to the Image.Source property with no success, image control remains blank. (for file access retriction a guess)
Try to assign FileStorage class to the Image.Source property with no success, image control remains blank.
Try to load the image file in a stream and load the stream in the BitmapImage, but there I’ve threading issues. More about this below.
About the threading issue, I’ve come to two issues for now.
When loading the file/stream:
var file = File.ReadAllBytes(_currentImagePath);
I get
System.InvalidOperationException: 'Synchronous operations should not be performed on the UI thread. Consider wrapping this method in Task.Run.'
When using Task.Run on any manner I get an exception when trying to set the Bitmap source
The application called an interface that was marshalled for a different thread. (Exception from HRESULT: 0x8001010E (RPC_E_WRONG_THREAD))
var bitmap = new BitmapImage();
return await Task.Run(async () =>
{
var file = File.ReadAllBytes(_currentImagePath);
using (var stream = new InMemoryRandomAccessStream())
{
await stream.WriteAsync(file.AsBuffer());
stream.Seek(0);
await bitmap.SetSourceAsync(stream); // **Exception here**
return bitmap;
}
});
Here the full source code:
namespace App1.Images
{
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks;
using Windows.Storage;
using Windows.Storage.Streams;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Media.Imaging;
using App1.Annotations;
public class ImagesLibrairies : INotifyPropertyChanged
{
private readonly HashSet<Image> _images;
private readonly string _fileTypes;
private readonly HashSet<Image> _preferrednessFiles;
private readonly DispatcherTimer _timer;
private ulong _minFileSize = 5 * 1024; // 5k in bytes
private ulong _maxFileSize = 1024 * 1024 * 20; //20 MBytes
private int _imageIndex = 0;
public ImagesLibrairies()
{
_preferrednessFiles = new HashSet<Image>();
_fileTypes = "*.jpg;*.jpeg;*.bmp;*.tif"; // ignore *.ico;*.png;*.svg;*.gif
_images = new HashSet<Image>();
_timer = new DispatcherTimer { Interval = new TimeSpan(0, 0, 0, 5) };
_timer.Tick += _timer_Tick;
}
private Uri _currentImage;
public Uri CurrentImage
{
get => _currentImage;
private set
{
_currentImage = value;
OnPropertyChanged(nameof(CurrentImage));
OnPropertyChanged(nameof(Image));
}
}
public async Task<BitmapImage> GetBitmapImage()
{
var bitmap = new BitmapImage();
return await Task.Run(async () =>
{
var file = File.ReadAllBytes(_currentImagePath);
using (var stream = new InMemoryRandomAccessStream())
{
await stream.WriteAsync(file.AsBuffer());
stream.Seek(0);
await bitmap.SetSourceAsync(stream);
return bitmap;
}
});
}
public BitmapImage Image
{
get
{
if (!string.IsNullOrEmpty(_currentImagePath))
return GetBitmapImage().Result;
return null;
}
}
private string _currentImagePath;
public string CurrentImagePath
{
get => _currentImagePath;
set
{
_currentImagePath = value;
OnPropertyChanged(nameof(CurrentImagePath));
CurrentImage = new Uri(value);
}
}
public object CurrentStorageFile { get; set; }
private void _timer_Tick(object sender, object e)
{
SetNext();
}
public async Task ScanLibrairies()
{
var picturesFolder = KnownFolders.PicturesLibrary;
var files = await picturesFolder.GetFilesAsync();
foreach (var file in files)
{
Debug.WriteLine(file.Name + ", " + file.DateCreated);
if (_images.Count >= int.MaxValue)
return;
var prop = await file.GetBasicPropertiesAsync();
var imageLocation = new Image
{
StorageFile = file,
Properties = prop
};
if (imageLocation.Properties.Size < _minFileSize || imageLocation.Properties.Size > _maxFileSize)
continue;
if (_preferrednessFiles.Any(o => o.Equals(imageLocation) && o.Preferredness == Preferredness.No))
continue;
_images.Add(imageLocation);
}
}
public void SetNext()
{
if (_images == null || !_images.Any())
{
return;
}
_imageIndex++;
var image = _images.Skip(_imageIndex).FirstOrDefault();
if (image != null)
{
Debug.WriteLine($"Displaying: {image.StorageFile.Path}");
}
CurrentImagePath = image?.StorageFile.Path;
CurrentImage = new Uri(CurrentImagePath);
}
public void SetPrevious()
{
if (_images == null || !_images.Any())
return;
_imageIndex--;
var image = _images.Skip(_imageIndex).FirstOrDefault();
CurrentImagePath = image?.StorageFile.Path;
}
public void LikeThisPicture(Image picture)
{
var pic = _preferrednessFiles.FirstOrDefault(o => o.Equals(picture));
if (pic == null)
{
picture.Preferredness = Preferredness.Very;
_preferrednessFiles.Add(picture);
return;
}
pic.Preferredness = Preferredness.Very;
}
public void DislikeThisPicture(Image picture)
{
var pic = _preferrednessFiles.FirstOrDefault(o => o.Equals(picture));
if (pic == null)
{
picture.Preferredness = Preferredness.No;
_preferrednessFiles.Add(picture);
return;
}
pic.Preferredness = Preferredness.No;
}
public void StartAsync()
{
ScanLibrairies();
_timer.Start();
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Here the xaml markup
<Page.DataContext>
<images:ImagesLibrairies/>
</Page.DataContext>
<Grid >
<!--Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">-->
<Image Stretch="UniformToFill" Margin="15">
<Image.Source>
<BitmapImage UriSource="{Binding Image}"></BitmapImage>
<!--<BitmapImage UriSource="{Binding CurrentImagePath}"></BitmapImage>-->
<!--<BitmapImage UriSource="D:\Utilisateurs\hugod\OneDrive\Images\jouets\20180106_132041.jpg"></BitmapImage>-->
<!--<BitmapImage UriSource="Assets/Black-And-White-Wall-Brick-Texture-Shadow-WallpapersByte-com-3840x2160.jpg"></BitmapImage>-->
<!--<BitmapImage UriSource="{Binding CurrentStorageFile}"></BitmapImage>-->
</Image.Source>
</Image>
</Grid>
Raymond is right!
CurrentImage = new BitmapImage();
await CurrentImage.SetSourceAsync(await image.StorageFile.OpenAsync(FileAccessMode.Read));
A goody for Raymond!
So I am designing an application for windows laptops to connect to a custom designed pressure sensor. The application pairs to the device and then receives notifications from the device every 10 ms. Then for some reason the communication stops. I know it is a problem with my application and not with the device, because when I connect to my phone, I do not have this problem.
Here is the main page where I create the devicewatcher and discover the device:
using System;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Core;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
using Windows.Devices.Bluetooth;
using Windows.Devices.Enumeration;
// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409
namespace BLEInterfaceTest
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
private DeviceWatcher deviceWatcher;
private ObservableCollection<DeviceInformation> deviceList = new ObservableCollection<DeviceInformation>();
public MainPage()
{
this.InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
this.DataContext = deviceList;
deviceListView.ItemsSource = deviceList;
deviceWatcher = DeviceInformation.CreateWatcher(
"System.ItemNameDisplay:~~\"Button\"",
new string[] {
"System.Devices.Aep.DeviceAddress",
"System.Devices.Aep.IsConnected" },
DeviceInformationKind.AssociationEndpoint);
deviceWatcher.Added += DeviceWatcher_Added;
deviceWatcher.Removed += DeviceWatcher_Removed;
deviceWatcher.Start();
base.OnNavigatedTo(e);
SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility =
AppViewBackButtonVisibility.Collapsed;
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
deviceWatcher.Stop();
base.OnNavigatedFrom(e);
}
private async void DeviceWatcher_Removed(DeviceWatcher sender, DeviceInformationUpdate args)
{
var toRemove = (from a in deviceList where a.Id == args.Id select a).FirstOrDefault();
if (toRemove != null)
{
await this.Dispatcher.RunAsync(
Windows.UI.Core.CoreDispatcherPriority.Normal,
() => { deviceList.Remove(toRemove); });
}
}
private async void DeviceWatcher_Added(DeviceWatcher sender, DeviceInformation args)
{
await this.Dispatcher.RunAsync(
Windows.UI.Core.CoreDispatcherPriority.Normal,
() => { deviceList.Add(args); });
}
private void deviceListView_ItemClick(object sender, ItemClickEventArgs e)
{
this.Frame.Navigate(typeof(DevicePage), e.ClickedItem);
}
}
}'
This next code is the page where the pressure sensor is connected to and where data is read from the device.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Core;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
using Windows.UI.Popups;
using Windows.Devices.Bluetooth.GenericAttributeProfile;
using Windows.Devices.Bluetooth;
using Windows.Devices.Enumeration;
using Windows.Storage.Pickers;
using Windows.Storage;
using Windows.Storage.Streams;
using System.Threading.Tasks;
using Windows.ApplicationModel.Background;
// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238
namespace BLEInterfaceTest
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class DevicePage : Page
{
private DeviceInformation device { get; set; }
private PressureSensor pSensor { get; set; }
public static DateTime startTime { get; set; }
public ObservableCollection<DataPoint> PressureData = new ObservableCollection<DataPoint>();
public static ObservableCollection<DataPoint> inbetween;
private static TextBox txtP;
private BluetoothLEDevice leDevice;
private DispatcherTimer timer = new DispatcherTimer();
private int packetNum = 0;
public DevicePage()
{
this.InitializeComponent();
SystemNavigationManager.GetForCurrentView().BackRequested += DevicePage_BackRequested;
txtP = txtValue1;
inbetween = PressureData;
}
public static void ChangeText(string text)
{
txtP.Text = text;
}
private async void InitializePressureSensor(GattDeviceService service)
{
pSensor = new PressureSensor(service, SensorUUIDs.PressureSensorUuid);
await pSensor.EnableNotifications();
btnStart.IsEnabled = true;
}
private async void StartRecievingData()
{
try
{
leDevice = await BluetoothLEDevice.FromIdAsync(device.Id);
string selector = "(System.DeviceInterface.Bluetooth.DeviceAddress:=\"" +
leDevice.BluetoothAddress.ToString("X") + "\")";
var services = await leDevice.GetGattServicesAsync(BluetoothCacheMode.Uncached);
foreach (var service in services.Services)
{
if (service.Uuid.ToString() == SensorUUIDs.ButtonSensorServiceUuid)
{
InitializePressureSensor(service);
}
}
timer.Interval = new TimeSpan(0, 0, 0, 0, 1);
timer.Tick += Timer_Tick1;
startTime = DateTime.Now;
timer.Start();
}
catch (Exception ex)
{
var messageDialog = new MessageDialog("An error has occured Please try again. \n" + ex.Message, "Error!");
}
}
public async void UpdateAllData()
{
while (pSensor != null && pSensor.MorePacketsAvailable)
{
int[] values = await pSensor.GetPressure();
int packetNumber = values[0];
if (packetNumber > packetNum)
{
packetNum = packetNumber;
txtValue1.Text = Convert.ToString(values[1]);
txtValue2.Text = Convert.ToString(values[5]);
for (int i = 1; i < 5; i++)
{
PressureData.Add(new DataPoint(DateTime.Now - startTime, packetNumber, ((i-1)*2.5 + 10*packetNumber), values[i], values[i + 4]));
}
}
}
}
private void Timer_Tick1(object sender, object e)
{
UpdateAllData();
}
private async void PairToDevice()
{
if (device.Pairing.CanPair)
{
var customPairing = device.Pairing.Custom;
customPairing.PairingRequested += CustomPairing_PairingRequested;
var result = await customPairing.PairAsync(DevicePairingKinds.ConfirmOnly);
customPairing.PairingRequested -= CustomPairing_PairingRequested;
if ((result.Status == DevicePairingResultStatus.Paired) || (result.Status == DevicePairingResultStatus.AlreadyPaired))
{
/*while (device.Pairing.IsPaired == false)
{
device = await DeviceInformation.CreateFromIdAsync(device.Id);
}*/
StartRecievingData();
}
}
else if (device.Pairing.IsPaired)
{
StartRecievingData();
}
}
private void CustomPairing_PairingRequested(DeviceInformationCustomPairing sender, DevicePairingRequestedEventArgs args)
{
args.Accept();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
btnSave.Content = "Save";
btnStop.IsEnabled = false;
btnStart.IsEnabled = false;
this.DataContext = PressureData;
device = (DeviceInformation)e.Parameter;
PairToDevice();
//StartRecievingData();
base.OnNavigatedTo(e);
Frame rootFrame = Window.Current.Content as Frame;
if (rootFrame.CanGoBack)
{
SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility =
AppViewBackButtonVisibility.Visible;
}
}
private void DevicePage_BackRequested(object sender, BackRequestedEventArgs eventArgs)
{
Frame rootFrame = Window.Current.Content as Frame;
if (rootFrame == null)
{
return;
}
// Navigate back if possible, and if the event has already been handled
if (rootFrame.CanGoBack && eventArgs.Handled ==false)
{
eventArgs.Handled = true;
rootFrame.GoBack();
}
}
private async void btnSave_Click(object sender, RoutedEventArgs e)
{
timer.Stop();
var picker = new FileSavePicker();
picker.SuggestedStartLocation = PickerLocationId.DocumentsLibrary;
picker.FileTypeChoices.Add("CSV", new List<string>() { ".csv" });
StorageFile file = await picker.PickSaveFileAsync();
if (file != null)
{
var stream = await file.OpenAsync(FileAccessMode.ReadWrite);
using (IOutputStream outputStream = stream.GetOutputStreamAt(0))
{
using (var writer = new DataWriter(outputStream))
{
foreach (DataPoint p in PressureData)
{
string text = p.TimeStamp.ToString() + "," + p.PacketNumber.ToString() + "," + p.InternalTimestamp.ToString() + "," + p.PressureValue1.ToString() + "," + p.PressureValue2.ToString() + "\n";
writer.WriteString(text);
}
await writer.StoreAsync();
await writer.FlushAsync();
}
}
stream.Dispose();
}
}
private async void btnStart_Click(object sender, RoutedEventArgs e)
{
if (pSensor != null)
{
btnStop.IsEnabled = true;
btnStart.IsEnabled = false;
startTime = DateTime.Now;
if (pSensor != null)
{
await pSensor.BeginCollecting();
}
}
}
private async void btnStop_Click(object sender, RoutedEventArgs e)
{
btnStart.IsEnabled = true;
btnStop.IsEnabled = false;
if (pSensor != null)
{
await pSensor.StopCollecting();
}
}
}
}
Here is where I define my SensorBase and PressureSensor class that handles the device connection:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.Devices.Bluetooth;
using Windows.Devices.Bluetooth.GenericAttributeProfile;
using Windows.Storage.Streams;
using Windows.Devices.Enumeration;
namespace BLEInterfaceTest
{
public static class SensorUUIDs
{
private static readonly string _packetUuid = "0000a043-0000-1000-8000-00805f9b34fb";
private static readonly string _buttonSensorServiceUuid = "0000a042-0000-1000-8000-00805f9b34fb";
private static readonly string _sensorStateUuid = "0000a044-0000-1000-8000-00805f9b34fb";
public static string PressureSensorUuid
{
get { return _packetUuid; }
}
public static string ButtonSensorServiceUuid
{
get { return _buttonSensorServiceUuid; }
}
public static string SensorStateUuid
{
get { return _sensorStateUuid; }
}
}
public class SensorBase : IDisposable
{
protected GattDeviceService deviceService;
protected string sensorDataUuid;
protected Queue<byte[]> fifoBuffer;
protected bool isNotificationSupported = false;
public bool newData = false;
private GattCharacteristic dataCharacteristic;
public SensorBase(GattDeviceService dataService, string sensorDataUuid)
{
this.deviceService = dataService;
this.sensorDataUuid = sensorDataUuid;
fifoBuffer = new Queue<byte[]>(20);
}
public bool MorePacketsAvailable
{
get
{
if (fifoBuffer.Count > 0)
{
return true;
}
else
{
return false;
}
}
}
public virtual async Task EnableNotifications()
{
GattCharacteristicsResult result = await deviceService.GetCharacteristicsAsync();
foreach (var test in result.Characteristics)
{
string t = test.Uuid.ToString();
}
isNotificationSupported = true;
dataCharacteristic = (await deviceService.GetCharacteristicsForUuidAsync(
new Guid(sensorDataUuid))).Characteristics[0];
dataCharacteristic.ValueChanged += dataCharacteristic_ValueChanged;
GattCommunicationStatus status = await dataCharacteristic.WriteClientCharacteristicConfigurationDescriptorAsync(
GattClientCharacteristicConfigurationDescriptorValue.Notify);
var currentDescriptorValue = await dataCharacteristic.ReadClientCharacteristicConfigurationDescriptorAsync();
if (currentDescriptorValue.Status != GattCommunicationStatus.Success
|| currentDescriptorValue.ClientCharacteristicConfigurationDescriptor != GattClientCharacteristicConfigurationDescriptorValue.Notify)
{
GattCommunicationStatus status2 = await dataCharacteristic.WriteClientCharacteristicConfigurationDescriptorAsync(
GattClientCharacteristicConfigurationDescriptorValue.Notify);
}
}
public virtual async Task DisableNotifications()
{
newData = false;
isNotificationSupported = false;
dataCharacteristic = (await deviceService.GetCharacteristicsForUuidAsync(
new Guid(sensorDataUuid))).Characteristics[0];
dataCharacteristic.ValueChanged -= dataCharacteristic_ValueChanged;
GattCommunicationStatus status = await dataCharacteristic.WriteClientCharacteristicConfigurationDescriptorAsync(GattClientCharacteristicConfigurationDescriptorValue.None);
}
protected async Task<byte[]> ReadValue()
{
if (!isNotificationSupported)
{
if (dataCharacteristic == null)
{
dataCharacteristic = (await deviceService.GetCharacteristicsForUuidAsync(
new Guid(sensorDataUuid))).Characteristics[0];
}
GattReadResult readResult = await dataCharacteristic.ReadValueAsync();
byte[] data = new byte[readResult.Value.Length];
DataReader.FromBuffer(readResult.Value).ReadBytes(data);
fifoBuffer.Enqueue(data);
}
return fifoBuffer.Dequeue();
}
protected async Task WriteByteArray(string characteristicUuid, byte[] value)
{
GattCharacteristic writeCharacteristic = (await deviceService.GetCharacteristicsForUuidAsync(
new Guid(characteristicUuid))).Characteristics[0];
var writer = new DataWriter();
writer.WriteBytes(value);
var res = await writeCharacteristic.WriteValueAsync(writer.DetachBuffer(), GattWriteOption.WriteWithoutResponse);
}
private void dataCharacteristic_ValueChanged(GattCharacteristic sender, GattValueChangedEventArgs args)
{
byte[] data = new byte[args.CharacteristicValue.Length];
DataReader.FromBuffer(args.CharacteristicValue).ReadBytes(data);
fifoBuffer.Enqueue(data);
newData = true;
}
public async void Dispose()
{
await DisableNotifications();
}
}
public class PressureSensor: SensorBase
{
public PressureSensor(GattDeviceService dataService, string sensorDataUuid)
: base(dataService, sensorDataUuid)
{
}
public async Task BeginCollecting()
{
await WriteByteArray(SensorUUIDs.SensorStateUuid, new byte[] { 0x01 });
}
public async Task<int[]> GetPressure()
{
byte[] data = await ReadValue();
if (data != null)
{
int[] values = new int[9];
values[0] = (int)BitConverter.ToInt32(data, 0);
for (int i = 1; i < values.Length; i++)
{
values[i] = (int)BitConverter.ToInt16(data, 2 * i + 2);
}
return values;
}
else
{
return new int[] { 0 };
}
}
public async Task StopCollecting()
{
await WriteByteArray(SensorUUIDs.SensorStateUuid, new byte[] { 0x00 });
}
}
}
Here is the DataPoint Class that I use to organize the data received from the pressure sensor:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace BLEInterfaceTest
{
public class DataPoint : INotifyPropertyChanged
{
private TimeSpan _timestamp;
private int _packetNumber;
private double _internalTimestamp;
private int _pressure1;
private int _pressure2;
public event PropertyChangedEventHandler PropertyChanged;
public TimeSpan TimeStamp
{
get { return _timestamp; }
set
{
_timestamp = value;
this.NotifyPropertyChanged();
}
}
public int PacketNumber
{
get { return _packetNumber; }
set
{
_packetNumber = value;
this.NotifyPropertyChanged();
}
}
public double InternalTimestamp
{
get { return _internalTimestamp; }
set
{
_internalTimestamp = value;
this.NotifyPropertyChanged();
}
}
public int PressureValue1
{
get { return _pressure1; }
set
{
_pressure1 = value;
this.NotifyPropertyChanged();
}
}
public int PressureValue2
{
get { return _pressure2; }
set
{
_pressure2 = value;
this.NotifyPropertyChanged();
}
}
public DataPoint(TimeSpan time,int packetNumber, double internalTimestamp, int pressure1, int pressure2)
{
_timestamp = time;
_packetNumber = packetNumber;
_internalTimestamp = internalTimestamp;
_pressure1 = pressure1;
_pressure2 = pressure2;
}
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
if (!string.IsNullOrEmpty(propertyName))
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
I have researched this extensively, and all I could find was help on how to initiate a disconnection. I have the opposite problem. One page I found stated that the problem might be caused by the device not properly storing the bonding state, but I have checked that and I did initialize the device to save the bonding state.
Interestingly if I do not pair the device to the computer before trying to read information from it then I do not have the problem. The connection never randomly stops. But when I do this, the computer does not receive every packet of data sent from the sensor device. It will receive one or two packets and then skip five or six packets. If I pair the device then I will receive every packet but the connection will randomly cut off.
So my question is two fold, I guess. How do I stop the connection from cutting off when the device is paired? Or alternatively, is there a way to allow the application to receive every packet of data when it is not paired?
UPDATE
I realized I should include more information on my sensor peripheral in case the error is in that code. I am currently designing a rapid prototyping of this sensor before I move on to designing the embedded version. To do this, I am using the BLE Nano 1 from RedBearLabs as a user friendly prototype. I am programing this device with the online MBED compiler. I have included the nRF51822 and BLE_API libraries to handle the bluetooth low energy communication.
UPDATE 2
So after more research into what is causing the problem, I have found that the disconnection occurs when a connection interval and a generation 2 garbage collection occur at the same time. In UWP the garbage collector can pause the UI Thread for generation 2 collections. (see here)
My thought is that if the thread is paused at the beginning of a connection interval, then the central is not able to initiate the connection with the peripheral and the peripheral therefore thinks the client is no longer listening (see more about how BLE connections work).
I discovered this by finding out exactly what is necessary to get the connection back once it has randomly stopped. I started with the entire connection process and reduced it down to this:
public async Task ReconnectDevice()
{
GattCommunicationStatus status = await dataCharacteristic.WriteClientCharacteristicConfigurationDescriptorAsync(
GattClientCharacteristicConfigurationDescriptorValue.Notify);
await WriteByteArray(SensorUUIDs.SensorStateUuid, new byte[] { 0x01 });
}
Because my BluetoothLEDevice, GattService, and GattCharacteristic objects are not disposed, all I need to do is resubscribe to notifications and write a 1 to the device so that it begins collecting data again.
I have reduced my memory allocations in my application significantly since discovering this, and the time for a gen2 collection has decreased to an average of 5 ms. Also, the amount of time before the connection disconnects has increased to around 4-5 sec.
UWP has a GattCharacteristicNotificationTrigger for receiving notifications in a BackgroundTask, but I have never had much success at incorporating background tasks in UWP.
I think I will try next to incorporate the windows.devices into a WPF application where I think I will have a better chance at getting it working.
So, after a while of trying different ideas I have finally stumbled across a solution to my problem. I had to make 2 changes:
Used the unpaired connection instead of the paired connection. This solved the problem of the connection dropping suddenly.
Increased the connection interval to 40 ms. For some reason when I did this, I received all of the data and no longer had any problems. Anything below 40 ms causes information to be lost when communicating to a Windows device (I had to make this change on the C code running on my sensors.)
I have used the devices for about 2 months now after making this change and have had no problems at all.
I seems to me that these problems are related to the BluetoothCacheMode Enum.
This indicates whether certain Bluetooth API methods should operate on values cached in the system or
retrieve those values from the Bluetooth device.
Using BluetoothCacheMode.Uncached attribute allows the service to update the attributes when needed.
If the device is paired then the BluetoothCacheMode is not needed(I think BluetoothCacheMode.Cached is default).
In your code the line:
var services = await leDevice.GetGattServicesAsync(BluetoothCacheMode.Uncached);
Can be the cause of the connection lost if paired.
GetGattServicesAsync(), GetCharacteristicsAsync() and ReadValueAsync()
must have the attribute BluetoothCacheMode.Uncached when not paired, when paired default or BluetoothCacheMode.Cached.
See https://msdn.microsoft.com/en-us/library/windows/apps/dn263758.aspx.
I am trying to use a WPF application in C# to display a RTF document which contains some relative links. To process the links in the document I found the following code online. I have verified that a link to the URL www.google.com works.
Now the problem is the RTF contains a few links to other pages, such as a link to the bookmark "Pag2". How do i get those to work?
//Code to process the links:
using System.Windows.Documents;
using System.Collections.Generic;
using System.Windows;
using System.Linq;
using System.Diagnostics;
#region Activate Hyperlinks in the Rich Text box
static class Hyperlinks
{
//http://stackoverflow.com/questions/5465667/handle-all-hyperlinks-mouseenter-event-in-a-loaded-loose-flowdocument
public static void SubscribeToAllHyperlinks(FlowDocument flowDocument)
{
var hyperlinks = GetVisuals(flowDocument).OfType<Hyperlink>();
foreach (var link in hyperlinks)
link.RequestNavigate += new System.Windows.Navigation.RequestNavigateEventHandler(link_RequestNavigate);
}
public static IEnumerable<DependencyObject> GetVisuals(DependencyObject root)
{
foreach (var child in LogicalTreeHelper.GetChildren(root).OfType<DependencyObject>())
{
yield return child;
foreach (var descendants in GetVisuals(child))
yield return descendants;
}
}
public static void link_RequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e)
{
//http://stackoverflow.com/questions/2288999/how-can-i-get-a-flowdocument-hyperlink-to-launch-browser-and-go-to-url-in-a-wpf
if (e.Uri.IsAbsoluteUri)
{
Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri));
}
else
{
// code to navigate to relative uri such as #Page2??
}
e.Handled = true;
}
}
#endregion Activate Hyperlinks in the Rich Text box
//Calling code:
class FlowDocTest_VM
{
public FlowDocTest_VM()
{
string filename = "D:\\Manual.rtf";
fdContent = new FlowDocument();
FileStream fsHelpfile = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read);
TextRange trContent = new TextRange(fdContent.ContentStart, fdContent.ContentEnd);
trContent.Load(fsHelpfile, DataFormats.Rtf);
Hyperlinks.SubscribeToAllHyperlinks(fdContent);
}
public FlowDocument Document
{
get
{
return fdContent;
}
set
{
fdContent = value;
}
}
private FlowDocument fdContent;
}
I am trying to apply saturation video effect implemented with IBasicVideoEffect according to https://github.com/aarononeal/media-contrib example.
When I add VideoEffectDefinition to MediaClip, preview video is set, but nothing is rendered. (video disappears)
private void AddSaturationEffectButton_Click(object sender, RoutedEventArgs e)
{
var clip = _composition.Clips[0];
clip.VideoEffectDefinitions.Add(new VideoEffectDefinition(typeof(SaturationVideoEffect).FullName));
SetupMediaStreamSource();
}
SaturationVideoEffect is implemented in separated Windows Runtime Component (Universal Windows) project.
using System;
using System.Collections.Generic;
using Windows.Foundation.Collections;
using Windows.Graphics.DirectX.Direct3D11;
using Windows.Media.Effects;
using Windows.Media.MediaProperties;
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Effects;
namespace VideoEffectsLibrary
{
public sealed class SaturationVideoEffect : IBasicVideoEffect
{
private VideoEncodingProperties _currentEncodingProperties;
private CanvasDevice _canvasDevice;
private IPropertySet _configuration;
private float Saturation
{
get
{
if (_configuration != null && _configuration.ContainsKey("Saturation"))
return (float)_configuration["Saturation"];
else
return 0.5f;
}
set
{
_configuration["Saturation"] = value;
}
}
public void ProcessFrame(ProcessVideoFrameContext context)
{
using (CanvasBitmap inputBitmap = CanvasBitmap.CreateFromDirect3D11Surface(_canvasDevice, context.InputFrame.Direct3DSurface))
using (CanvasRenderTarget renderTarget = CanvasRenderTarget.CreateFromDirect3D11Surface(_canvasDevice, context.OutputFrame.Direct3DSurface))
using (CanvasDrawingSession ds = renderTarget.CreateDrawingSession())
{
var saturation = new SaturationEffect()
{
Source = inputBitmap,
Saturation = this.Saturation
};
ds.DrawImage(saturation);
}
}
public void SetEncodingProperties(VideoEncodingProperties encodingProperties, IDirect3DDevice device)
{
_currentEncodingProperties = encodingProperties;
_canvasDevice = CanvasDevice.CreateFromDirect3D11Device(device);
CanvasDevice.DebugLevel = CanvasDebugLevel.Error;
}
public void SetProperties(IPropertySet configuration)
{
_configuration = configuration;
}
public bool IsReadOnly { get { return false; } }
public MediaMemoryTypes SupportedMemoryTypes { get { return MediaMemoryTypes.Gpu; } }
public bool TimeIndependent { get { return false; } }
public IReadOnlyList<VideoEncodingProperties> SupportedEncodingProperties
{
get
{
return new List<VideoEncodingProperties>()
{
// NOTE: Specifying width and height is only necessary due to bug in media pipeline when
// effect is being used with Media Capture.
// This can be changed to "0, 0" in a future release of FBL_IMPRESSIVE.
VideoEncodingProperties.CreateUncompressed(MediaEncodingSubtypes.Argb32, 800, 600)
};
}
}
public void Close(MediaEffectClosedReason reason)
{
// Clean up devices
if (_canvasDevice != null)
_canvasDevice.Dispose();
}
public void DiscardQueuedFrames()
{
// No cached frames to discard
}
}
}
How can I make this effect run properly? Thanks for help.
i am currently learning on QR code webcam decoder. i have taken an example from https://zxingnet.svn.codeplex.com/svn/trunk/Clients/AForgeDemo/ and have succesfully build it without error. however when i run it with my webcam connected, no input or it wont switch on the webcam. based on my understanding, the webcam would be switch on when user select it at combobox. well, since there is no error at build, i cant pinpoint what went wrong. i have also taken a look at a project which switch on the webcam when user press a button and i plan to implement it to the current project. i have already insert the button but i do not know what should i program at the button to switch on the webcam instead on having to choose at combobox
would someone kindly advise or guide me through it.
below is the main program, and 2 class
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using AForge.Video;
using ZXing;
using System.Threading;
namespace AForgeDemo
{
public partial class AForgeDemoForm : Form
{
private struct Device
{
public int Index;
public string Name;
public override string ToString()
{
return Name;
}
}
private readonly CameraDevices camDevices;
private Bitmap currentBitmapForDecoding;
private readonly Thread decodingThread;
private Result currentResult;
private readonly Pen resultRectPen;
public AForgeDemoForm()
{
InitializeComponent();
camDevices = new CameraDevices();
decodingThread = new Thread(DecodeBarcode);
decodingThread.Start();
pictureBox1.Paint += pictureBox1_Paint;
resultRectPen = new Pen(Color.Green, 10);
}
void pictureBox1_Paint(object sender, PaintEventArgs e)
{
if (currentResult == null)
return;
if (currentResult.ResultPoints != null && currentResult.ResultPoints.Length > 0)
{
var resultPoints = currentResult.ResultPoints;
var rect = new Rectangle((int)resultPoints[0].X, (int)resultPoints[0].Y, 1, 1);
foreach (var point in resultPoints)
{
if (point.X < rect.Left)
rect = new Rectangle((int)point.X, rect.Y, rect.Width + rect.X - (int)point.X, rect.Height);
if (point.X > rect.Right)
rect = new Rectangle(rect.X, rect.Y, rect.Width + (int)point.X - rect.X, rect.Height);
if (point.Y < rect.Top)
rect = new Rectangle(rect.X, (int)point.Y, rect.Width, rect.Height + rect.Y - (int)point.Y);
if (point.Y > rect.Bottom)
rect = new Rectangle(rect.X, rect.Y, rect.Width, rect.Height + (int)point.Y - rect.Y);
}
using (var g = pictureBox1.CreateGraphics())
{
g.DrawRectangle(resultRectPen, rect);
}
}
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
LoadDevicesToCombobox();
}
protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
{
base.OnClosing(e);
if (!e.Cancel)
{
decodingThread.Abort();
if (camDevices.Current != null)
{
camDevices.Current.NewFrame -= Current_NewFrame;
if (camDevices.Current.IsRunning)
{
camDevices.Current.SignalToStop();
}
}
}
}
private void LoadDevicesToCombobox()
{
cmbDevice.Items.Clear();
for (var index = 0; index < camDevices.Devices.Count; index++)
{
cmbDevice.Items.Add(new Device { Index = index, Name = camDevices.Devices[index].Name });
}
}
private void cmbDevice_SelectedIndexChanged(object sender, EventArgs e)
{
if (camDevices.Current != null)
{
camDevices.Current.NewFrame -= Current_NewFrame;
if (camDevices.Current.IsRunning)
{
camDevices.Current.SignalToStop();
}
}
camDevices.SelectCamera(((Device)(cmbDevice.SelectedItem)).Index);
camDevices.Current.NewFrame += Current_NewFrame;
camDevices.Current.Start();
}
private void Current_NewFrame(object sender, NewFrameEventArgs eventArgs)
{
if (IsDisposed)
{
return;
}
try
{
if (currentBitmapForDecoding == null)
{
currentBitmapForDecoding = (Bitmap)eventArgs.Frame.Clone();
}
Invoke(new Action<Bitmap>(ShowFrame), eventArgs.Frame.Clone());
}
catch (ObjectDisposedException)
{
// not sure, why....
}
}
private void ShowFrame(Bitmap frame)
{
if (pictureBox1.Width < frame.Width)
{
pictureBox1.Width = frame.Width;
}
if (pictureBox1.Height < frame.Height)
{
pictureBox1.Height = frame.Height;
}
pictureBox1.Image = frame;
}
private void DecodeBarcode()
{
var reader = new BarcodeReader();
while (true)
{
if (currentBitmapForDecoding != null)
{
var result = reader.Decode(currentBitmapForDecoding);
if (result != null)
{
Invoke(new Action<Result>(ShowResult), result);
}
currentBitmapForDecoding.Dispose();
currentBitmapForDecoding = null;
}
Thread.Sleep(200);
}
}
private void ShowResult(Result result)
{
currentResult = result;
txtBarcodeFormat.Text = result.BarcodeFormat.ToString();
txtContent.Text = result.Text;
}
private void button1_Click(object sender, EventArgs e)
{
}
}
}
the class for cameradevice
using System.Collections.Generic;
using System.Linq;
using System.Text;
using AForge.Video.DirectShow;
namespace AForgeDemo
{
internal class CameraDevices
{
public FilterInfoCollection Devices { get; private set; }
public VideoCaptureDevice Current { get; private set; }
public CameraDevices()
{
Devices = new FilterInfoCollection(FilterCategory.VideoInputDevice);
}
public void SelectCamera(int index)
{
if (index >= Devices.Count)
{
throw new ArgumentOutOfRangeException("index");
}
Current = new VideoCaptureDevice(Devices[index].MonikerString);
}
}
}
again, i kindly ask for anybody help on what i shall put at the button command to activate the webcam directly instead of choosing from combobox
thanks a million
The code which you need for your event handler behind the button is similar to the code within the method cmbDevice_SelectedIndexChanged.
I think it should look like the following
// select the first available camera and start capturing
camDevices.SelectCamera(0);
camDevices.Current.NewFrame += Current_NewFrame;
camDevices.Current.Start();
But I think the main challenge is to find out why the original example doesn't work as expected. It makes no difference if the same code is called in the handler of the combobox or in the handler of the button.