Detecting input keystroke during WPF processing - wpf

Greetings,
I want to write code that executes within an event handler, inside a WPF Windows application, that can detect a keypress, specifically an "Escape" character keypress, within a processing loop. This will allow the user to escape from processing. I realize this may be accomplished with some kind of multi-threaded approach, but the problem seems so simple I wondered if it might be accomplished as follows:
// Attempt 1: See if Keyboard static IsKeyDown method detects key presses while executing.
// Note that this was not successful. The Keyboard states do not appear to be updated during processing.
bool iskeypressed = false;
while (!iskeypressed)
{
System.Threading.Thread.Sleep(1000);
if (Keyboard.IsKeyDown(Key.Enter))
iskeypressed = true;
}
So, on to attempt #2. I saw some articles and samples using the Pinvoke "GetKeyboardState" method. I'm not sure I used the method correctly, but here is my attempt. It is a bit clumsy to refer to a Windows.Forms enumeration in a WPF application, but it seems like it could work.
// Attempt 2: Use Pinvoke GetKeyboardState method.
// So far, I've been unsuccessful with this as well, but I'm not sure my usage is correct.
bool iskeypressed = false;
while (!iskeypressed)
{
System.Threading.Thread.Sleep(1000);
if (isEscapePressed())
iskeypressed = true;
}
}
[DllImport("user32.dll")] public static extern int GetKeyboardState(byte[] lpKeyState);
private bool isEscapePressed()
{
byte[] keyboardState = new byte[255];
int keystate = GetKeyboardState(keyboardState);
if (keyboardState[(int)System.Windows.Forms.Keys.Escape] == 128)
return true;
else
return false;
}
But unfortunately, I'm not seeing any change in the keyboard states as this executes. I also played around a little with calls to the Dispatcher to see if I could get the keyboard information to refresh during processing, but I have not been successful with any technique.
I'm out of ideas. Can someone propose something? Thank you in advance for your assistance.
David

Something like this:
private bool IsCancelled { get; set; }
private void OnButtonClick(object sender, EventArgs e)
{
Action doWorkDelegate = DoWork;
doWorkDelegate.BeginInvoke(null, null);
}
protected override void OnKeyDown(KeyEventArgs e) {
if (e.Key == Key.Escape) {
IsCancelled = true;
e.Handled = true;
} else {
base.OnKeyDown(e);
}
}
private void DoWork()
{
IsCancelled = false;
while (!IsCancelled)
{
System.Threading.Thread.Sleep(1000);
}
}
The important point is that the method that does the work is executed in a separate thread so the main thread can process user input (key strokes).

You can not detect a key event while you are blocking WPF by executing a very long loop. You must use a multithreaded approach or you have to split the loop.
Using a BackgroundWorker is an easy way to let WPF continue handling the frontend while executing the loop.
private BackgroundWorker bw;
private void Button_Click(object sender, RoutedEventArgs e)
{
if (bw != null)
return;
bw = new BackgroundWorker();
bw.WorkerSupportsCancellation = true;
bw.WorkerReportsProgress = true;
bw.DoWork += (senderBw, eBw) =>
{
for (int i = 0; i < 100; i++)
{
Thread.Sleep(1000);
bw.ReportProgress(i);
if (eBw.Cancel)
return;
}
};
bw.ProgressChanged += (senderBw, eBw) =>
{
//TODO set progressbar to eBw.ProgressPercentage
};
bw.RunWorkerCompleted += (senderBw, eBw) =>
{
this.bw = null;
//TODO frontend stuff (hide progressbar etc)
};
bw.RunWorkerAsync();
}
private void MainWindow_KeyDown(object sender, KeyEventArgs e)
{
if (this.bw != null && this.bw.IsBusy && e.Key == Key.Escape)
this.bw.CancelAsync();
}

Related

How can I select (with focus) the text in telerik:RadNumericUpDown

