Make Autohotkey ignore any special characters in string - string

We're using a parser program (which I have no access to) that parses a bunch of computer generated mails but needs some help to decide on what it has to do in particular. Because of that, an employee kann use the subject line for additional commands. Since there are more than 500 mails per day that we feed to the program and the commands do all look similar to that: Ba,Vi;#TD*; x0003, it's impossible to write them manually. So I wrote a small C# script that creates an Autohotkey script which does 90% of the work. In theory. It works but only as long as I don't use any special characters, like , : & % etc.
I tried:
clipboard := Ba{,}Vi{;}{#}TD{*}{;} x0003
clipboard := "Ba,Vi;#TD*; x0003"
clipboard := Ba',Vi';'#TD'*'; x0003
clipboard := {raw} Ba,Vi;#TD*; x0003
(plus some others that I probably forgot here)
Here's the entire AHK script with annotations. You start it while having an email selected in Outlook:
;Win+z -> start script
#z::
;Currently only one iteration
loop,1
{
;CTRL+F to forward selected mail,
;which then automatically selects the "To:" line
Send, {CTRLDOWN}f{CTRUP}
Sleep, 500
Send, someemail#adress
Sleep, 500
;Initialize GUI
Gui, +AlwaysOnTop
Gui, Font, S5, Verdana, bold
Gui, Add, Text,, SCANNING-BOOSTER:
Gui, Color, F4A460
;Example for the C# generated buttons below (they all do the same thing):
;Clicking the Button "Google" will run the following script
;Start:
;clipboard := www.Google.com
;return
;This is the part where AHK fails because instead
;of www.Google.com I have codes like "Ba,Vi;#TD*; x0003" which crash AHK
Gui,add,Button,gLabel,Google
Gui,add,Button, ......
Gui,add,Button, ......
Gui,add,Button, ......
Gui,add,Button, ......
Gui,add,Button, ......
..... (around 60 more auto-generated buttons)
Gui,show
Return
Label:
;run the script that has the same name as the Button
;in this case it would be Google.ahk
Run, % A_GuiControl ".ahk"
GuiClose:
Gui, Hide
Sleep, 1000
;after the user has pressed a button and the according code
;has been copied to the clipboard, the GUI closes, the
;mail window becomes active again and we can continue to paste
;the code into the subject line
;Go to subject line
Send, {ALTDOWN}r{ALTUP}
Sleep, 500
;CTRL+a
Send, {CTRLDOWN}a{CTRUP}
Sleep, 500
;Write text from your clipboard to the subject line
Send, %clipboard%
Sleep, 500
return
}

Apparently it's currently not possible to copy a (more or less) random string to your clipboard in Autohotkey and then paste it somewhere else without receiving so many errors that it's worth pursuing it. I was left with no choice other than programming everything myself in C#. Here's how I did it:
First, start a new Console Application and then change it to a Windows Application How to change a console application to a windows form application?
This is the easiest way to make a program completely invisible to the user.
Next, we need a key listener. Here's the one I used: https://blogs.msdn.microsoft.com/toub/2006/05/03/low-level-keyboard-hook-in-c/
I would advise you to put it in a new class. This code needs to be slightly altered anyway because it prints every key press to the console. We don't want that, we want to call a method from it. Change HookCallback to:
private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN)
{
int vkCode = Marshal.ReadInt32(lParam);
if (KeyPressed == vkCode)
DoWork();
}
return CallNextHookEx(_HookID, nCode, wParam, lParam);
}
For the code above to work, we need to add a new delegate and a new int variable to our class:
private static int KeyPressed;
public delegate void SomethingToDo();
private static SomethingToDo DoWork;
SetHook also needs some minor alterations:
private static IntPtr SetHook(KeyboardProc _proc, int KeyCode, SomethingToDo GiveMeWork)
{
DoWork = GiveMeWork;
KeyPressed = KeyCode;
...(leave the rest as it is)...
}
The program is now completely invisible and can react to a key press. Let's do the opposite, simulating keys!
Ctrl key kept down after simulating a ctrl key down event and ctrl key up event
This is a lot simpler. I made three methods, PressKey, ReleaseKey and TapKey. Keep in mind that ALT and F10 are special system keys which might not work.
[DllImport("user32.dll", SetLastError = true)]
static extern void keybd_event(byte bVk, byte bScan, int dwFlags, int dwExtraInfo);
private const int KeyPressCode = 0x0001;
private const int KeyReleaseCode = 0x0002;
public static void PressKey(System.Windows.Forms.Keys Key)
{
keybd_event((byte)Key, 0, KeyPressCode | 0, 0);
Thread.Sleep(10);
}
public static void ReleaseKey(System.Windows.Forms.Keys Key)
{
keybd_event((byte)Key, 0, KeyPressCode | KeyReleaseCode, 0);
Thread.Sleep(10);
}
public static void TapKey(System.Windows.Forms.Keys Key)
{
PressKey(Key);
ReleaseKey(Key);
}
That's it, a basic Autohokey-Clone that can cope with strings. If you want to go even further and make a try icon for it:
https://translate.google.de/translate?sl=de&tl=en&js=y&prev=_t&hl=de&ie=UTF-8&u=https%3A%2F%2Fdotnet-snippets.de%2Fsnippet%2Fvorlage-fuer-tray-notifyicon-anwendung%2F541&edit-text=&act=url
Showing a GUI with buttons is also surprisingly simple:
using (Form _form = new Form())
{
_form.size = ...
//....
Button MyButton = new Button();
//....
//closing is also pretty simple, just like your everyday Windows Forms Application:
MyButton.Click += new EventHandler((sender, e) => { Application.Exit(); });
_form.ShowDialog();
}
Putting everything together in the main method:
private static NotifyIcon TrayIcon = new NotifyIcon();
[STAThread]
static void Main(string[] args)
{
bool WindowOpen = true;
try
{
//Make a new method above or below Main() and replace DoSomething with it.
//It will be executed everytime the F2 key is pressed.
KeyListener._HookID = KeyListener.SetHook(proc, System.Windows.Forms.Keys.F2, DoSomething);
System.Windows.Forms.ContextMenu SmallMenu = new System.Windows.Forms.ContextMenu();
System.Windows.Forms.MenuItem MenuElement;
int MenuIndex = 0;
MenuElement = new System.Windows.Forms.MenuItem();
MenuElement.Index = ++MenuIndex;
MenuElement.Text = "Close";
MenuElement.Click += new EventHandler((sender, e) => { WindowOpen = false; System.Windows.Forms.Application.Exit(); });
SmallMenu.MenuItems.Add(MenuElement);
TrayIcon.Icon = new System.Drawing.Icon("Ghost.ico");
TrayIcon.Text = "String Compatible AHK";
TrayIcon.Visible = true;
TrayIcon.ContextMenu = SmallMenu;
while (WindowOpen)
System.Windows.Forms.Application.Run();
}
finally
{
TrayIcon.Dispose();
KeyListener.UnhookWindowsHookEx(KeyListener._HookID);
}
}

