I suppose it is safe to say that WPF renders its contents as a window background. There are no child windows in a traditional HWND sense. So, when one introduces something HWND based in a WPF app, like a WebBrowser, things start to go wrong way it terms of visual appearance.
Consider a Window having a Grid with two children, WebBrowser and something else, e.g. TextBox. If WebBrowser were a red circle instead, the TextBox would render on top of it. In case of WebBrowser, no TextBox is to be found anywhere. This is because TextBox is rendered as main window's background and WebBrowser is actually a HWND child of the main window obscuring the background.
So all is (not) well. How does one achieve the desired behavior? I want to have TextBox rendered on top of WebBrowser. Has anyone encountered this problem?
I am thinking along the lines of having a second transparent top-level borderless WPF window, re-parent it so that the main window owns it and do some other tricks to make it happen.
Before I dig in, I was wondering if anybody had an obvious or a simpler solution?
Update by Meleak
I'm offering this Bounty to anyone who can post an implementation of Ray Burns Answer AirRepair. I tried myself but in vain
Take a read through WPF Interoperation: "Airspace" and Window Regions Overview.
Suggested solution
I suggest a simple "AirRepair" class with this signature:
public class AirRepair : Decorator
{
public HwndHost Win32Host ... // DP bound to HwndHost
public Geometry RepairArea ... // Default is entire decorated control,
// or use OpacityMask
}
Used this way:
<Grid>
<WebBrowser x:Name="browser" ... />
<AirRepair Win32Host="{Binding ElementName=browser}"
Margin="10" HorizontalAlignment="Left" ...>
<TextBox ... />
</AirRepair>
</Grid>
AirRepair can be used with WebBrowser, WindowsFormsHost, or any other HwndHost. The area covered by the decorated control is displayed inside the Win32 content and it accepts focus and mouse events. For non-rectangular decorated controls, the area to display can be specified by the RepairArea and/or OpacityMask properties.
How it works
AirRepair solves airspace issues by:
Creating a child hWnd under the given HwndHost using HwndSource
Setting its hRgn to the appropriate area
Setting its RootVisual to a Border whose Background is a VisualBrush of the decorated control
Forwarding WM_MOUSEMOVE etc received by the child hWnd to the main WPF window
The result of this is that WPF continues to draw the content behind the Win32 content but AirRepair's child window redraws the same content in front of the Win32 content in a separate Win32 control.
Some important implementation details
Getting the parent hWnd
When Win32Host is originally set, it may or may not have a hWnd. The PropertyChangedCallback should use PresentationSource.AddSourceChangedHandler / PresentationSource.RemoveSourceChangedHandler to detect possible hWnd changes, then update its own hWnd pointer in a Dispatcher.BeginInvoke callback so the HwndHost has a chance to finish handling the SourceChanged event.
Creating the child hWnd
The child hWnd can be created, parented and hooked in managed code using the HwndSource class. Be sure to dispose it when the Win32Host's parent hWnd is no longer available.
Positioning the child hWnd
The child hWnd's window position (relative to its parent) can be computed as:
var compositionTarget = PresentationSource.FromVisual(this).CompositionTarget;
var position = compositionTarget.TransformToDevice(
this.TransformToVisual(Win32Host));
The UIELement.LayoutUpdated event should be used to keep this up to date.
Computing the hRgn and opacity
Optional: Omit if only rectangular repair areas are supported
When the RepairArea or OpacityMask is set and the child hWnd exists, use a RenderTargetBitmap to paint the RepairArea using the OpacityMask then create the hRgn from it. If RepairArea is null, use a rectangle. If OpacityMask is null, use black. The RenderTargetBitmap size is set by transforming the AirRepair decorator's coordinates to device coordinates. Note that this does not properly handle a variable OpacityMask such as an animated brush or a VisualBrush whose Visual is changing.
Drawing the content on the child hWnd
Use a VisualBrush whose Visual is the AirRepair decorator, not the decorated control. This allows the decorated control to be replaced without changing the content.
childHwndSource.RootVisual =
new Border
{
Background = new VisualBrush
{
Visual = this,
ViewBoxUnits = BrushMappingMode.Absolute,
ViewPortUnits = BrushMappingMode.Absolute,
}
};
Forwarding mouse messages
Add a hook using HwndSource.AddHook, then use Win32 PostMessage to the container:
childHwndSource.AddHook((hwnd, msg, wParam, lParam, handled) =>
{
// Check if message needs forwarding and update parameters if necessary
switch(msg)
{
default:
return; // Not recognized - do not forward
case WM_MOUSEMOVE:
...
}
var target = PresentationSource.FromVisual(this).CompositionTarget as HwndTarget;
if(target!=null)
Win32Methods.PostMessage(target.Handle, msg, wParam, lParam);
};
if you're looking for a quick and easy solution, just use the popup control, here's an example
http://karlshifflett.wordpress.com/2009/06/13/wpf-float-buttons-over-web-browser-control/
If you are specifically targeting a web browser, there have been a couple of attempts at this. Chris Cavanagh has created an excellent solution based on Chrome.
Related
My main window's Height, Left, Top, and Width are all bound to their respective viewmodel properties through a style. I can confirm that these four properties in the view model are only ever set to 1920, 1920, 118, 1080 respectively.
But when I launch the app, the Top and Width properties on the main window are set to something else (Width will be 1440 and Top will be a random number usually less than 300). What would cause this?
Here's what I see when I Snoop the app. Notice how Top and Width come from a Local Value Source:
Strangely, when I right-click on those properties in Snoop and tell it to "Clear/Reset", then those properties begin behaving. What is Snoop doing that fixes this?
Other facts:
The getters for the Top and Width viewmodel properties are only called once while the main window is being initialized. The stack trace runs through framework binding initialization code.
The setters for the Top and Width viewmodel properties are only called once from the viewmodel constructor as it sets those properties to 118 and 1080 respectively.
The bindings for these four properties are all two-way.
None of these things cause the view's properties to change/be correct:
Changing the associated viewmodel properties at runtime, even after the view has been fully loaded.
Calling UpdateLayout() on the view.
Calling InvalidateArrange() on the view.
Calling InvalidateMeasure() on the view.
Calling InvalidateProperty(FrameworkElement.WidthProperty) on the view.
Calling InvalidateVisual() on the view.
I have searched and searched and do not see any code anywhere touching the view's Top or Width properties (other than the style bindings).
Here's the style:
Sorry I had to blank out type names and some other things—it's a company application. If it helps, the main window/the view is at the end of a long inheritance line with Window as its great-great grandaddy. I'm trying to make the main window more reusable by MVVM-ing it—formerly these layout properties were set in code-behind in the view, and the view had constructor parameters :'( That's related to why I need to key the style, and why the style is based on other stuff. But none of the inherited types manipulate layout properties.
P.S. I've seen other people complain about how hard it is to resize WPF's Window. The most commonly suggested solution is to bind MinWidth and MaxWidth as well as Width. When I do that then the Width is indeed forced to the value I want, but you can't resize the window, the Width property still has its Local Value Source, and Top is still incorrect.
Given that "[Top] cannot be set through a style", and given the complications with binding Window.Width, I solved this a different way.
I created this attached property called WindowLayout and bound it to a viewmodel property in my style:
public static class WindowLayoutBehavior
{
public static readonly DependencyProperty LayoutProperty = DependencyPropertyHelpers.RegisterAttached(
(Window x) => GetLayout(x),
new PropertyMetadata(HandleLayoutChanged));
private static void HandleLayoutChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
if (!(d is Window window))
return;
if (!(e.NewValue is Rect rect))
return;
window.Height = rect.Height;
window.Left = rect.Left;
window.Top = rect.Top;
window.Width = rect.Width;
}
[AttachedPropertyBrowsableForType(typeof(Window))]
public static Rect GetLayout(Window window) =>
window.GetValue(LayoutProperty) is Rect rect
? rect
: default;
public static void SetLayout(Window window, Rect rect) =>
window.SetValue(LayoutProperty, rect);
}
DependencyPropertyHelpers.RegisterAttached is a shorthand helper method for creating the attached property in the way you might expect.
Usage in the style:
<Setter
Property="WindowLayoutBehavior.Layout"
Value="{Binding WindowLayout, Mode=OneWay}"/>
Now when I Snoop the app, Height, Left, Top, and Width all show as having Local Value Sources, and they change when the viewmodel property changes, so that works for me.
How can i make, what i will call, a "popup" window" in WinForms?
Since i used my own made-up word "popup", let me give examples of this so-called "popup" window:
a tooltip window (can extend outside the boundaries of its parent form, doesn't appear in the taskbar, is not modal, and doesn't steal focus):
a popup menu window (can extend outside the boundaries of its parent form, doesn't appear in the taskbar, is not modal, and doesn't steal focus):
a drop-down window (can extend outside the boundaries of its parent form, doesn't appear in the taskbar, is not modal, and doesn't steal focus):
A main menu window (can extend outside the boundaries of its parent form, doesn't appear in the taskbar, is not modal, and doesn't steal focus):
Update A popup window not make itself the "active" window when interacted with using a mouse or keyboard (the "owner" window remains the active window):
The attributes that i'm looking for in this mythical "popup" are that it:
can extend outside the boundaries of its parent form (i.e. is not a child window)
doesn't appear in the taskbar (i.e. Window's heuristics of which windows should appear doesn't kick in, nor does it have WS_EX_APPWINDOW extended window style)
is not modal (i.e. doesn't disable its "owner")
doesn't steal focus
is always on-top of of it's "owner"
does not become the "active" window when interacted with (the owner remains active)
Windows applications are already managing to create such windows. How can i do it in a WinForms application?
Related questions
How do i achieve all the above in native code?
How do i create a popup window in Delphi?
i have this native code to show a "popup" window - what P/Invokes are required to perform the same actions in .NET?
i have a set of P/Invoke's in .NET - can i reuse a regular WinForm, overriding certain methods, to achieve the same effect?
i have WinForm that i'm showing as a "popup" by overriding certain methods - is there a built-in Control that can act as a popup for me?
How to simulate a drop-down window in WinForms?
Attempt#1
i tried the Show(onwer) + ShowWithoutActivation method:
PopupForm dd = new PopupForm ();
dd.Show(this);
with PopupForm:
public class PopupForm: Form
{
public PopupForm()
{
InitilizeComponent();
}
private void InitilizeComponent()
{
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
this.WindowState = FormWindowState.Normal;
this.ShowInTaskbar = false;
}
protected override bool ShowWithoutActivation
{ get { return true; } }
}
Very nearly solved the problem, but then i discovered was reminded of another property of "popup" windows: they do not take focus from their "owner" form become active when interacted with by mouse or keyboard.
You want an owned window. In your main form:
private void showPopup_Click(object sender, EventArgs e)
{
PopupForm popupForm = new PopupForm();
// Make "this" the owner of form2
popupForm.Show(this);
}
PopupForm should look like this:
public partial class PopupForm : Form
{
private bool _activating = false;
public PopupForm()
{
InitializeComponent();
}
// Ensure the popup isn't activated when it is first shown
protected override bool ShowWithoutActivation
{
get
{
return true;
}
}
private const int WM_NCACTIVATE = 0x86;
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
protected override void WndProc(ref Message m)
{
// The popup needs to be activated for the user to interact with it,
// but we want to keep the owner window's appearance the same.
if ((m.Msg == WM_NCACTIVATE) && !_activating && (m.WParam != IntPtr.Zero))
{
// The popup is being activated, ensure parent keeps activated appearance
_activating = true;
SendMessage(this.Owner.Handle, WM_NCACTIVATE, (IntPtr) 1, IntPtr.Zero);
_activating = false;
// Call base.WndProc here if you want the appearance of the popup to change
}
else
{
base.WndProc(ref m);
}
}
}
And make sure that PopupForm.ShowInTaskbar = false.
I was curious as to how combobox dropdowns and menus work, so I did some more research.
There are two basic approaches.
Create the popup as an overlapped window, owned by the main window
This method is required if the popup has embedded controls, or if you want the popup to behave as a modeless dialog.
If the user is going to interact with child controls in the popup window, it must receive activation. (So the various techniques for blocking activation, such as handling WM_MOUSEACTIVATE are red herrings.) And when it receives activation, Windows will deactivate the main window. The fix for this is to send a WM_NCACTIVATE message to the parent to update its visual appearance without changing its activation status. This is the approach used by the .Net ToolStrip, and my other answer illustrates it with code.
Create the popup as a child of the Desktop window
This method is used by combobox dropdowns and (I guess) menus, but you can't embed child controls so it's not widely applicable.
The popup is a child window so it doesn't interfere with activation. It is always on top. Mouse capture is used to detect clicks outside the popup and dismiss it.
However, this isn't straightforward to implement. Activation remains with the main application, so it keeps the focus and receives keystrokes. This seems to preclude embedded controls in the popup because they can't receive focus. The combobox handles this by forwarding keystroke messages to its dropdown list. (Note that menus, combobox dropdowns, etc. are all entirely owner-draw in the sense that they have no embedded windows.)
Create your "popup" window as a child window of desktop, then show it without activating it.
hWnd = CreateWindowEx(..., WS_CHILDWINDOW | WS_VISIBLE | WS_BORDER | WS_CLIPSIBLINGS, ..., GetDesktopWindow(), ...);
SetWindowPos(hWnd, HWND_TOPMOST, ..., SWP_NOACTIVATE);
After doing this, your original window remains activated even if you click on the "popuped" window. The "popup" window can have its own children controls. You can click the button on it. But if it is an edit control, you cannot edit it, I don't know why. Maybe because there is already a cursor on your original window, blinking.
I'm working on an XBAP app where Users primarily use the Keyboard for Navigation. When I display a MessageBox, I can hit Enter to close it but then the main application doesn't seem to regain focus. I have to manually click the mouse on the screen to put focus back on the application.
Is there a way around this?
Edit
I can verify that the app still has Logical Focus, but it just doesn't have Keyboard Focus
I found a hack that works, although I don't like it because I feel it ties my Views to my ViewModel
I'm using an IsFocused AttachedProperty to bind a control to a boolean property behind the View. The same View is also subscribing to a DisplayError event that displays a MessageBox error and reset the IsFocused property afterwards so it updates the UI. Last change made was to update my ViewModels to publish errors to the EventAggregator instead of handling themselves with a MessageBox, which is probably better anyways.
I suppose it works, even if I don't like it
Not sure if this will help your situation but in my circumstance it was ok for me to set focus back to main window, which was able to be accomplished with
App.Current.MainWindow.Focus();
Just be sure main window is properly initialized, which may not be the case if a splash screen or some login window or something initially grabbed the main window role (ie by StartupUri) and then nothing else was updated thereafter.
This worked for me since I was handling all keyboard events at the main window level to drive updates to my view model.
using System.Runtime.InteropServices;
using System.Windows.Interop;
public class Interop
{
[DllImport("user32.dll")]
public static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern IntPtr GetForegroundWindow();
public static IntPtr GetWindowHandle(Window window)
{
return new WindowInteropHelper(window).Handle;
}
}
// In main window, when the MessageBox is closed
IntPtr window = Interop.GetWindowHandle(this);
IntPtr focused = Interop.GetForegroundWindow();
if (window != focused)
{
Interop.SetForegroundWindow(window);
}
http://tech.avivo.si/2009/11/how-to-focus-window-in-wpf-when-it-gets-out-of-focus/
I've run into a puzzling limitation in a Silverlight 4 UserControl.
What I'm trying to achieve is to have a panel, which slides out from a minimised state when a button is pressed, but the title bar of it should be draggable with which this maximised state can be resized.
What I've done for the sliding out is to animate the MaxHeight property of the parent Grid of this panel which works quite well even with no hardcoded Height for the panel, but I don't know how can I make this dynamic.
Trying to bind a variable from the code-behind to the 'To' parameter of the 'DoubleAnimation' didn't work, it just silently gets ignored.
As I'm creating UserControls to represent Views, the elements with x:Name properties won't get autogenerated.
I tried to work around this using the code below which mimics what happens in the autogenerated code (with the added bonus of only being done after the layout is actually loaded):
public DoubleAnimation PanelOpenMaxHeightDoubleAnimation;
private void LayoutRoot_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
var LayoutRootreference = sender as Grid;
PanelOpenMaxHeightDoubleAnimation = ((DoubleAnimation)(LayoutRootreference.FindName("PanelOpenMaxHeightDoubleAnimation")));
PanelOpenMaxHeightDoubleAnimation.To = 383;
}
This however breaks when trying to set the value of To, as FindName returns null (I have x:Name manually set in XAML for this particular animation to "PanelOpenMaxHeightDoubleAnimation"). I have the sneaking suspicion FindName can't pick DoubleAnimations up from VisualStates, only actual layout children?
I did find the documentation about XAML Namescopes at http://msdn.microsoft.com/en-us/library/cc189026(v=VS.95).aspx#UserControls, but didn't really understand what my options are from this paragraph (other than being very limited):
For the case of a UserControl, there is no equivalent template part attribute convention for parts of the UserControl in the definition XAML, nor is there a template applied at all. Nevertheless, the namescopes between definition and usage remain disconnected, because the definition namescope is defined and then effectively sealed when you package your UserControl into an assembly for reuse. A best practice here is to define your UserControl such that any value that needs to be set to modify the definition XAML is also exposed as a public property of the UserControl.
What does it mean by the last sentence?
Wondering can I do next? Should I try to generate the entire state from code?
Well, managed to work it out so I'm sharing the solution.
Instead of trying to get a reference to the DoubleAnimation in Resources, I named the Grid in the layout I want to animate and get a reference to that using the code in the original question:
var SlidePanel = ((Grid)(LayoutRootreference.FindName("SlidePanel")));
This does return the element and using that it's possible to create a DoubleAnimation and a Storyboard from scratch purely in code. I just used this code example as a starting point: http://msdn.microsoft.com/en-us/library/cc189069(VS.95).aspx#procedural_code
Best part is, you can change the DoubleAnimation.To parameter even after setting everything up in the Storyboard, so now what I'm doing is just resetting that to my calculated value every time before calling Storyboard.Begin().
It's a bit fiddly to set all these up manually, but at least it works nicely once you do.
I'm hosting a WPF UserControl in a WinForms container. Now, I want to be able to theme/skin the UserControl. To do this, I've got several resource dictionaries that define the "skins." When my app starts up I create a "new System.Windows.Application()" so that Application.Current exists. To change the theme the old skin is removed and a new skin is merged into the Application level resource dictionary at runtime. However, this does not change any of the dyanamically referenced resources in the UserControl. I tried this in a straight WPF application and it worked just fine. Am I missing something, or is it not possible to do this at all? By the way, if I add a skin into the application resources before the UserControl is initialized it will work but I cannot change the skin after that.
To repo this in the most basic way:
Create a new WinForms application. Add a WPF UserControl to the app. This is simple enough:
<UserControl ...>
<Grid>
<Button
Background="{DynamicResource ButtonBG}"/>
</Grid>
</UserControl>
Create two ResourceDictionaries, White.xaml and Black.xaml (or whatever) that have a SolidColorBrush with the key ButtonBG with respective color. In Form1.cs, add two Buttons and an ElementHost. Set the child of the ElementHost to an instance of the UserControl we just created. Wire up the buttons to events that swap the skin:
private void White_Click(object sender, EventArgs e)
{
Application.Current.Resources.MergedDictionaries[0] =
(ResourceDictionary)Application.LoadComponent(
new Uri(#"\WpfThemes;component\White.xaml", UriKind.Relative)));
}
private void Black_Click(object sender, EventArgs e)
{
Application.Current.Resources.MergedDictionaries[0] =
(ResourceDictionary)Application.LoadComponent(
new Uri(#"\WpfThemes;component\Black.xaml", UriKind.Relative)));
}
In Program.cs, ensure that Application.Current exists and set the initial skin:
[STAThread]
static void Main()
{
new System.Windows.Application();
Application.Current.Resources.MergedDictionaries[0] =
(ResourceDictionary)Application.LoadComponent(
new Uri(#"\WpfThemes;component\White.xaml", UriKind.Relative)));
...
}
Now, when the White button is clicked I would expect the button in the UserControl to turn white and when the Black button is clicked I would expect the button to turn black. This does not happen, however.
Does anyone know why? Is there a solution?
Edit: Idea: Perhaps, if there's a way to force re-evaluation of DynamicResources when the theme changes, that would work.
Thanks,
Dusty
I think this may be an overlooked issue in the WPF framework.
From what I can tell via Reflector, it appears that when the Application resource dictionary is catastrophically changed (a change that will likely have wide ranging effects like adding, removing, or replacing a skin), there is code that loops over all of the Windows in the application and forces them to re-evaluate their DynamicResources. However, other elements that I would consider top-level in WPF like ElementHosts do not get the same treatment. This leads to the behavior that I'm experiencing.
My workaround to this issue is to manually go through all of my ElementHosts individually and add, remove, or replace the skin ResourceDictionary file. It's not perfect, but it gets the job done.
Dr. WPF came to my rescue when I was trying to do something similar. He shows how to create the Application object in WinForms. Now you can reference everything as StaticResource just like in a WPF application.
http://drwpf.com/blog/2007/10/05/managing-application-resources-when-wpf-is-hosted/
Another workaround would be to create a dummy window and specify the content of the elementhost as content.
If you look into the Application and check how it handles changes of resourcedictionaries, you see that it only notifies windows..
The only thing you should remind is to never show the window (-> exception), and to close it when disposing the elementhost, so the application can shutdown properly.