WPF에서 Application.DoEvents ()는 어디에 있습니까?


88

버튼을 누를 때마다 확대 / 축소되는 다음 샘플 코드가 있습니다.

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));
    }
}

출력은 다음과 같습니다.

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

보시다시피, 사용하여 해결한다고 생각한 문제가 Application.DoEvents();있지만 ... NET 4 에는 선험적 이지 않습니다 .

무엇을해야합니까?


8
스레딩? Application.DoEvents ()는 적절한 다중 스레드 응용 프로그램을 작성하기위한 가난한 사람의 대체물이었고 어떤 경우에도 극도로 열악한 실행이었습니다.
Colin Mackay

2
나는 그것이 가난하고 나쁘다는 것을 알고 있지만 전혀 아무것도 선호하지 않는 것을 선호합니다.
serhio

답변:


25

이전 Application.DoEvents () 메서드는 설명 된대로 처리를 수행하기 위해 Dispatcher 또는 백그라운드 작업자 스레드 를 사용하기 위해 WPF에서 더 이상 사용되지 않습니다 . 두 개체를 사용하는 방법에 대한 몇 가지 문서에 대한 링크를 참조하십시오.

Application.DoEvents ()를 반드시 사용해야하는 경우 단순히 system.windows.forms.dll을 응용 프로그램으로 가져 와서 메서드를 호출 할 수 있습니다. 그러나 이것은 WPF가 제공하는 모든 이점을 잃어 버리기 때문에 권장되지 않습니다.


나쁘고 나쁘다는 것을 알고 있지만, 아무것도 선호하지 않는 것을 선호합니다 ... 내 상황에서 Dispatcher를 어떻게 사용할 수 있습니까?
serhio

3
나는 당신의 상황을 이해합니다. 첫 번째 WPF 앱을 작성할 때 그 안에 있었지만 계속해서 새 라이브러리를 배우는 데 시간이 걸렸고 장기적으로는 훨씬 더 좋았습니다. 시간을내는 것이 좋습니다. 귀하의 특정 경우에 대해서는 클릭 이벤트가 발생할 때마다 디스패처가 좌표 표시를 처리하기를 원하는 것처럼 보입니다. 정확한 구현을 위해 Dispatcher에 대해 더 많이 읽어야합니다.
Dillie-O

아니요, Application.DoEventsmyScaleTransform.ScaleX를 증가시킨 후 전화 하겠습니다. Dispatcher로 가능한지 모르겠습니다.
serhio

12
Application.DoEvents ()를 제거하는 것은 MS가 Windows 8에서 "시작"단추를 제거하는 것처럼 거의 성가신
JeffHeaton 2014 년

2
아니, 그것은 좋은 일이었다. 그 방법은 좋은 것보다 더 많은 해를 끼쳤다.
제시

136

이런 식으로 시도

public static void DoEvents()
{
    Application.Current.Dispatcher.Invoke(DispatcherPriority.Background,
                                          new Action(delegate { }));
}

1
난 : 응용 프로그램에 대한 확장 방법을 썼습니다public static void DoEvents(this Application a)
serhio

@serhio : 깔끔한 확장 방법 :)
Fredrik Hedblad 2010

2
그러나 실제 응용 프로그램에서 Application.Current가끔은 null 이라는 점에 주목해야합니다 .
serhio

완전한. 이것이 답이되어야합니다. Naysayers는 신용을 얻어서는 안됩니다.
Jeson Martajaya

6
이것은 프레임을 푸시하지 않기 때문에 항상 작동하지 않을 것입니다. 만약 인터럽트 명령이 만들어 질 경우 (즉,이 명령을 동기화하는 WCF 메서드에 대한 호출) 차단 될 것이므로 '새로 고침'이 표시되지 않을 가능성이 있습니다. .. 이것이 MSDN 리소스에서 제공하는 flq 답변이 이것보다 더 정확한 이유입니다.
GY

57

글쎄, 방금 Dispatcher 스레드에서 실행되는 메서드에서 작업을 시작하고 UI 스레드를 차단하지 않고 차단해야하는 경우가 발생했습니다. msdn은 Dispatcher 자체를 기반으로 DoEvents ()를 구현하는 방법을 설명합니다.

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;
}

( Dispatcher.PushFrame 메서드 에서 가져옴 )

일부는 동일한 논리를 적용하는 단일 방법을 선호 할 수 있습니다.

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);
}

좋은 발견! 이것은 Meleak이 제안한 구현보다 더 안전 해 보입니다. 내가 발견 그것에 대해 블로그 포스트
HugoRune

2
@HugoRune 해당 블로그 게시물은이 접근 방식이 불필요하며 Meleak과 동일한 구현을 사용한다고 말합니다.
Lukazoid

1
@Lukazoid 내가 말할 수있는 한 간단한 구현은 추적하기 어려운 잠금을 유발할 수 있습니다. (원인을 잘 모르겠습니다. DoEvents를 다시 호출하는 디스패처 큐의 코드이거나 추가 디스패처 프레임을 생성하는 디스패처 큐의 코드 일 수 있습니다.) 어쨌든 exitFrame 솔루션은 그러한 문제를 나타내지 않았으므로 그것을 추천합니다. (또는 물론 doEvents를 전혀 사용하지 않음)
HugoRune

1
앱이 종료 될 때 caliburn의 VM 관련 방법과 함께 대화 상자 대신 창에 오버레이를 표시하면 콜백이 배제되고 차단없이 차단해야합니다. DoEvents 해킹이없는 솔루션을 제시해 주시면 기쁩니다.
flq

1
이전 블로그 게시물에 대한 새 링크 : kent-boogaart.com/blog/dispatcher-frames
CAD

