C# How do I save the program to open a file in? - c#

When a button is clicked in our application, it downloads a file from the server to the client and opens it for the user to view. We allow the user to select the application to open that file with, but the standard box offers an "Always use the selected program to open this kind of file" option. Unofrtunately ticking this makes no difference and the .rtf file defaults to opening in Word again next time.
How do we get this setting to store and retreive correctly?
The code that we're using to display this window is as follows:
//Ask the user what application they want to open the file in.
if (strFileName != "" && File.Exists(strFileName))
{
// Call Windows "Open With" dialog
CoreUtilities.ShowOpenFileDialog(strFileName);
}
Many thanks
Colin

Here's a code snippet that calls Windows's Open with... dialog,
Unless CoreUtilities.ShowOpenFileDialog is already implemented using this approach, maybe you should give it a hit:
[Serializable]
public struct ShellExecuteInfo
{
public int Size;
public uint Mask;
public IntPtr hwnd;
public string Verb;
public string File;
public string Parameters;
public string Directory;
public uint Show;
public IntPtr InstApp;
public IntPtr IDList;
public string Class;
public IntPtr hkeyClass;
public uint HotKey;
public IntPtr Icon;
public IntPtr Monitor;
}
// Code For OpenWithDialog Box
[DllImport("shell32.dll", SetLastError = true)]
extern public static bool
ShellExecuteEx(ref ShellExecuteInfo lpExecInfo);
public const uint SW_NORMAL = 1;
static void OpenAs(string file)
{
ShellExecuteInfo sei = new ShellExecuteInfo();
sei.Size = Marshal.SizeOf(sei);
sei.Verb = "openas";
sei.File = file;
sei.Show = SW_NORMAL;
if (!ShellExecuteEx(ref sei))
throw new System.ComponentModel.Win32Exception();
}

