Как избежать мерцания в полноэкранном приложении WPF?

У меня есть приложение WPF, которое является полноэкранным киоск-приложением. На данный момент это довольно сложное приложение, но вот код, демонстрирующий основную идею. По сути, всякий раз, когда пользователь переходит от одного экрана к другому, возникает серьезное мерцание, вызывающее новое окно. В тяжелых случаях рабочий стол отображается в течение нескольких секунд, прежде чем появится новый экран. В этом примере кода этого не происходит, потому что он такой простой, но добавьте еще несколько кнопок и стилей, и вы это увидите.

Приложение.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();
        }
    }
}

Окно1.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();
    }
}

Окно2.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();
    }
}

Менеджер.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 и т. д.) не меняет поведение мерцания. В этом простом приложении можно было бы просто скрыть другие экраны, которые не отображаются, но в более сложном приложении слишком много информации о состоянии, чтобы правильно и легко управлять информацией на экране.

Есть ли какое-то волшебное кодовое слово или метод, чтобы избежать мерцания, который работал бы в этом простом приложении, которое также масштабируется для более сложного приложения? Я беспокоюсь, что на этом этапе мне придется переписать весь пользовательский интерфейс, чтобы поддерживать скрытие и отображение, а это просто невозможно в мои временные рамки.

РЕДАКТИРОВАТЬ: я пробовал скрывать/показывать в некоторых диалогах, и это просто не имеет значения. Может быть, это потому, что основное киоск-приложение тяжелое по стилю?


person mmr    schedule 02.03.2010    source источник


Ответы (8)


Основная причина мерцания заключается в том, что всякий раз, когда вы .Hide() окно, его PresentationSource отключается, в результате чего события Unloaded запускаются для всего, и все, что кэшируется в слое MILCore WPF, отбрасывается. Затем, когда вы снова .Show() сделаете это позже, все будет перестроено.

Чтобы предотвратить мерцание, убедитесь, что ваш пользовательский интерфейс постоянно подключен к PresentationSource. Это можно сделать несколькими способами:

Одно окно со скрытым TabControl

Используйте одно окно, содержащее TabControl, чтобы вы не могли видеть вкладки. Переключайте вкладки в коде, когда вы обычно показываете или скрываете окна. Вы можете просто найти и заменить «Window» в существующем коде на «Page», а затем заменить вызовы «Show()» на свой собственный «Show()», который делает следующее:

  1. Проверить ранее созданный TabItem для этой страницы (используя словарь)
  2. Если TabItem не найден, оберните страницу внутри нового TabItem и добавьте его в TabControl.
  3. Переключите TabControl на новый TabItem

ContentTemplate, который вы бы использовали для своего TabControl, чрезвычайно прост:

<ContentTemplate TargetType="TabControl">
  <ContentPresenter x:Name="PART_SelectedContentHost"
                    ContentSource="SelectedContent" />
</ContentTemplate>

Использование фрейма с навигацией

Использование Frame с навигацией — очень хорошее решение для киоска, поскольку оно реализует множество функций переключения страниц и других функций. Однако обновление существующего приложения таким образом может потребовать больше усилий, чем использование TabControl. В любом случае вам нужно преобразовать Window в Page, но с Frame вам также нужно иметь дело с навигацией.

Несколько окон с непрозрачностью

Вы можете сделать окно почти полностью невидимым, используя низкую непрозрачность, но WPF все равно сохранит визуальное дерево. Это было бы тривиальным изменением: просто замените все вызовы Window.Show() и Window.Hide() вызовами "MyHide()" и "MyShow()", которые обновляют непрозрачность. Обратите внимание, что вы можете улучшить это, если эти подпрограммы запускают анимацию очень короткой продолжительности (например, 0,2 секунды), которая анимирует непрозрачность. Поскольку обе анимации будут установлены одновременно, анимация будет проходить плавно, и это будет аккуратный эффект.

person Ray Burns    schedule 02.03.2010

Мне любопытно, почему вы используете несколько окон для одного и того же приложения в киоске. Вы можете легко поместить все элементы управления в одно и то же «окно» и просто изменить видимость на панелях, чтобы отображать разные «экраны». Это, безусловно, предотвратит отображение рабочего стола и позволит вам делать изящные вещи, такие как плавные переходы или скользящие анимации и т. д.

