Send raw ZPL to Zebra printer via USB - usb

Typically, when I plug in my Zebra LP 2844-Z to the USB port, the computer sees it as a printer and I can print to it from notepad like any other generic printer. However, my application has some bar code features. My application parses some input and generates an in-memory string of ZPL. How would I send this ZPL data to my USB device?

I found the answer... or at least, the easiest answer (if there are multiple). When I installed the printer, I renamed it to "ICS Label Printer". Here's how to change the options to allow pass-through ZPL commands:
Right-click on the "ICS Label Printer" and choose "Properties".
On the "General" tab, click on the "Printing Preferences..." button.
On the "Advanced Setup" tab, click on the "Other" button.
Make sure there is a check in the box labeled "Enable Passthrough Mode".
Make sure the "Start sequence:" is "${".
Make sure the "End sequence:" is "}$".
Click on the "Close" button.
Click on the "OK" button.
Click on the "OK" button.
In my code, I just have to add "${" to the beginning of my ZPL and "}$" to the end and print it as plain text. This is with the "Windows driver for ZDesigner LP 2844-Z printer Version 2.6.42 (Build 2382)". Works like a charm!

I've found yet an easier way to write to a Zebra printer over a COM port. I went to the Windows control panel and added a new printer. For the port, I chose COM1 (the port the printer was plugged in to). I used a "Generic / Text Only" printer driver. I disabled the print spooler (a standard option in the printer preferences) as well as all advanced printing options. Now, I can just print any string to that printer and if the string contains ZPL, the printer renders the ZPL just fine! No need for special "start sequences" or funky stuff like that. Yay for simplicity!

Visual Studio C# solution (found at http://support.microsoft.com/kb/322091)
Step 1.) Create class RawPrinterHelper...
using System;
using System.IO;
using System.Runtime.InteropServices;
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;
}
}
Step 2.) Create a form with text box and button (text box will hold the ZPL to send in this example). In button click event add code...
private void button1_Click(object sender, EventArgs e)
{
// Allow the user to select a printer.
PrintDialog pd = new PrintDialog();
pd.PrinterSettings = new PrinterSettings();
if (DialogResult.OK == pd.ShowDialog(this))
{
// Send a printer-specific to the printer.
RawPrinterHelper.SendStringToPrinter(pd.PrinterSettings.PrinterName, textBox1.Text);
MessageBox.Show("Data sent to printer.");
}
else
{
MessageBox.Show("Data not sent to printer.");
}
}
With this solution, you can tweak to meet specific requirements. Perhaps hardcode the specific printer. Perhaps derive the ZPL text dynamically rather than from a text box. Whatever. Perhaps you don't need a graphical interface, but this shows how to send the ZPL. Your use depends on your needs.

You haven't mentioned a language, so I'm going to give you some some hints how to do it with the straight Windows API in C.
First, open a connection to the printer with OpenPrinter. Next, start a document with StartDocPrinter having the pDatatype field of the DOC_INFO_1 structure set to "RAW" - this tells the printer driver not to encode anything going to the printer, but to pass it along unchanged. Use StartPagePrinter to indicate the first page, WritePrinter to send the data to the printer, and close it with EndPagePrinter, EndDocPrinter and ClosePrinter when done.

ZPL is the correct way to go. In most cases it is correct to use a driver that abstracts to GDI commands; however Zebra label printers are a special case. The best way to print to a Zebra printer is to generate ZPL directly. Note that the actual printer driver for a Zebra printer is a "plain text" printer - there is not a "driver" that could be updated or changed in the sense we think of most printers having drivers. It's just a driver in the absolute minimalist sense.

Install an share your printer: \localhost\zebra
Send ZPL as text, try with copy first:
copy file.zpl \localhost\zebra
very simple, almost no coding.

I spent 8 hours to do that.
It is simple...
You shoud have a code like that:
private const int GENERIC_WRITE = 0x40000000;
//private const int OPEN_EXISTING = 3;
private const int OPEN_EXISTING = 1;
private const int FILE_SHARE_WRITE = 0x2;
private StreamWriter _fileWriter;
private FileStream _outFile;
private int _hPort;
Change that variable content from 3 (open file already exist) to 1 (create a new file).
It'll work at Windows 7 and XP.

Found amazing simple solution - working for Chrome (Windows, not tested on Mac)
Zebra ZP 450
Go here Zebra Generic Text
Go precisely by the manual
No COM1 or any other ports needed - USB is enough
When done (named the printer ZTEXT), does not matter if it won't print a test page
Turn of Spooling and enable direct printing in Printer Preferences - 1 note here 1 printer is ZP450 CPT and other ZP450 only - on the other one I do not even need to turn off spooling and it worked.
Go to Chrome and printing ZPL from there with Chrome Print Dialog Box by selecting the ZTEXT printer (Generic / Text) Printer (Do not choose Windows Dialog Box) - we needed this for Chrome to be working

