Mismatch between PointerPressed and PointerReleased events - c#

I was using Monogame and had an issue whereby if I rapidly placed lots of fingers on the screen and then quickly removed them, eventually (after a few repeats) I would end up with some 'Pressed' events that never got a matching 'Released' event.
I don't think the issue is with Monogame though as I can reproduce the issue (on my Nokia Lumia 920) with a tiny 'Windows Phone and Direct3D' App created using vs2012.
On the C++ side I just store a vector inside the generated Direct3DInterop class that records pressed events and released events
void Direct3DInterop::OnPointerPressed(DrawingSurfaceManipulationHost^ sender, PointerEventArgs^ args)
{
uint32 pointerID = args->CurrentPoint->PointerId;
auto i = std::find (_pressed.begin(), _pressed.end(), pointerID);
if (i == _pressed.end())
_pressed.push_back(pointerID);
}
void Direct3DInterop::OnPointerReleased(DrawingSurfaceManipulationHost^ sender, PointerEventArgs^ args)
{
uint32 pointerID = args->CurrentPoint->PointerId;
_pressed.erase(std::remove(_pressed.begin(), _pressed.end(), pointerID), _pressed.end());
}
In my XAML, I just have a textblock and a dispatch timer that periodically checks the count of elements in _pressed (it should always return to 0).
<!--LayoutRoot is the root grid where all page content is placed-->
<Grid x:Name="LayoutRoot" Background="Transparent"
Margin="0,115,0,0">
<DrawingSurface x:Name="DrawingSurface" Margin="0,-113,0,0" Loaded="DrawingSurface_Loaded"/>
<TextBlock x:Name="LostReleasedCount" Text="Lost released count = 0" HorizontalAlignment="Left" Margin="0,-113,0,0" VerticalAlignment="Top" Height="113" Width="480"/>
</Grid>
and in the code behind file
private Direct3DInterop m_d3dInterop = null;
private DispatcherTimer _timer;
private int _seconds;
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
// Creates a new instance of a timer.
_timer = new DispatcherTimer();
// Tells the _timer to tick every second.
_timer.Interval = TimeSpan.FromSeconds(1);
_timer.Tick += TimerOnTick;
_timer.Start();
_seconds = 0;
}
private void TimerOnTick(object sender, EventArgs eventArgs)
{
Dispatcher.BeginInvoke(() =>
{
_seconds++;
LostReleasedCount.Text = "Lost released count = " + m_d3dInterop.GetPressedCount().ToString();
});
}
Am I missing something obvious here? Should you always expect the pressed events and the released events to match? I cannot reproduce the problem if I use XAML directly with a Touch.FrameReported event so it's presumably some issue with DirectX?
Without a matching pressed/released pair, I'm not sure how to distinguish a genuine problem from someone just holding their finger on the screen and not moving it.

From http://msdn.microsoft.com/library/windows/apps/br208971:
Other events instead of PointerReleased may fire at the end of the action—for example, PointerCanceled or PointerCaptureLost. Don't rely on PointerPressed and PointerReleased events always occurring in pairs. To function properly, your app must listen for and handle all events that represent likely conclusions to the Press action. Some of the reasons why you might not get a PointerReleased occurrence are:
Differences in how specific hardware handles touch actions and Press actions
A programmatic pointer capture from a different pointer
User actions that change the relationship of the display area, such as changing resolution or monitor settings
Input interactions such as a stylus touching the same surface as a previous touch action

There may be another reason. Please see my answer here: How to handle multi touch in windows phone 8 app
Possible reason of this can be race condition.

add this function in Direct3DBackground class
void Direct3DBackground::SetManipulationHost(Windows::Phone::Input::Interop::
DrawingSurfaceManipulationHost^ manipulationHost)
{
manipulationHost->PointerPressed +=
ref new TypedEventHandler<
DrawingSurfaceManipulationHost^, PointerEventArgs^>
(this, &Direct3DBackground::OnPointerPressed);
manipulationHost->PointerMoved +=
ref new TypedEventHandler<DrawingSurfaceManipulationHost^,
PointerEventArgs^>(this, &Direct3DBackground::OnPointerMoved);
manipulationHost->PointerReleased +=
ref new TypedEventHandler<DrawingSurfaceManipulationHost^,
PointerEventArgs^>(this, &Direct3DBackground::OnPointerReleased);
}
and in XAML C# code:
DrawingSurfaceBackground.SetBackgroundManipulationHandler(m_d3dBackGround);

