How to implement keyboard handler cefSharp for shortcut keys - c#-4.0

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.

Related

Xamarin IOS file picker : unable to get selected file from icloud drive

I've installed filepicker control from Nuget and added tried adding reference from MonoTouch10 folder and later from github to my xamarin.ios project.
FileData file = await CrossFilePicker.Current.PickFile();
if (file != null) { }
this is the code i added to my browse button, after selecting a file from iCloud drive, control never comes to "if condition".
and again when i click on browse button for second time, app crashes saying "only one operation can be active at a time".
Modifying the source code of the FilePickerImplementation plugin for the iOS platform worked, in this way:
using Foundation;
using MobileCoreServices;
using Plugin.FilePicker.Abstractions;
using System;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using UIKit;
using System.Diagnostics;
namespace Plugin.FilePicker
{
/// <summary>
/// Implementation for FilePicker
/// </summary>
public class FilePickerImplementation : NSObject, IUIDocumentMenuDelegate, IFilePicker
{
private int _requestId;
private TaskCompletionSource<FileData> _completionSource;
/// <summary>
/// Event which is invoked when a file was picked
/// </summary>
public EventHandler<FilePickerEventArgs> Handler
{
get;
set;
}
private void OnFilePicked(FilePickerEventArgs e)
{
Handler?.Invoke(null, e);
}
public void DidPickDocumentPicker(UIDocumentMenuViewController documentMenu, UIDocumentPickerViewController documentPicker)
{
documentPicker.DidPickDocument += DocumentPicker_DidPickDocument;
documentPicker.WasCancelled += DocumentPicker_WasCancelled;
documentPicker.DidPickDocumentAtUrls += DocumentPicker_DidPickDocumentAtUrls;
UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewController(documentPicker, true, null);
}
private void DocumentPicker_DidPickDocumentAtUrls(object sender, UIDocumentPickedAtUrlsEventArgs e)
{
var control = (UIDocumentPickerViewController)sender;
foreach (var url in e.Urls)
DocumentPicker_DidPickDocument(control, new UIDocumentPickedEventArgs(url));
control.Dispose();
}
private void DocumentPicker_DidPickDocument(object sender, UIDocumentPickedEventArgs e)
{
var securityEnabled = e.Url.StartAccessingSecurityScopedResource();
var doc = new UIDocument(e.Url);
var data = NSData.FromUrl(e.Url);
var dataBytes = new byte[data.Length];
System.Runtime.InteropServices.Marshal.Copy(data.Bytes, dataBytes, 0, Convert.ToInt32(data.Length));
string filename = doc.LocalizedName;
string pathname = doc.FileUrl?.ToString();
// iCloud drive can return null for LocalizedName.
if (filename == null)
{
// Retrieve actual filename by taking the last entry after / in FileURL.
// e.g. /path/to/file.ext -> file.ext
// filesplit is either:
// 0 (pathname is null, or last / is at position 0)
// -1 (no / in pathname)
// positive int (last occurence of / in string)
var filesplit = pathname?.LastIndexOf('/') ?? 0;
filename = pathname?.Substring(filesplit + 1);
}
OnFilePicked(new FilePickerEventArgs(dataBytes, filename, pathname));
}
/// <summary>
/// Handles when the file picker was cancelled. Either in the
/// popup menu or later on.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public void DocumentPicker_WasCancelled(object sender, EventArgs e)
{
{
var tcs = Interlocked.Exchange(ref _completionSource, null);
tcs.SetResult(null);
}
}
/// <summary>
/// Lets the user pick a file with the systems default file picker
/// For iOS iCloud drive needs to be configured
/// </summary>
/// <returns></returns>
public async Task<FileData> PickFile()
{
var media = await TakeMediaAsync();
return media;
}
private Task<FileData> TakeMediaAsync()
{
var id = GetRequestId();
var ntcs = new TaskCompletionSource<FileData>(id);
if (Interlocked.CompareExchange(ref _completionSource, ntcs, null) != null)
throw new InvalidOperationException("Only one operation can be active at a time");
var allowedUtis = new string[] {
UTType.UTF8PlainText,
UTType.PlainText,
UTType.RTF,
UTType.PNG,
UTType.Text,
UTType.PDF,
UTType.Image,
UTType.UTF16PlainText,
UTType.FileURL
};
var importMenu =
new UIDocumentMenuViewController(allowedUtis, UIDocumentPickerMode.Import)
{
Delegate = this,
ModalPresentationStyle = UIModalPresentationStyle.Popover
};
UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewController(importMenu, true, null);
var presPopover = importMenu.PopoverPresentationController;
if (presPopover != null)
{
presPopover.SourceView = UIApplication.SharedApplication.KeyWindow.RootViewController.View;
presPopover.PermittedArrowDirections = UIPopoverArrowDirection.Down;
}
Handler = null;
Handler = (s, e) => {
var tcs = Interlocked.Exchange(ref _completionSource, null);
tcs?.SetResult(new FileData(e.FilePath, e.FileName, () => { var url = new Foundation.NSUrl(e.FilePath); return new FileStream(url.Path, FileMode.Open, FileAccess.Read); }));
};
return _completionSource.Task;
}
public void WasCancelled(UIDocumentMenuViewController documentMenu)
{
var tcs = Interlocked.Exchange(ref _completionSource, null);
tcs?.SetResult(null);
}
private int GetRequestId()
{
var id = _requestId;
if (_requestId == int.MaxValue)
_requestId = 0;
else
_requestId++;
return id;
}
public async Task<bool> SaveFile(FileData fileToSave)
{
try
{
var documents = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
var fileName = Path.Combine(documents, fileToSave.FileName);
File.WriteAllBytes(fileName, fileToSave.DataArray);
return true;
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
return false;
}
}
public void OpenFile(NSUrl fileUrl)
{
var docControl = UIDocumentInteractionController.FromUrl(fileUrl);
var window = UIApplication.SharedApplication.KeyWindow;
var subViews = window.Subviews;
var lastView = subViews.Last();
var frame = lastView.Frame;
docControl.PresentOpenInMenu(frame, lastView, true);
}
public void OpenFile(string fileToOpen)
{
var documents = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
var fileName = Path.Combine(documents, fileToOpen);
if (NSFileManager.DefaultManager.FileExists(fileName))
{
var url = new NSUrl(fileName, true);
OpenFile(url);
}
}
public async void OpenFile(FileData fileToOpen)
{
var documents = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
var fileName = Path.Combine(documents, fileToOpen.FileName);
if (NSFileManager.DefaultManager.FileExists(fileName))
{
var url = new NSUrl(fileName, true);
OpenFile(url);
}
else
{
await SaveFile(fileToOpen);
OpenFile(fileToOpen);
}
}
}
}
To answer my own question, i customized in plugin, like
Used DocumentPicker_DidPickDocumentAtUrls event instead of DocumentPicker_DidPickDocument.
while returning selected file used
new FileData(e.FilePath, e.FileName, () =>
{
var url = new Foundation.NSUrl(e.FilePath);
return new FileStream(url.Path, FileMode.Open, FileAccess.Read);
})
This solved my issue. Thanks.
There are several forks of the FilePicker Xamarin plugin. I recommend the following project, since it's the most actively maintained one:
https://github.com/jfversluis/FilePicker-Plugin-for-Xamarin-and-Windows (note: I'm one of the contributors to the project).
With this version of the plugin file picking should work. The example code from sandeep's answer was already incorporated into the latest version of the plugin. Be sure to read the README.md's Troubleshooting page in case you're getting problems.