person NebuSoft    schedule 02.03.2010
comment
Это очень, очень интересная идея. Я не уверен, сколько времени потребуется, чтобы переключиться на этот подход (опять же, проблемы с состоянием; раньше это было приложение WinForms, переведенное на WPF), но, возможно, стоит провести один или два эксперимента. - person mmr; 02.03.2010
comment
Еще одна идея, которая может предотвратить это ... Если отображение последующих окон занимает много времени, это может быть связано с тем, что компьютер увяз в попытке отобразить все. Возможно, вы захотите показать все окна при запуске и переключаться между ними, активируя нужное окно. Таким образом, это просто окна, переключающие активное (верхнее) окно, вместо того, чтобы фактически отображать все компоненты при каждом переходе. - person NebuSoft; 02.03.2010

WPF имеет встроенную функцию навигации.

Просто взгляните на Фрейм и страницу классы, которые вы можете легко создать с помощью VS или Blend.

person Matthias    schedule 02.03.2010

Согласен с комментариями об использовании встроенной навигационной функции, но если на данный момент вы привязаны к своему дизайну, возможно, стоит рассмотреть возможность анимации непрозрачности ваших окон? Короткая 100 или 200 мс анимация непрозрачности от 1 -> 0 для исходящего окна и 0 -> 1 для входящего окна может решить проблему. Обработайте фактическую очистку исходящего окна в событии Completed на раскадровке.

person Ben Von Handorf    schedule 02.03.2010
comment
Отличная идея, но мне сказали, что мне нельзя заниматься творчеством ни с чем подобным, просто чтобы все показывалось быстрее. - person mmr; 02.03.2010
comment
Если вас беспокоит полное разделение дизайнера и разработчика, вы можете создать раскадровку в коде, чтобы избежать проблем с существующей анимацией. Если это просто вопрос политики, что ж... Думаю, им нравится их мерцание. :) - person Ben Von Handorf; 02.03.2010
comment
Это больше похоже на искусство для меня! Ты кодер, тебе нельзя быть художником! Мы платим вам не за это! Возвращайтесь в шахты! своеобразный менталитет. - person mmr; 02.03.2010
comment
Ах... всегда полезно. Надеюсь, это то, что WPF может помочь вам преодолеть... эти небольшие переходы действительно улучшают удобство использования и впечатления пользователей, и их довольно легко сделать. - person Ben Von Handorf; 02.03.2010

Учитывая, как WPF использует DirectX и графический процессор для разгрузки обработки элементов экрана, обновлены ли DirectX и драйверы компьютера?

Кори

person CodeWarrior    schedule 02.03.2010
comment
Это происходит на каждой машине, на которой тестируется приложение, и все они должны быть полностью обновлены. Если .NET требует установки большего количества драйверов DirectX, я думаю, что это будет частью установщика ... но на моей машине-разработчике определенно установлен самый последний DirectX, и он мерцает здесь. - person mmr; 02.03.2010

Если у вас есть какая-либо инициализация в конструкторе, которая занимает много времени, это может вызвать задержку и мерцание. Вы можете попробовать использовать асинхронный метод или поместить эту инициализацию в фоновый поток, чтобы он не блокировал отображение окна.

Примером того, что может вызвать задержку, может быть запрос к базе данных или запрос данных по сети.

Быстрым экспериментом было бы отключить части конструктора в медленном окне, чтобы выяснить, что вызывает задержку в отображении окна.

person Doug Ferguson    schedule 02.03.2010

Как было сказано ранее, использование элементов управления Frames/Tab позволяет избежать мерцания во время переходов.

Если вы не хотите менять свое приложение и хотите удалить это мерцание (промежуточное мигание рабочего стола) в Windows7 или WindowsVista, вы можете оптимизировать настройку «Визуальных эффектов» Windows, чтобы она была "Настроить для лучшей производительности"

person ashes_onfire    schedule 17.06.2011
comment
На самом деле это не общее решение — мы не можем просить всех наших клиентов отключить темы рабочего стола из-за нашей неспособности кодировать. Мы просто должны стать лучше. - person mmr; 19.06.2011

Вот простая альтернатива, которая работает для меня в моем приложении, похожем на киоск, с черным фоном, вдохновленном приведенными выше ответами. Здесь у меня есть «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

Вуаля!

(выполнено с помощью Visual Studio 2015, .NET Framework 4.6, WPF, VB.net)

person François Cusson-Lafrenaye    schedule 31.01.2016