WinRT - MessageDialog.ShowAsync will throw UnauthorizedAccessException in my custom class - c#

I Want to write my own control, when the ctor is invoked, a MessageBox is shown.
public class Class1
{
public Class1()
{
ShowDialog();
}
void ShowDialog()
{
SynchronizationContext context = SynchronizationContext.Current;
if (context != null)
{
context.Post((f) =>
{
MessageDialog dialog = new MessageDialog("Hello!");
dialog.ShowAsync();
}, null);
}
}
}
If my class is used by someone, and write the codes as below, UnauthorizedAccessException is always thrown in dialog.ShowAsync();
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
ClassLibrary1.Class1 c1 = new ClassLibrary1.Class1();
MessageDialog dialog1 = new MessageDialog("");
dialog1.ShowAsync();
}
Is there a way to show a message dialog without exception?
I found a way, enjoy it!
Task ShowDialog()
{
CoreDispatcher dispatcher = Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher;
Func<object, Task<bool>> action = null;
action = async (o) =>
{
try
{
if (dispatcher.HasThreadAccess)
await new MessageDialog("Hello!").ShowAsync();
else
{
dispatcher.RunAsync(CoreDispatcherPriority.Normal,
() => action(o));
}
return true;
}
catch (UnauthorizedAccessException)
{
if (action != null)
{
Task.Delay(500).ContinueWith(async t => await action(o));
}
}
return false;
};
return action(null);
}

As MessageDialogue needs to run on the UI thread, can you try switching it to:
var dispatcher = Windows.UI.Core.CoreWindow.GetForCurrentThread().Dispatcher;
dispatcher.RunAsync(DispatcherPriority.Normal, <lambda for your code which should run on the UI thread>);

