Tenho o seguinte código de amostra que aumenta o zoom sempre que um botão é pressionado:
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));
}
}
a saída é:
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 podem ver, tem um problema, que pensei resolver usando Application.DoEvents();
mas ... não existe a priori no .NET 4.
O que fazer?
Respostas:
O antigo método Application.DoEvents () foi descontinuado no WPF em favor do uso de um Dispatcher ou um Background Worker Thread para fazer o processamento conforme você descreveu. Veja os links para alguns artigos sobre como usar os dois objetos.
Se você realmente precisar usar Application.DoEvents (), poderá simplesmente importar o system.windows.forms.dll para o seu aplicativo e chamar o método. No entanto, isso realmente não é recomendado, pois você está perdendo todas as vantagens que o WPF oferece.
fonte
Tente algo assim
public static void DoEvents() { Application.Current.Dispatcher.Invoke(DispatcherPriority.Background, new Action(delegate { })); }
fonte
public static void DoEvents(this Application a)
Application.Current
às vezes é nulo ... então talvez não seja totalmente equivalente.Bem, acabei de chegar a um caso em que comecei a trabalhar em um método que é executado no thread do Dispatcher e ele precisa ser bloqueado sem bloquear o UI Thread. Acontece que o msdn explica como implementar um DoEvents () com base no próprio 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; }
(retirado do método Dispatcher.PushFrame )
Alguns podem preferir em um único método que aplicará a mesma 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); }
fonte
Se você precisa apenas atualizar o gráfico da janela, é melhor usar assim
public static void DoEvents() { Application.Current.Dispatcher.Invoke(DispatcherPriority.Render, new Action(delegate { })); }
fonte
myCanvas.UpdateLayout();
parece funcionar bem.
fonte
Um problema com as duas abordagens propostas é que elas implicam no uso ocioso da CPU (até 12% na minha experiência). Isso não é ideal em alguns casos, por exemplo, quando o comportamento modal da interface do usuário é implementado usando esta técnica.
A variação a seguir introduz um atraso mínimo entre os quadros usando um temporizador (observe que está escrito aqui com Rx, mas pode ser obtido com qualquer 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()));
fonte
Desde a introdução de
async
eawait
agora é possível abandonar o thread da IU parcialmente por meio de um (anteriormente) * bloco síncrono de código usandoTask.Delay
, por exemplo,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())); }
Vou ser honesto, não tentei com o código exato acima, mas eu o uso em loops apertados quando estou colocando muitos itens em um
ItemsControl
que tem um modelo de item caro, às vezes adicionando um pequeno atraso para dar o outro coisas na IU mais tempo.Por exemplo:
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); } }
Na Windows Store, quando há uma boa transição de tema na coleção, o efeito é bastante desejável.
Lucas
fonte
await
fará com que o compilador inscreva o restante do método assíncrono como uma continuação da tarefa esperada. Essa continuação ocorrerá no thread de interface do usuário (mesmo contexto de sincronização). O controle então retorna ao chamador do método assíncrono, isto é, subsistema de eventos WPFs, onde os eventos serão executados até que a continuação programada seja executada algum tempo após o período de atraso expirar.Faça seu DoEvent () no 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 bem para mim!
fonte
Respondendo à pergunta original: Onde está DoEvents?
Acho que DoEvents é VBA. E o VBA não parece ter uma função Sleep. Mas o VBA tem uma maneira de obter exatamente o mesmo efeito de um Sleep ou Delay. Parece-me que DoEvents é equivalente a Sleep (0).
Em VB e C #, você está lidando com .NET. E a pergunta original é uma pergunta C #. Em C #, você usaria Thread.Sleep (0), onde 0 é 0 milissegundos.
Você precisa
using System.Threading.Task;
na parte superior do arquivo para usar
Sleep(100);
em seu código.
fonte