Run RichTextBox on its own thread? - multithreading
Background
I have a RichTextBox control I am using essentially like a console in my WinForms application. Currently my application-wide logger posts messages using delegates and one of the listeners is this RTB. The logger synchronously sends lots of short (less than 100 char) strings denoting event calls, status messages, operation results, etc.
Posting lots of these short messages to the RTB using BeginInvoke provides UI responsiveness until heavy parallel processing starts logging lots of messages and then the UI starts posting items out of order, or the text is far behind (hundreds of milliseconds). I know this because when the processing slows down or is stopped, the console keeps writing for some time afterwords.
My temporary solution was to invoke the UI synchronously and add a blocking collection buffer. Basically taking the many small items from the Logger and combining them in a stringbuilder to be posted in aggregate to the RTB. The buffer posts items as they come if the UI can keep up, but if the queue gets too high, then it aggregates them and then posts to the UI. The RTB is thus updated piece-meal and looks jumpy when lots of things are being logged.
Question
How can I run a RichTextBox control on its own UI thread to keep other buttons on the same Form responsive during frequent but small append operations? From research, I think I need to run an STA thread and call Application.Run() on it to put the RTB on its own thread, but the examples I found lacked substantive code samples and there don't seem to be any tutorials (perhaps because what I want to do is ill advised?). Also I wasn't sure if there where any pitfalls for a single Control being on its own thread relative to the rest of the Form. (ie. Any issues closing the main form or will the STA thread for the RTB just die with the form closing? Any special disposing? etc.)
This should demonstrate the issue once you add 3 Buttons and a RichTextBox to the form. What I essentially want to accomplish is factoring away the BufferedConsumer by having the RTB on its own thread. Most of this code was hacked out verbatim from my main application, so yes, it is ugly.
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
// Fields
private int m_taskCounter;
private static CancellationTokenSource m_tokenSource;
private bool m_buffered = true;
private static readonly object m_syncObject = new object();
// Properties
public IMessageConsole Application_Console { get; private set; }
public BufferedConsumer<StringBuilder, string> Buffer { get; private set; }
public Form1()
{
InitializeComponent();
m_tokenSource = new CancellationTokenSource();
Application_Console = new RichTextBox_To_IMessageConsole(richTextBox1);
Buffer =
new BufferedConsumer<StringBuilder, string>(
p_name: "Console Buffer",
p_appendBuffer: (sb, s) => sb.Append(s),
p_postBuffer: (sb) => Application_Console.Append(sb));
button1.Text = "Start Producer";
button2.Text = "Stop All";
button3.Text = "Toggle Buffering";
button1.Click += (o, e) => StartProducerTask();
button2.Click += (o, e) => CancelAllProducers();
button3.Click += (o, e) => ToggleBufferedConsumer();
}
public void StartProducerTask()
{
var Token = m_tokenSource.Token;
Task
.Factory.StartNew(() =>
{
var ThreadID = Interlocked.Increment(ref m_taskCounter);
StringBuilder sb = new StringBuilder();
var Count = 0;
while (!Token.IsCancellationRequested)
{
Count++;
sb.Clear();
sb
.Append("ThreadID = ")
.Append(ThreadID.ToString("000"))
.Append(", Count = ")
.AppendLine(Count.ToString());
if (m_buffered)
Buffer
.AppendCollection(sb.ToString()); // ToString mimicks real world Logger passing strings and not stringbuilders
else
Application_Console.Append(sb);
Sleep.For(1000);
}
}, Token);
}
public static void CancelAllProducers()
{
lock (m_syncObject)
{
m_tokenSource.Cancel();
m_tokenSource = new CancellationTokenSource();
}
}
public void ToggleBufferedConsumer()
{
m_buffered = !m_buffered;
}
}
public interface IMessageConsole
{
// Methods
void Append(StringBuilder p_message);
}
// http://stackoverflow.com/a/5706085/1718702
public class RichTextBox_To_IMessageConsole : IMessageConsole
{
// Constants
private const int WM_USER = 0x400;
private const int WM_SETREDRAW = 0x000B;
private const int EM_GETEVENTMASK = WM_USER + 59;
private const int EM_SETEVENTMASK = WM_USER + 69;
private const int EM_GETSCROLLPOS = WM_USER + 221;
private const int EM_SETSCROLLPOS = WM_USER + 222;
//Imports
[DllImport("user32.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, Int32 wMsg, Int32 wParam, ref Point lParam);
[DllImport("user32.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, Int32 wMsg, Int32 wParam, IntPtr lParam);
// Fields
private RichTextBox m_richTextBox;
private bool m_attachToBottom;
private Point m_scrollPoint;
private bool m_painting;
private IntPtr m_eventMask;
private int m_suspendIndex = 0;
private int m_suspendLength = 0;
public RichTextBox_To_IMessageConsole(RichTextBox p_richTextBox)
{
m_richTextBox = p_richTextBox;
var h = m_richTextBox.Handle;
m_painting = true;
m_richTextBox.DoubleClick += RichTextBox_DoubleClick;
m_richTextBox.MouseWheel += RichTextBox_MouseWheel;
}
// Methods
public void SuspendPainting()
{
if (m_painting)
{
m_suspendIndex = m_richTextBox.SelectionStart;
m_suspendLength = m_richTextBox.SelectionLength;
SendMessage(m_richTextBox.Handle, EM_GETSCROLLPOS, 0, ref m_scrollPoint);
SendMessage(m_richTextBox.Handle, WM_SETREDRAW, 0, IntPtr.Zero);
m_eventMask = SendMessage(m_richTextBox.Handle, EM_GETEVENTMASK, 0, IntPtr.Zero);
m_painting = false;
}
}
public void ResumePainting()
{
if (!m_painting)
{
m_richTextBox.Select(m_suspendIndex, m_suspendLength);
SendMessage(m_richTextBox.Handle, EM_SETSCROLLPOS, 0, ref m_scrollPoint);
SendMessage(m_richTextBox.Handle, EM_SETEVENTMASK, 0, m_eventMask);
SendMessage(m_richTextBox.Handle, WM_SETREDRAW, 1, IntPtr.Zero);
m_painting = true;
m_richTextBox.Invalidate();
}
}
public void Append(StringBuilder p_message)
{
var WatchDogTimer = Stopwatch.StartNew();
var MinimumRefreshRate = 2000;
m_richTextBox
.Invoke((Action)delegate
{
// Last resort cleanup
if (WatchDogTimer.ElapsedMilliseconds > MinimumRefreshRate)
{
// m_richTextBox.Clear(); // Real-world behaviour
// Sample App behaviour
Form1.CancelAllProducers();
}
// Stop Drawing to prevent flickering during append and
// allow Double-Click events to register properly
this.SuspendPainting();
m_richTextBox.SelectionStart = m_richTextBox.TextLength;
m_richTextBox.SelectedText = p_message.ToString();
// Cap out Max Lines and cut back down to improve responsiveness
if (m_richTextBox.Lines.Length > 4000)
{
var NewSet = new string[1000];
Array.Copy(m_richTextBox.Lines, 1000, NewSet, 0, 1000);
m_richTextBox.Lines = NewSet;
m_richTextBox.SelectionStart = m_richTextBox.TextLength;
m_richTextBox.SelectedText = "\r\n";
}
this.ResumePainting();
// AutoScroll down to display newest text
if (m_attachToBottom)
{
m_richTextBox.SelectionStart = m_richTextBox.Text.Length;
m_richTextBox.ScrollToCaret();
}
});
}
// Event Handler
void RichTextBox_DoubleClick(object sender, EventArgs e)
{
// Toggle
m_attachToBottom = !m_attachToBottom;
// Scroll to Bottom
if (m_attachToBottom)
{
m_richTextBox.SelectionStart = m_richTextBox.Text.Length;
m_richTextBox.ScrollToCaret();
}
}
void RichTextBox_MouseWheel(object sender, MouseEventArgs e)
{
m_attachToBottom = false;
}
}
public class BufferedConsumer<TBuffer, TItem> : IDisposable
where TBuffer : new()
{
// Fields
private bool m_disposed = false;
private Task m_consumer;
private string m_name;
private CancellationTokenSource m_tokenSource;
private AutoResetEvent m_flushSignal;
private BlockingCollection<TItem> m_queue;
// Constructor
public BufferedConsumer(string p_name, Action<TBuffer, TItem> p_appendBuffer, Action<TBuffer> p_postBuffer)
{
m_name = p_name;
m_queue = new BlockingCollection<TItem>();
m_tokenSource = new CancellationTokenSource();
var m_token = m_tokenSource.Token;
m_flushSignal = new AutoResetEvent(false);
m_token
.Register(() => { m_flushSignal.Set(); });
// Begin Consumer Task
m_consumer = Task.Factory.StartNew(() =>
{
//Handler
// .LogExceptions(ErrorResponse.SupressRethrow, () =>
// {
// Continuously consumes entries added to the collection, blocking-wait if empty until cancelled
while (!m_token.IsCancellationRequested)
{
// Block
m_flushSignal.WaitOne();
if (m_token.IsCancellationRequested && m_queue.Count == 0)
break;
// Consume all queued items
TBuffer PostBuffer = new TBuffer();
Console.WriteLine("Queue Count = " + m_queue.Count + ", Buffering...");
for (int i = 0; i < m_queue.Count; i++)
{
TItem Item;
m_queue.TryTake(out Item);
p_appendBuffer(PostBuffer, Item);
}
// Post Buffered Items
p_postBuffer(PostBuffer);
// Signal another Buffer loop if more items were Queued during post sequence
var QueueSize = m_queue.Count;
if (QueueSize > 0)
{
Console.WriteLine("Queue Count = " + QueueSize + ", Sleeping...");
m_flushSignal.Set();
if (QueueSize > 10 && QueueSize < 100)
Sleep.For(1000, m_token); //Allow Queue to build, reducing posting overhead if requests are very frequent
}
}
//});
}, m_token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool p_disposing)
{
if (!m_disposed)
{
m_disposed = true;
if (p_disposing)
{
// Release of Managed Resources
m_tokenSource.Cancel();
m_flushSignal.Set();
m_consumer.Wait();
}
// Release of Unmanaged Resources
}
}
// Methods
public void AppendCollection(TItem p_item)
{
m_queue.Add(p_item);
m_flushSignal.Set();
}
}
public static partial class Sleep
{
public static bool For(int p_milliseconds, CancellationToken p_cancelToken = default(CancellationToken))
{
//p_milliseconds
// .MustBeEqualOrAbove(0, "p_milliseconds");
// Exit immediate if cancelled
if (p_cancelToken != default(CancellationToken))
if (p_cancelToken.IsCancellationRequested)
return true;
var SleepTimer =
new AutoResetEvent(false);
// Cancellation Callback Action
if (p_cancelToken != default(CancellationToken))
p_cancelToken
.Register(() => SleepTimer.Set());
// Block on SleepTimer
var Canceled = SleepTimer.WaitOne(p_milliseconds);
return Canceled;
}
}
}
Posting answer as per the OP's request:
You can integrate my example of a Virtualized, High-Performance, Rich, highly customizable WPF log Viewer in your existing winforms application by using the ElementHost
Full source code in the link above
Related
Worker stop in the middle and not updating progress bar
I'm trying to load information with worker and using reportprogress to update progress bar until finshing, but the worker stop working and doesn't finish the for loop or continue after several minutes. I don't know what's the problem and I tried every post here I can find related to this problem and didn't get anything. I hope I can finally get an answer posting my problem here. Here's the relevant code: public class Class1 { public BackgroundWorker unit_read_data_setting_Worker; public Class1() { unit_read_data_setting_Worker = new BackgroundWorker(); unit_read_data_setting_Worker.WorkerReportsProgress = true; unit_read_data_setting_Worker.WorkerSupportsCancellation = false; unit_read_data_setting_Worker.DoWork += new DoWorkEventHandler(unit_read_data_setting); unit_read_data_setting_Worker.ProgressChanged += new ProgressChangedEventHandler(_update_progress); unit_read_data_setting_Worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(Completed); } public void unit_read_data_setting(object sender = null, DoWorkEventArgs e = null) { lock (FamilyTreeViewModel.Lock_ParameterList) { ObservableCollection<Parameter_UC_ViewModel> _internal_parameter_list = FamilyTreeViewModel.GetInstance.ParameterList; if (FamilyTreeViewModel.GetInstance.SelectedUnitSetting != null) { if (_internal_parameter_list.Count > 0) { for (int i = 0; i < _internal_parameter_list.Count; i++) { int startValue = i * 100 / _internal_parameter_list.Count; unit_read_data_setting_Worker.ReportProgress(startValue); // do stuff related to the index // when progress_bar at 76 value, the loop stoppes and cotinue after a minute } } } } } private void _update_progress(object sender, ProgressChangedEventArgs e) { FamilyTreeViewModel.GetInstance.Unit_read_data_setting_progress_bar = e.ProgressPercentage; } private void Completed(object sender, RunWorkerCompletedEventArgs e) { // never gets here but there's no error } }
How do you make Music shuffle on Android Studio? Using or without using arrays
I need help making the songs Shuffle (mixed) but I don't know how and also I want it to go to another music each time without repeating. public class MusicMix { private Music music1, music2, music3, music4, music5,music6,music7,music8,music9,music10,music11,music12,music13,music14,music15,music16,music17; music1 = Gdx.audio.newMusic(Gdx.files.internal("musicA.mp3")); music2 = Gdx.audio.newMusic(Gdx.files.internal("musicB.mp3")); music3 = Gdx.audio.newMusic(Gdx.files.internal("musicC.mp3")); music4 = Gdx.audio.newMusic(Gdx.files.internal("musicD.mp3")); music5 = Gdx.audio.newMusic(Gdx.files.internal("musicE.mp3")); music6 = Gdx.audio.newMusic(Gdx.files.internal("musicF.mp3")); music7 = Gdx.audio.newMusic(Gdx.files.internal("musicG.mp3")); music8 = Gdx.audio.newMusic(Gdx.files.internal("musicH.mp3")); music9 = Gdx.audio.newMusic(Gdx.files.internal("musicJ.mp3")); music10 = Gdx.audio.newMusic(Gdx.files.internal("musicK.mp3")); music11 = Gdx.audio.newMusic(Gdx.files.internal("musicL.mp3")); music12 = Gdx.audio.newMusic(Gdx.files.internal("musicM.mp3")); music13 = Gdx.audio.newMusic(Gdx.files.internal("musicN.mp3")); music14 = Gdx.audio.newMusic(Gdx.files.internal("musicO.mp3")); music15 = Gdx.audio.newMusic(Gdx.files.internal("musicP.mp3")); music16 = Gdx.audio.newMusic(Gdx.files.internal("musicQ.mp3")); music17 = Gdx.audio.newMusic(Gdx.files.internal("musicR.mp3")); }
First of all create first an array that should make your life easier. Instead of manually doing it one by one. Array<Music> musics = new Array<Music>(); for (int i = 65; i < 25; i++) { //The ascii of 65 = A char ascii = (char) i; Music music = Gdx.audio.newMusic(Gdx.files.internal("music" + ascii + ".mp3")); musics.add(music); } Then after that just call this method. It's a built-in method in Libgdx. This method should answer your question. musics.shuffle();
I tried to implement your requirement in this way. public class TestGame extends Game { private Array<String> musicName; private Array<Music> musicList; private IntArray intArray; #Override public void create() { musicName=new Array<String>(new String[]{"sound/x1.ogg","sound/x2.ogg","sound/x3.ogg","sound/x4.ogg","sound/x5.ogg"}); musicList=new Array<Music>(); intArray=new IntArray(); for (String path:musicName) { Music music=Gdx.audio.newMusic(Gdx.files.internal(path)); musicList.add(music); intArray.add(musicName.indexOf(path,true)); music.setOnCompletionListener(new OnComplete(this)); } int value=MathUtils.random(musicList.size-1); intArray.removeValue(value); musicList.get(value).play(); } public class OnComplete implements Music.OnCompletionListener { private TestGame testGame; public OnComplete(TestGame game){ testGame=game; } #Override public void onCompletion(Music music) { testGame.playUnPlayedMusic(); } } public void playUnPlayedMusic(){ int value ; if(intArray.size>0) { value = intArray.get(MathUtils.random(intArray.size - 1)); intArray.removeValue(value); }else { for (int i=0;i<musicList.size;i++) intArray.add(i); value = intArray.get(MathUtils.random(intArray.size - 1)); intArray.removeValue(value); } Music music= musicList.get(value); music.play(); } #Override public void render() { } }
How to implement keyboard handler cefSharp for shortcut keys
I've building a windows app for browsing web pages using cefSharp. I need to implement some short cut keys into that application, can any one tell me how can i achieve this functionality. Ex. ctrl + tab = move to next tab I'm able to track if user presses any single key, but unable to track multi key pressing. IKeyboardHandler public class KeyboardHandler : IKeyboardHandler { public bool OnKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey) { bool result = false; Debug.WriteLine(String.Format("OnKeyEvent: KeyType: {0} 0x{1:X} Modifiers: {2}", type, windowsKeyCode, modifiers)); // TODO: Handle MessageNeeded cases here somehow. return result; } public bool OnPreKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey, ref bool isKeyboardShortcut) { const int WM_SYSKEYDOWN = 0x104; const int WM_KEYDOWN = 0x100; const int WM_KEYUP = 0x101; const int WM_SYSKEYUP = 0x105; const int WM_CHAR = 0x102; const int WM_SYSCHAR = 0x106; const int VK_TAB = 0x9; bool result = false; isKeyboardShortcut = false; // Don't deal with TABs by default: // TODO: Are there any additional ones we need to be careful of? // i.e. Escape, Return, etc...? if (windowsKeyCode == VK_TAB) { return result; } Control control = browserControl as Control; int msgType = 0; switch (type) { case KeyType.RawKeyDown: if (isSystemKey) { msgType = WM_SYSKEYDOWN; } else { msgType = WM_KEYDOWN; } break; case KeyType.KeyUp: if (isSystemKey) { msgType = WM_SYSKEYUP; } else { msgType = WM_KEYUP; } break; case KeyType.Char: if (isSystemKey) { msgType = WM_SYSCHAR; } else { msgType = WM_CHAR; } break; default: Trace.Assert(false); break; } // We have to adapt from CEF's UI thread message loop to our fronting WinForm control here. // So, we have to make some calls that Application.Run usually ends up handling for us: PreProcessControlState state = PreProcessControlState.MessageNotNeeded; // We can't use BeginInvoke here, because we need the results for the return value // and isKeyboardShortcut. In theory this shouldn't deadlock, because // atm this is the only synchronous operation between the two threads. control.Invoke(new Action(() => { Message msg = new Message() { HWnd = control.Handle, Msg = msgType, WParam = new IntPtr(windowsKeyCode), LParam = new IntPtr(nativeKeyCode) }; // First comes Application.AddMessageFilter related processing: // 99.9% of the time in WinForms this doesn't do anything interesting. bool processed = Application.FilterMessage(ref msg); if (processed) { state = PreProcessControlState.MessageProcessed; } else { // Next we see if our control (or one of its parents) // wants first crack at the message via several possible Control methods. // This includes things like Mnemonics/Accelerators/Menu Shortcuts/etc... state = control.PreProcessControlMessage(ref msg); } })); if (state == PreProcessControlState.MessageNeeded) { // TODO: Determine how to track MessageNeeded for OnKeyEvent. isKeyboardShortcut = true; } else if (state == PreProcessControlState.MessageProcessed) { // Most of the interesting cases get processed by PreProcessControlMessage. result = true; } Debug.WriteLine(String.Format("OnPreKeyEvent: KeyType: {0} 0x{1:X} Modifiers: {2}", type, windowsKeyCode, modifiers)); Debug.WriteLine(String.Format("OnPreKeyEvent PreProcessControlState: {0}", state)); return result; }
Finally got an alternative to implement shortcut functionality without implementing IKeyboardHandler. Here i used Global Keyboard Hook to implement this. GlobalKeyboardHook public class GlobalKeyboardHook { #region Variables and dll import #region For key capturing [DllImport("user32.dll")] static extern int CallNextHookEx(IntPtr hhk, int code, int wParam, ref keyBoardHookStruct lParam); [DllImport("user32.dll")] static extern IntPtr SetWindowsHookEx(int idHook, LLKeyboardHook callback, IntPtr hInstance, uint theardID); [DllImport("user32.dll")] static extern bool UnhookWindowsHookEx(IntPtr hInstance); [DllImport("kernel32.dll")] static extern IntPtr LoadLibrary(string lpFileName); public delegate int LLKeyboardHook(int Code, int wParam, ref keyBoardHookStruct lParam); public struct keyBoardHookStruct { public int vkCode; public int scanCode; public int flags; public int time; public int dwExtraInfo; } const int WH_KEYBOARD_LL = 13; const int WM_KEYDOWN = 0x0100; const int WM_KEYUP = 0x0101; const int WM_SYSKEYDOWN = 0x0104; const int WM_SYSKEYUP = 0x0105; LLKeyboardHook llkh; public List<Keys> HookedKeys = new List<Keys>(); IntPtr Hook = IntPtr.Zero; public event KeyEventHandler KeyDown; public event KeyEventHandler KeyUp; #endregion #region For modifier capturing /// <summary> /// Gets the state of modifier keys for a given keycode. /// </summary> /// <param name="keyCode">The keyCode</param> /// <returns></returns> [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true, CallingConvention = CallingConvention.Winapi)] public static extern short GetKeyState(int keyCode); //Modifier key vkCode constants private const int VK_SHIFT = 0x10; private const int VK_CONTROL = 0x11; private const int VK_MENU = 0x12; private const int VK_CAPITAL = 0x14; #endregion #endregion #region Constructor // This is the Constructor. This is the code that runs every time you create a new GlobalKeyboardHook object public GlobalKeyboardHook() { llkh = new LLKeyboardHook(HookProc); // This starts the hook. You can leave this as comment and you have to start it manually // Or delete the comment mark and your hook will start automatically when your program starts (because a new GlobalKeyboardHook object is created) // That's why there are duplicates, because you start it twice! I'm sorry, I haven't noticed this... // hook(); <-- Choose! } ~GlobalKeyboardHook() { unhook(); } #endregion #region Functions and implementation /// <summary> /// Hook (Start listening keybord events) /// </summary> public void hook() { IntPtr hInstance = LoadLibrary("User32"); Hook = SetWindowsHookEx(WH_KEYBOARD_LL, llkh, hInstance, 0); } /// <summary> /// Unhook (Stop listening keybord events) /// </summary> public void unhook() { UnhookWindowsHookEx(Hook); } /// <summary> /// Pass key into event /// </summary> /// <param name="Code">Key code</param> /// <param name="wParam">int event type (keydown/keyup)</param> /// <param name="lParam">keyBoardHookStruct enum for detecting key</param> /// <returns>next hook call</returns> public int HookProc(int Code, int wParam, ref keyBoardHookStruct lParam) { if (Code >= 0) { Keys key = (Keys)lParam.vkCode; if (HookedKeys.Contains(key)) { //Get modifiers key = AddModifiers(key); KeyEventArgs kArg = new KeyEventArgs(key); if ((wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) && (KeyDown != null)) { KeyDown(this, kArg); } else if ((wParam == WM_KEYUP || wParam == WM_SYSKEYUP) && (KeyUp != null)) { KeyUp(this, kArg); } if (kArg.Handled) return 1; } } return CallNextHookEx(Hook, Code, wParam, ref lParam); } /// <summary> /// Checks whether Alt, Shift, Control or CapsLock /// is pressed at the same time as the hooked key. /// Modifies the keyCode to include the pressed keys. /// </summary> private Keys AddModifiers(Keys key) { //CapsLock if ((GetKeyState(VK_CAPITAL) & 0x0001) != 0) key = key | Keys.CapsLock; //Shift if ((GetKeyState(VK_SHIFT) & 0x8000) != 0) key = key | Keys.Shift; //Ctrl if ((GetKeyState(VK_CONTROL) & 0x8000) != 0) key = key | Keys.Control; //Alt if ((GetKeyState(VK_MENU) & 0x8000) != 0) key = key | Keys.Alt; return key; } #endregion } Start hook To start hook add below code to form load or application start //Start listening keybord events gHook = new GlobalKeyboardHook(); gHook.KeyDown += new KeyEventHandler(gHook_KeyDown); foreach (Keys key in Enum.GetValues(typeof(Keys))) { gHook.HookedKeys.Add(key); } gHook.hook(); Stop hook Add this code on application exit(form closing event) //Stop listening keybord events gHook.unhook(); Handel key down event for shortcut implementation public void gHook_KeyDown(object sender, KeyEventArgs e) { if (this.ContainsFocus) { if (e.KeyData.ToString().ToUpper().IndexOf("ALT".ToUpper()) >= 0 && e.KeyValue == 66)//B = 66 { //ALT + B new Thread(() => { // execute this on the gui thread. (winforms) this.Invoke(new Action(delegate { tosBrowserBtnBack_Click(this, new EventArgs()); })); }).Start(); } else if (e.KeyData.ToString().ToUpper().IndexOf("ALT".ToUpper()) >= 0 && e.KeyValue == 70)//F = 70 { //ALT + F new Thread(() => { // execute this on the gui thread. (winforms) this.Invoke(new Action(delegate { tosBrowserBtnFor_Click(this, new EventArgs()); })); }).Start(); } } Global hook works for every keystroke whether your application has focus or not, so here I've used this.ContainsFocus to check current form has focus or not, if has then handle shortcut events. If you need to implementation shortcut on hole application then you need to check if application has focus or not. For that you need to create a new class ApplicationFocus public class ApplicationFocus { /// <summary>Returns true if the current application has focus, false otherwise</summary> public static bool ApplicationIsActivated() { var activatedHandle = GetForegroundWindow(); if (activatedHandle == IntPtr.Zero) { return false; // No window is currently activated } var procId = Process.GetCurrentProcess().Id; int activeProcId; GetWindowThreadProcessId(activatedHandle, out activeProcId); return activeProcId == procId; } [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)] private static extern IntPtr GetForegroundWindow(); [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern int GetWindowThreadProcessId(IntPtr handle, out int processId); } Then replace if (this.ContainsFocus) with ApplicationFocus.ApplicationIsActivated() in gHook_KeyDown event. This is worked for me.
Arguments and changing the value
A newbie, please excuse the terms and explanation. I have a program that accepts arguments which are in seconds. This in turn runs a timer that shows on the form. I want to give the user the ability to pause the timer for a set amount of time. I am having difficulty in trying to add time after the user Clicks the button as the method that runs the timer and countdown I have a method that gets the parameters of the argument. I would like to maybe just get the parameters/arguments globally and then use them in all the different methods or threads. I am trying to do this in c# and wpf. Code below: public partial class MainWindow : Window { //int TimeOutPeriod = Int32.Parse("3600"); //private Timer timer; System.Timers.Timer timer = new System.Timers.Timer(); public MainWindow() { InitializeComponent(); //timer = new Timer(1000); timer = new System.Timers.Timer(1000); timer.Elapsed += new ElapsedEventHandler(timer_Elapsed); timer.Start(); string[] param = Environment.GetCommandLineArgs(); int TimeOutPeriod = Int32.Parse(param[1]); ProgressBar1.Maximum = TimeOutPeriod; } void timer_Elapsed(object sender, ElapsedEventArgs e) { string [] param = Environment.GetCommandLineArgs(); int TimeOutPeriod = Int32.Parse(param[1]); int InYourFaceDisplay = Int32.Parse(param[2]); this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, (Action)(() => { if (ProgressBar1.Value < TimeOutPeriod) { ProgressBar1.Value += 1; RebootCDown.Content = "Machine will reboot in : " + (TimeOutPeriod - ProgressBar1.Value) + " Secs"; if ((TimeOutPeriod - ProgressBar1.Value) < InYourFaceDisplay) { this.Activate(); } } else { ShutDownWindows("0"); timer.Stop(); this.Close(); } })); } private void SnoozeNow_Click(object sender, RoutedEventArgs e) { WindowState = WindowState.Minimized; System.Windows.Forms.NotifyIcon ni = new System.Windows.Forms.NotifyIcon(); ShowInTaskbar = false; ni.Icon = new System.Drawing.Icon("C:\\temp\\RebootIco.ico"); ni.Visible = true; ShowInTaskbar = false; ni.ShowBalloonTip(5000, "Test App", "Notify icon is working! Right click to access the context menu.", System.Windows.Forms.ToolTipIcon.Info); Task.Delay(10000); ShowInTaskbar = true; } }
List getting threadsafe with removing items
I'm trying to remove items from a list until its empty with multithreading. Code: public void testUsers() { final List<User> users = userDao.findAll(); final int availableProcessors = Runtime.getRuntime().availableProcessors() * multiplier; final List<String> loggingList = Lists.newArrayList(); final List<Integer> sizeChecked = Lists.newArrayList(); int totalSizeChecked = 0; int sizeList = users.size(); ExecutorService executorService = Executors.newFixedThreadPool(availableProcessors); for (int i = 0; i < availableProcessors; i++) { createThread(executorService, users, loggingList, sizeChecked); } executorService.shutdown(); try { // wait for all threads to die executorService.awaitTermination(1, TimeUnit.HOURS); } catch (InterruptedException ex) { } for (Integer count : sizeChecked) { totalSizeChecked += count; } Assert.assertTrue(totalSizeChecked==sizeList); } private void createThread(ExecutorService executorService, final List<User> users, final Collection<String> loggingList, final List<Integer> sizeChecked) { executorService.execute(new Runnable() { #Override public void run() { int totalChecked = 0; while (!users.isEmpty()) { User user = null; synchronized (users) { if (!users.isEmpty()) { user = users.remove(0); } } totalChecked++; if (user != null) { String reason = checkUser(user); if (reason != null) { loggingList.add(reason); } } else { LOGGER.info("user is null"); } } sizeChecked.add(totalChecked); } }); } Now I was thinking this couldn't be so wrong cause I made the list synchronised for removing the first item. I'm testing with a multiplier of 6.(on prod it will be lowered to 1-2) I get this in the email : The batch was not correctly executed. Size of accounts that must be checked : 28499. Size of accounts that have been checked: 25869 What do I wrong to get it threadsafe?
List<Integer> sizeChecked is not thread safe. Therefore you cannot add elements in parallel in it. Synchronize your add operation or use a thread-safe structure. If sizeChecked is just a counter, use an AtomicLong instead and make each thread increment it.