Tengo el siguiente código de muestra que hace zoom cada vez que se presiona un botón:
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 salida es:
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
como ves, hay un problema, que pensé resolver usando Application.DoEvents();
pero ... no existe a priori en .NET 4.
¿Qué hacer?
Respuestas:
El antiguo método Application.DoEvents () ha quedado obsoleto en WPF a favor del uso de un Dispatcher o un Subproceso de trabajo en segundo plano para realizar el procesamiento como ha descrito. Consulte los enlaces para ver un par de artículos sobre cómo utilizar ambos objetos.
Si es absolutamente necesario utilizar Application.DoEvents (), entonces simplemente puede importar system.windows.forms.dll a su aplicación y llamar al método. Sin embargo, esto realmente no es recomendable, ya que está perdiendo todas las ventajas que ofrece WPF.
fuente
Application.DoEvents
después de incrementar myScaleTransform.ScaleX. No sé si es posible con Dispatcher.Prueba algo como esto
public static void DoEvents() { Application.Current.Dispatcher.Invoke(DispatcherPriority.Background, new Action(delegate { })); }
fuente
public static void DoEvents(this Application a)
Application.Current
veces es nulo ... por lo que quizás no sea del todo equivalente.Bueno, acabo de llegar a un caso en el que empiezo a trabajar en un método que se ejecuta en el subproceso de Dispatcher, y debe bloquearse sin bloquear el subproceso de la interfaz de usuario. Resulta que msdn explica cómo implementar un DoEvents () basado en el propio 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; }
(tomado del método Dispatcher.PushFrame )
Algunos pueden preferirlo en un solo método que aplicará la misma lógica:
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); }
fuente
Si solo necesita actualizar el gráfico de la ventana, mejor utilícelo así
public static void DoEvents() { Application.Current.Dispatcher.Invoke(DispatcherPriority.Render, new Action(delegate { })); }
fuente
myCanvas.UpdateLayout();
parece funcionar también.
fuente
Un problema con ambos enfoques propuestos es que implican un uso inactivo de la CPU (hasta un 12% en mi experiencia). Esto es subóptimo en algunos casos, por ejemplo, cuando el comportamiento de la interfaz de usuario modal se implementa utilizando esta técnica.
La siguiente variación introduce un retraso mínimo entre fotogramas utilizando un temporizador (tenga en cuenta que está escrito aquí con Rx pero se puede lograr con cualquier temporizador regular):
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()));
fuente
Desde la introducción de
async
yawait
ahora es posible renunciar al subproceso de la interfaz de usuario a la mitad de un (anteriormente) * bloque de código síncrono utilizandoTask.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())); }
Seré honesto, no lo he probado con el código exacto anterior, pero lo uso en bucles estrechos cuando coloco muchos elementos en una
ItemsControl
plantilla de elemento costosa, a veces agregando un pequeño retraso para dar al otro cosas en la interfaz de usuario más tiempo.Por ejemplo:
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); } }
En Windows Store, cuando hay una buena transición de tema en la colección, el efecto es bastante deseable.
Luke
fuente
await
hará que el compilador registre el resto del método asíncrono como una continuación de la tarea esperada. Esa continuación ocurrirá en el hilo de la interfaz de usuario (mismo contexto de sincronización). El control luego regresa al llamador del método asíncrono, es decir, el subsistema de eventos WPF, donde los eventos se ejecutarán hasta que la continuación programada se ejecute en algún momento después de que expire el período de retraso.Haga su DoEvent () en 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();
¡Funciona bien para mí!
fuente
Respondiendo a la pregunta original: ¿Dónde está DoEvents?
Creo que DoEvents es VBA. Y VBA no parece tener una función de suspensión. Pero VBA tiene una forma de obtener exactamente el mismo efecto que una suspensión o un retraso. Me parece que DoEvents es equivalente a Sleep (0).
En VB y C #, se trata de .NET. Y la pregunta original es una pregunta de C #. En C #, usaría Thread.Sleep (0), donde 0 es 0 milisegundos.
Necesitas
using System.Threading.Task;
en la parte superior del archivo para usar
Sleep(100);
en su código.
fuente