The cleaner solution may look like this. The interesting part ist hidden in die showDialogAsync(). For convenience you can use the Close() method to close the current dialog again programmatically. The IUIDispatcher is another helper interface you can rebuild yourself easily:
private readonly IUIDispatcher _dispatcher;
readonly Object _queueMonitor = new object();
readonly Object _showMonitor = new object();
private IAsyncOperation<IUICommand> _currentDialogOperation;
readonly Queue<MessageDialog> _dialogQueue = new Queue<MessageDialog>();
public async Task ShowAsync(string content)
{
var md = new MessageDialog(content);
await showDialogAsync(md);
}
public async Task ShowAsync(string content, string caption)
{
var md = new MessageDialog(content, caption);
await showDialogAsync(md);
}
public async Task<MessageDialogResult> ShowAsync(string content, MessageDialogType dialogType)
{
var messageDialogResult = await ShowAsync(content, null, dialogType);
return messageDialogResult;
}
public async Task<MessageDialogResult> ShowAsync(string content, string caption, MessageDialogType dialogType)
{
var result = MessageDialogResult.Ok;
var md = string.IsNullOrEmpty(caption) ? new MessageDialog(content) : new MessageDialog(content, caption);
switch (dialogType)
{
case MessageDialogType.Ok:
md.Commands.Add(new UICommand(ResWrapper.Strings["MessageDialogButtonOk"], command => result = MessageDialogResult.Ok));
md.CancelCommandIndex = 0;
md.DefaultCommandIndex = 0;
break;
case MessageDialogType.OkCancel:
md.Commands.Add(new UICommand(ResWrapper.Strings["MessageDialogButtonOk"], command => result = MessageDialogResult.Ok));
md.Commands.Add(new UICommand(ResWrapper.Strings["MessageDialogButtonCancel"], command => result = MessageDialogResult.Cancel));
md.DefaultCommandIndex = 0;
md.CancelCommandIndex = 1;
break;
case MessageDialogType.YesNo:
md.Commands.Add(new UICommand(ResWrapper.Strings["MessageDialogButtonYes"], command => result = MessageDialogResult.Yes));
md.Commands.Add(new UICommand(ResWrapper.Strings["MessageDialogButtonNo"], command => result = MessageDialogResult.No));
md.DefaultCommandIndex = 0;
md.CancelCommandIndex = 1;
break;
case MessageDialogType.YesNoCancel:
md.Commands.Add(new UICommand(ResWrapper.Strings["MessageDialogButtonYes"], command => result = MessageDialogResult.Yes));
md.Commands.Add(new UICommand(ResWrapper.Strings["MessageDialogButtonNo"], command => result = MessageDialogResult.No));
md.Commands.Add(new UICommand(ResWrapper.Strings["MessageDialogButtonCancel"], command => result = MessageDialogResult.Cancel));
md.DefaultCommandIndex = 0;
md.CancelCommandIndex = 1;
break;
default:
throw new ArgumentOutOfRangeException("dialogType");
}
await showDialogAsync(md);
return result;
}
/// <summary>
/// Shows the dialogs, queued and one after the other.
/// We need this as a workaround for the the UnauthorizedAcsess exception.
/// </summary>
/// <param name="messageDialog">The message dialog.</param>
/// <returns></returns>
async Task showDialogAsync(MessageDialog messageDialog)
{
//Calls this function in a separated task to avoid ui thread deadlocks.
await Task.Run(async () =>
{
lock (_queueMonitor)
{
_dialogQueue.Enqueue(messageDialog);
}
try
{
while (true)
{
MessageDialog nextMessageDialog;
lock (_queueMonitor)
{
if (_dialogQueue.Count > 1)
{
Debug.WriteLine("MessageDialogService.cs | showDialogAsync | Next dialog is waiting for MessageDialog to be accessable!!");
Monitor.Wait(_queueMonitor); //unlock and wait - regains lock after waiting
}
nextMessageDialog = _dialogQueue.Peek();
}
var showing = false;
_dispatcher.Execute(async () =>
{
try
{
lock (_showMonitor)
{
showing = true;
_currentDialogOperation = nextMessageDialog.ShowAsync();
}
await _currentDialogOperation;
lock (_showMonitor)
_currentDialogOperation = null;
}
catch (Exception e)
{
Debug.WriteLine("MessageDialogService.cs | showDialogAsync | " + e);
}
lock (_showMonitor)
{
showing = false;
Monitor.Pulse(_showMonitor); //unlock and wait - regains lock after waiting
}
});
lock (_showMonitor)
{
if (showing)
{
Debug.WriteLine("MessageDialogService.cs | showDialogAsync | Waiting for MessageDialog to be closed!!");
//we must wait here manually for the closing of the dialog, because the dispatcher does not return a waitable task.
Monitor.Wait(_showMonitor); //unlock and wait - regains lock after waiting
}
}
Debug.WriteLine("MessageDialogService.cs | showDialogAsync | MessageDialog was closed.");
return true;
}
}
finally
{
//make sure we leave this in a clean state
lock (_queueMonitor)
{
_dialogQueue.Dequeue();
Monitor.Pulse(_queueMonitor);
}
}
});
}
public void Close(string keyContent="")
{
try
{
if (keyContent.IsNullOrEmpty())
{
lock (_showMonitor)
{
if (_currentDialogOperation == null) return;
_currentDialogOperation.Cancel();
_currentDialogOperation = null;
}
}
else
{
var cancel = false;
lock (_queueMonitor)
{
if (_dialogQueue.Count == 0)
return;
var currentDialog = _dialogQueue.Peek();
Debug.WriteLine("MessageDialogService.cs | Close | {0}", currentDialog.Content);
if (currentDialog.Content == keyContent)
{
cancel = true;
}
}
if (!cancel) return;
lock (_showMonitor)
{
if (_currentDialogOperation == null) return;
_currentDialogOperation.Cancel();
_currentDialogOperation = null;
}
}
}
catch (Exception e)
{
Debug.WriteLine("MessageDialogService.cs | Close | " + e);
}
}

I think I've found it. I had the same problem when creating messageboxes in any other threads besides the main thread.
This is the C++ solution but I think you can convert it easily ;)
IAsyncOperation<IUICommand^> ^Op = msgbox->ShowAsync();
task<IUICommand^>( Op ).then([=](IUICommand^ C)
{
}).then([](task<void> t)
{
try
{
t.get();
}
catch (Platform::Exception ^e)
{
//ERROR!
}
});
On a side note this is the correct way to handle ANY WinRT/Windows 8 Store C++ exception.