For anybody torturing themselves with the above in 2021, if you're in the .NET world then get hold of the RawPrint NuGet package.
From there to print your ZPL string directly to the printer, bypassing drivers, it's:
IPrinter printer = new Printer();
printer.PrintRawStream(#"Your Printer Name", GenerateStreamFromString(yourZplString), #"docname");

Related

Set FolderBrowserDialog to show directly the set filepath folder [duplicate]

As show in this screen shot, the selected folder is not in the view. It needs to be scrolled down to view the selected folder.
Same dialog shows selected folder visible on different computer
I ran it on two computers both having windows 7. It works correctly on one but does not on 2nd. It looks something with windows environment instead some code issue? Can anyone suggest any fix?
There is no change in code. I used longer paths from different drives but results are same.
private void TestDialog_Click ( object sender, EventArgs e )
{
//Last path store the selected path, to show the same directory as selected on next application launch.
//Properties.Settings.Default.LastPath
FolderBrowserDialog dlgFolder = new FolderBrowserDialog ();
dlgFolder.RootFolder = Environment.SpecialFolder.DesktopDirectory;
dlgFolder.SelectedPath = Properties.Settings.Default.LastPath;
if (dlgFolder.ShowDialog () == System.Windows.Forms.DialogResult.OK)
{
Properties.Settings.Default.LastPath = dlgFolder.SelectedPath;
Properties.Settings.Default.Save ();
}
}
The fundamental problem is a poor design decision in the FolderBrowserDialog. First, we need to realize that the FolderBrowserDialog is not a .NET control, but is rather the Common Dialog and is part of Windows. The designer of this dialog elected not to send the TreeView control a TVM_ENSUREVISIBLE message after the dialog is displayed and an initial folder is selected. This message causes a TreeView control to scroll so that the currently selected item is visible in the window.
So, all we need to do to fix this is to send the TreeView that is part of the FolderBrowserDialog the TVM_ENSUREVISIBLE message and everything will be great. Right? Well, not so fast. This is indeed the answer, but there some things standing in our way.
First, because the FolderBrowserDialog is not really a .NET control, it does not have an internal Controls collection. This means that we can't just find and access the TreeView child control from .NET.
Second, the designers of the .NET FolderBrowserDialog class decided to seal this class. This unfortunate decision prevents us from deriving from it and overriding the window message handler. Had we been able to do this, we might have tried to post the TVM_ENSUREVISIBLE message when we got the WM_SHOWWINDOW message in the message handler.
The third issue is that we can’t send the TVM_ENSUREVISIBLE message until the Tree View control actually exists as a real window, and it does not exist until we call the ShowDialog method. However, this method blocks, so we won’t have the opportunity to post our message once this method is called.
To get around these issues, I created a static helper class with a single method that can be used to show a FolderBrowserDialog, and will cause it to scroll to the selected folder. I manage this by starting a short Timer just prior to calling the dialogue's ShowDialog method, and then tracking down the handle of the TreeView control in the Timer handler (i.e., after the dialogue is displayed) and sending our TVM_ENSUREVISIBLE message.
This solution is not perfect because it depends on some prior knowledge about the FolderBrowserDialog. Specifically, I find the dialogue using its window title. This will break with non-English installations. I track down the child controls in the dialogue using their dialogue Item IDs, rather than title text or class name, because I felt this would be more reliable over time.
This code has been tested on Windows 7 (64 bit), and Windows XP.
Here is the code:
(You may need: using System.Runtime.InteropServices;)
public static class FolderBrowserLauncher
{
/// <summary>
/// Using title text to look for the top level dialog window is fragile.
/// In particular, this will fail in non-English applications.
/// </summary>
const string _topLevelSearchString = "Browse For Folder";
/// <summary>
/// These should be more robust. We find the correct child controls in the dialog
/// by using the GetDlgItem method, rather than the FindWindow(Ex) method,
/// because the dialog item IDs should be constant.
/// </summary>
const int _dlgItemBrowseControl = 0;
const int _dlgItemTreeView = 100;
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll")]
static extern IntPtr GetDlgItem(IntPtr hDlg, int nIDDlgItem);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
/// <summary>
/// Some of the messages that the Tree View control will respond to
/// </summary>
private const int TV_FIRST = 0x1100;
private const int TVM_SELECTITEM = (TV_FIRST + 11);
private const int TVM_GETNEXTITEM = (TV_FIRST + 10);
private const int TVM_GETITEM = (TV_FIRST + 12);
private const int TVM_ENSUREVISIBLE = (TV_FIRST + 20);
/// <summary>
/// Constants used to identity specific items in the Tree View control
/// </summary>
private const int TVGN_ROOT = 0x0;
private const int TVGN_NEXT = 0x1;
private const int TVGN_CHILD = 0x4;
private const int TVGN_FIRSTVISIBLE = 0x5;
private const int TVGN_NEXTVISIBLE = 0x6;
private const int TVGN_CARET = 0x9;
/// <summary>
/// Calling this method is identical to calling the ShowDialog method of the provided
/// FolderBrowserDialog, except that an attempt will be made to scroll the Tree View
/// to make the currently selected folder visible in the dialog window.
/// </summary>
/// <param name="dlg"></param>
/// <param name="parent"></param>
/// <returns></returns>
public static DialogResult ShowFolderBrowser( FolderBrowserDialog dlg, IWin32Window parent = null )
{
DialogResult result = DialogResult.Cancel;
int retries = 10;
using (Timer t = new Timer())
{
t.Tick += (s, a) =>
{
if (retries > 0)
{
--retries;
IntPtr hwndDlg = FindWindow((string)null, _topLevelSearchString);
if (hwndDlg != IntPtr.Zero)
{
IntPtr hwndFolderCtrl = GetDlgItem(hwndDlg, _dlgItemBrowseControl);
if (hwndFolderCtrl != IntPtr.Zero)
{
IntPtr hwndTV = GetDlgItem(hwndFolderCtrl, _dlgItemTreeView);
if (hwndTV != IntPtr.Zero)
{
IntPtr item = SendMessage(hwndTV, (uint)TVM_GETNEXTITEM, new IntPtr(TVGN_CARET), IntPtr.Zero);
if (item != IntPtr.Zero)
{
SendMessage(hwndTV, TVM_ENSUREVISIBLE, IntPtr.Zero, item);
retries = 0;
t.Stop();
}
}
}
}
}
else
{
//
// We failed to find the Tree View control.
//
// As a fall back (and this is an UberUgly hack), we will send
// some fake keystrokes to the application in an attempt to force
// the Tree View to scroll to the selected item.
//
t.Stop();
SendKeys.Send("{TAB}{TAB}{DOWN}{DOWN}{UP}{UP}");
}
};
t.Interval = 10;
t.Start();
result = dlg.ShowDialog( parent );
}
return result;
}
}
I know this thread is WAY old, but with extension methods, this can be added to the FolderBrowserDialog.ShowDialog method, and then used repeatedly where needed.
The sample (below) is just using the easy SendKeys method (which I hate doing, but in this case, it works well). When using the SendKeys method to jump to the selected folder in the dialog, if you are debugging this in Visual Studio, then the SendKeys call applies to the current window, which would be the active VS window. To be more foolproof and to avoid the wrong window from getting the SendKeys message, then the extension method would contain the external method calls to send messages to the specific window similar to what Marc F posted, but translated to C#.
internal static class FolderBrowserDialogExtension
{
public static DialogResult ShowDialog(this FolderBrowserDialog dialog, bool scrollIntoView)
{
return ShowDialog(dialog, null, scrollIntoView);
}
public static DialogResult ShowDialog(this FolderBrowserDialog dialog, IWin32Window owner, bool scrollIntoView)
{
if (scrollIntoView)
{
SendKeys.Send("{TAB}{TAB}{RIGHT}");
}
return dialog.ShowDialog(owner);
}
}
I have used a workaround from https://www.daniweb.com/software-development/csharp/threads/300578/folderbrowserdialog-expanding-the-selected-directory-
FolderBrowserDialog^ oFBD = gcnew FolderBrowserDialog;
oFBD->RootFolder = Environment::SpecialFolder::MyComputer;
oFBD->SelectedPath = i_sPathImport;
oFBD->ShowNewFolderButton = false; // use if appropriate in your application
SendKeys::Send ("{TAB}{TAB}{RIGHT}"); // <<-- Workaround
::DialogResult oResult = oFBD->ShowDialog ();
It's not the nicest way, but it works for me.
Without the RootFolder it does NOT work on the first call, but on the 2nd and following. With it, it works always.
As others have observed that this failure is dependent on the operating system:
I am using Win 7 Pro x64 SP1
on VB.Net code, just put this line of code right before showing the dialog.
SendKeys.Send ("{TAB}{TAB}{RIGHT}")
this works for me
folderBrowserDialog1.Reset();
folderBrowserDialog1.RootFolder = Environment.SpecialFolder.MyComputer;
folderBrowserDialog1.SelectedPath = WorkingFolder;
but only after the second use of the dialog
I read at different forums that it could be due to RootFolder because SelectedPath and RootFolder are are mutually exclusive, that means both cannot co-exists but with default RootFolder(.Desktop), It allows ,at least, climbing the Tree(navigate the drive/folders).
However, if RootFolder is changed to other than Desktop, you would not be able to navigate to UNC paths.
Answer to Hans Passant:
I tried this Dialog Extension, which has TextBox, but no luck.
Customising the browse for folder dialog to show the path
I have found that:
If .SelectedPath ends with "\", the Dialog will scroll down to make the path visible.
If .SelectedPath does not end with "\", the path is still selected, but not ensured visible.
I computed something in VB.NET, so it would be easy to transform it into C#.
I'm French, and I'm beginner in VB.
Anyway, you can try my solution.
My idea is to launch an asynchronous task just before showing the folderBrowserDialog.
I found this myself, but I was inspired by Brad post.
Here's my code:
Imports System.Threading.Tasks
Imports Microsoft.VisualBasic.FileIO.FileSystem
Public Enum GW
HWNDFIRST = 0
HWNDLAST = 1
HWNDNEXT = 2
HWNDPREV = 3
OWNER = 4
CHILD = 5
ENABLEDPOPUP = 6
End Enum
Public Declare Function SendMessageW Lib "user32.dll" (ByVal hWnd As IntPtr, ByVal msg As UInteger, ByVal wParam As Integer, <MarshalAs(UnmanagedType.LPWStr)> ByVal lParam As String) As IntPtr
Public Declare Function FindWindowExW Lib "user32.dll" (ByVal hWndParent As IntPtr, ByVal hWndChildAfter As IntPtr, <MarshalAs(UnmanagedType.LPWStr)> ByVal lpszClass As String, <MarshalAs(UnmanagedType.LPWStr)> ByVal lpszWindow As String) As IntPtr
Public Declare Function GetWindow Lib "user32" (ByVal hwnd As IntPtr, ByVal wCmd As Long) As Long
Public Declare Function GetDesktopWindow Lib "user32" () As IntPtr
Public Declare Function GetClassName Lib "user32" Alias "GetClassNameA" (ByVal hwnd As IntPtr, ByVal lpClassName As System.Text.StringBuilder, ByVal nMaxCount As Integer) As Integer
Private Sub FolderBrowserDialog_EnsureVisible(FB As FolderBrowserDialog, _Owner As IntPtr)
Dim hwnd As IntPtr
Dim sClassname As New System.Text.StringBuilder(256)
Thread.Sleep(50) 'necessary to let FolderBrowserDialog construct its window
hwnd = GetDesktopWindow() 'Desktop window handle.
hwnd = GetWindow(hwnd, GW.CHILD) 'We will find all children.
Do Until hwnd = 0
If GetWindow(hwnd, GW.OWNER) = _Owner Then 'If one window is owned by our main window...
GetClassName(hwnd, sClassname, 255)
If sClassname.ToString = "#32770" Then 'Check if the class is FolderBrowserDialog.
Exit Do 'Then we found it.
End If
End If
hwnd = GetWindow(hwnd, GW.HWNDNEXT) 'Next window.
Loop 'If no found then exit.
If hwnd = 0 Then Exit Sub
Dim hChild As IntPtr = 0
Dim hTreeView As IntPtr = 0
Dim i As Integer = 0
Do
i += 1
If i > 1000 Then Exit Sub 'Security to avoid infinite loop.
hChild = FindWindowExW(hwnd, hChild, Nothing, Nothing) 'Look for children windows of FolderBrowserDialog.
hTreeView = FindWindowExW(hChild, 0, "SysTreeView32", Nothing) 'Look for treeview of FolderBrowserDialog.
Thread.Sleep(5) 'delay necessary because FolderBrowserDialog is in construction, then treeview maybe not yet exist.
Loop While hTreeView = 0
If SendMessageW(hwnd, &H46A, 1, FB.SelectedPath) = 0 Then 'Send message BFFM_SETEXPANDED to FolderBrowserDialog.
SendMessageW(hTreeView, &H7, 0, Nothing) 'Send message WM_SETFOCUS to the treeeview.
End If
End Sub
Dim My_save_dir = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) & "\My-Saves"
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim FolderBrowserDialog1 As New FolderBrowserDialog
FolderBrowserDialog1.Description = "Choose your save files path."
If Directory.Exists(My_save_dir) Then
FolderBrowserDialog1.SelectedPath = My_save_dir
Else
FolderBrowserDialog1.SelectedPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)
End If
Dim Me_handle = Me.Handle 'Store the main handle to compare after with each windows owner.
Task.Run(Sub() FolderBrowserDialog_EnsureVisible(FolderBrowserDialog1, Me_handle)) 'Here's the trick, run an asynchronous task to modify the folderdialog.
If FolderBrowserDialog1.ShowDialog(Me) = System.Windows.Forms.DialogResult.OK Then
My_save_dir = FolderBrowserDialog1.SelectedPath
End If
End Sub
I'm waiting for your suggestions.
And someone can translate it into C# because I don't know C#.
I run into same problem in c++ /mfc. It worked for me to use ::PostMessage rather than ::SendMessage in the BFFM_INITIALIZED callback to place the TVM_ENSUREVISIBLE msg
case BFFM_INITIALIZED:
{
// select something
::SendMessage(m_hDialogBox, BFFM_SETSELECTION, TRUE, (LPARAM) pszSelection);
// find tree control
m_hTreeCtrl = 0;
HWND hchild = GetWindow(hWnd, GW_CHILD) ;
while (hchild != NULL)
{
VS_TChar classname[200] ;
GetClassName(hchild, classname, 200) ;
if (VS_strcmp(classname, _T("SHBrowseForFolder ShellNameSpace Control")) == 0)
{
HWND hlistctrl = GetWindow(hchild, GW_CHILD) ;
do
{
GetClassName(hlistctrl, classname, 200) ;
if (lstrcmp(classname, _T("SysTreeView32")) == 0)
{
m_hTreeCtrl = hlistctrl;
break ;
}
hlistctrl = GetWindow(hlistctrl, GW_HWNDNEXT) ;
} while (hlistctrl != NULL);
}
if (m_hTreeCtrl)
break;
hchild = GetWindow(hchild, GW_HWNDNEXT);
}
if (m_hTreeCtrl)
{
int item = ::SendMessage(m_hTreeCtrl, TVM_GETNEXTITEM, TVGN_CARET, 0);
if (item != 0)
::PostMessage(m_hTreeCtrl, TVM_ENSUREVISIBLE,0,item);
}
break;
}
I have read the above discussion and solutions. Particularly Brat Oestreicher put me in the right direction. In essence, we must first find the TreeView control in the SHBrowseForFolder dialog, and send that window the TVM_ENSUREVISIBLE message. The following does this in C.
#include <windows.h>
#include <objbase.h>
#include <objidl.h>
#include <Shlobj.h>
#include <Dsclient.h>
#include <wchar.h>
//
// EnumCallback - Callback function for EnumWindows
//
static BOOL CALLBACK EnumCallback(HWND hWndChild, LPARAM lParam)
{
char szClass[MAX_PATH];
HTREEITEM hNode;
if (GetClassName(hWndChild, szClass, sizeof(szClass))
&& strcmp(szClass,"SysTreeView32")==0) {
hNode = TreeView_GetSelection(hWndChild); // found the tree view window
TreeView_EnsureVisible (hWndChild, hNode); // ensure its selection is visible
return(FALSE); // done; stop enumerating
}
return(TRUE); // continue enumerating
}
//
// BrowseCallbackProc - Callback function for SHBrowseForFolder
//
static INT CALLBACK BrowseCallbackProc (HWND hWnd, UINT uMsg, LPARAM lParam, LPARAM lpData)
{
switch (uMsg)
{
case BFFM_INITIALIZED:
SendMessage (hWnd, BFFM_SETEXPANDED, TRUE, lpData); // expand the tree view
SendMessage (hWnd, BFFM_SETSELECTION, TRUE, lpData); // select the item
break;
case BFFM_SELCHANGED:
EnumChildWindows(hWnd, EnumCallback,0);
break;
}
return 0;
}
//
// SelectDirectory - User callable entry point
//
int SelectDirectory (HWND hWndParent, char *path, int pathSize)
{
BROWSEINFO bi = {0};
LPITEMIDLIST pidl = NULL;
wchar_t ws[MAX_PATH];
CoInitialize(0);
if (pathSize < MAX_PATH) return(FALSE);
swprintf(ws, MAX_PATH, L"%hs", path);
bi.hwndOwner = hWndParent;
bi.lpszTitle = "Select Directory";
bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE;
bi.lpfn = BrowseCallbackProc;
bi.lParam = (LPARAM) ws;
pidl = SHBrowseForFolder (&bi);
if (pidl != NULL)
{
LPMALLOC pMalloc = NULL;
SHGetPathFromIDList (pidl, path);
path[pathSize-1]= '\0';
SHGetMalloc(&pMalloc);
pMalloc->lpVtbl->Free(pMalloc,pidl); // deallocate item
pMalloc->lpVtbl->Release(pMalloc);
return (TRUE);
}
return (FALSE);
}
Many thanks to Gary Beene.
dlgFolder.RootFolder = Environment.SpecialFolder.DesktopDirectory;
is not the same as
dlgFolder.RootFolder = Environment.SpecialFolder.Desktop;
What's the difference between SpecialFolder.Desktop and SpecialFolder.DesktopDirectory?
The thread linked indicates that as a path, they do get the same result. But they are not the same, as one is a logical path and the other is a physical path.
I have found when either one is assigned to the RootFolder of the open folder dialog, the resulting behavior can be different.
As a .RootFolder assignment, some versions of windows, like win7, treat either one as "Desktop". That is, you can see the "Computer" sub-entry, and open that to see the individual drive letters. The .SelectedPath gets selected either way, but the selected path is only made visible when the logical path of the desktop is assigned to the .RootFolder.
Worse, when using the browse folder dialog in win10 pre-release, it appears that "DesktopDirectory" as just that, the contents of the Desktop Directory only, with no link whatsoever to the logical desktop directory. And not listing any sub-items under it. Very frustrating if an app written for win7 is trying to be used with win10.
I think the problem the OP is having is that they employed the physical desktop as the root, when they should have employed the logical desktop.
I don't have an explanation for why the OP's two different machines respond differently. I would speculate that they have two different versions of the .NET framework installed.
The fact that win10 prerelease has the "Stuck on Desktop" issue with the browse folder dialog may be due to the more recent .NET framework shipped with win10 prerelease. Unfortunately, I remain ignorant of all the facts in this (win10) case, as I have not updated yet.
P.S. I found that win8 also experiences the "Stuck on Desktop" symptom:
https://superuser.com/questions/869928/windows-8-1-folder-selection-dialog-missing-my-computer-and-sub-items
The workaround there was to select the alternate GUI in win8. Perhaps something similar can be done in win10 prerelease.
In response to Marc F's post - I've converted the VB.Net to C#
public enum GW
{
HWNDFIRST = 0,
HWNDLAST = 1,
HWNDNEXT = 2,
HWNDPREV = 3,
OWNER = 4,
CHILD = 5,
ENABLEDPOPUP = 6
}
[System.Runtime.InteropServices.DllImport("user32.dll", EntryPoint = "SendMessageW", ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Ansi, SetLastError = true)]
public static extern IntPtr SendMessageW(IntPtr hWnd, uint msg, int wParam, [MarshalAs(UnmanagedType.LPWStr)] string lParam);
[System.Runtime.InteropServices.DllImport("user32.dll", EntryPoint = "FindWindowExW", ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Ansi, SetLastError = true)]
public static extern IntPtr FindWindowExW(IntPtr hWndParent, IntPtr hWndChildAfter, [MarshalAs(UnmanagedType.LPWStr)] string lpszClass, [MarshalAs(UnmanagedType.LPWStr)] string lpszWindow);
[System.Runtime.InteropServices.DllImport("user32", EntryPoint = "GetWindow", ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Ansi, SetLastError = true)]
public static extern UInt32 GetWindow(IntPtr hwnd, UInt32 wCmd);
[System.Runtime.InteropServices.DllImport("user32", EntryPoint = "GetDesktopWindow", ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Ansi, SetLastError = true)]
public static extern IntPtr GetDesktopWindow();
[System.Runtime.InteropServices.DllImport("user32", EntryPoint = "GetClassNameA", ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Ansi, SetLastError = true)]
public static extern int GetClassName(IntPtr hwnd, System.Text.StringBuilder lpClassName, int nMaxCount);
private void FolderBrowserDialog_EnsureVisible(FolderBrowserDialog FB, IntPtr _Owner)
{
IntPtr hwnd = System.IntPtr.Zero;
System.Text.StringBuilder sClassname = new System.Text.StringBuilder(256);
Thread.Sleep(50); //necessary to let FolderBrowserDialog construct its window
hwnd = GetDesktopWindow(); //Desktop window handle.
hwnd = (System.IntPtr)GetWindow(hwnd, (UInt32)GW.CHILD); //We will find all children.
while (!(hwnd == (System.IntPtr)0))
{
if (GetWindow(hwnd, (UInt32)GW.OWNER) == (UInt32)_Owner) //If one window is owned by our main window...
{
GetClassName(hwnd, sClassname, 255);
if (sClassname.ToString() == "#32770") //Check if the class is FolderBrowserDialog.
{
break; //Then we found it.
}
}
hwnd = (System.IntPtr)GetWindow(hwnd, (UInt32)GW.HWNDNEXT); //Next window.
} //If no found then exit.
if (hwnd == (System.IntPtr)0)
{
return;
}
IntPtr hChild = (System.IntPtr)0;
IntPtr hTreeView = (System.IntPtr)0;
int i = 0;
do
{
i += 1;
if (i > 1000) //Security to avoid infinite loop.
{
return;
}
hChild = FindWindowExW(hwnd, hChild, null, null); //Look for children windows of FolderBrowserDialog.
hTreeView = FindWindowExW(hChild, (System.IntPtr)0, "SysTreeView32", null); //Look for treeview of FolderBrowserDialog.
Thread.Sleep(5); //delay necessary because FolderBrowserDialog is in construction, then treeview maybe not yet exist.
} while (hTreeView == (System.IntPtr)0);
if (SendMessageW(hwnd, 0x46A, 1, FB.SelectedPath) == (System.IntPtr)0) //Send message BFFM_SETEXPANDED to FolderBrowserDialog.
{
SendMessageW(hTreeView, 0x7, 0, null); //Send message WM_SETFOCUS to the treeeview.
}
}
Tested this and it works fine. Make sure you reference System.Runtime.InteropServices, System.Threading, an System.Threading.Tasks
This link has a simple answer that worked for me fine (I have windows 8.1)
FolderBrowserDialog: Expanding the selected directory
I tried the code SendKeys.Send("{TAB}{TAB}{RIGHT}");, but the same code sometimes just didn't work.
At the end the best solution which I went with, where I can't get it not working, was:
public static class FolderBrowserDialogExt
{
public static void ScrollSelectedPathIntoView(this FolderBrowserDialog fbd)
{
System.Threading.Tasks.Task.Run(() =>
{
SendKeys.SendWait("{TAB}");
SendKeys.SendWait("{TAB}");
SendKeys.SendWait("{RIGHT}");
});
}
}
void SomeMeth(string selPath)
{
using (var fbd = new FolderBrowserDialog())
{
fbd.SelectedPath = selPath;
fbd.ScrollSelectedPathIntoView();
fbd.ShowDialog();//fbd.ShowDialog(owner);
}
}
This is how I use the folder browser dialog. This code solves the selected folder issue, and also selects the folder from the clipboard or the registry (if any), and if the folder is deleted it goes up throw parents until selecting an existing folder. This makes using the dialog very comfortable:
Dim FldrBr As New FolderBrowserDialog With {
.RootFolder = Environment.SpecialFolder.Desktop,
.Description = "Chose a flder",
.ShowNewFolderButton = False
}
Dim x = Clipboard.GetText()
Dim lastDir = GetSetting("Mp4Joiner", "SrcFolder", "Path", "")
Try
If x = "" Then
x = lastDir
ElseIf File.Exists(x) Then
x = Path.GetDirectoryName(x)
ElseIf Not Directory.Exists(x) Then
x = lastDir
End If
Catch
x = lastDir
End Try
Do
If x = "" OrElse Directory.GetDirectoryRoot(x) = x OrElse Directory.Exists(x) Then
Exit Do
End If
x = Path.GetDirectoryName(x)
Loop
FldrBr.SelectedPath = x
Dim th As New Threading.Thread(
Sub()
Threading.Thread.Sleep(300)
SendKeys.SendWait("{TAB}{TAB}{RIGHT}")
End Sub)
th.Start()
If FldrBr.ShowDialog(Me) = Windows.Forms.DialogResult.OK Then
SaveSetting("Mp4Joiner", "SrcFolder", "Path", FldrBr.SelectedPath)
' ........
End If
The best approach, at least the most reliable is to make your own browser class dialog box. The tree scrolling issue has been a pain for many years - it will never get fixed!
If you know how to render in paint there is not much you can't do.. fast in paint well that is another story.
The first place I would look is at the open source .Net source code on GitHub, in your .Net version of choice, for the dialog class you're interested in improving. You may be surprised what you can achieve with a little effort and follow through. Just duplicate the control and debug to the point where the error occurs and patch - that'a what Microsoft does, so too can you!
Since this is an old thread and posting samples may never get read. It would make more since to post if asked.
Yet for someone looking to solve such an issue as with tree scrolling to the "expected" directory, here is some solid advise. If an issue exists with a control or library that has no immediate solution, create your own version, when possible extend the original and patch the problem. I've revamped everything from the Windows.Form.Control class to Win32 libraries for the sole purpose of getting predictable and accurate results.
The good news is that with C# there is a lot of low level control available to achieve almost any reasonable objective and the is C too.
In the past I have spent way too many hours searching for a solution to a problem where had I just recreated what was not working a lot of time would have been saved.

