WPF: Make window unresizeable, but keep the frame?
Asked Answered
C

4

8

I have a window that does not have a title bar (WindowStyle == WindowStyle.None). The entire window uses the Aero glass effect. When I make the window unresizeable (ResizeMode == ResizeMode.NoResize), the glass effect disappears and my controls just hang in midair. (Essentially, the window itself disappears but leaves its contents.)

Is there a way for me to make the window unresizeable without getting rid of the window frame?


I have read the question Enable Vista glass effect on a borderless WPF window, but that's not quite what I want--I would like to keep the window border. For an example of what I would like my window to look like, hit Alt+Tab with Aero enabled.


To clarify, I do no want the resize cursors to show up at all when hovering over the window border. This is essentially what I want my window to look like:

Projector

The solution doesn't have to be strictly WPF--I am fine with hacking around with the Win32 API in order to achieve this.

Cenac answered 2/8, 2010 at 9:26 Comment(0)
C
13

You can hook the wndproc and intercept the WM_WINDOWPOSCHANGING message. Not strictly WPF, but probably your best bet.

If you want to hide the resize cursors, then your best bet is to intercept WM_NCHITTEST. Call the DefWindowProc (to get the default behavior), and test the return value; if it's HTBOTTOM, HTBOTTOMLEFT, HTBOTTOMRIGHT, HTTOP, HTTOPLEFT, or HTTOPRIGHT, change the return value to HTBORDER.

Coats answered 11/8, 2010 at 20:54 Comment(2)
You would still have the resize cursors, and OP said (in another comment) that those are undesirable.Photoreceptor
Worked like a charm! Thanks! Enjoy 275 more rep. :)Cenac
A
11

Based on Erics answer.

Example Image

public partial class MainWindow : Window
{
    [DllImport("DwmApi.dll")]
    public static extern int DwmExtendFrameIntoClientArea(
        IntPtr hwnd,
        ref MARGINS pMarInset);

    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public static extern IntPtr DefWindowProc(
        IntPtr hWnd,
        int msg,
        IntPtr wParam,
        IntPtr lParam);

    private const int WM_NCHITTEST = 0x0084;
    private const int HTBORDER = 18;
    private const int HTBOTTOM = 15;
    private const int HTBOTTOMLEFT = 16;
    private const int HTBOTTOMRIGHT = 17;
    private const int HTLEFT = 10;
    private const int HTRIGHT = 11;
    private const int HTTOP = 12;
    private const int HTTOPLEFT = 13;
    private const int HTTOPRIGHT = 14;

    public MainWindow()
    {
        InitializeComponent();

        this.Loaded += new RoutedEventHandler(MainWindow_Loaded);
    }

    void MainWindow_Loaded(object sender, RoutedEventArgs e)
    {
        try
        {
            // Obtain the window handle for WPF application
            IntPtr mainWindowPtr = new WindowInteropHelper(this).Handle;
            HwndSource mainWindowSrc = HwndSource.FromHwnd(mainWindowPtr);
            mainWindowSrc.CompositionTarget.BackgroundColor = Color.FromArgb(0, 0, 0, 0);
            mainWindowSrc.AddHook(WndProc);

            // Set Margins
            MARGINS margins = new MARGINS();
            margins.cxLeftWidth = 10;
            margins.cxRightWidth = 10;
            margins.cyBottomHeight = 10;
            margins.cyTopHeight = 10;

            int hr = DwmExtendFrameIntoClientArea(mainWindowSrc.Handle, ref margins);
            //
            if (hr < 0)
            {
                //DwmExtendFrameIntoClientArea Failed
            }
        }
        // If not Vista, paint background white.
        catch (DllNotFoundException)
        {
            Application.Current.MainWindow.Background = Brushes.White;
        }
    }

