DismissViewController sometimes causes the program to freeze or crash - c#

I have the following MonoTouch program:
using System.Drawing;
using MonoTouch.Foundation;
using MonoTouch.UIKit;
using System;
namespace Experiment
{
// This is just for the example, I use a singleton in my app to do this.
public class AppSettings
{
public static bool IsLoggedIn = false;
}
[Register ("AppDelegate")]
public class AppDelegate : UIApplicationDelegate
{
UIWindow window;
public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
{
UIViewController baseViewController = new RootViewController();
window = new UIWindow(UIScreen.MainScreen.Bounds);
window.AddSubview(baseViewController.View);
window.MakeKeyAndVisible();
return true;
}
static void Main(string[] args)
{
UIApplication.Main(args, null, "AppDelegate");
}
}
public class RootViewController : UITableViewController
{
public override void ViewDidAppear(bool animated)
{
base.ViewDidAppear(animated);
if (!AppSettings.IsLoggedIn) {
this.PresentViewController(new LoginPopupController(), false, () => {});
}
}
}
public class LoginPopupController : UIViewController
{
public override void LoadView()
{
View = new UIView(UIScreen.MainScreen.ApplicationFrame);
View.BackgroundColor = UIColor.White;
UIButton button = new UIButton();
button.SetTitle("press me", UIControlState.Normal);
button.Frame = new RectangleF(50, 50, 80, 80);
button.TouchUpInside += delegate {
LoginSucceeded();
};
button.BackgroundColor = UIColor.Purple;
View.AddSubview(button);
}
public void LoginSucceeded()
{
AppSettings.IsLoggedIn = true;
Console.WriteLine("This line causes multiple problems at random");
DismissViewController(true, () => {});
}
}
}
It has two controllers the RootViewController and the LoginPopupController. The FinishedLoading method adds the RootViewController to the window. The RootViewController in the ViewDidAppear method checks if the app is logged in. If not, it will present the LoginPopupController.
The LoginPopupController has a button, which when pressed will set IsLoggedIn to true, and dismiss itself.
Basically, I want a login window to appear if the user isn't logged in, and then dismiss itself after it has entered login details into a singleton settings object.
However, the app is very unreliable at the moment. Pressing the "press me" button can cause the following to happen at random:
Work as expected
Freeze the app
Crash the app
Do nothing on first press, crash the app on the second press
The Console.WriteLine line has a big effect on this - if you remove it the code succeeds more often (but not always).
Can anyone figure out what is causing this problem? It seems like a race condition (as the results change between runs), however I can't figure out what could be causing that.
I'm running the code on the simulator using iOS 5.1. I have MonoTouch version 5.2.10 installed with Mono 2.10.8.

I have done two changes in your code and it never crashed, at least for my number of tries.
First, move the UIButton declaration to the class:
UIButton button;
public override LoadView()
{
//...
}
Second, initialize the button with the UIButton.FromType(UIButtonType) static method:
button = UIButton.FromType(UIButtonType.Custom);
The UIButton() constructor had problems in previous versions of MonoTouch. In some versions it was not even available. I could not find anything specific for its current state, however creating buttons with the static method is the normal way to go, according to Apple docs.

Related

C# How to replace an Event by a Delegate?

Mac OS, VS Community, C#, Cocoa application.
The following code generates a run time error.
using AppKit;
using CoreGraphics;
using Foundation;
using System;
namespace NSTextFieldValidation
{
public partial class ViewController : NSViewController
{
public ViewController(IntPtr handle) : base(handle) { }
public override void ViewDidLoad()
{
base.ViewDidLoad();
var fm = new NSNumberFormatter { Maximum = 999, Minimum = 0 };
NSTextField Tbx1 = new NSTextField(new CGRect(10, 50, 100, 30));
View.AddSubview(Tbx1);
Tbx1.Formatter = fm;
Tbx1.Delegate = new MyTextFieldDelegate();
Tbx1.DidFailToValidatePartialString += TbxDidFailToValidatePartialString;
}
private void TbxDidFailToValidatePartialString(object sender, NSControlTextErrorEventArgs e)
{
Console.WriteLine("DidFailToValidatePartialString activated");
}
}
public class MyTextFieldDelegate : NSTextFieldDelegate
{
override public void EditingEnded(NSNotification notification)
{
Console.WriteLine("EditingEnded");
}
}
}
Here's the exception:
System.InvalidOperationException
Event registration is overwriting existing delegate. Either just use
events or your own delegate: NSTextFieldValidation.MyTextFieldDelegate
AppKit.NSTextField+_NSTextFieldDelegate
I understand that it is not correct to use event and delegate at the same time. I only ask which method I need to override in my delegate to deal with "DidFailToValidatePartialString", because I did not find it (perhaps I did not look well).
In the Delegate class, the DidFailToValidatePartialString method is not declared like others (i.e EditingEnded). We need to add an Export attribute:
[Export("control:didFailToValidatePartialString:errorDescription:")]
I would like the Xamarin documentation to explain this.

