Hide all visible Metro Dialogs before showing another one - c#

I'm using MahApps.Metro on my WPF project, and I am building a class to help me showing Dialogs. I would like to know if there's a way of closing all visible dialogs before showing up another one.
Sometimes, when I show a ProgressDialog and then a MessageDialog the ProgressDialog isn't correctly closed, and stays in the background, so when I close the MessageDialog, it stays there freezing the UI.
Here's how I'm currently trying to hide all Dialogs:
public static async void HideVisibleDialogs(MetroWindow parent)
{
BaseMetroDialog dialogBeingShow = await parent.GetCurrentDialogAsync<BaseMetroDialog>();
while (dialogBeingShow != null)
{
await parent.HideMetroDialogAsync(dialogBeingShow);
dialogBeingShow = await parent.GetCurrentDialogAsync<BaseMetroDialog>();
}
}
I call it like this:
public static MessageDialogResult ShowMessage(String title, String message, MetroWindow parent, Int32 timeout, MessageDialogStyle style, MetroDialogSettings settings, MessageDialogResult defaultResult)
{
AutoResetEvent arEvent = new AutoResetEvent(false);
App.Current.Dispatcher.Invoke(() =>
{
HideVisibleDialogs(parent);
arEvent.Set();
});
arEvent.WaitOne();
[Rest of method]
}
Any help is appreciated. Thank you!
#EDIT
Apparently, the problem seems to be solved, thanks to Thomas Freudenberg
This is how it is now:
public static Task HideVisibleDialogs(MetroWindow parent)
{
return Task.Run(async () =>
{
await parent.Dispatcher.Invoke(async () =>
{
BaseMetroDialog dialogBeingShow = await parent.GetCurrentDialogAsync<BaseMetroDialog>();
while (dialogBeingShow != null)
{
await parent.HideMetroDialogAsync(dialogBeingShow);
dialogBeingShow = await parent.GetCurrentDialogAsync<BaseMetroDialog>();
}
});
});
}
And I call it like this:
HideVisibleDialogs(parent).Wait();

HideVisibleDialogs is an async method. I'd try to change its return type to Task and wait for it, i.e. HideVisibleDialogs(parent).Wait(). Otherwise the call would immediately return.

Related

Pause a for loop until external callback method is called

