Related
it's me again!
This question is quite hard, for I'll do my best explaining it: As I mentioned in a previous question, I'm working in a scanner management on C#, using a C++ dll sent by provider. According to the API's manual, there are certain messages sent under certain conditions. In example: After Starting up the scanner, it should send the message DEVICE_CONNECTED (with a value of 0), and then change it state.
Those messages values are defined in the .dll
My issue is trying to get those messages on my C# project
I been looking for information about messages transfering, and I found out there's a WndProc that processes Windows messages, for I tried following their example:
private const int DEVICE_CONNECTED = 0;
/*Some code*/
[System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")]
protected override void WndProc(ref Message m)
{
if (m.Msg == DEVICE_CONNECTED)
listBox1.Items.Add("Connected");
base.WndProc(ref m);
}
Of course, that one failed.
Later, I been checking the API's manual, and I think I got a clue where I can get the messages from:
// This is how is defined at .dll (C++)
DWORD StartUp( HWND Handle, UINT SorterMessage )
Where "Handle" is the handle to the application's messages destination window.
So my C# import is as follows:
[DllImport(path, EntryPoint = "?StartUp##YGKPAUHWND__##I#Z")]
public static extern int StartUp(IntPtr HWMD, uint SorterMessage);
Now I got a pointer from where I could extract the messages. My question is: How?
I found this example in another forum:
[Serializable, StructLayout(LayoutKind.Sequential)]
public struct MSG
{
public IntPtr hwnd;
public int message;
public IntPtr wParam;
public IntPtr lParam;
public int time;
public int pt_x;
public int pt_y;
};
[DllImport("user32.dll", CharSet = CharSet.Ansi)]
public static extern bool GetMessage([In, Out] ref MSG msg, IntPtr hWnd, int uMsgFilterMin, int uMsgFilterMax);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr DispatchMessage([In] ref MSG msg);
MSG msg = new MSG();
while (GetMessage(ref msg, IntPtr.Zero, 0, 0))
DispatchMessage(ref msg);
I tried to use it, as follows:
// Added a constructor inside of the struct:
public MSG(IntPtr hwndPtr)
{
hwnd = hwndPtr;
message = -1;
wParam = new IntPtr();
lParam = new IntPtr();
time = 0;
pt_x = 0;
pt_y = 0;
}
// Left the dll imports like in their example (although I fixed the path)
// Calling the method in my main
int ID, st;
ID = Class1.StartUp(hwnd, 10); // Just used 10 like in the API's manual
Console.WriteLine("Turning on device");
MSG msg = new MSG(hwnd);
while(Class1.GetMessage(ref msg, IntPtr.Zero, 0, 0))
Class1.DispatchMessage(ref msg);
Console.WriteLine(msg.message);
do { Class1.GetState(ID, out st); }
while (st != (int) DevStates.chgParams);
Console.WriteLine("Device on");
What I expect? After printing "Turning on device" I shoud get the message (because during start up, and according to manual, it sends a message before change the state), and then the "Device on" string.
What do I get? Just after printing "Turning on device" program does nothing but blink the cursor (and of course, the "Device on" string never shows up). Looks like it's waiting for any message. Tried placing the messages call in different places and the behaviour is the same.
Any advices? Thanks in advance.
Solved it (finally)
This is how I did it:
Used windows forms, since it has the class "Message"
Imported the .dll I was working on to make stuff easier, placed
all methods in a "ScanMgr" class.
using ...
using APIcsharp;
class ScanMgr
{
int ID = 0;
public string startUp(IntPtr hwmd, uint wmApp)
{
int state;
ID = NativeAPI.StartUp(hwmd, wmApp);
if(ID != 0)
{
do { NativeAPI.GetState(ID, out state); }
while(state == (int)(DevStates.StartingUp)); // DevStates is a enum
return "Device on";
}
return "Error turning on";
}
/* Other stuff to do */
}
Then, defined an override method for the messages
public partial class Form1 : Form
{
const uint wm_channel = 0x8000 + 1;
ScanMgr scanner = new ScanMgr();
public Form1()
{ InitializeComponent(); }
private void StartBtn_Click(object sender, EventArgs e)
{ log.Items.Add(scanner.startUp(this.Handle, wm_channel)); }
/* Other stuff yadda yadda */
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if(m.Msg == wm_channel)
{ /* To do stuff with m.WParam and m.LParam */ }
}
}
Being new to .net, i am not able to get how to close the show dialog modal window once its open. As i have learnt we cannot close that automatically until explicitly its to be called. Here is my code:
//process - notepad.exe
Process p = Process.Start(process);
frm_Save fsave = new frm_Save();
Using (p)
{
do
{
if(!p.HasExited)
{
p.Refresh();
fsave.ShowDialog(); // it just stuck here and doesn't go to next line
}
}
while(!p.WaitForExit(1000));
}
//frm_Save.cs
public frm_Save()
{
InitializeComponent();
}
private void frm_Save_Load(...,....)
{
//
}
private void frm_Save_Shown(...,...)
{
Sleep(100);
Forms.Application.DoEvents();
Close();
}
As you have explained, you want to show a dialog with icon that you are saving a video in the background and prevent the user to do something. One regular way to do that is with a BackgroundWorker in your Dialog. Here is the code how it would work:
public class frm_Save : Form
{
public FrmProgress(List<TransferOptions> transferOptions)
{
InitializeComponent();
BackgroundWorker BgrdWorker = new System.ComponentModel.BackgroundWorker();
this.BgrdWorker.DoWork += new System.ComponentModel.DoWorkEventHandler(this.BgrdWorker_DoWork);
this.BgrdWorker.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(this.BgrdWorker_RunWorkerCompleted);
}
private void FrmProgress_Load(object sender, EventArgs e)
{
// Show image and message...
}
private void BgrdWorker_DoWork(object sender, DoWorkEventArgs e)
{
// Call your video Process start Function
// after that
var stopWatch = new StopWatch();
stopWatch.Start()
while (true)
{
if (stopWatch.ElapsedMilliseconds >1000 || videoProcessHasReturnedSuccessfully)
{
break
}
}
}
private void BgrdWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// inform the user the video processing is finished
this.Close();
}
}
Then in the main form of your console app when you want to start the whole process, you call:
frm_Save fsave = new frm_Save();
fsave.ShowDialog()
Tip: You can also use BgrdWorker.ProgressChanged to show the progress of background task to the user by communicating between the background task and the UI if necessary, but you have not requested that in your question.
This approach may work for you, note the use of TopMost.
using System.Runtime.InteropServices;
private static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
private const UInt32 SWP_NOSIZE = 0x0001;
private const UInt32 SWP_NOMOVE = 0x0002;
private const UInt32 TOPMOST_FLAGS = SWP_NOMOVE | SWP_NOSIZE;
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter,
int X, int Y, int cx, int cy, uint uFlags);
....
frm_Save fsave = new frm_Save();
fsave.Show();
SetWindowPos(frm_Save.Handle, HWND_TOPMOST, 0, 0, 0, 0, TOPMOST_FLAGS);
Process p = Process.Start(process);
using (p)
{
while (!p.WaitForExit(1000))
{
fsave.Refresh();
}
}
fsave.Close();
I am trying to adapt the code posted in this question:
https://stackoverflow.com/a/44059700
to allow me to embed a Unity3D app inside a WPF app.
This is my slightly edited version:
namespace WPFWithUnity
{
public partial class Page1 : Page
{
[DllImport("User32.dll")]
static extern bool MoveWindow(IntPtr handle, int x, int y, int width, int height, bool redraw);
internal delegate int WindowEnumProc(IntPtr hwnd, IntPtr lparam);
[DllImport("user32.dll")]
internal static extern bool EnumChildWindows(IntPtr hwnd, WindowEnumProc func, IntPtr lParam);
[DllImport("user32.dll")]
static extern int SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
private Process process;
private IntPtr unityHWND = IntPtr.Zero;
private const int WM_ACTIVATE = 0x0006;
private readonly IntPtr WA_ACTIVE = new IntPtr(1);
private readonly IntPtr WA_INACTIVE = new IntPtr(0);
Frame p = MainWindow.Instance.floatingFrame;
bool initialized = false;
public Page1()
{
InitializeComponent();
MainWindow.Instance.MainWindowClosing += Application_Exit;
System.Windows.Threading.DispatcherTimer dispatcherTimer = new System.Windows.Threading.DispatcherTimer();
dispatcherTimer.Tick += attemptInit;
dispatcherTimer.Interval = new TimeSpan(0, 0, 1);
dispatcherTimer.Start();
}
void attemptInit(object sender, EventArgs e) {
if (initialized)
return;
HwndSource source = (HwndSource)HwndSource.FromVisual(p);
Console.WriteLine("attempting to get handle...");
if (source == null) {
Console.WriteLine("Failed to get handle source");
return;
}
IntPtr hWnd = source.Handle;
try
{
process = new Process();
process.StartInfo.FileName = "Child.exe";
process.StartInfo.Arguments = "-parentHWND " + hWnd.ToInt32() + " " + Environment.CommandLine;
process.StartInfo.UseShellExecute = true;
process.StartInfo.CreateNoWindow = true;
process.Start();
process.WaitForInputIdle();
// Doesn't work for some reason ?!
//unityHWND = process.MainWindowHandle;
EnumChildWindows(hWnd, WindowEnum, IntPtr.Zero);
//unityHWNDLabel.Text = "Unity HWND: 0x" + unityHWND.ToString("X8");
Console.WriteLine("Unity HWND: 0x" + unityHWND.ToString("X8"));
panel1_Resize(this, EventArgs.Empty);
initialized = true;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message + ".\nCheck if Container.exe is placed next to UnityGame.exe.");
}
}
private void ActivateUnityWindow()
{
SendMessage(unityHWND, WM_ACTIVATE, WA_ACTIVE, IntPtr.Zero);
}
private void DeactivateUnityWindow()
{
SendMessage(unityHWND, WM_ACTIVATE, WA_INACTIVE, IntPtr.Zero);
}
private int WindowEnum(IntPtr hwnd, IntPtr lparam)
{
unityHWND = hwnd;
ActivateUnityWindow();
return 0;
}
private void panel1_Resize(object sender, EventArgs e)
{
MoveWindow(unityHWND, 0, 0, (int)p.Width, (int)p.Height, true);
Console.WriteLine("RESIZED UNITY WINDOW TO: " + (int)p.Width + "x" + (int)p.Height);
ActivateUnityWindow();
}
// Close Unity application
private void Application_Exit(object sender, EventArgs e)
{
try
{
process.CloseMainWindow();
Thread.Sleep(1000);
while (!process.HasExited)
process.Kill();
}
catch (Exception)
{
}
}
private void Form1_Activated(object sender, EventArgs e)
{
ActivateUnityWindow();
}
private void Form1_Deactivate(object sender, EventArgs e)
{
DeactivateUnityWindow();
}
}
}
And here is the relevant part of the XAML:
<Frame Name="floatingFrame" Grid.Row="15" Grid.RowSpan="5" Grid.Column="0" Grid.ColumnSpan="2" Width="640" Height="480" Margin="100,0,0,0" Panel.ZIndex="100" Source="Page1.xaml"/>
Really, the only difference is that I'm trying to use a WPF Page inside a Frame instead of a WinForms panel (trying to avoid WinForms). The embedded Unity app starts up fine...except that it takes up the whole window (i.e. you can't see any of the WPF controls anymore).
So, the question:
How do I get the Unity app to only stay inside the WPF page (which is inside a Frame)?
enter image description here
(The Y of this XY problem would be that I'm just trying to create a 3D graphics display of something inside a WPF app.)
Thanks in advance for any help.
Use a WindowsFormsHost or HwndHost control in your WPF. The hwnd is in the host control's Handle property. So you can change this line to put Unity in just the host control.
process.StartInfo.Arguments = "-parentHWND " + hwndHost.Handle.ToInt32() + " " + Environment.CommandLine;
And remove the code that gets the hwnd for the floating frame
HwndSource source = (HwndSource)HwndSource.FromVisual(p);
The problem with the above solution is that getting focus on the Unity-exe seems to be not possible. So yes, I was able to load the exe on a certain cell of a certain user control of a certain WPF application, but could not click on anything in the Unity-frame.
I spent the whole day searching for a solution and can now come with a solution, that is in my opinion cleaner and also solves the problem with the focus.
I describe what you can do in steps:
Documentation of Unity : In here, it is explained how you can embed a Unity-exe in a Winforms control. There is even a .zip-file "EmbeddedWindow.zip" where you can download example code. Copy the essential files out of the Container .
Form1.cs contains exactly the code as in this question.
public partial class Form1: Form
{
[DllImport("User32.dll")]
private static extern bool MoveWindow(IntPtr handle, int x, int y, int width, int height, bool redraw);
internal delegate int WindowEnumProc(IntPtr hwnd, IntPtr lparam);
[DllImport("user32.dll")]
internal static extern bool EnumChildWindows(IntPtr hwnd, WindowEnumProc func, IntPtr lParam);
[DllImport("user32.dll")]
private static extern int SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
private Process process;
private IntPtr unityHWND = IntPtr.Zero;
private const int WM_ACTIVATE = 0x0006;
private readonly IntPtr WA_ACTIVE = new IntPtr(1);
private readonly IntPtr WA_INACTIVE = new IntPtr(0);
public Form1()
{
InitializeComponent();
TopLevel = false;
try
{
process = new Process();
process.StartInfo.FileName = "[INSERT_FILE_NAME_OF_YOUR_EXE].exe";
process.StartInfo.Arguments = "-parentHWND " + panel1.Handle.ToInt32() + " " + Environment.CommandLine;
process.StartInfo.UseShellExecute = true;
process.StartInfo.CreateNoWindow = true;
process.Start();
process.WaitForInputIdle();
// Doesn't work for some reason ?!
//unityHWND = process.MainWindowHandle;
EnumChildWindows(panel1.Handle, WindowEnum, IntPtr.Zero);
unityHWNDLabel.Text = "Unity HWND: 0x" + unityHWND.ToString("X8");
}
catch (Exception ex)
{
MessageBox.Show(ex.Message + ".\nCheck if Container.exe is placed next to Child.exe.");
}
}
private void ActivateUnityWindow()
{
SendMessage(unityHWND, WM_ACTIVATE, WA_ACTIVE, IntPtr.Zero);
}
private void DeactivateUnityWindow()
{
SendMessage(unityHWND, WM_ACTIVATE, WA_INACTIVE, IntPtr.Zero);
}
private int WindowEnum(IntPtr hwnd, IntPtr lparam)
{
unityHWND = hwnd;
ActivateUnityWindow();
return 0;
}
private void panel1_Resize(object sender, EventArgs e)
{
MoveWindow(unityHWND, 0, 0, panel1.Width, panel1.Height, true);
ActivateUnityWindow();
}
// Close Unity application
private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
try
{
process.CloseMainWindow();
Thread.Sleep(1000);
while (process.HasExited == false)
process.Kill();
}
catch (Exception)
{
}
}
private void Form1_Activated(object sender, EventArgs e)
{
ActivateUnityWindow();
}
private void Form1_Deactivate(object sender, EventArgs e)
{
DeactivateUnityWindow();
}
}
Do not forget to have a look in "Form1.cs", it is possible that the exe you want to update is not "Child.exe", so if it is another one, just edit the string in the code. process.StartInfo.FileName = "[INSERT_FILE_NAME_OF_YOUR_EXE].exe"; Also check "SelectablePanel.cs", where the setting of Selectable to true is essential. This SelectablePanel is used in the Form1.Designer.cs .
The left panel1 of the SplitContainer is a SelectablePanel instead of a Panel !
class SelectablePanel : Panel
{
public SelectablePanel()
{
this.SetStyle(ControlStyles.Selectable, true);
this.TabStop = true;
}
}
Please also note that in the constructor of the Winforms-control, I needed to set Toplevel = false; . This was not mentioned in the example of Unity but is needed to avoid an exception when you embed it in your WPF application.
Go to your WPF Application and make a User Control that will contain your Winforms-Control. Make a control similar to what is done in this link . In this example, you have a Grid named Grid_To_Embed_Winforms_Control_In and a small piece of code-behind, like the underlying code.
public partial class WPF_User_Control: UserControl
{
public bool Already_Loaded = false;
public WPF_User_Control()
{
InitializeComponent();
}
private void On_Load(object sender, RoutedEventArgs e)
{
if (!Already_Loaded)
{
// Create the interop host control.
var host =
new WindowsFormsHost();
// Embed the Winforms Control
host.Child = new Embed_Unity_Exe_Winforms_Control();
// Add the interop host control to the Grid
// control's collection of child controls.
Grid_To_Embed_Winforms_Control_In.Children.Add(host);
Already_Loaded = true;
}
}
}
Do not forget to add using System.Windows.Forms.Integration; on top. Please note that I added the boolean Already Loaded to make sure that when the viewport changes (you want to view another page of your WPF application) the process is not started again. In my complete solution, I make use of Microsoft Dependency Injection and this control is in a viewmodel which is added as a singleton. In this way, I only start the process once.
So this is it, this worked for me.
What is better than other solutions I saw while googling:
The resizing is working better and done "automatically", I did not need to invoke a resize-method myself (except for the one in Form1.cs) .
I can also have control and focus over the Unity.
PS: If you want to make it look nicer and more "embedded", you can do the following things with the Winform-control:
Select splitcontainer1 and go to Properties
Set BorderStyle to 'None'
Set Panel2Collapsed to 'True'
I want my game to have normal text input, but it seems very unpleasant to do using pure XNA.
Earlier I found this piece of code which lets me use MessageBox all around my game, safely pausing its execution and showing a message:
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern uint MessageBox(IntPtr hWnd, String text, String caption, uint type);
Is there something similar to this which could add InputBox functionality to my game, preferrably without interrupting (pausing) the game?
Ah, the text input - I have very recent experience with this.
Problem
Usually, Keyboard.GetKeyboardState() sucks at getting text input, and that is for many reasons, some of them being:
You have to code a HUGE switch to detect what key had been pressed
You have to manually detect whether to capitalize letters (Shift or CapsLock)
You have to decipher those OemPeriod-like keys (as in test) to see where they actually are, and map them to specific values.
There is no way to detect/use keyboard layout or keyboard language
You have to implement own mechanism for timing repetition in case of key being held down
Second part of the problem is detecting which of your TextBoxes (or UI controls in general) is currently receiving this input, since you don't want all of your boxes to receive text as you type.
Third, you need to draw the TextBox in specified bounds, and you could also want to draw the caret (the blinking vertical position indicator), the current selection (if you want to go so far to implement it), the texture that represents the box, and the textures for highlighted (with mouse) or selected (has focus) state.
Fourth, you have to manually implement copy-paste features.
Quick note
You probably don't need all these features, as I didn't need them. You'd just want simple input, and detection for keys such as enter or tab, as well as mouse click. Maybe also paste.
Solution
The thing is (at least when we talk about Windows, not X-Box or WP7), the operating system already has the mechanisms necessary to implement everything you need from your keyboard:
Gives characters based on current keyboard layout and language
Automatically handles repeating input (in case of key being held down)
Automatically capitalizes and provides special characters
Solution I use for getting keyboard input, I've copied off this Gamedev.net forum post. It is the code below, and you just need to copy-paste it into a .cs file which you'll never have to open again.
It is used for receiving localized input from your keyboard, and all you need to do is initialize it in your Game.Initialize() override method (by using Game.Window), and hook up to the events to receive input anywhere you'd like.
You need to add PresentationCore (PresentationCore.dll) to your references in order to use this code (needed for System.Windows.Input namespace). This works for .NET 4.0 and for .NET 4.0 Client Profile.
EventInput
using System;
using System.Runtime.InteropServices;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using System.Text;
using System.Windows.Input;
namespace EventInput
{
public class KeyboardLayout
{
const uint KLF_ACTIVATE = 1; //activate the layout
const int KL_NAMELENGTH = 9; // length of the keyboard buffer
const string LANG_EN_US = "00000409";
const string LANG_HE_IL = "0001101A";
[DllImport("user32.dll")]
private static extern long LoadKeyboardLayout(
string pwszKLID, // input locale identifier
uint Flags // input locale identifier options
);
[DllImport("user32.dll")]
private static extern long GetKeyboardLayoutName(
System.Text.StringBuilder pwszKLID //[out] string that receives the name of the locale identifier
);
public static string getName()
{
System.Text.StringBuilder name = new System.Text.StringBuilder(KL_NAMELENGTH);
GetKeyboardLayoutName(name);
return name.ToString();
}
}
public class CharacterEventArgs : EventArgs
{
private readonly char character;
private readonly int lParam;
public CharacterEventArgs(char character, int lParam)
{
this.character = character;
this.lParam = lParam;
}
public char Character
{
get { return character; }
}
public int Param
{
get { return lParam; }
}
public int RepeatCount
{
get { return lParam & 0xffff; }
}
public bool ExtendedKey
{
get { return (lParam & (1 << 24)) > 0; }
}
public bool AltPressed
{
get { return (lParam & (1 << 29)) > 0; }
}
public bool PreviousState
{
get { return (lParam & (1 << 30)) > 0; }
}
public bool TransitionState
{
get { return (lParam & (1 << 31)) > 0; }
}
}
public class KeyEventArgs : EventArgs
{
private Keys keyCode;
public KeyEventArgs(Keys keyCode)
{
this.keyCode = keyCode;
}
public Keys KeyCode
{
get { return keyCode; }
}
}
public delegate void CharEnteredHandler(object sender, CharacterEventArgs e);
public delegate void KeyEventHandler(object sender, KeyEventArgs e);
public static class EventInput
{
/// <summary>
/// Event raised when a character has been entered.
/// </summary>
public static event CharEnteredHandler CharEntered;
/// <summary>
/// Event raised when a key has been pressed down. May fire multiple times due to keyboard repeat.
/// </summary>
public static event KeyEventHandler KeyDown;
/// <summary>
/// Event raised when a key has been released.
/// </summary>
public static event KeyEventHandler KeyUp;
delegate IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
static bool initialized;
static IntPtr prevWndProc;
static WndProc hookProcDelegate;
static IntPtr hIMC;
//various Win32 constants that we need
const int GWL_WNDPROC = -4;
const int WM_KEYDOWN = 0x100;
const int WM_KEYUP = 0x101;
const int WM_CHAR = 0x102;
const int WM_IME_SETCONTEXT = 0x0281;
const int WM_INPUTLANGCHANGE = 0x51;
const int WM_GETDLGCODE = 0x87;
const int WM_IME_COMPOSITION = 0x10f;
const int DLGC_WANTALLKEYS = 4;
//Win32 functions that we're using
[DllImport("Imm32.dll", CharSet = CharSet.Unicode)]
static extern IntPtr ImmGetContext(IntPtr hWnd);
[DllImport("Imm32.dll", CharSet = CharSet.Unicode)]
static extern IntPtr ImmAssociateContext(IntPtr hWnd, IntPtr hIMC);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
/// <summary>
/// Initialize the TextInput with the given GameWindow.
/// </summary>
/// <param name="window">The XNA window to which text input should be linked.</param>
public static void Initialize(GameWindow window)
{
if (initialized)
throw new InvalidOperationException("TextInput.Initialize can only be called once!");
hookProcDelegate = new WndProc(HookProc);
prevWndProc = (IntPtr)SetWindowLong(window.Handle, GWL_WNDPROC,
(int)Marshal.GetFunctionPointerForDelegate(hookProcDelegate));
hIMC = ImmGetContext(window.Handle);
initialized = true;
}
static IntPtr HookProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
{
IntPtr returnCode = CallWindowProc(prevWndProc, hWnd, msg, wParam, lParam);
switch (msg)
{
case WM_GETDLGCODE:
returnCode = (IntPtr)(returnCode.ToInt32() | DLGC_WANTALLKEYS);
break;
case WM_KEYDOWN:
if (KeyDown != null)
KeyDown(null, new KeyEventArgs((Keys)wParam));
break;
case WM_KEYUP:
if (KeyUp != null)
KeyUp(null, new KeyEventArgs((Keys)wParam));
break;
case WM_CHAR:
if (CharEntered != null)
CharEntered(null, new CharacterEventArgs((char)wParam, lParam.ToInt32()));
break;
case WM_IME_SETCONTEXT:
if (wParam.ToInt32() == 1)
ImmAssociateContext(hWnd, hIMC);
break;
case WM_INPUTLANGCHANGE:
ImmAssociateContext(hWnd, hIMC);
returnCode = (IntPtr)1;
break;
}
return returnCode;
}
}
}
Now you could already use this as it is (by subscribing to EventInput.CharEntered event), and use logic to detect where to send your input.
KeyboardDispatcher, IKeyboardSubscriber
What I did was create a class KeyboardDispatcher, which handles the dispatching of keyboard input by way of having a property of type IKeyboardSubscriber to which it sends received input. The idea is that you set this property to that UI control that you want to receive input.
Definitions are as follows:
public interface IKeyboardSubscriber
{
void RecieveTextInput(char inputChar);
void RecieveTextInput(string text);
void RecieveCommandInput(char command);
void RecieveSpecialInput(Keys key);
bool Selected { get; set; } //or Focused
}
public class KeyboardDispatcher
{
public KeyboardDispatcher(GameWindow window)
{
EventInput.EventInput.Initialize(window);
EventInput.EventInput.CharEntered += new EventInput.CharEnteredHandler(EventInput_CharEntered);
EventInput.EventInput.KeyDown += new EventInput.KeyEventHandler(EventInput_KeyDown);
}
void EventInput_KeyDown(object sender, EventInput.KeyEventArgs e)
{
if (_subscriber == null)
return;
_subscriber.RecieveSpecialInput(e.KeyCode);
}
void EventInput_CharEntered(object sender, EventInput.CharacterEventArgs e)
{
if (_subscriber == null)
return;
if (char.IsControl(e.Character))
{
//ctrl-v
if (e.Character == 0x16)
{
//XNA runs in Multiple Thread Apartment state, which cannot recieve clipboard
Thread thread = new Thread(PasteThread);
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
thread.Join();
_subscriber.RecieveTextInput(_pasteResult);
}
else
{
_subscriber.RecieveCommandInput(e.Character);
}
}
else
{
_subscriber.RecieveTextInput(e.Character);
}
}
IKeyboardSubscriber _subscriber;
public IKeyboardSubscriber Subscriber
{
get { return _subscriber; }
set
{
if (_subscriber != null)
_subscriber.Selected = false;
_subscriber = value;
if(value!=null)
value.Selected = true;
}
}
//Thread has to be in Single Thread Apartment state in order to receive clipboard
string _pasteResult = "";
[STAThread]
void PasteThread()
{
if (Clipboard.ContainsText())
{
_pasteResult = Clipboard.GetText();
}
else
{
_pasteResult = "";
}
}
}
Usage is fairly simple, instantiate KeyboardDispatcher, i.e. in Game.Initialize() and keep a reference to it (so you can switch between selected [focused] controls), and pass it a class that uses the IKeyboardSubscriber interface, such as your TextBox.
TextBox
Next up is your actual control. Now I've originally programed a fairly complicated box that used render targets to render the text to a texture so I could move it around (if text was larger than the box), but then after a lot of pain i scrapped it and made a really simple version. Feel free to improve it!
public delegate void TextBoxEvent(TextBox sender);
public class TextBox : IKeyboardSubscriber
{
Texture2D _textBoxTexture;
Texture2D _caretTexture;
SpriteFont _font;
public int X { get; set; }
public int Y { get; set; }
public int Width { get; set; }
public int Height { get; private set; }
public bool Highlighted { get; set; }
public bool PasswordBox { get; set; }
public event TextBoxEvent Clicked;
string _text = "";
public String Text
{
get
{
return _text;
}
set
{
_text = value;
if (_text == null)
_text = "";
if (_text != "")
{
//if you attempt to display a character that is not in your font
//you will get an exception, so we filter the characters
//remove the filtering if you're using a default character in your spritefont
String filtered = "";
foreach (char c in value)
{
if (_font.Characters.Contains(c))
filtered += c;
}
_text = filtered;
while (_font.MeasureString(_text).X > Width)
{
//to ensure that text cannot be larger than the box
_text = _text.Substring(0, _text.Length - 1);
}
}
}
}
public TextBox(Texture2D textBoxTexture, Texture2D caretTexture, SpriteFont font)
{
_textBoxTexture = textBoxTexture;
_caretTexture = caretTexture;
_font = font;
_previousMouse = Mouse.GetState();
}
MouseState _previousMouse;
public void Update(GameTime gameTime)
{
MouseState mouse = Mouse.GetState();
Point mousePoint = new Point(mouse.X, mouse.Y);
Rectangle position = new Rectangle(X, Y, Width, Height);
if (position.Contains(mousePoint))
{
Highlighted = true;
if (_previousMouse.LeftButton == ButtonState.Released && mouse.LeftButton == ButtonState.Pressed)
{
if (Clicked != null)
Clicked(this);
}
}
else
{
Highlighted = false;
}
}
public void Draw(SpriteBatch spriteBatch, GameTime gameTime)
{
bool caretVisible = true;
if ((gameTime.TotalGameTime.TotalMilliseconds % 1000) < 500)
caretVisible = false;
else
caretVisible = true;
String toDraw = Text;
if (PasswordBox)
{
toDraw = "";
for (int i = 0; i < Text.Length; i++)
toDraw += (char) 0x2022; //bullet character (make sure you include it in the font!!!!)
}
//my texture was split vertically in 2 parts, upper was unhighlighted, lower was highlighted version of the box
spriteBatch.Draw(_textBoxTexture, new Rectangle(X, Y, Width, Height), new Rectangle(0, Highlighted ? (_textBoxTexture.Height / 2) : 0, _textBoxTexture.Width, _textBoxTexture.Height / 2), Color.White);
Vector2 size = _font.MeasureString(toDraw);
if (caretVisible && Selected)
spriteBatch.Draw(_caretTexture, new Vector2(X + (int)size.X + 2, Y + 2), Color.White); //my caret texture was a simple vertical line, 4 pixels smaller than font size.Y
//shadow first, then the actual text
spriteBatch.DrawString(_font, toDraw, new Vector2(X, Y) + Vector2.One, Color.Black);
spriteBatch.DrawString(_font, toDraw, new Vector2(X, Y), Color.White);
}
public void RecieveTextInput(char inputChar)
{
Text = Text + inputChar;
}
public void RecieveTextInput(string text)
{
Text = Text + text;
}
public void RecieveCommandInput(char command)
{
switch (command)
{
case '\b': //backspace
if (Text.Length > 0)
Text = Text.Substring(0, Text.Length - 1);
break;
case '\r': //return
if (OnEnterPressed != null)
OnEnterPressed(this);
break;
case '\t': //tab
if (OnTabPressed != null)
OnTabPressed(this);
break;
default:
break;
}
}
public void RecieveSpecialInput(Keys key)
{
}
public event TextBoxEvent OnEnterPressed;
public event TextBoxEvent OnTabPressed;
public bool Selected
{
get;
set;
}
}
When you instantiate a TextBox, don't forget to set X, Y, and Width (!!!) values on the instance (Height is auto-set by font).
The texture I used for the box was (unhighlighted has a gradient, which looks nice on a black background :) )
To display the box call the .Draw() method on the instance (in your Game.Draw() method), with spritebatch already started (SpriteBatch.Begin() called!!!). For each box you're displaying, if you want it to recieve mouse input you should call .Update() method.
When you want a specific instance to recieve keyboard input, use your KeyboardDispatcher instance to subscribe it, such as:
_keyboardDispatcher.Subscriber = _usernameTextBox;
You can use the Click, Tab and Enter events on the textbox to switch subscribers (which I recommend as it gives a really nice feel to the UI when you can tab through it, and click to select).
Unresolved issues
Ofc, I had talked about some features I had not implemented, such as the box being able to pan the text if the text was wider than the box, the ability to move the caret around (insert the text, not just append), to select and copy text, etc.
These problems you could solve with a light to medium effort, I'm sure of it, but before you do, ask yourself:
Do I really need it?
Having written such code a few times, I'd say it's not that difficult to program a basic textbox in XNA. You define a rectangle that you fill with a background color, a string that represents what the user has typed, and display the string using Spritebatch.DrawString() inside the rectangle ! Using SpriteFont.MeasureString(), you can align the text however you want, wrap text to the next line when it's off-limits, etc.
Then you look at Keyboard.GetState() each update and check which keys have been pressed. That's maybe the biggest problem, because if the user types fast, you'll miss some keystrokes - the game only updates so many times per second. The problem is widely documented on the internet, and has solutions, for example here.
Another option would be to use a pre-made XNA GUI component, such as what you get with the Nuclex framework.
Well, the simplest way would be as follow (from my point of view ;])
using TextboxInputTest.Textbox.TextInput;
private TextboxInput _inputTextBox
then I'd recommend to enable mouse (set it visible)
IsMouseVisible = true;
now ya need to initialize the textBox itself
this._inputTextBox = new TextboxInput(this, "background_box", "Arial");
this stands for the game, which is this one (doubt you'd need to change that)
background_box is the name of picture ya wanna use to be displayed (afaik, there's no default option for this)
Arial is the font ya wanna use (don't forget you have to add it to the content of the game
Set the box's position
this._inputTextBox.Position = new Vector2(100,100);
And as the final step you have to add the box to the component array
Components.Add(this._inputTextBox);
There are many features you might like to edit, for that, i'd recommend to use IntelliSense
edit: my fault, sorry, i use them so commonly i completely forgot about that ;] saying in advance, what ya see bellow is not my work
http://www.4shared.com/file/RVqzHWk0/TextboxInput.html
Hope it helped.
Regards,
Releis
So, I did search google and SO prior to asking this question. Basically I have a DLL that has a form compiled into it. The form will be used to display information to the screen. Eventually it will be asynchronous and expose a lot of customization in the dll. For now I just want it to display properly. The problem that I am having is that I use the dll by loading it in a Powershell session. So when I try to display the form and get it to come to the top and have focus, It has no problem with displaying over all the other apps, but I can't for the life of me get it to display over the Powershell window. Here is the code that I am currently using to try and get it to display. I am sure that the majority of it won't be required once I figure it out, this just represents all the things that I found via google.
CLass Blah
{
[DllImport("user32.dll", EntryPoint = "SystemParametersInfo")]
public static extern bool SystemParametersInfo(uint uiAction, uint uiParam, uint pvParam, uint fWinIni);
[DllImport("user32.dll", EntryPoint = "SetForegroundWindow")]
public static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("User32.dll", EntryPoint = "ShowWindowAsync")]
private static extern bool ShowWindowAsync(IntPtr hWnd, int cmdShow);
private const int WS_SHOWNORMAL = 1;
public void ShowMessage(string msg)
{
MessageForm msgFrm = new MessageForm();
msgFrm.lblMessage.Text = "FOO";
msgFrm.ShowDialog();
msgFrm.BringToFront();
msgFrm.TopMost = true;
msgFrm.Activate();
SystemParametersInfo((uint)0x2001, 0, 0, 0x0002 | 0x0001);
ShowWindowAsync(msgFrm.Handle, WS_SHOWNORMAL);
SetForegroundWindow(msgFrm.Handle);
SystemParametersInfo((uint)0x2001, 200000, 200000, 0x0002 | 0x0001);
}
}
As I say I'm sure that most of that is either not needed or even flat out wrong, I just wanted to show the things that I had tried. Also, as I mentioned, I plan to have this be asynchronously displayed at some point which I suspect will wind up requiring a separate thread. Would splitting the form out into it's own thread make it easier to cause it to get focus over the Powershell session?
#Joel, thanks for the info. Here is what I tried based on your suggestion:
msgFrm.ShowDialog();
msgFrm.BringToFront();
msgFrm.Focus();
Application.DoEvents();
The form still comes up under the Powershell session. I'll proceed with working out the threading. I've spawned threads before but never where the parent thread needed to talk to the child thread, so we'll see how it goes.
Thnks for all the ideas so far folks.
Ok, threading it took care of the problem. #Quarrelsome, I did try both of those. Neither (nor both together) worked. I am curious as to what is evil about using threading? I am not using Application.Run and I have yet to have a problem. I am using a mediator class that both the parent thread and the child thread have access to. In that object I am using a ReaderWriterLock to lock one property that represents the message that I want displayed on the form that the child thread creates. The parent locks the property then writes what should be displayed. The child thread locks the property and reads what it should change the label on the form to. The child has to do this on a polling interval (I default it to 500ms) which I'm not real happy about, but I could not find an event driven way to let the child thread know that the proerty had changed, so I'm stuck with polling.
I also had trouble activating and bringing a window to the foreground. Here is the code that eventually worked for me. I'm not sure if it will solve your problem.
Basically, call ShowWindow() then SetForegroundWindow().
using System.Diagnostics;
using System.Runtime.InteropServices;
// Sets the window to be foreground
[DllImport("User32")]
private static extern int SetForegroundWindow(IntPtr hwnd);
// Activate or minimize a window
[DllImportAttribute("User32.DLL")]
private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
private const int SW_SHOW = 5;
private const int SW_MINIMIZE = 6;
private const int SW_RESTORE = 9;
private void ActivateApplication(string briefAppName)
{
Process[] procList = Process.GetProcessesByName(briefAppName);
if (procList.Length > 0)
{
ShowWindow(procList[0].MainWindowHandle, SW_RESTORE);
SetForegroundWindow(procList[0].MainWindowHandle);
}
}
Here is some code that I've used on one form or another for a few years. There are a few gotchas to making a window in another app pop up. Once you have the window handle do this:
if (IsIconic(hWnd))
ShowWindowAsync(hWnd, SW_RESTORE);
ShowWindowAsync(hWnd, SW_SHOW);
SetForegroundWindow(hWnd);
// Code from Karl E. Peterson, www.mvps.org/vb/sample.htm
// Converted to Delphi by Ray Lischner
// Published in The Delphi Magazine 55, page 16
// Converted to C# by Kevin Gale
IntPtr foregroundWindow = GetForegroundWindow();
IntPtr Dummy = IntPtr.Zero;
uint foregroundThreadId = GetWindowThreadProcessId(foregroundWindow, Dummy);
uint thisThreadId = GetWindowThreadProcessId(hWnd, Dummy);
if (AttachThreadInput(thisThreadId, foregroundThreadId, true))
{
BringWindowToTop(hWnd); // IE 5.5 related hack
SetForegroundWindow(hWnd);
AttachThreadInput(thisThreadId, foregroundThreadId, false);
}
if (GetForegroundWindow() != hWnd)
{
// Code by Daniel P. Stasinski
// Converted to C# by Kevin Gale
IntPtr Timeout = IntPtr.Zero;
SystemParametersInfo(SPI_GETFOREGROUNDLOCKTIMEOUT, 0, Timeout, 0);
SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, Dummy, SPIF_SENDCHANGE);
BringWindowToTop(hWnd); // IE 5.5 related hack
SetForegroundWindow(hWnd);
SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, Timeout, SPIF_SENDCHANGE);
}
I won't post the whole unit since since it does other things that aren't relevant
but here are the constants and imports for the above code.
//Win32 API calls necesary to raise an unowned processs main window
[DllImport("user32.dll")]
private static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);
[DllImport("user32.dll")]
private static extern bool IsIconic(IntPtr hWnd);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool SystemParametersInfo(uint uiAction, uint uiParam, IntPtr pvParam, uint fWinIni);
[DllImport("user32.dll", SetLastError = true)]
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr lpdwProcessId);
[DllImport("user32.dll")]
private static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
private static extern bool AttachThreadInput(uint idAttach, uint idAttachTo, bool fAttach);
[DllImport("user32.dll")]
static extern bool BringWindowToTop(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, Int32 nMaxCount);
[DllImport("user32.dll")]
private static extern int GetWindowThreadProcessId(IntPtr hWnd, ref Int32 lpdwProcessId);
[DllImport("User32.dll")]
public static extern IntPtr GetParent(IntPtr hWnd);
private const int SW_HIDE = 0;
private const int SW_SHOWNORMAL = 1;
private const int SW_NORMAL = 1;
private const int SW_SHOWMINIMIZED = 2;
private const int SW_SHOWMAXIMIZED = 3;
private const int SW_MAXIMIZE = 3;
private const int SW_SHOWNOACTIVATE = 4;
private const int SW_SHOW = 5;
private const int SW_MINIMIZE = 6;
private const int SW_SHOWMINNOACTIVE = 7;
private const int SW_SHOWNA = 8;
private const int SW_RESTORE = 9;
private const int SW_SHOWDEFAULT = 10;
private const int SW_MAX = 10;
private const uint SPI_GETFOREGROUNDLOCKTIMEOUT = 0x2000;
private const uint SPI_SETFOREGROUNDLOCKTIMEOUT = 0x2001;
private const int SPIF_SENDCHANGE = 0x2;
Doesn't ShowDialog() have different window behavior than just Show()?
What if you tried:
msgFrm.Show();
msgFrm.BringToFront();
msgFrm.Focus();
TopMost = true;
.Activate() ?
Either of those any good?
Splitting it out into its own thread is a bit evil as it wont work properly if you don't call it with Application.Run and that will swallow up the thread. In the worst case scenario I guess you could separate it out into a different process and communicate via the disk or WCF.
The following solution should meet your requirements:
Assembly can be loaded into PowerShell and main class instantiated
When ShowMessage method on this instance is called, a new window is shown and activated
If you call ShowMessage multiple times, this same window updates its title text and is activated
To stop using the window, call Dispose method
Step 1: Let's create a temporary working directory (you can naturally use your own dir)
(powershell.exe)
mkdir C:\TEMP\PshWindow
cd C:\TEMP\PshWindow
Step 2: Now let's define class that we will be interacting with in PowerShell:
// file 'InfoProvider.cs' in C:\TEMP\PshWindow
using System;
using System.Threading;
using System.Windows.Forms;
namespace PshWindow
{
public sealed class InfoProvider : IDisposable
{
public void Dispose()
{
GC.SuppressFinalize(this);
lock (this._sync)
{
if (!this._disposed)
{
this._disposed = true;
if (null != this._worker)
{
if (null != this._form)
{
this._form.Invoke(new Action(() => this._form.Close()));
}
this._worker.Join();
this._form = null;
this._worker = null;
}
}
}
}
public void ShowMessage(string msg)
{
lock (this._sync)
{
// make sure worker is up and running
if (this._disposed) { throw new ObjectDisposedException("InfoProvider"); }
if (null == this._worker)
{
this._worker = new Thread(() => (this._form = new MyForm(this._sync)).ShowDialog()) { IsBackground = true };
this._worker.Start();
while (this._form == null || !this._form.Created)
{
Monitor.Wait(this._sync);
}
}
// update the text
this._form.Invoke(new Action(delegate
{
this._form.Text = msg;
this._form.Activate();
}));
}
}
private bool _disposed;
private Form _form;
private Thread _worker;
private readonly object _sync = new object();
}
}
As well as the Form that will be shown:
// file 'MyForm.cs' in C:\TEMP\PshWindow
using System;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;
namespace PshWindow
{
internal sealed class MyForm : Form
{
public MyForm(object sync)
{
this._sync = sync;
this.BackColor = Color.LightGreen;
this.Width = 200;
this.Height = 80;
this.FormBorderStyle = FormBorderStyle.SizableToolWindow;
}
protected override void OnShown(EventArgs e)
{
base.OnShown(e);
this.TopMost = true;
lock (this._sync)
{
Monitor.PulseAll(this._sync);
}
}
private readonly object _sync;
}
}
Step 3: Let's compile the assembly...
(powershell.exe)
csc /out:PshWindow.dll /target:library InfoProvider.cs MyForm.cs
Step 4: ... and load the assembly in PowerShell to have fun with it:
(powershell.exe)
[System.Reflection.Assembly]::LoadFile('C:\TEMP\PshWindow\PshWindow.dll')
$a = New-Object PshWindow.InfoProvider
$a.ShowMessage('Hello, world')
A green-ish window with title 'Hello, world' should now pop-up and be active. If you reactivate the PowerShell window and enter:
$a.ShowMessage('Stack overflow')
The Window's title should change to 'Stack overflow' and the window should be active again.
To stop working with our window, dispose the object:
$a.Dispose()
This solution works as expected in both Windows XP SP3, x86 and Windows Vista SP1, x64. If there are question about how this solution works I can update this entry with detailed discussion. For now I'm hoping the code if self-explanatory.
Huge thanks people.
I think I've made it a bit shorter, here's what I put on a seperate thread and seems to be working ok.
private static void StatusChecking()
{
IntPtr iActiveForm = IntPtr.Zero, iCurrentACtiveApp = IntPtr.Zero;
Int32 iMyProcID = Process.GetCurrentProcess().Id, iCurrentProcID = 0;
IntPtr iTmp = (IntPtr)1;
while (bIsRunning)
{
try
{
Thread.Sleep(45);
if (Form.ActiveForm != null)
{
iActiveForm = Form.ActiveForm.Handle;
}
iTmp = GetForegroundWindow();
if (iTmp == IntPtr.Zero) continue;
GetWindowThreadProcessId(iTmp, ref iCurrentProcID);
if (iCurrentProcID == 0)
{
iCurrentProcID = 1;
continue;
}
if (iCurrentProcID != iMyProcID)
{
SystemParametersInfo(SPI_GETFOREGROUNDLOCKTIMEOUT, 0, IntPtr.Zero, 0);
SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, IntPtr.Zero, SPIF_SENDCHANGE);
BringWindowToTop(iActiveForm);
SetForegroundWindow(iActiveForm);
}
else iActiveForm = iTmp;
}
catch (Exception ex)
{
Definitions.UnhandledExceptionHandler(ex, 103106);
}
}
}
I don`t bother repasting the definitions...
You shouldn't need to import any win32 functions for this. If .Focus() isn't enough the form should also have a .BringToFront() method you can use. If that fails, you can set it's .TopMost property to true. You don't want to leave it true forever, so then call Application.DoEvents so the form can process that message and set it back to false.
Don't you just want the dialog to be a child of the calling form?
To do that you'll need the pass in the calling window and
use the ShowDialog( IWin32Window owner ) method.