Отображение текстового индикатора перед замораживанием потока пользовательского интерфейса Silverlight

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

StatusTextBlock.Text = "Performing Some Operation...";
System.Threading.Thread.Sleep(4000); // Just as an example

Проблема в том, что поток пользовательского интерфейса зависает до того, как текст элемента управления TextBlock обновляется. Как я могу получить текст уведомления, отображаемый до начала операции?

Кроме того, перенос тяжелой операции в фоновый поток для меня не вариант, поскольку он имеет дело с объектами пользовательского интерфейса (переключает визуальный корень приложения) и должен выполняться в потоке пользовательского интерфейса.


person Narek Malkhasyan    schedule 10.04.2013    source источник
comment
Использование фонового потока невозможно? всегда хорошо выводить длительные операции из потока пользовательского интерфейса.   -  person tsiorn    schedule 10.04.2013
comment
Спасибо за предложение, но я не могу использовать фоновый поток. Пожалуйста, взгляните на отредактированный вопрос.   -  person Narek Malkhasyan    schedule 10.04.2013


Ответы (5)


Мое предложение состоит в том, чтобы снять его с потока пользовательского интерфейса и использовать фоновый поток...

StatusTextBox.Text = "Before Sleep";
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
bw.RunWorkerAsync();

void bw_DoWork(object sender, DoWorkEventArgs e)
{
    System.Threading.Thread.Sleep(8000);}


void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    StatusTextBox.Text = "after Sleep";
}
person tsiorn    schedule 10.04.2013
comment
Спасибо за ваш ответ, но использование фонового потока для меня не вариант, так как операция имеет дело с объектами пользовательского интерфейса и должна выполняться в потоке пользовательского интерфейса (я отредактировал свой вопрос). - person Narek Malkhasyan; 10.04.2013
comment
Да, я понятия не имею, как сделать то, чего вы добиваетесь (поскольку bg worker не вариант), поэтому я подниму ваш вопрос... Мне самому любопытно, как вы можете заставить его работать. Я оставлю свой ответ на случай, если он поможет кому-то еще, где они могут использовать bg worker. - person tsiorn; 10.04.2013

Я нашел решение с помощью сообщения в блоге Джеффа Просиза: http://www.wintellect.com/cs/blogs/jprosise/archive/2008/10/25/cool-silverlight-trick-5.aspx

Идея состоит в том, чтобы отложить вызов, выполняющий длительную задачу, до тех пор, пока не сработает событие рендеринга пользовательского интерфейса Silverlight. Для этого я использовал событие CompositionTarget.Rendering. Я подписался на него в конструкторе пользовательского элемента управления:

CompositionTarget.Rendering += this.CompositionTargetRendering;

После обновления текста элемента управления TextBlock я устанавливаю частный флаг, который указывает, что в обработчике событий должна быть выполнена некоторая обработка:

StatusTextBlock.Text = "Performing Some Operation...";
this.processRenderingEvent = true;

А вот и код обработчика:

private void CompositionTargetRendering(Object sender, EventArgs e)
{
    if (this.processRenderingEvent)
    {
        if (++this.renderingEventCounter == 2)
        {
            System.Threading.Thread.Sleep(4000); // Example of long running task
            this.processRenderingEvent = false;
        }
    }
}

Здесь важно упомянуть, что я использую закрытое целочисленное поле renderingEventCounter для запуска долговременной задачи не в первый раз, когда событие срабатывает, а во второй. Причина этого в том, что событие CompositionTarget.Rendering запускается непосредственно перед тем, как механизм рендеринга пользовательского интерфейса Silverlight отрисовывает новый кадр на поверхности дисплея приложения, а это означает, что при первом запуске события текст элемента управления TextBlock еще не обновлен. Но он будет обновлен во второй раз.

person Narek Malkhasyan    schedule 16.04.2013

Я думаю, вам следует реализовать поток BackgroundWorker — это ответ Циома, но используйте Dispatcher.BeginInvoke для работы с объектами пользовательского интерфейса, вот статья MSDN о том, как использовать этот метод: http://msdn.microsoft.com/en-us/library/cc190824%28v=vs.95%29.aspx

Кроме того, ознакомьтесь с другим вопросом StackOverflow, чтобы узнать о более подробном сценарии использования Dispatcher: Понимание Silverlight Dispatcher.

