4

I've got a borderless WPF window that needs to be able to hide one of its controls and shrink the window at the same time.

The problem is that it looks terrible.

Here's what I am doing now:

private void btnShowHideTopBar_Click(object sender, RoutedEventArgs e)
{
    if (commandTopHide == true)
    {
        txtblkShowHideTopBar.Text = "Show Top Bar";
        commandTopHide = false;
        myWindow.Left = 1100;
        myWindow.Width = 180;
        RSide.Width = new GridLength(0, GridUnitType.Pixel);
    }
    else if (commandTopHide == false)
    {
        txtblkShowHideTopBar.Text = "Hide Top Bar";
        commandTopHide = true;
        myWindow.Left = 1030;
        myWindow.Width = 250;
        RSide.Width = new GridLength(70, GridUnitType.Pixel);
    }
}

And here is what it looks like in slow motion:
the window flickers

To correct this, I have tried a few things. Each of which apparently only apply to Winforms.
For example, I followed this blog post at Bee Eee to disable drawing and 'lock window update' to no avail.

I've also tried overriding the WM_PAINT message as seen in this post but I got stuck on what to do with the message once I catch it. The line base.WndProc(ref msg); throws the error "'System.Windows.Window' does not contain a definition for 'WndProc'"

Granted, that code was for Winforms and I am using the hwndSource.AddHook method as described in How to handle WndProc messages in WPF?)...



My xaml is fairly massive due to a my custom buttons, so I'll leave that out. Here is what's left:

<Window x:Name="myWindow" x:Class="myNamespace.myWindowClass"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"
    Title="WiMan" Height="210" Width="250" ResizeMode="NoResize" WindowStyle="None" MinWidth="180" 
    MinHeight="210" MaxWidth="250" MaxHeight="210" Left="1030" Top="66" Loaded="ControlBoxLoadEvent" 
    Icon="pack://siteoforigin:,,,/Resources/App.ico" Closing="MainWindowIsClosing" Foreground="White" 
    Background="{x:Null}" AllowsTransparency="True">
<Window.Resources>
    <ControlTemplate ...stuff I left out.. > lots of stuff... </ControlTemplate>
</Window.Resources>
<Grid x:Name="MainWindowGrid" RenderTransformOrigin="0.5,0.5">
    <Grid.Background>
        <LinearGradientBrush EndPoint="160,240" StartPoint="160,-20" MappingMode="Absolute">
            <LinearGradientBrush.RelativeTransform>
                <TransformGroup>
                    <ScaleTransform CenterY="0.5" CenterX="0.5" ScaleY="1" ScaleX="1"/>
                    <SkewTransform AngleY="0" AngleX="0" CenterY="0.5" CenterX="0.5"/>
                    <RotateTransform Angle="-4.764" CenterY="0.5" CenterX="0.5"/>
                    <TranslateTransform/>
                </TransformGroup>
            </LinearGradientBrush.RelativeTransform>
            <GradientStop Color="#FF1B1B1B" Offset="1"/>
            <GradientStop Color="#7FC3C3C3"/>
        </LinearGradientBrush>
    </Grid.Background>
    <Grid.RowDefinitions>
        <RowDefinition/>
        <RowDefinition/>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Name="RSide" Width="70"/>
        <ColumnDefinition Width="84*"/>
        <ColumnDefinition Width="83*"/>
    </Grid.ColumnDefinitions>
    <Button x:Name="btnShowHideTopBar" FontFamily="Arial Rounded MT Bold" FontSize="18" Click="btnShowHideTopBar_Click" 
      Template="...the stuff I left out..." Foreground="White" Background="{x:Null}" BorderBrush="{x:Null}" Margin="2" Grid.Row="1" Grid.Column="1">
        <TextBlock x:Name="txtblkShowHideTopBar" TextWrapping="Wrap" Text="Hide Topbar" TextAlignment="Center"/>
    </Button>
    <Button x:Name="btnShowHideSideBar" Grid.Column="2" Grid.Row="1" FontFamily="Arial Rounded MT Bold" FontSize="18" Click="eventHideShowSideBar_Click" 
     Template="...the stuff I left out..." Foreground="White" Background="{x:Null}" BorderBrush="{x:Null}" Margin="2">
        <TextBlock x:Name="txtblkShowHideSideBar" TextWrapping="Wrap" Text="Hide Sidebar" TextAlignment="Center"/>
    </Button>
    <Button Content="Configure" Click="btnConfigure_Click" FontFamily="Arial Rounded MT Bold" FontSize="18" 
     Template="...the stuff I left out..." Foreground="White" Background="{x:Null}" BorderBrush="{x:Null}" Margin="2" Grid.ColumnSpan="2" Grid.Column="1"/>
    <Grid Grid.RowSpan="2">
        <Grid.Background>
            <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                <GradientStop Color="#FF3C8AE8" Offset="0"/>
                <GradientStop Color="#FF032440" Offset="1"/>
            </LinearGradientBrush>
        </Grid.Background>
    </Grid>
    <Button x:Name="btnScrollLeft" Content="R Side" Click="RSideScrollButton_Click" FontFamily="Arial Rounded MT Bold" FontSize="18" 
      Template="...the stuff I left out..." Foreground="White" Background="{x:Null}" BorderBrush="{x:Null}" Margin="3,2,3,3" Grid.RowSpan="2"/>
</Grid>

