I have a ListView whose columns I'd like to prevent being resized. I'm using code similar to this question, however my HDN_BEGINTRACK message isn't recognised.
My code to create the ListView:
HWND Instructions_ListView = CreateWindowEx(LVS_EX_DOUBLEBUFFER |
LVS_EX_FULLROWSELECT, WC_LISTVIEW, L"", WS_CHILD | WS_VISIBLE |
LVS_REPORT | LVS_EDITLABELS, 320, 50, 300, 400, hWnd, NULL, NULL, NULL);
My code to handle the header events follows. WM_NOTIFY is inside of WndProc for the main program window:
case WM_NOTIFY:
{
UINT debugval = (((LPNMHDR)lParam)->code);
switch (((LPNMHDR)lParam)->code)
{
case HDN_BEGINTRACKA:
case HDN_BEGINTRACK:
{
::MessageBox(hWnd, L"RESIZE", L"", MB_OK);
break;
}
}
break;
}
When debugging the value of debugval is 4294966969 when breaking on (what should be) the HDN_BEGINTRACK event.
Absolutely stumped as to why it's not working as intended; any help would be greatly appreciated.
The ListView's header control is a child of the ListView, so the header's WM_NOTIFY notifications will be sent to the ListView itself, not to your parent window. As such, your WndProc will not see them.
To catch WM_NOTIFY (and WM_COMMAND) messages sent by the ListView's internal child controls, you need to subclass the ListView using SetWindowLongPtr(GWL_WNDPROC) or SetWindowSubclass().
FYI, HDN_BEGINTRACKA has a value of 4294966990 (-306, hex 0xFFFFFECE), and HDN_BEGINTRACKW has a value of 4294966970 ( -326, hex 0xFFFFFEBA).
You say you are getting a WM_NOTIFY notification with a code of 4294966969. That is 0xFFFFFEB9 (dec -327), which is the HDN_ENDTRACKW notification.
When using COMCTL32 version 5, applications need to send common controls a CCM_SETVERSION message to take advantage of new functionality and fixes not available in earlier versions. The list view control does not forward all header notifications unless the control version is greater than or equal to 5. The List View control in COMCTL32 version 6 forwards all header notifications without sending the control a CCM_SETVERSION message.
So in your sample after creation of list view please add the below line
SendMessage(Instructions_ListView, CCM_SETVERSION, 5, 0);
Related
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
I have a win32api application written in c here. All controls in main window are created manually like this:
hEditSource = CreateWindowEx(WS_EX_CLIENTEDGE, L"EDIT", NULL,
WS_VISIBLE | WS_CHILD | WS_TABSTOP | ES_MULTILINE | ES_READONLY,
someLeft, someTop, someWidth, someHeight,
hWndMain, NULL, hInst, NULL);
At first I didn't apply IsDialogMessage test in main message loop so all controls' tab stop won't work. Now I have it done, every control are OK except the multi-line textbox above. Indeed nothing happens when press tab in it. No focus moving, no tab charactor inserting(it will discard read-only style afterwards).
Other textboxes are all single-line ones, looks like this:
editSearch = CreateWindowEx(WS_EX_CLIENTEDGE, L"EDIT", NULL,
WS_VISIBLE | WS_CHILD | ES_AUTOHSCROLL | WS_TABSTOP,
someLeft, someTop, someWidth, someHeight,
hWndMain, NULL, hInst, NULL);
It seems sth related to multiple line style is the cause. But in C# winform, it is very easy to create a multi-line edit with working tab-stop (it is the default behavior of a textbox, whether it is multi-line). I have tried to use spy++, to find if there is any clue in window style. However, 2 textboxes' window style are indentical if the only difference is the "Accept Tab" property.
Rightnow I cannot find another way to locate the root cause. Does anyone have a clue? Any help will be appreciated.
The behaviour of the IsDialogMessage is influenced by how the controls respond to WM_GETDLGCODE. As documented, for a multi-line edit control the following is returned by the default window procedure:
DLGC_WANTCHARS | DLGC_HASSETSEL | DLGC_WANTARROWS | DLGC_WANTALLKEYS
The inclusion of DLGC_WANTALLKEYS stops IsDialogMessage from responding to TAB and moving the focus to the next control. So, you will need to subclass your multi-line edit control and remove that flag. The sub-classed window procedure might look like this:
LRESULT CALLBACK MultiLineEditWndProc(HWND hWnd, UINT message, WPARAM wParam,
LPARAM lParam)
{
LRESULT res = CallWindowProc(wpOld, hWnd, message, wParam, lParam);
switch (message)
{
case WM_GETDLGCODE:
res &= ~DLGC_WANTALLKEYS;
}
return res;
}
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 have a WPF app with a usercontrol that contains a HwndHost. The HwndHost is created as follows:
hwndHost = CreateWindowEx(0, "static", "",
WS_CHILD | WS_VISIBLE,
0, 0,
hostHeight, hostWidth,
hwndParent.Handle,
(IntPtr)HOST_ID,
IntPtr.Zero,
0);
hwndControl = CreateWindowEx(0, "Static", "",
WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN
,
0, 0,
hostHeight, hostWidth,
hwndHost,
(IntPtr)PICTUREBOX_ID,
IntPtr.Zero,
0);
I then hook into the message pump using HwndSourceHook and loads of messages come through.
Except the ones I want i.e. WM_MOUSEMOVE, WM_MOUSEHOVER, WM_LBUTTONDOWN and WM_LBUTTONUP
Also the OnMouseLeftButtonDown event is not fired in the WPF code on the main window or the control, I assume because windows is trapping it and throwing it away.
Anybody know how I can get these to come through, either with or without using the WIN32 window messages?
You need to handle WM_NCHITTEST. Until you do, it won't send you mouse messages.
// C++/CLI implementation of HwndHost.WndProc().
virtual IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, bool% handled) override
{
switch (msg)
{
case WM_NCHITTEST:
{
handled = true;
return IntPtr(HTCLIENT);
}
}
return IntPtr::Zero;
}
It would appear that the Border element in WPF prevents the OnMouseLeftButtonDown being thrown. My temporary solution until I find a better one is to change the WPF border to a WPF button then all mouse events are triggered.
Not ideal for most people but as I am using it to render 3D onto it doesn't matter what is underneath it.
Why don't you register a WndClass and get messages delivered to your own WndProc instead of hooking the message pump? I suspect you'd get much better results.
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.