Sending PictureBox Contents to MsPaint

How do i go about sending the contents of a picturebox to be edited in paint?
I've thought of quickly saving it temporarily then sending the temp address to be loaded, but i'd think that would cause some minor saving issues.
Unfortunately I'm providing the answer in C# at this time. Luckily, just syntax and not content will have to change.
Assuming this is your picturebox control, take the contents (as a bitmap) and put it on the clipboard. Now you can paste it into MSPaint however you'd like with SendMessage or SendKeys if you make it foreground, etc.
Bitmap bmp = new Bitmap(pictureBox1.Image);
Clipboard.SetData(System.Windows.Forms.DataFormats.Bitmap, bmp);
A poor example, with the optional opening of the mspaint and waiting for it to appear, using SendKeys to paste.
[DllImport("User32.dll", SetLastError = true)]
public static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr childAfter, string className, string windowTitle);
[DllImport("user32.dll")]
private static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool SetForegroundWindow(IntPtr hWnd);
private static void TestSendPictureToMSPaint()
{
Bitmap bmp = new Bitmap(pictureBox1.Image);
Clipboard.SetData(System.Windows.Forms.DataFormats.Bitmap, bmp);
//optional#1 - open MSPaint yourself
//var proc = Process.Start("mspaint");
IntPtr msPaint = IntPtr.Zero;
//while (msPaint == IntPtr.Zero) //optional#1 - if opening MSPaint yourself, wait for it to appear
msPaint = FindWindowEx(IntPtr.Zero, new IntPtr(0), "MSPaintApp", null);
SetForegroundWindow(msPaint); //optional#2 - if not opening MSPaint yourself
IntPtr currForeground = IntPtr.Zero;
while (currForeground != msPaint)
{
Thread.Sleep(250); //sleep before get to exit loop and send immediately
currForeground = GetForegroundWindow();
}
SendKeys.SendWait("^v");
}
Answering myself only to show my working code for anyone else who may want a nice simple example.
So with img_picture as my picturebox
Dim sendimage As Bitmap = CType(img_picture.Image, Bitmap)
Clipboard.SetDataObject(sendimage)
Dim programid As Integer = Shell("mspaint", AppWinStyle.MaximizedFocus)
System.Threading.Thread.Sleep(100)
AppActivate(programid)
SendKeys.Send("^v")
Without the thread pause you'll get an error with AppActivate claiming no such process exits.
Thanks to Bland for helping.