When MFC (Feature Pack) calling for CDockablePane::Serialize()?

Does CDockablePane::Serialize() method is calling from MFC Feature Pack core?
I have dockable window class that inherited from CDockablePane class. My class override virtual Serialize() method and declared as serial DECLARE_SERIAL/IMPLEMENT_SERIAL. But MFC does not call my Serialize() method! Why ?
MSDN say that CDockablePane class have serialization methods: SaveState(), LoadState() and Serialize(). First two (SaveState(), LoadState()) are used internally and Serialize() used for "serializes the pane". But it is not calling!
No, the Serialize() method is not called by MFC framework automatically. You have to call it manually usually from CDocument::Serialize(). LoadState() and SaveState() methods are used to save/restore pane position and state. They do use Registry as storage.
As for DECLARE_SERIAL and IMPLEMENT_SERIAL they are used to support operator>> and other CArchive-specific things plus runtime class information. So you'll be able to use BOOL IsKindOf(RUNTIME_CLASS(...)) automagically.
My answer proposition. My class CSerializableDockablePane performs call for Serialize method when this docked pane is created or stored by framework (framework calls methods LoadState() and SaveState()).
You can use CSerializableDockablePane as base class for your dockable pane and overwrite virtual Serialize() method for your need.
Code:
class CSerializableDockablePane
: public CDockablePane
{
DECLARE_SERIAL(CSerializableDockablePane)
public:
typedef CDockablePane TBase;
public:
CSerializableDockablePane();
virtual ~CSerializableDockablePane();
virtual BOOL LoadState(LPCTSTR lpszProfileName = NULL, int nIndex = -1, UINT uiID = (UINT)-1);
virtual BOOL SaveState(LPCTSTR lpszProfileName = NULL, int nIndex = -1, UINT uiID = (UINT)-1);
virtual void Serialize(CArchive& ar);
};
/////////////////////////////////////////////////////////////////////////////
// CSerializableDockablePane
#define _MFC_DOCVIEW_PROFILE _T("DockableViews")
#define _REG_UI_DOCVIEWSECTION_FMT _T("%TsSerializableDockablePane-%d")
#define _REG_UI_DOCVIEWSECTION_FMT_EX _T("%TsSerializableDockablePane-%d%x")
#define _REG_UI_SETTINGS _T("Settings")
IMPLEMENT_SERIAL(CSerializableDockablePane, CSerializableDockablePane::TBase, VERSIONABLE_SCHEMA | 2)
CSerializableDockablePane::CSerializableDockablePane()
{
}
CSerializableDockablePane::~CSerializableDockablePane()
{
}
BOOL CSerializableDockablePane::LoadState(LPCTSTR lpszProfileName /*= NULL*/, int nIndex /*= -1*/, UINT uiID /*= (UINT)-1*/)
{
BOOL bRes = TBase::LoadState(lpszProfileName, nIndex, uiID);
if (bRes) {
const CString strProfileName = ::AFXGetRegPath(_MFC_DOCVIEW_PROFILE, lpszProfileName);
if (nIndex == -1) {
nIndex = GetDlgCtrlID();
}
CString strSection;
if (uiID == (UINT)-1) {
strSection.Format(_REG_UI_DOCVIEWSECTION_FMT, (LPCTSTR)strProfileName, nIndex);
}
else {
strSection.Format(_REG_UI_DOCVIEWSECTION_FMT_EX, (LPCTSTR)strProfileName, nIndex, uiID);
}
LPBYTE lpbData = nullptr;
UINT uiDataSize = 0;
CSettingsStoreSP regSP;
CSettingsStore& reg = regSP.Create(FALSE, TRUE);
if (!reg.Open(strSection)) {
return FALSE;
}
if (!reg.Read(_REG_UI_SETTINGS, &lpbData, &uiDataSize)) {
return FALSE;
}
try
{
CMemFile file(lpbData, uiDataSize);
CArchive ar(&file, CArchive::load);
Serialize(ar);
bRes = TRUE;
}
catch (CMemoryException* pEx)
{
pEx->Delete();
TRACE(_T("Memory exception in CSerializableDockablePane::LoadState()!\n"));
}
catch (CArchiveException* pEx)
{
pEx->Delete();
TRACE(_T("CArchiveException exception in CSerializableDockablePane::LoadState()!\n"));
}
if (lpbData != nullptr) {
delete[] lpbData;
}
}
return bRes;
}
BOOL CSerializableDockablePane::SaveState(LPCTSTR lpszProfileName /*= NULL*/, int nIndex /*= -1*/, UINT uiID /*= (UINT)-1*/)
{
BOOL bRes = TBase::SaveState(lpszProfileName, nIndex, uiID);
if (bRes) {
const CString strProfileName = ::AFXGetRegPath(_MFC_DOCVIEW_PROFILE, lpszProfileName);
if (nIndex == -1) {
nIndex = GetDlgCtrlID();
}
CString strSection;
if (uiID == (UINT)-1) {
strSection.Format(_REG_UI_DOCVIEWSECTION_FMT, (LPCTSTR)strProfileName, nIndex);
}
else {
strSection.Format(_REG_UI_DOCVIEWSECTION_FMT_EX, (LPCTSTR)strProfileName, nIndex, uiID);
}
try
{
CMemFile file;
{
CArchive ar(&file, CArchive::store);
Serialize(ar);
ar.Flush();
}
UINT uiDataSize = (UINT)file.GetLength();
LPBYTE lpbData = file.Detach();
if (lpbData != NULL)
{
CSettingsStoreSP regSP;
CSettingsStore& reg = regSP.Create(FALSE, FALSE);
if (reg.CreateKey(strSection)) {
bRes = reg.Write(_REG_UI_SETTINGS, lpbData, uiDataSize);
}
free(lpbData);
}
}
catch (CMemoryException* pEx)
{
pEx->Delete();
TRACE(_T("Memory exception in CSerializableDockablePane::SaveState()!\n"));
}
}
return bRes;
}
void CSerializableDockablePane::Serialize(CArchive& ar)
{
TBase::Serialize(ar);
}
// CSerializableDockablePane
/////////////////////////////////////////////////////////////////////////////