Related

Unable to reset ProgressBar

I am experimenting a behavior which makes me crazy.
I have a ProgressBar which represents the evolution of an import in database (in percents, from 0 to 100).
After the import is done (ProgressBar.Value = 100.0), I open a log window with a code which looks like this :
RadWindow window = new RadWindow()
{
//Set some properties
};
window.Closed += Log_Closed;
window.ShowDialog();
After the RadWindow is closed, I want to reset the ProgressBar. As you can see I use the function Log_Closed whose code is bellow :
private void Log_Closed(object sender, EventArgs e)
{
//pbImport.Value = pbImport.Minimum; (didn't work)
pbImport.Value = 0;
}
Note : pbImport is my progress bar.
The instruction in Log_Closed has no effect.
Before instruction :
After instruction :
Obviously, the progress bar is not updated in UI. I can't understand this. Thank you for your help.
Animations hold onto properties, in order to reset them in code, you have to remove the animation first so that the property is "released".
See https://msdn.microsoft.com/en-us/library/aa970493%28v=vs.110%29.aspx for information on how to set a property after an animation in WPF.
Resetting the progress Bar can be achieved by using an "if" loop and incrementing the progress bar.
You can set a bool value for the database process and then simply:
private void Log_Closed(object sender, EventArgs e)
{
//pbImport.Value = pbImport.Minimum; (didn't work)
pbImport.Value = 0;
if (database)
{
pbImport.Increment(100);
}
}
From Microsoft's documentation -
To remove a specific AnimationClock from a list of clocks, use the Controller property of the AnimationClock to retrieve a ClockController, then call the Remove method of the ClockController. This is typically done in the Completed event handler for a clock. Note that only root clocks can be controlled by a ClockController; the Controller property of a child clock will return null. Note also that the Completed event will not be called if the effective duration of the clock is forever. In that case, the user will need to determine when to call Remove.
In the example below I demonstrate setting up an event handler that runs when the animation is complete and removes the clock controller there, then set the ProgressBar value back to 0.
void RunAnimation()
{
Duration duration = new Duration(TimeSpan.FromSeconds(10));
DoubleAnimation doubleanimation = new DoubleAnimation(100.0, duration);
doubleanimation.Completed += ProgressBarCompleted;
ProgBar.BeginAnimation(ProgressBar.ValueProperty, doubleanimation);
}
private void ProgressBarCompleted(object sender, EventArgs e)
{
var clock = (AnimationClock)sender;
clock.Controller.Remove();
ProgBar.Value = 0;
}
Note: ProgBar is defined in a .xaml file like
<ProgressBar Margin="0,0,0,0"
Padding="0,0,0,0"
x:Name="ProgBar"
Width="800"
HorizontalAlignment="Right"
Foreground="LightGray"/>

Show a notification window then fade it out, but pause the animation on mouse over

