The code snipper below is taken from "WPF 4 Unleashed". it demonstrates hwo in windows 7 it's possible to create the Aero Glass effect using WIN32 API. In this demo, the WndProc events procedure is used with respect to the Window instance. I noticed that in this routine there is no invocation of the default window procedure, as if there are no other events that need to be handled by that WPF window.
What brings me to post that question -- which is more of a general question regarding WPF -- is whether the events that are normally handles by WPF window (and I am sure there are many of them) are handled by some other procedure. In other words, is WPF window different than WinForms --- does it gets messages from Operating system (mouse clicking, mouse movements) by other means ?
[StructLayout(LayoutKind.Sequential)]
public struct MARGINS
{
public MARGINS(Thickness t)
{
Left = (int)t.Left;
Right = (int)t.Right;
Top = (int)t.Top;
Bottom = (int)t.Bottom;
}
public int Left;
public int Right;
public int Top;
public int Bottom;
}
public class GlassHelper
{
[DllImport("dwmapi.dll", PreserveSig=false)]
static extern void DwmExtendFrameIntoClientArea( IntPtr hWnd, ref MARGINS pMarInset);
[DllImport("dwmapi.dll", PreserveSig=false)]
static extern bool DwmIsCompositionEnabled();
public static bool ExtendGlassFrame(Window window, Thickness margin)
{
if (!DwmIsCompositionEnabled())
return false;
IntPtr hwnd = new WindowInteropHelper(window).Handle;
if (hwnd == IntPtr.Zero)
throw new InvalidOperationException(
"The Window must be shown before extending glass.");
// Set the background to transparent from both the WPF and Win32 perspectives
window.Background = Brushes.Transparent;
HwndSource.FromHwnd(hwnd).CompositionTarget.BackgroundColor =Colors.Transparent;
MARGINS margins = new MARGINS(margin);
DwmExtendFrameIntoClientArea(hwnd, ref margins);
return true;
}
}
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
// This can’t be done any earlier than the SourceInitialized event:
GlassHelper.ExtendGlassFrame(this, new Thickness(-1));
// Attach a window procedure in order to detect later enabling of desktop
// composition
IntPtr hwnd = new WindowInteropHelper(this).Handle;
HwndSource.FromHwnd(hwnd).AddHook(new HwndSourceHook(WndProc));
}
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == WM_DWMCOMPOSITIONCHANGED)
{
// Reenable glass:
GlassHelper.ExtendGlassFrame(this, new Thickness(-1));
handled = true;
}
return IntPtr.Zero;
}
private const int WM_DWMCOMPOSITIONCHANGED = 0x031E;
WPF window is the same as WinForms window in terms of using WndProc. I had no problem to put the snippet into my WPF app. In fact, I didn't find any WndProc related code, that didn't work in WPF so far.
WPF windows are like Windows Forms windows and like classic Windows windows in that they all have a Message Loop for receiving messages and a WindowProc (the actual name can be whatever the programmer chooses to make it) to process them. All the windows can be subclassed (as in About Window Procedures), at least at the level below WPF. I don't know if WndProc for Windows Forms or for WPF are subclassing the relevant windows but they might be.
Related
I'm trying to get event on window title bar (for a standard wpf window).
A simple mouse down event (no matter the button) would be enough.
The idea is that i want to perform action on a custom control (which contain a textbox) when user click outside the control.
For now i'm adding two events handler to the control to help that:
UIElement.IsKeyboardFocusWithinChanged and Window.PreviewMouseDown. It works almost perfectly with this two event, except when user click on the window title bar as none of these two events are being fired...
Any ideas ? Thanks
You can use the following code to check if a window border has been clicked:
private const int WM_NCLBUTTONDOWN = 0x00a1;
// added after window loaded
HwndSource source = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
source.AddHook(new HwndSourceHook(WndProc));
private static IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
switch (msg)
{
case WM_NCLBUTTONDOWN:
// your action
break;
}
return IntPtr.Zero;
}
we have a wpf application that should be 'piloted' by a legacy win32 winform application. (We don't own the code ;) )
The legacy application should pilot our application (minimize, bring to front, shut, etc) via windowsclass name we should provide as a configuration parameter written into an ini file.
The problem is we cannot make it work with wpf since if we insert the classname Spy++ gives us, nothing happens. The point is Spi++ returns something like this
HwndWrapper[MyWpfProgram.exe;;16978ce2-3b8d-4c46-81ee-e1c6d6de4e6d]
where the guid is randomly generated at every run.
Is there any way to solve this issue?
Thank you.
There is no way to do what I asked. But we found a walkaround. "Simply" embedding the xaml windows within a windows form.
These are the steps we followed:
1 - Add a Windows Form to the project.
2 - Remove the app.xaml and make the new form the entry point of the application.
3 - Since we need the hwnd of the main.xaml we added this prop to its code behind
public IntPtr Hwnd
{
get { return new WindowInteropHelper(this).Handle; }
}
4 - then from the constructor of the form we create an instance of the wpf window class
private Main app;
public ContainerForm()
{
InitializeComponent();
app = new Main();
ElementHost.EnableModelessKeyboardInterop(app);
}
we needed
ElementHost.EnableModelessKeyboardInterop(app);
since we want all the keyboard input to pass from the the windows form to the xaml window
5 - now we want to bond the xpf window to the winform. In order to do that we need to use Windows Api and we do it at the OnShow event of the form (the reason why will be explicated later).
[DllImport("user32.dll", SetLastError = true)]
private static extern long SetFocus(IntPtr hWnd);
[DllImport("user32.dll", SetLastError = true)]
private static extern long SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool MoveWindow(IntPtr hwnd, int x, int y, int cx, int cy, bool repaint);
[DllImport("user32.dll", EntryPoint = "SetWindowLongA", SetLastError = true)]
private static extern long SetWindowLong(IntPtr hwnd, int nIndex, long dwNewLong);
private const int GWL_STYLE = (-16);
private const int WS_VISIBLE = 0x10000000;
private void ContainerForm_Shown(object sender, EventArgs e)
{
app.Show();
SetParent(app.Hwnd, this.Handle);
SetWindowLong(app.Hwnd, GWL_STYLE, WS_VISIBLE);
MoveWindow(app.Hwnd, 0, 0, this.Width, this.Height, true);
SetFocus(app.Hwnd);
}
with
SetParent(app.Hwnd, this.Handle);
wo do the magic, then with
SetWindowLong(app.Hwnd, GWL_STYLE, WS_VISIBLE);
we remove al the chrome from the wpf window (there is a border even if the window is defined borderless, don't ask me why)
then we make the wpf window fill all the client area of the winform
MoveWindow(app.Hwnd, 0, 0, this.Width, this.Height, true);
and then we focus the wpf window
SetFocus(app.Hwnd);
that's the reason why we do everything in the show event. Since if we do it at form's constructor, then the wpf window will loose its focus since the in winform the main window got the focus from the operating system.
We didn't understand why we needed to add the other api calls at this point, but if we left them at constructor's the trick didn't work.
Anyway,
problem solved ;)
Use HwndSource.
You can use native Windows API calls to create a window with the expected classname, then use HwndSource to add WPF content to it:
var source = HwndSource.FromHwnd(nativeWindowHandle);
source.RootVisual = mainGrid;
If you need to use a WPF window, I think you could still solve this with a "proxy" window, but it wouldn't be pretty:
Have your WPF application spawn a native message-only window.
Use HwndSource.AddHook to handle messages like WM_CLOSE, WM_SIZE on the native window and pass them along to the "real" WPF window.
For the window handles, titles, and class names, Spy++ uses fairly simple Windows APIs.
FindWindowEx http://msdn.microsoft.com/en-us/library/windows/desktop/ms633500%28v=vs.85%29.aspx
EnumWindows http://msdn.microsoft.com/en-us/library/windows/desktop/ms633497%28v=vs.85%29.aspx
GetClassName http://msdn.microsoft.com/en-us/library/windows/desktop/ms633582%28v=vs.85%29.aspx
You can create a "loader" program that will...
Start the wpf app
Use the above APIs to get the proper class names and windows handles
Edit the legacy INI
Start the legacy app
We have a WinForms application that we are progressively converting to WPF. At this point the application's main form is a Form (WinForm) that contains a vertical sidebar built in WPF. The sidebar is hosted in an ElementHost control.
In the main form, KeyPreview is set to true and we override OnKeyDown() to process application wide keyboard shortcuts. When the sidebar has the focus, keyboard events are not sent to OnKeyDown.
What is the correct way to fix this?
Yes, it seems the KeyPreview is not consider by ElementHost, here is a workaround:
Derive from ElementHost and override ProcessCmdKey, when the base.ProcessCmdKey result says "not processed", pass the message to the parent even if it is not your main form, this way your main form will receive it because other winforms control will behave correctly. Here is a sample...
public class KeyPreviewEnabledElementHost : ElementHost
{
public KeyPreviewEnabledElementHost()
{
}
[DllImport("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
protected override bool ProcessCmdKey(ref System.Windows.Forms.Message m, System.Windows.Forms.Keys keyData)
{
bool processed = base.ProcessCmdKey(ref m, keyData);
if (!processed)
{
SendMessage(Parent.Handle, m.Msg, m.WParam, m.LParam);
}
return processed;
}
}
We have a WinForms application that we are progressively converting to WPF. At this point the application's main form is a Form (WinForms) that contains a vertical sidebar built in WPF. The sidebar is hosted in an ElementHost control.
The sidebar is made of a ScrollViewer that contains other controls. The problem is that when the focus is somewhere in the WinForms aera and I use the mouse wheel over the ScrollViewer, it does not scroll.
This is related to the WPF/WinForms integration because in a 100% WPF project, the ScrollViewer reacts to the mouse wheel even if the focus is on another control.
What is the correct way to fix this?
consider doing a message filter and when you receive WM_MOUSEWHEEL, determine if the mouse is over your WPF control. If so then send the message directly to your Element window handle.
Something like this:
System.Windows.Forms.Application.AddMessageFilter( new MouseWheelMessageFilter( YourElementInsideAnElementHost ) );
Dont forget to call RemoveMessageFilter when you go out of scope
public class MouseWheelMessageFilter : IMessageFilter
{
private const int WM_MOUSEWHEEL = 0x020A;
private FrameworkElement _element;
[DllImport("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
public MouseWheelMessageFilter(FrameworkElement element)
{
_element = element;
}
public bool PreFilterMessage(ref Message m)
{
if (m.Msg == WM_MOUSEWHEEL)
{
Rect rect = new Rect(0, 0, _element.ActualWidth, _element.ActualHeight);
Point pt = Mouse.GetPosition(_element);
if (rect.Contains(pt))
{
HwndSource hwndSource = (HwndSource)HwndSource.FromVisual(_element);
SendMessage(hwndSource.Handle, m.Msg, m.WParam, m.LParam);
return true;
}
}
return false;
}
}
Try setting the focus to the ElementHost by calling elementHost.Select()
This made the MouseWheel event work for me.
I have a WinForms usercontrol hosting a WPF custom Listbox in it. After the WinForms user control gets disabled and then re-enabled the WPF control in the WinForms usercontrol is unresponsive. Has anyone else experienced this?
We had to hack a soultion into remove and re-add the element host each time the control gets disable / enabled to fix the issue.
WinForms
wpfControl.Enabled = false;
...
wpfControl.Enabled = true;
Hack for fixing it in the WinForms EnabledChanged method for the usercontrol
if ( Enabled )
{
ElementHost oldEh = ctlElementHost;
ElementHost eh = new ElementHost();
eh.Name = oldEh.Name;
oldEh.Child = null;
eh.Child = wpfControl;
this.Controls.Remove( ctlElementHost );
this.Controls.Add( eh );
eh.Dock = DockStyle.Fill;
oldEh.Dispose();
ctlElementHost = eh;
}
There seems to be a memory leak where the disposed element hosts are still sticking around until the parent form that was hosting the WinForms usercontrol gets closed.
A co-worker (thanks KwB) of mine managed to find a fix for this issue: http://support.microsoft.com/kb/955753
It involves inheriting from ElementHost and manually telling the window region to enable:
public class MyElementHost : ElementHost
{
protected override void OnEnabledChanged(EventArgs e)
{
SynchChildEnableState();
base.OnEnabledChanged(e);
}
private void SynchChildEnableState()
{
IntPtr childHandle = GetWindow(Handle, GW_CHILD);
if (childHandle != IntPtr.Zero)
{
EnableWindow(childHandle, Enabled);
}
}
private const uint GW_CHILD = 5;
[DllImport("user32.dll")]
private extern static IntPtr GetWindow(IntPtr hWnd, uint uCmd);
[DllImport("user32.dll")]
private extern static bool EnableWindow(IntPtr hWnd, bool bEnable);
}
Does the element host subscribe to events from the WPF user control? If so, and the events aren't unwired before trying to dispose the element host, it will hang around in memory until the WPF control is disposed (and since it looks like you're using the same instance of the control throughout, that isn't until the form is closed.)