I'm trying to setup a simple connection over SPI, but when trying to get a SpiDevice, the SpiDevice.FromIdAsync method never finishes. This happens almost always, with a few rare exceptions that seem random.
spi = await SpiDevice.FromIdAsync(dis[0].Id, settings);
The code used to setup the connection comes from https://learn.microsoft.com/en-us/windows/iot-core/learn-about-hardware/pinmappings/pinmappingsrpi#spi-bus.
In the example, I output a single byte to a LED driver working as a shift register. I use a Raspberry Pi 3 as target device and Visual Studio Community 2017 as my IDE.
MainPage.xaml.cs
using System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.Devices.Gpio;
using Windows.Devices.Spi;
using Windows.Devices.Enumeration;
using System.Threading.Tasks;
namespace SPI_Test
{
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
try
{
InitGPIO();
InitSpi().Wait();
timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromMilliseconds(500);
timer.Tick += Timer_Tick;
}
catch (Exception)
{
}
}
GpioPin enablePin;
SpiDevice spi;
private void InitGPIO()
{
var gpio = GpioController.GetDefault();
//create pin to control the output of the LED driver
//the outputs are active when the pin is low
enablePin = gpio.OpenPin(12);
enablePin.Write(GpioPinValue.High); //disable outputs at first
enablePin.SetDriveMode(GpioPinDriveMode.Output);
}
private async Task InitSpi()
{
try
{
// Use chip select line CS0
var settings = new SpiConnectionSettings(0);
// Set clock to 10MHz
settings.ClockFrequency = 10000000;
// Get a selector string that will return our wanted SPI controller
string aqs = SpiDevice.GetDeviceSelector("SPI0");
// Find the SPI bus controller devices with our selector string
var dis = await DeviceInformation.FindAllAsync(aqs);
spi = await SpiDevice.FromIdAsync(dis[0].Id, settings); /* Create an SpiDevice with our bus controller and SPI settings */
}
/* If initialization fails, display the exception and stop running */
catch (Exception ex)
{
throw new Exception("SPI Initialization Failed", ex);
}
}
private DispatcherTimer timer;
private byte value = 0;
private void Timer_Tick(object sender, object e)
{
enablePin.Write(GpioPinValue.High); //disable outputs
spi.Write(new byte[] { value++ }); //send the current value to the LEDs and increase by 1
enablePin.Write(GpioPinValue.Low); //re-enable outputs
}
private void Button_Click(object sender, RoutedEventArgs e)
{
timer.Start();
}
}
}
MainPage.xaml
<Page
x:Class="SPI_Test.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:SPI_Test"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<Button Click="Button_Click" Content="Start" Margin="15" />
</StackPanel>
</Grid>
</Page>
You are experiencing a deadlock because of the mixing of blocking calls .Wait() and asynchronous calls. I would suggest moving that logic to an event handler. Like Loaded
public MainPage() {
this.InitializeComponent();
this.Loaded += async (sender, e) => {
try {
InitGPIO();
await InitSpi();
timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromMilliseconds(500);
timer.Tick += Timer_Tick;
} catch (Exception) {
}
};
}
Related
So basically, I m making a program where 25 tasks write the same random task to one file until it reaches a certain size, and I would like to dynamically display the file size of the selected file in 0.5 seconds interval, so I made a timer hooked with Timer_Elapsed which should be executed every 0.5 seconds and display on UI which I specify on mainWindow.xaml on the textblock x:Name="fileSize", so I placed the createTimer function in to btnGo_Click function in mainwindow.xaml.cs so the event would extract the right fileInfo of the selectedFile. Any advice for my wrong would be appreciated. I'm also sharing the FileIO class in case it is needed, so they are full solutions. Even aside from the questions I asked, any general advice to better my code would be appreciated because I need to get a grasp of the good code example.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Windows;
using System.Threading;
namespace WriteFile
{
internal class FileIO
{
private object _lock = new object();
public volatile bool sizeReached = false;
private StreamWriter sw = null;
Mutex mut = null;
public FileIO()
{
if (!Mutex.TryOpenExisting("MyMutex", out mut))
{
mut = new Mutex(true, "MyMutex");
mut.ReleaseMutex();
}
}
internal void WriteFile(string FilePath)
{
while (!sizeReached)
{
mut.WaitOne();
try
{
using (sw = new StreamWriter(FilePath, true))
{
sw.WriteLine(Guid.NewGuid());
}
}
catch (Exception ex)
{
MessageBox.Show("Exception: " + ex.Message);
}
finally
{
if (sw != null)
{
sw.Close();
}
}
mut.ReleaseMutex();
}
}
internal void SizeMonitor(string FPath, int MaxSize, Task[] tasks)
{
FileInfo fi = null;
while (!sizeReached)
{
if (File.Exists(FPath))
{
fi = new FileInfo(FPath);
if (fi.Length >= MaxSize)
{
sizeReached = true;
}
}
if (sizeReached)
{
foreach (Task task in tasks)
{
task.Wait();
}
}
Thread.Sleep(1);
}
MessageBox.Show(fi.Length.ToString());
MessageBox.Show("Done");
}
}
}
mainWindow.xaml
<Window x:Class="WriteFile.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:local="clr-namespace:WriteFile"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<TextBlock x:Name="fileSize"/>
<TextBox Name ="TargetSize" VerticalAlignment="Center" FontSize="20">
</TextBox>
<Label Content="Target Size" HorizontalAlignment="Left" Margin="92,150,0,0" VerticalAlignment="Top"/>
<Button Name ="btnGo" Content="Write to file" HorizontalAlignment="Left" Margin="92,267,0,0" VerticalAlignment="Top" Width="100" Click="btnGo_Click"/>
</Grid>
</Window>
mainWindow.xaml.cs
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.Navigation;
using System.Windows.Shapes;
using System.IO;
using Microsoft.Win32;
using System.ComponentModel;
using System.Timers;
using System.Threading;
using System.Runtime.CompilerServices;
namespace WriteFile
{
public partial class MainWindow : Window
{
System.Timers.Timer timer = new System.Timers.Timer();
Task[] tasks;
Task MonitorTask;
static FileIO fio = new FileIO();
static string fPath;
static FileInfo fileInfo;
public MainWindow()
{
InitializeComponent();
CreateTimer();
}
public void CreateTimer()
{
var timer = new System.Timers.Timer(500); // fire every 0.5 second
timer.Enabled = true;
timer.Elapsed += Timer_Elapsed;
}
private void Timer_Elapsed(object sender, ElapsedEventArgs e)
{
fileSize.Text = fileInfo.Length.ToString();
}
private void btnGo_Click(object sender, RoutedEventArgs e)
{
SaveFileDialog saveFileDialog = new SaveFileDialog();
saveFileDialog.ShowDialog();
Stream myStream;
saveFileDialog.FilterIndex = 2;
saveFileDialog.RestoreDirectory = true;
if (File.Exists(saveFileDialog.FileName))
{
File.Delete(saveFileDialog.FileName);
}
if ((myStream = saveFileDialog.OpenFile()) != null)
{
StreamWriter sw = new StreamWriter(myStream);
sw.Write(" your text");
myStream.Close();
}
int NoOfTasks = 25;
int Target = Convert.ToInt32(TargetSize.Text);
fPath = saveFileDialog.FileName;
tasks = new Task[NoOfTasks];
fio.sizeReached = false;
fileInfo = new FileInfo(fPath);
for (int i = 0; i < NoOfTasks; i++)
{
tasks[i] = new Task(() => fio.WriteFile(fPath));
tasks[i].Start();
}
MonitorTask = new Task(() => fio.SizeMonitor(fPath, Target, tasks));
MonitorTask.Start();
}
}
}
As mentioned by others, FileInfo.Length value is pre-cached. If the associated file has changed, the FileInfo requires to refresh those cached values. To accomplish this, you must explicitly call the FileInfo.Refresh method. FileInfo does not monitor the associated file.
Furthermore, System.Threading.Timer executes the callback on a background thread. In WPF you can only reference a DispatcherObject (for example TextBlock) from a dispatcher thread (UI thread). For this reason you should use the DispatcherTimer. DispatcherTimer executes the callback on the dispatcher thread.
Additionally, you would want to stop the timer once the writing to the file is completed.
You should not use Task.Start. Instead of Task.Start and a Mutex you should simply await the Task. In your context, you can await a collection of Task objects using Task.WhenALl.
And instead of creating Task explicitly, you should use the async API of the StreamWriter.
You actually don't have concurrent code (neither would you benefit from it): the Mutex, declaration of a volatile field and the lock object are redundant. Also closing the StreamWriter explicitly in a finally block is not needed as you already use a using-block to handle the lifetime of the instance.
There are some more flaws in your code (some logical issues for instance). I have refactored your version to eliminate some of the issues for example by using the asynchronous StreamWriter API and async/await.
Because it is difficult to make any sense of your code (due to the lack of context) it is not possible to further improve it. For example, creating a single concatenated string (consisting of e.g. 25 values) would result in a single file write (and resource allocation), which would significantly improve the overall performance.
FileIO.cs
namespace WriteFile
{
internal class FileIO
{
public bool IsSizeReached { get; private set; }
public FileIO()
{
}
internal async Task WriteToFileAsync(string filePath)
{
if (this.IsSizeReached)
{
return;
}
using (var sw = new StreamWriter(filePath, true))
{
await sw.WriteLineAsync(Guid.NewGuid());
}
}
internal async Task SizeMonitorAsync(string fPath, int maxSize, IEnumerable<Task> tasks)
{
FileInfo fi = new FileInfo(fPath);
this.IsSizeReached = fi.Length >= maxSize;
if (this.IsSizeReached)
{
return;
}
await Task.WhenAll(tasks);
MessageBox.Show(fi.Length.ToString());
MessageBox.Show("Done");
}
}
}
MainWindow.xaml.cs
namespace WriteFile
{
public partial class MainWindow : Window
{
private DispatcherTimer Timer { get; }
private FileIO Fio { get; }
private FileInfo DestinationFileInfo { get; }
public MainWindow()
{
InitializeComponent();
this.Fio = new FileIO();
}
public void StartTimer()
{
this.Timer = new DispatcherTimer(
TimeSpan.FromMilliseconds(500),
DispatcherPriority.Background,
OnTimerElapsed,
this.Dispatcher);
this.Timer.Start();
}
public void StopTimer() => this.Timer.Stop();
private void OnTimerElapsed(object sender, EventArgs e)
{
this.fileSize.Text = this.DestinationFileInfo.Length.ToString();
}
private async void btnGo_Click(object sender, RoutedEventArgs e)
{
SaveFileDialog saveFileDialog = new SaveFileDialog();
saveFileDialog.ShowDialog();
saveFileDialog.FilterIndex = 2;
saveFileDialog.RestoreDirectory = true;
if (File.Exists(saveFileDialog.FileName))
{
File.Delete(saveFileDialog.FileName);
}
Stream myStream;
if ((myStream = saveFileDialog.OpenFile()) != null)
{
// Dispose StreamWriter and underlying Stream
using (var sw = new StreamWriter(myStream))
{
await sw.WriteAsync(" your text");
}
}
int noOfTasks = 25;
// TODO::You must validate the input to prevent invalid or unreasonable values.
// Currently, this line will throw and crash the application if the input is invalid (not numeric).
int target = Convert.ToInt32(TargetSize.Text);
string fPath = saveFileDialog.FileName;
var tasks = new List<Task>();
this.DestinationFileInfo = new FileInfo(fPath);
StartTimer();
for (int i = 0; i < noOfTasks; i++)
{
Task writeToFileTask = this.Fio.WriteToFileAsync(fPath);
tasks.Add(writeToFileTask);
}
await this.Fio.SizeMonitorAsync(fPath, target, tasks));
StopTimer();
}
}
}
Timers.Timer works asynchronously and it raises the Elapsed event on the pool thread if SynchronizingObject is not set.
And work with WPF UI elements can only be done on the thread of their Dispatcher (almost always this is the main UI thread of the application).
Two solutions out of many possible:
private void Timer_Elapsed(object sender, ElapsedEventArgs e)
{
// Asynchronously getting the right data
string text = fileInfo.Length.ToString();
// Using the Window's Dispatcher
// to setting ready-made data to UI elements.
Dispatcher.BeginInvoke(() => fileSize.Text = text);
}
The second option is without using a timer:
public MainWindow()
{
Initialized += RefreshAsync;
InitializeComponent();
}
public async void RefreshAsync(object? sender, EventArgs e)
{
while(true)
{
string text = await Task.Run(() => fileInfo.Length.ToString());
fileSize.Text = text;
await Task.Delay(500);
}
}
My CaptureElements are showing strange behavior. When I set a instantiated MediaCapture as the CaptureElements Source and then call MediaCapture.StartPreviewAsync() the CaptureElement doesn't show anything.
I have one Application (main-app) with a functional BarcodeScanner on the LoginPage.
-> Works!
Then I wanted to copy the same code to the SettingsPage with small modifications so in case of several attached cameras, the default one can be set.
-> Doesn't work
Then I tried to run the main-app with the help of the remote debugger on other windows tablets with same Windows 10 Version as my machine (keep in mind, that the BarcodeScanner on the Login-Screen works on my machine).
-> doesn't work
Because of these failures I copied the running code from the main-apps LoginPage to a completely new solution (lets call it test-app) with the same settings as the original one. I even experimented with referencing the same Dlls, implementing the same design pattern etc.
-> doesn't work
My Machine:
Win 10 Pro
Version 1809
Build 17763.652
DevEnv:
MS Visual Studio 2019 Pro
Vers. 16.1.6
EDIT: As minimum required Windows Version I selected Build 16229 and
my target Version is Build 17763 (my systems Win version)
The "Allow apps to access your camera"-Option in the Widows Settings is switched to ON, so all apps are allowed to access the camera.
Xaml
<Page
x:Class="QrCodeTest.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:QrCodeTest"
xmlns:vm="using:QrCodeTest.ViewModels"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Page.DataContext>
<vm:TestViewModel x:Name="ViewModel" />
</Page.DataContext>
<ScrollViewer>
<StackPanel>
<Button Content="Start Preview" HorizontalAlignment="Center" Click="Button_Click" Margin="5" />
<CaptureElement x:Name="capturePreview" HorizontalAlignment="Center" Stretch="Uniform" Width="0" Height="0" Margin="10" />
<Button Content="Stop Preview" HorizontalAlignment="Center" Click="Button_Click_1" Margin="5" />
<TextBlock Text="{Binding Etikett, Mode=TwoWay}" HorizontalAlignment="Center" Margin="5" />
</StackPanel>
</ScrollViewer>
</Page>
CodeBehind
private BarcodeScanner scanner { get; set; }
private ClaimedBarcodeScanner claimedScanner { get; set; }
private MediaCapture captureManager { get; set; }
internal async Task StartScannerAsync () {
capturePreview.Visibility = Visibility.Visible;
capturePreview.Width = 400; capturePreview.Height = 300;
scanner = null;
scanner = await DeviceHelpers.GetFirstDeviceAsync(BarcodeScanner.GetDeviceSelector(connectionTypes), async (id) => await BarcodeScanner.FromIdAsync(id));
if (scanner != null) {
captureManager = new MediaCapture();
claimedScanner = await scanner.ClaimScannerAsync();
if (claimedScanner != null) {
claimedScanner.ReleaseDeviceRequested += claimedScanner_ReleaseDeviceRequested;
claimedScanner.DataReceived += claimedScanner_DataReceived;
claimedScanner.IsDecodeDataEnabled = true;
IReadOnlyList<uint> supportedSymbologies = await scanner.GetSupportedSymbologiesAsync();
foreach (uint symbology in supportedSymbologies) {
listOfSymbologies.Add(new SymbologyListEntry(symbology));
}
await claimedScanner.EnableAsync();
MediaCaptureInitializationSettings _captureInitSettings = new MediaCaptureInitializationSettings {
VideoDeviceId = scanner.VideoDeviceId,
StreamingCaptureMode = StreamingCaptureMode.AudioAndVideo,
PhotoCaptureSource = PhotoCaptureSource.VideoPreview
};
await captureManager.InitializeAsync(_captureInitSettings);
capturePreview.Source = captureManager;
try {
// Change to false, in case you wanna compare different methods of doing the same
bool Like_MP_PAT_UWP = false;
if (Like_MP_PAT_UWP) {
await capturePreview.Source.StartPreviewAsync();
await claimedScanner.StartSoftwareTriggerAsync();
} else {
LocalDataContext.Etikett = "await captureManager.StartPreviewAsync();";
await captureManager.StartPreviewAsync();
await claimedScanner.StartSoftwareTriggerAsync();
Thread.Sleep(2000);
await claimedScanner.StopSoftwareTriggerAsync();
await captureManager.StopPreviewAsync();
LocalDataContext.Etikett = "await capturePreview.Source.StartPreviewAsync();";
await capturePreview.Source.StartPreviewAsync();
await claimedScanner.StartSoftwareTriggerAsync();
Thread.Sleep(2000);
await claimedScanner.StopSoftwareTriggerAsync();
await capturePreview.Source.StopPreviewAsync();
LocalDataContext.Etikett = "await claimedScanner.ShowVideoPreviewAsync();";
await claimedScanner.ShowVideoPreviewAsync();
await claimedScanner.StartSoftwareTriggerAsync();
Thread.Sleep(2000);
await claimedScanner.StopSoftwareTriggerAsync();
claimedScanner.HideVideoPreview();
}
} catch (Exception e) {
Exception x = e; displayRequest.RequestRelease();
} finally {
LocalDataContext.Etikett = string.Empty;
}
}
}
}
ViewModel:
public class TestViewModel: INotifyPropertyChanged {
public static TestViewModel Instance { get; set; }
private string _Etikett;
public string Etikett { get { return _Etikett; } set { _Etikett = value; NotifyPropertyChanged(); } }
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged ([CallerMemberName] String propertyName = "") {
//PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
if (PropertyChanged != null) {
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I've wasted already 4 working days just to compare the solutions, the codes etc. The above code was copied from the test-app but it's mostly identical to the one on the main-apps LoginPage (except for the "if (Like_MP_PAT_UWP) {...}".
Every hint is welcome.
Thank you in advance.
The Problem was Kaspersky Endpoint Security's "advanced Threat Protection/host intrusion prevention"-setting. It prevented ALL apps outside from our dev-harddrive (i. e. on our tablets or from our network-drive) to access the camera (Dev-Drive = "Trusted Zone").
It was necessary to reconfigure that feature in Kaspersky Endpoint Security for the whole environment (declare necessary locations/clients as a trusted zone).
Hope, this might help someone with a similar problem or at least give a hint to someone.
Just spit-balling here, but I'd suggest attempting to reduce your test to just code that controls the MediaCapture object as much as possible, as that seems to be the symptom where you describe the main issue.
After that, try lowering the SharingMode of the camera to just read-only, in case another app is using the camera with exclusive access. Also, you can reduce the pop up consent check to just the camera, and not have the microphone consent. Sometimes if you accidentally don't agree during the consent pop-up, the app will be denied access to the camera until you allow it again from the system settings (Settings -> Privacy -> Camera).
Below is a sub-optimal and boiled down version of what you described above, but all parts included. I tried to segregate your starting a barcode session from disposing things. Using the MS samples as a guide will be far more reliable than this one. Nonetheless, there's lots more trace points to add, but below has a few traces around where the MediaCapture fails, and other points in the barcode scanner enable section. Hope it helps.
using System;
using System.Collections.Generic;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.Devices.PointOfService;
using System.Threading.Tasks;
using Windows.Media.Capture;
using Windows.Devices.Enumeration;
using System.Diagnostics;
using Windows.Storage.Streams;
namespace StackOverflowQrTest
{
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
}
private async void Button_Click(object sender, RoutedEventArgs e)
{
if (claimedScanner == null)
{
await StartScannerAsync();
}
}
private async void Button_Click_1(object sender, RoutedEventArgs e)
{
await StopScannerAsync();
}
private BarcodeScanner scanner { get; set; }
private ClaimedBarcodeScanner claimedScanner { get; set; }
private MediaCapture captureManager { get; set; }
internal async Task StartScannerAsync()
{
capturePreview.Visibility = Visibility.Visible;
capturePreview.Width = 400; capturePreview.Height = 300;
scanner = await DeviceHelpers.GetFirstDeviceAsync(BarcodeScanner.GetDeviceSelector(), async (id) => await BarcodeScanner.FromIdAsync(id));
if (scanner != null)
{
claimedScanner = await scanner.ClaimScannerAsync();
if (claimedScanner != null)
{
claimedScanner.ReleaseDeviceRequested += ClaimedScanner_ReleaseDeviceRequested;
claimedScanner.DataReceived += ClaimedScanner_DataReceived;
claimedScanner.IsDecodeDataEnabled = true;
await claimedScanner.EnableAsync();
try
{
bool haveAssociatedCamera = !string.IsNullOrEmpty(scanner.VideoDeviceId);
if (haveAssociatedCamera)
{
captureManager = new MediaCapture();
captureManager.Failed += CaptureManager_Failed;
MediaCaptureInitializationSettings _captureInitSettings = new MediaCaptureInitializationSettings
{
VideoDeviceId = scanner.VideoDeviceId,
SharingMode = MediaCaptureSharingMode.SharedReadOnly, // share
StreamingCaptureMode = StreamingCaptureMode.Video // just video
};
await captureManager.InitializeAsync(_captureInitSettings);
capturePreview.Source = captureManager;
}
UpdateMessage("waiting..." + (!haveAssociatedCamera ? "But scanner not camera type" : ""));
if (captureManager != null) await captureManager.StartPreviewAsync();
await claimedScanner.StartSoftwareTriggerAsync();
}
catch (Exception e)
{
UpdateMessage(e.Message);
Debug.WriteLine("EXCEPTION: " + e.Message);
}
}
else
{
UpdateMessage("Could not claim barcode scanner");
}
}
else
{
UpdateMessage("No barcode scanners found");
}
}
private void CaptureManager_Failed(MediaCapture sender, MediaCaptureFailedEventArgs errorEventArgs)
{
string msg = string.Format("MediaCapture_Failed: (0x{0:X}) {1}", errorEventArgs.Code, errorEventArgs.Message);
UpdateMessage(msg);
}
internal async Task StopScannerAsync()
{
if (captureManager != null)
{
if (captureManager.CameraStreamState == Windows.Media.Devices.CameraStreamState.Streaming)
{
await captureManager.StopPreviewAsync();
}
captureManager.Dispose();
captureManager = null;
}
if (claimedScanner != null)
{
claimedScanner.Dispose();
claimedScanner = null;
}
if (scanner != null)
{
scanner.Dispose();
scanner = null;
}
}
private void ClaimedScanner_DataReceived(ClaimedBarcodeScanner sender, BarcodeScannerDataReceivedEventArgs args)
{
var scanDataLabelReader = DataReader.FromBuffer(args.Report.ScanDataLabel);
string barcode = scanDataLabelReader.ReadString(args.Report.ScanDataLabel.Length);
UpdateMessage(barcode);
}
private void ClaimedScanner_ReleaseDeviceRequested(object sender, ClaimedBarcodeScanner e)
{
UpdateMessage("Another process is requesting barcode scanner device.");
e.RetainDevice();
}
private async void UpdateMessage (string message)
{
await LastBarcodeRead.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
LastBarcodeRead.Text = message;
});
}
}
public partial class DeviceHelpers
{
// We use a DeviceWatcher instead of DeviceInformation.FindAllAsync because
// the DeviceWatcher will let us see the devices as they are discovered,
// whereas FindAllAsync returns results only after discovery is complete.
public static async Task<T> GetFirstDeviceAsync<T>(string selector, Func<string, Task<T>> convertAsync)
where T : class
{
var completionSource = new TaskCompletionSource<T>();
var pendingTasks = new List<Task>();
DeviceWatcher watcher = DeviceInformation.CreateWatcher(selector);
watcher.Added += (DeviceWatcher sender, DeviceInformation device) =>
{
Func<string, Task> lambda = async (id) =>
{
T t = await convertAsync(id);
if (t != null)
{
completionSource.TrySetResult(t);
}
};
pendingTasks.Add(lambda(device.Id));
};
watcher.EnumerationCompleted += async (DeviceWatcher sender, object args) =>
{
// Wait for completion of all the tasks we created in our "Added" event handler.
await Task.WhenAll(pendingTasks);
// This sets the result to "null" if no task was able to produce a device.
completionSource.TrySetResult(null);
};
watcher.Start();
// Wait for enumeration to complete or for a device to be found.
T result = await completionSource.Task;
watcher.Stop();
return result;
}
}
}
Where main xaml is...
<Page
x:Class="StackOverflowQrTest.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:StackOverflowQrTest"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ScrollViewer>
<StackPanel>
<Button Content="Start Preview" HorizontalAlignment="Center" Click="Button_Click" Margin="5" />
<CaptureElement x:Name="capturePreview" HorizontalAlignment="Center" Stretch="Uniform" Width="0" Height="0" Margin="10" />
<Button Content="Stop Preview" HorizontalAlignment="Center" Click="Button_Click_1" Margin="5" />
<TextBox Header="LastBarcode" Name="LastBarcodeRead" IsReadOnly="True" HorizontalAlignment="Center" Margin="5" />
</StackPanel>
</ScrollViewer>
</Page>
I have two WPF applications (Application1 and Application2) and Application1 adds a user to a Users.xml file and Application2 displays all the Names from Users.xml to a Label. To refresh the Label in Application2, I have to press the Refresh button in my current implementation. I want to make a mechanism so that whenever I add a user with Application1, the Application2 automatically updates the Label. One way I could achieve this is whenever Application1 adds a user, it sets some flag in XML file (for example IsSync=false) and Application2 constantly monitor the flag and whenever it sees IsSync=false, it updates the Label and sets IsSync=true. But I would like to know if there are any other best ways (maybe publisher/subscriber way by handling the Refresh button of Application2 from Application1) to achieve this. Could you please help me to achieve this? I have attached both XAML/code below:
Application1
XAML
<Window x:Class="Application1.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:local="clr-namespace:Application1"
mc:Ignorable="d"
Title="Application1" Height="500" Width="500">
<Grid>
<Label Name="lblUserName" Content="Name" HorizontalAlignment="Left" Margin="42,60,0,0" VerticalAlignment="Top"/>
<TextBox Name="txtUserName" HorizontalAlignment="Left" Height="23" Margin="91,60,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="300"/>
<Button Name="btnAdd" Content="Add" HorizontalAlignment="Left" Margin="310,110,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
</Grid>
</Window>
Codebehind
using System.IO;
using System.Windows;
using System.Xml;
using System.Xml.Linq;
namespace Application1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private static string fullPath = "C:\\Files\\Users.xml";
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
if (!string.IsNullOrWhiteSpace(txtUserName.Text))
{
if (!File.Exists(fullPath))
{
// If Users.xml is not exists, create a file and add Textbox Name to the file
CreateUsersXMLAndAddUser();
txtUserName.Text = "";
}
else
{
// Add Textbox name to the Users.xml
AddUser();
txtUserName.Text = "";
}
}
else
{
MessageBox.Show(this, "Name can not be empty", "Required", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void AddUser()
{
XElement xEle = XElement.Load(fullPath);
xEle.Add(new XElement("User", new XAttribute("Name", txtUserName.Text.Trim())));
xEle.Save(fullPath);
}
private void CreateUsersXMLAndAddUser()
{
XDocument xDoc = new XDocument(
new XDeclaration("1.0", "UTF-8", null),
new XElement("Users",
new XElement("User",
new XAttribute("Name", txtUserName.Text.Trim())
)));
StringWriter sw = new StringWriter();
XmlWriter xWrite = XmlWriter.Create(sw);
xDoc.Save(xWrite);
xWrite.Close();
xDoc.Save(fullPath);
}
}
}
Application2
XAML
<Window x:Class="Application2.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:local="clr-namespace:Application2"
mc:Ignorable="d"
Title="Application2" Height="500" Width="600">
<Grid>
<Label Name="lblUsers" FontSize="20" FontWeight="UltraBold" HorizontalAlignment="Left" Margin="20,20,0,0" VerticalAlignment="Top"/>
<Button Name="btnRefreshUsers" Content="Refresh" FontSize="20" FontWeight="UltraBold" HorizontalAlignment="Left" Margin="450,39,0,0" VerticalAlignment="Top" Height="100" Width="100" Click="btnRefreshUsers_Click"/>
</Grid>
</Window>
Codebehind
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Windows;
using System.Xml.Linq;
namespace Application2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private static string fullPath = "C:\\Files\\Users.xml";
StringBuilder userList;
public MainWindow()
{
InitializeComponent();
userList = new StringBuilder();
lblUsers.Content = string.Empty;
lblUsers.Content = LoadUsers();
}
private StringBuilder LoadUsers()
{
userList.AppendLine(" Users ");
userList.AppendLine("---------");
if (File.Exists(fullPath))
{
XElement xelement = XElement.Load(fullPath);
IEnumerable<XElement> users = xelement.Elements();
foreach (var user in users)
{
userList.AppendLine(user.Attribute("Name").Value);
}
}
else
{
userList.AppendLine("Nothing to show ...");
}
return userList;
}
private void btnRefreshUsers_Click(object sender, RoutedEventArgs e)
{
userList.Clear();
lblUsers.Content = string.Empty;
lblUsers.Content = LoadUsers();
}
}
}
There are several ways to communicate between processes:
MSMQ (Microsoft MessageQueue)
Socket Programming
Named Pipeline
Web Services (WCF , ...)
Theoretically at the low level of communication most of this technologies are using sockets as the main part. so Socket Programming is the low level communication, you have more control and you need to do more to get this to work.
I have read a good answer on SO:
IPC Mechanisms in C# - Usage and Best Practices
I have implemented FileSystemWatcher to achieve my target. Here is changed the complete code in Application2 to listen to the changes in Users.xml.
Application2
Code behind
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Windows;
using System.Xml.Linq;
namespace Application2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private static string fullPath = "C:\\Files\\Users.xml";
StringBuilder userList;
public MainWindow()
{
InitializeComponent();
userList = new StringBuilder();
lblUsers.Content = string.Empty;
lblUsers.Content = LoadUsers();
CreateFileWatcher(#"C:\\Files");
}
private StringBuilder LoadUsers()
{
userList.AppendLine(" Users ");
userList.AppendLine("---------");
if (File.Exists(fullPath))
{
XElement xelement = XElement.Load(fullPath);
IEnumerable<XElement> users = xelement.Elements();
foreach (var user in users)
{
userList.AppendLine(user.Attribute("Name").Value);
}
}
else
{
userList.AppendLine("Nothing to show ...");
}
return userList;
}
private void btnRefreshUsers_Click(object sender, RoutedEventArgs e)
{
userList.Clear();
lblUsers.Content = string.Empty;
lblUsers.Content = LoadUsers();
}
private void CreateFileWatcher(string path)
{
// Create a new FileSystemWatcher and set its properties.
FileSystemWatcher watcher = new FileSystemWatcher();
watcher.Path = path;
/* Watch for changes in LastAccess and LastWrite times, and the renaming of files or directories. */
watcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite
| NotifyFilters.FileName | NotifyFilters.DirectoryName;
// Only watch text files.
watcher.Filter = "Users.xml";
// Add event handlers.
watcher.Changed += new FileSystemEventHandler(OnChanged);
watcher.Created += new FileSystemEventHandler(OnChanged);
watcher.Deleted += new FileSystemEventHandler(OnChanged);
// Begin watching.
watcher.EnableRaisingEvents = true;
}
// Define the event handlers.
private void OnChanged(object source, FileSystemEventArgs e)
{
// Specify what is done when a file is changed, created, or deleted.
lblUsers.Dispatcher.Invoke(new Action(delegate ()
{
userList.Clear();
lblUsers.Content = string.Empty;
lblUsers.Content = LoadUsers();
}), System.Windows.Threading.DispatcherPriority.Normal);
}
}
}
using System;
using System.Data.SQLite;
using System.Drawing;
using System.Timers;
using System.Windows.Forms;
using Tulpep.NotificationWindow;
public partial class Form1 : Form
{
System.Timers.Timer timer = null;
public Form1()
{
InitializeComponent();
}
private void buttonStart_Click(object sender, EventArgs e)
{
if (timer == null)
{
timer = new System.Timers.Timer();
timer.Elapsed += new System.Timers.ElapsedEventHandler(ObjTimer_Elapsed);
timer.Interval = 10000;
timer.Start();
}
}
private void ObjTimer_Elapsed(object sender, ElapsedEventArgs e)
{
try
{
PopupNotifier pop = new PopupNotifier();
pop.TitleText = "Test";
pop.ContentText = "Hello World";
pop.Popup();
//MessageBox.Show(""); !!! here is problem !!!
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
Here i am using Tulpep notification for create desktop notification. I have one start button in my form. When start button clicked, timer start to pop desktop notification. But it is shows notification only when i not comment on MessageBox.Show("");. and if i remove or comment MessageBox.Show(""); it is not showing notification. I debug in both case, there is no error or exception in both case.
Is any one have idea why is this happening?
I am using .net framework 4.5.2,visual studio 2015, windows 8.
PopupNotifier needs to be called out of the UI-Thread. Since the handler of your timer runs in a different thread you need to invoke your form to solve the problem.
this.Invoke((MethodInvoker)delegate
{
PopupNotifier pop = new PopupNotifier();
pop.TitleText = "Test";
pop.ContentText = "Hello World";
pop.Popup();
});
Create a static class ControlExtensions:
public static void InvokeOnUiThreadIfRequired(this Control control, Action action)
{
if (control.InvokeRequired)
{
control.BeginInvoke(action);
}
else
{
action.Invoke();
}
}
After that go again at the line where you call the Tulpep.NotificationWindow. and assign the main form to a variable like this:
//popup var is the notificationwindow inside form1
Form1 ff = (Form1)Application.OpenForms["Form1"];
ff.InvokeOnUiThreadIfRequired(() =>
{
ff.popup.Image = Properties.Resources.info_icon; //icon from resources
ff.popup.TitleText = title; // some text here
ff.popup.ContentText = contentMessage; // some text here
ff.popup.Popup();
});
Now you invoke the main form and show the NotificationWindow
I had the same problem but with Task.Run(), I tried calling Popup inside SomeMethod with no luck. Solved using Invoke. Hope this helps someone.
Task.Run(() => {
SomeMethod(); //Some method that executes in background
//Popup when SomeMethod is finished using Fruchtzwerg answer
this.Invoke((MethodInvoker)delegate
{
PopupNotifier pop = new PopupNotifier();
pop.TitleText = "Test";
pop.ContentText = "Hello World";
pop.Popup();
});
});
OVERVIEW
I'm new to WPF, I have simple window with a textarea, a button and a textblock. On click of the button i want to update the textblock to say "started.. " or something, run a routine that takes a few minutes and at the end update it to "done".
PROBLEM:
Currently my textblock only updates with the "Done" message. :(
CODE:
public partial class MainWindow : Window, INotifyPropertyChanged
{
private string myValue;
public string MyValue
{
get { return myValue; }
set
{
myValue = value;
RaisePropertyChanged("MyValue");
}
}
private void RaisePropertyChanged(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
public event PropertyChangedEventHandler PropertyChanged;
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
}
private void ScriptToFileButton_Click(object sender, RoutedEventArgs e)
{
Task.Factory.StartNew(() =>
{
for (int i = 0; i < 50; i++)
{
System.Threading.Thread.Sleep(100);
MyValue = i.ToString();
}
});
MyValue = "Scripting, please wait..";
String text = DBObjectsTextArea.Text;
String[] args = text.Split(' ');
SQLScripter scripter = new SQLScripter();
scripter.script(args);
MyValue = "Done!";
}
private void Window_Closed(object sender, EventArgs e)
{
Application.Current.Shutdown();
}
}
XAML:
<TextBox Height="109" HorizontalAlignment="Left" Margin="45,12,0,0" Name="DBObjectsTextArea" VerticalAlignment="Top" Width="418" />
<Button Content="Script To File" Height="23" HorizontalAlignment="Left" Margin="173,145,0,0" Name="ScriptToFileButton" VerticalAlignment="Top" Width="168" Click="ScriptToFileButton_Click" />
<TextBlock Height="56" HorizontalAlignment="Left" Margin="45,197,0,0" Name="StatusTextBlock" Text="{Binding Path=MyValue}" VerticalAlignment="Top" Width="409" />
SIMILAR LINK:
I based my code off of this:
How do I refresh visual control properties (TextBlock.text) set inside a loop?
You can change your event handler to this...
private void ScriptToFileButton_Click(object sender, RoutedEventArgs e)
{
String text = DBObjectsTextArea.Text;
Task t = new Task(() => PerformOnDispatcher(() => { MyValue = "Scripting, please wait.."; }));
ManualResetEvent mre = new ManualResetEvent(false);
t.ContinueWith((x) =>
{
// scripting code goes here...
mre.Set();
}, TaskContinuationOptions.LongRunning);
t.ContinueWith((y) =>
{
mre.WaitOne();
PerformOnDispatcher(() => { MyValue = "Scripting, please wait.."; });
});
t.Start();
}
This segregates the work into three phases: telling the user that scripting has started, then doing the work, then telling the user that the work is done. Synchronization is controlled by the "ContinueWith" method. This method waits until the previous method has completed before starting.
Because it's in a Task, changes to the UI are marshalled back via the dispatcher, which uses a separate scheduler and thus will not be blocked.
The last statement, t.Start() kicks off the sequence of events, so the user experience of clicking the button is not hampered by a blocked UI. The event handler returns almost immediately.
If the work happens really quickly, you still may only see the "Done" because the previous messages were overwritten before you had a chance to read them. So the best way to debug these things is to anchor the debugger on this statement:
PropertyChanged(this, new PropertyChangedEventArgs(propName));
This will let you observe what is being piped out to the binding engine.
Finally, to make the event handler less cluttered so as to focus on the question, I used a convenience method...
private void PerformOnDispatcher(Action a)
{
Dispatcher.InvokeAsync(a);
}
For industrial strength apps, this would be an extension method or done within the property setter.
If you wish the task to run on a background thread you must account for updating the WPF UI on the UI thread. This post from Dr. Xu explains it. Dispatcher.InvokeAsync was added with .Net Framework 4.5 and is preferred. Similar functionality can also be had in earlier versions of .Net with Dispatcher.BeginInvoke. Don't forget to clean up your task and determine how or if the app should close if the task is incomplete.
// MainWindow...
Task t; // keep for cleanup
// Update on the UI thread
private void SetMyValueAsync(string value)
{
Application.Current.Dispatcher.BeginInvoke((Action)(() => MyValue = value));
}
private void ScriptToFileButton_Click(object sender, RoutedEventArgs e)
{
t = Task.Factory.StartNew(() =>
{
SetMyValueAsync("Scripting, please wait..");
// If the following lines will update the UI
// they should do so on the UI thread per Dr. Xu's post.
// String text = DBObjectsTextArea.Text;
// String[] args = text.Split(' ');
// SQLScripter scripter = new SQLScripter();
//scripter.script(args);
for (int i = 0; i < 50; i++)
{
System.Threading.Thread.Sleep(100);
SetMyValueAsync(i.ToString());
}
SetMyValueAsync("Done!");
});
}
protected override void OnClosing(CancelEventArgs e)
{
if (t != null)
{
try { t.Dispose(); }
catch (System.InvalidOperationException)
{
e.Cancel = true;
MessageBox.Show("Cannot close. Task is not complete.");
}
}
base.OnClosing(e);
}
I got this working by using the following:
private void ScriptToFileButton_Click(object sender, RoutedEventArgs e)
{
Task.Factory.StartNew(() =>
{
for (var i = 0; i < 50; i++)
{
Thread.Sleep(100);
MyValue = i.ToString(CultureInfo.InvariantCulture);
}
})
.ContinueWith(s =>
{
MyValue = "Scripting, please wait..";
String text = DBObjectsTextArea.Text;
String[] args = text.Split(' ');
SQLScripter scripter = new SQLScripter();
scripter.script(args);
Thread.Sleep(3000); // This sleep is only used to simulate scripting
})
.ContinueWith(s =>
{
MyValue = "Done!";
});
}
You need to create task continuations. The reason why you are only seeing "Done" is because you are setting MyValue directly after you start the Task. You are not waiting for the Task to complete it's initial processing.