Individually, all code works perfectly. The snippet for saving the file, the snippet for picking a directory to save it to and also the message dialog works great.
But when I tie it all together, I get an access denied. I am not using the DocumentsLibrary capability since it is not required of me to do so in this case, however, enabling this capability after running into issues confirmed that it is not the issue.
Scenario:
User wants to create a new document after entering text in the text box. A MessageDialog appears, asking them if they want to save changes to the existing file first - the user clicks Yes (save file).
Now, here is where you handle the event that was raised by the MessageDialog.
Inside the IUICommand command event handler, you test for which button was clicked, and act accordingly.
I did this with a switch statement:
switch(command.Label) {
case "Yes":
SaveFile(); // extension method containing save file code that works on its own
break;
case "No":
ClearDocument();
break;
default:
break;
}
Now, each case works great except for the Yes button. When you click yes, an e tension method is called which has code that saves to a file
It is when you click yes button that you get the ACCESS DENIED exception. Details of the exception didn't reveal anything.
I think that it has something to do with how I am using the MesaageDialog. But after searching for hours I have yet to find a sample on how to save a file with the FileSavePicker when a MesaageDialog button is pressed.
Any ideas in how this should be done?
Update w/ Code
When the user clicks the New document button on the AppBar, this method fires:
async private void New_Click(object sender, RoutedEventArgs e)
{
if (NoteHasChanged)
{
// Prompt to save changed before closing the file and creating a new one.
if (!HasEverBeenSaved)
{
MessageDialog dialog = new MessageDialog("Do you want to save this file before creating a new one?",
"Confirmation");
dialog.Commands.Add(new UICommand("Yes", new UICommandInvokedHandler(this.CommandInvokedHandler)));
dialog.Commands.Add(new UICommand("No", new UICommandInvokedHandler(this.CommandInvokedHandler)));
dialog.Commands.Add(new UICommand("Cancel", new UICommandInvokedHandler(this.CommandInvokedHandler)));
dialog.DefaultCommandIndex = 0;
dialog.CancelCommandIndex = 2;
// Show it.
await dialog.ShowAsync();
}
else { }
}
else
{
// Discard changes and create a new file.
RESET();
}
}
And the FileSavePicker stuff:
private void CommandInvokedHandler(IUICommand command)
{
// Display message showing the label of the command that was invoked
switch (command.Label)
{
case "Yes":
MainPage rootPage = this;
if (rootPage.EnsureUnsnapped())
{
// Yes was chosen. Save the file.
SaveNewFileAs();
}
break;
case "No":
RESET(); // Done.
break;
default:
// Not sure what to do, here.
break;
}
}
async public void SaveNewFileAs()
{
try
{
FileSavePicker saver = new FileSavePicker();
saver.SuggestedStartLocation = PickerLocationId.Desktop;
saver.CommitButtonText = "Save";
saver.DefaultFileExtension = ".txt";
saver.FileTypeChoices.Add("Plain Text", new List<String>() { ".txt" });
saver.SuggestedFileName = noteTitle.Text;
StorageFile file = await saver.PickSaveFileAsync();
thisFile = file;
if (file != null)
{
CachedFileManager.DeferUpdates(thisFile);
await FileIO.WriteTextAsync(thisFile, theNote.Text);
FileUpdateStatus fus = await CachedFileManager.CompleteUpdatesAsync(thisFile);
//if (fus == FileUpdateStatus.Complete)
// value = true;
//else
// value = false;
}
else
{
// Operation cancelled.
}
}
catch (Exception exception)
{
Debug.WriteLine(exception.InnerException);
}
}
Any progress on this issue? I currently have the same problem. I have also found that the same problem occurs if a second MessageDialog is shown in the IUICommand event.
My solution is to cancel the first operation (that shows the first message dialog). Here some code I’m using (it’s accessible in a global object):
private IAsyncInfo mActiveDialogOperation = null;
private object mOperationMutex = new object();
private void ClearActiveOperation(IAsyncInfo operation)
{
lock (mOperationMutex)
{
if (mActiveDialogOperation == operation)
mActiveDialogOperation = null;
}
}
private void SetActiveOperation(IAsyncInfo operation)
{
lock (mOperationMutex)
{
if (mActiveDialogOperation != null)
{
mActiveDialogOperation.Cancel();
}
mActiveDialogOperation = operation;
}
}
public void StopActiveOperations()
{
SetActiveOperation(null);
}
public async void ShowDialog(MessageDialog dialog)
{
StopActiveOperations();
try
{
IAsyncOperation<IUICommand> newOperation = dialog.ShowAsync();
SetActiveOperation(newOperation);
await newOperation;
ClearActiveOperation(newOperation);
}
catch (System.Threading.Tasks.TaskCanceledException e)
{
System.Diagnostics.Debug.WriteLine(e.Message);
}
}
So every time I want to show a MessageDialog I call ShowDialog. This will cancel the current dialog if any (then a TaskCanceledException occurs).
In the case when I will use a FileSavePicker, I call StopActiveOperations before PickSaveFileAsync is called.
This works but I can’t say I like it. It feels like I’m doing something wrong.
OK, now I have figured it out :-). The documentation says explicit that you shouldn’t show new popups/file pickers in the UICommand:
http://msdn.microsoft.com/en-US/library/windows/apps/windows.ui.popups.messagedialog.showasync
This is an example of a bad way to do it:
private async void Button_Click(object sender, RoutedEventArgs e)
{
MessageDialog dialog = new MessageDialog("Press ok to show new dialog (the application will crash).");
dialog.Commands.Add(new UICommand("OK", new UICommandInvokedHandler(OnDialogOkTest1)));
dialog.Commands.Add(new UICommand("Cancel"));
await dialog.ShowAsync();
}
private async void OnDialogOkTest1(IUICommand command)
{
MessageDialog secondDialog = new MessageDialog("This is the second dialog");
secondDialog.Commands.Add(new UICommand("OK"));
await secondDialog.ShowAsync();
}
This is the correct way to do it:
private async void Button_Click_1(object sender, RoutedEventArgs e)
{
MessageDialog dialog = new MessageDialog("Press ok to show new dialog");
UICommand okCommand = new UICommand("OK");
UICommand cancelCommand = new UICommand("Cancel");
dialog.Commands.Add(okCommand);
dialog.Commands.Add(cancelCommand);
IUICommand response = await dialog.ShowAsync();
if( response == okCommand )
{
MessageDialog secondDialog = new MessageDialog("This is the second dialog");
secondDialog.Commands.Add(new UICommand("OK"));
await secondDialog.ShowAsync();
}
}
Quite simple actually, I should have get this earlier...
Related
I make a connection in C# with a login and a password and I would like
put a button to cancel the connection to the database if it gets too much
long.
I would like to know how to do it and put it in a thread if possible.
Here is the code:
private void btncon_Click(object sender, EventArgs e)
{
string strLogin = tblogin.Text.Trim();
string pass = tbpwd.Text;
bool success = false;
if (String.IsNullOrWhiteSpace(strLogin) || String.IsNullOrWhiteSpace(pass))
{
MessageBox.Show("Veuillez remplir tous les champs SVP ");
}
else if(String.IsNullOrEmpty(strLogin) || String.IsNullOrEmpty(pass)){
MessageBox.Show("Veuillez remplir tous les champs SVP ");
}
else
{
model.Connexion cm = new model.Connexion();
pass = Snippets.SHA1Util.SHA1HashStringForUTF8String(pass).ToString();
string[] user = cm.login(strLogin, pass);
if(user[0] != null)
{
Int32.TryParse(user[0], out iduser);
Int32.TryParse(user[1], out idGrp);
Int32.TryParse(user[2], out idbtq);
nom = user[3];
if (idGrp != 3)
success = true;
else
{
MessageBox.Show("Accès Non autorisé , Veuillez contacter l'administrateur");
success = false;
}
}
else
{
MessageBox.Show("Email ou mot de passe incorrect.");
success = false;
}
}
if (success)
{
main Principale = new main();
Principale.Show();
Hide();`
}
}
Problem is that when user clicks the Cancel button, there is no way to cancel the cm.login() method gracefully while it's executing. You could use Thread.Abort() to terminate login forcefully, but it's unsafe and strongly discouraged, because making it right would require execute code in another AppDomain and would make the code very complicated.
Fortunatelly, you can still implement Cancel button, if the following conditions are true:
It is safe to call cm.login() on another thread
Letting cm.login() finish in the background (on another thread) after user clicks on the Cancel button does not have undesired effects.
This code also assumes that it is an Winform app (but solution for WPF app would be very similar). It also assumes that main form has a button btnCancel that is hidden (Visible=false) and it's Click event handler is set to btnCancel_Click method.
TaskCompletionSource<Object> CancelLoginTcs = new TaskCompletionSource<Object>();
// Make button click handler method async
private async void btncon_Click(object sender, EventArgs e)
{
try
{
// Make Cancel button visible, so that user can click on it
btnCancel.Visible = true;
// Prepare everything needed to start login
//var strLogin = ...;
//var pass = ...;
//model.Connexion cm = new model.Connexion();
// ...
// Start login on another thread
var loginTask = Task<string[]>.Run(() => cm.login(strLogin, pass));
// Create task that is used to wake-up main thread, when user clicks
// on the Cancel button before login finishes.
CancelLoginTcs = new TaskCompletionSource<Object>();
// Wait for login task or cancel task to complete, whichever finishes first
await Task.WhenAny(loginTask, CancelLoginTcs.Task);
if (CancelLoginTcs.Task.IsCanceled)
{
// User clicked on the Cancel button.
// Login method will be running in the background, until it
// finishes. This assumes that it is safe to do so.
// Here you should do neccessary clean-up, inform user, etc.
// ...
}
else
{
// Login finished and user did NOT click on the Cancel button.
try
{
// Simply read result of login. If an Exception occured during login,
// it will be rethrow now, so you should handle it appropriatelly
var user = loginTask.Result;
// Here program continues in a usual way
// ...
}
catch(Exception E)
{
// Handle login exception
// ...
}
}
}
finally
{
// Hide Cancel button again
btnCancel.Visible = false;
CancelLoginTcs = null;
}
}
private void btnCancel_Click(object sender, EventArgs e)
{
// Set cancel task to cancelled state.
// This will wake-up main thread and let it continue
CancelLoginTcs.SetCanceled();
}
Is there a way to wrap the UWP async function for creating dialog boxes in such a way that they can be called from a normal method without the async keyword? Example:
var msgbox = new ContentDialog
{
Title = "Error",
Content = "Already at the top of the stack",
CloseButtonText = "OK"
};
await msgbox.ShowAsync();
No, there is no way to do this at the moment. If you try to block waiting for the dialog to close, your app will deadlock.
Here is how I do it (I saw this in some live demo back when UWP was just introduced):
var msgbox = new ContentDialog
{
Title = "Error",
Content = "Already at the top of the stack",
CloseButtonText = "OK"
};
var ignored = msgbox.ShowAsync();
This works as expected in a non-async void method.
public Task<ContentDialogResult> MsgBox(string title, string content)
{
Task<ContentDialogResult> X = null;
var msgbox = new ContentDialog
{
Title = title,
Content = content,
CloseButtonText = "OK"
};
try
{
X = msgbox.ShowAsync().AsTask<ContentDialogResult>();
return X;
}
catch {
return null;
}
}
private void B1BtnBack_Click(object sender, RoutedEventArgs e)
{
MsgBox("Beep", "Already at the top of stack");
return;
// ^^^ Careful here. MsgBox returns with an active task
// running to display dialog box. This works because
// the next statement is a return that directly
// returns to the UI message loop. And the
// ContentDialog is modal meaning it disables
// the page until ok is clicked in the dialog box.
}
Can anyone help me understand why my call to dialogservice executes after the CanNavigateAway function has returned its value? (My goal is to warn the user they are about to navigate away from a view without saving their changes. If they click OK, the navigation is allowed. I'm using MVVM Light.
When I step through the code, it does reach the dialog service, but then proceeds to the end of CanNavigateAway before creating the dialog. The CanNavigateAway method is called by OnNavigatingFrom.
public bool CanNavigateAway()
{
if (!changesSaved && Model.IsModified && !continueNavigation)
{
dialogService.ShowMessage("Are you sure you want to continue?",
"Confirmation",
buttonConfirmText: "Continue", buttonCancelText: "Discard",
afterHideCallback: (confirmed) =>
{
if (confirmed)
{
// User has pressed the "confirm" button.
// ...
continueNavigation = true;
}
else
{
// User has pressed the "cancel" button
// (or has discared the dialog box).
// ...
continueNavigation = false;
}
});
return continueNavigation;
}
}
Here is the OnNavigatingFrom method from the MVVM Light Bindable Page class:
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
var navigableViewModel = this.DataContext as INavigable;
if (navigableViewModel != null)
{
if (!navigableViewModel.CanNavigateAway())
{
e.Cancel = true;
}
}
}
I tried this a different way to get the dialog service out of the mix, but showConfirmationDialogAsync still does not seem to execute in time:
public bool CanNavigateAway()
{
continueNavigation = false;
if (!changesSaved && Model.IsModified && !continueNavigation)
{
showConfirmationDialogAsync();
return continueNavigation;
}
private async void showConfirmationDialogAsync()
{
continueNavigation = false;
ContentDialog noSaveConfirmation = new ContentDialog
{
Title = "Warning",
Content = "You have unsaved changes. Are you sure you want to leave this page without saving?",
PrimaryButtonText = "Leave without saving",
SecondaryButtonText = "Stay and finish"
};
ContentDialogResult result = await noSaveConfirmation.ShowAsync();
if (result == ContentDialogResult.Primary)
{
continueNavigation = true;
}
else if (result == ContentDialogResult.Secondary)
{
continueNavigation = false;
}
}
None of the solutions will work if you require a response from the user. The problem is that when the code is inside the navigation event handler, it is running on the UI thread and the user prompt runs asynchronously, so that the UI is free to present the dialog to the user. This however means that the event handler finishes before the user has a chance to respond.
However, you can use a workaround solution. Add a flag bool field like forceNavigation. Then inside the OnNavigatingFrom display the dialog to the user and set Cancel to true right away and display the user the confirmation dialog. If the user says yes, then set forceNavigaiton to true and trigger the navigation manually again. Now it will skip the confirmation part and navigate right away.
protected async override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
//if navigation is forced, skip all logic
if ( !forceNavigation )
{
var navigableViewModel = this.DataContext as INavigable;
if (navigableViewModel != null)
{
e.Cancel = true;
//display the dialog to the user, if he says yes, set
//forceNavigation = true; and repeat the navigation (e.g. GoBack, ... )
}
}
}
I'm developing an app where the app will ask a question to the user, a few actually - for instance asking if the user wants to rate the app. I need to run this method, but it greatly increases the app startup time. How can I run this in the background? I checked other questions on stack overflow without much help. The method that needs to be run in the background:
Called simply like this:
checkUserStats();
Method:
private void checkUserStats()
{
// Load settings from IsolatedStorage first
try
{
userRemindedOfRating = Convert.ToBoolean(settings["userRemindedOfRating"].ToString());
}
catch (Exception)
{
userRemindedOfRating = false;
}
try
{
wantsAndroidApp = Convert.ToBoolean(settings["wantsAndroidApp"].ToString());
}
catch (Exception)
{
wantsAndroidApp = false;
}
//Check if the user has added more 3 notes, if so - remind the user to rate the app
if (mainListBox.Items.Count.Equals(4))
{
//Now check if the user has been reminded before
if (userRemindedOfRating.Equals(false))
{
//Ask the user if he/she wants to rate the app
var ratePrompt = new MessagePrompt
{
Title = "Hi!",
Message = "I See you've used the app a little now, would u consider doing a review in the store? It helps a lot! Thanks!:)"
};
ratePrompt.IsCancelVisible = true;
ratePrompt.Completed += ratePrompt_Completed;
ratePrompt.Show();
//Save the newly edited settings
try
{
settings.Add("userRemindedOfRating", true);
settings.Save();
}
catch (Exception)
{
}
//Update the in-memory boolean
userRemindedOfRating = true;
}
else if (userRemindedOfRating.Equals(true))
{
// Do nothing
}
}
else
{
}
// Ask the user if he/she would like an android app
if (wantsAndroidApp.Equals(false))
{
// We haven't asked the user yet, ask him/her
var androidPrompt = new MessagePrompt
{
Title = "Question about platforms",
Message = "Hi! I just wondered if you wanted to have this app for android? If so, please just send me a quick email. If enough people wants it, I'll create it:)"
};
androidPrompt.IsCancelVisible = true;
androidPrompt.Completed += androidPrompt_Completed;
androidPrompt.Show();
//Save the newly edited settings
try
{
settings.Add("wantsAndroidApp", true);
settings.Save();
}
catch (Exception)
{
}
//Update the in-memory boolean
wantsAndroidApp = true;
}
else if (wantsAndroidApp.Equals(true))
{
// We have asked the user already, do nothing
}
}
I tried this now:
Using:
using System.ComponentModel;
Declaration:
BackgroundWorker worker;
Initialization:
worker = new BackgroundWorker();
worker.DoWork+=worker_DoWork;
Method:
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
checkUserStats();
}
But it causes a System.UnauthorizedAccessException: Invalid cross-thread access in my app.xaml.cs
you could use a background worker thread and put your method call inside it
'The Silverlight BackgroundWorker class provides an easy way to run time-consuming operations on a background thread. The BackgroundWorker class enables you to check the state of the operation and it lets you cancel the operation.
When you use the BackgroundWorker class, you can indicate operation progress, completion, and cancellation in the Silverlight user interface. For example, you can check whether the background operation is completed or canceled and display a message to the user.'
You basically just need to initialize a backgroundworker object and subscribe to its DoWork event.
And the remedy for your exception
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
Dispatcher.BeginInvoke(() =>
{
checkUserStats();
});
}
just give a look at this msdn article
and one more article.
I'm having issues with the new SystemMediaTransportControls that replace MediaControl.
Currently, I have my app set up with:-
systemControls = SystemMediaTransportControls.GetForCurrentView();
systemControls.IsPlayEnabled = true;
systemControls.IsStopEnabled = true;
systemControls.IsPauseEnabled = true;
systemControls.ButtonPressed += SystemControls_ButtonPressed;
And
async void SystemControls_ButtonPressed(SystemMediaTransportControls sender, SystemMediaTransportControlsButtonPressedEventArgs args)
{
System.Diagnostics.Debug.WriteLine(args.Button);
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
switch (args.Button)
{
case SystemMediaTransportControlsButton.Play:
if (mediaElement1.CurrentState != MediaElementState.Playing)
{
restartSource();
}
else
{
completeClosure();
}
break;
case SystemMediaTransportControlsButton.Pause:
case SystemMediaTransportControlsButton.Stop:
completeClosure();
break;
default:
break;
}
});
}
And:
private async void completeClosure()
{
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
mediaElement1.Stop();
mediaElement1.Source = null;
timer.Stop();
});
}
private async void restartSource()
{
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
mediaElement1.Source = new Uri(holdThisSource, UriKind.Absolute);
mediaElement1.Play();
timer.Start();
});
}
When a user presses the Pause Button, args.Button shows up as "Play", hence the need for the checking for MediaElement's state. However, when I attempt to resume to media, it successfully resumes in restartSource() and updates the app accordingly but the icon on the Volume Control does not change from the Play sign, although hardware buttons still work.
Along with this, pressing the hardware Stop button NEVER works, and fails to even show up in Debug.WriteLine.
This is an online streaming app where the source does not allow resuming and thus I have to close the stream this way.
I'd love some help on this.
Since you did not update the systemControls.PlaybackStatus, the control button on transport control will not auto change to correct status.
You should always update the systemControls.PlaybackStatus property when the playback state has changed.
May this could solve your problems.