Non-technical explanation of what I'm trying to do:
I have a bot that sends a user a picture, when the user clicks the "done" button it sends him the next picture, and so on until there are no more pictures.
Problem:
The only way to listen to that button using the API I'm using is by using this method:
private static async void BotOnCallbackQueryReceived(object sender, CallbackQueryEventArgs callbackQueryEventArgs) {
var callbackQuery = callbackQueryEventArgs.CallbackQuery;
try {
await Task.Run(() => {
Bot.AnswerCallbackQueryAsync(callbackQuery.Id, "Answer has been submitted");
string data = callbackQuery.Data.ToLower();
if (data.Equals("notfound")){
//TODO He couldn't find the item
}
else if (data.Equals("done")) {
}
});
}
}
And of course, subscribe to it via:
Bot.OnCallbackQuery += BotOnCallbackQueryReceived;
To send the image and menu, I have a for loop that covers a List<Item> (Item is a POCO).
await Task.Run(() => {
// - Which buttons to send. MenuItem's constructor is (ButtonText, Callbackdata)
List<MenuItem> menuItems = new List<MenuItem>();
menuItems.Add(new MenuItem("Done", "done"));
menuItems.Add(new MenuItem("Not found", "notFound"));
//telegramLineItemObjects is a List<Item>
foreach (Item item in telegramLineItemObjects) {
string message = $"<b>Shelf Number:</b> {item.ShelfNumber}\n<b>Sku:</b> {item.Sku}\n<b>Barcode:</b> {item.Barcode}\n<b>Description:</b> {item.Description}\n<b>Quantity:</b> {item.Quantity}\n";
//sendImage(Image URL, message, message parse mode)
telegram.sendImage(item.Picture, message, ParseMode.HTML);
Message sentMenu = telegram.sendMenu(message, menuItems);
}
}
I want to pause the for loop, wait for BotOnCallBackQueryReceived to be called, then resume the for loop.
To do that, I need a way to listen for when BotOnCallBackQueryReceived is called. How do I do that?

Action after ActivateItem in Caliburn.Micro

I have a question. I have button where i launch ActivateItem:
public void LoadTaskManagerPage()
{
this.ActivateItem(new TaskManagerViewModel(this.LoggedUser, this.repository));
if (this.LoggedUser.GetUserTask() != null)
{
this.IsActiveTaskButtonVisible = Visibility.Visible;
this.NotifyOfPropertyChange(() => this.IsActiveTaskButtonVisible);
}
}
Is it possible to hang an app and go to if statement only, if ActivateItem ends?
How to wait for end of ActivateItem in Caliburn.Micro?
EDIT:
Trying something like:
public void LoadTaskManagerPage()
{
var taskManagerTask = Task.Run(() => this.ActivateItem(new TaskManagerViewModel(this.LoggedUser, this.repository)));
taskManagerTask.Wait();
if (!this.LoggedUser.GetUserTask().IsTaskTakenByUser())
{
this.IsActiveTaskButtonVisible = Visibility.Visible;
this.NotifyOfPropertyChange(() => this.IsActiveTaskButtonVisible);
}
}
With Tasks, but when i click on LoadTaskManagerPage() it wont show any window, application hangs forever
EDIT2
Based on my issue on github, i upgraded Caliburn to alpha 4.0:
public void LoadTaskManagerPage()
{
this.ActivateItemAsync(new TaskManagerViewModel(this.LoggedUser, this.repository));
if (!this.LoggedUser.GetUserTask().IsTaskTakenByUser())
{
//logic
}
}
i changed ActiveItem() to ActiveItemAsync() but still, it hit if statement before active item will close. using async/await do the same
EDIT3
When i make async/await it still works the same, look at my view on the right. Its clicked, User Control not appear. In the same time (even when my User Control not show up) im hitting if statement too early. I want to hit it on User Control close.
You should await the ActivateItemAsync method. This means that your LoadTaskManagerPage() should return a Task and you should await this one as well:
public async Task LoadTaskManagerPageAsynnc()
{
await this.ActivateItemAsync(new TaskManagerViewModel(this.LoggedUser, this.repository));
if (!this.LoggedUser.GetUserTask().IsTaskTakenByUser())
{
//logic
}
}

How to update UI in button click before and after task runs

I have a Web Forms app where I need to show a progress spinner when button is clicked, and hide it when a task finishes executing. I have tried a bunch of different ways to do this. I can only get either the hide or show functionality to work properly but not both. For the code below, the spinner isn't shown until after the page reloads (I tested this by removing the hide spinner part). So basically, the hiding part occurs and works as expected but the show part does not.
As a side note, the page is in a re-load state for the duration of this method. Thanks for any help!
protected async void btnPullQBData_Click(object sender, EventArgs e)
{
loadingContainer.Visible = true;
string type = "";
string response = "";
response = await SyncData();
loadingContainer.Visible = false;
}
public Task<string> SyncData()
{
return Task.Run(() =>
{
QBService service = new QBService(User.Identity.GetUserId());
string response = service.RefreshDBFromQB();
return response;
});
}

"Only a single ContentDialog can be open at any time." error while opening another contentdialog

I am using Windows.UI.Xaml.Controls.ContentDialog to show a confirmation. And based on the response from the first dialog I would (or would not) show another dialog. But, when I am trying to open the second content dialog it throws : "Only a single ContentDialog can be open at any time." error. Even though in the UI, first dialog would be closed but somehow I am still not able to open the second dialog. Any idea?
I have created some code to handle this type of conundrum in my Apps:
public static class ContentDialogMaker
{
public static async void CreateContentDialog(ContentDialog Dialog, bool awaitPreviousDialog) { await CreateDialog(Dialog, awaitPreviousDialog); }
public static async Task CreateContentDialogAsync(ContentDialog Dialog, bool awaitPreviousDialog) { await CreateDialog(Dialog, awaitPreviousDialog); }
static async Task CreateDialog(ContentDialog Dialog, bool awaitPreviousDialog)
{
if (ActiveDialog != null)
{
if (awaitPreviousDialog)
{
await DialogAwaiter.Task;
DialogAwaiter = new TaskCompletionSource<bool>();
}
else ActiveDialog.Hide();
}
ActiveDialog = Dialog;
ActiveDialog.Closed += ActiveDialog_Closed;
await ActiveDialog.ShowAsync();
ActiveDialog.Closed -= ActiveDialog_Closed;
}
public static ContentDialog ActiveDialog;
static TaskCompletionSource<bool> DialogAwaiter = new TaskCompletionSource<bool>();
private static void ActiveDialog_Closed(ContentDialog sender, ContentDialogClosedEventArgs args) { DialogAwaiter.SetResult(true); }
}
To use these Methods, you need to create the ContentDialog and its content in a variable, then pass the variable, and bool into the Method.
Use CreateContentDialogAsync(), if you require a callback in your app code, say if you have a button in your Dialog, and you want wait for a button press, and then get the value from the form in code after the dialog.
Use CreateContentDialog(), if you don't need to wait for the Dialog to complete in your UI Code.
Use awaitPreviousDialog to wait for the previous dialog to complete before showing the next Dialog, or set false, to remove the previous Dialog, then show the next Dialog, say, if you want to show an Error Box, or the next Dialog is more important.
Example:
await ContentDialogMaker.CreateContentDialogAsync(new ContentDialog
{
Title = "Warning",
Content = new TextBlock
{
Text = "Roaming Appdata Quota has been reached, if you are seeing this please let me know via feedback and bug reporting, this means that any further changes to data will not be synced across devices.",
TextWrapping = TextWrapping.Wrap
},
PrimaryButtonText = "OK"
}, awaitPreviousDialog: true);
William Bradley's approach above is good. Just to polish it up a bit, here is an extension method to submit and await the showing of a content dialog; the dialog will be shown after all the other content dialogs that have already been submitted. Note: by the time the user clicks through earlier backlogged dialogs you may no longer want to show the dialog that you have submitted; to indicate this you may pass a predicate that will be tested after the other dialogs have been dismissed.
static public class ContentDialogExtensions
{
static public async Task<ContentDialogResult> EnqueueAndShowIfAsync( this ContentDialog contentDialog, Func<bool> predicate = null)
{
TaskCompletionSource<Null> currentDialogCompletion = new TaskCompletionSource<Null>();
TaskCompletionSource<Null> previousDialogCompletion = null;
// No locking needed since we are always on the UI thread.
if (!CoreApplication.MainView.CoreWindow.Dispatcher.HasThreadAccess) { throw new NotSupportedException("Can only show dialog from UI thread."); }
previousDialogCompletion = ContentDialogExtensions.PreviousDialogCompletion;
ContentDialogExtensions.PreviousDialogCompletion = currentDialogCompletion;
if (previousDialogCompletion != null) {
await previousDialogCompletion.Task;
}
var whichButtonWasPressed = ContentDialogResult.None;
if (predicate == null || predicate()) {
whichButtonWasPressed = await contentDialog.ShowAsync();
}
currentDialogCompletion.SetResult(null);
return whichButtonWasPressed;
}
static private TaskCompletionSource<Null> PreviousDialogCompletion = null;
}
Another way might be to use a SemaphoreSlim(1,1).
"Only a single ContentDialog can be open at a time"
This statement is not entirely true. You can only ShowAsync one ContentDialog at a time. All you need to do is hide the current ContentDialog before opening another one. Then, after the "await ShowAsync" of the second ContentDailog, your simply call "var T = this.ShowAync()" to unhide it. Example:
public sealed partial class MyDialog2 : ContentDialog
{
...
}
public sealed partial class MyDialog1 : ContentDialog
{
...
private async void Button1_Click(object sender, RoutedEventArgs e)
{
// Hide MyDialog1
this.Hide();
// Show MyDialog2 from MyDialog1
var C = new MyDialog2();
await C.ShowAsync();
// Unhide MyDialog1
var T = ShowAsync();
}
}
I know this is slightly old, but one simpler solution instead of going through all this pain is to just register a callback for the ContentDialog_Closed event. By this point you can be sure the previous dialog has been closed, and can open your next dialog. :)
Only a single ContentDialog can be open at any time.
That is a fact. (I was really surprised, but just for a moment)
You can't have more than one at any time and it is more like guideline from Microsoft, because it's really messy to have multiple dialogs on top of each other filled with content.
Try to change your UX to display only one sophisticated ContentDialog and for all other messages use MessageDialog - it supports multiple buttons(only two for phones, but more on desktop) for user response but without Checkboxes or similar "smart"-content stuff.
In my case MessageDialogs were really helpful, but in some areas I used chained ContentDialogs but for that you must await the first one, and open second right after without any exceptions. In your case it seems like ContentDialog was not fully closed when you tried to open next one.
Hope it helps!
I like this answer https://stackoverflow.com/a/47986634/942855, this will allow us ot handle binding all events.
So extended it a little to check the multiple calls to show dialog.
private int _dialogDisplayCount;
private async void Logout_OnClick(object sender, RoutedEventArgs e)
{
try
{
_dialogDisplayCount++;
ContentDialog noWifiDialog = new ContentDialog
{
Title = "Logout",
Content = "Are you sure, you want to Logout?",
PrimaryButtonText = "Yes",
CloseButtonText = "No"
};
noWifiDialog.PrimaryButtonClick += ContentDialog_PrimaryButtonClick;
//await noWifiDialog.ShowAsync();
await noWifiDialog.EnqueueAndShowIfAsync(() => _dialogDisplayCount);
}
catch (Exception exception)
{
_rootPage.NotifyUser(exception.ToString(), NotifyType.DebugErrorMessage);
}
finally
{
_dialogDisplayCount = 0;
}
}
modified predicate
public class Null { private Null() { } }
public static class ContentDialogExtensions
{
public static async Task<ContentDialogResult> EnqueueAndShowIfAsync(this ContentDialog contentDialog, Func<int> predicate = null)
{
TaskCompletionSource<Null> currentDialogCompletion = new TaskCompletionSource<Null>();
// No locking needed since we are always on the UI thread.
if (!CoreApplication.MainView.CoreWindow.Dispatcher.HasThreadAccess) { throw new NotSupportedException("Can only show dialog from UI thread."); }
var previousDialogCompletion = _previousDialogCompletion;
_previousDialogCompletion = currentDialogCompletion;
if (previousDialogCompletion != null)
{
await previousDialogCompletion.Task;
}
var whichButtonWasPressed = ContentDialogResult.None;
if (predicate == null || predicate() <=1)
{
whichButtonWasPressed = await contentDialog.ShowAsync();
}
currentDialogCompletion.SetResult(null);
return whichButtonWasPressed;
}
private static TaskCompletionSource<Null> _previousDialogCompletion;
}

Show and hide Statusbar in Windows Phone 8.1 just work first time

I´m using statusbar when loading things from internet into the app. I have two functions in App.xaml.cs which I use to hide and show but when I have called HideProgressAsync() once, next time I call ShowProgressAsync the status bar is not showed. What am I doing wrong? Missing a flag?
public async Task ShowProgressAsync(string message)
{
var statusBar = Windows.UI.ViewManagement.StatusBar.GetForCurrentView();
statusBar.ProgressIndicator.Text = message;
await statusBar.ProgressIndicator.ShowAsync();
}
public async Task HideProgressAsync()
{
var statusBar = Windows.UI.ViewManagement.StatusBar.GetForCurrentView();
await statusBar.HideAsync();
}
Code issue, in code above I hide the whole statusBar. Below is correct code
public async Task HideProgressAsync()
{
var statusBar = Windows.UI.ViewManagement.StatusBar.GetForCurrentView();
await statusBar.ProgressIndicator.HideAsync();
}

Categories

Resources