There are two (it would seem) idiomatic ways to minimize a WPF window in code:
window.WindowState = window.WindowState.Minimized
SystemCommands.MinimizeWindow(window)
The desired behavior is that the windows 11 window minimize animation is seen. The actual behavior is that the window instantly disappears from the screen.
Please note: This is for a window with a custom chrome - therefore triggering the behavior from code seems necessary:
<Window
WindowStyle="None"
AllowTransparency="True
Background="Transparent">
<!-- custom windows chrome here -->
</Window>
Is this a defect in the WPF desktop pack for .NET 5 (and 6)?
How does one not get the desired behavior without reverting back to default windows with standard chrome? It is not an option to use a WinUI 3.0 window since XAML islands have not been released yet and gradual modernization is needed.
There are some proposed solutions on the web, like:
https://coderedirect.com/questions/352460/custom-window-style-with-minimize-animation
And the other (old) stackoverflow question:
Custom window style with minimize animation
However, I could not get it to work with a window with a transparent background.
===== update after issue was closed =====
Reviewer: please read this carefully before voting to close the issue again and point to exact workable solution.
Issues with existing answers:
First suggestion is to change WindowStyle, then WindowState; this poses a problem since it requires AllowTransparency=false which defeats the purpose of the exercise here - which is getting a custom chrome.
Other suggestion is to use Win32 API's. Again I tried to do that but it did not work in my setting. I haven't logged the exact error messages I got though. However, looking at the code again there is an obvious issue with it: it has the same window requirement as the first suggestion: the window must not be transparent - observe code from the old answer:
private IntPtr WindowProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == ApiCodes.WM_SYSCOMMAND)
{
if (wParam.ToInt32() == ApiCodes.SC_MINIMIZE)
{
WindowStyle = WindowStyle.SingleBorderWindow;
WindowState = WindowState.Minimized;
handled = true;
}
else if (wParam.ToInt32() == ApiCodes.SC_RESTORE)
{
WindowState = WindowState.Normal;
WindowStyle = WindowStyle.None;
handled = true;
}
}
return IntPtr.Zero;
}
It seems to be on the roadmap in Themes of .NET for .NET 7:
Epic - Support Windows 11 look and feel for WPF controls
Also this amazing piece of open-source just landed:
WPF UI - Windows 11 styles and controls for WPF
Related
I'm currently working on a WPF application which will run on Windows 8.1 tablet and I don't find any posts about the following issue :
My application needs to be full screen, so, I set for my views :
WindowState="Maximized"
WindowStyle="ToolWindow"
All right - my application will be displayed in full screen mode:
But, if I rotate the tablet a space is allocated and the application is not in full screen.
I don't want this scenario to happen, the application should stay always in full screen.
I tried to listen SystemEvents.DisplaySettingsChanged event and manually set full screen:
SystemEvents.DisplaySettingsChanged += Current_SizeChanged;
private void Current_SizeChanged(object sender, EventArgs eventArgs)
{
this.WindowStyle = WindowStyle.None;
this.ResizeMode = ResizeMode.NoResize;
this.WindowState = WindowState.Maximized;
this.UpdateLayout();
}
As you can see, i tried even to update layout, but still, not working!
What is strange, is the fact that if you start the application in any position but not in the normal one, it will work. For ex: if the application is started in portrait mode, rotation will not change the dimensions of the window, but if you run the application starting from landscape mode... the bug appears.
You can debug this issue using Ctrl + Alt + Arrows.
Any suggestions?
Edit: It seems that the problem is caused by keyboard. The reserved zone is for keyboard, but i don't find a way to resize to full screen. Actual width, Width and Desired Width are all the same...
Edit2: This bug can be reproduced only on Windows 8.1
You can use DisplaySettingsChanged event of the SystemEvents class. Here is an example -> How to Detect Screen Rotation
Use a property in viewmodel which will set in the Current_SizeChanged method. Then put a data trigger on this boolean and apply RotateTransform 90deg to your LayoutTransform.
A workaraund for this bug is to listen to DisplaySettingsChanged and manually set windows state to normal and after windows state to maximized, like below :
SystemEvents.DisplaySettingsChanged += Current_SizeChanged;
private void Current_SizeChanged(object sender, EventArgs eventArgs)
{
this.WindowState = WindowState.Normal;
this.WindowState = WindowState.Maximized;
}
This is caused by :
WindowStyle="ToolWindow"
Hope microsoft will solve this bug (I submitted the bug on WPF threads on MSDN). Thank you for your help!
One of our customers hosts our WinForms .NET grid control iGrid.NET (http://www.10tec.com/) inside a WPF ElementHost container together with other WPF controls. It may look strange as it's a WinForms control inside a WPF host inside a WinForms form, but they have no choice because of the other WPF stuff they use (it's the AvalonDock http://avalondock.codeplex.com/ docking container).
The problem is that our .NET datagrid control's infrastructure requires to know the parent WinForms form, but the following construction we use for that always return null in this situation:
Form myTopLevelOwnerForm = fCurrentGrid.TopLevelControl as Form;
I.e. the standard Control.TopLevelControl property intended for this purpose returns null - though most likely it should be so in the case of WPF host.
The question is: are there other ways to know the parent form from the current control's code? Say, using WinAPI handles or better other native .NET memebrs?
The following code works. At least, in our project :)
// API declaration
[System.Runtime.InteropServices.DllImport("user32.dll", ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Auto)]
public static extern IntPtr GetParent(IntPtr hWnd);
// Main code snippet
Control myTopLevelControl = fOwner.TopLevelControl;
if (myTopLevelControl == null)
{
IntPtr handle = fOwner.Handle;
while (true)
{
IntPtr parentHandle = GetParent(handle);
if (parentHandle == IntPtr.Zero)
{
myTopLevelControl = Control.FromHandle(handle) as Form;
break;
}
handle = parentHandle;
}
}
I'm writing an industrial process control application to run on a PC, using .Net. The program monitors the progress of various parts being assembled by teams on the factory floor. There can be an arbitrary number of parts - 1,2,3,4,5, etc, and in the old VB6 version of the app each part gets its own window and the operators like to arrange them on the screen.
What I'm describing is a classic MDI interface but WPF doesn't support MDI. Other threads on SO have suggested the wpfmdi project on Codeplex, but that's listed as "abandoned" since last February ( http://wpfmdi.codeplex.com ) and avalondocks but those are docking tiles that don't look like they can be arbitrarily dragged and moved.
I don't know what to do. I didn't want to use WinForms because WPF/XAML provides cooler visuals and easier styling and because Microsoft seems to have abandoned WinForms. The current VB6 version of this product is 12 years old and I'd like to plan on a similar lifespan for the new one.
Thanks in advance!
I think you should consider using a payed-for third party component for MDI support. Nearly all of the standard vendors, DevExpress, Component One, Infragisitcs, Telerik provide an MDI solution.
Personally, I think MDI is still an entirely valid application UI structure!
I found the answer on another discussion forum (I can't remember which one or I'd give them credit). It turned out to be easier than I thought. If you hook the WM_MOVING message (I do it, below when the window is loaded) you can intercept moves before the window is moved and constrain the location of the window.
private void Window_Loaded(object sender, RoutedEventArgs e)
{
WindowInteropHelper helper = new WindowInteropHelper(this);
HwndSource.FromHwnd(helper.Handle).AddHook(HwndMessageHook);
InitialWindowLocation = new Point(this.Left, this.Top);
}
// Grab the Win32 WM_MOVING message so we can intercept a move BEFORE
// it happens and constrain the child window's location.
private IntPtr HwndMessageHook(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam, ref bool bHandled)
{
switch (msg)
{
// might also want to handle case WM_SIZING:
case WM_MOVING:
{
WIN32Rectangle rectangle = (WIN32Rectangle)Marshal.PtrToStructure(lParam, typeof(WIN32Rectangle));
if (rectangle.Top < 50)
{
rectangle.Top = 50;
rectangle.Bottom = 50 + (int)this.Height;
bHandled = true;
}
if (rectangle.Left < 10)
{
rectangle.Left = 10;
rectangle.Right = 10 + (int)this.Width;
bHandled = true;
}
if (rectangle.Bottom >800)
{
rectangle.Bottom = 800;
rectangle.Top = 800 - (int)this.Height;
bHandled = true;
}
// do anything to handle Right case?
if (bHandled)
{
Marshal.StructureToPtr(rectangle, lParam, true);
}
}
break;
}
return IntPtr.Zero;
}
The XAML header looks like this:
<Window x:Class="Mockup_9.Entity11"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Mockup_9"
ShowInTaskbar="False"
Background="LightGoldenrodYellow"
Loaded="Window_Loaded"
Title="Mockup_part -" Height="540" Width="380" ResizeMode="NoResize"
Icon="/Mockup_9;component/Images/refresh-icon1.jpg">
. . . etc.
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.
I am trying to use this function from a COM API which enables the window to receive drops (as in drag & dop) from another application.
It is pretty straightforward in Windows Forms and works:
public void EnableDropSupport(System.Windows.Forms.Form form)
{
IntPtr hwnd = form.Handle;
_comAPI.RegisterDropWindow((int)hwnd);
}
But I have a WPF window where it does not work and I don't understand why. I have tried the following:
public void EnableDropSupport(System.Windows.Window window)
{
window.AllowDrop = true;
WindowInteropHelper windowInteropHelper = new WindowInteropHelper(window);
IntPtr hwnd = windowInteropHelper.Handle;
_comAPI.RegisterDropWindow((int)hwnd);
}
The last two lines are basically identical but it will just not work in WPF. While
window.AllowDrop = true;
will make it appear as if it will accept the drop, the drop event of that COM API is not raised.
Am I missing something or can someone help?
This is Pavel Minaev's answer (which he posted as a comment to the question) which was correct:
You're not missing anything on WPF
side of things. Most likely the
problem is with RegisterDropWindow.