Mam następujący przykładowy kod, który powiększa się po każdym naciśnięciu przycisku:
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));
}
}
wynik to:
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
jak widać, jest problem, który myślałem, że można go rozwiązać za pomocą, Application.DoEvents();
ale ... nie istnieje a priori w .NET 4.
Co robić?
Odpowiedzi:
Stara metoda Application.DoEvents () została uznana za przestarzałą w WPF na korzyść używania modułu Dispatcher lub wątku roboczego w tle do przetwarzania zgodnie z opisem. Zobacz linki do kilku artykułów na temat używania obu obiektów.
Jeśli absolutnie musisz użyć Application.DoEvents (), możesz po prostu zaimportować system.windows.forms.dll do swojej aplikacji i wywołać metodę. Jednak to naprawdę nie jest zalecane, ponieważ tracisz wszystkie zalety oferowane przez WPF.
źródło
Application.DoEvents
po zwiększeniu myScaleTransform.ScaleX. Nie wiem, czy jest to możliwe z Dispatcherem.Spróbuj czegoś takiego
public static void DoEvents() { Application.Current.Dispatcher.Invoke(DispatcherPriority.Background, new Action(delegate { })); }
źródło
public static void DoEvents(this Application a)
Application.Current
czasami jest zerowa ... więc być może nie jest całkiem równoważna.Cóż, właśnie trafiłem na przypadek, w którym zaczynam pracę nad metodą działającą w wątku Dispatchera i musi ona blokować bez blokowania wątku interfejsu użytkownika. Okazuje się, że msdn wyjaśnia, jak zaimplementować DoEvents () na podstawie samego Dispatchera:
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; }
(pobrane z metody Dispatcher.PushFrame )
Niektórzy mogą preferować to w jednej metodzie, która będzie egzekwować tę samą logikę:
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); }
źródło
Jeśli potrzebujesz tylko zaktualizować grafikę okna, lepiej użyj w ten sposób
public static void DoEvents() { Application.Current.Dispatcher.Invoke(DispatcherPriority.Render, new Action(delegate { })); }
źródło
myCanvas.UpdateLayout();
wydaje się również działać.
źródło
Jednym z problemów z obydwoma proponowanymi podejściami jest to, że wiążą się one z bezczynnym wykorzystaniem procesora (z mojego doświadczenia do 12%). W niektórych przypadkach jest to nieoptymalne, na przykład gdy zaimplementowano zachowanie modalnego interfejsu użytkownika przy użyciu tej techniki.
Poniższa odmiana wprowadza minimalne opóźnienie między klatkami przy użyciu timera (zauważ, że jest napisane tutaj z Rx, ale można to osiągnąć za pomocą dowolnego zwykłego timera):
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()));
źródło
Od czasu wprowadzenia
async
iawait
teraz można zrezygnować z wątku interfejsu użytkownika w trakcie (dawniej) * synchronicznego bloku kodu przy użyciuTask.Delay
np.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())); }
Będę szczery, nie próbowałem tego z dokładnym kodem powyżej, ale używam go w ciasnych pętlach, kiedy umieszczam wiele przedmiotów w
ItemsControl
drogim szablonie przedmiotu, czasami dodając małe opóźnienie, aby nadać drugiemu rzeczy w interfejsie użytkownika więcej czasu.Na przykład:
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); } }
W Sklepie Windows, gdy w kolekcji jest ładna zmiana motywu, efekt jest całkiem pożądany.
Łukasz
źródło
await
spowoduje, że kompilator zarejestruje resztę metody asynchronicznej jako kontynuację oczekiwanego zadania. Ta kontynuacja nastąpi w wątku interfejsu użytkownika (ten sam kontekst synchronizacji). Następnie formant powraca do obiektu wywołującego metody asynchronicznej, tj. Podsystemu zdarzeń WPF, w którym zdarzenia będą uruchamiane do momentu, gdy zaplanowana kontynuacja zostanie uruchomiona po upływie określonego czasu opóźnienia.Utwórz DoEvent () w 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();
Pracuj dobrze dla mnie!
źródło
Odpowiadając na pierwotne pytanie: gdzie jest DoEvents?
Myślę, że DoEvents to VBA. Wydaje się, że VBA nie ma funkcji uśpienia. Ale VBA ma sposób na uzyskanie dokładnie tego samego efektu, co uśpienie lub opóźnienie. Wydaje mi się, że DoEvents jest odpowiednikiem Sleep (0).
W VB i C # masz do czynienia z .NET. Pierwotne pytanie to pytanie w języku C #. W C # użyłbyś Thread.Sleep (0), gdzie 0 to 0 milisekund.
Potrzebujesz
using System.Threading.Task;
u góry pliku, aby użyć
Sleep(100);
w swoim kodzie.
źródło