Где находится Application.DoEvents () в WPF?

88

У меня есть следующий пример кода, который увеличивает масштаб при каждом нажатии кнопки:

XAML:

<Window x:Class="WpfApplication12.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">

    <Canvas x:Name="myCanvas">

        <Canvas.LayoutTransform>
            <ScaleTransform x:Name="myScaleTransform" />
        </Canvas.LayoutTransform> 

        <Button Content="Button" 
                Name="myButton" 
                Canvas.Left="50" 
                Canvas.Top="50" 
                Click="myButton_Click" />
    </Canvas>
</Window>

* .cs

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void myButton_Click(object sender, RoutedEventArgs e)
    {
        Console.WriteLine("scale {0}, location: {1}", 
            myScaleTransform.ScaleX,
            myCanvas.PointToScreen(GetMyByttonLocation()));

        myScaleTransform.ScaleX =
            myScaleTransform.ScaleY =
            myScaleTransform.ScaleX + 1;

        Console.WriteLine("scale {0}, location: {1}",
            myScaleTransform.ScaleX,
            myCanvas.PointToScreen(GetMyByttonLocation()));
    }

    private Point GetMyByttonLocation()
    {
        return new Point(
            Canvas.GetLeft(myButton),
            Canvas.GetTop(myButton));
    }
}

вывод:

scale 1, location: 296;315
scale 2, location: 296;315

scale 2, location: 346;365
scale 3, location: 346;365

scale 3, location: 396;415
scale 4, location: 396;415

Как видите, есть проблема, которую я думал решить с помощью, Application.DoEvents();но ... она не существует априори в .NET 4.

Что делать?

Серхио
источник
8
Поток? Application.DoEvents () был плохой заменой для написания правильных многопоточных приложений и в любом случае крайне плохой практикой.
Колин Маккей
2
Я знаю, что это плохо и плохо, но я предпочитаю то, что совсем ничего.
serhio

Ответы:

25

Старый метод Application.DoEvents () объявлен устаревшим в WPF в пользу использования Dispatcher или Background Worker Thread для выполнения описанной вами обработки. По ссылкам вы найдете пару статей о том, как использовать оба объекта.

Если вам абсолютно необходимо использовать Application.DoEvents (), вы можете просто импортировать system.windows.forms.dll в свое приложение и вызвать метод. Однако это действительно не рекомендуется, поскольку вы теряете все преимущества, которые предоставляет WPF.

Дилли-О
источник
Я знаю, что это плохо и плохо, но я предпочитаю то, что совсем ничего ... Как я могу использовать Dispatcher в моей ситуации?
serhio
3
Я понимаю твою ситуацию. Я участвовал в ней, когда писал свое первое приложение WPF, но я пошел дальше и нашел время, чтобы изучить новую библиотеку, и в долгосрочной перспективе это было намного лучше. Я очень рекомендую уделить время. Что касается вашего конкретного случая, мне кажется, что вы хотите, чтобы диспетчер обрабатывал отображение координат каждый раз, когда срабатывает ваше событие click. Для точной реализации вам нужно больше узнать о Диспетчере.
Dillie-O
нет, я бы позвонил Application.DoEventsпосле увеличения myScaleTransform.ScaleX. Не знаю, возможно ли это с Диспетчером.
serhio
12
Удаление Application.DoEvents () почти так же раздражает, как удаление MS кнопки «Пуск» в Windows 8.
JeffHeaton 01
2
Нет, это хорошо, этот метод принес больше вреда, чем пользы.
Джесси
136

Попробуйте что-то вроде этого

public static void DoEvents()
{
    Application.Current.Dispatcher.Invoke(DispatcherPriority.Background,
                                          new Action(delegate { }));
}
Фредрик Хедблад
источник
1
Я даже написал способ расширения приложения :)public static void DoEvents(this Application a)
serhio
@serhio: Отличный метод наращивания :)
Фредрик Хедблад
2
Однако я должен отметить, что в реальном приложении Application.Currentиногда имеет значение null ... так что, возможно, это не совсем эквивалент.
serhio
Отлично. Это должен быть ответ. Скептики не должны получать должное.
Jeson Martajaya 01
6
Это не всегда будет работать, так как он не подталкивает кадр, если выполняется инструкция прерывания (т.е. вызов метода WCF, который в синхронном режиме продолжает эту команду), скорее всего, вы не увидите `` обновление '', поскольку оно будет заблокировано .. Вот почему ответ flq, предоставленный с ресурса MSDN, более правильный, чем этот.
GY
57

Ну, я просто попал в случай, когда я начинаю работать над методом, который работает в потоке Dispatcher, и ему нужно заблокировать, не блокируя поток пользовательского интерфейса. Оказывается, msdn объясняет, как реализовать DoEvents () на основе самого Dispatcher:

