Application.DoEvents () ใน WPF อยู่ที่ไหน


89

ฉันมีโค้ดตัวอย่างต่อไปนี้ที่ซูมทุกครั้งที่กดปุ่ม:

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 () เมธอดเก่าได้เลิกใช้แล้วใน WPF เพื่อสนับสนุนการใช้DispatcherหรือBackground Worker Threadเพื่อดำเนินการประมวลผลตามที่คุณได้อธิบายไว้ ดูลิงก์สำหรับบทความสองสามข้อเกี่ยวกับวิธีใช้วัตถุทั้งสอง

หากคุณต้องใช้ Application.DoEvents () อย่างแน่นอนคุณสามารถนำเข้า system.windows.forms.dll ไปยังแอปพลิเคชันของคุณและเรียกใช้เมธอด อย่างไรก็ตามไม่แนะนำให้ทำเช่นนี้เนื่องจากคุณกำลังสูญเสียข้อดีทั้งหมดที่ WPF มอบให้


1
ฉันรู้ว่ามันแย่และไม่ดี แต่ฉันชอบอะไรที่ไม่มีอะไรเลย ... ฉันจะใช้ Dispatcher ในสถานการณ์ของฉันได้อย่างไร?
serhio

3
ฉันเข้าใจสถานการณ์ของคุณ ฉันอยู่ในนั้นเมื่อฉันเขียนแอป WPF ครั้งแรก แต่ฉันก็ใช้เวลาในการเรียนรู้ไลบรารีใหม่และดีกว่ามากสำหรับมันในระยะยาว ฉันขอแนะนำให้สละเวลา สำหรับกรณีเฉพาะของคุณฉันคิดว่าคุณต้องการให้ผู้มอบหมายงานจัดการการแสดงพิกัดทุกครั้งที่เหตุการณ์การคลิกของคุณเริ่มทำงาน คุณต้องอ่านข้อมูลเพิ่มเติมเกี่ยวกับ Dispatcher เพื่อการใช้งานที่แน่นอน
Dillie-O

13
การลบ Application.DoEvents () เกือบจะน่ารำคาญพอ ๆ กับ MS ที่ลบปุ่ม "Start" บน Windows 8
JeffHeaton

2
ไม่มันเป็นสิ่งที่ดีวิธีนั้นก่อให้เกิดผลเสียมากกว่าผลดี
Jesse

1
ฉันไม่อยากจะเชื่อเลยว่านี่ยังคงเป็นปัญหาในยุคนี้ ฉันจำได้ว่าต้องเรียก DoEvents ใน VB6 เพื่อจำลองพฤติกรรม async ปัญหาคือผู้ทำงานเบื้องหลังจะทำงานเฉพาะเมื่อคุณไม่ได้ทำการประมวลผล UI แต่การประมวลผล UI เช่นการสร้าง ListBoxItems หลายพันรายการสามารถล็อกเธรด UI ได้ เราควรทำอย่างไรเมื่อมีงาน UI หนัก ๆ ที่ต้องทำ?
Christian Findlay

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

3
อย่างไรก็ตามฉันควรสังเกตว่าในแอปพลิเคชันจริงApplication.Currentบางครั้งอาจเป็นโมฆะ ... ดังนั้นอาจจะไม่เทียบเท่า
serhio

สมบูรณ์แบบ. นี่น่าจะเป็นคำตอบ ผู้จ่ายไม่ควรได้รับเครดิต
Jeson Martajaya

7
สิ่งนี้จะไม่ได้ผลเสมอไปเนื่องจากไม่ได้ผลักเฟรมหากมีการทำคำสั่งขัดจังหวะ (เช่นการเรียกใช้วิธี WCF ซึ่งในซิงโครนิกดำเนินการต่อไปยังคำสั่งนี้) โอกาสที่คุณจะไม่เห็น 'รีเฟรช' เนื่องจากจะถูกบล็อก .. นี่คือเหตุผลว่าทำไมคำตอบ flq จากทรัพยากร MSDN จึงถูกต้องมากกว่าอันนี้
GY

