I'm using a HtmlEditor control inside a Windows Form.
I got the control from this page:
http://windowsclient.net/articles/htmleditor.aspx
I want to extend the controls functionality by allowing the user to paste images from the clipboard. Right now you can paste plain and formatted text, but when trying to paste an image it does nothing.
Basically what I thought was to detect when the user presses Ctrl+V on the editor, check the clipboard for images and if there's an image, insert it manually to the editor.
The problem with this approach is that I cannot get the OnKeyDown or OnKeyPress events of the form to be raised.
I have the KeyPreview property set to true on the form, but still the events aren't raised.
I also tried to Subclass the form and the editor (as explained here) to intercept the WM_PASTE message, but it isn't raised either.
Any ideas on how to achieve this?
Thanks a lot
I spent all day on this problem and finally have a solution. Trying to listen for the WM_PASTE message doesn't work because Ctrl-V is being PreProcessed by the underlying mshtml Control. You can listen for OnKeyDown/Up etc to catch a Ctrl-V but this won't stop the underlying Control from proceeding with its default Paste behavior. My solution is to prevent the PreProcessing of the Ctrl-V message and then implementing my own Paste behavior. To stop the control from PreProcessing the CtrlV message I had to subclass my Control which is AxWebBrowser,
public class DisabledPasteWebBrowser : AxWebBrowser
{
const int WM_KEYDOWN = 0x100;
const int CTRL_WPARAM = 0x11;
const int VKEY_WPARAM = 0x56;
Message prevMsg;
public override bool PreProcessMessage(ref Message msg)
{
if (prevMsg.Msg == WM_KEYDOWN && prevMsg.WParam == new IntPtr(CTRL_WPARAM) && msg.Msg == WM_KEYDOWN && msg.WParam == new IntPtr(VKEY_WPARAM))
{
// Do not let this Control process Ctrl-V, we'll do it manually.
HtmlEditorControl parentControl = this.Parent as HtmlEditorControl;
if (parentControl != null)
{
parentControl.ExecuteCommandDocument("Paste");
}
return true;
}
prevMsg = msg;
return base.PreProcessMessage(ref msg);
}
}
Here is my custom method to handle Paste commands, yours might do something similar with the Image data from the Clipboard.
internal void ExecuteCommandDocument(string command, bool prompt)
{
try
{
// ensure command is a valid command and then enabled for the selection
if (document.queryCommandSupported(command))
{
if (command == HTML_COMMAND_TEXT_PASTE && Clipboard.ContainsImage())
{
// Save image to user temp dir
String imagePath = Path.GetTempPath() + "\\" + Path.GetRandomFileName() + ".jpg";
Clipboard.GetImage().Save(imagePath, System.Drawing.Imaging.ImageFormat.Jpeg);
// Insert image href in to html with temp path
Uri uri = null;
Uri.TryCreate(imagePath, UriKind.Absolute, out uri);
document.execCommand(HTML_COMMAND_INSERT_IMAGE, false, uri.ToString());
// Update pasted id
Guid elementId = Guid.NewGuid();
GetFirstControl().id = elementId.ToString();
// Fire event that image saved to any interested listeners who might want to save it elsewhere as well
if (OnImageInserted != null)
{
OnImageInserted(this, new ImageInsertEventArgs { HrefUrl = uri.ToString(), TempPath = imagePath, HtmlElementId = elementId.ToString() });
}
}
else
{
// execute the given command
document.execCommand(command, prompt, null);
}
}
}
catch (Exception ex)
{
// Unknown error so inform user
throw new HtmlEditorException("Unknown MSHTML Error.", command, ex);
}
}
Hope someone finds this helpful and doesn't waste a day on it like me today.
Related
I am working on trying to close a specific MessageBox if it shows up based on the caption and text. I have it working when the MessageBox doesn't have an icon.
IntPtr handle = FindWindowByCaption(IntPtr.Zero, "Caption");
if (handle == IntPtr.Zero)
return;
//Get the Text window handle
IntPtr txtHandle = FindWindowEx(handle, IntPtr.Zero, "Static", null);
int len = GetWindowTextLength(txtHandle);
//Get the text
StringBuilder sb = new StringBuilder(len + 1);
GetWindowText(txtHandle, sb, len + 1);
//close the messagebox
if (sb.ToString() == "Original message")
{
SendMessage(new HandleRef(null, handle), WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
}
The above code works just fine when the MessageBox is shown without an icon like the following.
MessageBox.Show("Original message", "Caption");
However, if it includes an icon (from MessageBoxIcon) like the following, it doesn't work; GetWindowTextLength returns 0 and nothing happens.
MessageBox.Show("Original message", "Caption", MessageBoxButtons.OK, MessageBoxIcon.Information);
My best guess is that the 3rd and/or 4th paramters of FindWindowEx need to change but I'm not sure what to pass instead. Or maybe the 2nd parameter needs to change to skip the icon? I'm not really sure.
It appears that when the MessageBox has an icon, FindWindowEx returns the text of the first child (which is the icon in this case) hence, the zero length. Now, with the help of this answer, I got the idea to iterate the children until finding one with a text. This should work:
IntPtr handle = FindWindowByCaption(IntPtr.Zero, "Caption");
if (handle == IntPtr.Zero)
return;
//Get the Text window handle
IntPtr txtHandle = IntPtr.Zero;
int len;
do
{
txtHandle = FindWindowEx(handle, txtHandle, "Static", null);
len = GetWindowTextLength(txtHandle);
} while (len == 0 && txtHandle != IntPtr.Zero);
//Get the text
StringBuilder sb = new StringBuilder(len + 1);
GetWindowText(txtHandle, sb, len + 1);
//close the messagebox
if (sb.ToString() == "Original message")
{
SendMessage(new HandleRef(null, handle), WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
}
Obviously, you could adjust it to fit your particular situation (e.g., keep iterating until you find the actual text you're looking for) although I think the child with the text will probably always be the second one:
This is a UI Automation method that can detect a Window Opened event anywhere in the System, identify the Window using the Text of one its child elements and close the Window upon positive identification.
The detection is initialized using Automation.AddAutomationEventHandler with WindowPattern.WindowOpenedEvent and Automation Element argument set to AutomationElement.RootElement, which, having no other ancestors, identifies the whole Desktop (any Window).
The WindowWatcher class exposes a public method (WatchWindowBySubElementText) that allows to specify the Text contained in one of the sub elements of a Window that just opened. If the specified Text is found, the method closes the Window and notifies the operation using a custom event handler that a subscriber can use to determine that the watched Window has been detected and closed.
Sample usage, using the Text string as provided in the question:
WindowWatcher watcher = new WindowWatcher();
watcher.ElementFound += (obj, evt) => { MessageBox.Show("Found and Closed!"); };
watcher.WatchWindowBySubElementText("Original message");
WindowWatcher class:
This class requires a Project Reference to these assemblies:
UIAutomationClient
UIAutomationTypes
Note that, upon identification, the class event removes the Automation
event handler before notifying the subscribers. This is just an
example: it points out that the handlers need to be removed at some
point. The class could implement IDisposable and remove the
handler(s) when disposed of.
EDIT:
Changed the condition that doesn't consider a Window created in the current Process:
if (element is null || element.Current.ProcessId != Process.GetCurrentProcess().Id)
As noted in the comments, it imposes a limitation that is probably not necessary: the Dialog could also belong to the current Process. I left there just the null check.
using System.Diagnostics;
using System.Windows.Automation;
public class WindowWatcher
{
public delegate void ElementFoundEventHandler(object sender, EventArgs e);
public event ElementFoundEventHandler ElementFound;
public WindowWatcher() { }
public void WatchWindowBySubElementText(string ElementText) =>
Automation.AddAutomationEventHandler(WindowPattern.WindowOpenedEvent,
AutomationElement.RootElement, TreeScope.Subtree, (UIElm, evt) => {
AutomationElement element = UIElm as AutomationElement;
try {
if (element is null) return;
AutomationElement childElm = element.FindFirst(TreeScope.Children,
new PropertyCondition(AutomationElement.NameProperty, ElementText));
if (childElm != null) {
(element.GetCurrentPattern(WindowPattern.Pattern) as WindowPattern).Close();
OnElementFound(new EventArgs());
}
}
catch (ElementNotAvailableException) {
// Ignore: generated when a Window is closed. Its AutomationElement
// is no longer available. Usually a modal dialog in the current process.
}
});
public void OnElementFound(EventArgs e)
{
// Automation.RemoveAllEventHandlers(); <= If single use. Add to IDisposable.Dispose()
ElementFound?.Invoke(this, e);
}
}
I'm working on a feature which is to create a backup when a open word saved each times.
I'm using the blow code to hooking into word process and bind events to it, the word is opened by process.
officeApplication = (Application)Marshal.GetActiveObject("Word.Application").
officeApplication.DocumentBeforeSave += new ApplicationEvents4_DocumentBeforeSaveEventHandler(App_BeforeSaveDocument);
And in App_BeforeSaveDocument I did my work.
I get officeApplication right, and bind events were fine, when I click save in word, the events triggered perfectly.
The problem is, a few seconds(may be 30s) after, the events will not fire anymore, no matter click save or save us or close document.
Is there any suggestions?
After a lot of researching, I still can't find the reason. And I decide to use a trick to approach it.
First, open a thread in the binding event:
static void App_BeforeSaveDocument(Microsoft.Office.Interop.Word.Document document, ref bool saveAsUI, ref bool cancel)
{
if (th != null)
th.Abort();
th = new Thread(backupOnSave);
th.IsBackground = true;
th.Start(document);
}
Then do an infinity loop in the thread:
internal static void backupOnSave(object obj)
{
try
{
Application app = obj as Application;
if (app == null || app.ActiveDocument == null)
{
return;
}
Microsoft.Office.Interop.Word.Document document = app.ActiveDocument;
if (!tempData.ContainsKey(document.FullName))
return;
var loopTicks = 2000;
while (true)
{
Thread.Sleep(loopTicks);
if (document.Saved)
{
if (!tempData.ContainsKey(document.FullName))
break;
var p = tempData[document.FullName];
var f = new FileInfo(p.FileFullName);
if (f.LastWriteTime != p.LastWriteTime)//changed, should create new backup
{
BackupFile(p, f);
p.LastWriteTime = f.LastWriteTime;
}
}
}
}
catch (Exception ex)
{
log.write(ex);
}
}
And it works fine. Don't remember to abort the thread when the document closed or exception happen.
I am in the process of integrating support for USB barcode scanners into a WPF application, but I have hit a snag.
Currently I am successfully identifying and capturing input with the help of this article.
The issue that I am facing is that although I am successfully identifying and routing input from the scanner device, I am unable to block the input events for further processing (e.g. if a textbox control on my application has input focus, the input from the scanner will be written to it).
I have tried two methods of capturing input:
var wndSourceHandle = HwndSource.FromHwnd(new WindowInteropHelper(
_wndEventSource = value).Handle);
wndSourceHandle.AddHook(this.WndProc);
WndProc definition:
private IntPtr WndProc(IntPtr hwnd, int iMessage, IntPtr wParam, IntPtr lParam, ref bool bisHandled)
{
if (iMessage == Win32.WM_INPUT)
{
var result = ProcessRawInput(lParam);
bisHandled = result != null && result.Value;
return IntPtr.Zero;
}
return IntPtr.Zero;
}
As well as:
ComponentDispatcher.ThreadFilterMessage += (ref MSG msg, ref bool handled) =>
{
if (msg.message == Win32.WM_INPUT)
{
var result = ProcessRawInput(msg.lParam);
handled = result != null && result.Value;
return;
}
handled = false;
};
The ProcessRawInput method returns true if the source of the input is the barcode scanner, false otherwise.
After a bit more research I found a solution applicable to WinForms here. I was able to modify it for WPF as follows:
ComponentDispatcher.ThreadFilterMessage += (ref MSG msg, ref bool handled) =>
{
if (msg.message == Win32.WM_INPUT)
{
var result = ProcessRawInput(msg.lParam);
this.m_bIgnoreNextKeyDownMessage = result != null && result.Value;
return;
}
if (msg.message == Win32.WM_KEYDOWN && this.m_bIgnoreNextKeyDownMessage)
{
handled = true;
}
this.m_bIgnoreNextKeyDownMessage = false;
};
This solution basically marks the first WM_KEYDOWN message after a barcode WM_INPUT message as "handled". I am not sure if this is the only/best/safest solution, but it looks like it does the trick.
Update:
With the above solution I still found that every now and again one random character from the scanned barcode would slip through to a focussed textbox - I am not sure why this is the case - could be a timing issue with the keyboard events as they are passed through the message handler. Another solution for checking whether the WM_KEYDOWN message should be ignored:
if (msg.message == Win32.WM_KEYDOWN && !String.IsNullOrEmpty(this.m_strKeyInput))
{
handled = true;
}
The buffer m_strKeyInput contains the current scanned barcode value - this buffer is empty when no barcode is available, built up one character at a time as the barcode scanner pushes down the barcode - and then emptied once a custom BarcodeScanned event is generated. The only drawback of this solution that I can think of is that all keyboards will stop functioning for the few milliseconds the barcode is being pushed from the scanner - which is acceptable for my scenario.
It sounds like since you are routing the input from the scanner (treating it as a keyboard) and into a textbox you could simply use one of the Preview* events on that textbox to perform your additional processing. For example you could override PreviewTextInput
private void TextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
// MyBarcodeScanner would be a reference to the IInput device that represents the scanner:
if (e.Device == MyBarcodeScanner)
{
// Process the text, rejecting it in this case.
// Marking the event as handled will prevent the 'TextChanged' event
// from firing, so the characters will not appear in the text box.
e.Handled = true;
}
else
{
// This is some other keyboard, by not handling the event,
// the users will be allowed to enter text normally.
}
}
You may have to do a little trial and error to figure out how to identify the barcode scanner that is identified by 'e.Device' but that should be relatively easy. Sorry I can't be more complete, but I don't have a barcode scanner of my own to experiment with.
I have Two Winform Applications and when I try to Copy paste Text from that ProcessCmdKey Works Correctly if i made a check its not pasted ..
But when i try to paste my text in notepad its getting pasted ... I just want to Achieve COPY and PASTE of Text in same application
If I Focus on other Windows forms Text has not to be pasted... is there any way ..
private const Keys CopyKeys = Keys.Control | Keys.C;
private const Keys PasteKeys = Keys.Control | Keys.V;
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
bool bVal = false;
Process[] p2 = Process.GetProcesses();
foreach (Process pro in p2)
{
if (string.Compare(pro.ProcessName, "TestForm.vshost", true) == 0 && (keyData == CopyKeys) || (keyData == PasteKeys))
{
bVal = true; // Text will be pasted
return true;
}
else
return base.ProcessCmdKey(ref msg, keyData); // Text will not be pasted
}
return bVal;
}
This works correctly. When i-I try to achieve same the same for Notepad wordpad. It getting pasted.
If you really want to be sure that other applications can't get the data in the clipboard you need to use a custom format and put your data in the clipboard yourself.
This is just an example how to do it. You need more work to have a working solution because you need to intercept the Ctrl+C yourself and put your data in the clipboard instead of using the predefined data formats that (by definition) are available for every application
public void cmdTest_Click(object sender, EventArgs e)
{
Clipboard.SetData("MyCustomFormat", new MyData("This text should not be pasted"));
if(Clipboard.ContainsData("MyCustomFormat"))
{
MyData result = Clipboard.GetData("MyCustomFormat") as MyData;
MessageBox.Show(result.MyValue);
}
}
[Serializable]
class MyData
{
string _internalValue;
public MyData(string newValue)
{ _internalValue = newValue;}
public string MyValue
{
get{return _internalValue;}
}
}
If you follow this method other applications cannot use your custom format (of course, if security is a real concern more tweaking will be required)
You can clear the Clipboard text when your application is minimized or lost focus.
I have a application that reads data from health cards and parse them for basic info like D.O.B., Health Card #, and names. Right now, I have a textbox that takes input from the card swiper and it works great, but I feel there could be a better approach for this.
I want to have a keyboard listener in the background of the application that captures input from the card swiper and parse the data without the need of a textbox. I figure I'll need a loop function in the Form1_Load that actively listens for keyboard inputs, prepare a buffer for the input, and then when a carriage return is detected, go ahead and parse the buffered data. When the parsing is done, clear the buffer.
My problem is I'm relatively new to C# and I don't know what I should use for listening to keyboard inputs without a textbox. Could someone point me in the right direction?
Here's my code in case if anyone's interested: http://pastebin.com/q6AkghvN
Just a note, I followed the credit card swipe guide from
http://www.markhagan.me/Samples/CreditCardSwipeMagneticStripProcessing and modified it slightly for my usecase.
--- EDITED ---
Thanks Paul and everyone else for their help!
Here is my solution if anyone is interested:
private void frmMain_KeyPress(object sender, KeyPressEventArgs e)
{
lblStatus.Text = "Reading Card...";
lblStatus.ForeColor = Color.Blue;
if (e.KeyChar != (char)Keys.Enter)
{
buffer += e.KeyChar;
}
else
{
lblStatus.Text = "Parsing Card...";
if (buffer.Contains('^') && buffer.Contains(';') && buffer.Contains('='))
{
try
{
string[] cardData = buffer.Split(';');
string[] caretData = cardData[0].Split('^');
string[] nameData = caretData[1].Split('/');
string[] equalData = cardData[1].Split('=');
tBoxHealthCardNumber.Text = equalData[0];
tBoxDateOfBirth.Text = FormatBirthday(equalData[1]);
tBoxFirstName.Text = TrimName(nameData[1]);
tBoxLastName.Text = TrimName(nameData[0]);
tBoxDateTimeScanned.Text = DateTime.UtcNow.ToString("yyyy/MM/dd HH:mm");
e.Handled = true;
}
catch (Exception)
{
throw;
}
}
else
{
lblStatus.Text = "Error Reading Card";
}
buffer = "";
lblStatus.Text = "Ready";
lblStatus.ForeColor = Color.Green;
}
}
If you add a key handler to the form you will not see the key presses when focus is on a control, e.g. a textbox. For the form to see the key presses even when there is a focused control, you must also enable the KeyPreview property.
You can then add a handler for KeyDown, KeyPress and/or KeyUp on the form as you desire to receive these events.
As you can read in the documentation to KeyPreview, if you set the Handled property to true, you can prevent the event from being subsequently sent to the focused control, i.e. you can hide certain key events from being seen by the focused control.