Xamarin C# - Android - Prevent an AlertDialog from closing on PositiveButton click - c#

I'm new to Xamarin and I don't know how to do the following in c#. I want to prevent an alertdialog from closing when clicking on the Positive/Negative buttons. I need to do some validation on the input first. If the input is correct, the dialog can close, else I will show a message with instructions. Basically, I have the following code:
private void CreateAddProjectDialog() {
//some code
var alert = new AlertDialog.Builder (this);
alert.SetTitle ("Create new project");
alert.SetView (layoutProperties);
alert.SetCancelable (false);
alert.SetPositiveButton("Create", HandlePositiveButtonClick);
alert.SetNegativeButton("Cancel", HandelNegativeButtonClick);
}
private void HandlePositiveButtonClick (object sender, EventArgs e) {
//Do some validation here and return false (prevent closing of dialog) if invalid, else close....
}
Now, I red the following post on StackOverflow: How to prevent a dialog from closing when a button is clicked
I think the code below (taken from the thread) has the solution, but I don't know how to rewrite my c# code to implement the Java:
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setMessage("Test for preventing dialog close");
builder.setPositiveButton("Test",
new DialogInterface.OnClickListener()
{
#Override
public void onClick(DialogInterface dialog, int which)
{
//Do nothing here because we override this button later to change the close behaviour.
//However, we still need this because on older versions of Android unless we
//pass a handler the button doesn't get instantiated
}
});
AlertDialog dialog = builder.create();
dialog.show();
//Overriding the handler immediately after show is probably a better approach than OnShowListener as described below
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(new View.OnClickListener()
{
#Override
public void onClick(View v)
{
Boolean wantToCloseDialog = false;
//Do stuff, possibly set wantToCloseDialog to true then...
if(wantToCloseDialog)
dismiss();
//else dialog stays open. Make sure you have an obvious way to close the dialog especially if you set cancellable to false.
}
});
How to code this in c#? Especially the override part in the setPositiveButton area...

This requires to think a bit outside the box. You will have to manipulate the AlertDialog object directly:
// Build the dialog.
var builder = new AlertDialog.Builder(this);
builder.SetTitle("Click me!");
// Create empty event handlers, we will override them manually instead of letting the builder handling the clicks.
builder.SetPositiveButton("Yes", (EventHandler<DialogClickEventArgs>)null);
builder.SetNegativeButton("No", (EventHandler<DialogClickEventArgs>)null);
var dialog = builder.Create();
// Show the dialog. This is important to do before accessing the buttons.
dialog.Show();
// Get the buttons.
var yesBtn = dialog.GetButton((int)DialogButtonType.Positive);
var noBtn = dialog.GetButton((int)DialogButtonType.Negative);
// Assign our handlers.
yesBtn.Click += (sender, args) =>
{
// Don't dismiss dialog.
Console.WriteLine("I am here to stay!");
};
noBtn.Click += (sender, args) =>
{
// Dismiss dialog.
Console.WriteLine("I will dismiss now!");
dialog.Dismiss();
};

Related

Using C# & Xamarin Forms - How can I close one modal without setting of chain of closures