Android 8 receive of stop receiving media buttons events

I have a service that listens to media buttons, but I only want to listen to those when my service is running since I have a button on the UI to start/stop the service.
How can I achieve the following two actions:
On MyService start-up, start receiving media button events to com.myApp/MyService.
On MyService end, stop receiving media button events in com.myApp/MyService.
The related logs are the following:
D MediaSessionService: Sending KeyEvent { action=ACTION_DOWN, keyCode=KEYCODE_HEADSETHOOK, scanCode=226, metaState=0, flags=0x8, repeatCount=0, eventTime=13258317, downTime=13258317, deviceId=4, source=0x101 } to com.myApp/MyService (userId=0)
Note:
I found out that I can start receiving media events after my app started to play audio. This is however not ideal, since I don't want to play audio in my app. (I am only using media events in order to trigger voice recognition)
After I played audio, my service is apparently the default media receiver. This means that when a media button is pressed, my service get instantiated, does nothing, and get destroyed. This is not an idea behavior for the end user since my app ends up 'stealing' these events that could potentially be handled by something else.
Overriding OnStartCommand() in my service does not help
Calling SetMediaButtonReceiver on the MediaSession does not help either
Using a BroadcastReceiver with the intent "android.intent.action.MEDIA_BUTTON" registered in the AudioManager does not work either
Relevant code (in C#, I am on Xamarin.Forms, but that should not have any impact on the way to achieve this)
public class MediaSessionCompatCallback : MediaSessionCompat.Callback
{
public Func<Intent, bool> MediaButtonEvent { get; set; }
public override bool OnMediaButtonEvent(Intent mediaButtonEvent) => MediaButtonEvent?.Invoke(mediaButtonEvent) ?? false;
}
[Service(Exported = true, Enabled = true)]
[IntentFilter(new[] { ServiceInterface })]
public class MediaBrowserService : MediaBrowserServiceCompat, AudioManager.IOnAudioFocusChangeListener
{
private MediaSessionCompat mediaSession;
private MediaSessionCompatCallback mediaSessionCallback;
private void CreateMediaSessionCallback()
{
mediaSessionCallback = new MediaSessionCompatCallback()
{
MediaButtonEvent = OnMediaButtonEvent
};
}
private void SetupMediaSession()
{
CreateMediaSessionCallback();
var stateBuilder = new PlaybackStateCompat.Builder().SetActions(PlaybackStateCompat.ActionPlay | PlaybackStateCompat.ActionPlayPause);
mediaSession = new MediaSessionCompat(this, nameof(MediaBrowserService));
mediaSession.SetFlags(MediaSessionCompat.FlagHandlesMediaButtons | MediaSessionCompat.FlagHandlesTransportControls);
mediaSession.SetPlaybackState(stateBuilder.Build());
mediaSession.SetCallback(mediaSessionCallback);
mediaSession.Active = true;
SessionToken = mediaSession.SessionToken;
}
private void BuildNotification() { [...] }
public override void OnCreate()
{
base.OnCreate();
SetupMediaSession();
StartForeground(135, BuildNotification());
ContextCompat.StartForegroundService(ApplicationContext, new Intent(ApplicationContext, Java.Lang.Class.FromType(typeof(MediaBrowserService))));
}
public override void OnDestroy()
{
base.OnDestroy();
if (mediaSession != null)
{
mediaSession.Active = false;
mediaSession.SetCallback(null);
mediaSession.Release();
mediaSession.Dispose();
mediaSession = null;
}
if (mediaSessionCallback != null)
{
mediaSessionCallback.Dispose();
mediaSessionCallback = null;
}
StopForeground(true);
StopSelf();
}
}

Close with timeout is not working in Console to close splashscreen

I have made a class, I make an instance of. In said instance I have these lines of code to show and close the splashscreen.
// Open (show)
public void ShowSplashScreen(bool autoClose = false)
{
splashscreen.Show(autoClose, true);
}
// Close (don't show)
public void CloseSplashScreen()
{
splashscreen.Close(TimeSpan.FromSeconds(0.3));
}
It shows up fine, but never closes, just stays there.
This is the documentation of splashscreen Close: https://learn.microsoft.com/en-us/dotnet/api/system.windows.splashscreen.close?view=netframework-4.8
[System.Security.SecurityCritical]
public void Close (TimeSpan fadeoutDuration);
Note: I am using the show method with the parameters AutoClose set to false, and TopMost set to true, this makes it not auto close as I want to close it programmatically and not subscribe to existing events.
I am running the lines of code from a Console (.NET framework) application for testing purposes before implementing it into my UI fully.
What I have tried:
Debugging and even trying to call show again before calling close.
It is definitely something going wrong with the class, as calling the class and directly manipulating the property works:
ClassSplashScreen rss = new ClassSplashScreen();
rss.splashscreen.Show(false);
rss.splashscreen.Close(TimeSpan.FromSeconds(1));
My best guess is something is hanging the UI and freezing it? But I am unsure what to do about it.
Code to run to test this
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
namespace NamespaceName
{
public class StackOverFlowCode
{
static void Main(string[] args)
{
ClassSplashScreen screen = new ClassSplashScreen();
screen.ShowSplashScreen();
screen.CloseSplashScreen();
}
}
public class ClassSplashScreen
{
public SplashScreen splashscreen { get; set; }
public ClassSplashScreen()
{
splashscreen = new SplashScreen("Resource Image Link");
}
public void ChangeSplashResource(SplashScreen resource)
{
splashscreen = resource;
}
public void ShowSplashScreen(bool autoClose = false)
{
splashscreen.Show(autoClose, true);
}
public void CloseSplashScreen()
{
splashscreen.Close(TimeSpan.FromSeconds(1));
}
}
}
The SplashScreen relies on a dispatcher but there is no one in a console application by default. If you create a System.Windows.Application, it should work as expected:
public class StackOverFlowCode
{
[STAThread]
static void Main(string[] args)
{
Application app = new Application();
app.Startup += (s, e) =>
{
ClassSplashScreen screen = new ClassSplashScreen();
screen.ShowSplashScreen();
screen.CloseSplashScreen();
};
app.Run();
}
}
public class ClassSplashScreen
{
private readonly SplashScreen splashscreen;
public ClassSplashScreen() => splashscreen = new SplashScreen("Resource Image Link");
public void ShowSplashScreen() => splashscreen.Show(false);
public void CloseSplashScreen() => splashscreen.Close(TimeSpan.FromSeconds(1));
}

Visual Studio App Center: The 'await' operator can only be used within an async method

I need help with App Center push notifications. I want to find out if the user has enabled or disabled push notifications on his iOS/Android device. This task should be done when the application launches. I followed this App Center tutorial but I get an error message when I check if push notifications are enabled or disabled.
App Center tutorial
Error CS4033: The 'await' operator can only be used within an async
method. Consider marking this method with the 'async' modifier and
changing its return type to 'Task'.
What is wrong? How can I find out if the user has enabled or disabled push notifications?
using Foundation;
using UIKit;
using Microsoft.AppCenter;
using Microsoft.AppCenter.Analytics;
using Microsoft.AppCenter.Crashes;
using Microsoft.AppCenter.Push;
namespace iosprojectnew.iOS
{
[Register("AppDelegate")]
class Program : UIApplicationDelegate
{
private static Game1 game;
internal static void RunGame()
{
game = new Game1();
game.Run();
}
static void Main(string[] args)
{
UIApplication.Main(args, null, "AppDelegate");
}
public override void FinishedLaunching(UIApplication app)
{
if (!AppCenter.Configured)
{
bool isEnabled = await Push.IsEnabledAsync();
Push.PushNotificationReceived += (sender, e) =>
{
// Add the notification message and title to the message
var summary = $"Push notification received:" +
$"\n\tNotification title: {e.Title}" +
$"\n\tMessage: {e.Message}";
// If there is custom data associated with the notification,
// print the entries
if (e.CustomData != null)
{
summary += "\n\tCustom data:\n";
foreach (var key in e.CustomData.Keys)
{
summary += $"\t\t{key} : {e.CustomData[key]}\n";
}
}
// Send the notification summary to debug output
System.Diagnostics.Debug.WriteLine(summary);
};
}
AppCenter.Start("...", typeof(Analytics), typeof(Crashes), typeof(Push));
RunGame();
}
}
}
A method that uses await must be marked as async. Try to add the async keyword before void FinishedLaunching:
public override async void FinishedLaunching(UIApplication app) { ... }
The await keyword can be used only into a async method, but this method is an override, so you cant' transform to a task.
Anyway you can write the line code where you use async like this:
bool isEnabled = await Push.IsEnabledAsync().Result;

Visio Interop Application events causing undesired behaviour

I'm trying to use Visio Application events. When instantiating a new Application object, and setting any event (i.e. BeforeDocumentClose), this appears to result in unable to restore the Visio window after minimizing it.
I'm using VS/C# 2013, Windows Forms, Visio 2013 (on Windows 7). Though my main code project is huge implementing exchange between various office applications using Add-Ins, the following simple code reproduces the same issue. It is a Windows Forms project (with added Reference to Microsoft.Office.Interop.Visio).
using Visio = Microsoft.Office.Interop.Visio;
Visio.Application app;
bool initialised = false;
private void visioButton_Click(object sender, EventArgs e)
{
init();
app.Documents.Add("c:\\test.vst"); // creates new document from template
}
void init()
{
if (!initialised)
{
// only initialise once
app = new Visio.Application();
app.BeforeDocumentClose += app_BeforeDocumentClose;
initialised = true;
}
}
void app_BeforeDocumentClose(Visio.Document doc)
{
}
Issue #1: This is the main issue. Creating one or more Visio Documents, the Visio Window is not maximized after being minimized. No Exceptions thrown as far as I can see. Windows just does it's audible error 'ping'.
Issue #2: This is a secondary issue. Creating two or more Visio Documents, hovering over the Windows Taskbar, the preview windows show the waiting cursor instead of normal document preview.
Conditions: Issue #1 only occurs when using an event on the Application. Document, Page/Shape events don't cause any problem. All events are captured fine. Issue #2 always occurs, but this is less important for me.
I've been searching for this issue for a while, but can't find anything related to it, so any help is greatly appreciated.
I am not quite sure what is causing Visio to not respond to restore, but you can try the approach with "AddAdvise" instead:
[ComVisible(true)]
public partial class Form1 : Form, Visio.IVisEventProc
{
public Form1()
{
InitializeComponent();
}
Visio.Application app;
bool initialised = false;
private void button1_Click(object sender, EventArgs e)
{
init();
app.Documents.Add("C:\\test.vst"); // creates new document from template
}
void init()
{
if (!initialised)
{
// only initialise once
app = new Visio.Application();
// app.BeforeDocumentClose += app_BeforeDocumentClose;
app.EventList.AddAdvise(DocCloseEventCode, this, null, null);
initialised = true;
Application.DoEvents();
}
}
const short DocCloseEventCode = unchecked((short)Visio.VisEventCodes.visEvtDoc + (short)Visio.VisEventCodes.visEvtDel);
object Visio.IVisEventProc.VisEventProc(short eventCode, object source, int eventID, int eventSeqNum, object subject,object moreInfo)
{
if (eventCode == DocCloseEventCode)
app_BeforeDocumentClose(subject as Visio.Document);
return null;
}
void app_BeforeDocumentClose(Visio.Document doc)
{
}
}
To provide the completed solution for multiple events using Nikolay's advice, here is the completed code including both events and (de)initialisation of Visio Application, and without using templates. (Note that the Message boxes may turn up in the background, behind the Visio window.)
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using Visio = Microsoft.Office.Interop.Visio;
namespace VisioInteropTest
{
[ComVisible(true)]
public partial class TestForm : Form, Visio.IVisEventProc
{
Visio.Application app;
bool initialised = false;
// all AddAdvise events:
// https://msdn.microsoft.com/en-us/library/office/ff768620.aspx
const short appCloseEventCode = (short)(Visio.VisEventCodes.visEvtApp | Visio.VisEventCodes.visEvtBeforeQuit);
const short docCloseEventCode = (short)(Visio.VisEventCodes.visEvtDoc | Visio.VisEventCodes.visEvtDel);
public TestForm()
{
InitializeComponent();
}
private void visioButton_Click(object sender, EventArgs e)
{
if (init())
{
app.Documents.Add("");
}
}
bool init()
{
if (!initialised)
{
app = new Visio.Application();
app.EventList.AddAdvise(appCloseEventCode, this, null, null);
app.EventList.AddAdvise(docCloseEventCode, this, null, null);
initialised = true;
}
return initialised;
}
object Visio.IVisEventProc.VisEventProc(short eventCode, object source, int eventID, int eventSeqNum, object subject, object moreInfo)
{
switch (eventCode)
{
case appCloseEventCode: app_BeforeAppClose((Visio.Application)subject); break;
case docCloseEventCode: app_BeforeDocumentClose((Visio.Document)subject); break;
}
return null;
}
void app_BeforeAppClose(Visio.Application app)
{
initialised = false;
MessageBox.Show("App closed");
}
void app_BeforeDocumentClose(Visio.Document doc)
{
MessageBox.Show("Doc closed");
}
}
}

Categories