Run RichTextBox on its own thread?

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

What are the possible values for CreateParams.Style?

So, I've been playing with trying to create an AppBar program I'm making. Now, the program itself is actually quite simple but I had to borrow some code from a CodeProject project to make it an AppBar.
So, my code is the following:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
}
[StructLayout(LayoutKind.Sequential)]
struct RECT
{
public int left;
public int top;
public int right;
public int bottom;
}
[StructLayout(LayoutKind.Sequential)]
struct APPBARDATA
{
public int cbSize;
public IntPtr hWnd;
public int uCallbackMessage;
public int uEdge;
public RECT rc;
public IntPtr lParam;
}
enum ABMsg : int
{
ABM_NEW = 0,
ABM_REMOVE,
ABM_QUERYPOS,
ABM_SETPOS,
ABM_GETSTATE,
ABM_GETTASKBARPOS,
ABM_ACTIVATE,
ABM_GETAUTOHIDEBAR,
ABM_SETAUTOHIDEBAR,
ABM_WINDOWPOSCHANGED,
ABM_SETSTATE
}
enum ABNotify : int
{
ABN_STATECHANGE = 0,
ABN_POSCHANGED,
ABN_FULLSCREENAPP,
ABN_WINDOWARRANGE
}
enum ABEdge : int
{
ABE_LEFT = 0,
ABE_TOP,
ABE_RIGHT,
ABE_BOTTOM
}
private bool fBarRegistered = false;
private int uCallBack;
private int whereToPin;
[DllImport("SHELL32", CallingConvention = CallingConvention.StdCall)]
static extern uint SHAppBarMessage(int dwMessage, ref APPBARDATA pData);
[DllImport("USER32")]
static extern int GetSystemMetrics(int Index);
[DllImport("User32.dll", ExactSpelling = true,
CharSet = System.Runtime.InteropServices.CharSet.Auto)]
private static extern bool MoveWindow
(IntPtr hWnd, int x, int y, int cx, int cy, bool repaint);
[DllImport("User32.dll", CharSet = CharSet.Auto)]
private static extern int RegisterWindowMessage(string msg);
private void RegisterBar()
{
APPBARDATA abd = new APPBARDATA();
abd.cbSize = Marshal.SizeOf(abd);
abd.hWnd = this.Handle;
if (!fBarRegistered)
{
uCallBack = RegisterWindowMessage("AppBarMessage");
abd.uCallbackMessage = uCallBack;
uint ret = SHAppBarMessage((int)ABMsg.ABM_NEW, ref abd);
fBarRegistered = true;
ABSetPos();
}
else
{
SHAppBarMessage((int)ABMsg.ABM_REMOVE, ref abd);
fBarRegistered = false;
}
}
private void ABSetPos()
{
APPBARDATA abd = new APPBARDATA();
abd.cbSize = Marshal.SizeOf(abd);
abd.hWnd = this.Handle;
abd.uEdge = whereToPin;
if (abd.uEdge == (int)ABEdge.ABE_LEFT || abd.uEdge == (int)ABEdge.ABE_RIGHT)
{
abd.rc.top = 0;
abd.rc.bottom = SystemInformation.PrimaryMonitorSize.Height;
if (abd.uEdge == (int)ABEdge.ABE_LEFT)
{
abd.rc.left = 0;
abd.rc.right = Size.Width;
}
else
{
abd.rc.right = SystemInformation.PrimaryMonitorSize.Width;
abd.rc.left = abd.rc.right - Size.Width;
}
}
else
{
abd.rc.left = 0;
abd.rc.right = SystemInformation.PrimaryMonitorSize.Width;
if (abd.uEdge == (int)ABEdge.ABE_TOP)
{
abd.rc.top = 0;
abd.rc.bottom = Size.Height;
}
else
{
abd.rc.bottom = SystemInformation.PrimaryMonitorSize.Height;
abd.rc.top = abd.rc.bottom - Size.Height;
}
}
SHAppBarMessage((int)ABMsg.ABM_QUERYPOS, ref abd);
switch (abd.uEdge)
{
case (int)ABEdge.ABE_LEFT:
abd.rc.right = abd.rc.left + Size.Width;
break;
case (int)ABEdge.ABE_RIGHT:
abd.rc.left = abd.rc.right - Size.Width;
break;
case (int)ABEdge.ABE_TOP:
abd.rc.bottom = abd.rc.top + 100;
break;
case (int)ABEdge.ABE_BOTTOM:
abd.rc.top = abd.rc.bottom - Size.Height;
break;
}
SHAppBarMessage((int)ABMsg.ABM_SETPOS, ref abd);
MoveWindow(abd.hWnd, abd.rc.left, abd.rc.top,
abd.rc.right - abd.rc.left, abd.rc.bottom - abd.rc.top, true);
}
protected override void WndProc(ref System.Windows.Forms.Message m)
{
if (m.Msg == uCallBack)
{
switch (m.WParam.ToInt32())
{
case (int)ABNotify.ABN_POSCHANGED:
ABSetPos();
break;
}
}
base.WndProc(ref m);
}
protected override System.Windows.Forms.CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.Style &= (~0x00C00000); // WS_CAPTION
cp.Style &= (~0x00800000); // WS_BORDER
cp.ExStyle = 0x00000080 | 0x00000008; // WS_EX_TOOLWINDOW | WS_EX_TOPMOST
return cp;
}
}
private void button1_Click(object sender, EventArgs e)
{
whereToPin = (int)ABEdge.ABE_LEFT;
RegisterBar();
}
private void button2_Click(object sender, EventArgs e)
{
whereToPin = (int)ABEdge.ABE_RIGHT;
RegisterBar();
}
}
My two questions are:
What are the possible values cp.Style and how do those values effect the display of the AppBar? (The code to which I refer to is located in the System.Windows.Forms.CreateParams override)
I see they are values such as (~0x00C00000) but I have no idea how they work beyond those specific values and can't seem to find any enumeration of different values.
I'm a rather new, self teaching, programmer who does well by taking examples and molding them to my own uses. Thanks in advance for any help you can provide.
Here you go...
Window Styles
As per the OP's original query, there's also:
Window Class Styles