There is a dirty hack, but I don't know if you'll like it :)
You could delete stored assoc in registry before calling CoreUtilities.ShowOpenFileDialog() every time.
Here's the path in registry
HKEY_CURRENT_USER/Software/Microsoft/Windows/CurrentVersion/Explorer/FileExts
Or you can try running
System.Diagnostics.Process.Start(path);
This will always use the default program. Or show the open with dialog when needed (when there's no default program associated)

Have you tried opening the file using the "open" verb?
public static void displayLabel(string labelFileName)
{
System.Diagnostics.ProcessStartInfo info = new System.Diagnostics.ProcessStartInfo(labelFileName);
info.UseShellExecute = true;
info.Verb = "open";
System.Diagnostics.Process.Start(info);
}
I use the code above to open up files. It will open the file with the default application you have assigned for the given extension. For example if called with a filename of .pdf it would open in Acrobat, where as if you passed in a .txt it would open in Notepad.

Related

How to get the original location of AppData\Roaming after the user has changed it?

I need to access contents in the folder %AppData%\Roaming\Microsoft.
This usually works fine by doing the following:
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Microsoft");
The problem is that now the explorer lets you change the location of %AppData% by right clicking the Roaming folder and setting the location to some other place. However, this doesn't change the location of the Microsoft folder, which will remain in the original %AppData%.
I've thought about doing something like this:
string roaming = "C:\Users\" + Environment.UserName + #"\AppData\Roaming";
Though this just looks bad and looks like it could break easily.
Any suggestions?
I don't know if .NET can do it but WinAPI can. PInvoke SHGetFolderPath with the SHGFP_TYPE_DEFAULT flag:
using System;
using System.Runtime.InteropServices;
namespace Test { class TestApp {
public class WinApi
{
public const int CSIDL_APPDATA = 0x1a;
public const int SHGFP_TYPE_DEFAULT = 1;
[DllImport("shell32.dll")]
public static extern int SHGetFolderPath(IntPtr hwnd, int csidl, IntPtr hToken, uint flags, [Out] System.Text.StringBuilder Path);
}
[STAThread]
static void Main()
{
System.Text.StringBuilder builder = new System.Text.StringBuilder(260);
int result = WinApi.SHGetFolderPath(IntPtr.Zero, WinApi.CSIDL_APPDATA, IntPtr.Zero, WinApi.SHGFP_TYPE_DEFAULT, builder);
string path = "";
if (result == 0) path = builder.ToString();
Console.WriteLine(string.Format("{0}:{1}", result, path));
}
} }
You can try use following code to access %AppData%\Roaming\Microsoft:
string appData= Environment.ExpandEnvironmentVariables("%AppData%");
string roamingMicrosoft = Path.Combine(appData, #"Microsoft");
But I'm not really sure if Windows changes environment variable %AppData% by default when user changes path to AppData by it own.

WPF Create a txt file from a byte array in memory and open it with notepad [duplicate]

This question already has answers here:
How to open text in Notepad from .NET?
(5 answers)
Closed 6 years ago.
Someone told me that if you end up on the Google 3rd page on all of your searches is almost impossible to do it, but this should be very simple.
I am receiving a byte array from WCF and I would like to convert it to a txt file. The thing is that i would like to open them using a button in WPF without writing a copy of that file on the client side hard-drive. If it is necessary the user can save it locally from notepad directly.
All the files are very small(up to 100kb) so RAM should not be affected.
Thank you for all of your answers! I will post the result here in case someone need it in the future!
_______________________________________________________________________________
Create a new class: NotePadHelper(include System.Runtime.InteropServices;)
public class NotePadHelper
{
[DllImport("user32.dll", EntryPoint = "SetWindowText")]
private static extern int SetWindowText(IntPtr hWnd, string text);
[DllImport("user32.dll", EntryPoint = "FindWindowEx")]
private static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
[DllImport("User32.dll", EntryPoint = "SendMessage")]
private static extern int SendMessage(IntPtr hWnd, int uMsg, int wParam, string lParam);
public static void ShowMessage(string message = null, string title = null)
{
Process notepad = Process.Start(new ProcessStartInfo("notepad.exe"));
if (notepad != null)
{
notepad.WaitForInputIdle();
if (!string.IsNullOrEmpty(title))
SetWindowText(notepad.MainWindowHandle, title);
if (!string.IsNullOrEmpty(message))
{
IntPtr child = FindWindowEx(notepad.MainWindowHandle, new IntPtr(0), "Edit", null);
SendMessage(child, 0x000C, 0, message);
}
}
}
}
Now you need to convert the byte array to string and show the Doc in Notepad
var str = System.Text.Encoding.Default.GetString(docObject.Image);
NotePadHelper.ShowMessage(str, docObject.Name);
Thank you #keyboardP and #PInvoke!
This truly can be done. Its just a bit of work.
First you need to get a string of your byte[].
This is quite easy:
string text = System.Text.Encoding.UTF8.GetString(byteArray);
Now all you need to do is to open Notepad and write that text to it...
So what happens in many common programs a local copy is created in background to prevent potential dataloss.
I would suggest doing the same. Just write to a file (in temp dir), save the path and open it.
//To Save to a file:
string tempPath = Path.Combine(Path.GetTempPath(), fileName);
File.WriteAllText(tempPath, text);
//Now open the file you just created:
Process.Start(tempPath);
Assuming the user is saving it on a different path you're free to delete the temp file.
If you wanna try the thing with writing into notepad here's a link on where to get so details:
Open Notepad and add Text not working
I dislike working with handles and manipulating other windows but feel free to give it a try.
Hope this helps

How do I add a console into my Windows form application? [duplicate]

To get stuck in straight away, a very basic example:
using System;
using System.Windows.Forms;
class test
{
static void Main()
{
Console.WriteLine("test");
MessageBox.Show("test");
}
}
If I compile this with default options (using csc at command line), as expected, it will compile to a console application. Also, because I imported System.Windows.Forms, it will also show a message box.
Now, if I use the option /target:winexe, which I think is the same as choosing Windows Application from within project options, as expected I will only see the Message Box and no console output.
(In fact, the moment it is launched from command line, I can issue the next command before the application has even completed).
So, my question is - I know that you can have "windows"/forms output from a console application, but is there anyway to show the console from a Windows application?
this one should work.
using System.Runtime.InteropServices;
private void Form1_Load(object sender, EventArgs e)
{
AllocConsole();
}
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool AllocConsole();
Perhaps this is over-simplistic...
Create a Windows Form project...
Then: Project Properties -> Application -> Output Type -> Console Application
Then can have Console and Forms running together, works for me
If you are not worrying about opening a console on-command, you can go into the properties for your project and change it to Console Application
.
This will still show your form as well as popping up a console window. You can't close the console window, but it works as an excellent temporary logger for debugging.
Just remember to turn it back off before you deploy the program.
You can call AttachConsole using pinvoke to get a console window attached to a WinForms project: http://www.csharp411.com/console-output-from-winforms-application/
You may also want to consider Log4net ( http://logging.apache.org/log4net/index.html ) for configuring log output in different configurations.
Create a Windows Forms Application, and change the output type to Console.
It will result in both a console and the form to open.
This worked for me, to pipe the output to a file.
Call the console with
cmd /c "C:\path\to\your\application.exe" > myfile.txt
Add this code to your application.
[DllImport("kernel32.dll")]
static extern bool AttachConsole(UInt32 dwProcessId);
[DllImport("kernel32.dll")]
private static extern bool GetFileInformationByHandle(
SafeFileHandle hFile,
out BY_HANDLE_FILE_INFORMATION lpFileInformation
);
[DllImport("kernel32.dll")]
private static extern SafeFileHandle GetStdHandle(UInt32 nStdHandle);
[DllImport("kernel32.dll")]
private static extern bool SetStdHandle(UInt32 nStdHandle, SafeFileHandle hHandle);
[DllImport("kernel32.dll")]
private static extern bool DuplicateHandle(
IntPtr hSourceProcessHandle,
SafeFileHandle hSourceHandle,
IntPtr hTargetProcessHandle,
out SafeFileHandle lpTargetHandle,
UInt32 dwDesiredAccess,
Boolean bInheritHandle,
UInt32 dwOptions
);
private const UInt32 ATTACH_PARENT_PROCESS = 0xFFFFFFFF;
private const UInt32 STD_OUTPUT_HANDLE = 0xFFFFFFF5;
private const UInt32 STD_ERROR_HANDLE = 0xFFFFFFF4;
private const UInt32 DUPLICATE_SAME_ACCESS = 2;
struct BY_HANDLE_FILE_INFORMATION
{
public UInt32 FileAttributes;
public System.Runtime.InteropServices.ComTypes.FILETIME CreationTime;
public System.Runtime.InteropServices.ComTypes.FILETIME LastAccessTime;
public System.Runtime.InteropServices.ComTypes.FILETIME LastWriteTime;
public UInt32 VolumeSerialNumber;
public UInt32 FileSizeHigh;
public UInt32 FileSizeLow;
public UInt32 NumberOfLinks;
public UInt32 FileIndexHigh;
public UInt32 FileIndexLow;
}
static void InitConsoleHandles()
{
SafeFileHandle hStdOut, hStdErr, hStdOutDup, hStdErrDup;
BY_HANDLE_FILE_INFORMATION bhfi;
hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
hStdErr = GetStdHandle(STD_ERROR_HANDLE);
// Get current process handle
IntPtr hProcess = Process.GetCurrentProcess().Handle;
// Duplicate Stdout handle to save initial value
DuplicateHandle(hProcess, hStdOut, hProcess, out hStdOutDup,
0, true, DUPLICATE_SAME_ACCESS);
// Duplicate Stderr handle to save initial value
DuplicateHandle(hProcess, hStdErr, hProcess, out hStdErrDup,
0, true, DUPLICATE_SAME_ACCESS);
// Attach to console window – this may modify the standard handles
AttachConsole(ATTACH_PARENT_PROCESS);
// Adjust the standard handles
if (GetFileInformationByHandle(GetStdHandle(STD_OUTPUT_HANDLE), out bhfi))
{
SetStdHandle(STD_OUTPUT_HANDLE, hStdOutDup);
}
else
{
SetStdHandle(STD_OUTPUT_HANDLE, hStdOut);
}
if (GetFileInformationByHandle(GetStdHandle(STD_ERROR_HANDLE), out bhfi))
{
SetStdHandle(STD_ERROR_HANDLE, hStdErrDup);
}
else
{
SetStdHandle(STD_ERROR_HANDLE, hStdErr);
}
}
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
{
// initialize console handles
InitConsoleHandles();
if (args.Length != 0)
{
if (args[0].Equals("waitfordebugger"))
{
MessageBox.Show("Attach the debugger now");
}
if (args[0].Equals("version"))
{
#if DEBUG
String typeOfBuild = "d";
#else
String typeOfBuild = "r";
#endif
String output = typeOfBuild + Assembly.GetExecutingAssembly()
.GetName().Version.ToString();
//Just for the fun of it
Console.Write(output);
Console.Beep(4000, 100);
Console.Beep(2000, 100);
Console.Beep(1000, 100);
Console.Beep(8000, 100);
return;
}
}
}
I found this code here: http://www.csharp411.com/console-output-from-winforms-application/
I thought is was worthy to post it here as well.
There are basically two things that can happen here.
Console output
It is possible for a winforms program to attach itself to the console window that created it (or to a different console window, or indeed to a new console window if desired). Once attached to the console window Console.WriteLine() etc works as expected. One gotcha to this approach is that the program returns control to the console window immediately, and then carries on writing to it, so the user can also type away in the console window. You can use start with the /wait parameter to handle this I think.
Link to start Command syntax
Redirected console output
This is when someone pipes the output from your program somewhere else, eg.
yourapp > file.txt
Attaching to a console window in this case effectively ignores the piping. To make this work you can call Console.OpenStandardOutput() to get a handle to the stream that the output should be piped to. This only works if the output is piped, so if you want to handle both of the scenarios you need to open the standard output and write to it and attach to the console window. This does mean that the output is sent to the console window and to the pipe but its the best solution I could find. Below the code I use to do this.
// This always writes to the parent console window and also to a redirected stdout if there is one.
// It would be better to do the relevant thing (eg write to the redirected file if there is one, otherwise
// write to the console) but it doesn't seem possible.
public class GUIConsoleWriter : IConsoleWriter
{
[System.Runtime.InteropServices.DllImport("kernel32.dll")]
private static extern bool AttachConsole(int dwProcessId);
private const int ATTACH_PARENT_PROCESS = -1;
StreamWriter _stdOutWriter;
// this must be called early in the program
public GUIConsoleWriter()
{
// this needs to happen before attachconsole.
// If the output is not redirected we still get a valid stream but it doesn't appear to write anywhere
// I guess it probably does write somewhere, but nowhere I can find out about
var stdout = Console.OpenStandardOutput();
_stdOutWriter = new StreamWriter(stdout);
_stdOutWriter.AutoFlush = true;
AttachConsole(ATTACH_PARENT_PROCESS);
}
public void WriteLine(string line)
{
_stdOutWriter.WriteLine(line);
Console.WriteLine(line);
}
}
//From your application set the Console to write to your RichTextkBox
//object:
Console.SetOut(new RichTextBoxWriter(yourRichTextBox));
//To ensure that your RichTextBox object is scrolled down when its text is
//changed add this event:
private void yourRichTextBox_TextChanged(object sender, EventArgs e)
{
yourRichTextBox.SelectionStart = yourRichTextBox.Text.Length;
yourRichTextBox.ScrollToCaret();
}
public delegate void StringArgReturningVoidDelegate(string text);
public class RichTextBoxWriter : TextWriter
{
private readonly RichTextBox _richTextBox;
public RichTextBoxWriter(RichTextBox richTexttbox)
{
_richTextBox = richTexttbox;
}
public override void Write(char value)
{
SetText(value.ToString());
}
public override void Write(string value)
{
SetText(value);
}
public override void WriteLine(char value)
{
SetText(value + Environment.NewLine);
}
public override void WriteLine(string value)
{
SetText(value + Environment.NewLine);
}
public override Encoding Encoding => Encoding.ASCII;
//Write to your UI object in thread safe way:
private void SetText(string text)
{
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (_richTextBox.InvokeRequired)
{
var d = new StringArgReturningVoidDelegate(SetText);
_richTextBox.Invoke(d, text);
}
else
{
_richTextBox.Text += text;
}
}
}
Building on Chaz's answer, in .NET 5 there is a breaking change, so two modifications are required in the project file, i.e. changing OutputType and adding DisableWinExeOutputInference. Example:
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0-windows10.0.17763.0</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>
<DisableWinExeOutputInference>true</DisableWinExeOutputInference>
<Platforms>AnyCPU;x64;x86</Platforms>
</PropertyGroup>
using System;
using System.Runtime.InteropServices;
namespace SomeProject
{
class GuiRedirect
{
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool AttachConsole(int dwProcessId);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr GetStdHandle(StandardHandle nStdHandle);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool SetStdHandle(StandardHandle nStdHandle, IntPtr handle);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern FileType GetFileType(IntPtr handle);
private enum StandardHandle : uint
{
Input = unchecked((uint)-10),
Output = unchecked((uint)-11),
Error = unchecked((uint)-12)
}
private enum FileType : uint
{
Unknown = 0x0000,
Disk = 0x0001,
Char = 0x0002,
Pipe = 0x0003
}
private static bool IsRedirected(IntPtr handle)
{
FileType fileType = GetFileType(handle);
return (fileType == FileType.Disk) || (fileType == FileType.Pipe);
}
public static void Redirect()
{
if (IsRedirected(GetStdHandle(StandardHandle.Output)))
{
var initialiseOut = Console.Out;
}
bool errorRedirected = IsRedirected(GetStdHandle(StandardHandle.Error));
if (errorRedirected)
{
var initialiseError = Console.Error;
}
AttachConsole(-1);
if (!errorRedirected)
SetStdHandle(StandardHandle.Error, GetStdHandle(StandardHandle.Output));
}
}
Setting the output type as Console in the project properties will give you a Console application along with the form you created.
if what you want is simple debug output the following works for me. I am using VS 2022 programming in C#
add "using System.Diagnostics"
then
Debug.WriteLine("*****");
Debug.WriteLine(...);
Debug.WriteLine("");
THe output appears in the debug console of VS2022. There is a lot of stuff there so I use the Debug.WriteLine("*****") and Debug.WriteLine("") to help me find my output. You can also clear the debug output after start up.
I am still working but running under VS there is no output when running wo debugging
Why not just leave it as a Window Forms app, and create a simple form to mimic the Console. The form can be made to look just like the black-screened Console, and have it respond directly to key press.
Then, in the program.cs file, you decide whether you need to Run the main form or the ConsoleForm. For example, I use this approach to capture the command line arguments in the program.cs file. I create the ConsoleForm, initially hide it, then pass the command line strings to an AddCommand function in it, which displays the allowed commands. Finally, if the user gave the -h or -? command, I call the .Show on the ConsoleForm and when the user hits any key on it, I shut down the program. If the user doesn't give the -? command, I close the hidden ConsoleForm and Run the main form.
You can any time switch between type of applications, to console or windows. So, you will not write special logic to see the stdout. Also, when running application in debugger, you will see all the stdout in output window. You might also just add a breakpoint, and in breakpoint properties change "When Hit...", you can output any messages, and variables. Also you can check/uncheck "Continue execution", and your breakpoint will become square shaped. So, the breakpoint messages without changhing anything in the application in the debug output window.

If Windows explorer is open at a specific path, do not create a new instance

I am using the following code so that when the user clicks on a button, an instance of Windows Explorer is opened at a specific path. But this causes a new instance of the Explorer to be opened.
I want to change it so that, if Explorer is already open in the same path, the program does not create a new process and instead bring the open instance to front.
private void button_Click(object sender, EventArgs e)
{
if (Directory.Exists(myPath))
Process filesFolder = Process.Start("explorer.exe", Conf.FilesLocation);
}
You can use the "open" verb, which will open directories in explorer and re-use an an existing explorer.exe if you pass it a directory that it already has open:
So, assuming Conf.FilesLocation is a directory:
var proc = new ProcessStartInfo();
proc.FileName = Conf.FilesLocation;
proc.Verb = "open";
proc.WindowStyle = ProcessWindowStyle.Hidden;
Process.Start(proc );
What I have found to work as well, is to use the file:// protocol, when you do this Windows seems to give focus to the folder if it is already open, rather than opening another window
Process.Start("file://" + Conf.FilesLocation);
Although #nos's answer works well, mostly (sometimes, in a not deterministically way it creates another explorer windows even though it may already exists), I became unsatisfied with the time spent by the Process.Start(proc) to open an existing window, sometimes 2 to 4 seconds.
So, adapting some VB.NET code I achieve a very fast way of reusing a existing explorer window that is pointing to a desired folder:
First, add COM references:
using Shell32;//Shell32.dll for ShellFolderView
using SHDocVw;//Microsoft Internet Controls for IShellWindows
[DllImport("user32.dll")]
public static extern int ShowWindow(IntPtr Hwnd, int iCmdShow);
[DllImport("user32.dll")]
public static extern bool IsIconic(IntPtr Hwnd);
public static bool ShowInExplorer(string folderName)
{
var SW_RESTORE = 9;
var exShell = (IShellDispatch2)Activator.CreateInstance(
Type.GetTypeFromProgID("Shell.Application"));
foreach (ShellBrowserWindow w in (IShellWindows) exShell.Windows())
{
if (w.Document is ShellFolderView)
{
var expPath = w.Document.FocusedItem.Path;
if (!Directory.Exists(Path.GetDirectoryName(expPath)) ||
Path.GetDirectoryName(expPath) != folderName) continue;
if (IsIconic(new IntPtr(w.HWND)))
{
w.Visible = false;
w.Visible = true;
ShowWindow(new IntPtr(w.HWND),SW_RESTORE);
break;
}
else
{
w.Visible = false;
w.Visible = true;
break;
}
}
}
}
Although we are interested in ShellFolderView objects, and foreach (ShellBrowserWindow w in (ShellFolderView) exShell.Windows()) was more logical, unfortunately ShellFolderView does not implement IEnumerable, so, no foreach :(
Anyway, these is a very fast (200 ms) way of select and blink the correct already opened explorer window.
For me, none of the solutions here work in Win10. For Marcelo's solution exShell.Windows() is always empty, Josh's solution doesn't work for directories, you'll get file does not exist error, and nos' solution throws access denied error.
So here's my working solution:
[DllImport("user32.dll")]
private static extern bool SetForegroundWindow(IntPtr Hwnd);
[DllImport("user32.dll")]
private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
public static void ShowInExplorer(string filePath) {
IntPtr hWnd = FindWindow("CabinetWClass", Path.GetDirectoryName(filePath));
if(hWnd != IntPtr.Zero) {
SetForegroundWindow(hWnd);
} else {
Process.Start("explorer.exe", Path.GetDirectoryName(filePath));
}
}
Windows Explorer has window class CabinetWClass and sets title to the browsed directory.
So the above code checks to see if an explorer window with a specific directory exists.
If it does exist, the window is brought to the front, otherwise a new instance of explorer is started with the specified directory. Do note that you need to specifically start explorer.exe with the path given as argument, otherwise it'll throw access denied error.

Open file in the currently running instance of my program?

An Example:
If you have Visual Studio ( 2010 ) open and running, and then double click a misc *.cs file on your PC desktop, the file will open in the current running instance of Visual Studio, instead of opening another instance of VS.
How can I get my own C# program to mimic this behavior ?
In other words, if I have a file type such as *.myfile associated with my program, and the user double-clicks the *.myfile in Windows Explorer, and.... my program is already running..... it should open the file without Windows starting another instance of my program. If the program was not running, then Windows can start an instance normally.
Note that multiple instances of my program are allowed - same as Visual Studio.
Any suggestions would be appreciated !!
If you look at what is registered for for .cs file in registry you will see that it is not the Visual Studio. For Express edition e.g. the registered application is 'VCSExpress.exe' and the studio is running in in 'WDExpress.exe'. In advanced versions I think the studio runs as 'devenv.exe'. The interesting point is that there are two applications: your UI application and a kind of launcher application. I don't know how VS does it, but I could imagine this way: launcher communicates with UI by any kind of interprocess communication e.g. named pipes. (See here) Maybe try this:
Launcher application (your file extension is registered with it) tries to open a pipe to UI application as client.
If it fails, it starts a new instance of UI application and passes file name as parameter. UI application start server side of named pipe
If pipe is opened successful i.e. there is already running a UI instance, launcher sends file name to existing UI process via pipe.
Launcher exits after passing the job to UI
I made some project-template which implements this stuff, using windows-messaging.
The template is huge and contains some other stuff (like localization, updates, formclosing, clipboard, and an interface for documents, this way the actions in the MDI can be easily forwarded to the MDI-children).
If you want to view the template, try this link (or this link)
Some of the code:
Win32.cs:
public partial class Win32
{
//public const int WM_CLOSE = 16;
//public const int BN_CLICKED = 245;
public const int WM_COPYDATA = 0x004A;
public struct CopyDataStruct : IDisposable
{
public IntPtr dwData;
public int cbData;
public IntPtr lpData;
public void Dispose()
{
if (this.lpData != IntPtr.Zero)
{
LocalFree(this.lpData);
this.lpData = IntPtr.Zero;
}
}
}
[DllImport("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, ref CopyDataStruct lParam);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr LocalAlloc(int flag, int size);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr LocalFree(IntPtr p);
}
Program.cs:
static class Program
{
static Mutex mutex = new Mutex(true, guid());
static string guid()
{
// http://stackoverflow.com/questions/502303/how-do-i-programmatically-get-the-guid-of-an-application-in-net2-0
Assembly assembly = Assembly.GetExecutingAssembly();
var attribute = (GuidAttribute)assembly.GetCustomAttributes(typeof(GuidAttribute), true)[0];
return attribute.Value;
}
static int MainWindowHandle
{
get
{
return Settings.Default.hwnd;
}
set
{
Settings sett = Settings.Default;
sett.hwnd = value;
sett.Save();
}
}
public static string GetFileName()
{
ActivationArguments a = AppDomain.CurrentDomain.SetupInformation.ActivationArguments;
// aangeklikt bestand achterhalen
string[] args = a == null ? null : a.ActivationData;
return args == null ? "" : args[0];
}
[STAThread]
static void Main()
{
if (mutex.WaitOne(TimeSpan.Zero, true))
{
#region standaard
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
#endregion
#region Culture instellen
string cult = CultureInfo.CurrentCulture.Name;
Thread.CurrentThread.CurrentUICulture = new CultureInfo(cult);
Thread.CurrentThread.CurrentCulture = new CultureInfo(cult);
#endregion
MainForm frm = new MainForm();
MainWindowHandle = (int)frm.Handle;
Application.Run(frm);
MainWindowHandle = 0;
mutex.ReleaseMutex();
}
else
{
int hwnd = 0;
while (hwnd == 0)
{
Thread.Sleep(600);
hwnd = MainWindowHandle;
}
if (hwnd != 0)
{
Win32.CopyDataStruct cds = new Win32.CopyDataStruct();
try
{
string data = GetFileName();
cds.cbData = (data.Length + 1) * 2; // number of bytes
cds.lpData = Win32.LocalAlloc(0x40, cds.cbData); // known local-pointer in RAM
Marshal.Copy(data.ToCharArray(), 0, cds.lpData, data.Length); // Copy data to preserved local-pointer
cds.dwData = (IntPtr)1;
Win32.SendMessage((IntPtr)hwnd, Win32.WM_COPYDATA, IntPtr.Zero, ref cds);
}
finally
{
cds.Dispose();
}
}
}
}
}
MainFrom.cs:
[PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case Win32.WM_COPYDATA:
Win32.CopyDataStruct st = (Win32.CopyDataStruct)Marshal.PtrToStructure(m.LParam, typeof(Win32.CopyDataStruct));
string strData = Marshal.PtrToStringUni(st.lpData);
OpenFile(strData);
Activate();
break;
default:
// let the base class deal with it
base.WndProc(ref m);
break;
}
}
It even works when launching up to 15 files at once.
It's been almost 20 years since I had to do something like this, but IIRC, you do something like this:
Before anything else, create a Mailslot (or any other convenient IPC tool)
If you are asked to open a document of the type that should go to an existing instance and if there are no other Mailslots, you continue on
If there IS a Mailslot, you send the Mailslot an open message with the file info and then you exit.
Write code to respond to Mailslot open messages.
If you do the steps before you create windows, it should act like what you want.

Categories

Resources