This NumericUpDown (NUD) floats over a map. When it gets visible I need to re-direct the next key-stroke inside the control overriding the current value.
With great pain I've found this solution:
private void LengthInput_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if ((bool)(e.NewValue))
{
...
LengthInputBox.ShowButtons = true;
try
{
LengthInputBox.Focus();
if (m_lengthTextBox == null)
{
LengthInputBox.ApplyTemplate();
m_lengthTextBox = LengthInputBox.Template.FindName("textbox", LengthInputBox) as TextBox;
}
if (m_lengthTextBox != null)
{
m_lengthTextBox.SelectAll();
m_lengthTextBox.Focus();
}
}
finally
{
LengthInputBox.ShowButtons = false;
}
...
NUD is the LengthInputBox control. Focus method sets the focus on the NUD buttons.
Template.FindName("textbox"... retrieve the internal TextBox of NUD. If found, or previously found, it selects all and set focus on the text.
Finally, I remove the Up/Down buttons (I don't need them. Although I've done lot of variations with or without them, their presence does not change the behavior...)
It works for the first time, but on the second attempt it fails again.
Any ideas?
Select and Focus are bit slow. Using a Dispatcher has solved the issue:
private void LengthInputBox_GotFocus(object sender, RoutedEventArgs e)
{
if (m_lengthTextBox == null)
{
LengthInputBox.ApplyTemplate();
m_lengthTextBox = LengthInputBox.Template.FindName("textbox", LengthInputBox) as TextBox;
}
if (m_lengthTextBox != null)
{
m_lengthTextBox.Focusable = true;
m_lengthTextBox.IsTabStop = true;
if (!m_lengthTextBox.IsFocused)
Dispatcher.BeginInvoke(new Action(() =>
{
var dot = m_lengthTextBox.Text.IndexOf('.');
m_lengthTextBox.Select(dot, m_lengthTextBox.Text.Length - dot);
m_lengthTextBox.Focus();
}));
}
LengthInputBox.CaptureMouse();
}
(Don't forget to release the mouse:
private void LengthInput_KeyDown(object sender, KeyEventArgs e)
{
switch (e.Key)
{
case Key.Escape:
case Key.Enter:
LengthInputBox.ReleaseMouseCapture();
ViewModel.IsLengthInputVisible = false;
e.Handled = true;
break;
}
}
)

How to ignore user clicks in WinForms?

When a user clicks a button, it starts some task. I don't want to block the main application thread, so I run it in a separate thread. Now I need to forbid a user to click the button until my task finishes.
I could set
button.Enabled = false;
, but I'm looking for some way to ignore clicks on it.
I could add some check in click event handler:
if (executingThread != null) return;
, but I will have to do it for each handler which is bad idea.
I know that there is some way to filter user's messages. Could you point me how to do this? And I don't want to filter out all messages, because some other buttons must stay clickable, I need to filter out messages that come to particular controls (buttons,grids and etc).
SOLUTION
internal class MessagesFilter: IMessageFilter
{
private readonly IntPtr ControlHandler;
private const int WM_KEYUP = 0x0101;
public MessagesFilter(IntPtr ControlHandler)
{
this.ControlHandler = ControlHandler;
}
#region IMessageFilter Members
public bool PreFilterMessage(ref Message m)
{
// TODO: Add MessagesFilter.PreFilterMessage implementation
if (m.Msg == WM_KEYUP)
{
if (m.HWnd == ControlHandler)
{
Keys k = ((Keys) ((int) m.WParam));
if (k == Keys.Enter)
return true;
}
}
return false;
}
#endregion
}
As always, the UI should be presented in such a way that user understands what the application is doing and should talk to the user with UI elements.
As Adam Houldsworth suggested I would also prefer keeping the button either disabled or enabled but I would also suggest that the caption of the button should convey the message to the user that the long processing is in progress when the new thread starts..and so the caption of the button should be immediately changed to something like "Processing..Please wait..." (in addition to being disabled or even if you want to keep it enabled), and then if you have kept the button enabled just check the caption of the button (or a isProcessing bool flag) on its click event to return if it says "Processing..Please wait..." or (isProcessing == true).
Lots of the Websites which help users to upload files/images change the Upload button's caption to "Uploading..Please wait..." to inform the user to wait until the upload finishes and additionally some sites also disable the upload button so that the user is not able to click again on Upload button.
You would need to also revert back the caption to normal when the thread finishes long processing.
There may be other advanced ways but the idea is to keep it as simple and basic as possible.
Look at this example on Threading in Windows Forms which shows to disable the button while multi-threading.
+1 for all the suggestions so far. As CSharpVJ suggests - My idea was to additionally inform the user by changing the button's caption making the UI design more intuitive
This can be achieved elegantly with Backgroundworker component in Winforms [No hassles code]. Just copy-paste and HIT F5 (After creating a New Winforms Project with a Button and a Label on it)!
You do not have to check anything related to button here. Everything will be taken care by the appropriate event handlers. its just that you have to do correct stuffs int he resepctive event handlers. Try it !
using System.ComponentModel;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form3 : Form
{
private BackgroundWorker _worker;
public Form3()
{
InitializeComponent();
InitWorker();
}
private void InitWorker()
{
if (_worker != null)
{
_worker.Dispose();
}
_worker = new BackgroundWorker
{
WorkerReportsProgress = true,
WorkerSupportsCancellation = true
};
_worker.DoWork += DoWork;
_worker.RunWorkerCompleted += RunWorkerCompleted;
_worker.ProgressChanged += ProgressChanged;
}
/// do time consuming work here...
void DoWork(object sender, DoWorkEventArgs e)
{
int highestPercentageReached = 0;
if (_worker.CancellationPending)
{
e.Cancel = true;
}
else
{
double i = 0.0d;
for (i = 0; i <= 199990000; i++)
{
// Report progress as a percentage of the total task.
var percentComplete = (int)(i / 199990000 * 100);
if (percentComplete > highestPercentageReached)
{
highestPercentageReached = percentComplete;
// Report UI abt the progress
_worker.ReportProgress(percentComplete);
_worker.CancelAsync();
}
}
}
}
void RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
button1.Enabled = true;
if (e.Cancelled)
{
// Display some message to the user that task has been
// cancelled
label1.Text = "Cancelled the operation";
}
else if (e.Error != null)
{
// Do something with the error
}
button1.Text = "Start again";
}
void ProgressChanged(object sender, ProgressChangedEventArgs e)
{
label1.Text = string.Format("Result {0}: Percent {1}",e.UserState, e.ProgressPercentage);
}
private void OnStartClick(object sender, System.EventArgs e)
{
_worker.RunWorkerAsync();
button1.Text = "Processing started...";
button1.Enabled = false;
}
}
}
As mentioned in other answers, there is probably a better solution than what you are asking for.
To directly answer your question, check out the IMessageFilter interface
Create your filter to have it suppress the mouse messages you don't desire, apply it when necessary using Application.AddMessageFilter().
Something along these lines (this should probably compile...):
public class MouseButtonFilter : IMessageFilter
{
private const int WM_LBUTTONDOWN = 0x0201;
private const int WM_LBUTTONUP = 0x0202;
private const int WM_LBUTTONDBLCLK = 0x0203;
private const int WM_RBUTTONDOWN = 0x0204;
private const int WM_RBUTTONUP = 0x0205;
private const int WM_RBUTTONDBLCLK = 0x0206;
private const int WM_MBUTTONDOWN = 0x0207;
private const int WM_MBUTTONUP = 0x0208;
bool IMessageFilter.PreFilterMessage(ref Message m)
{
switch (m.Msg)
{
case WM_LBUTTONDOWN:
/* case ... (list them all here; i'm being lazy) */
case WM_MBUTTONUP:
return true;
}
return false;
}
}

