UWP Webview only triggers NavigationCompleted event for the last request - c#

I have a foreach loop to call _webView.NavigateToLocalStreamUri(url[index], resolver) many times. As I know each time I navigate to a specific URL, the events NavigationStarting and NavigationCompleted are triggered. But in my case, NavigationStarting is called for every request but NavigationCompleted only trigger for the last request.
I want to know Is there a way to make sure NavigationCompleted is called for every request. I searched but there is no answer meet my purpose.
for (int i = 0; i < 3; i++){
// here I raise event to call `NavigateToLocalStreamUri` of _webview
}
_webview have 2 events NavigationStarting and NavigationCompleted
// when i = 0 or 1
the program only calls event handler of NavigationStarting, but not call event handler of NavigationCompleted
// when i = 2 (always, the last iteration of for loop)
the program calls event handler of both events NavigationStarting and NavigationCompleted
// Is there any solution to make sure when i = 0 or 1, NavigationCompleted handler is called?

I reproduced your issue. The issue is caused by the for loop continue before the previous navigation completed.
The key point to resolve this is to wait for the previous NavigationCompleted event handle occurred.You could use AutoResetEvent which notifies a waiting thread that an event has occurred. For example:
public MainPage()
{
this.InitializeComponent();
urls = new List<Uri>();
waitForNavComplete = new AutoResetEvent(false);
urls.Add(...);
}
List<Uri> urls;
AutoResetEvent waitForNavComplete;
private async void btnnavigate_Click(object sender, RoutedEventArgs e)
{
for (int i = 0; i < 3; i++)
{
mywebview.Navigate(urls[i]);
await Task.Run(() => { waitForNavComplete.WaitOne(); });
}
}
private void mywebview_NavigationCompleted(WebView sender, WebViewNavigationCompletedEventArgs args)
{
System.Diagnostics.Debug.WriteLine(args.Uri.ToString());
waitForNavComplete.Set();
}
Using Set method to set the state of the completed event to signaled so that for loop will stop waiting and continue.

I think #sunteen-wu-msft's solution is a bit complicated. Sloppy use of AutoResetEvent can lead to a lot of headache. If the goal is to let NavigationCompleted to fire, then why not just navigate to the next URL when first one is completed, e.g. call NavigateToLocalStreamUri in the NavigationCompleted itself:
private Stack<Uri> toNavigate = ...
private void mywebview_NavigationCompleted(WebView sender, WebViewNavigationCompletedEventArgs args)
{
// Handle NavigationCompleted
...
// Navigate to the next Uri
var uri = toNavigate.Pop();
mywebview.NavigateToLocalStreamUri(uri, resolver);
}
Another option would be to simply create several webviews and navigate them all to different Uris. This can take quite a lot of resources though since all loaded websites will live in memory until you kill its webview.

Related

C# Event Handler trigger multiple times, however need to handle it once in 10 secs

I got a condition, were I have listener which listens for a event.
Trying to do something like below, event handler may receive multiple events, however i need to merge them in one and trigger once.
private static void QueueRequestChanged()
{
// This may get 1 or 10 events here.
// want to keep wait for 10 seconds.
// trigger one event to handle it further
}
I am trying to do this, to avoid dead lock situation. Any suggestion on approach will help.
Thank you.
You should look into Reactive Extensions (Rx): https://www.nuget.org/packages/System.Reactive/
It makes these kind of things easy if you know LINQ:
using System.Reactive.Linq;
...
System.Reactive.Linq.Observable
.FromEventPattern<RoutedEventArgs>(btn, "Click")
.Sample(TimeSpan.FromSeconds(10)) //take the last event for every 10 seconds
.Subscribe(_ => { MessageBox.Show("clicked!"); });
The above sample code bundles all Clicked events raised by a WPF Button named "btn" during the last 10 seconds and handles only the latest one by displaying a MessageBox.
You need to change RoutedEventArgs to whatever the type of your EventArgs is, "Click" to the actual name of the event and "btn" to the name of the object that raises the event.
You can have the following code, which will not put your thread in a wait state:
private const Timespan c_maxTime = new Timespan(0, 0, 10);
private static DateTime? s_lastValidEvent;
private static object s_lockObject = new Object();
private static void QueueRequestChanged()
{
lock (s_lockObject)
{
if (!s_lastValidEvent.HasValue || (DateTime.Now - s_lastValidEvent.Value) > c_maxTime)
{
// Do whatever the event is supposed to trigger
s_lastValidEvent = DateTime.Now;
}
}
}
For now, i have did as below, and seems working fine for me.
var events = 0
private static void QueueRequestChanged()
{
events++;
Task.Delay(10000).ContinueWith(t => Trigger());
}
private static void Trigger()
{
if(events > 0)
{
events = 0;
// do code here
}
}
Any better suggestion would be appreciated. Thank you.