You can always use
Execute.OnUIThread( async () => {
...
var dialog = new MessageDialog(yourMessage);
await dialog.ShowAsync();
...
});
This doesn't solve race conditions in the UI if you are trying to launch multiple dialogs from background threads. But you could use a try/catch to make sure you cover for that case.

Related

Processing Dialog after QR Scan in Xamarin

I'm using QR Code Scanner in Xamarin App, when it scans the qr code, there are some operation it does which takes about a minute, while it's performing operation, I want to show a loading dialog on the screen. But, it isn't showing on the screen, and elsewhere in the app, it's working perfectly.
Code
var expectedFormat = ZXing.BarcodeFormat.QR_CODE;
var opts = new ZXing.Mobile.MobileBarcodeScanningOptions { PossibleFormats = new List<ZXing.BarcodeFormat> { expectedFormat } };
var scanner = new ZXing.Mobile.MobileBarcodeScanner();
var result = await scanner.Scan(opts);
if (result == null)
{
// code
return null;
}
else
{
using (UserDialogs.Instance.Loading("Processing"))
{
// code
}
}
UPDATE CODE SAMPLE
public async Task Verification(object sender, EventArgs e)
{
try
{
var expectedFormat = ZXing.BarcodeFormat.QR_CODE;
var opts = new ZXing.Mobile.MobileBarcodeScanningOptions { PossibleFormats = new List<ZXing.BarcodeFormat> { expectedFormat } };
var scanner = new ZXing.Mobile.MobileBarcodeScanner();
var result = await scanner.Scan(opts);
if (result == null)
{
// Code
return null;
}
else
{
try
{
// QR Scan Result
string qr_scan = result.Response;
var result = JsonConvert.DeserializeObject<QRScan>(qr_scan);
await CreateConnection();
}
catch (Exception error)
{ }
finally
{
// navigate to next page
await NavigationService.NavigateToAsync<NextViewModel>();
}
}
}
catch (Exception error)
{ }
return null;
}
public async Task CreateConnection()
{
UserDialogs.Instance.Loading("Processing");
if ()
{
try
{
// Code
}
catch (Exception error)
{
// Code
}
finally
{
await CreateFolder(default, default);
}
}
}
public async Task CreateFolder(object sender, EventArgs e)
{
UserDialogs.Instance.Loading("Processing");
try
{
// Code
}
catch (Exception error)
{
// Code
}
return null;
}
You can use Xamarin.Essentials' MainThread class and more specifically - the InvokeOnMainThreadAsync method. The idea of this method is to not only execute a code on the UI/main thread, but to also to await it's code. This way you can have both async/await logic and main thread execution.
try
{
// QR Scan Result
string qr_scan = result.Response;
var result = JsonConvert.DeserializeObject<QRScan>(qr_scan);
await MainThread.InvokeOnMainThreadAsync(() => CreateConnection());
}
catch (Exception error)
{ }
finally
{
// navigate to next page
await NavigationService.NavigateToAsync<NextViewModel>();
}
Keep in mind that if the method CreateConnection takes a long time to execute, then it would be better to execute on the main thread only the dialog presentation (UserDialogs.Instance.Loading("")).
Try something like this
Device.BeginInvokeOnMainThread(async () =>
{
try
{
using (UserDialogs.Instance.Loading(("Processing")))
{
await Task.Delay(300);
//Your Service code
}
}
catch (Exception ex)
{
var val = ex.Message;
UserDialogs.Instance.Alert("Test", val.ToString(), "Ok");
}
});

Exception from barcode scanner with custom preview

