views:

306

answers:

4

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.

A: 

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.

RQDQ
Well the whole point is to get rid of the resize cursors, so that won't help, unfortunately.
musicfreak
+1  A: 

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!

Kevin
An when I mean wrap it around a window, i mean wrap basically<border><grid/></border>
Kevin
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.
musicfreak
I'm not throwing this answer away, though, I may end up having to do this.
musicfreak
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?
Kevin
Kevin
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](http://browse.deviantart.com/customization/skins/windows7/visualstyle/#/d2vcxx3), 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).
musicfreak
Why can't you use WPF's built-in support? E.g., a DynamicResource to `SystemColors`? (http://msdn.microsoft.com/en-us/library/system.windows.systemcolors.aspx)
Greg D
+6  A: 

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.

Eric Brown
Interesting, I'll take a look and let you know how it goes (not at my computer at the moment). Thanks. :)
musicfreak
You would still have the resize cursors, and OP said (in another comment) that those are undesirable.
Joe White
Worked like a charm! Thanks! Enjoy 275 more rep. :)
musicfreak
+3  A: 

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>
Cameron MacFarland
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. :)
musicfreak
That's why I mentioned it. I just wanted to see if I could get it working.
Cameron MacFarland