Где находится приложение.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.
что делать?
7 ответов:
старое приложение.Метод DoEvents () был устаревшим в WPF в пользу использования диспетчер или Фоновый Рабочий Поток чтобы сделать обработку, как вы описали. Ссылки на пару статей о том, как использовать оба объекта.
Если вы абсолютно должны использовать приложение.DoEvents (), то вы можете просто импортировать систему.окна.формы.dll в ваше приложение и вызовите метод. Однако, это не рекомендуется, так как вы теряете все преимущества, которые предоставляет WPF.
попробуйте что-то вроде этого
public static void DoEvents() { Application.Current.Dispatcher.Invoke(DispatcherPriority.Background, new Action(delegate { })); }
Ну, я просто попал в случай, когда я начинаю работать над методом, который работает в потоке Dispatcher, и ему нужно блокировать, не блокируя поток пользовательского интерфейса. Оказывается, msdn объясняет, как реализовать DoEvents () на основе самого диспетчера:
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; }
(взято из диспетчер.Способ PushFrame)
одна из проблем с обоими предложенными подходами заключается в том, что они влекут за собой простое использование ЦП (до 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()));
Если вам нужно просто обновить графическое окно, лучше использовать так
public static void DoEvents() { Application.Current.Dispatcher.Invoke(DispatcherPriority.Render, new Action(delegate { })); }
С момента введения
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, когда есть хороший переход темы на коллекции, эффект вполне желателен.
Люк
- см. комментарии. Когда я быстро писал свой ответ, я думал о том, чтобы взять синхронный блок кода, а затем отказаться от потока обратно к его вызывающему, эффект которого делает блок кода асинхронным. Я не хочу полностью перефразировать свой ответ, потому что тогда читатели не могут видеть из-за чего мы с Серви ссорились.