I'm normally an ASP.NET programmer, so I don't have much experience with WPF. I'm trying to create a notification window that opens on the screen in response to some server side event. I want it to behave similarly to the notifications given by Microsoft Outlook. The window should show for 5 seconds, then begin fading out which should last for 5 seconds, at which point the window should close. If the user mouses over the window, the windowTimer or the fade out animation should pause depending on how much time has elapsed. This works fine in the first 5 seconds of execution. However, once the fade out animation starts, it doesn't pause by mousing over. My question is: Why isn't my code pausing the animation when the mouse enters?
public partial class MainWindow : Window
{
Timer windowTimer; //System.Timers.Timer
AnimationClock clock;
bool fadingOut = false;
public MainWindow()
{
InitializeComponent();
}
protected override void OnContentRendered(EventArgs e)
{
base.OnContentRendered(e);
windowTimer = new Timer(5000);//5 seconds
windowTimer.Start();
windowTimer.Elapsed += new ElapsedEventHandler(windowTimer_Elapsed);
}
protected override void OnMouseEnter(MouseEventArgs e)
{
base.OnMouseEnter(e);
if (!fadingOut)
{
windowTimer.Stop();
}
if (clock != null)
{
clock.Controller.Pause();
}
}
protected override void OnMouseLeave(MouseEventArgs e)
{
base.OnMouseLeave(e);
if (!fadingOut)
{
windowTimer.Start();
}
if (clock != null)
{
clock.Controller.Resume();
}
}
private void windowTimer_Elapsed(object sender, ElapsedEventArgs e)
{
windowTimer.Elapsed -= windowTimer_Elapsed;
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background,
new Action(() => BeginFadeout()));
}
private void BeginFadeout()
{
windowTimer.Stop();
fadingOut = true;
DoubleAnimation fadeoutanim = new DoubleAnimation(0,
(Duration)TimeSpan.FromSeconds(5));
fadeoutanim.Completed += (s, _) => this.Close();
clock = fadeoutanim.CreateClock();
this.BeginAnimation(UIElement.OpacityProperty, fadeoutanim);
}
}
So the easiest way to do this is with storyboards. Since blend is a very easy tool to use and makes quick work of this I will use this as the example. You can also of course create the storyboard by hand.
Step 1: Create your control
Step2: Create your storyboard by clicking the little plus symbol on the left. Name it something meaningful. You will note that the UI element you have selected in the list on the left will be the one that the storyboard is applied to.
Step 3: On the left set the keyframe. This is how long the animation will take to execute and where it will execute what you tell it to. Make sure to set the keyframe FIRST then set the properties.
Step 4: Finally go into visual studio and execute your storyboard.
In this example the storyboard resides in the static resources. So I pull it out of the static resources and tell it to either begin or stop (which returns it to the start which in this case is opacity 1)
If you still want to do it by hand then add this to your notification window XAML
<Window.Resources>
<Storyboard x:Key="FadeOutAnimation">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="window">
<EasingDoubleKeyFrame KeyTime="0:0:1.1" Value="0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</Window.Resources>
I know a quick copy and paste seems faster but learning to use Blend will save you a ton of UI heart ache in the future.
To get your code working you could just replace this.BeginAnimation(UIElement.OpacityProperty, fadeoutanim); with this.ApplyAnimationClock(OpacityProperty, clock);, otherwise the clock you get is not the one that controls the animation.
Also, your code will throw exception if the mouse is over the window when it starts, you should ensure that windowTimer is not null before starting / stopping it.
But using a storyboard is clearly a cleaner solution.
Instead of recreating your own notification, you should have a look at this library, which is available from NuGet and has a good howto.

Dispatch Timer is acting static, but desire non-static behavior