    private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
    {
        // Override the window hit test
        // and if the cursor is over a resize border,
        // return a standard border result instead.
        if (msg == WM_NCHITTEST)
        {
            handled = true;
            var htLocation = DefWindowProc(hwnd, msg, wParam, lParam).ToInt32();
            switch (htLocation)
            {
                case HTBOTTOM:
                case HTBOTTOMLEFT:
                case HTBOTTOMRIGHT:
                case HTLEFT:
                case HTRIGHT:
                case HTTOP:
                case HTTOPLEFT:
                case HTTOPRIGHT:
                    htLocation = HTBORDER;
                    break;
            }

            return new IntPtr(htLocation);
        }

        return IntPtr.Zero;
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        this.Close();
    }
}

[StructLayout(LayoutKind.Sequential)]
public struct MARGINS
{
    public int cxLeftWidth;      // width of left border that retains its size
    public int cxRightWidth;     // width of right border that retains its size
    public int cyTopHeight;      // height of top border that retains its size
    public int cyBottomHeight;   // height of bottom border that retains its size
};

<Window x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="150" Width="200" 
    Background="Transparent"
    WindowStyle="None"
    ResizeMode="CanResize"
>
    <Grid Background="White" Margin="10,10,10,10">
        <Button Content="Go Away" Click="Button_Click" Height="20" Width="100" />
    </Grid>
</Window>
Aerometry answered 26/8, 2010 at 3:26 Comment(5)
I'm going to give the answer to Eric to be fair, since he first posted the idea, but thank you for the working example. :)Cenac
That's why I mentioned it. I just wanted to see if I could get it working.Aerometry
Shouldn't DefWindowProc be called in all cases though? In this code you only call the original function if the msg is WM_NCHITTEST. I'd imagine that to do the override correctly, any other message should simply return the result given by the original function.Endue
This doesn't quite work. A white border is added to the inner frame of the window. That's fine if you're using a white background, but otherwise it won't work.Detainer
@Ed S. In the MainWindow_Loaded event handler, change the margins on all sides from 10 to 12 will move the border to underneath the WPF window area, effectively hiding it.Aerometry
H
1

One hackish way to do it would be to set the MinWidth/MaxWidth and MinHeight/MaxHeight properties to effectively make it unresizeable. Of course, the problem there is you'll still get the resize cursors over the borders.

Headline answered 2/8, 2010 at 15:55 Comment(1)
Well the whole point is to get rid of the resize cursors, so that won't help, unfortunately.Cenac
F
1

Why don't you just create this window border for the window? It's using an offset to set the colors of the window. So, an easy way is just to wrap a whole border around your window and on top of that you get your own colors!

Forethought answered 2/8, 2010 at 16:0 Comment(7)
An when I mean wrap it around a window, i mean wrap basically <border> <grid/> </border>Forethought
But then I can't make it match the user's current theme. I could make a custom border that looks exactly like the default Aero theme, but if the user has customized it (or is using a different theme), the window wouldn't match.Cenac
I'm not throwing this answer away, though, I may end up having to do this.Cenac
well... if you want customized colors. Why don't you just bind the offset and let the user's select there own colors? if you are using the MVVM approach, in your view model set a string with the color code and bind it into the offset, wouldn't that work?Forethought
and if you want something to change based on key strokes. make a property that does this : public void OnKeyChanged (object sender, keyboardEventArgs e ) { if(e.Key = Tab && e.Key = Alt) { PropertyColor = "White" } else PropertyColor = Blue (but use color codes) }Forethought
That's not my point. I want the window frame to match the user's current theme. So if they are using some custom black Aero theme like this, I want the window to look like that as well. Making the user go through the trouble of creating their own window theme that doesn't even fully match their selected one is unacceptable, and having the window be hard-coded to use the default Aero style (as a compromise) is a sub-par solution (though I might have to resort to that).Cenac
Why can't you use WPF's built-in support? E.g., a DynamicResource to SystemColors? (msdn.microsoft.com/en-us/library/…)Pinna

© 2022 - 2024 — McMap. All rights reserved.