My app has multiple windows, I want to do something when the mouse is outside of all of them for a specified time

My application has a couple of windows. I want to perform a certain action once the mouse is outside of all my windows for a specified time (say half a second).
For a single window I'd start a timer in the MouseLeave event, and delete that time in the MouseEnter event, but how would I go about implementing this for multiple windows ?
All Windows and Pages have access to App. Just start and cancel a BackGroundWorker that has a built in delay. If the worker completes then do you thing. I tested this with two pages.
Register a MouseEnter and MouseLeave on all pages
private void MainWindowsMouseLeave(object sender, MouseEventArgs e)
{
// MessageBox.Show("MouseLeave");
tbMouseEnterLeave.Text = "MouseLeave";
if (App.BackgroundWorkerApp.IsBusy) App.BackgroundWorkerApp.CancelAsync();
else
{
Thread.Sleep(10);
if (App.BackgroundWorkerApp.IsBusy)App.BackgroundWorkerApp.CancelAsync();
}
if (!App.BackgroundWorkerApp.IsBusy) App.BackgroundWorkerApp.RunWorkerAsync();
}
private void MainWindowsMouseEnter(object sender, MouseEventArgs e)
{
tbMouseEnterLeave.Text = "MouseEnter";
App.BackgroundWorkerApp.CancelAsync();
}
public partial class App : Application
{
private static System.ComponentModel.BackgroundWorker backgroundWorkerApp = new BackgroundWorker();
public App()
{
backgroundWorkerApp.WorkerSupportsCancellation = true;
backgroundWorkerApp.DoWork +=
new DoWorkEventHandler(backgroundWorkerApp_DoWork);
backgroundWorkerApp.RunWorkerCompleted +=
new RunWorkerCompletedEventHandler(
backgroundWorkerApp_RunWorkerCompleted);
}
public static System.ComponentModel.BackgroundWorker BackgroundWorkerApp { get { return backgroundWorkerApp; } }
private void backgroundWorkerApp_DoWork(object sender,
DoWorkEventArgs e)
{
// Get the BackgroundWorker that raised this event.
BackgroundWorker worker = sender as BackgroundWorker;
e.Result = ComputeApp(worker, e);
}
// This event handler deals with the results of the
// background operation.
private void backgroundWorkerApp_RunWorkerCompleted(
object sender, RunWorkerCompletedEventArgs e)
{
// First, handle the case where an exception was thrown.
if (e.Error != null)
{
MessageBox.Show(e.Error.Message);
}
else if (e.Cancelled)
{
// Next, handle the case where the user canceled
// the operation.
// Note that due to a race condition in
// the DoWork event handler, the Cancelled
// flag may not have been set, even though
// CancelAsync was called.
// MessageBox.Show("Cancel");
}
else
{
// Finally, handle the case where the operation
// succeeded.
// this where you do that thing you want to do
MessageBox.Show("Complete");
}
}
string ComputeApp(BackgroundWorker worker, DoWorkEventArgs e)
{
// Abort the operation if the user has canceled.
// Note that a call to CancelAsync may have set
// CancellationPending to true just after the
// last invocation of this method exits, so this
// code will not have the opportunity to set the
// DoWorkEventArgs.Cancel flag to true. This means
// that RunWorkerCompletedEventArgs.Cancelled will
// not be set to true in your RunWorkerCompleted
// event handler. This is a race condition.
if (worker.CancellationPending)
{
e.Cancel = true;
return "cancelled";
}
for (int i=0; i < 10; i++)
{
Thread.Sleep(100);
if (worker.CancellationPending)
{
e.Cancel = true;
return "cancelled";
}
}
return "complete";
}
}

