none
隠れたウィンドウに対する画面更新が、ウィンドウ表示後に処理されるので、一瞬古い画面が表示されてしまうのを防ぎたい RRS feed

  • 質問

  • お世話になっております。

    複数のウィンドウを持ち、画面遷移ごとにウィンドウを切り替えて1つのウィンドウを全画面に表示するアプリケーションです。

    画面遷移時に、これから表示するウィンドウの各コントロールを予め準備してから表示切り替える処理をしていますが、

    実際の画面では準備前の状態が一瞬表示されてしまい、その後更新されるという動きになります。


    以下のURLで同様の質問があり、回答としてはサポート無しという事でした。

    ----------------------------------------------------

    https://social.msdn.microsoft.com/Forums/en-US/e974c849-90ea-4741-9a27-0ae4857bc3c2/wpf-update-ui-in-while-window-is-hidden?forum=wpf

    これは、WPFとウィンドウマネージャー間の同期の問題です。WPFがバッキングサーフェスを更新する前に、オペレーティングシステムは古いウィンドウの内容で再描画を開始します。 現在、フレームワークで非表示または最小化されているウィンドウの更新はサポートされていません。 他のシナリオでこの問題を発見した最善の回避策は、代わりにウィンドウを再作成することですが、これが実行可能かどうかはわかりません。

    ----------------------------------------------------

    上記質問は2009年当時でしたので、現在も同じか確認したく質問しました。

    または、代替えの方法等ございましたらご教示頂けますと助かります。

    以上、よろしくお願い致します。

    ----------------------------------------------------
    2019年10月10日 5:39

回答

  • 現在も同じかという疑問にたいしては、実際に試してそのように表示されているなら、同じかどうかは意味がないです。変わっていたとしても同じような表示だということなので。
    #Vista以降はDWM

    古い画面が表示されることが問題というのなら、ウィンドウを隠す前に空白の状態にしてやってから隠しては?

    それでだめだというのなら、全画面表示するウィンドウは1個だけにして、切り替わっていくWindowをUserControlに変更して、画面遷移は全画面ウィンドウのContenプロパティにそれらのUserControlを適用してやればいいです。(Window内での描画タイミングの問題だけになるので)

    namespace WpfApp1
    {
        using System;
        using System.Collections.Generic;
        using System.Linq;
        using System.Threading.Tasks;
        using System.Windows;
        using System.Windows.Controls;
        using System.Windows.Input;
        using System.Windows.Media;
        using System.Windows.Shapes;
        using System.Windows.Threading;
    
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
                this.WindowStyle = WindowStyle.None;
                this.WindowState = WindowState.Maximized;
    
                for (int i = 0; i < 3; i++)
                {
                    this.items.Add(new Item(i));
                    this.items[i].SetLines();
                }
    
                this.tokensource = new System.Threading.CancellationTokenSource();
                Dispatcher.BeginInvoke(new Action(async () =>
                {
                    await Test(this.tokensource.Token);
                }), DispatcherPriority.Input);
    
                this.MouseDoubleClick += MainWindow_MouseDoubleClick;
            }
    
            private List<Item> items = new List<Item>();
            private System.Threading.CancellationTokenSource tokensource = new System.Threading.CancellationTokenSource();
    
            /// <summary>このウィンドウのContentを変更していく紙芝居</summary>
            private async Task Test(System.Threading.CancellationToken token)
            {
                try
                {
                    Random rnd = new Random();
                    foreach (Brush brush in new Brush[] { Brushes.White, Brushes.LightBlue, Brushes.Pink })
                    {
                        for (int i = 0; i < items.Count; i++)
                        {
                            Item item = items[i];
                            item.SetLines();
                            item.textBlock.Text = "0";
                            this.Content = item.Control;
    
                            for (int sec = 0; sec < 10; sec++)
                            {
                                await Task.Delay(1000, token);
                                token.ThrowIfCancellationRequested();
                                items[i].textBlock.Text = sec.ToString();
                            }
                        }
                    }
                }
                catch (OperationCanceledException)
                {
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message);
                }
            }
    
            private void MainWindow_MouseDoubleClick(object sender, MouseButtonEventArgs e)
            {
                tokensource.Cancel();
                this.Content = null;
                //別々のウィンドウに分離
                for (int i = 0; i < items.Count; i++)
                {
                    Window w = new Window();
                    w.Width = 1000;
                    w.Height = 1000;
                    w.Content = items[i].Control;
                    w.Show();
                    w.MouseDoubleClick += ChildWindow_MouseDoubleClick;
                }
                this.Visibility = Visibility.Collapsed;
            }
    
            private async void ChildWindow_MouseDoubleClick(object sender, MouseButtonEventArgs e)
            {
                //分離しているウィンドウを消して戻す
                this.Visibility = Visibility.Visible;
                foreach (Window childWindow in Application.Current.Windows.OfType<Window>().Where(_ => _ != this))
                {
                    childWindow.Content = null;
                    childWindow.Close();
                }
    
                this.tokensource = new System.Threading.CancellationTokenSource();
                await Test(this.tokensource.Token);
            }
        }
    
        class Item
        {
            public UserControl Control { get; }
            private Canvas canvas { get; }
            public TextBlock textBlock { get; }
    
            public Item(int i)
            {
                var label = new TextBlock() { Text = i.ToString(), FontSize = 100 };
                textBlock = new TextBlock() { Text = "0", FontSize = 100 };
                var textBox = new TextBox() { FontSize = 100, Background = Brushes.Transparent };
                StackPanel stack = new StackPanel();
                stack.Children.Add(label);
                stack.Children.Add(textBlock);
                stack.Children.Add(textBox);
    
                canvas = new Canvas();
    
                Grid grid = new Grid();
                grid.Children.Add(canvas);
                grid.Children.Add(stack);
    
                Control = new UserControl();
                Control.Content = grid;
            }
    
            public void SetLines()
            {
                this.canvas.Children.Clear();
                Random rnd = new Random();
                byte[] bs = new byte[3];
                rnd.NextBytes(bs);
                var lineBrush = new SolidColorBrush(Color.FromRgb(bs[0], bs[1], bs[2]));
                for (int x = 0; x < 1000; x++)
                {
                    Line line = new Line();
                    line.Stroke = lineBrush;
                    line.X1 = rnd.NextDouble() * 1000;
                    line.X2 = rnd.NextDouble() * 1000;
                    line.Y1 = rnd.NextDouble() * 1000;
                    line.Y2 = rnd.NextDouble() * 1000;
                    canvas.Children.Add(line);
                }
            }
        }
    
    }
    既存のWindowからの変更は、XamlのタグをWindowからUserControlに変えて、コードビハインド側のクラスの継承をWindowからUserControlにかえてやればいいだけです。


    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    2019年10月10日 11:01