I'm currently using a BarcodeScanner with a custom preview showed in a ContentDialog.
I genuinely used the code provided on the Microsoft Documentation, and the thing is working right, but only once.
Here's my whole BarcodeScanner service class:
public class BarcodeScannerUtil : IBarcodeScanner
{
private static Windows.Devices.PointOfService.BarcodeScanner _scanner = null;
private static ClaimedBarcodeScanner _claimedBarcodeScanner = null;
private static Action<string> _callback;
MediaCapture mediaCapture;
bool isPreviewing;
DisplayRequest displayRequest = new DisplayRequest();
CameraPreviewDialog preview = new CameraPreviewDialog();
public async Task ClaimScannerAsync()
{
string selector = Windows.Devices.PointOfService.BarcodeScanner.GetDeviceSelector();
DeviceInformationCollection deviceCollection = await DeviceInformation.FindAllAsync(selector);
if (_scanner == null)
_scanner = await Windows.Devices.PointOfService.BarcodeScanner.FromIdAsync(deviceCollection[0].Id);
if (_scanner != null)
{
if (_claimedBarcodeScanner == null)
_claimedBarcodeScanner = await _scanner.ClaimScannerAsync();
if (_claimedBarcodeScanner != null)
{
_claimedBarcodeScanner.DataReceived += ClaimedBarcodeScanner_DataReceivedAsync;
_claimedBarcodeScanner.ReleaseDeviceRequested += ClaimedBarcodeScanner_ReleaseDeviceRequested;
_claimedBarcodeScanner.IsDecodeDataEnabled = true;
_claimedBarcodeScanner.IsDisabledOnDataReceived = true;
await _claimedBarcodeScanner.EnableAsync();
//await _claimedBarcodeScanner.ShowVideoPreviewAsync();
await _claimedBarcodeScanner.StartSoftwareTriggerAsync();
await StartPreviewAsync();
Debug.WriteLine("Barcode Scanner claimed");
}
}
}
private MediaCaptureInitializationSettings InitCaptureSettings()
{
var _captureInitSettings = new MediaCaptureInitializationSettings();
_captureInitSettings.VideoDeviceId = _scanner.VideoDeviceId;
_captureInitSettings.StreamingCaptureMode = StreamingCaptureMode.Video;
_captureInitSettings.PhotoCaptureSource = PhotoCaptureSource.VideoPreview;
return _captureInitSettings;
}
private async Task StartPreviewAsync()
{
try
{
mediaCapture = new MediaCapture();
await mediaCapture.InitializeAsync(InitCaptureSettings());
displayRequest.RequestActive();
DisplayInformation.AutoRotationPreferences = DisplayOrientations.Landscape;
}
catch (UnauthorizedAccessException)
{
await new ErrorDialog("Impossible d'acceder à la caméra, veuillez vérifier les permissions de l'application.").ShowAsync();
}
try
{
preview.Source = mediaCapture;
await mediaCapture.StartPreviewAsync();
isPreviewing = true;
ContentDialogResult resPreview = await preview.ShowAsync();
//clic sur le bouton annuler
if (resPreview == ContentDialogResult.Secondary)
{
await CleanupCameraAsync();
await _claimedBarcodeScanner.StopSoftwareTriggerAsync();
await _claimedBarcodeScanner.DisableAsync();
}
}
catch (System.IO.FileLoadException)
{
mediaCapture.CaptureDeviceExclusiveControlStatusChanged += _mediaCapture_CaptureDeviceExclusiveControlStatusChanged;
}
}
private async Task CleanupCameraAsync()
{
if (mediaCapture != null)
{
if (isPreviewing)
{
await mediaCapture.StopPreviewAsync();
}
await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
preview.Source = null;
if (displayRequest != null)
{
displayRequest.RequestRelease();
}
mediaCapture.Dispose();
mediaCapture = null;
});
}
}
public void Subscribe(Action<string> callback)
{
// it makes sense to have only one foreground barcode reader client at a time
_callback = callback;
}
/// <summary>
/// Retire une action callback du BarcodeScanner
/// </summary>
public void Unsubscribe()
{
_callback = null;
}
private async void ClaimedBarcodeScanner_DataReceivedAsync(ClaimedBarcodeScanner sender, BarcodeScannerDataReceivedEventArgs args)
{
await CleanupCameraAsync();
await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
preview.Hide();
});
if (_callback == null)
return;
var barcode = CryptographicBuffer.ConvertBinaryToString(BinaryStringEncoding.Utf8, args.Report.ScanDataLabel);
_callback(barcode);
}
private void ClaimedBarcodeScanner_ReleaseDeviceRequested(object sender, ClaimedBarcodeScanner e)
{
// always retain the device
e.RetainDevice();
}
private async void _mediaCapture_CaptureDeviceExclusiveControlStatusChanged(MediaCapture sender, MediaCaptureDeviceExclusiveControlStatusChangedEventArgs args)
{
if (args.Status == MediaCaptureDeviceExclusiveControlStatus.SharedReadOnlyAvailable)
{
await new ErrorDialog("Impossible d'acceder à la caméra car elle est utilisée par une autre application.").ShowAsync();
}
else if (args.Status == MediaCaptureDeviceExclusiveControlStatus.ExclusiveControlAvailable && !isPreviewing)
{
await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
{
await StartPreviewAsync();
});
}
}
}
When I'm showing my preview I'm using IsDisabledOnDataReceived = true; but when I reopen my barcode scanner a second time, I get an exception saying System.Exception : 'The request is invalid in the current state. Started' at await mediaCapture.StopPreviewAsync(); in the CleanupCameraAsync()
That's really weird because when I click on the Cancel button of the ContentDialog showing the preview it's doing exactly the same thing and there's no problem after that.
I've been searching for an hour now and have no clue about what it could come from.
Finally found an answer by myself :
I moved the CleanupCameraAsync() from my OnReceived event to my content dialog results
ContentDialogResult resPreview = await preview.ShowAsync();
//clic sur le bouton annuler
if (resPreview == ContentDialogResult.Secondary)
{
await _claimedBarcodeScanner.StopSoftwareTriggerAsync();
await _claimedBarcodeScanner.DisableAsync();
await CleanupCameraAsync();
}
else
{
await CleanupCameraAsync();
}
So when I close the dialog with the button it works, and if it closes by any other way CleanupCameraAsync() will still fire.
Plus, I added some lines in the CleanupCameraAsync() to reset all the components of my BarcodeScanner
private async Task CleanupCameraAsync()
{
if (mediaCapture != null)
{
if (isPreviewing)
{
await mediaCapture.StopPreviewAsync();
}
await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
preview.Source = null;
if (displayRequest != null)
{
displayRequest.RequestRelease();
}
mediaCapture.Dispose();
mediaCapture = null;
/*******added these lines*********/
displayRequest = new DisplayRequest();
preview = new CameraPreviewDialog();
_scanner = null;
_claimedBarcodeScanner = null;
});
}
}
It's not really clean but it forces the BarcodeScanner to have the default behavior everytime I call it.
If you have any better solution, let me know please.