I have an application that at its heart is a keyboard hook. This hook allows me to turn on and off some features on a Tablet that the company I work for manufactures. The program started off as a WinForms application and the developer who first made it is no longer working with this company. Long story short there were some bugs in the program that needed to get fixed so I've been spending some time cleaning up the code base, and updating the UI to have a more fresh feeling to it. One of the WinForms window was to display at the bottom center of the screen for 500ms with a picture of the option that you just modified. For instance if you just disabled bluetooth a grayed out bluetooth symbol would show up on the bottom of the screen.
Well I've been on a kick lately to stop using Winforms all together and I wanted the challenge of updating the UI to use WPF. I came up with this code to replace the on screen window that was shown before (actual xaml not shown as that isn't the problem)
public partial class OnScreenDisplayDialog : Window
{
public static void ShowUserUpdate(Enums.OnScreenDisplayOptions target)
{
var f = new OnScreenDisplayDialog();
var imageSource = GetPictureFromOptions(target);
f.targetImage.Source = new BitmapImage(new Uri(imageSource, UriKind.Relative));
f.Show();
f.Top = SystemParameters.PrimaryScreenHeight - f.ActualHeight;
f.StartCloseTimer();
}
private OnScreenDisplayDialog()
{
InitializeComponent();
}
private void StartCloseTimer()
{
DispatcherTimer timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromMilleseconds(500);
timer.Tick += TimerTick;
timer.Start();
}
private void TimerTick(object sender, EventArgs e)
{
DispatcherTimer timer = (DispatcherTimer)sender;
timer.Stop();
timer.Tick -= TimerTick;
var sb = new Storyboard();
var fadeout = new DoubleAnimation(0, new Duration(TimeSpan.FromSeconds(1)));
Storyboard.SetTargetProperty(fadeout, new System.Windows.PropertyPath("Opacity"));
sb.Children.Add(fadeout);
sb.Completed += closeStoryBoard_Completed;
this.BeginStoryboard(sb);
}
private void closeStoryBoard_Completed(object sender, EventArgs e)
{
this.Close();
}
}
So the idea is that I only want to make a single call to this Window like this
Forms.OnScreenDisplayDialog.ShowUserUpdate(Enums.OnScreenDisplayOptions.BluetoothOn);
The problem that I appear to be having is that with a simple test like the code below I get some unexpected results
Forms.OnScreenDisplayDialog.ShowUserUpdate(Enums.OnScreenDisplayOptions.BluetoothOn);
System.Threading.Thread.Sleep(1000);
Forms.OnScreenDisplayDialog.ShowUserUpdate(Enums.OnScreenDisplayOptions.BrightnessDown);
System.Threading.Thread.Sleep(1000);
Forms.OnScreenDisplayDialog.ShowUserUpdate(Enums.OnScreenDisplayOptions.BrightnessUp);
So i would expect that first window to show for 500ms then dissappear. 500ms later the next window shows (repeat). but instead all 3 windows remain visible, and even after about 10 seconds the windows are still visible. If I click elsewhere (so that they loose focus) they close. And I can't for the life of me figure out why. I put a break point on the Storyboard completed method and all 3 complete at the same time (which makes me think the timer is static, instead of dynamic).
Can someone please help me figure out what I am doing wrong here?
EDIT
So as my comment mentioned I wanted to show what I put in my code to test. There are 2 things here. One is from the main entry point in my program for quick and easy testing. The second is ran after my keyboard hook is setup and running. Normally I filter out the keys being pressed and act accordingly, but in this case if a key is pressed or released I open this window. So here is the main entry point portion of code
[STAThread]
static void Main()
{
InstantiateProgram();
EnsureApplicationResources();
if (true)
{
new System.Threading.Thread(Test).Start();
System.Threading.Thread.Sleep(1000);
new System.Threading.Thread(Test).Start();
System.Threading.Thread.Sleep(1000);
new System.Threading.Thread(Test).Start();
}
singleton = new System.Threading.Mutex(true, "Keymon");
if (!singleton.WaitOne(System.TimeSpan.Zero, true)) return;
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
System.Windows.Forms.Application.Run(main);
}
private static void Test()
{
Console.Beep(1000, 200);
System.Windows.Application.Current.Dispatcher.Invoke(new Action(delegate
{
Forms.OnScreenDisplayDialog.ShowUserUpdate((Enums.OnScreenDisplayOptions)r.Next(0,7));
}));
//System.Threading.Thread.Sleep(1000);
}
static Random r = new Random();
public static void EnsureApplicationResources()
{
if (System.Windows.Application.Current == null)
{
// create the Application object
new System.Windows.Application();
System.Windows.Application.Current.ShutdownMode = ShutdownMode.OnExplicitShutdown;
// merge in your application resources
System.Windows.Application.Current.Resources.MergedDictionaries.Add(
System.Windows.Application.LoadComponent(
new Uri("/Themes/Generic.xaml",
UriKind.Relative)) as ResourceDictionary);
}
}
private static void InstantiateProgram()
{
System.Windows.Forms.Application.EnableVisualStyles();
System.Windows.Forms.Application.SetCompatibleTextRenderingDefault(false);
main = new frmMain();
}
Here is the code I mentioned that is used after the main form is initialized
public frmMain()
{
InitializeComponent();
theHook = new KeyboardHookLibrary.KeyboardHook();
theHook.KeyDown += theHook_KeyDown;
theHook.KeyUp += theHook_KeyUp;
theHook.Start();
}
~frmMain()
{
theHook.Dispose();
}
void theHook_KeyUp(int key)
{
Forms.OnScreenDisplayDialog.ShowUserUpdate(Enums.OnScreenDisplayOptions.BrightnessDown);
}
void theHook_KeyDown(int key)
{
Forms.OnScreenDisplayDialog.ShowUserUpdate(Enums.OnScreenDisplayOptions.BluetoothOn);
}
and just in case I'll include xaml of the On screen display
<Window x:Class="XPKbdMon.Forms.OnScreenDisplayDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
Height="200"
Width="200"
WindowStyle="None"
AllowsTransparency="True"
Background="Black"
ShowInTaskbar="False">
<!--WindowStartupLocation="CenterScreen"-->
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Image x:Name="targetImage" />
<ProgressBar x:Name="brightnessBar" Grid.Row="1" Visibility="Collapsed" Height="30"/>
</Grid>
</Window>
I think the problem lies in the test code rather than in the actual animation code. You're calling Thread.Sleep(1000) on the main (GUI) thread, right? That will effectively block both animations and the DispatcherTimer.
Try running your test code on a different thread. You'll need to make sure that the calls to ShowUserUpdate are run on the GUI thread, though, using Dispatcher.Invoke or BeginInvoke.

