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.)
Related
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.
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 (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 situation where I need to find the parent Window or WinForm which is hosting the WPF control. I need to get the handle of either the parent Window or WinForm whatever the case may be.
The problem is when the WPF control is hosted in a WinForm using ElementHost. How can I find the Handle of the hosting WinForm from a WPF control.
Just figured it out!
var presentationSource = (HwndSource)PresentationSource.FromVisual(child);
var parentHandle = presentationSource.Handle;
[DllImport("user32.dll")]
public static extern int GetParent(int hwnd);
public int GetParentWindowHandle(Visual child)
{
HwndSource presentationSource = (HwndSource)PresentationSource.FromVisual(child);
int parentHandle = presentationSource.Handle.ToInt32();
int handle = parentHandle;
while (parentHandle != 0)
{
handle = parentHandle;
parentHandle = ApplicationHelperInterop.GetParent(parentHandle);
}
return handle;
}
You could then loop through System.Windows.Forms.Application.OpenForms collection to find a WinForm corresponding to the return value of GetParentWindowHandle method above.
Alex D.
I have a WinForms based app with traditional MDI implementation within it except that I'm hosting WPF based UserControls via the ElementHost control as the main content for each of my MDI children. This is the solution recommended by Microsoft for achieving MDI with WPF although there are various side effects unfortunately. One of which is that my Ctrl+Tab functionality for tab switching between each MDI child is gone because the tab key seems to be swallowed up by the WPF controls.
Is there a simple solution to this that will let the Ctrl+tab key sequences reach my WinForms MDI parent so that I can get the built-in tab switching functionality?
In the host WinForm add a PreviewKeyDown handler for the hosted WPF control that captures Ctrl-(Shift)-Tab, activates the next or previous MDI child and marks the event as handled:
TheHostedWpfControl.PreviewKeyDown += (s, e) =>
{
if (e.Key == Key.Tab && ModifierKeys.HasFlag(Keys.Control))
{
ActivateNextMdiChild(ModifierKeys.HasFlag(Keys.Shift));
e.Handled = true;
}
};
And here is the next/prev MDI child activation:
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, int lParam);
private const int WM_MDINEXT = 0x224;
private void ActivateNextMdiChild(bool backward = false)
{
if (MdiParent != null)
{
MdiClient mdiClient = MdiParent.Controls.OfType<MdiClient>().FirstOrDefault();
if (mdiClient != null)
{
SendMessage(mdiClient.Handle, WM_MDINEXT, Handle, backward ? 1 : 0);
}
}
}