person Boluc Papuccuoglu    schedule 10.04.2013
comment
Спасибо за ответ. Я прочитал статью о том, как дождаться завершения рендеринга пользовательского интерфейса с помощью Dispatcher в WCF (jonathanantoine.com/2011/08/29/). Это делается путем присвоения DispatcherPriority.ContextIdle приоритета действию, выполняемому Dispatcher, который ниже приоритета рендеринга пользовательского интерфейса. Но, к сожалению, Silverlight не поддерживает DispatcherPriority, поэтому я не уверен, сможет ли Dispatcher помочь в этом сценарии. - person Narek Malkhasyan; 12.04.2013

Я сам только что столкнулся с этой ситуацией. Проблема (я думаю) в том, что до того, как текст обновится, вы уже начали интенсивную работу, поэтому вам нужно подождать.

Что вы можете сделать, так это прикрепить прослушиваемый метод к текстовому полю, который вызывается только после обновления текста (возможно, textChanged?), а ЗАТЕМ вызвать вашу интенсивную операцию.

Хотя мне это кажется хакерским...

person TruthOf42    schedule 10.04.2013
comment
Спасибо за ответ. Я попробовал предложенное вами решение, но, к сожалению, безрезультатно. Я пытался использовать все типы событий, которые пришли мне в голову, такие как TextChanged элемента управления TextBox, LayoutUpdated элемента управления FrameworkElement и т. д. Но оказывается, что во время выполнения обработчиков этих событий пользовательский интерфейс еще не обновляется. - person Narek Malkhasyan; 12.04.2013
comment
в конце концов он обновляется, верно? при условии, что вы не пытаетесь сделать интенсивный процесс, верно? Я уверен, что это так, но всегда полезно проверить работоспособность - person TruthOf42; 15.04.2013
comment
Он обновляется только после завершения интенсивного процесса, а это не то, чего я хочу (не уверен, что ответил на ваш вопрос). - person Narek Malkhasyan; 15.04.2013

Это некрасиво, но это работает. Откладывая инициализацию длительной операции с помощью DispatcherTimer, мы можем разрешить обновление пользовательского интерфейса до запуска операции.

XAML:

<UserControl x:Class="SilverlightApplication13.MainPage"
             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"
             d:DesignHeight="300"
             d:DesignWidth="400">

    <Grid x:Name="LayoutRoot"
          Background="White">

        <StackPanel>

            <Border x:Name="Brd01"
                    Visibility="Collapsed"
                    Background="Red">
                <TextBlock VerticalAlignment="Center"
                           Margin="30">Sleeping for 4 seconds...</TextBlock>
            </Border>

            <Border x:Name="Brd02"
                    Visibility="Collapsed"
                    Background="Lime">
                <TextBlock VerticalAlignment="Center"
                           Margin="30">Done!</TextBlock>
            </Border>

            <Button Content="Start Operation"
                    Click="Button_Click_1"></Button>
        </StackPanel>

    </Grid>
</UserControl>

Код программной части:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Threading;

namespace SilverlightApplication13
{
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();
        }

        private void Button_Click_1(object sender, RoutedEventArgs e)
        {
            //Show the "working..." message
            Brd01.Visibility = System.Windows.Visibility.Visible;

            //Initialize a timer with a delay of 0.1 seconds
            var timer = new DispatcherTimer();
            timer.Interval = TimeSpan.FromMilliseconds(100);
            timer.Tick += Timer_Tick;
            timer.Start();
        }

        private void Timer_Tick(object sender, EventArgs e)
        {
            //Start the long running operation
            Thread.Sleep(4000);

            Brd01.Visibility = System.Windows.Visibility.Collapsed;
            Brd02.Visibility = System.Windows.Visibility.Visible;

            //Kill the timer so it will only run once. 
            (sender as DispatcherTimer).Stop();
            (sender as DispatcherTimer).Tick -= Timer_Tick;
        }
    }
}
person Henrik Söderlund    schedule 11.04.2013
comment
Я также думал о каком-то обходном пути, подобном этому, но это не надежное решение, как вы также упомянули. В любом случае, спасибо за ваши усилия. - person Narek Malkhasyan; 12.04.2013
comment
Вы правы, это некрасиво. Но со всем программированием пользовательского интерфейса я постоянно попадаю в такие ситуации, когда уродливый обходной путь неизбежен. Если вам абсолютно необходимо показать рабочее... сообщение в вашей ситуации (где вы не можете использовать фоновый поток), это единственный способ, который я могу придумать. - person Henrik Söderlund; 12.04.2013