如何避免WPF全屏应用程序中的闪烁?

11
我有一个WPF应用程序,是一个全屏幕的信息亭应用程序。此时,它实际上是一个相当复杂的应用程序,但下面的代码显示了基本思路。每当用户从一个屏幕切换到另一个屏幕时,新窗口的出现会导致一些严重的闪烁。在严重的情况下,桌面显示几秒钟才出现新的屏幕。这个简单的示例代码中没有这种情况,因为它太简单了,但如果添加几个按钮和样式,你会看到这种情况。
App.xaml.cs:
public partial class App : Application {
    Manager mManager;
    public App() {
        mManager = new Manager();
        Window1 screen1 = new Window1(mManager);
        mManager.Screen1 = screen1;
        try {
            this.Run(screen1);
        } catch (Exception e) {
            System.Console.WriteLine(e.ToString());                
        } finally {
            Application.Current.Shutdown();
        }
    }
}

Window1.xaml.cs:

public partial class Window1 : Window {
    Manager Manager{get; set;}
    public Window1(Manager inManager) {
        InitializeComponent();
        Manager = inManager;
    }

    private void OnChangeScreen(object sender, RoutedEventArgs e) {
        Manager.OpenScreen2();
    }
}

Window2.xaml.cs:

public partial class Window2 : Window {
    Manager Manager{get; set;}
    public Window2(Manager inManager) {
        InitializeComponent();
        Manager = inManager;
    }

    private void OnChangeScreen(object sender, RoutedEventArgs e) {
        Manager.OpenScreen1();
    }
}

Manager.cs:

public class Manager {
    public Window1 Screen1{ get; set;}
    public Window2 Screen2{ get; set;}

    public Manager(){
        Screen1 = new Window1(this);
    }

    public void OpenScreen2() {
        Screen2 = new Window2(this);
        Screen2.Show();
        if (Screen1 != null) {
            Screen1.Hide();
        }
    }

    public void OpenScreen1() {
        Screen1 = new Window1(this);
        Screen1.Show();
        if (Screen2 != null) {
            Screen2.Hide();
        }
    }
}

Window1.xaml(基本上被Window2.xaml模拟):

<Window x:Class="WpfApplication1.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" 
        WindowStyle="None"
        WindowState="Maximized"
        Width="1280"
        Height="1024"
        FontFamily="Global User Interface"
        ResizeMode="NoResize">

    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
        </Grid.RowDefinitions>
        <Button Name="ChangeScreenButton" Click="OnChangeScreen" Grid.Row="2" Grid.Column="2" Content="Toggle Screen 2"></Button>
    </Grid>
</Window>

将两个窗口的显示交错(即先显示窗口1再删除窗口2等)不会改变闪烁行为。在这个简单的应用程序中,可以只隐藏未显示的其他屏幕,但在更复杂的应用程序中,有太多的状态信息需要管理屏幕信息才能正确和轻松地进行操作。

是否有一些避免闪烁的魔法代码或技术适用于这个简单的应用程序,并且还能适用于更复杂的应用程序?我担心现在被迫重新编写整个UI以支持隐藏和显示,而这在我的时间框架内是不可行的。

编辑:我已经在一些对话框上尝试了隐藏/显示的方法,但似乎并没有什么作用。也许是因为主要的自助式应用程序风格比较重?

8个回答

15
无论何时您使用 .Hide() 方法隐藏一个窗口时,它的 PresentationSource 就会断开连接,导致在 WPF 的 MILCore 层中缓存的所有内容和所有组件都会被丢弃并触发 Unloaded 事件,这是闪烁的潜在原因。 当您稍后再次使用 .Show() 方法显示窗口时,所有组件都需要重新构建,因此就会出现闪烁现象。
为了防止闪烁,请确保始终将您的 UI 连接到 PresentationSource。有几种方法可以实现这一点:
使用伪装的 TabControl 的单个窗口
使用一个包含 TabControl 的窗口,并使其样式使选项卡不可见。当您通常要显示或隐藏窗口时,在代码中切换选项卡。您只需将现有代码中的 "Window" 替换为 "Page",然后将调用自定义的 "Show()" 方法替换为 "Show()",该方法执行以下操作:
  1. 检查此页面的先前创建的 TabItem(使用字典)
  2. 如果找不到 TabItem,则将页面包装在新的 TabItem 中并将其添加到 TabControl 中
  3. 将 TabControl 切换到新的 TabItem
您将使用的 TabControl 的 ContentTemplate 非常简单:
<ContentTemplate TargetType="TabControl">
  <ContentPresenter x:Name="PART_SelectedContentHost"
                    ContentSource="SelectedContent" />
</ContentTemplate>

使用带有导航的Frame

