I'm trying to create an application containing a flipview that automatically flips to the next page when the user hasn't interacted with it for a while. This works fine using a basic DispatcherTimer that gets restarted when the flipview's selection changes.
So far so good, but I also don't want the timer to run when the user is interacting with the current item in the flipview, like a listview or something. I figured that I could just wire up a PointerPressed and PointerReleased handler to the page and stop the timer whenever a pointer is pressed, and restart it when a pointer is released.
This works, except when the pointer is on the flipview: the pressed handler gets executed, but FlipView gobbles up all the other pointer events, so the PointerReleased handler never gets executed.
I can't figure out how to get this to work. In WPF, I'd just use a tunneling event, but that whole concept seems to have disappeared with WinRT? Any advice on how to get this to work?
Update with code
Sure. I have a page containing a flipview and a dispatchertimer:
public sealed partial class MainPage : Page
{
private DispatcherTimer slideScrollTimer;
public MainPage()
{
// Set up a timer that'll flip to the next page every 5 seconds.
this.slideScrollTimer= new DispatcherTimer()
{
Interval = TimeSpan.FromSeconds(5)
};
slideScrollTimer.Tick += slideScrollTimer_Tick;
slideScrollTimer.Start();
}
void slideScrollTimer_Tick(object sender, object e)
{
// When the timer runs out, go to the next page, or back
// to the first.
if (flipView.SelectedIndex < flipView.Items.Count - 1)
{
flipView.SelectedIndex++;
}
else
{
flipView.SelectedIndex = 0;
}
}
private void flipView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// restart the timer if someone flips to a different page
if (this.slideScrollTimer != null)
{
this.slideScrollTimer.Start();
}
}
}
Basically what I want is for the timer to reset whenever someone touches the application. I tried adding a PointerPressed/PointerReleased handler using AddHandler, but released only fires if you're not on the flipview, or just tapping it instead of scrolling it or manipulating its contents.
You can use UIElement.AddHandler to handle events that have already been marked as handled elsewhere.
Adds a routed event handler for a specified routed event, adding the handler to the handler collection on the current element. Specify handledEventsToo as true to have the provided handler be invoked even if the event is handled elsewhere.
Related
There is Activated event in a Window in WPF. What is the the closest match for Activated event for a page. I want to use an event that triggers every time a page is displayed.
How about using the IsVisibleChanged event.
In your window, either your base class definition that you use throughout your app, or just the one you are interested in. Add a call at the opening of it something like
public class MyBaseclassWindow : Window
{
public MyBaseclassWindow()
{
IsVisibleChanged += MyBaseclassWindow_IsVisibleChanged;
}
private void MyBaseclassWindow_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
// e.NewValue will be TRUE when the visibility is coming back on
if ( !e.NewValue )
return;
// do whatever you want every time window becomes visible.
}
}
If this is something you want done throughout your system, then you can just use THIS window as your base window by always using it as forms are created.
There is the Loaded event which occurs when the element (Page) is laid out, rendered, and ready for interaction.
The Frame class, which I guess is used to host your pages, also has a ContentRendered event that you can handle.
I am trying to create a method to, using the WebBrowser control, navigate to a webpage and block execution until the page has fully loaded.
Unfortunately knowing if the page has fully loaded or not is not that simple and just based on the events Navigating and DocumentCompleted.
Together they make a pair: when Navigating fires, a DocumentCompleted will always fire after that.
Unfortunately DocumentCompleted fires for every frame that has loaded, so it could fire multiple times per page, meaning it's not unusual to see the following sequence: Navigating - DocumentCompleted - Navigating - DocumentCompleted.
As a result, I've defined "page fully loaded" as the following: if DocumentCompleted has fired and a second has elapsed without Navigating firing again during that time.
Anyway, I've got the following as my Rx code to try to do the above:
_pageLoaded = navigating.Select(_ => documentCompleted.Delay(TimeSpan.FromSeconds(1))).Switch().FirstAsync();
Then somewhere else I do an await _pageLoaded before continuing.
However, for some bizarre reason, this does not work the first time around. So when the application loads up and the WebBrowser navigates somewhere, it gets stuck on the await _pageLoaded until both Navigating and DocumentCompleted fire again (i.e., I go to another URL - or the same one).
I did a quick project just to test this out and the events Navigating and DocumentCompleted fire as expected, here's my full code:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private IObservable<object> _pageLoaded;
private void Form1_Load(object sender, EventArgs e)
{
var navigating = Observable.FromEventPattern(webBrowser, "Navigating");
var documentCompleted = Observable.FromEventPattern(webBrowser, "DocumentCompleted");
_pageLoaded = navigating.Select(_ => documentCompleted.Delay(TimeSpan.FromSeconds(1))).Switch().FirstAsync();
}
private async void button1_Click(object sender, EventArgs e)
{
webBrowser.Navigate(textBox1.Text);
await _pageLoaded;
MessageBox.Show("DoneLoading");
}
private void webBrowser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
MessageBox.Show("DocumentCompleted fired");
}
private void webBrowser_Navigating(object sender, WebBrowserNavigatingEventArgs e)
{
MessageBox.Show("Navigating fired");
}
}
The result when someone enters a URL into the TextBox (I tested with google.com) and hits the Button is:
Navigating fired
DocumentCompleted fired
... ... nothing
If then on the WebBrowser I click on a link, say images at the top for example, the following happens:
Navigating fired
DocumentCompleted fired
1 sec later ... DoneLoading
If I hit the Button again what then happens is:
Navigating fired
DocumentCompleted fired
1 sec later ... DoneLoading
And obviously if I haven't clicked any link within the page before, then I get a second DoneLoading MessageBox.
So, my question is simple: what does it not work the first time around and how can I fix it?
EDIT: I just did a test with a different website where the events fire multiple times, and it works fine on those websites. So it's literally websites where it only fires the one time, and it appears to only be an issue the first time around.
The problem is that you are waiting after you've called Navigate - so depending whether the Navigate event is raised synchronously or not, either the Rx never sees the first navigate event or it is a race.
Try this:
private async void button1_Click_1(object sender, EventArgs e)
{
var waiter = _pageLoaded.GetAwaiter();
webBrowser.Navigate(textBox1.Text);
await waiter;
MessageBox.Show("DoneLoading");
}
Incidentally, for just this reason its a very common pattern in asynchronous code to start waiting for an event to complete and then initiate it - so this is a good one to remember.
I have several buttons, and I want them to do something when the cursor has been positioned over them for an already specified time. In this case they should just write their content in a textbox.
This is the Timer:
private static System.Timers.Timer myTimer =
new System.Timers.Timer(1500);
This is the method the buttons execute with the MouseEnter event:
private void keysHover(object sender, EventArgs e)
{
myTimer.Elapsed += delegate { keysHoverOK(sender); };
myTimer.Enabled = true;
}
And this is what gets executed if the Timer finishes:
private void keysHoverOK(object sender)
{
this.Dispatcher.Invoke((Action)(() =>
{
txtTest.Text += (sender as System.Windows.Controls.Button).Content.ToString();
}));
myTimer.Enabled = false;
}
I don't quite understand why this is happening, but everytime one of the buttons completes the Timer the keysHoverOK method will write as many characters as there have been hovered. For example, if I hover over the button A, it will write A, if I then hover over the button B, it will write AB, thus getting AAB written on the textbox and so on and so forth, the sentence executes as many times as the rest of the buttons have executed the keysHover method, even if they didn't complete the Timer themselves, it's like their content got saved somewhere. Now of course all I want the buttons to do is to write their content and their content only. So do you have an idea of what I'm doing wrong?
Do you mean the MouseEnter event? I'm not aware of any MouseOver event in WPF.
Without a good, minimal, complete code example, it's impossible to know for sure what the problem is. However, based on the small amount of code you've shared and your problem description, it appears that your main issue is that you're sharing a single Timer object with multiple controls. This is exacerbated by the fact that when one control subscribes to the Timer.Elapsed event, it never unsubscribes. So if another control enables the timer (subscribing to the event as well), both controls are notified when the timer interval elapses.
Even a single control is problematic, as it subscribes itself to the event each time the MouseEnter event is raised.
The fix is to disable the timer and unsubscribe from the event when the mouse leaves the bounds of the control, or when the timer interval has elapsed. That might look something like this:
private EventHandler _timerElapsedHandler;
// Subscribed to the MouseEnter event
private void keysHover(object sender, EventArgs e)
{
_timerElapsedHandler = delegate { keysHoverOK(sender); };
myTimer.Elapsed += _timerElapsedHandler;
myTimer.Enabled = true;
}
// Subscribed to the MouseLeave event
private void keysLeave(object sender, EventArgs e)
{
DisableTimer();
}
private void keysHoverOK(object sender)
{
this.Dispatcher.Invoke((Action)(() =>
{
txtTest.Text += (sender as System.Windows.Controls.Button).Content.ToString();
}));
DisableTimer();
}
private void DisableTimer()
{
myTimer.Elapsed -= _timerElapsedHandler;
myTimer.Enabled = false;
_timerElapsedHandler = null;
}
Other comments:
You should cast instead of using as. Only use as when a reference can legitimately be of a different type than you are checking for. Use a cast when it is always supposed to be the type you are checking for. That way, if you have a bug, you will get a meaningful exception, instead of just some NullReferenceException
The above example fixes the problem with the least disruption to your code. But really, I would make other changes too. For example, rather than storing the delegate in a field, I would just get the Content.ToString() value and store that. Then instead of using an anonymous method for the delegate instance, I would use a named method that simply uses the stored string value to append to the Text property. You can subscribe and unsubscribe the named method by name; the delegate type does the right thing even though it's using a different delegate instance for the subscribe and the unsubscribe.
Another change you might consider making is to use a different Timer instance for each control. Then you don't have to subscribe or unsubscribe as the mouse events occur; just subscribe during initialization.
Finally, especially as this is WPF code, you really should consider storing the appended text in an observable property (e.g. DependencyProperty, or implement INotifyPropertyChanged), and bind it to the txtTest.Text property rather than manipulating that property directly.
I'm assuming when you say:
This is the method the buttons execute with the MouseOver event:
You might mean the MouseEnter event?
From what I see:
You have one central timer
It will start the elapsed count down on the first button you enter
You have not stopped the timer if you leave that button before it elapses
You seem only to add delegates to the event without removing any
The code segment myTimer.Elapsed += delegate { keysHoverOK(sender); }; adds another delegate to the list of already added delegates. It does not replace the list with just one delegate.
If you leave the button before the timer elapses you need to remove the delegate from the timer elapsed event using the minus-equal operator (myTimer.Elapsed -= ....), and then stop the timer. Here you have a problem that you've created an anonymous method so you'd need:-
Research into removing anonymous methods
or
Research into removing all event handlers
or possibly the simplest menthod
Stop and destroy any running timer and create a new timer instance each time you enter the button.
In my WP8 app that controls Lego Mindstorms I have a Button with UIElement.Hold Event that triggers method runMotor() When I release the Button motor keeps on going but I would like it to stop. Method for stopping is stopMotor(), I've already tried to assign it to KeyUp Event but it doesn't work. Any solutions?
You can try to call stopMotor() in ManupulationCompleted event. Note that ManipulationCompleted event will get invoked after any gesture manipulation including Tap, Double Tap, Hold, and other gesture. Take that into account. If application scenario is still simple, checking if motor already running before calling stopMotor in ManipulationCompleted event handler maybe enough :
private void MyButton_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
{
if(isMotorRunning) stopMotor();
}
This question already has answers here:
Is it possible to avoid multiple button clicks on a Winform?
(3 answers)
Closed 2 years ago.
I have an app with many user controls and many buttons on each, each button has an OnClick event which does some important stuff and then sends to a new user control.
The problem comes when the user clicks really fast multiple times, the event code gets executed more than once before exitting to a new user control, causing problems.
My current solution is to disable the button on the very first line of the event handler, but doing this to every window and handler would be troublesome, what can I do?
EDIT: Would it be a good solution to derive from Button, and override the OnClick event so it always does a check for a "working" variable, and if it is true, it doesnt start the event ? Something like:
public class MyButton : Button
{
private static bool isWorking = false;
protected override void OnClick(EventArgs e)
{
if (!isWorking)
{
isWorking = true;
base.OnClick(e);
isWorking = false;
}
//Else do nothing
}
}
You can use some timeStamp to delay between 2 clicks:
DateTime timeStamp;
//this will handle the clicks with the allowed interval being 0.5 second
//Note that a tick is equal to 1/10,000,000 of second.
private void click_Handler(object sender, EventArgs e) {
if ((DateTime.Now - timeStamp).Ticks < 5000000) return;
timeStamp = DateTime.Now;
//your code goes here ....
}
If you want all buttons to wait until one button's work is done, add a bool isProcessing variable to your form. Wrap the work of each button inside an if (!isProcessing), and set that flag to true in the first line inside the if statement. Then don't forget to set it back to false right before you exit the if.
I'm assuming you're doing all of this asynchronously, since if it's all in the same thread, the form will lock while it's processing the work. This will solve your issue though.
Disabling controls while sensitive operation is on-going is a typical solution that I always apply.
But since there can be quite a few controls on one screen that are affected by some click or change in UI, I typically design forms to have a specialized method which walks through all the affected controls and disables/enables them accordingly.
Something like this:
void EnableControls(bool enable)
{
foreach (Control ctl in this.Controls)
ctl.Enabled = enable;
}
Similarly, you could group controls into related buckets, so to disable/enable only one of them etc. Depends on your precise needs.
There is an alternative solution to use timer - disable the button, but enable it after 1 sec. This prevents nervous users from clicking multiple times if that would cause damage to data (i.e. each click is treated as a new operation).
I would call the same function from every button and then perform the specific task:
private void Button_Click(object sender, EventAgrs e)
{
Button btn = sender;
btn.disable = true;
switch (btn.AccessibleName)
// call specific function for the particular button or do it all here
}
I'm not sure if this would even work, but just an idea...
You could try with aspect oriented approach (with the help of Postsharp for example):
Create two aspects, one for method entry and one for method exit. In the method entry mark the current method as 'processing' (add the method name to a hash set for example). In the method exit mark the method as 'not processing' (remove it from the hash set). Then in the method entry check if the method is processing and if it is, then cancel the method (like this: https://stackoverflow.com/a/2437794/113858)
Mark all of your event handlers with this aspect.