public void DoEvents()
{
    DispatcherFrame frame = new DispatcherFrame();
    Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background,
        new DispatcherOperationCallback(ExitFrame), frame);
    Dispatcher.PushFrame(frame);
}

public object ExitFrame(object f)
{
    ((DispatcherFrame)f).Continue = false;

    return null;
}

(взято из метода Dispatcher.PushFrame )

Некоторые могут предпочесть это в одном методе, который будет применять ту же логику:

public static void DoEvents()
{
    var frame = new DispatcherFrame();
    Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background,
        new DispatcherOperationCallback(
            delegate (object f)
            {
                ((DispatcherFrame)f).Continue = false;
                return null;
            }),frame);
    Dispatcher.PushFrame(frame);
}
flq
источник
Хорошая находка! Это выглядит безопаснее, чем предлагаемая реализация Meleak. Я нашел сообщение в блоге об этом
HugoRune
2
@HugoRune В этом сообщении блога говорится, что этот подход не нужен и использует ту же реализацию, что и Meleak.
Lukazoid
1
@Lukazoid Насколько я могу судить, простая реализация может вызвать затруднительные для отслеживания блокировки. (Я не уверен в причине, возможно, проблема в коде в очереди диспетчера, снова вызывающем DoEvents, или в коде в очереди диспетчера, генерирующем дополнительные кадры диспетчера.) В любом случае решение с exitFrame не вызывало таких проблем, поэтому я бы рекомендую это. (Или, конечно, вообще не использовать doEvents)
HugoRune
1
Отображение оверлея в вашем окне вместо диалогового окна в сочетании со способом caliburn задействовать виртуальные машины, когда приложение закрывается, исключили обратные вызовы и потребовали от нас блокировки без блокировки. Я был бы рад, если бы вы представили мне решение без взлома DoEvents.
flq
1
новая ссылка на старый пост в блоге: kent-boogaart.com/blog/dispatcher-frames
CAD bloke
11

Если вам нужно просто обновить графику окна, лучше используйте это

public static void DoEvents()
{
    Application.Current.Dispatcher.Invoke(DispatcherPriority.Render,
                                          new Action(delegate { }));
}
Александр Пекшев
источник
Использование DispatcherPriority.Render работает быстрее, чем DispatcherPriority.Background. Сегодня протестирован с помощью StopWatcher
Александр Пекшев
Я просто использовал этот трюк, но для достижения наилучших результатов использую DispatcherPriority.Send (наивысший приоритет); более отзывчивый интерфейс.
Бент Расмуссен
6
myCanvas.UpdateLayout();

похоже, тоже работает.

Серхио
источник
Я собираюсь использовать это, так как мне это кажется намного безопаснее, но я оставлю DoEvents для других случаев.
Картер Медлин,
Не знаю почему, но у меня это не работает. DoEvents () отлично работает.
Newman
В моем случае я должен был сделать это, а также DoEvents ()
Джефф
3

Одна проблема с обоими предлагаемыми подходами заключается в том, что они влекут за собой простое использование ЦП (по моему опыту до 12%). В некоторых случаях это неоптимально, например, когда модальное поведение пользовательского интерфейса реализуется с использованием этой техники.

Следующий вариант вводит минимальную задержку между кадрами с использованием таймера (обратите внимание, что здесь это написано с помощью Rx, но может быть достигнуто с помощью любого обычного таймера):

 var minFrameDelay = Observable.Interval(TimeSpan.FromMilliseconds(50)).Take(1).Replay();
 minFrameDelay.Connect();
 // synchronously add a low-priority no-op to the Dispatcher's queue
 Application.Current.Dispatcher.Invoke(DispatcherPriority.Background, new Action(() => minFrameDelay.Wait()));
Йонас Шапюи
источник
1

С момента появления asyncи awaitтеперь стало возможным отказаться от потока пользовательского интерфейса на полпути через (ранее) * синхронный блок кода, используя Task.Delay, например,

private async void myButton_Click(object sender, RoutedEventArgs e)
{
    Console.WriteLine("scale {0}, location: {1}", 
        myScaleTransform.ScaleX,
        myCanvas.PointToScreen(GetMyByttonLocation()));

    myScaleTransform.ScaleX =
        myScaleTransform.ScaleY =
        myScaleTransform.ScaleX + 1;

    await Task.Delay(1); // In my experiments, 0 doesn't work. Also, I have noticed
                         // that I need to add as much as 100ms to allow the visual tree
                         // to complete its arrange cycle and for properties to get their
                         // final values (as opposed to NaN for widths etc.)

    Console.WriteLine("scale {0}, location: {1}",
        myScaleTransform.ScaleX,
        myCanvas.PointToScreen(GetMyByttonLocation()));
}

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

