I've written a IE Toolbar in C# and everything is working fine except that when I open a child Windows Form from my toolbar, the tab key doesn't work on the child form to allow me to move from field to field.
The interesting part is that when I open my child form using form.showDialog() instead of form.show() the tabs work like normal.
The toolbar I've created is based on this article and this article
I've implemented TranslateAcceleratorIO as mentioned in several articles, but still no luck.
Here are my implmentations of TranslateAcceleratorIO() and HasFocusIO() (implemented in my toolband class)
[DllImport("user32.dll")]
public static extern int TranslateMessage(ref MSG lpMsg);
[DllImport("user32", EntryPoint = "DispatchMessage")]
static extern bool DispatchMessage(ref MSG msg);
public int HasFocusIO()
{
return this.ContainsFocus ? 0 : 1; //S_OK : S_FALSE;
}
public int TranslateAcceleratorIO(ref MSG msg)
{
if (msg.message == 0x100)//WM_KEYDOWN
if (msg.wParam == (uint)Keys.Tab || msg.wParam ==(uint)Keys.F6)
{
if (SelectNextControl(
ActiveControl,
ModifierKeys == Keys.Shift ? false : true,
true,
true,
false)
)
{
return 0;//S_OK
}
}
else
{
TranslateMessage(ref msg);
DispatchMessage(ref msg);
return 0;//S_OK
}
return 1;//S_FALSE
}
I've also tried having TranslateAccelerator like this with no luck:
public int TranslateAcceleratorIO(ref MSG msg)
{
TranslateMessage(ref msg);
DispatchMessage(ref msg);
return 0;//S_OK
}
Has anybody else run into this issue?
Are you also implementing HasFocusIO? I believe your main toolbar class must also implement HasFocusIO and return true.
These types of problems with IE toolbars were the bane of my existence for a while. I think what I eventually ended up doing was creating separate UI threads and making my dialogs modal in those threads, which eliminated a bunch of weird issues. But I think implementing HasFocusIO and TranslateAcceleratorIO should work for this particular one.
Where are you implementing these? It's hard to tell from what you have there, are you implementing them in your Form or are you implementing them in your deskband class?
You need to implement them in your DeskBand implementation, and HasFocusIO needs to return true whenever one of your windows has focus (not just when the toolbar has focus). Then the messages for Tab, Delete, arrow keys, etc should be dispatched to the TranslateAcceleratorIO, also in your deskband, and from there you'll have to pass them along to your form.
The IE plugin framework is incredibly hacky that way.
Related
I have a winforms application where I need to obtain the current keyboard layout of the user. To do that I'm using System.Windows.Forms.InputLanguage.CurrentInputLanguage.LayoutName.
This works fine as long as the user has the form as his active window, once he focuses something else and changes the language the former property wont return a proper value, it will return the last used language while the form was still the active window.
Is there a way I can get the users keyboard layout's name even if he is not focusing the form, there are no restrictions to what can be used.
As you might already know that the System.Windows.Forms.InputLanguage.CurrentInputLanguage.LayoutName property returns the keyboard layout for the current thread and no matter what layout you choose it would remain the same for the executing thread unless you select that window and change the keyboard input layout for that window.
That said, what you are essentially trying to do is check the current keyboard layout culture and be able to know when it changes. I had a similar requirement a while ago and I came up with the following code which served me well:
public delegate void KeyboardLayoutChanged(int oldCultureInfo, int newCultureInfo);
class KeyboardLayoutWatcher : IDisposable
{
private readonly Timer _timer;
private int _currentLayout = 1033;
public KeyboardLayoutChanged KeyboardLayoutChanged;
public KeyboardLayoutWatcher()
{
_timer = new Timer(new TimerCallback(CheckKeyboardLayout), null, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1));
}
[DllImport("user32.dll")] static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")] static extern uint GetWindowThreadProcessId(IntPtr hwnd, IntPtr proccess);
[DllImport("user32.dll")] static extern IntPtr GetKeyboardLayout(uint thread);
public int GetCurrentKeyboardLayout()
{
try
{
IntPtr foregroundWindow = GetForegroundWindow();
uint foregroundProcess = GetWindowThreadProcessId(foregroundWindow, IntPtr.Zero);
int keyboardLayout = GetKeyboardLayout(foregroundProcess).ToInt32() & 0xFFFF;
if (keyboardLayout == 0)
{
// something has gone wrong - just assume English
keyboardLayout = 1033;
}
return keyboardLayout;
}
catch (Exception ex)
{
// if something goes wrong - just assume English
return 1033;
}
}
private void CheckKeyboardLayout(object sender)
{
var layout = GetCurrentKeyboardLayout();
if (_currentLayout != layout && KeyboardLayoutChanged != null)
{
KeyboardLayoutChanged(_currentLayout, layout);
_currentLayout = layout;
}
}
private void ReleaseUnmanagedResources()
{
_timer.Dispose();
}
public void Dispose()
{
ReleaseUnmanagedResources();
GC.SuppressFinalize(this);
}
~KeyboardLayoutWatcher()
{
ReleaseUnmanagedResources();
}
}
And use it like:
new KeyboardLayoutWatcher().KeyboardLayoutChanged += (o, n) =>
{
this.CurrentLayoutLabel.Text = $"{o} -> {n}"; // old and new KB layout
};
Couple of things to note here. Keyboard selection in Windows is done on a per-thread basis. This allows the user to select a different keyboard locale for any given application, which Windows will respect, while leaving other applications alone.
The user does this by enabling the language bar (for installed keyboards) in the Windows Taskbar. If they select a different keyboard while another application has the focus, then the selection only applies to that application. And besides, if your application doesn't have the focus, it can't do anything about it anyway. By using the language bar in this way, the user is plainly stating their intention to have the selected keyboard apply to the active application only. There is no way for your application to find out about it, since from Windows' point of view, it's none of your application's business.
Now, if you want to know if the user changes the keyboard for the entire system (using the control panel applet), that's feasible. If your application doesn't have the focus, there's no way to trap the notification message. Your form will still consider the current language to be the one it started with. However, the system-wide change does change the InputLanguage.DefaultInputLanguage. to the newly-selected keyboard. So, override the OnActivated handler and check the value of the default language, not the current one (which is per-thread).
The problem I have is that when one switches from one child form to another a strange thing happens: the form to be shown appears in a strange way as if it has been minimized, restored and then maximized, causing an effect of like several drawing events at the same time.
The problem does not appear (ie, everything works) in these situations:
when one switches between forms using CTRL+TAB or CTRL+SHIFT+TAB
when child forms are not maximized and one simply changes the order/position of the form
Possibly related; there are a tonne of methods one can called for showing forms, including (and related):
child.Focus()
child.Show()
child.Activate()
child.Select()
child.BringToFront()
My question is, what exactly should I be calling?
Edit:
In my case, I have the following code that works, but still causes the weird effect I described above:
private void tabForms_MouseClick(object sender, MouseEventArgs e)
{
// handle middle-mouse-button click (close)
if (e.Button == System.Windows.Forms.MouseButtons.Middle)
{
// See: http://stackoverflow.com/a/745361
TabPage tab = tabForms.TabPages.Cast<TabPage>().Where((t, i) => tabForms.GetTabRect(i).Contains(e.Location)).FirstOrDefault();
if (tab != null && tab.Tag != null) (tab.Tag as Form).Close();
}
// handle left-mouse-button click (show)
if ((tabForms.SelectedTab != null) && (tabForms.SelectedTab.Tag != null) && (ActiveMdiChild != tabForms.SelectedTab.Tag))
{
(tabForms.SelectedTab.Tag as Form).Select();
(tabForms.SelectedTab.Tag as Form).Show();
}
}
PS: Without the .Select() it doesn't work. Although it seems that it still works if I replace .Select() and .Show() with .Focus().
This might be old now, but for the benefit of anyone else with this issue there's a simple solution to this. You can call the Win32 method LockWindowUpdate, providing the window handle to your form, create and open / maximise your form, then call LockWindowUpdate again and provide an IntPtr.Zero value (as shown below).
try
{
LockWindowUpdate(this.Handle);
// Open your form, maximise it etc
}
catch (Exception ex)
{
// Handle any errors
}
finally
{
LockWindowUpdate(IntPtr.Zero);
}
LockWindowUpdate disables drawing for whichever window handle you provide it, calling it a second time with a zero IntPtr resumes drawing the window. Only one window can be locked at a time.
You will need two other things; a DLL import, and a using reference for System.Runtime.InteropServices. Here's the DLL import:
[DllImport("user32.dll", EntryPoint = "LockWindowUpdate", SetLastError = true,
ExactSpelling = true, CharSet = CharSet.Auto,
CallingConvention = CallingConvention.StdCall)]
public static extern long LockWindowUpdate(IntPtr hWndLock);
I want to disable the screensaver and monitor power off. At this stage there's no windows form, which I could youse. Thus I wan't to use NativeWindow.
Here's my code
sealed class ObserverWindow : NativeWindow, IDisposable
{
internal ObserverWindow()
{
this.CreateHandle(new CreateParams()
{
Parent= IntPtr.Zero
});
}
public void Dispose()
{
DestroyHandle();
}
protected override void WndProc(ref Message msg)
{
if (msg.Msg == WM_SYSCOMMAND &&
((((long)msg.WParam & 0xFFF0) == SC_SCREENSAVE) ||
((long)msg.WParam & 0xFFF0) == SC_MONITORPOWER))
{
msg.Msg = 0;
msg.HWnd = IntPtr.Zero;
}
base.WndProc(ref msg);
}
}
The Problem is, that the WndProc is not called with WM_SYSCOMMAND. Actualy the WndProc is called 4 times. At the last call there's msg.Msg == WM_CREATE.
I think I'm missing some create parameter. Does anyone have advise?
Regards Michael
UPDATE
I was running the code in a non STA thread. Thus the window did not reveive any messages exept the initial ones. Now I'm receiving WM_SYSCOMMAND messages. But when the screensaver is activated, there's no message.
I also tried to overwrite a Form's WndProc with the same result. But this used to work in Windows XP. Is there a change in Windows 7?
OS: Windows 7 64bit.
SOLUTION
As a comment in this Question states, only the foreground window can cancel the screensaver. Thus the above code can't work. The NativeWindow is great for receiving messages, but not for canceling a screensaver. For latter I recommend the answer to this question.
The proper way to do this is by telling Windows that your thread needs to have the display active. Commonly used by video players. P/Invoke the SetThreadExecutionState() API function, pass ES_DISPLAY_REQUIRED. And ES_SYSTEM_REQUIRED to keep the machine from shutting down automatically. Visit pinvoke.net for the required declarations.
Disabling the screen saver is much easier, according to this KB article:
This can be done easily using:
SystemParametersInfo( SPI_SETSCREENSAVEACTIVE,
FALSE,
0,
SPIF_SENDWININICHANGE
);
[...]
If you need the screen saver to start up again, you'll need to reinitialize the time-out period. Do this by [c]alling SystemParametersInfo (SPI_SETSCREENSAVEACTIVE, TRUE, 0, SPIF_SENDWININICHANGE).
You could try overriding DefWndProc instead.
public override void DefWndProc(ref Message msg)
{
if (msg.Msg == WM_SYSCOMMAND &&
((((long)msg.WParam & 0xFFF0) == SC_SCREENSAVE) ||
((long)msg.WParam & 0xFFF0) == SC_MONITORPOWER))
{
msg.Msg = 0;
msg.HWnd = IntPtr.Zero;
}
base.DefWndProc(ref msg);
}
I'm not on a Windows box right now, so I cannot test this. Let me know if it works.
I want to disable the screensaver and monitor power off. At this stage there's no windows form, which I could youse. Thus I wan't to use NativeWindow.
Here's my code
sealed class ObserverWindow : NativeWindow, IDisposable
{
internal ObserverWindow()
{
this.CreateHandle(new CreateParams()
{
Parent= IntPtr.Zero
});
}
public void Dispose()
{
DestroyHandle();
}
protected override void WndProc(ref Message msg)
{
if (msg.Msg == WM_SYSCOMMAND &&
((((long)msg.WParam & 0xFFF0) == SC_SCREENSAVE) ||
((long)msg.WParam & 0xFFF0) == SC_MONITORPOWER))
{
msg.Msg = 0;
msg.HWnd = IntPtr.Zero;
}
base.WndProc(ref msg);
}
}
The Problem is, that the WndProc is not called with WM_SYSCOMMAND. Actualy the WndProc is called 4 times. At the last call there's msg.Msg == WM_CREATE.
I think I'm missing some create parameter. Does anyone have advise?
Regards Michael
UPDATE
I was running the code in a non STA thread. Thus the window did not reveive any messages exept the initial ones. Now I'm receiving WM_SYSCOMMAND messages. But when the screensaver is activated, there's no message.
I also tried to overwrite a Form's WndProc with the same result. But this used to work in Windows XP. Is there a change in Windows 7?
OS: Windows 7 64bit.
SOLUTION
As a comment in this Question states, only the foreground window can cancel the screensaver. Thus the above code can't work. The NativeWindow is great for receiving messages, but not for canceling a screensaver. For latter I recommend the answer to this question.
The proper way to do this is by telling Windows that your thread needs to have the display active. Commonly used by video players. P/Invoke the SetThreadExecutionState() API function, pass ES_DISPLAY_REQUIRED. And ES_SYSTEM_REQUIRED to keep the machine from shutting down automatically. Visit pinvoke.net for the required declarations.
Disabling the screen saver is much easier, according to this KB article:
This can be done easily using:
SystemParametersInfo( SPI_SETSCREENSAVEACTIVE,
FALSE,
0,
SPIF_SENDWININICHANGE
);
[...]
If you need the screen saver to start up again, you'll need to reinitialize the time-out period. Do this by [c]alling SystemParametersInfo (SPI_SETSCREENSAVEACTIVE, TRUE, 0, SPIF_SENDWININICHANGE).
You could try overriding DefWndProc instead.
public override void DefWndProc(ref Message msg)
{
if (msg.Msg == WM_SYSCOMMAND &&
((((long)msg.WParam & 0xFFF0) == SC_SCREENSAVE) ||
((long)msg.WParam & 0xFFF0) == SC_MONITORPOWER))
{
msg.Msg = 0;
msg.HWnd = IntPtr.Zero;
}
base.DefWndProc(ref msg);
}
I'm not on a Windows box right now, so I cannot test this. Let me know if it works.
I'm creating an alt-tab replacement for Vista but I have some problems listing all active programs.
I'm using EnumWindows to get a list of Windows, but this list is huge. It contains about 400 items when I only have 10 windows open. It seems to be a hwnd for every single control and a lot of other stuff.
So I have to filter this list somehow, but I can't manage to do it exactly as alt-tab does.
This is the code I use to filter the list right now. It works pretty well, but I get some unwanted windows like detached tool-windows in Visual Studio and I also miss windows like iTunes and Warcraft3.
private bool ShouldWindowBeDisplayed(IntPtr window)
{
uint windowStyles = Win32.GetWindowLong(window, GWL.GWL_STYLE);
if (((uint)WindowStyles.WS_VISIBLE & windowStyles) != (uint)WindowStyles.WS_VISIBLE ||
((uint)WindowExStyles.WS_EX_APPWINDOW & windowStyles) != (uint)WindowExStyles.WS_EX_APPWINDOW)
{
return true;
}
return false;
}
Raymond Chen answered this a while back
(https://devblogs.microsoft.com/oldnewthing/20071008-00/?p=24863):
It's actually pretty simple although
hardly anything you'd be able to guess
on your own. Note: The details of this
algorithm are an implementation
detail. It can change at any time, so
don't rely on it. In fact, it already
changed with Flip and Flip3D; I'm just
talking about the Classic Alt+Tab
window here.
For each visible window, walk up its
owner chain until you find the root
owner. Then walk back down the visible
last active popup chain until you find
a visible window. If you're back to
where you're started, then put the
window in the Alt+Tab list. In
pseudo-code:
BOOL IsAltTabWindow(HWND hwnd)
{
// Start at the root owner
HWND hwndWalk = GetAncestor(hwnd, GA_ROOTOWNER);
// See if we are the last active visible popup
HWND hwndTry;
while ((hwndTry = GetLastActivePopup(hwndWalk)) != hwndTry) {
if (IsWindowVisible(hwndTry)) break;
hwndWalk = hwndTry;
}
return hwndWalk == hwnd;
}
Follow the link to Chen's blog entry for more details and some corner conditions.
Thanks Mike B.
The example from Raymonds blog pointed me in the correct direction.
There are however some exceptions that has to be made, Windows Live messenger got alot of hacks for creating shadows under windows etc :#
Here is my complete code, have been using it for one day now and havn't noticed any differences from the real alt tab. There's some underlying code not posted but it's no problem figuring out what it does. :)
private static bool KeepWindowHandleInAltTabList(IntPtr window)
{
if (window == Win32.GetShellWindow()) //Desktop
return false;
//http://stackoverflow.com/questions/210504/enumerate-windows-like-alt-tab-does
//http://blogs.msdn.com/oldnewthing/archive/2007/10/08/5351207.aspx
//1. For each visible window, walk up its owner chain until you find the root owner.
//2. Then walk back down the visible last active popup chain until you find a visible window.
//3. If you're back to where you're started, (look for exceptions) then put the window in the Alt+Tab list.
IntPtr root = Win32.GetAncestor(window, Win32.GaFlags.GA_ROOTOWNER);
if (GetLastVisibleActivePopUpOfWindow(root) == window)
{
WindowInformation wi = new WindowInformation(window);
if (wi.className == "Shell_TrayWnd" || //Windows taskbar
wi.className == "DV2ControlHost" || //Windows startmenu, if open
(wi.className == "Button" && wi.windowText == "Start") || //Windows startmenu-button.
wi.className == "MsgrIMEWindowClass" || //Live messenger's notifybox i think
wi.className == "SysShadow" || //Live messenger's shadow-hack
wi.className.StartsWith("WMP9MediaBarFlyout")) //WMP's "now playing" taskbar-toolbar
return false;
return true;
}
return false;
}
private static IntPtr GetLastVisibleActivePopUpOfWindow(IntPtr window)
{
IntPtr lastPopUp = Win32.GetLastActivePopup(window);
if (Win32.IsWindowVisible(lastPopUp))
return lastPopUp;
else if (lastPopUp == window)
return IntPtr.Zero;
else
return GetLastVisibleActivePopUpOfWindow(lastPopUp);
}
Good work vhanla. My Pascal is a bit rusty, but your solution was a great help. I'm new to this, so please excuse my code and/or way of expressing things. The answer is relatively simple.
To make the list of Alt-Tab Windows, it appears you need three criteria
1) The Window must be visible - using GetWindowVisible
2) The Window must not be a Tool bar Window - using GetWindowInfo
3) The Window must not be cloaked - using DwmGetWindowAttribute
I don't believe that you need to look at class names. I think the WS_EX_APPWINDOW flag often fails the test (e.g. Chrome) - even when used in conjunction with the WS_EX_TOOLWINDOW. Also ... I don't think you need to look at the parent Window if you are enumerating Windows at the top level.
public static bool IsAltTabWindow(IntPtr hWnd)
{
const uint WS_EX_TOOLWINDOW = 0x00000080;
const uint DWMWA_CLOAKED = 14;
// It must be a visible Window
if (!IsWindowVisible(hWnd)) return false;
// It must not be a Tool bar window
WINDOWINFO winInfo = new WINDOWINFO(true);
GetWindowInfo(hWnd, ref winInfo);
if ((winInfo.dwExStyle & WS_EX_TOOLWINDOW) != 0) return false;
// It must not be a cloaked window
uint CloakedVal;
DwmGetWindowAttribute(hWnd, DWMWA_CLOAKED, out CloakedVal, sizeof(uint));
return CloakedVal == 0;
}
This is a function in pascal/delphi, you can easily translate it to C#.
It includes support for Windows 10 applications.
EnumWindows(#ListApps, 0);
function ListApps(LHWindow: HWND; lParam: Pointer): Boolean; stdcall;
var
LHDesktop: HWND;
LHParent: HWND;
LExStyle: DWORD;
AppClassName: array[0..255] of char;
Cloaked: Cardinal;
titlelen: Integer;
title: String;
begin
LHDesktop:=GetDesktopWindow;
GetClassName(LHWindow, AppClassName, 255);
LHParent:=GetWindowLong(LHWindow,GWL_HWNDPARENT);
LExStyle:=GetWindowLong(LHWindow,GWL_EXSTYLE);
if AppClassName = 'ApplicationFrameWindow' then
DwmGetWindowAttribute(LHWindow, DWMWA_CLOAKED, #cloaked, sizeof(Cardinal))
else
cloaked := DWM_NORMAL_APP_NOT_CLOAKED;
if IsWindowVisible(LHWindow)
and (AppClassName <> 'Windows.UI.Core.CoreWindow')
and ( (cloaked = DWM_NOT_CLOAKED) or (cloaked = DWM_NORMAL_APP_NOT_CLOAKED) )
and ( (LHParent=0) or (LHParent=LHDesktop) )
and (Application.Handle<>LHWindow)
and ((LExStyle and WS_EX_TOOLWINDOW = 0) or (LExStyle and WS_EX_APPWINDOW <> 0))
then
begin
titlelen := GetWindowTextLength(LHWindow);
SetLength(title, titlelen);
GetWindowText(LHWindow, PChar(title), titlelen + 1);
{ add each to a list }
But.ListBox1.Items.Add(title);
{ also add each HWND to the list too, later switch using SwitchToThisWindow }
{ ... }
end;
Result := True;
end;