In one stage of my app (Android & iOS are the ones we care about) we've got three pages which take in details and then open a webView for the user to input their card details to take a payment - this can't be done in the app due to Apple's guidelines.
I need to format the navigation in a way that when the user has finished in the webView it closes and then closes the 3 previous modals to get back to the original page. I've got it all working with the Appearing event so each page just closes itself:
this.Appearing += async (s, e) =>
{
await Navigation.PopModalAsync();
};
The issue I'm now having is that when the user presses the back button on the phone, it closes all of the pages that they've been through already & back to the original. I thought about implementing a custom nav bar and disabling the back button on the hardware but this would cause the same problem with the Appearing event.
Is there any easy way to solve this?
EDIT: Relevant code;
async void OnButtonClicked(object sender, EventArgs eventArgs)
{
if (IsConnected)
{
ActivityIndicator.IsVisible = true;
var button = (Button) sender;
button .IsEnabled = false;
await Navigation.PushModalAsync(new Page());
this.Appearing += (s, e) =>
{
ActivityIndicator.IsVisible = false;
button.IsEnabled = true;
RefreshPage();
};
}
else
{
NoInternetLabel.IsVisible = true;
}
}
Use this:
YourButton.Clicked += OpenPage;
OpenPage looks like this:
async public void OpenPage(object sender, EventArgs args)
{
await Navigation.PushAsync(new PageToShow());
}
You don't have to do anything to handle the PageToShow() closing, that happens by itself when the user presses the back button.
Managed to solve this by using Actions. In each new Page() we passed up an async method to close it once the one after had completed;
var nextPage = new Page(async () =>
{
await Navigation.PopModalAsync();
_completedSuccessfully();
});
await Navigation.PushModalAsync(nextPage);
And in the new page class;
private readonly Action _completedSuccessfully;
public Page(Action completedSuccessfully)
{
_completedSuccessfully = completedSuccessfully;
}
This meant that when the webView closed it called the completedSuccessfully() action and then chained all of them to the original page.

How to handle SecondaryTile click event in windows apps

Present i am working with SecondaryTile, i want to fire an event when tile is clicked, is there any possibility for that please help me..
Here is my code,
SecondaryTile initialData = new SecondaryTile();
initialData = new SecondaryTile(
ShowID,
ojsShow.Title,
"NoArguments",
new Uri("ms-appx:///Images/" + ojsShow.TileImage),
TileSize.Square150x150);
initialData.VisualElements.ShowNameOnSquare150x150Logo = true;
await initialData.RequestCreateAsync();
In app.xaml you can find whether clicked title or not
protected override void OnActivated(IActivatedEventArgs args)
{
var data= e.TileId;
if(data=="App")
{
//code for normal app start...
}
else
{
//code for tile click..
//you can see the tile in data parameter
}
}
The only thing you can do is to open the app and send some parameters to take action/ handle it..
for the SecondaryTile, I believe you can set the page/ parameters by your new instance ..
for the handling process, you can override the OnActivated event and check the parameters and the page:
protected override void OnActivated(IActivatedEventArgs args)

How to Capture 'Send' button event for Outlook using UI Automation in C#?

