J'ai l'exemple de code suivant qui effectue un zoom chaque fois qu'un bouton est enfoncé:
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));
}
}
la sortie est:
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
comme vous pouvez le voir, il y a un problème, que je pensais résoudre en utilisant Application.DoEvents();
mais ... il n'existe pas a priori dans .NET 4.
Que faire?
Réponses:
L'ancienne méthode Application.DoEvents () a été déconseillée dans WPF en faveur de l'utilisation d'un Dispatcher ou d'un Background Worker Thread pour effectuer le traitement comme vous l'avez décrit. Voir les liens pour quelques articles sur la façon d'utiliser les deux objets.
Si vous devez absolument utiliser Application.DoEvents (), vous pouvez simplement importer le system.windows.forms.dll dans votre application et appeler la méthode. Cependant, ce n'est vraiment pas recommandé, car vous perdez tous les avantages fournis par WPF.
la source
Application.DoEvents
après avoir incrémenté myScaleTransform.ScaleX. Je ne sais pas si c'est possible avec Dispatcher.Essayez quelque chose comme ça
public static void DoEvents() { Application.Current.Dispatcher.Invoke(DispatcherPriority.Background, new Action(delegate { })); }
la source
public static void DoEvents(this Application a)
Application.Current
parfois est nul ... alors peut-être que ce n'est pas tout à fait équivalent.Eh bien, je viens de frapper un cas où je commence à travailler sur une méthode qui s'exécute sur le thread Dispatcher, et elle doit bloquer sans bloquer le thread UI. Il s'avère que msdn explique comment implémenter un DoEvents () basé sur le Dispatcher lui-même:
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; }
(tiré de la méthode Dispatcher.PushFrame )
Certains peuvent le préférer dans une seule méthode qui appliquera la même logique:
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); }
la source
Si vous avez juste besoin de mettre à jour le graphique de la fenêtre, mieux vaut l'utiliser comme ceci
public static void DoEvents() { Application.Current.Dispatcher.Invoke(DispatcherPriority.Render, new Action(delegate { })); }
la source
myCanvas.UpdateLayout();
semble fonctionner aussi bien.
la source
Un problème avec les deux approches proposées est qu'elles impliquent une utilisation du processeur inactive (jusqu'à 12% selon mon expérience). Ceci est sous-optimal dans certains cas, par exemple lorsque le comportement modal de l'interface utilisateur est implémenté à l'aide de cette technique.
La variation suivante introduit un délai minimum entre les trames en utilisant un timer (notez qu'il est écrit ici avec Rx mais peut être réalisé avec n'importe quel timer régulier):
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()));
la source
Depuis l'introduction de
async
etawait
il est maintenant possible d'abandonner le thread d'interface utilisateur à mi-chemin à travers un (anciennement) * bloc de code synchrone en utilisantTask.Delay
, par exempleprivate 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())); }
Je vais être honnête, je ne l'ai pas essayé avec le code exact ci-dessus, mais je l'utilise dans des boucles serrées lorsque je place de nombreux éléments dans un
ItemsControl
qui a un modèle d'article coûteux, ajoutant parfois un petit délai pour donner l'autre trucs sur l'interface utilisateur plus de temps.Par exemple:
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); } }
Sur Windows Store, lorsqu'il y a une belle transition de thème sur la collection, l'effet est tout à fait souhaitable.
Luke
la source
await
fera en sorte que le compilateur signe le reste de la méthode async en tant que continuation de la tâche attendue. Cette continuation se produira sur le thread d'interface utilisateur (même contexte de synchronisation). Le contrôle revient ensuite à l'appelant de la méthode async, c'est-à-dire le sous-système d'événements WPF, où les événements s'exécuteront jusqu'à ce que la continuation planifiée s'exécute quelque temps après l'expiration du délai.Créez votre DoEvent () dans 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();
Travaillez bien pour moi!
la source
Réponse à la question initiale: Où est DoEvents?
Je pense que DoEvents est VBA. Et VBA ne semble pas avoir de fonction de veille. Mais VBA a un moyen d'obtenir exactement le même effet qu'un Sleep ou Delay. Il me semble que DoEvents est équivalent à Sleep (0).
En VB et C #, vous avez affaire à .NET. Et la question d'origine est une question C #. En C #, vous utiliseriez Thread.Sleep (0), où 0 correspond à 0 millisecondes.
Vous avez besoin
using System.Threading.Task;
en haut du fichier pour utiliser
Sleep(100);
dans votre code.
la source