11

업데이트 창 그래픽이 필요한 경우 다음과 같이 사용하는 것이 좋습니다.

public static void DoEvents()
{
    Application.Current.Dispatcher.Invoke(DispatcherPriority.Render,
                                          new Action(delegate { }));
}

DispatcherPriority.Render를 사용하면 DispatcherPriority.Background보다 더 빠르게 작동합니다. StopWatcher로 오늘 테스트
Александр Пекшев

이 트릭을 사용했지만 최상의 결과를 위해 DispatcherPriority.Send (가장 높은 우선 순위)를 사용했습니다. 더 반응이 빠른 UI.
Bent Rasmussen

6
myCanvas.UpdateLayout();

잘 작동하는 것 같습니다.


나에게 훨씬 안전 해 보이기 때문에 이것을 사용할 것이지만 다른 경우에는 DoEvents를 유지할 것입니다.
Carter Medlin 2013

이유를 모르지만 이것은 나를 위해 작동하지 않습니다. DoEvents ()가 제대로 작동합니다.
newman

제 경우에는 DoEvents ()와 마찬가지로이 작업을 수행해야했습니다
Jeff

3

두 가지 제안 된 접근 방식의 한 가지 문제점은 유휴 CPU 사용량 (내 경험상 최대 12 %)이 수반된다는 것입니다. 예를 들어 모달 UI 동작이이 기술을 사용하여 구현되는 경우와 같이 일부 경우에 이는 차선책입니다.

다음 변형은 타이머를 사용하여 프레임 사이에 최소 지연을 도입합니다 (여기서는 Rx로 작성되지만 일반 타이머로 달성 할 수 있음).

 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()));

1

도입 이후 asyncawaitA (이전)를 통해 UI 스레드 도중에 포기 위해 가능해 코드 동기 블록 * 사용 Task.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()));
}

솔직히 말해서 위의 정확한 코드로 시도하지는 않았지만 ItemsControl값 비싼 항목 템플릿이있는에 많은 항목을 배치 할 때 타이트한 루프에서 사용하고 때로는 다른 항목에 약간의 지연을 추가합니다. UI에 더 많은 시간이 있습니다.

예를 들면 :

        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);
            }
        }

Windows Store에서 컬렉션에 멋진 테마 전환이있을 때 그 효과는 매우 바람직합니다.

루크

  • 주석을 참조하십시오. 내 대답을 빠르게 작성할 때 동기 코드 블록을 가져온 다음 스레드를 호출자에게 다시 넘기는 행위에 대해 생각하고 있었는데, 그 결과 코드 블록이 비 동기화되었습니다. 독자들이 Servy와 내가 무슨 말을하는지 알 수 없기 때문에 내 대답을 완전히 바꾸고 싶지 않습니다.

"이제 동기식 블록을 통해 UI 스레드를 부분적으로 포기할 수 있습니다."아니요, 그렇지 않습니다. 당신은 한 코드 비동기를 만들어 오히려 동기 방식의 UI 스레드에서 메시지를 펌핑보다. 이제 올바르게 설계된 WPF 응용 프로그램은 기존 메시지 펌프가 메시지를 적절하게 펌핑 할 수 있도록 비동기를 사용하여 처음에 UI 스레드에서 장기 실행 작업을 동 기적으로 실행하여 UI 스레드를 차단하지 않는 응용 프로그램입니다.
Servy

@Servy 내부적으로 await는 컴파일러가 대기중인 작업의 연속으로 나머지 비동기 메서드를 등록하도록합니다. 이 연속은 UI 스레드에서 발생합니다 (동일한 동기화 컨텍스트). 그런 다음 제어는 비동기 메서드, 즉 WPF 이벤트 하위 시스템의 호출자에게 돌아갑니다. 여기서 이벤트는 지연 기간이 만료 된 후 예약 된 연속이 실행될 때까지 실행됩니다.
Luke Puplett 2014

네, 잘 알고 있습니다. 이것이 메서드를 비동기식으로 만드는 것입니다 (호출자에게 제어를 양보하고 연속 작업 만 예약). 귀하의 대답은 메서드가 실제로 비동기 를 사용하여 UI를 업데이트 할 때 동기식이라고 말합니다 .
Servy 2011

첫 번째 방법 (OP의 코드)은 동기식 Servy입니다. 두 번째 예제는 루프에 있거나 항목을 긴 목록에 쏟아야 할 때 UI를 계속 유지하기위한 팁입니다.
Luke Puplett 2014

그리고 여러분이 한 것은 동기 코드를 비동기 적으로 만드는 것입니다. 설명에 나와 있거나 답변이 요구하는대로 동기식 메서드 내에서 UI가 응답하도록 유지하지 않았습니다.
Servy

0

WPF에서 DoEvent ()를 만듭니다.

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();

나를 위해 잘 작동하십시오!


-2

원래 질문에 대한 답 : DoEvents는 어디에 있습니까?

DoEvents는 VBA라고 생각합니다. 그리고 VBA에는 절전 기능이없는 것 같습니다. 그러나 VBA에는 Sleep 또는 Delay와 똑같은 효과를 얻을 수있는 방법이 있습니다. DoEvents가 Sleep (0)에 해당하는 것 같습니다.

VB 및 C #에서는 .NET을 다루고 있습니다. 그리고 원래 질문은 C # 질문입니다. C #에서는 Thread.Sleep (0)을 사용합니다. 여기서 0은 0 밀리 초입니다.

당신은 필요합니다

using System.Threading.Task;

사용하려면 파일 상단에

Sleep(100);

귀하의 코드에서.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.