WPF closing child- closes parent-window

i have the pretty same sample as mentioned here.
Fast concluded: MainWindow closes when the last childwindow is closed.
My Problem: I couldn't solve my problems with the described solutions. I can't produce a program where it als takes place. Only in one of my bigger progs. Maybe someone has an idea or knows any further steps.
Thanks for reading - Thomas
As requested here's a bit of code:
This is the part in the MainWindow:
bool editAfterSearch = false;
Movie selectedMovie = (Movie)this.listView.SelectedItem;
Movie backup = (Movie)selectedMovie.Clone();
if (new OnlineSearchWindow().EditMovieViaOnlineSearch(ref selectedMovie, out editAfterSearch))
{
this.coverFlow.Update(selectedMovie);
}
And that's the part of the ChildWindow:
public bool EditMovieViaOnlineSearch(ref Movie preset, out bool editAfter)
{
this.exitWithOk = false;
this.editMovieAfterSearch = false;
this.tbx_SearchTerm.Text = preset.Title;
this.linkedMovie = preset;
this.ShowDialog();
editAfter = editMovieAfterSearch;
if (this.exitWithOk)
{
this.linkedMovie.CloneOnlineInformation(ref preset);
preset.Bitmap = this.linkedMovie.Bitmap;
return true;
}
else
{
return false;
}
}
Try playing with the ShutDownMode property of your App.xaml.cs. The 3 values are OnMainWindowClose, OnLastWindowClose, and OnExplicitShutdown, and the default is OnLastWindowClose
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
this.ShutdownMode = System.Windows.ShutdownMode.OnMainWindowClose;
}
}
The below code worked for me.
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
if (MessageBoxResult.No == (MessageBox.Show("Are you sure you want to close this?.", "ProjectName", MessageBoxButton.YesNo)))
{
e.Cancel = true;
foreach (var item in Application.Current.Windows)
{
Window window = item as Window;
if (window.Title == "PopUpWindowName")
{
window.Topmost = true;
break;
}
}
return;
}
else
{
base.OnClosed(e);
Application.Current.Shutdown();
}
}
you can try setting the child window's allowShutDown to false and then show the mainwindow. I'm assuming you will start with mainwindow's visibility set to hidden.
Application.Current.MainWindow.Visibility = System.Windows.Visibility.Visible;
this.allowShutDown = false;
The allowShutDown will be your own property which u can set to enable you have to handle the closing event.

