Ich habe den folgenden Beispielcode, der jedes Mal zoomt, wenn eine Taste gedrückt wird:
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));
}
}
die Ausgabe ist:
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
wie Sie sehen, gibt es ein Problem, das ich mit Application.DoEvents();
lösen wollte, aber ... es existiert nicht a priori in .NET 4.
Was ist zu tun?
Die alte Application.DoEvents () -Methode wurde in WPF verworfen, um die beschriebene Verarbeitung mit einem Dispatcher oder einem Background Worker Thread durchzuführen. Unter den Links finden Sie einige Artikel zur Verwendung beider Objekte.
Wenn Sie Application.DoEvents () unbedingt verwenden müssen, können Sie einfach die Datei system.windows.forms.dll in Ihre Anwendung importieren und die Methode aufrufen. Dies wird jedoch nicht empfohlen, da Sie alle Vorteile von WPF verlieren.
Versuchen Sie so etwas
public static void DoEvents()
{
Application.Current.Dispatcher.Invoke(DispatcherPriority.Background,
new Action(delegate { }));
}
Nun, ich bin gerade auf einen Fall gestoßen, in dem ich mit der Arbeit an einer Methode beginne, die auf dem Dispatcher-Thread ausgeführt wird, und die blockiert werden muss, ohne den UI-Thread zu blockieren. Es stellte sich heraus, dass msdn erklärt, wie DoEvents () basierend auf dem Dispatcher selbst implementiert werden:
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;
}
(entnommen aus Dispatcher.PushFrame-Methode )
Wenn Sie nur die Fenstergrafik aktualisieren müssen, verwenden Sie diese besser
public static void DoEvents()
{
Application.Current.Dispatcher.Invoke(DispatcherPriority.Render,
new Action(delegate { }));
}
myCanvas.UpdateLayout();
scheint auch zu funktionieren.
Ein Problem bei beiden vorgeschlagenen Ansätzen ist, dass sie eine inaktive CPU-Auslastung mit sich bringen (nach meiner Erfahrung bis zu 12%). Dies ist in einigen Fällen suboptimal, wenn beispielsweise das modale Verhalten der Benutzeroberfläche mithilfe dieser Technik implementiert wird.
Die folgende Variation führt eine minimale Verzögerung zwischen Frames mit einem Timer ein (beachten Sie, dass dies hier mit Rx geschrieben ist, aber mit jedem normalen Timer erreicht werden kann):
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()));
Seit der Einführung von async
und await
ist es jetzt möglich, den UI-Thread teilweise durch einen (früher) * synchronen Codeblock mit Task.Delay
, Z.
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()));
}
Ich bin ehrlich, ich habe es nicht mit dem exakten Code oben versucht, aber ich verwende es in engen Schleifen, wenn ich viele Elemente in eine ItemsControl
lege, die eine teure Elementvorlage enthält, manchmal füge ich eine hinzu kleine Verzögerung, um den anderen Dingen auf der Benutzeroberfläche mehr Zeit zu geben.
Beispielsweise:
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);
}
}
Im Windows Store ist der Effekt bei einem Nice-Themaübergang in der Sammlung sehr wünschenswert.
Luke