Подождите, пока не будет нарисован UIElement (без updatelayout)
У меня есть функция, которая занимает около 5 секунд, если нажать кнопку. Если кнопка нажата, я хочу отобразить какое-то уведомление, чтобы указать, что нажатие кнопки обрабатывается, что-то вроде
<Button Click="OnButtonClick" Content="Process Input" />
<Border x:Name="NotificationBorder" Opacity="0" IsHitTestVisible="False"
Width="500" Height="100" Background="White">
<TextBlock Text="Your input is being processed" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
И в коде позади кнопки нажмите:
private void OnButtonClick(Object sender, RoutedEventArgs e)
{
DoubleAnimation da = new DoubleAnimation
{
From = 5,
To = 0,
Duration = TimeSpan.FromSeconds(2.5),
};
// Making border visible in hopes that it's drawn before animation kicks in
NotificationBorder.Opacity = 1;
da.Completed += (o, args) => NotificationBorder.Opacity = 0;
NotificationBorder.UpdateLayout(); //Doesn't do anything
UpdateLayout(); // Doesn't do anything
NotificationBorder.BeginAnimation(OpacityProperty, da);
// Simulate calculationheavy functioncall
Thread.Sleep(5000);
}
как-то UpdateLayout()
недостаточно быстро, уведомление отображается только после 5 секунд Thread.Sleep
старше.
Dispatcher.Invoke((Action)(() => NotificationBorder.Opacity = 1), DispatcherPriority.Render);
тоже не сработает.
Кроме того, я не могу позволить Thread.Sleep
запускать в отдельном рабочем потоке - в реальном приложении ему нужно считывать данные из принадлежащих Dispatcher объектов и (пере) создавать части пользовательского интерфейса.
Есть ли способ сделать это видимым раньше? Thread.Sleep()
называется?
3 ответа
Проблема в том, что любая длительная активность в основном потоке (потоке пользовательского интерфейса) приведет к зависанию в пользовательском интерфейсе, поэтому вы не увидите никакой анимации.
Вы должны сделать свои расчеты в отдельной теме. Если вам нужен доступ к принадлежащим Dispatcher объектам, вы можете использовать uiObject.Dispatcher.BeginInvoke или Invoke. BackgroundWorker может помочь вам выполнить некоторые длительные вычисления и подписаться на событие Completed, которое будет автоматически запущено BackgroundWorker в потоке пользовательского интерфейса.
Если вы в порядке с зависанием пользовательского интерфейса, измените некоторые свойства и переназначьте другие вещи с помощью Dispatcher.BeginInvoke(DispatcherPriority.Background,...), например
NotificationBorder.Opacity = 1; Dispatcher.BeginInvoke(DispatcherPriority.Background, (Action)(()=>Thread.Sleep(5000)));
Если вы хотите, чтобы пользовательский интерфейс обновлялся, вам необходимо изменить свою длительную операцию на асинхронную, что означает правильное отделение зависимостей пользовательского интерфейса от любой логики или, возможно, операций, связанных с вводом-выводом, которые занимают все время. Если вы этого не сделаете, ни одно из обновлений пользовательского интерфейса, происходящих в вашей длительной операции, в действительности не будет отражено в пользовательском интерфейсе до тех пор, пока это не будет сделано.
Если вы работаете с.NET 4.5, вот мой предпочтительный шаблон, как это сделать:
public async Task DoComplexStuff()
{
//Grab values from UI objects that must be accessed on UI thread
//Run some calculations in the background that don't depend on the UI
var result = await Task.Run((Func<string>)DoComplexStuffInBackground);
//Update UI based on results
//Possibly do more stuff in the background, etc.
}
private string DoComplexStuffInBackground()
{
return "Stuff";
}
Это несколько "назад" по сравнению с подходом BackgroundWorker старого стиля, но я считаю, что это приводит к более чистой логике. Вместо написания своего фонового кода с явными вызовами для обновлений пользовательского интерфейса, вы пишете свой код пользовательского интерфейса вокруг явных фоновых рабочих вызовов через Task.Run()
, Это также ведет к естественному разделению UI-ориентированного кода и базового логического кода, так как вам необходимо реорганизовать свою логику в методы, которые можно вызывать таким образом. Это хорошая идея для долгосрочной ремонтопригодности в любом случае.
Таким образом, вы можете отправить оба действия (пользовательский интерфейс и ваши вычисления) в диспетчер, и они будут выполнены в строке
Dispatcher.BeginInvoke(do ui things);
Dispatcher.BeginInvoke(do your logic things);
Но вам лучше перенести ваш расчет в фоновый поток.