在kiosk中,使用带有导航的Frame是一个非常好的解决方案,因为它实现了许多页面切换和其他功能。但是,与使用TabControl相比,使用此方法更新现有应用程序可能需要更多的工作。在任何情况下,您都需要从Window转换为Page,但是使用Frame时还需要处理导航。

使用透明度创建多个窗口

您可以使用低不透明度使窗口几乎完全不可见,而WPF仍将保留可视树。这只需要进行微小的更改:将所有调用Window.Show()Window.Hide()替换为调用"MyHide()"和"MyShow()",它们会更新不透明度。请注意,您可以通过使这些例程触发非常短的持续时间(例如0.2秒)的动画来进一步改进此过程的效果。由于两个动画将同时设置,因此动画将平稳进行,并且效果非常好。


2

我很好奇为什么你在一个自助服务机上使用多个窗口来运行同一个应用程序。你可以将所有控件都放在同一个“窗口”中,并简单地通过更改面板的可见性来显示不同的“屏幕”。这样做肯定可以防止桌面被显示出来,并且可以让你做些有趣的事情,比如淡入淡出的过渡或者滑动动画等。


这是一个非常有趣的想法。我不确定切换到这种方法需要多长时间(再次提到状态问题;这曾经是一个WinForms应用程序改为了WPF),但可能值得进行一两次实验。 - mmr
5
另一个可能有助于防止这种情况的想法是:如果显示后续窗口需要很长时间,可能是因为计算机试图渲染所有内容而导致负载过重。你可以在启动时“展示”所有窗口,并通过激活所需窗口来切换它们。这样做只涉及到窗口的切换(将所选窗口置于顶部),而不需要在每次切换时实际渲染所有组件。 - NebuSoft

2

WPF内置导航功能。

只需查看使用VS或Blend轻松设计的Frame和Page类即可。Frame


1

这里有一个简单的替代方案,适用于我的黑色背景类似亭台的应用程序,受上面答案的启发。我这里有一个“LanguageWindow”,可以从应用程序的任何地方打开,以更改当前语言。

在LanguageWindow.xaml中(检查WindowState = Minimized):

<Window x:Class="LanguageWindow"
    ...
    Title="LanguageWindow" Height="1024" Width="1280" WindowStyle="None" WindowState="Minimized" Background="Black">

在 LanguageWindow.xaml.vb 文件中:
Private Sub LanguageWindow_ContentRendered(sender As Object, e As EventArgs) Handles Me.ContentRendered
    Me.WindowState = WindowState.Maximized
End Sub

Voilà!

(使用Visual Studio 2015,.NET Framework 4.6,WPF,VB.net完成)


1

考虑到 WPF 使用 DirectX 和图形处理器来卸载屏幕元素的处理,计算机的 DirectX 和驱动程序是否为最新版本?

Cory


这在应用程序测试的每台机器上都发生了,它们都应该是完全更新的。如果.NET需要安装更多的DirectX驱动程序,我认为它应该是安装程序的一部分...但是我的开发机器肯定已经安装了最新的DirectX,但是它在这里闪烁。 - mmr

0

如之前所述,使用框架/选项卡控件可以避免在过渡期间出现闪烁。

如果您不想更改应用程序,并且想要在Windows7或WindowsVista上消除闪烁(在桌面之间闪烁),则可以将窗口的“视觉效果”设置优化为'调整为最佳性能'


1
这并不是一个通用的解决方案——我们不能要求所有客户关闭桌面主题,因为我们的编码能力有限。我们只需要变得更好。 - mmr

0

同意使用内置导航功能的评论,但如果您现在已经锁定了设计,也许可以考虑动画化窗口的不透明度?对于即将离开的窗口,从1 -> 0的短暂100或200毫秒的不透明度动画和对于即将进入的窗口的0 -> 1的动画可能会解决问题。在故事板的完成事件中处理即将离开的窗口的实际清理。


好主意,但我被告知不允许在这方面有创意,只需使事情显示得更快。 - mmr
1
如果你担心的是设计师和开发者之间的分离问题,你可以通过在代码中创建故事板来避免与现有动画的冲突。如果这只是一个政策问题,那么...我猜他们喜欢闪烁。 :) - Ben Von Handorf
这更像是一种“在我看来那是艺术!你是个程序员,不允许你成为艺术家!我们雇你的目的不是这个!回到矿井里去吧!”的心态。 - mmr
啊...总是很有帮助。希望这是WPF可以帮助您克服的问题...这些小过渡确实有助于可用性和用户印象,而且它们非常容易实现。 - Ben Von Handorf

0

如果您在构造函数中有任何需要很长时间的初始化操作,可能会导致延迟和闪烁。您可以尝试使用异步方法或将该初始化操作放在后台线程上,以便不会阻塞窗口的显示。

会导致延迟的示例包括数据库查询或通过网络请求数据。

一个快速的实验是禁用慢窗口中构造函数的某些部分,以找出导致窗口显示延迟的原因。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接