58

ฉันเพิ่งเจอกรณีที่ฉันเริ่มทำงานกับวิธีการที่ทำงานบนเธรดผู้จัดส่งและจำเป็นต้องบล็อกโดยไม่ปิดกั้นเธรด UI ปรากฎว่า msdn อธิบายวิธีการใช้งาน DoEvents () ตาม 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;
}

(นำมาจากวิธี 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 bloke

13

หากคุณต้องการเพียงแค่อัปเดตกราฟิกหน้าต่างให้ใช้แบบนี้ดีกว่า

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

1
การใช้ DispatcherPriority.Render จะทำงานได้เร็วขึ้นจากนั้น DispatcherPriority.Background ทดสอบแล้ววันนี้กับ StopWatcher
АлександрПекшев

ฉันเพิ่งใช้เคล็ดลับนี้ แต่ใช้ DispatcherPriority.Send (ลำดับความสำคัญสูงสุด) เพื่อผลลัพธ์ที่ดีที่สุด UI ที่ตอบสนองมากขึ้น
Bent Rasmussen

6
myCanvas.UpdateLayout();

ดูเหมือนจะทำงานได้ดี


ฉันจะใช้สิ่งนี้เพราะดูเหมือนจะปลอดภัยกว่ามากสำหรับฉัน แต่ฉันจะเก็บ DoEvents ไว้สำหรับกรณีอื่น ๆ
Carter Medlin

ไม่รู้ว่าทำไม แต่มันไม่ได้ผลสำหรับฉัน DoEvents () ทำงานได้ดี
น้องใหม่

ในกรณีของฉันฉันต้องทำสิ่งนี้เช่นเดียวกับ 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

ตั้งแต่การเปิดตัวasyncและawaitตอนนี้เป็นไปได้ที่จะยกเลิกเธรด 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จะทำให้คอมไพเลอร์ลงทะเบียนส่วนที่เหลือของวิธีการ async เป็นความต่อเนื่องของงานที่รอคอย ความต่อเนื่องนั้นจะเกิดขึ้นบนเธรด UI (บริบทการซิงค์เดียวกัน) จากนั้นการควบคุมจะส่งกลับไปยังผู้เรียกของเมธอด async นั่นคือ WPFs eventing subsystem ซึ่งเหตุการณ์จะทำงานจนกว่าความต่อเนื่องตามกำหนดการจะทำงานในช่วงเวลาหนึ่งหลังจากหมดช่วงเวลาหน่วง
Luke Puplett

ใช่ฉันรู้ดีถึงเรื่องนี้ นั่นคือสิ่งที่ทำให้เมธอดเป็นแบบอะซิงโครนัส (ให้การควบคุมผู้โทรและกำหนดเวลาความต่อเนื่องเท่านั้น) คำตอบของคุณระบุว่าเมธอดนั้นซิงโครนัสเมื่อใช้อะซิงโครไนซ์เพื่ออัปเดต UI
Servy

วิธีแรก (รหัสของ OP) คือซิงโครนัส Servy ตัวอย่างที่สองเป็นเพียงเคล็ดลับในการทำให้ UI ดำเนินต่อไปเมื่ออยู่ในวงหรือต้องเทรายการลงในรายการยาว ๆ
Luke Puplett

และสิ่งที่คุณทำคือทำให้รหัสซิงโครนัสไม่ตรงกัน คุณไม่ได้รักษาการตอบสนองของ UI จากภายในวิธีซิงโครนัสตามที่คำอธิบายของคุณระบุหรือคำตอบที่ถาม
Servy

0

สร้าง DoEvent () ของคุณใน 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();

ทำงานได้ดีสำหรับฉัน!


-2

ตอบคำถามเดิม DoEvents อยู่ที่ไหน?

ฉันคิดว่า DoEvents คือ VBA และ VBA ดูเหมือนจะไม่มีฟังก์ชัน Sleep แต่ 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.