SetWindowLong & SetLayeredWindowAttributes doesnt work on different user C#

The goal is
I want to change the opacity of some application (target) running on win xp.
The situation
I logged in win xp as AD (active dir) user account (some-domain\username)
most of the target applications are run as local user or another ad user
The problem and my Question
SetWindowLong & SetLayeredWindowAttributes doesn't work on target application that run as other user account. But it works on target application that runs under the same user account (logged user account)
How to change other app's window opacity that run as different user account?
Illustration app
It's a win form app (let's call it OpaciToggler.exe). I have 2 buttons (btnRunSomething and btnHideThatThing) and a textbox (txtPid). As simple as that.
When I click the btnRunSomething, it run an .exe as different user. All the details is in the app.config. In this case I run this application (OpaciToggler.exe, from the debug/bin) as different user (localComputer\user1)
The txtPid is for manual pid input. Usually I open the task manager (win > run > taskmgr) and find the pid (under the process tab) of any application (target) I want to test with, then type it here.
When I click the btnHideThatThing, it'll toggle the opacity of the target application (whose pid in the txtPid)
Code C#
This is how I call the target app.
private void btnRunSomething_Click(object sender, EventArgs e)
{
System.Diagnostics.Process p = new System.Diagnostics.Process();
p.StartInfo.Domain = ConfigurationManager.AppSettings["StartInfoDomain"];
p.StartInfo.UserName = ConfigurationManager.AppSettings["StartInfoUserName"];
p.StartInfo.FileName = ConfigurationManager.AppSettings["StartInfoFileName"];
p.StartInfo.Arguments = ConfigurationManager.AppSettings["StartInfoArguments"];
System.String rawPassword = ConfigurationManager.AppSettings["StartInfoPassword"];
System.Security.SecureString encPassword = new System.Security.SecureString();
foreach (System.Char c in rawPassword)
{
encPassword.AppendChar(c);
}
p.StartInfo.Password = encPassword;
p.StartInfo.UseShellExecute = false;
p.Start();
}
This is how I try to call the opacity thing
private void btnHideThatThing_Click(object sender, EventArgs e)
{
// manual input pid
int pid = int.Parse(txtPid.Text);
IntPtr mwh = Process.GetProcessById(pid).MainWindowHandle;
doHideThing(mwh);
// doHideThing(Process.GetProcessById(int.Parse(txtPid.Text)).MainWindowHandle);
}
The hide method
private void doHideThing(IntPtr hndl)
{
SetWindowLong(hndl, GWL_EXSTYLE, GetWindowLong(hndl, GWL_EXSTYLE) ^ WS_EX_LAYERED).ToString();
// _whatNow = Marshal.GetLastWin32Error();
SetLayeredWindowAttributes(hndl, 0, (255 * 20) / 100, LWA_ALPHA).ToString();
// _whatNow = Marshal.GetLastWin32Error();
RedrawWindow(hndl, IntPtr.Zero, IntPtr.Zero,
RedrawWindowFlags.Erase | RedrawWindowFlags.Invalidate |
RedrawWindowFlags.Frame | RedrawWindowFlags.AllChildren);
}
other codes
private const int GWL_EXSTYLE = -20;
private const int GWL_STYLE = -16;
private const int WS_EX_LAYERED = 0x80000;
private const int LWA_ALPHA = 0x2;
private const int LWA_COLORKEY = 0x1;
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
private static extern int GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("user32.dll", SetLastError = true)]
static extern bool RedrawWindow(IntPtr hWnd, IntPtr lprcUpdate, IntPtr hrgnUpdate, RedrawWindowFlags flags);
RedrawWindowFlags enum (I don't remember where I get this)
[Flags()]
enum RedrawWindowFlags : uint
{
/// <summary>
/// Invalidates the rectangle or region that you specify in lprcUpdate or hrgnUpdate.
/// You can set only one of these parameters to a non-NULL value. If both are NULL, RDW_INVALIDATE invalidates the entire window.
/// </summary>
Invalidate = 0x1,
/// <summary>Causes the OS to post a WM_PAINT message to the window regardless of whether a portion of the window is invalid.</summary>
InternalPaint = 0x2,
/// <summary>
/// Causes the window to receive a WM_ERASEBKGND message when the window is repainted.
/// Specify this value in combination with the RDW_INVALIDATE value; otherwise, RDW_ERASE has no effect.
/// </summary>
Erase = 0x4,
/// <summary>
/// Validates the rectangle or region that you specify in lprcUpdate or hrgnUpdate.
/// You can set only one of these parameters to a non-NULL value. If both are NULL, RDW_VALIDATE validates the entire window.
/// This value does not affect internal WM_PAINT messages.
/// </summary>
Validate = 0x8,
NoInternalPaint = 0x10,
/// <summary>Suppresses any pending WM_ERASEBKGND messages.</summary>
NoErase = 0x20,
/// <summary>Excludes child windows, if any, from the repainting operation.</summary>
NoChildren = 0x40,
/// <summary>Includes child windows, if any, in the repainting operation.</summary>
AllChildren = 0x80,
/// <summary>Causes the affected windows, which you specify by setting the RDW_ALLCHILDREN and RDW_NOCHILDREN values, to receive WM_ERASEBKGND and WM_PAINT messages before the RedrawWindow returns, if necessary.</summary>
UpdateNow = 0x100,
/// <summary>
/// Causes the affected windows, which you specify by setting the RDW_ALLCHILDREN and RDW_NOCHILDREN values, to receive WM_ERASEBKGND messages before RedrawWindow returns, if necessary.
/// The affected windows receive WM_PAINT messages at the ordinary time.
/// </summary>
EraseNow = 0x200,
Frame = 0x400,
NoFrame = 0x800
}
Example, Test and Test Result
logged in win xp as domainA\ad_user1
run the OpaciToggler.exe application (Instance1, run as domainA\ad_user1)
Then I click the run something. It opens another OpaciToggler.exe (Instance2), runs as another user account (run as localcomputer\l_user1) OR right click the .exe then click run as..
Instance1
pid: 1234
run as: domainA\ad_user1
txtPid: 5678 (Instance2, doesn't work)
txtPid: 1234 (self, works)
txtPid: any pid run as the same account (ex: notepad.exe, calc.exe, etc, works)
Instance2
pid: 5678
run as: localcomputer\l_user1
txtPid: 1234 (Instance 1, doesnt work)
txtPid: 5678 (self, works)
txtPid: any pid run as the same account (localcomputer\l_user1) (ex: notepad.exe, calc.exe, etc, doesn't work!!)
Again, my Question
How to change other app's window opacity that run as different user account?
Thanks and sorry for my bad English :(.

How to know if a textbox has wrapped lines?

I'm trying to change the font of a textbox when it is resized, in order to show the fittest font size, but mantaining the original text's line count. But I haven't achieved to know if lines is wrapped, to decrease the font size.
Could you give me an idea of how doing it?
It can't be accomplished just by referring to the component's methods or properties.
You need to use EM_GETLINECOUNT message
Sample code (converted from original code sample in Visual Basic to C#):
using System.Runtime.InteropServices;
public class Form1
{
private const int EM_GETLINECOUNT = 0xba;
[DllImport("user32", EntryPoint = "SendMessageA", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
private static extern int SendMessage(int hwnd, int wMsg, int wParam, int lParam);
private void TextBox1_TextChanged(System.Object sender, System.EventArgs e)
{
var numberOfLines = SendMessage(textBox1.Handle.ToInt32(), EM_GETLINECOUNT, 0, 0);
}
}
I have tested it and it works.
The System.Drawing.Graphics object has a Measure String function if you want a precise look at string lengths.
System.Drawing.SizeF len2 = graphic.MeasureString(*text*, *font*);
It doesn't take into account leading spaces, so for my measurements I used something like this to replace spaces with 'X' which was generally close in size.
if (ibText.Content.Length > 0 && ibText.Content[0] == ' ')
len2 = graphic.MeasureString(ibText.Content.Replace(' ', 'X'), ibText.Font);
maybe you could wrap your textbox inside a ViewBox instead : it will do the job of resizing for you.

Check if WCF(namedpipes) host is available?

Hi,
We have a winform application that is only to be executed as a singelton, If a second instance try to start this new instance will connect to the current and transmit parameters over namedpipes.
The problem is that when starting the first instance there will be a try to connect to existing host. If the host is not existing(like in this case) an exception will be thrown. There is no problem to handle this exception but our developers is often using "Break on Exception" and that means that every time we startup the application the developer will get two(in this case) breaks about exception. Thay will have to hit F5 twice for every start.
Is there any way to check if the service is available without throw exception if its not?
BestRegards
Edit1:
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool CloseHandle(IntPtr hObject);
[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr OpenFileMapping(uint dwDesiredAccess, bool bInheritHandle, string lpName);
The following code says : Error 152 Cannot implicitly convert type 'System.IntPtr' to 'Orbit.Client.Main.Classes.Controllers.MyClientController.SafeFileMappingHandle'
using (SafeFileMappingHandle fileMappingHandle
= OpenFileMapping(FILE_MAP_READ, false, sharedMemoryName))
{
If there is already a WCF server listening on the named pipe endpoint, there will be a shared memory object created, via which the server publishes the actual name of the pipe. See here for details of this.
You can check for the existence of this shared memory object with code something like the following, which will not throw, just return false, if there is no server running already. (I've extracted this from code I already have working, and then edited it to do what you want - but without testing the edited version, so apologies if you have to fix up assembly/namespace refs etc to get it running.)
public static class ServiceInstanceChecker
{
public static bool DoesAServerExistAlready(string hostName, string path)
{
return IsNetNamedPipeSharedMemoryMetaDataPublished(DeriveSharedMemoryName(hostName, path));
}
private static string DeriveSharedMemoryName(string hostName, string path)
{
StringBuilder builder = new StringBuilder();
builder.Append(Uri.UriSchemeNetPipe);
builder.Append("://");
builder.Append(hostName.ToUpperInvariant());
builder.Append(path);
byte[] uriBytes = Encoding.UTF8.GetBytes(builder.ToString());
string encodedNameRoot;
if (uriBytes.Length >= 0x80)
{
using (HashAlgorithm algorithm = new SHA1Managed())
{
encodedNameRoot = ":H" + Convert.ToBase64String(algorithm.ComputeHash(uriBytes));
}
}
else
{
encodedNameRoot = ":E" + Convert.ToBase64String(uriBytes);
}
return Uri.UriSchemeNetPipe + encodedNameRoot;
}
private static bool IsNetNamePipeSharedMemoryMetaDataPublished(string sharedMemoryName)
{
const uint FILE_MAP_READ = 0x00000004;
const int ERROR_FILE_NOT_FOUND = 2;
using (SafeFileMappingHandle fileMappingHandle
= OpenFileMapping(FILE_MAP_READ, false, sharedMemoryName))
{
if (fileMappingHandle.IsInvalid)
{
int errorCode = Marshal.GetLastWin32Error();
if (ERROR_FILE_NOT_FOUND == errorCode) return false;
throw new Win32Exception(errorCode); // The name matched, but something went wrong opening it
}
return true;
}
}
private class SafeFileMappingHandle : SafeHandleZeroOrMinusOneIsInvalid
{
public SafeFileMappingHandle() : base(true) { }
public SafeFileMappingHandle(IntPtr handle) : base(true) { base.SetHandle(handle); }
protected override bool ReleaseHandle()
{
return CloseHandle(base.handle);
}
}
}
The host name and path you pass in are derived from the WCF service url. Hostname is either a specific hostname (e.g. localhost) or +, or *, depending on the setting for HostNameComparisonMode.
EDIT: You'll also need a couple of P/Invoke declarations for the Win API functions:
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool CloseHandle(IntPtr hObject);
[DllImport("kernel32.dll", SetLastError = true)]
static extern SafeFileMappingHandle OpenFileMapping(
uint dwDesiredAccess,
bool inheritHandle,
string name
);
EDIT2: We need to tweak the return value of DeriveSharedMemoryName to specify the Local kernel namespace, assuming that your application is not run with elevated privileges. Change the last line of this function to read:
return #"Local\" + Uri.UriSchemeNetPipe + encodedNameRoot;
You also need to specify the hostname parameter correctly to match the hostNameComparisonMode setting used in your binding. As far as I recall, this defaults to StrongWildcard matching in the NetNamedPipeBinding, so you probably need to pass in "+" rather than "localhost".
Can you try to list the named pipes available using
String[] listOfPipes = System.IO.Directory.GetFiles(#"\.\pipe\");
and then determine is your named pipe is amongst them?
My solution is the following :
if (Debugger.IsAttached)
return true;
This will make sure that the code for checking the service is never runned during debugging.
BestRegards