Related

UI thread slow to respond to Progress updaters on async Task method using VS2022 & Net6.0

I’ve run into a performance obstacle and I’m uncertain of the cause, all of this is running under VS2022 & Net6.0. As this is my 1st time using this combination of a modal windows form, and progress bar, with the work running on a background thread and two Progress objects updating the UI, the progress bar, and a text label, I don’t know where to attack the problem. Prior to placing the workload on a background thread, everything was snappy, searching a thousand files with about 600 lines of text in each, in about a minute. Naturally, the windows form was frozen during this, which is why the workload was placed on a background thread.
After doing so, the workload will be 25-50% complete before the UI starts displaying the values from the Progress objects, and overall, the entire process now takes 10x as long to complete. Progress objects aren’t skipping over any values sent to them, the UI thread just seems slow in getting the information. Likewise, if I try to drag the modal form to a new spot on the desktop it’s unresponsive for 20—30 seconds before it finally moves. One more thing, I can step through the code on the background thread and see it calling the Progress updaters, but the UI thread is just very slow in responding to them.
I could use some suggestions on how to uncover the problem or if clearly evident, point out where the likely problem could be. Here are the essential controls and methods used.
public class SearchProgressForm : Form
{
private System.Windows.Forms.Button btnSearch = new Button();
private System.Windows.Forms.TextBox txtTextSearch = new TextBox();
private System.Windows.Forms.Label lblSearchFile = new Label();
private System.Windows.Forms.ProgressBar SearchProgressBar = new ProgressBar();
public event LogSearchEventHandler SearchSucceededEvent;
protected void OnSearchSucceeded(LogSearchEventArguments p_eventArguments)
{
LogSearchEventHandler handler = SearchSucceededEvent;
if (handler != null)
{
handler(this, p_eventArguments);
}
}
private void InitializeComponent()
{
this.btnSearch.Name = "btnSearch";
this.btnSearch.Text = "Search";
this.btnSearch.Click += new System.EventHandler(this.btnSearch_Click);
this.lblSearchFile.Text = "Searching File: ";
this.txtTextSearch.Text = "search string";
}
public SearchProgressForm() { }
private void btnSearch_Click(object sender, EventArgs e)
{
this.SearchByText(this.txtTextSearch.Text);
}
private void SearchByText(string p_searchParameter)
{
// Setup a progress report for thr ProgressBar
var _progressBarUpdate = new Progress<int>(value =>
{
this.SearchProgressBar.Value = value;
this.SearchProgressBar.Refresh();
});
var _progressFileNameUpdate = new Progress<string>(value =>
{
this.lblSearchFile.Text = "Searching File For : " + value;
this.lblSearchFile.Refresh();
});
// Start search on a backgroud thread and report progress as it occurs
Task.Run(async () => await this.SearchByStringAsync(p_searchParameter, _progressBarUpdate, _progressFileNameUpdate));
}
private async Task SearchByStringAsync(string p_searchParameter, IProgress<int> p_progressBar, IProgress<string> p_progressFileName)
{
await Task.Delay(1);
TextFileReader textFileReader = new TextFileReader();
LogSearchEventArguments logSearchEventArguments = null;
long _sessionloopCount = 0;
long _totalTextLinesCount = this.GetTotalSearchCount(p_searchParameter, SearchType.TextString);
// Get file names from SQL table
var _logFiles = DataOperations.LogFileSortableList(null);
foreach (var log in _logFiles)
{
// Format a file name to be read from the file system
string _fileName = log.Directory + "\\" + log.FileName;
p_progressFileName.Report(log.FileName);
// If we've raised an event for this file, then stop iterating over remaning text
if (logSearchEventArguments != null)
{
logSearchEventArguments = null;
break;
}
// Read in file contents from file system
List<string> _fileContents = textFileReader.ReadAndReturnStringList(_fileName);
long _fileTotalRecordCount = _fileContents.Count;
long _fileRecordCount = 0;
foreach (var _line in _fileContents)
{
if (_line.ToUpper().Contains(p_searchParameter.ToUpper()))
{
// Raise an event so search parameter and file name can be captured in another form
logSearchEventArguments =
new LogSearchEventArguments
(
"TextSearch", p_searchParameter, SearchType.TextString, true, log,
new DateTime(
Convert.ToInt32("20" + log.FileName.Substring(14, 2)),
Convert.ToInt32(log.FileName.Substring(16, 2)),
Convert.ToInt32(log.FileName.Substring(18, 2)))
);
// We found a match, so no further searching is needed in this log file,
// and it's been flagged in the DB, so raise the event to save search parameter and file name
// then break out of this loop to get the next file to search in.
this.OnSearchSucceeded(logSearchEventArguments);
break;
}
// These calcs are based on actual searches performed
_fileRecordCount++;
_sessionloopCount++;
p_progressBar.Report(Convert.ToInt32((_sessionloopCount * 100) / _totalTextLinesCount));
}
// Because we exit a search as soon as the 1st match is made, need to resynch all counts
// and update the progress bar accordingly
if (_fileRecordCount < _fileTotalRecordCount)
{
long _countDifference = _fileTotalRecordCount - _fileRecordCount;
// Add count difference to sessionLoopCount and update progress bar
_sessionloopCount += _countDifference;
p_progressBar.Report(Convert.ToInt32((_sessionloopCount * 100) / _totalTextLinesCount));
}
}
//Search is complete set Progress to 100% and report before exiting
p_progressBar.Report(100);
// Close the modal SearchForm and exit
this.Close();
}
}
I solved this problem but I'm still not certain of what caused it. I eliminated the method "private void SearchByText(string p_searchParameter)" and moved the code there into the btnSearch_Click event handler so I could call my background worker "SearchByStringAsync" directly from the button click event handler.
I also updated the EFCore NuGet Packages, which were version Net6.0 to version 6.0.4, because of single line of code in my Async background method, "var _logFiles = DataOperations.LogFileSortableList(null)".
That call returned a Sortable BindingList, using BindingList <T>. Between the NuGet updates and a minor change on a custom comparer method in my BindingList <T> class, the windows modal form now updates the ProgressBar and Label text as expected, and the form now responds immediately to user interaction.