Например:

        var levelOptions = new ObservableCollection<GameLevelChoiceItem>();

        this.ViewModel[LevelOptionsViewModelKey] = levelOptions;

        var syllabus = await this.LevelRepository.GetSyllabusAsync();
        foreach (var level in syllabus.Levels)
        {
            foreach (var subLevel in level.SubLevels)
            {
                var abilities = new List<GamePlayingAbility>(100);

                foreach (var g in subLevel.Games)
                {
                    var gwa = await this.MetricsRepository.GetGamePlayingAbilityAsync(g.Value);
                    abilities.Add(gwa);
                }

                double PlayingScore = AssessmentMetricsProcessor.ComputePlayingLevelAbility(abilities);

                levelOptions.Add(new GameLevelChoiceItem()
                    {
                        LevelAbilityMetric = PlayingScore,
                        AbilityCaption = PlayingScore.ToString(),
                        LevelCaption = subLevel.Name,
                        LevelDescriptor = level.Ordinal + "." + subLevel.Ordinal,
                        LevelLevels = subLevel.Games.Select(g => g.Value),
                    });

                await Task.Delay(100);
            }
        }

В Магазине Windows, когда в коллекции есть красивый переход темы, эффект желателен.

Люк

  • см. комментарии. Когда я быстро писал свой ответ, я думал о том, чтобы взять синхронный блок кода, а затем передать поток обратно его вызывающей стороне, в результате чего блок кода становится асинхронным. Я не хочу полностью перефразировать свой ответ, потому что тогда читатели не смогут понять, о чем мы с Серви спорили.
Люк Пуплетт
источник
«Теперь можно отказаться от потока пользовательского интерфейса частично через синхронный блок» Нет, это не так. Вы только что сделали код асинхронным , а не перекачивали сообщения из потока пользовательского интерфейса синхронным методом. Теперь правильно спроектированное приложение WPF будет таким, которое никогда не блокирует поток пользовательского интерфейса, в первую очередь синхронно выполняя длительные операции в потоке пользовательского интерфейса, используя асинхронность, чтобы позволить существующему насосу сообщений перекачивать сообщения надлежащим образом.
Servy
@Servy Под покровом, awaitзаставит компилятор зарегистрировать оставшуюся часть асинхронного метода в качестве продолжения ожидаемой задачи. Это продолжение будет происходить в потоке пользовательского интерфейса (тот же контекст синхронизации). Затем управление возвращается вызывающей стороне асинхронного метода, то есть подсистеме обработки событий WPF, где события будут выполняться до тех пор, пока не будет запущено запланированное продолжение через некоторое время после истечения периода задержки.
Люк Пуплетт
да, я это прекрасно понимаю. Это то, что делает метод асинхронным (уступка управления вызывающей стороне и планирование только продолжения). В вашем ответе указано, что метод является синхронным, хотя на самом деле он использует асинхронность для обновления пользовательского интерфейса.
Servy
Первый метод (код OP) - синхронный, Servy. Второй пример - это всего лишь совет по поддержанию работоспособности пользовательского интерфейса в цикле или необходимости складывать элементы в длинный список.
Люк Пуплетт
И вы сделали синхронный код асинхронным. Вы не сохранили отзывчивость пользовательского интерфейса из синхронного метода, как указано в вашем описании или в ответе на запрос.
Servy 03
0

Сделайте DoEvent () в WPF:

Thread t = new Thread(() => {
            // do some thing in thread
            
            for (var i = 0; i < 500; i++)
            {
                Thread.Sleep(10); // in thread

                // call owner thread
                this.Dispatcher.Invoke(() => {
                    MediaItem uc = new MediaItem();
                    wpnList.Children.Add(uc);
                });
            }
            

        });
        t.TrySetApartmentState(ApartmentState.STA); //for using Clipboard in Threading
        t.Start();

Хорошо работай для меня!

VinaCaptcha
источник
-2

Отвечая на исходный вопрос: где находится DoEvents?

Я думаю, что DoEvents - это VBA. И VBA, похоже, не имеет функции сна. Но у VBA есть способ добиться того же эффекта, что и у сна или задержки. Мне кажется, что DoEvents эквивалентно Sleep (0).

В VB и C # вы имеете дело с .NET. И исходный вопрос - это вопрос C #. В C # вы должны использовать Thread.Sleep (0), где 0 - это 0 миллисекунд.

Тебе нужно

using System.Threading.Task;

в верхней части файла, чтобы использовать

Sleep(100);

в вашем коде.

Indinfer
источник