@Walt Ritscher, I don't know about 'hiding' the column, I'm just setting it's width to 0. But the bigger problem seems to be that I need to resize and reposition the window that contains it.



@ErnodeWeerd, I've implemented a stopwatch as you can see here:

    Stopwatch s = new Stopwatch();
    string a; string b; string c; 

    s.Start();
    myWindow.Left = 1100;
    s.Stop(); a = s.Elapsed.ToString(); s.Reset();

    s.Start();
    myWindow.Width = 180;
    s.Stop(); b = s.Elapsed.ToString(); s.Reset();

    s.Start();
    RSide.Width = new GridLength(0, GridUnitType.Pixel);
    s.Stop(); c = s.Elapsed.ToString(); s.Reset();

    MessageBox.Show(a + Environment.NewLine +
            b + Environment.NewLine +
            c + Environment.NewLine);

This is the result:
stopwatch result

Community
  • 1
  • 1
Scott Solmer
  • 3,871
  • 6
  • 44
  • 72
  • 1
    I might help to show your XAML. Have you looked into putting the elements in a grid column with width set to Auto, then show/hide content in the column. – Walt Ritscher Mar 21 '14 at 17:01
  • Resizing the window will likely cause a full redraw/re-layout of the visual tree. That can be very costly. Is there no way to keep the window size? – Emond Mar 21 '14 at 18:37
  • @WaltRitscher updated my question. Thanks for your suggestion. I suspect it would behave the same way. However I'm not exposing the visibility property because I need to have a fixed pixel size for that column. – Scott Solmer Mar 21 '14 at 18:38
  • @ErnodeWeerd The window absolutely needs to resize (and move in this case). The "hide" function's purpose is to expose more area of the user's desktop. – Scott Solmer Mar 21 '14 at 18:40
  • That sound pretty scary. Have you tried using the Performance tools to measure where most of the time is spent during the resize? – Emond Mar 21 '14 at 18:44
  • @ErnodeWeerd Apparently changing the width consumes the most time. – Scott Solmer Mar 21 '14 at 18:58
  • Using the performance tools you might be able to find out what elements/events are most costly during resize. – Emond Mar 21 '14 at 19:04
  • @Okuma.Scott I meant to hide the content within the column, not the column itself. When the column is auto-size then it will collapse when content is hidden. But it seems you know this already. :> and yes, it seems the bigger problem is resizing the window. – Walt Ritscher Mar 21 '14 at 20:30
  • @Okuma.Scott You could try the suggestion here: http://social.msdn.microsoft.com/Forums/vstudio/en-US/dfd65cde-9760-4bb1-bc05-b22a795ceb14/double-buffer-in-wpf?forum=wpf – o_weisman Mar 24 '14 at 12:05
  • @o_weisman Thanks for the link. Unfortunately they are basically saying what I need to do can't be done. I need to resize the actual window, not just change it's size in a transparent window. User's have to be able to interact with whatever is behind the space that is freed up. But then I am intrigued by what Tim Dawson said about "using reflection to get at internal members of the Window class." – Scott Solmer Mar 24 '14 at 12:18
  • @Okuma.Scott Have you tried using animation? How about reversing the order of hiding and resizing? – o_weisman Mar 24 '14 at 12:57
  • @o_weisman Yes I've tried changing the order of operations. I'm not sure animation would help. [according to this](http://stackoverflow.com/q/1769317/2596334) animating a window resize can be done manually (the accepted answer is not using xaml behaviors) so basically the only purpose that would serve would be to go from a flicker to something that looks more like my slow-motion example. The goal is to get the blue 'R Side' button to simply disappear. – Scott Solmer Mar 24 '14 at 13:06
  • A dirty little trick so you don't have to move your window, since you are using a borderless application, why don't you set your window background to transparent and then when you need to hide a control, set it's visibility to collapsed. It will efficiently hide your control, it won't move the application but you will be able to see through it. – woutervs Mar 24 '14 at 13:21
  • @woutervs I would do that, but then the user wouldn't be able to interact with what's behind the transparent portion of the window. – Scott Solmer Mar 24 '14 at 13:31
  • Quoting: When a Control or Window is fully transparent, it can then be clicked through. This is the case if you set your Window.Background="Transparent", or Opacity="0". As far as I know, this is by design in WPF. (http://stackoverflow.com/questions/1646346/create-a-fully-transparent-wpf-window-to-capture-mouse-events) – woutervs Mar 24 '14 at 13:35
  • I've tried my hypotheses and it works as well, all you have to do on the window is set AllowTransparency to true. After that you can click through any transparent part. If you want I can send you my test solution. – woutervs Mar 24 '14 at 13:50
  • @woutervs Yep, I just implemented and tested it. Please post as an answer and I'll accept it. I've used transparent windows before, but never realized this! – Scott Solmer Mar 24 '14 at 14:02

1 Answers1

2

You can use a transparent window (AllowTransparency = True) with a grid, then when your control needs to be hidden you can set the visibility of the control to collapsed. There will be no flickering and no moving of the window but there won't be anything there either so you will see throug as if you would have resized, moved your window.

woutervs
  • 1,500
  • 12
  • 28
  • This worked! Specifically, I'm using `btnScrollLeft.Visibility = System.Windows.Visibility.Hidden;` and `btnScrollLeft.Visibility = System.Windows.Visibility.Visible;` which results in the same effect as if resizing the window. The area behind the button does not capture mouse events and the user can interact with whatever is behind it. – Scott Solmer Mar 24 '14 at 14:11