C# and RS485/USB receiving extraneous characters

In my project with six laser particle counters, when I send the commands, always return extraneous characters. And sometimes the string received in the form this incomplete. Below is a snippet of code: Who know fix this?
/// <summary>
/// Class to keep track of string and color for lines in output window.
/// </summary>
private class Line
{
public string Str;
public Color ForeColor;
public Line(string str, Color color)
{
Str = str;
ForeColor = color;
}
}
ArrayList lines = new ArrayList();
Font origFont;
Font monoFont;
public Form1()
{
InitializeComponent();
outputList_Initialize();
Settings.Read();
TopMost = Settings.Option.StayOnTop;
CommPort com = CommPort.Instance;
com.StatusChanged += OnStatusChanged;
com.DataReceived += OnDataReceived;
com.Open();
}
#region Event handling - data received and status changed
/// <summary>
/// Prepare a string for output by converting non-printable characters.
/// </summary>
/// <param name="StringIn">input string to prepare.</param>
/// <returns>output string.</returns>
private String PrepareData(String StringIn)
{
// The names of the first 32 characters
string[] charNames = {
// "NUL", "SOH", "STX", "ETX", "EOT",
//"ENQ", "ACK", "BEL", "BS", "TAB", "LF", "VT", "FF", "CR", "SO", "SI",
//"DLE", "DC1", "DC2", "DC3", "DC4", "NAK", "SYN", "ETB", "CAN", "EM", "SUB",
//"ESC", "FS", "GS", "RS", "US", "Space"
};
string StringOut = "";
foreach (char c in StringIn)
{
if (Settings.Option.HexOutput)
{
StringOut = StringOut + String.Format("{0:X2} ", (int)c);
}
else if (c < 32 && c != 9)
{
StringOut = StringOut + "";// +"<"+charNames[c]+">";
//Uglier "Termite" style
//StringOut = StringOut + String.Format("[{0:X2}]", (int)c);
}
else
{
StringOut = StringOut + c;
}
}
return StringOut;
}
/// <summary>
/// Partial line for AddData().
/// </summary>
private Line partialLine = null;
/// <summary>
/// Add data to the output.
/// </summary>
/// <param name="StringIn"></param>
/// <returns></returns>
private Line AddData(String StringIn)
{
String StringOut = PrepareData(StringIn);
// if we have a partial line, add to it.
if (partialLine != null)
{
// tack it on
partialLine.Str = partialLine.Str + StringOut;
outputList_Update(partialLine);
return partialLine;
}
return outputList_Add(StringOut, receivedColor);
}
// delegate used for Invoke
internal delegate void StringDelegate(string data);
/// <summary>
/// Handle data received event from serial port.
/// </summary>
/// <param name="data">incoming data</param>
public void OnDataReceived(string dataIn)
{
//Handle multi-threading
if (InvokeRequired)
{
Invoke(new StringDelegate(OnDataReceived), new object[] { dataIn });
return;
}
// pause scrolling to speed up output of multiple lines
bool saveScrolling = scrolling;
scrolling = false;
// if we detect a line terminator, add line to output
int index;
while (dataIn.Length > 0 &&
((index = dataIn.IndexOf("\r")) != -1 ||
(index = dataIn.IndexOf("\n")) != -1))
{
String StringIn = dataIn.Substring(0, index);
dataIn = dataIn.Remove(0, index + 1);
logFile_writeLine(AddData(StringIn).Str);
logFile_writeLine1(AddData(StringIn).Str);
partialLine = null; // terminate partial line
}
// if we have data remaining, add a partial line
if (dataIn.Length > 0)
{
partialLine = AddData(dataIn);
}
// restore scrolling
scrolling = saveScrolling;
outputList_Scroll();
listBox1_Scroll();
}
/// <summary>
/// Update the connection status
/// </summary>
public void OnStatusChanged(string status)
{
//Handle multi-threading
if (InvokeRequired)
{
Invoke(new StringDelegate(OnStatusChanged), new object[] { status });
return;
}
textBox1.Text = status;
}
#endregion
Could it be bit (or byte) stuffing ?

Resources