I want to capture 'Send' button event of outlook using UI Automation.
Right now i am able to get 'Focus Change Event' like whenever iam minimizing or maximizing the WINWORD window the the event is raised instead of that i want to get the event on Send button click.
private void SendButtonInvoke()
{
Process[] processes = Process.GetProcessesByName("WINWORD");
AutomationElement aeOutLook = null;
foreach (var item in processes)
{
aeOutLook = AutomationElement.FromHandle(item.MainWindowHandle);
}
//AutomationElement outlookelm = AutomationElement.FromHandle(processName.MainWindowHandle);
AutomationElement buttonAddInstance = aeOutLook.FindFirst(TreeScope.Descendants,
new PropertyCondition(AutomationElement.NameProperty, "Send"));
if (buttonAddInstance == null)
{
MessageBox.Show("Add button instance not found");
}
else
{
AutomationPropertyChangedEventHandler ButtonEvent =
new AutomationPropertyChangedEventHandler(ButtonChecked_EventHandler);
//Attaching the EventHandler
Automation.AddAutomationPropertyChangedEventHandler(buttonAddInstance, TreeScope.Children,
ButtonEvent, AutomationElement.NameProperty);
}
}
private void ButtonChecked_EventHandler(object sender, AutomationEventArgs e)
{
AutomationElement ar = sender as AutomationElement;
MessageBox.Show("Button Clicked Sucessfully.");
}
You have to specifiy the EventHandler for the involved UIA Pattern. (For your case it's likely to be the InvokePattern):
Automation.AddAutomationEventHandler(InvokePattern.InvokedEvent, AutomationElement buttonAddInstance ,TreeScope.Element, new AutomationEventHandler(OnStartInvoke));
private static void OnStartInvoke(object src, AutomationEventArgs e)
{
//logic
}
I wrote and tested the code below and it seems to work for me.
private void AddEmailSendEvent()
{
// Find the new email window
PropertyCondition newEmailWindowCondition = new PropertyCondition(AutomationElement.NameProperty, "Untitled - Message (HTML) ");
AutomationElement NewEmailWindow = AutomationElement.RootElement.FindFirst(TreeScope.Children, newEmailWindowCondition);
// Find the Send Button
PropertyCondition sendEmailButtonCondition = new PropertyCondition(AutomationElement.NameProperty, "Send");
AutomationElement sendButton = NewEmailWindow.FindFirst(TreeScope.Descendants, sendEmailButtonCondition);
// If supported, add the invoke event
if (sendButton.GetSupportedPatterns().Any(p => p.Equals(InvokePattern.Pattern)))
Automation.AddAutomationEventHandler(InvokePattern.InvokedEvent, sendButton, TreeScope.Element, handler);
}
private void handler(object sender, AutomationEventArgs e)
{
// Do whatever is needed, for testing this just adds a message to my forms Main UI
AddMessage("Invoke event occured");
}
I should note that I'm using the .Net 4.0 automation libs. I've found the older ones don't always work the way I want them. I also tested this with Outlook 2013, and both outlook and the new email message were already open when I tested this. It doesn't handle waiting for them to appear.
Just so your aware, these events don't always work for all controls. Some custom controls are made in such a way the invoke events are not reported to the UI in a way the event can register. With that said, from my testing you should be able to use this method on the send button.
Invoking vs mouse clicks: Just to add a little more detail, the standard control causes the invoke event to fire when a user clicks it. "Invoke" is just the standard event fired on clickable controls. The only time a click wouldn't fire the same invoke is if the developer decided to intercept the click somehow and redirect it elsewhere. I've seen this a lot when people build there own custom controls.
If your not sure about whether a control using/firing the invoke event or not you can get use the Accessible Event Watcher to watch a control as you click it. You can get more information on the tool here: https://msdn.microsoft.com/en-us/library/windows/desktop/dd317979(v=vs.85).aspx

"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;
}

CustomMessageBox from Windows Phone 8 Toolkit throw NullPointerException