WPF & Multi-threading questions

I'm working on building a multi-threaded UI. I would like long processes to be handled by the BackgroundWorker class, and have a small timer on the UI to keep track of how long the process is taking. It's my first time building such a UI, so I'm reading up on related resources on the web. My test code is thus:
private BackgroundWorker worker;
private Stopwatch swatch = new Stopwatch();
private delegate void simpleDelegate();
System.Timers.Timer timer = new System.Timers.Timer(1000);
string lblHelpPrevText = "";
private void btnStart_Click(object sender, RoutedEventArgs e)
{
try
{
worker = new BackgroundWorker(); //Create new background worker thread
worker.DoWork += new DoWorkEventHandler(BG_test1);
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(BG_test1end);
worker.RunWorkerAsync();
simpleDelegate del = new simpleDelegate(clockTicker);
AsyncCallback callBack = new AsyncCallback(clockEnd);
IAsyncResult ar = del.BeginInvoke(callBack, null);
lblHelpText.Text = "Processing...";
}
finally
{
worker.Dispose(); //clear resources
}
}
private void clockTicker()
{
//Grab Text
simpleDelegate delLblHelpText = delegate()
{ lblHelpPrevText = this.lblHelpText.Text; };
this.Dispatcher.BeginInvoke(DispatcherPriority.Send, delLblHelpText);
//Start clock
timer.Elapsed += new ElapsedEventHandler(clockTick);
timer.Enabled = true;
swatch.Start();
}
private void clockTick(object sender, ElapsedEventArgs e)
{
simpleDelegate delUpdateHelpTxt = delegate()
{ this.lblHelpText.Text = String.Format("({0:00}:{1:00}) {2}", swatch.Elapsed.Minutes, swatch.Elapsed.Seconds, lblHelpPrevText); };
this.Dispatcher.BeginInvoke(DispatcherPriority.Send, delUpdateHelpTxt);
}
private void BG_test1(object sender, DoWorkEventArgs e)
{
//this.lblHelpText.Text = "Processing for 10 seconds...";
Thread.Sleep(15000);
}
private void BG_test1end(object sender, RunWorkerCompletedEventArgs e)
{
this.lblHelpText.Text = "Process done.";
this.timer.Enabled = false;
this.swatch.Stop();
this.swatch.Reset();
}
static void clockEnd(IAsyncResult ar)
{
simpleDelegate X = (simpleDelegate)((AsyncResult)ar).AsyncDelegate;
X.EndInvoke(ar);
}
The idea is when the button is clicked, we take the status text from a Label (e.g. "Processing...") then append the time onto it every second. I could not access the UI elements from the Timer class as it's on a different thread, so I had to use delegates to get and set the text.
It works, but is there a better way to handle this? The code seems much for such a basic operation. I'm also not fully understanding the EndInvoke bit at the bottom. I obtained the snippet of code from this thread Should One Always Call EndInvoke a Delegate inside AsyncCallback?
I understand the idea of EndInvoke is to receive the result of BeginInvoke. But is this the correct way to use it in this situation? I'm simply worried about any resource leaks but when debugging the callback appears to execute before my timer starts working.
Don't use a separate timer to read the progress of your BackgroundWorker and update the UI. Instead, make the BackgroundWorker itself "publish" its progress to the UI directly or indirectly.
This can be done pretty much anyway you want to, but there's a built-in provision exactly for this case: the BackgroundWorker.ProgressChanged event.
private void BG_test1(object sender, DoWorkEventArgs e)
{
for(var i = 0; i < 15; ++i) {
Thread.Sleep(1000);
// you will need to get a ref to `worker`
// simplest would be to make it a field in your class
worker.ReportProgress(100 / 15 * (i + 1));
}
}
This way you can simply attach your own handler to ProgressChanged and update the UI using BeginInvoke from there. The timer and everything related to it can (and should) go.
You can use timer to update UI. It is normal practice. Just instead of System.Timer.Timer I suggest use System.Windows.Threading.DispatcherTimer. The DispatcherTimer runs on the same thread as the Dispatcher. Also, instead of BackgroundWorker you can use ThreadPool.
Here is my sample:
object syncObj = new object();
Stopwatch swatch = new Stopwatch();
DispatcherTimer updateTimer; // Assume timer was initialized in constructor.
void btnStart_Click(object sender, RoutedEventArgs e) {
lock (syncObj) {
ThreadPool.QueueUserWorkItem(MyAsyncRoutine);
swatch.Start();
updateTimer.Start();
}
}
void updateTimer_Tick(object sender, EventArgs e) {
// We can access UI elements from this place.
lblHelpText.Text = String.Format("({0:00}:{1:00}) Processing...", swatch.Elapsed.Minutes, swatch.Elapsed.Seconds);
}
void MyAsyncRoutine(object state) {
Thread.Sleep(5000);
lock (syncObj)
Dispatcher.Invoke(new Action(() => {
swatch.Stop();
updateTimer.Stop();
lblHelpText.Text = "Process done.";
}), null);
}
private void button1_Click(object sender, EventArgs e)
{
string strFullFilePath = #"D:\Print.pdf";
ProcessStartInfo ps = new ProcessStartInfo();
ps.UseShellExecute = true;
ps.Verb = "print";
ps.CreateNoWindow = true;
ps.WindowStyle = ProcessWindowStyle.Hidden;
ps.FileName = strFullFilePath;
Process.Start(ps);
Process proc = Process.Start(ps);
KillthisProcess("AcroRd32");
}
public void KillthisProcess(string name)
{
foreach (Process prntProcess in Process.GetProcesses())
{
if (prntProcess.ProcessName.StartsWith(name))
{
prntProcess.WaitForExit(10000);
prntProcess.Kill();
}
}
}

Resources