Prevent WPF window flicker
Asked Answered
H

1

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

Hilaryhilbert answered 21/3, 2014 at 15:51 Comment(17)
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.Ressieressler
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?Fretwork
@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.Hilaryhilbert
@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.Hilaryhilbert
That sound pretty scary. Have you tried using the Performance tools to measure where most of the time is spent during the resize?Fretwork
@ErnodeWeerd Apparently changing the width consumes the most time.Hilaryhilbert
Using the performance tools you might be able to find out what elements/events are most costly during resize.Fretwork
@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.Ressieressler
@Okuma.Scott You could try the suggestion here: social.msdn.microsoft.com/Forums/vstudio/en-US/…Insensibility
@Insensibility 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."Hilaryhilbert
@Okuma.Scott Have you tried using animation? How about reversing the order of hiding and resizing?Insensibility
@Insensibility Yes I've tried changing the order of operations. I'm not sure animation would help. according to this 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.Hilaryhilbert
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.Eyeshot
@Eyeshot I would do that, but then the user wouldn't be able to interact with what's behind the transparent portion of the window.Hilaryhilbert
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. (#1646846)Eyeshot
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.Eyeshot
@Eyeshot 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!Hilaryhilbert
E
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.

Eyeshot answered 24/3, 2014 at 14:8 Comment(1)
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.Hilaryhilbert

© 2022 - 2024 — McMap. All rights reserved.