Code for creating the CustomMessageBox:
CustomMessageBox is a property, and not a reference to the C# Class in the Toolkit.
CustomMessageBox.Dismissed += (dismissSender, dismissedEvent) =>
{
switch (dismissedEvent.Result)
{
case CustomMessageBoxResult.LeftButton:
PlaceCall(clickedFavorite.Name, clickedFavorite.PhoneNo);
break;
case CustomMessageBoxResult.RightButton:
HERE ---> SendText(clickedFavorite.PhoneNo);
break;
}
};
Code for SendText() method:
private void SendText(String phoneNo)
{
var smsTask = new SmsComposeTask
{
To = phoneNo
};
smsTask.Show();
}
Thing is when the SmsComposeTask has started, the Phone navigates to the SMS application, which is correct.
If the user then decides to go back, with the Hardware Back Button, the SMS application closes and the phone shows my app again - but immediately closes, caused by a NullPointerException:
at Microsoft.Phone.Controls.CustomMessageBox.ClosePopup(Boolean restoreOriginalValues)
at Microsoft.Phone.Controls.CustomMessageBox.<>c__DisplayClass4.<Dismiss>b__1(Object s, EventArgs e)
at Microsoft.Phone.Controls.Transition.OnCompleted(Object sender, EventArgs e)
at MS.Internal.CoreInvokeHandler.InvokeEventHandler(Int32 typeIndex, Delegate handlerDelegate, Object sender, Object args)
at MS.Internal.JoltHelper.FireEvent(IntPtr unmanagedObj, IntPtr unmanagedObjArgs, Int32 argsTypeIndex, Int32 actualArgsTypeIndex, String eventName)
I have also tried to override the OnBackKeyPress event, like this:
protected override void OnBackKeyPress(System.ComponentModel.CancelEventArgs e)
{
if (CustomMessageBox != null && CustomMessageBox.IsEnabled)
{
e.Cancel = true;
}
else
{
base.OnBackKeyPress(e);
}
}
Does anyone know what to do?
I have found a solution to my own problem. Instead of using the faulty CustomMessageBox, I found Coding4Fun Windows Phone Toolkit which provides a by far, more stable message box called MessagePrompt - here's how to use it.
Create buttons
var smsButton = new Button { Content = "SMS" };
smsButton.Click += (o, args) =>
{
// do something
};
var buttonList = new List<Button>
{
smsButton
};
Create the actual message prompt
var msgPrompt = new MessagePrompt
{
Title = "Message Prompt Title",
Body = new TextBlock { Text = "Text for the Body", FontSize = 25, TextWrapping = TextWrapping.Wrap },
ActionPopUpButtons = buttonList
};
Show it
msgPrompt.Show()
No bullocks
The good thing, which I have experienced with this MessagePrompt is that you are not bound to two static Left and Right buttons like with CustomMessageBox.
And if you want, you can set the Body property to a whole new XAML page, which makes this control flexible.
Reference: Coding4Fun WP7 Message Prompt in depth
Doesn't this problem has something to do with Windows Phone Application lifecycle. As can be found here, figure 6. When activiting another program when your program is active you should save all application data so when a reactivating event ,such as navigating with your back button back to your application, starts your program again you can load the user's data again.
I'm not sure what's happening, but you can just delay the SMS task to avoid the issue:
CustomMessageBox.Dismissed += (dismissSender, dismissedEvent) =>
{
switch (dismissedEvent.Result)
{
case CustomMessageBoxResult.LeftButton:
PlaceCall(clickedFavorite.Name, clickedFavorite.PhoneNo);
break;
case CustomMessageBoxResult.RightButton:
Dispatcher.BeginInvoke(new Action(() => SendText(clickedFavorite.PhoneNo)));
break;
}
};
My 0.02$: this is a bug in the CustomMessageBox. They're keeping lots of singletons alive there and a good timing bug doesn't do that a world of good. Agreed with KooKiz that you can't work around with that without either fixing CustomMessageBox or waiting until the CustomMessageBox finishes its thing. From my ad-hoc testing it requires anywhere between 2-6 Dispatcher.BeginInvoke() until those actions finish. Instead, maybe consider using DispatcherTimer and wait 256MS which should be enough time.
private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
var msgBox = new CustomMessageBox()
{
Caption = "foo",
Message = "bar",
LeftButtonContent = "baz",
RightButtonContent = "goo",
IsFullScreen = false,
};
msgBox.Dismissed += (s, args) =>
{
DispatcherTimerHelper.InvokeReallySoon(() =>
{
new SmsComposeTask()
{
Body = "foo",
To = "bar"
}.Show();
});
};
msgBox.Show();
}
public static class DispatcherTimerHelper
{
public static void InvokeReallySoon(Action action)
{
var t = new DispatcherTimer() {Interval = TimeSpan.FromMilliseconds(256)};
t.Tick += (s, args) => action();
t.Start();
}
}
The problem just happen in wp8.
I use the same code in wp7, nothing wrong happens.
Use Code4fun messagebox is a good choice,but is Button Click handler you need to call
MessagePrompt.Hide();
to close the MessagePrompt.
used a boolean on the dismissed event to define which button had been pressed. I then implemented the code I would of implemented in the dismissed event in the Unloaded event instead. This seemed to solve the issue.
i.e
messageBox.Dismissed += (s1, e1) =>
{
switch (e1.Result)
{
case CustomMessageBoxResult.LeftButton:
{
delete = true ;
}
break;
case CustomMessageBoxResult.RightButton:
break;
case CustomMessageBoxResult.None:
break;
default:
break;
}
};
messageBox.Unloaded += (s1, e1) =>
{
if (delete)
DeleteWorkout();
};
This is a known bug.
It was fixed in the latest version.
Remove the reference and install the toolkit again.

Categories

Resources