Implicit function evaluation: webbrowser in thread, body always null [duplicate]

Problem Scope:
I'm writing an aplication to save the HTML's retrieved from the Bing and Google searches. I know there are classes to execute the Web Requests using stream such as this example, but since Google and Bing both use Javascript and Ajax to render the results into the HTML, there's no way i can simply read the stream and use get to the result i need.
The solution to this, is to use the WebBrowser class and navigate to the url i want, so that the Browser itself will handle all the Javascript and Ajax scripting executions.
MultiThreading:
In order to make it more efficient, i have the same Form aplication firing a thread for each service (one for Bing, and one for Google).
Problem:
Since i need the WebBrowser, i have instantiated one for each thread (which are 2, at this moment). According to Microsoft, there is a known bug that prevents the DocumentCompleted event from firing if the WebBrowser is not visible and is not added to a visible form aswell (for more information, follow this link).
Real Problem:
The main issue is that, the DocumentCompleted event of the browser, never fires. Never.
I have wrote a proper handler for the DocumentCompleted event that never gets the callback. For handling the wait needed for the Browser event to fire, i have implemented a AutoResetEvent with a high timeout (5 minutes), that will dispose the webbrowser thread if it does not fire the event i need after 5 minutes.
At the moment, i have the Browser created and added into a WindowsForm, both are visible, and the event is still not firing.
Some Code:
// Creating Browser Instance
browser = new WebBrowser ();
// Setting up Custom Handler to "Document Completed" Event
browser.DocumentCompleted += DocumentCompletedEvent;
// Setting Up Random Form
genericForm = new Form();
genericForm.Width = 200;
genericForm.Height = 200;
genericForm.Controls.Add (browser);
browser.Visible = true;
As for the Navigation i have the Following (method for the browser) :
public void NavigateTo (string url)
{
CompletedNavigation = false;
if (browser.ReadyState == WebBrowserReadyState.Loading) return;
genericForm.Show (); // Shows the form so that it is visible at the time the browser navigates
browser.Navigate (url);
}
And, for the call of the Navigation i have this :
// Loading URL
browser.NavigateTo(URL);
// Waiting for Our Event To Fire
if (_event.WaitOne (_timeout))
{
// Success
}
{ // Error / Timeout From the AutoResetEvent }
TL:DR:
My WebBrowser is instantiated into a another STAThread, added to a form, both are visible and shown when the Browser Navigation fires, but the DocumentCompleted event from the Browser is never fired, so the AutoResetEvent always times out and i have no response from the browser.
Thanks in Advance and sorry for the long post
Although this seems a strange way, here is my attempt.
var tasks = new Task<string>[]
{
new MyDownloader().Download("http://www.stackoverflow.com"),
new MyDownloader().Download("http://www.google.com")
};
Task.WaitAll(tasks);
Console.WriteLine(tasks[0].Result);
Console.WriteLine(tasks[1].Result);
public class MyDownloader
{
WebBrowser _wb;
TaskCompletionSource<string> _tcs;
ApplicationContext _ctx;
public Task<string> Download(string url)
{
_tcs = new TaskCompletionSource<string>();
var t = new Thread(()=>
{
_wb = new WebBrowser();
_wb.ScriptErrorsSuppressed = true;
_wb.DocumentCompleted += _wb_DocumentCompleted;
_wb.Navigate(url);
_ctx = new ApplicationContext();
Application.Run(_ctx);
});
t.SetApartmentState(ApartmentState.STA);
t.Start();
return _tcs.Task;
}
void _wb_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
//_tcs.TrySetResult(_wb.DocumentText);
_tcs.TrySetResult(_wb.DocumentTitle);
_ctx.ExitThread();
}
}

Timer to fire an event WPF