Cancelling task in C#

I have a Form with two buttons (Start , Stop).
When I press Start Button a Task is initialized and a function is called that keeps running until Stop button is pressed.
But When I press Stop button Form freezes. why?
I have copied snippet from StackOverflow but it still freezes Form.
So tell me how to Cancel Task properly?
public partial class Form1 : Form
{
private readonly CancellationTokenSource _cts = new CancellationTokenSource();
private Task _task;
public Form1()
{
InitializeComponent();
}
//Funtion that runs when Task is initialized.
private void EventLoop(CancellationToken token)
{
//Code here..
while (!token.IsCancellationRequested)
{
//Code here..
}
if (token.IsCancellationRequested)
{
MessageBox.Show("Operation Complete..!!");
}
}
//Function for Start Button.
private void Start_Click(object sender, EventArgs e)
{
_task = Task.Factory.StartNew(() => EventLoop(_cts.Token), _cts.Token);
}
//Function for Stop button.
private void Stop_Click(object sender, EventArgs e)
{
_cts.Cancel();
}
}
Similar Example from MSDN:
var compute = Task.Factory.StartNew(() =>
{
return SumRootN(j);
}, tokenSource.Token);`
Form After Stop button is pressed.
token.IsCancellationRequested is true .
Full EventLoop() Function.
private void EventLoop(CancellationToken token)
{
SerialPort sp = new SerialPort();
string text, batch, part, courseName;
text = batch = part = courseName = "";
int courseId = 0;
this.Invoke((MethodInvoker)delegate()
{
text = portCB.SelectedItem.ToString();
batch = comboBox2.SelectedItem.ToString();
part = comboBox3.SelectedItem.ToString();
courseName = comboBox1.SelectedItem.ToString();
progressBar1.Value = 20;
using (Model1 db = new Model1())
{
courseId = db.Courses.Where(c => c.Course_name.ToUpper() == courseName.ToUpper()).Select(c => c.Course_Id).Single();
}
});
sp.PortName = text;
sp.BaudRate = 9600;
sp.Open();
while (!token.IsCancellationRequested)
{
text = sp.ReadLine();
if (text.Contains("Found ID #"))
{
this.Invoke((MethodInvoker)delegate()
{
textBox2.Clear();
textBox2.Text = "Getting Registation ID.\n";
progressBar1.Value = 60;
});
string splitText = text.Split('#')[1];
int end = splitText.IndexOf(' ');
int id = int.Parse(splitText.Substring(0, end));
using (Model1 db = new Model1())
{
var result = db.Students.Where(s => s.Reg_Id == id && s.Batch == batch && s.Class == part).Select(s => s).SingleOrDefault();
if (result != null)
{
Attendance a = new Attendance();
a.Course_Id = courseId;
a.Student_Id = id;
a.Status = "P";
a.Date = DateTime.Today.Date;
a.Batch = batch;
a.Part = part;
db.Attendances.Add(a);
string message = "";
if (db.SaveChanges() != 0)
{
message = "Attendance Uploaded..!!\n";
}
else
{
message = "Attendance Not Uploaded ..!!\n";
}
this.Invoke((MethodInvoker)delegate()
{
progressBar1.Value = 100;
textBox2.AppendText(message);
});
}
else
{
this.BeginInvoke((MethodInvoker)delegate()
{
textBox2.AppendText("Student Does not belong to Specified Batch Or Part..\n");
});
}
}
}
else
{
this.Invoke((MethodInvoker)delegate()
{
textBox2.AppendText("No Match Found..!! \n");
});
}
this.Invoke((MethodInvoker)delegate()
{
textBox1.AppendText(text + "\n");
});
}
sp.Close();
// This exception will be handled by the Task
// and will not cause the program to crash
if (token.IsCancellationRequested)
{
//MessageBox.Show("Operation Comptele..!!");
}
}
You call MessageBox.Show("Operation Complete..!!"); in the progress of cancellation. This is highly not recommended, not talking about that you are calling an UI operation from other than the UI thread.
Comment the MessageBox.Show("Operation Complete..!!"); line out
* Edit *
Question author's comment on his original question, found the mistake, which line was removed from the post. Here are my conclusion:
Always try to isolate an issue, and reproduce in its purest form. During that process you will probably diagnose and find the issues itself :-).
So if the code with issue is long to post, it is definitely not the way just deleting lines then post it. The way is deleting lines and see if the issue exist, with other words: Isolating the issue in its purest reproducable form
please use async call on your task
private async void Start_Click(object sender, EventArgs e)
{
_task = Task.Factory.StartNew(() => EventLoop(_cts.Token), _cts.Token);
await task;
}

Callback problems with WPF

I've been running into a Callback problems with async programming in WPF .Net 4.5.
There should be a way of doing this code in a more understandable way (I have suppressed a big part of it to make it easier to see what is going on).
I don't think there is a way to simply remove code because I need to call Dispatcher in order to manipulate WPF controls and calls like in TbSequence.Focus() and Utils.ShowMessageBox.
private void Save_Executed(object sender, ExecutedRoutedEventArgs e)
{
try
{
Controller.Busy = true;
System.Threading.Tasks.Task.Run(() =>
{
try
{
Controller.SaveItem();
}
catch (BdlDbException ex)
{
if (ex.ExceptionSubType == DbExceptionSubTypes.UniqueViolation)
{
HandleUniqueViolation(ex);
}
else
{
string errorMessage = "";
errorMessage = ex.Message;
Dispatcher.BeginInvoke(new Action(() => Utils.ShowMessageBox(t_MessageBox.Attention, errorMessage)));
}
}
Controller.Busy = false;
});
}
catch (FieldException ex)
{
if (ex.FieldName == "FirstName")
{
TbFirstName.ValidationError = true;
TbFirstName.ApplyErrorToolTip(ex.Message);
}
}
}
public void Init(UcEdit container, Employee entity = null)
{
Controller.Busy = true;
System.Threading.Tasks.Task.Run(() =>
{
try
{
Controller.Init(entity);
}
catch (BdlEntryNotFoundException ex)
{
HandleNotFoundException(ex);
}
Container.Dispatcher.BeginInvoke(new Action(() =>
{
Container.DataContext = Controller;
// Instructions order for focusing TbSequence after load should be different in case we have an existent item
// because we have to focus the control AFTER it is filled with data, in order to set the caret position correctly.
if (Controller.IsNewItem)
{
this.DataContext = Controller;
TbSequence.Focus();
Controller.Busy = false;
}
else
{
TbSequence.TextChanged += TbSequence_TextChanged;
this.DataContext = Controller;
SetButtons();
}
}));
});
}
private void HandleUniqueViolation(BdlDbException ex)
{
string errorMessage = "";
bool isSequence = false; // if true, it's name.
if (ex.Fields[1] == "Sequence")
{
errorMessage = "There is already an Employee with the sequence \"" + Controller.Item.Sequence + "\".";
isSequence = true;
}
else
{
errorMessage = "There is already an Employee named \"" + Controller.Item.FirstName +
" " + Controller.Item.LastName + "\".";
}
errorMessage += "\r\nLoad it from Database?\r\n(All the changes on this window will be lost.)";
Dispatcher.BeginInvoke(new Action(() =>
{
MessageBoxResult res = Utils.ShowMessageBox(t_MessageBox.Question, errorMessage, MessageBoxButton.YesNo);
switch (res)
{
case MessageBoxResult.Yes:
if (isSequence)
{
System.Threading.Tasks.Task.Run(() =>
{
Controller.GetEmployeeBySequence(Controller.Item.Sequence);
Init(Container, Controller.OriginalItem);
});
}
else
{
System.Threading.Tasks.Task.Run(() =>
{
Controller.GetEmployeeByName(Controller.Item.FirstName, Controller.Item.LastName);
Init(Container, Controller.OriginalItem);
});
}
break;
case MessageBoxResult.No:
break;
}
}));
}
As you can see, there is a major Callback problem here that behaves like this:
Save_Executed (UI) -> HandleUniqueViolation (Task) -> ShowMessageBox (UI) -> Controller.GetEmployeeBySequence (Task) -> Controller.Init ...
And so it goes.
Is there a way to make this code more easy to read?
Thank you.
You're starting off your code by putting (more or less) the entirety of your method body in a call to Task.Run and then explicitly marshalling to the UI thread all over the place within that callback.
Just narrow the scope of your Task.Run call so that your UI code is just outside the call to Run rather than inside both it and a call to Invoke:
private async void Save_Executed(object sender, ExecutedRoutedEventArgs e)
{
try
{
Controller.Busy = true;
try
{
await Task.Run(() => Controller.SaveItem());
}
catch (BdlDbException ex)
{
if (ex.ExceptionSubType == DbExceptionSubTypes.UniqueViolation)
{
HandleUniqueViolation(ex);
}
else
{
string errorMessage = "";
errorMessage = ex.Message;
Utils.ShowMessageBox(t_MessageBox.Attention, errorMessage);
}
}
Controller.Busy = false;
}
catch (FieldException ex)
{
if (ex.FieldName == "FirstName")
{
TbFirstName.ValidationError = true;
TbFirstName.ApplyErrorToolTip(ex.Message);
}
}
}
Here you're running the actual long running business operation that you have in a thread pool thread, but doing all of your error handling in the UI thread.
You can do the same thing throughout your application. Rather than putting everything into a background thread and then explicitly marshaling, just only ever execute operations in a background thread that should be entirely executed in a background thread.

Check tag for content with Overwrite warning

void device_DeviceArrived(ProximityDevice sender)
{
//Compatible Device enters area
if (stance == WriteStage.PREP)
{
System.Diagnostics.Debug.WriteLine("Writestages won");
//Perhaps here
updateStatusRectangle(Colors.Yellow);
stance = WriteStage.WRITING;
updateStatusText("Writing...");
writeToTag(msg);
}
else
{
updateReceivedText("Device connected!");
}
}
private void MessageReceivedHandler(ProximityDevice sender, ProximityMessage message)
{
System.Diagnostics.Debug.WriteLine("Handler ran");
var rawMsg = message.Data.ToArray();
var ndefMessage = NdefMessage.FromByteArray(rawMsg);
foreach (NdefRecord record in ndefMessage)
{
System.Diagnostics.Debug.WriteLine("Record type: " + Encoding.UTF8.GetString(record.Type, 0, record.Type.Length));
var specType = record.CheckSpecializedType(false);
if (specType == typeof(NdefTextRecord))
{
var textrec = new NdefTextRecord(record);
updateReceivedText(textrec.Text);
}
}
}
The above event and handler are executed when the phone comes into contact with an NFC device. For intents and purposes in this app, I need to ensure that before writing to a card, if it already has content, it will prompt the user to verify overwriting the data. I commented where I think it should go, but as far as checking for the Message, I'm not sure how to go about it. I can't call the handler without the ProximityMessage, and I don't know of another way to view the message.
The Question: Is it possible to call the MessageReceivedHandler (or check the message at all), from device_DeviceArrived?
(Note: Debug.Writelines are for test purposes, and this is just a quick NFC writer I'm throwing together).
UPDATE: In attempting to find a work around, I ran into a different problem.
public bool promptUserForOverwrite()
{
bool response = false;
Dispatcher.BeginInvoke(() =>
{
MessageBoxResult cc = MessageBox.Show("You are about to overwrite data. Proceed?", "Overwrite?", MessageBoxButton.OKCancel);
if (cc == MessageBoxResult.OK)
{
System.Diagnostics.Debug.WriteLine("MessageBox OK result");
response = true;
}
});
return response;
}
private void MessageReceivedHandler(ProximityDevice sender, ProximityMessage message)
{
System.Diagnostics.Debug.WriteLine("Handler ran");
var rawMsg = message.Data.ToArray();
var ndefMessage = NdefMessage.FromByteArray(rawMsg);
foreach (NdefRecord record in ndefMessage)
{
System.Diagnostics.Debug.WriteLine("Record type: " + Encoding.UTF8.GetString(record.Type, 0, record.Type.Length));
var specType = record.CheckSpecializedType(false);
if (specType == typeof(NdefTextRecord))
{
var textrec = new NdefTextRecord(record);
updateReceivedText(textrec.Text);
}
}
bool pow = promptUserForOverwrite();
if (!pow)
{
System.Diagnostics.Debug.WriteLine("Prompt returned");
//This always hits - pow is always false.
}
if (stance == WriteStage.WRITING && pow)
{
//writeToTag(msg);
}
}
This would work as a work around; the problem is the beginInvoke method. I need it for cross thread access, but used like this seems to make it run at a later time (when the thread is free?). The bool pow is always false, even after I click ok on the messagebox (debugged, and it does get the result, but after I can no longer use it). Is there an alternative that I can use for the Dispatcher?
Ugly, but I have this working. You need to get a TaskScheduler from the UI thread, so declare a
private TaskScheduler sched;
and then on the OnLoaded event for the page
sched = TaskScheduler.FromCurrentSynchronizationContext();
Then your methods
public async Task<bool> promptUserForOverwrite()
{
return false;
}
private async void MessageReceivedHandler(ProximityDevice sender, ProximityMessage message)
{
System.Diagnostics.Debug.WriteLine("Handler ran");
var rawMsg = message.Data.ToArray();
var ndefMessage = NdefMessage.FromByteArray(rawMsg);
foreach (NdefRecord record in ndefMessage)
{
System.Diagnostics.Debug.WriteLine("Record type: " + Encoding.UTF8.GetString(record.Type, 0, record.Type.Length));
var specType = record.CheckSpecializedType(false);
if (specType == typeof(NdefTextRecord))
{
var textrec = new NdefTextRecord(record);
updateReceivedText(textrec.Text);
}
}
var task = promptUserForOverwrite();
var pow = await task.ContinueWith(t =>
{
MessageBoxResult cc = MessageBox.Show("You are about to overwrite data. Proceed?", "Overwrite?", MessageBoxButton.OKCancel);
if (cc == MessageBoxResult.OK)
{
System.Diagnostics.Debug.WriteLine("MessageBox OK result");
return true;
}
else
{
return false;
}
}, CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, sched);
if (!pow)
{
System.Diagnostics.Debug.WriteLine("Prompt returned");
//This always hits - pow is always false.
}
if (stance == WriteStage.WRITING && pow)
{
//writeToTag(msg);
}
}

Categories

Resources