すべての返信

  • 現在も同じかという疑問にたいしては、実際に試してそのように表示されているなら、同じかどうかは意味がないです。変わっていたとしても同じような表示だということなので。
    #Vista以降はDWM

    古い画面が表示されることが問題というのなら、ウィンドウを隠す前に空白の状態にしてやってから隠しては?

    それでだめだというのなら、全画面表示するウィンドウは1個だけにして、切り替わっていくWindowをUserControlに変更して、画面遷移は全画面ウィンドウのContenプロパティにそれらのUserControlを適用してやればいいです。(Window内での描画タイミングの問題だけになるので)

    namespace WpfApp1
    {
        using System;
        using System.Collections.Generic;
        using System.Linq;
        using System.Threading.Tasks;
        using System.Windows;
        using System.Windows.Controls;
        using System.Windows.Input;
        using System.Windows.Media;
        using System.Windows.Shapes;
        using System.Windows.Threading;
    
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
                this.WindowStyle = WindowStyle.None;
                this.WindowState = WindowState.Maximized;
    
                for (int i = 0; i < 3; i++)
                {
                    this.items.Add(new Item(i));
                    this.items[i].SetLines();
                }
    
                this.tokensource = new System.Threading.CancellationTokenSource();
                Dispatcher.BeginInvoke(new Action(async () =>
                {
                    await Test(this.tokensource.Token);
                }), DispatcherPriority.Input);
    
                this.MouseDoubleClick += MainWindow_MouseDoubleClick;
            }
    
            private List<Item> items = new List<Item>();
            private System.Threading.CancellationTokenSource tokensource = new System.Threading.CancellationTokenSource();
    
            /// <summary>このウィンドウのContentを変更していく紙芝居</summary>
            private async Task Test(System.Threading.CancellationToken token)
            {
                try
                {
                    Random rnd = new Random();
                    foreach (Brush brush in new Brush[] { Brushes.White, Brushes.LightBlue, Brushes.Pink })
                    {
                        for (int i = 0; i < items.Count; i++)
                        {
                            Item item = items[i];
                            item.SetLines();
                            item.textBlock.Text = "0";
                            this.Content = item.Control;
    
                            for (int sec = 0; sec < 10; sec++)
                            {
                                await Task.Delay(1000, token);
                                token.ThrowIfCancellationRequested();
                                items[i].textBlock.Text = sec.ToString();
                            }
                        }
                    }
                }
                catch (OperationCanceledException)
                {
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message);
                }
            }
    
            private void MainWindow_MouseDoubleClick(object sender, MouseButtonEventArgs e)
            {
                tokensource.Cancel();
                this.Content = null;
                //別々のウィンドウに分離
                for (int i = 0; i < items.Count; i++)
                {
                    Window w = new Window();
                    w.Width = 1000;
                    w.Height = 1000;
                    w.Content = items[i].Control;
                    w.Show();
                    w.MouseDoubleClick += ChildWindow_MouseDoubleClick;
                }
                this.Visibility = Visibility.Collapsed;
            }
    
            private async void ChildWindow_MouseDoubleClick(object sender, MouseButtonEventArgs e)
            {
                //分離しているウィンドウを消して戻す
                this.Visibility = Visibility.Visible;
                foreach (Window childWindow in Application.Current.Windows.OfType<Window>().Where(_ => _ != this))
                {
                    childWindow.Content = null;
                    childWindow.Close();
                }
    
                this.tokensource = new System.Threading.CancellationTokenSource();
                await Test(this.tokensource.Token);
            }
        }
    
        class Item
        {
            public UserControl Control { get; }
            private Canvas canvas { get; }
            public TextBlock textBlock { get; }
    
            public Item(int i)
            {
                var label = new TextBlock() { Text = i.ToString(), FontSize = 100 };
                textBlock = new TextBlock() { Text = "0", FontSize = 100 };
                var textBox = new TextBox() { FontSize = 100, Background = Brushes.Transparent };
                StackPanel stack = new StackPanel();
                stack.Children.Add(label);
                stack.Children.Add(textBlock);
                stack.Children.Add(textBox);
    
                canvas = new Canvas();
    
                Grid grid = new Grid();
                grid.Children.Add(canvas);
                grid.Children.Add(stack);
    
                Control = new UserControl();
                Control.Content = grid;
            }
    
            public void SetLines()
            {
                this.canvas.Children.Clear();
                Random rnd = new Random();
                byte[] bs = new byte[3];
                rnd.NextBytes(bs);
                var lineBrush = new SolidColorBrush(Color.FromRgb(bs[0], bs[1], bs[2]));
                for (int x = 0; x < 1000; x++)
                {
                    Line line = new Line();
                    line.Stroke = lineBrush;
                    line.X1 = rnd.NextDouble() * 1000;
                    line.X2 = rnd.NextDouble() * 1000;
                    line.Y1 = rnd.NextDouble() * 1000;
                    line.Y2 = rnd.NextDouble() * 1000;
                    canvas.Children.Add(line);
                }
            }
        }
    
    }
    既存のWindowからの変更は、XamlのタグをWindowからUserControlに変えて、コードビハインド側のクラスの継承をWindowからUserControlにかえてやればいいだけです。


    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    2019年10月10日 11:01
  • お世話になっております。

    ご返信いただき、誠にありがとうございます。

    おっしゃる通り、.NET Frameworkのバージョンを切り替えて試しても動作は変わらずでしたので、

    現時点で未対応と思われます。

    ウィンドウを隠す前に表示を初期化する・・という事を試してみましたが、次画面を表示する前に初期化が走るとチラついて(押したボタン凹みが戻るのが見える/画面表示している文言が消去されるのが見える)しまうためNGでした。

    次画面を表示した後、背面で初期化を実行した場合は、冒頭の症状となってしまいNGでした。

    そこでご教示頂いた、Window→UserControlに切り替えて試した所、問題なく画面更新する事ができました!

    思わぬ相乗効果としてWindowを切り替えるより倍以上の速さで動作するようになりました。

    またメモリリーク等は現在も確認中ですが、消費量も80%程度まで少なくなりました。

    以上となります。

    ご対応ありがとうございました。

    2019年10月16日 11:56