Где находится Application.DoEvents() в WPF?
У меня есть следующий пример кода, который увеличивается при каждом нажатии кнопки:
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 не существует.
Что делать?
10 ответов
Старый метод Application.DoEvents() устарел в WPF, поскольку в нем используется диспетчер или фоновый рабочий поток для выполнения обработки, как вы описали. Смотрите ссылки на пару статей о том, как использовать оба объекта.
Если вам абсолютно необходимо использовать Application.DoEvents(), вы можете просто импортировать system.windows.forms.dll в свое приложение и вызвать метод. Однако это действительно не рекомендуется, поскольку вы теряете все преимущества, которые предоставляет WPF.
Попробуйте что-то вроде этого
public static void DoEvents()
{
Application.Current.Dispatcher.Invoke(DispatcherPriority.Background,
new Action(delegate { }));
}
Ну, я просто столкнулся со случаем, когда я начинаю работу над методом, который выполняется в потоке 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()
{
Application.Current.Dispatcher.Invoke(DispatcherPriority.Render,
new Action(delegate { }));
}
Одна проблема с обоими предложенными подходами состоит в том, что они влекут за собой использование процессора вхолостую (до 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()));
С момента введения 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, когда в коллекции есть хороший переход к теме, эффект весьма желателен.
Люк
- см комментарии Когда я быстро писал свой ответ, я думал о том, чтобы взять синхронный блок кода и затем вернуть поток обратно его вызывающей стороне, в результате чего блок кода становится асинхронным. Я не хочу полностью перефразировать мой ответ, потому что тогда читатели не смогут увидеть, о чем ссорились мы с Серви.
Сделайте 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();
Хорошо работай для меня!
Толкающая рамка:
using System.Windows.Threading;
...
var frame = new DispatcherFrame();
Dispatcher.PushFrame(frame);
Выходной кадр:
frame.Continue = false;
В следующем примере показано, как использовать DispatcherFrame для достижения результатов, подобных методу Windows Forms DoEvents.
https://learn.microsoft.com/dotnet/api/system.windows.threading.dispatcher.pushframe
Отвечая на исходный вопрос: где находится DoEvents?
Я думаю, что DoEvents - это VBA. И VBA, похоже, не имеет функции сна. Но у VBA есть способ добиться того же эффекта, что и у сна или задержки. Мне кажется, что DoEvents эквивалентно Sleep(0).
В VB и C# вы имеете дело с.NET. И исходный вопрос - это вопрос C#. В C# вы должны использовать Thread.Sleep(0), где 0 - это 0 миллисекунд.
Тебе нужно
using System.Threading.Task;
в верхней части файла, чтобы использовать
Sleep(100);
в вашем коде.