WPF: Detect Animation or Cancel Timeline.Completed Event? How?

I'm moving 3d camera like this:
Point3DAnimation pa;
// Triggered by user click
void MoveCamera(object sender, EventArgs e)
{
pa = new Point3DAnimation(myPoint3D, TimeSpan.FromMilliseconds(2000));
pa.Completed += new EventHandler(pa_Completed);
Camera.BeginAnimation(PerspectiveCamera.PositionProperty, pa); // anim#1
}
// we're in place. do some idle animation
void pa_Completed(object sender, EventArgs e)
{
pa = new Point3DAnimation(myPoint3Ddz, TimeSpan.FromMilliseconds(5000));
Camera.BeginAnimation(PerspectiveCamera.PositionProperty, pa); // anim#2
}
User clicks.
Camera moves to choosen position (anim#1).
When anim#1 ends anim#2 is played.
Everything is ok... until user triggers MoveCamera when previous anim#1 is not finished.
In that case:
New anim#1 is starting.
Completed event of old anim#1 is triggered.
anim#2 is started instatntly (overlapping new anim#1).
2 & 3 are wrong here. How i can avoid that?
I think that pa_Completed() should detect that new anim#1 is already playing, or MoveCamera() should unregister Complete event from old anim#1. But what the right way to do it?
If the goal is to chain two animations together, let WPF do the heavy lifting by using the Point3DAnimationUsingKeyFrames class.
First, build the key frame animation in XAML (it's a bear to do it in code):
<Window.Resources>
<Point3DAnimationUsingKeyFrames x:Key="CameraMoveAnimation" Duration="0:0:7">
<LinearPoint3DKeyFrame KeyTime="28%" />
<LinearPoint3DKeyFrame KeyTime="100%" />
</Point3DAnimationUsingKeyFrames>
</Window.Resources>
Next, consume it and set the actual Point3D values (using your code names):
private void MoveCamera(object sender, EventArgs e) {
Point3DAnimationUsingKeyFrames cameraAnimation =
(Point3DAnimationUsingKeyFrames)Resources["CameraMoveAnimation"];
cameraAnimation.KeyFrames[0].Value = myPoint3D;
cameraAnimation.KeyFrames[1].Value = myPoint3dz;
Camera.BeginAnimation(PerspectiveCamera.PositionProperty, cameraAnimation);
}

Ignoring queued mouse events

I have an application written in C# targeting .NET Compact Framework 3.5, running on Windows CE. From time to time, operations lasting for a second or so are being performed on the UI thread. I currently set the Cursor.Current property to indicate that the application is busy, but this does not prevent mouse events from eager users to queue up. This sometimes leads to unintended clicks.
What is the best way to ignore queued mouse messages on the .NET Compact Framework platform? Sadly, the code has to run on the UI thread.
Disabling the controls won't help you, as I've found from my POS application that the users can sneak in another click in about 50ms, especially when using a touch screen that is not calibrated.
One of the problems this creates is when producing an invoice, you can't have a duplicate click produce another invoice, just because there's a 50ms delay before clearing the current invoice.
In cases like this, I use a pattern similar to this:
public static void ClearMouseClickQueue()
{
Message message;
while (PeekMessage(out message,IntPtr.Zero, (uint) MessageCodes.WM_MOUSEFIRST,(uint) MessageCodes.WM_MOUSELAST,1) != 0)
{
}
}
private object approvalLockObject = new object();
private void btnApproveTransaction_Click(object sender, EventArgs e)
{
ApproveTransactionAndLockForm();
}
private void ApproveTransactionAndLockForm()
{
lock (approvalLockObject)
{
if (ApprovalLockCount == 0)
{
ApprovalLockCount++;
ApproveTransaction();
}
else
{
CloseAndRetry();
}
}
}
private void ApproveTransaction()
{
ClearMouseClickQueue();
this.Enabled = false;
Logger.LogInfo("Before approve transaction");
MouseHelper.SetCursorToWaitCursor();
... validate invoice and print
}
In case you need to reenable the screen, do this:
this.Enabled = true;
ApprovalLockCount = 0;
DialogResult = DialogResult.None;
I believe that the best solution is to prevent the events from happening. You can do that by disabling all the controls and re-enabling them, when the lengthy operation finishes.

Categories