I have a project here and it has set by default that the actions occur by MouseEnter event. I mean, opening a Window, closing, returning, whatever, happens only by the MouseEnter event.
I was requested to make the event fire only after 3 seconds. That means that the user will place the mouse on the control and only after 3 seconds the event must happen for all the controls in the window.
So, I thought about a global timer or something alike, that will return false untill the timer reaches 3... I think that's the way...
Geez, does anybody knows how can I make such thing?
Thanks!!
You can define a class that will expose a DelayedExecute method that receives an action to execute and creates timers as needed for the delayed execution. It would look something like this:
public static class DelayedExecutionService
{
// We keep a static list of timers because if we only declare the timers
// in the scope of the method, they might be garbage collected prematurely.
private static IList<DispatcherTimer> timers = new List<DispatcherTimer>();
public static void DelayedExecute(Action action, int delay = 3)
{
var dispatcherTimer = new System.Windows.Threading.DispatcherTimer();
// Add the timer to the list to avoid it being garbage collected
// after we exit the scope of the method.
timers.Add(dispatcherTimer);
EventHandler handler = null;
handler = (sender, e) =>
{
// Stop the timer so it won't keep executing every X seconds
// and also avoid keeping the handler in memory.
dispatcherTimer.Tick -= handler;
dispatcherTimer.Stop();
// The timer is no longer used and shouldn't be kept in memory.
timers.Remove(dispatcherTimer);
// Perform the action.
action();
};
dispatcherTimer.Tick += handler;
dispatcherTimer.Interval = TimeSpan.FromSeconds(delay);
dispatcherTimer.Start();
}
}
Then you can call it like this:
DelayedExecutionService.DelayedExecute(() => MessageBox.Show("Hello!"));
or
DelayedExecutionService.DelayedExecute(() =>
{
DoSomething();
DoSomethingElse();
});
I just wanted to add a simpler solution:
public static void DelayedExecute(Action action, int delay = 3000)
{
Task.Factory.StartNew(() =>
{
Thread.Sleep(delay);
action();
}
}
Then use it just like in this other answer

Stop loop in class from another class

So I have two event handlers button1_Click() and button2_Click()
In button1_Click() I have something running like this:
toGet = textbox1.Text;
got = 0;
while (got <= toGet)
{
//DoStuff
}
But button2_Click is supposed to be a stop button, and stop button1 early.
How do I go about this?
Thanks for the help. I saw this article here about it, but couldn't get it to work.
Windows.Forms answer
The least sophisticated method is this:
private bool m_stop;
private void button1_Click (object s, EventArgs ea)
{
try
{
// Don't forget to disable all controls except the ones you want a user to be able to click while your method executes.
toGet = textbox1.Text;
got = 0;
while (got <= toGet)
{
Application.DoEvents ();
// DoEvents lets other events fire. When they are done, resume.
if (m_stop)
break;
//DoStuff
}
finally
{
// Enable the controls you disabled before.
}
}
private void button2_Click (object s, EventArgs ea)
{
m_stop = true;
}
It has the distinct advantage of letting you execute button1_Click on the UI thread, still lets the UI respond to your stop button.
It has a disadvantage that you must protect against reentrancy. What happens if they click your button1 while button1_click is already executing!?!?
Edit: Another way I have used is to use a Timer instead of a loop. Then, the stop method just stops the timer.
As much as I understood, correct me if I'm wrong, you're on single thread.
Wired, but you can check for single boolean value inside the your While loop, just as post suggested.
May be to make life easier (may be this is what "couldn't get it to work" means) is inside loop call
1) Windows Forms: Application.DoEvents()
2) WPF (little bit more tricky) : DoEvents in WPF
This to make breathe system.
You need to start the process inside the button1 in new thread, and when you press the button2 flag a local variable to false to stop the loop. like:
using System.Threading;
private volatile bool _requestStop = false;
private readonly object _oneExecuteLocker = new object();
private void OnButton1Click(ojbect sender, EventArgs e)
{
new Thread(() =>
{
if (Monitor.TryEnter(_oneExecuteLocker))
{//if we are here that is means the code is not already running..
try
{
while (!_requestStop)
{
//DoStuff
}
}
finally
{
Monitor.Exit(_oneExecuteLocker);
}
}
}){ IsBackground = true }.Start();
}
private void OnButton2Click(object sender, EventArgs e)
{
_requestStop = true;
}
Notes:
When ever you want to update a UI control inside the newly created thread you should use contorl.Invoke(/*the code get/set or call method in the UI*/).
The Monitro.Enter is just to be sure that your code will not executed multiple time per click if it already running.

C# event not being handled