Starting Doc/View application hidden

Using Visual studio 2010 and MFC Doc/View Applications I want my SDI application to start up completely hidden, and after sometime or with receiving some message from tray icon it shows the mainframe, view and so on. I change the line m_pMainWnd->ShowWindow(SW_NORMAL); to m_pMainWnd->ShowWindow(SW_HIDE); in BOOL CMyApp::InitInstance() but the main frame just flickers after executing the application and then goes hiiden what should I do inorder to avoid this problem and keep the showing capability of main frame when ever I want.
Here is the solution for SDI/MDI app: The new MFC (with VC2010) overrides the m_nCmdShow value with a setting stored in the system registry. To change this behaviour, simply override the LoadWindowPlacement virtual function in the application class.
BOOL CAdVisuoApp::LoadWindowPlacement(CRect& rectNormalPosition, int& nFflags, int& nShowCmd)
{
BOOL b = CWinAppEx::LoadWindowPlacement(rectNormalPosition, nFflags, nShowCmd);
nShowCmd = SW_HIDE;
return b;
}
Normally if you have VC2005 or earlier the following will do:
// Parse command line for standard shell commands, DDE, file open
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);
m_nCmdShow = SW_HIDE;
// Dispatch commands specified on the command line. Will return FALSE if
// app was launched with /RegServer, /Register, /Unregserver or /Unregister.
if (!ProcessShellCommand(cmdInfo))
return FALSE;
// The one and only window has been initialized, so show and update it
m_pMainWnd->ShowWindow( m_nCmdShow);
m_pMainWnd->UpdateWindow();
Note that m_nCmdShow should be set to SW_HIDE before ProcessShallCommand for the flicker not to occur.
It looks like there might be a bug in VC2010 though. Since I have done this before it intrigued me and tried a fresh VC2010 project but it was not working. I noticed the problem was deep in the following MFC function.
BOOL CFrameWnd::LoadFrame(UINT nIDResource, DWORD dwDefaultStyle,
CWnd* pParentWnd, CCreateContext* pContext)
{
// only do this once
ASSERT_VALID_IDR(nIDResource);
ASSERT(m_nIDHelp == 0 || m_nIDHelp == nIDResource);
m_nIDHelp = nIDResource; // ID for help context (+HID_BASE_RESOURCE)
CString strFullString;
if (strFullString.LoadString(nIDResource))
AfxExtractSubString(m_strTitle, strFullString, 0); // first sub-string
VERIFY(AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG));
// attempt to create the window
LPCTSTR lpszClass = GetIconWndClass(dwDefaultStyle, nIDResource);
CString strTitle = m_strTitle;
if (!Create(lpszClass, strTitle, dwDefaultStyle, rectDefault,
pParentWnd, ATL_MAKEINTRESOURCE(nIDResource), 0L, pContext))
{
return FALSE; // will self destruct on failure normally
}
// save the default menu handle
ASSERT(m_hWnd != NULL);
m_hMenuDefault = m_dwMenuBarState == AFX_MBS_VISIBLE ? ::GetMenu(m_hWnd) : m_hMenu;
// load accelerator resource
LoadAccelTable(ATL_MAKEINTRESOURCE(nIDResource));
if (pContext == NULL) // send initial update
SendMessageToDescendants(WM_INITIALUPDATE, 0, 0, TRUE, TRUE);
return TRUE;
}
m_nCmdShow is still SW_HIDE when this function executes but it changes to SW_SHOWNORMAL when if (!Create(lpszClass... line executes. I don't know why this happens in VC2010 project only, sounds like a bug to me.
My sample project was SDI.
This comes from a dialog based application but you should be able to convert it to a Doc/View app as well. You need to handle the OnWindowPosChanging event. The key line is the the one inside the if statement. This allows my application to start completely hidden from view.
void CIPViewerDlg::OnWindowPosChanging( WINDOWPOS FAR* lpWindowPosition )
{
if( !m_bVisible )
{
lpWindowPosition->flags &= ~SWP_SHOWWINDOW;
}
CDialog::OnWindowPosChanging( lpWindowPosition );
}
Make sure that you are correctly turning off the WS_VISIBLE bit in CMainFrame::PreCreateWindow(CREATESTRUCT& cs). Something like this should worK:
cs.style &= ~WS_VISIBLE;
We had simply been negating the bit instead of turning it off, and we got away with it in VS 6.0 because this function was called only once. It is called twice in newer versions of Visual Studio, so in the second call we were flipping it right back on again. :-O
I tried all for Visual Studio 2010 and finished up with:
class CMainFrame : public CFrameWndEx
{
// ...
// Attributes
public:
BOOL m_bForceHidden;
// ...
// Overrides
public:
virtual void ActivateFrame(int nCmdShow = -1);
//...
};
CMainFrame::CMainFrame() : m_bForceHidden(TRUE)
{
// ...
}
void CMainFrame::ActivateFrame(int nCmdShow)
{
if(m_bForceHidden)
{
nCmdShow = SW_HIDE;
m_bForceHidden = FALSE;
}
CFrameWndEx::ActivateFrame(nCmdShow);
}
Other tricks did not work for me.
Found solution at:
http://forums.codeguru.com/showthread.php?478882-RESOLVED-Can-a-Doc-view-be-hidden-at-startup
I found in VS2017 (using BCGControlBar Pro which is what MFC Feature Pack was based on) that you have to handle things in two places:
BOOL CMainFrame::LoadFrame(UINT nIDResource, DWORD dwDefaultStyle, CWnd* pParentWnd, CCreateContext* pContext)
{
if (!__super::LoadFrame(nIDResource, dwDefaultStyle, pParentWnd, pContext))
{
return FALSE;
}
// undo what __super::LoadFrame() does where it will set it to SW_NORMAL if not SW_MAXIMIZED
AfxGetApp()->m_nCmdShow = SW_HIDE;
}
BOOL CTheApp::LoadWindowPlacement(CRect& rectNormalPosition, int& nFflags, int& nShowCmd)
{
BOOL b = __super::LoadWindowPlacement(rectNormalPosition, nFflags, nShowCmd);
nShowCmd = SW_HIDE;
return b;
}

Best way to display progress of background thread in ATL?

When using ATL, what is the best way to display the progress of a background thread (e.g. when it's searching for a file) without blocking the UI?
I still want to be able to process messages, to allow for a Cancel button and to possibly allow the user to continue working with the program while the search happens.
There is no ATL specific here. One of the ways to do is to update progress details into member variable and post a message to GUI window, then handle the message by pulling the data from member variable and updating GUI, such as updating static and/or progress bar.
Worker thread pseudo-code:
m_DataCriticalSection.Lock();
m_nProgress = (INT) (nCurrent * 100 / nTotal);
m_DataCriticalSection.Unlock();
PostMessage(WM_MYUPDATEPROGRESS);
Window:
OnMyUpdateProgress()
{
m_DataCriticalSection.Lock();
INT nProgress = m_nProgress;
m_DataCriticalSection.Unlock();
m_ProgressBar.SetPos(nProgress);
}
UPD. A real code snippet, AddText is called on background thread, :
VOID AddText(const CString& sText)
{
_A(sText.Find(_T('\n')) < 0);
BOOL bIsTextEmpty;
{
CRoCriticalSectionLock TextLock(m_TextCriticalSection);
bIsTextEmpty = m_sText.IsEmpty();
m_sText.Append(sText);
m_sText.Append(_T("\r\n"));
}
if(bIsTextEmpty)
PostPrivateMessage(WM_UPDATETEXT);
}
And the code handler:
BEGIN_MSG_MAP_EX(CMainDialog)
// ...
MESSAGE_HANDLER_EX(WM_UPDATETEXT, OnUpdateText)
LRESULT OnUpdateText(UINT, WPARAM, LPARAM)
{
CString sText;
{
CRoCriticalSectionLock TextLock(m_TextCriticalSection);
sText = m_sText;
m_sText.Empty();
}
if(!sText.IsEmpty())
{
m_TextEdit.SetValue(m_TextEdit.GetValue() + sText);
const INT nTextLength = m_TextEdit.GetWindowTextLength();
m_TextEdit.SetSel(nTextLength, nTextLength);
}
return 0;
}
This uses custom classes (not 'pure' ATL), but I hope you get the idea.

Console application: How to update the display without flicker?

Using C# 4 in a Windows console application that continually reports progress how can I make the "redraw" of the screen more fluid?
I'd like to do one of the following:
- Have it only "redraw" the part of the screen that's changing (the progress portion) and leave the rest as is.
- "Redraw" the whole screen but not have it flicker.
Currently I re-write all the text (application name, etc.). Like this:
Console.Clear();
WriteTitle();
Console.WriteLine();
Console.WriteLine("Deleting:\t{0} of {1} ({2})".FormatString(count.ToString("N0"), total.ToString("N0"), (count / (decimal)total).ToString("P2")));
Which causes a lot of flickering.
Try Console.SetCursorPosition. More details here: How can I update the current line in a C# Windows Console App?
static void Main(string[] args)
{
Console.SetCursorPosition(0, 0);
Console.Write("################################");
for (int row = 1; row < 10; row++)
{
Console.SetCursorPosition(0, row);
Console.Write("# #");
}
Console.SetCursorPosition(0, 10);
Console.Write("################################");
int data = 1;
System.Diagnostics.Stopwatch clock = new System.Diagnostics.Stopwatch();
clock.Start();
while (true)
{
data++;
Console.SetCursorPosition(1, 2);
Console.Write("Current Value: " + data.ToString());
Console.SetCursorPosition(1, 3);
Console.Write("Running Time: " + clock.Elapsed.TotalSeconds.ToString());
Thread.Sleep(1000);
}
Console.ReadKey();
}
I know this question is a bit old but I found if you set Console.CursorVisible = false then the flickering stops as well.
Here's a simple working demo that shows multi-line usage without flickering. It shows the current time and a random string every second.
private static void StatusUpdate()
{
var whiteSpace = new StringBuilder();
whiteSpace.Append(' ', 10);
var random = new Random();
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
var randomWord = new string(Enumerable.Repeat(chars, random.Next(10)).Select(s => s[random.Next(s.Length)]).ToArray());
while (true)
{
Console.SetCursorPosition(0, 0);
var sb = new StringBuilder();
sb.AppendLine($"Program Status:{whiteSpace}");
sb.AppendLine("-------------------------------");
sb.AppendLine($"Last Updated: {DateTime.Now}{whiteSpace}");
sb.AppendLine($"Random Word: {randomWord}{whiteSpace}");
sb.AppendLine("-------------------------------");
Console.Write(sb);
Thread.Sleep(1000);
}
}
The above example assumes your console window is blank to start. If not, make sure to use Console.Clear() first.
Technical Note:
SetCursorPosition(0,0) places the cursor back to the top (0,0) so the next call to Console.Write will start from line 0, char 0. Note, it doesn't delete the previous content before writing. As an example, if you write "asdf" over a previous line such as "0123456", you'll end up with something like "asdf456" on that line. For that reason, we use a whiteSpace variable to ensure any lingering characters from the previous line are overwritten with blank spaces. Adjust the length of the whiteSpace variable to meet your needs. You only need the whiteSpace variable for lines that change.
Personal Note:
For my purposes, I wanted to show the applications current status (once a second) along with a bunch of other status information and I wanted to avoid any annoying flickering that can happen when you use Console.Clear(). In my application, I run my status updates behind a separate thread so it constantly provides updates even though I have numerous other threads and long running tasks going at the same time.
Credits:
Thanks to previous posters and dtb for the random string generator used in the demo.
How can I generate random alphanumeric strings in C#?
You could try to hack something together using the core libraries.
Rather than waste your time for sub-standard results, I would check out this C# port of the ncurses library (which is a library used for formatting console output):
Curses Sharp
I think you can use \r in Windows console to return the beginning of a line.
You could also use SetCursorPosition.
I would recommend the following extension methods. They allow you to use a StringBuilder to refresh the console view without any flicker, and also tidies up any residual characters on each line
The Problem: The following demo demonstrates using a standard StringBuilder, where updating lines that are shorter than the previously written line get jumbled up. It does this by writing a short string, then a long string on a loop:
public static void Main(string[] args)
{
var switchTextLength = false;
while(true)
{
var sb = new StringBuilder();
if (switchTextLength)
sb.AppendLine("Short msg");
else
sb.AppendLine("Longer message");
sb.UpdateConsole();
switchTextLength = !switchTextLength;
Thread.Sleep(500);
}
}
Result:
The Solution: By using the extension method provided below, the issue is resolved
public static void Main(string[] args)
{
var switchTextLength = false;
while(true)
{
var sb = new StringBuilder();
if (switchTextLength)
sb.AppendLineEx("Short msg");
else
sb.AppendLineEx("Longer message");
sb.UpdateConsole();
switchTextLength = !switchTextLength;
Thread.Sleep(500);
}
}
Result:
Extension Methods:
public static class StringBuilderExtensions
{
/// <summary>
/// Allows StrinbBuilder callers to append a line and blank out the remaining characters for the length of the console buffer width
/// </summary>
public static void AppendLineEx(this StringBuilder c, string msg)
{
// Append the actual line
c.Append(msg);
// Add blanking chars for the rest of the buffer
c.Append(' ', Console.BufferWidth - msg.Length - 1);
// Finish the line
c.Append(Environment.NewLine);
}
/// <summary>
/// Combines two StringBuilders using AppendLineEx
/// </summary>
public static void AppendEx(this StringBuilder c, StringBuilder toAdd)
{
foreach (var line in toAdd.ReadLines())
{
c.AppendLineEx(line);
}
}
/// <summary>
/// Hides the console cursor, resets its position and writes out the string builder
/// </summary>
public static void UpdateConsole(this StringBuilder c)
{
// Ensure the cursor is hidden
if (Console.CursorVisible) Console.CursorVisible = false;
// Reset the cursor position to the top of the console and write out the string builder
Console.SetCursorPosition(0, 0);
Console.WriteLine(c);
}
}
I actually had this issue so I made a quick simple method to try and eliminate this.
static void Clear(string text, int x, int y)
{
char[] textChars = text.ToCharArray();
string newText = "";
//Converts the string you just wrote into a blank string
foreach(char c in textChars)
{
text = text.Replace(c, ' ');
}
newText = text;
//Sets the cursor position
Console.SetCursorPosition(x, y);
//Writes the blank string over the old string
Console.WriteLine(newText);
//Resets cursor position
Console.SetCursorPosition(0, 0);
}
It actually worked surprisingly well and I hope it may work for you!
Naive approach but for simple applications is working:
protected string clearBuffer = null; // Clear this if window size changes
protected void ClearConsole()
{
if (clearBuffer == null)
{
var line = "".PadLeft(Console.WindowWidth, ' ');
var lines = new StringBuilder();
for (var i = 0; i < Console.WindowHeight; i++)
{
lines.AppendLine(line);
}
clearBuffer = lines.ToString();
}
Console.SetCursorPosition(0, 0);
Console.Write(clearBuffer);
Console.SetCursorPosition(0, 0);
}
Console.SetCursorPosition(0, 0); //Instead of Console.Clear();
WriteTitle();
Console.WriteLine();
Console.WriteLine("Deleting:\t{0} of {1} ({2})".FormatString(count.ToString("N0")

Print to a label printer from a web site / web application? [closed]

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
We don’t allow questions seeking recommendations for books, tools, software libraries, and more. You can edit the question so it can be answered with facts and citations.
Closed 2 years ago.
Improve this question
Are there any known label printers that will accept print commands from a web site or web application?
Specifically, the one-off label printers such as Dymo, Brother, Zebra, etc.
Has anyone had success in printing to these printers (without using an Internet Explorer-Only hack or ActiveX control)
I would think that, eventually, there would have to be a solution as we move into a more browser-centric world.
Wow. I know this was asked over 4 years ago, but having burnt the better part of a week in search of a robust method of printing labels from a web app, I had to voice in here.
Here's what I've found:
DYMO seems the most likely candidate for excellence. But no, turns out it only prints from (a) its own app, or (b) something using its SDK. The DYMO SDK uses an XML drawing model that is both overly complex and limited in layout and styling. The documentation is scattered and incomprehensible (e.g. what are the <Bounds> values for the common label sizes? There's no description of the tag parameters anywhere!) So frustrating, so disappointing.
There's qz (was jzebra), which enables browser printing for devices that speak EPL, ZPL, FGL, ESC/POS, EPCL and CPCL ... which includes the Zebra series. It requires a load of integration (running a web server on device the label printer is attached to), but it works.
There's a well designed 3rd party app by Peninsula, which works for DYMO (among others), but requires a middle-step of printing from browser to PDF. They also assume you'll never scale what you want printed down less than 70%, which is incorrect.
The OP says "I would think that, eventually, there would have to be a solution as we move into a more browser-centric world." Four years later, I'd go a step further and suggest any label printer that can't print off a browser (or just behave like a regular printer with small paper) is falling WAY short of its potential. Layout in HTML+CSS is a snap. Browsers parse it perfectly, and render it at any resolution to any output device. It seems so obvious.
If anyone knows of a thermal label printer that prints from the browser instead of imprisoning you in archaic integration methodologies, I'd very much like to know!
The Dymo printers have a browser plugin that let you print from javascript. Very easy to use.
Revisiting this question a few years later.
The networked Zebra printers are easy to print to. In one project I had the webapplication open a socket to the printer and feed it instructions in ZPL.
You could also trying looking PrintNode which provide a cloud printing solution and means you can print straight to any printer over the internet.
A cross-browser/platform-compatible web page/web page script doesn't have the low-level access necessary to transmit printer language command (such as Zebra Printer Language [ZPL]) to the printer. In order to do this, an ActiveX control/browser plugin/applet/similar bit of executable code is required. This limitation is non-printer specific, coming from the browser not the printer manufacturer.
However, many label printers allow you to print to them as though they were normal printers--just ones that print to very small pieces of paper. You could layout your label using HTML/CSS. When displaying the results, instruct the user to go to "File > Print" and select their label printer before clicking print.
Hope this helps,
Ben
Zebra now has a tool called 'BrowserPrint' that works like PrintNode specifically for Zebra printers. (unlike Printnode, it's free to use with Zebra printers)
Some printers also have ppd files available and/or can be configured in cups on a linux platform system or otherwise connected and spoken too via linux. (which then means it's not impossible to use something from shell scripting, php, perl or other methods to print to it by building a simple server daemon or setting up something like apache or lighttpd on a linux box or even a raspberry pi connected to the printer)
You can print from a signed Java applet, or from a Java Web Start application:
http://java.sun.com/javase/technologies/desktop/printing/
That should work, if you format your print output so it fits on the labels.
Note that most modern browsers are restricting support for Java applets, so you may run into trouble with an applet, depending on the browsers in use. In particular, Google Chrome will stop supporting Java applets in September 2015. These restrictions do not apply to Java Web Start, which should continue to work.
using System;
using System.Collections.Generic;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Drawing;
using System.Drawing.Printing;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.IO;
using System.ComponentModel;
using System.Data;
using System.Linq;
using System.Text;
using System.Management;
using System.Reflection;
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
GetAllPrinterList();
this.textBox1.Attributes.Add(
"onkeypress", "button_click(this,'" + this.Button1.ClientID + "')");
this.lstPrinterList.Attributes.Add(
"onkeypress", "button_click(this,'" + this.Button1.ClientID + "')");
}
private void GetAllPrinterList()
{
//ManagementScope objScope = new ManagementScope(ManagementPath.DefaultPath); //For the local Access
//objScope.Connect();
//SelectQuery selectQuery = new SelectQuery();
//selectQuery.QueryString = "Select * from win32_Printer";
//ManagementObjectSearcher MOS = new ManagementObjectSearcher(objScope, selectQuery);
//ManagementObjectCollection MOC = MOS.Get();
//foreach (ManagementObject mo in MOC)
//{
// lstPrinterList.Items.Add(mo["Name"].ToString());
// //lstPrinterList.Items.Add(new ListItem(mo["Name"].ToString()));
//}
lstPrinterList.Items.Add("\\\\10.32.65.6\\Parcel-Zebra-FDX ZM400");
lstPrinterList.Items.Add("\\\\10.32.65.4\\Singles_Station_Z_Printer");
lstPrinterList.Items.Add("\\\\10.32.65.12\\ZebraZ4M-Packaging");
if (lstPrinterList.Items.Count > 3)
{
lstPrinterList.Items.RemoveAt(5);
lstPrinterList.Items.RemoveAt(4);
lstPrinterList.Items.RemoveAt(3);
}
//LocalPrintServer printServer = new LocalPrintServer();
//string printer;
//printer = LocalPrintServer.DefaultPrintQueue;
//System.Management.ObjectQuery oquery =
// new System.Management.ObjectQuery("SELECT * FROM Win32_Printer");
// System.Management.ManagementObjectSearcher mosearcher =
// new System.Management.ManagementObjectSearcher(oquery);
// System.Management.ManagementObjectCollection moc = mosearcher.Get();
// foreach (ManagementObject mo in moc)
// {
// System.Management.PropertyDataCollection pdc = mo.Properties;
// foreach (System.Management.PropertyData pd in pdc)
// {
// // if ((bool)mo["Network"])
// // {
// lstPrinterList.Items.Add(mo["Name"].ToString());
// // }
// }
// }
//}
// using (var printServer = new PrintServer(string.Format(#"\\{0}", PrinterServerName)))
//{
// foreach (var queue in printServer.GetPrintQueues())
// {
// if (!queue.IsShared)
// {
// continue;
// }
// Debug.WriteLine(queue.Name);
// }
// }
//foreach (string printer in System.Drawing.Printing.PrinterSettings.InstalledPrinters)
//{
//MessageBox.Show(printer);
//System.Web.UI.WebControls.ListBox lstPrinterList = new System.Web.UI.WebControls.ListBox();
//System.Web.UI.WebControls.ListBox lstPrinterList = (System.Web.UI.WebControls.ListBox)Page.FindControl("lstPrinterList");
//lstPrinterList.Text = "Zebra 110PAX4 (203 ;dpi)";
//lstPrinterList.Items.Add(printer.ToString());
//lstPrinterList.Items.Add(new ListItem(printer));
//lstPrinterList.Items.Add(printer);
//System.Web.UI.WebControls.ListBox lstPrinterList = (System.Web.UI.WebControls.ListBox)Page.FindControl("ListBox1");
//Zebra 110PAX4 (203 dpi)
//lstPrinterList.Items.Insert(printer);
//}
}
//private void lstPrinterList_OnClick(object sender, System.EventArgs e)
//{
// // Get the currently selected item in the ListBox.
// string curItem = lstPrinterList.SelectedItem.ToString();
// // Find the string in ListBox2.
// int index = lstPrinterList.DataTextField(curItem);
// //int index = lstPrinterList.FindString(curItem);
// //lstPrinterList.DataTextField(curItem);
// // If the item was not found in ListBox 2 display a message box, otherwise select it in ListBox2.
// if (index == -1)
// MessageBox.Show("Item is not available in ListBox2");
// else
// lstPrinterList.SetSelected(index, true);
//}
//private void button1_Click(object sender, System.EventArgs e)
//{
// string str = File.ReadAllText("lpn.prn");
// str = str.Replace("BOX_TYPE", "boom");
// str = str.Replace("11111111", textBox1.Text);
// File.WriteAllText("lpn.prn", str);
// // Print the file to the printer.
// RawPrinterHelper.SendFileToPrinter("\\\\Zebra-FDX ZM400 200 dpi (ZPL)", "C:\\Users\\Administrator\\Documents\\Visual Studio 2015\\Projects\\WindowsFormsApplication4\\Prn Print\\bin\\Debug\\lpn.prn");
//}
public void button1_Click(object sender, System.EventArgs e)
{
String prnFile = "lpn2.prn";
string s = File.ReadAllText(prnFile);
string printer;
String Printer = lstPrinterList.SelectedItem.ToString();
s = s.Replace("*description*", "snuffles");
s = s.Replace("*barcode*", textBox1.Text);
//File.WriteAllText("PPlpn.prn", s);
//s = "^XA^LH30,30\n^FO20,10^ADN,90,50^AD^FDHello World^FS\n^XZ";
printer = lstPrinterList.SelectedItem.Value;
PrintDialog pd = new PrintDialog();
pd.PrinterSettings = new PrinterSettings();
RawPrinterHelper.SendStringToPrinter(printer, s);
//RawPrinterHelper.SendStringToPrinter(Printer, s);
//Response.Redirect(Request.RawUrl.Replace(Request.Url.Query, "?__VIEWSTATE=%2%3D&ctl00%24MainContent%24textBox1=bp300&ctl00%24MainContent%24lstPrinterList=Shipping+Printer"));
}
protected void textBox1_TextChanged(object sender, EventArgs e)
{
}
}
public class RawPrinterHelper
{
// Structure and API declarions:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public class DOCINFOA
{
[MarshalAs(UnmanagedType.LPStr)]
public string pDocName;
[MarshalAs(UnmanagedType.LPStr)]
public string pOutputFile;
[MarshalAs(UnmanagedType.LPStr)]
public string pDataType;
}
[DllImport("winspool.Drv", EntryPoint = "OpenPrinterA", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool OpenPrinter([MarshalAs(UnmanagedType.LPStr)] string szPrinter, out IntPtr hPrinter, IntPtr pd);
[DllImport("winspool.Drv", EntryPoint = "ClosePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool ClosePrinter(IntPtr hPrinter);
[DllImport("winspool.Drv", EntryPoint = "StartDocPrinterA", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool StartDocPrinter(IntPtr hPrinter, Int32 level, [In, MarshalAs(UnmanagedType.LPStruct)] DOCINFOA di);
[DllImport("winspool.Drv", EntryPoint = "EndDocPrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool EndDocPrinter(IntPtr hPrinter);
[DllImport("winspool.Drv", EntryPoint = "StartPagePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool StartPagePrinter(IntPtr hPrinter);
[DllImport("winspool.Drv", EntryPoint = "EndPagePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool EndPagePrinter(IntPtr hPrinter);
[DllImport("winspool.Drv", EntryPoint = "WritePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool WritePrinter(IntPtr hPrinter, IntPtr pBytes, Int32 dwCount, out Int32 dwWritten);
// SendBytesToPrinter()
// When the function is given a printer name and an unmanaged array
// of bytes, the function sends those bytes to the print queue.
// Returns true on success, false on failure.
public static bool SendBytesToPrinter(string szPrinterName, IntPtr pBytes, Int32 dwCount)
{
Int32 dwError = 0, dwWritten = 0;
IntPtr hPrinter = new IntPtr(0);
DOCINFOA di = new DOCINFOA();
bool bSuccess = false; // Assume failure unless you specifically succeed.
di.pDocName = "My C#.NET RAW Document";
di.pDataType = "RAW";
// Open the printer.
if (OpenPrinter(szPrinterName.Normalize(), out hPrinter, IntPtr.Zero))
{
// Start a document.
if (StartDocPrinter(hPrinter, 1, di))
{
// Start a page.
if (StartPagePrinter(hPrinter))
{
// Write your bytes.
bSuccess = WritePrinter(hPrinter, pBytes, dwCount, out dwWritten);
EndPagePrinter(hPrinter);
}
EndDocPrinter(hPrinter);
}
ClosePrinter(hPrinter);
}
// If you did not succeed, GetLastError may give more information
// about why not.
if (bSuccess == false)
{
dwError = Marshal.GetLastWin32Error();
}
return bSuccess;
}
public static bool SendFileToPrinter(string szPrinterName, string szFileName)
{
// Open the file.
FileStream fs = new FileStream(szFileName, FileMode.Open);
// Create a BinaryReader on the file.
BinaryReader br = new BinaryReader(fs);
// Dim an array of bytes big enough to hold the file's contents.
Byte[] bytes = new Byte[fs.Length];
bool bSuccess = false;
// Your unmanaged pointer.
IntPtr pUnmanagedBytes = new IntPtr(0);
int nLength;
nLength = Convert.ToInt32(fs.Length);
// Read the contents of the file into the array.
bytes = br.ReadBytes(nLength);
// Allocate some unmanaged memory for those bytes.
pUnmanagedBytes = Marshal.AllocCoTaskMem(nLength);
// Copy the managed byte array into the unmanaged array.
Marshal.Copy(bytes, 0, pUnmanagedBytes, nLength);
// Send the unmanaged bytes to the printer.
bSuccess = SendBytesToPrinter(szPrinterName, pUnmanagedBytes, nLength);
// Free the unmanaged memory that you allocated earlier.
Marshal.FreeCoTaskMem(pUnmanagedBytes);
return bSuccess;
}
public static bool SendStringToPrinter(string szPrinterName, string szString)
{
IntPtr pBytes;
Int32 dwCount;
// How many characters are in the string?
dwCount = szString.Length;
// Assume that the printer is expecting ANSI text, and then convert
// the string to ANSI text.
pBytes = Marshal.StringToCoTaskMemAnsi(szString);
// Send the converted ANSI string to the printer.
SendBytesToPrinter(szPrinterName, pBytes, dwCount);
Marshal.FreeCoTaskMem(pBytes);
return true;
}
}
This is what I created in C# and it worked out great. This is a web app.

Resources