I'm learning C# event handling by writing an app that uses the iTunes COM API. I have a method that should run when iTunes stops playing a song, but the method is never getting called when I trigger the event in the app by hitting the "stop/pause" button.
EDIT: Based on dboarman's reply, I deleted the while loop. Now the event does get handled, but only if I restart iTunes prior to running PlayPlaylist(). If I run PlayPlaylist() a second time, the stop event no longer gets fired/handled.
void trayIcon_Click(object sender, EventArgs e)
{
PlayPlaylist();
}
public static void PlayPlaylist()
{
itapp = new iTunesApp();
itapp.OnPlayerStopEvent +=
new _IiTunesEvents_OnPlayerStopEventEventHandler(itapp_OnPlayerStopEvent);
lastPlaylist = itapp.LibraryPlaylist;
itapp.Play();
}
static void itapp_OnPlayerStopEvent(object iTrack)
{
Debug.WriteLine("Stop Event fired");
//...
}
Updated source in Pastebin here (lines 59-68 are the relevant ones).
Spec: My app is supposed to play the songs in a Genius recommendations playlist from first to last (iTunes by default doesn't play Genius recommendations consecutively). The StopEvent should trigger the next song in the list to play.
Here is the complete code that is in question:
public static void PlayPlaylist()
{
itapp = new iTunesApp();
itapp.OnPlayerStopEvent += new _IiTunesEvents_OnPlayerStopEventEventHandler(itapp_OnPlayerStopEvent);
lastPlaylistID = itapp.LibraryPlaylist.playlistID;
Debug.WriteLine("Last playlist:");
Debug.WriteLine(lastPlaylistID);
itapp.Play();
while (true)
{
System.Threading.Thread.Sleep(1000);
}
}
I suspect that the while loop is causing the event to never fire because the thread will sleep for a second and because true is, well...always true.
I would put your playlist in into a list. Something like:
static List<myTrack> Tracks;
public static void PlayPlaylist()
{
itapp = new iTunesApp();
itapp.OnPlayerStopEvent += new _IiTunesEvents_OnPlayerStopEventEventHandler(itapp_OnPlayerStopEvent);
foreach (myTrack track in Tracks)
{
// perform play
}
}
See how that works for you.
When your itapp goes out of scope, be sure to release it with
System.Runtime.InteropServices.Marshal.ReleaseComObject(itapp);
or you'll have to restart iTunes for it to work again. Unregistering the event handlers with -= probably wouldn't hurt either.
If you want the thread to block and wait for the event you can use the ManualResetEvent class.
private ManualResetEvent _resetEvent;
public void PlayPlaylist()
{
_resetEvent = new ManualResetEvent(false);
itapp = new iTunesApp();
itapp.OnPlayerStopEvent += new _IiTunesEvents_OnPlayerStopEventEventHandler(itapp_OnPlayerStopEvent);
// Block until the resetEvent has been Set() or
// give up waiting after 5 minutes
_resetEvent.WaitOne(1000*5*60);
}
Inside itapp_OnPlayerStopEvent() you must call:
_resetEvent.Set();
To answer your original question, I'm pretty sure the while loop is not giving the thread any time to respond to the stop event, hence you are not seeing it being handled.
I'm wondering if the fact that the event handler doesn't unhook is causing an issue somewhere along the line (i.e. iTunes holds a singular reference to the initial instance of your app). This may solve it? I don't have the iTunes API so I'm flying a little blind; apologize if it's a waste of time.
private bool stopIt;
private void trayIcon_Click(object sender, EventArgs e)
{
if (!stopIt)
{
PlayPlaylist();
stopIt = true;
}
else
{
StopPlaylist();
stopIt = false;
}
}
public static void PlayPlaylist()
{
itapp = new iTunesApp();
itapp.OnPlayerStopEvent +=
new _IiTunesEvents_OnPlayerStopEventEventHandler(itapp_OnPlayerStopEvent);
lastPlaylist = itapp.LibraryPlaylist;
itapp.Play();
}
public static void StopPlaylist()
{
itapp.Stop(); // don't know if this is the right method name
// unhook the event so the reference object can free if necessary.
itapp.OnPlayerStopEvent -=
new _IiTunesEvents_OnPlayerStopEventEventHandler(itapp_OnPlayerStopEvent);
}
private static void itapp_OnPlayerStopEvent(object iTrack)
{
Debug.WriteLine("Stop Event fired");
// ...
}

Categories

Resources