Application.DoEvents () trong WPF ở đâu?


88

Tôi có mã mẫu sau sẽ phóng to mỗi khi nhấn một nút:

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

đầu ra là:

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

như bạn có thể thấy, có một vấn đề mà tôi nghĩ sẽ giải quyết bằng cách sử dụng Application.DoEvents();nhưng ... nó không tồn tại trước trong .NET 4.

Để làm gì?


8
Phân luồng? Application.DoEvents () là sự thay thế của người nghèo để viết các ứng dụng đa luồng đúng cách và thực hành cực kỳ kém trong mọi trường hợp.
Colin Mackay

2
Tôi biết điều đó là nghèo nàn và tồi tệ, nhưng tôi thích một thứ không có gì cả.
serhio

Câu trả lời:


25

Phương thức Application.DoEvents () cũ đã không được chấp nhận trong WPF để sử dụng Điều phối viên hoặc Luồng công nhân nền để thực hiện xử lý như bạn đã mô tả. Xem các liên kết cho một vài bài viết về cách sử dụng cả hai đối tượng.

Nếu bạn nhất thiết phải sử dụng Application.DoEvents (), thì bạn chỉ cần nhập system.windows.forms.dll vào ứng dụng của mình và gọi phương thức. Tuy nhiên, điều này thực sự không được khuyến khích, vì bạn đang mất tất cả những lợi thế mà WPF cung cấp.


Tôi biết điều đó là kém và tệ, nhưng tôi thích thứ gì đó không có gì cả ... Làm thế nào tôi có thể sử dụng Điều phối viên trong tình huống của mình?
serhio

3
Tôi hiểu tình huống của bạn. Tôi đã ở trong đó khi viết ứng dụng WPF đầu tiên của mình, nhưng tôi đã tiếp tục và dành thời gian để tìm hiểu thư viện mới và về lâu dài sẽ tốt hơn nhiều cho nó. Tôi thực sự khuyên bạn nên dành thời gian. Đối với trường hợp cụ thể của bạn, có vẻ như tôi muốn người điều phối xử lý việc hiển thị các tọa độ mỗi khi sự kiện nhấp chuột của bạn kích hoạt. Bạn cần đọc thêm về Điều phối viên để biết cách triển khai chính xác.
Dillie-O

không, tôi sẽ gọi Application.DoEventssau khi tăng myScaleTransform.ScaleX. Không biết nếu có thể với Dispatcher.
serhio

12
Gỡ bỏ ứng
dụng.DoEvents

2
Không, đó là một điều tốt, phương pháp đó gây hại nhiều hơn lợi.
Jesse

136

Hãy thử một cái gì đó như thế này

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

1
Tôi thậm chí đã viết một phương pháp khuyến nông để áp dụng :)public static void DoEvents(this Application a)
serhio

@serhio: Phương pháp mở rộng gọn gàng :)
Fredrik Hedblad

2
Tuy nhiên, tôi nên nhận xét rằng trong ứng dụng thực Application.Currentđôi khi là null ... vì vậy có lẽ nó không hoàn toàn tương đương.
serhio

Hoàn hảo. Đây nên là câu trả lời. Naysayers không nên nhận được tín dụng.
Jeson Martajaya

6
Điều này sẽ không phải lúc nào cũng hoạt động vì nó không đẩy khung, nếu một lệnh ngắt được thực hiện (tức là một lệnh gọi đến một phương thức WCF trong một phương thức đồng bộ tiếp tục lệnh này) rất có thể bạn sẽ không thấy 'refresh' vì nó sẽ bị chặn .. Đây là lý do tại sao câu trả lời flq được cung cấp từ tài nguyên MSDN đúng hơn câu này.
GY

57

Chà, tôi vừa gặp một trường hợp khi tôi bắt đầu làm việc trên một phương thức chạy trên luồng Điều phối và nó cần chặn mà không chặn Luồng giao diện người dùng. Hóa ra msdn giải thích cách triển khai DoEvents () dựa trên chính 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;
}

(lấy từ Phương pháp Dispatcher.PushFrame )

Một số có thể thích nó trong một phương pháp duy nhất sẽ thực thi cùng một logic:

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

Rất vui! Điều này trông an toàn hơn so với cách triển khai do Meleak đề xuất. Tôi tìm thấy một bài viết trên blog về nó
HugoRune

2
@HugoRune Bài đăng trên blog đó nói rằng cách tiếp cận này là không cần thiết và sử dụng cách triển khai tương tự như Meleak.
Lukazoid

1
@Lukazoid Theo như tôi có thể nói, việc triển khai đơn giản có thể gây ra các khóa khó theo dõi. (Tôi không chắc về nguyên nhân, có thể sự cố là mã trong hàng đợi điều phối viên gọi lại DoEvents hoặc mã trong hàng đợi điều phối viên tạo ra các khung điều phối khác.) Trong mọi trường hợp, giải pháp với exitFrame không cho thấy vấn đề nào như vậy nên tôi đề nghị một trong những. (Hoặc, tất nhiên, hoàn toàn không sử dụng doEvents)
HugoRune 26/10/12

1
Hiển thị lớp phủ trên cửa sổ của bạn thay vì hộp thoại kết hợp với cách thức của Calurn để liên quan đến máy ảo khi ứng dụng đang đóng đã loại trừ các lệnh gọi lại và yêu cầu chúng tôi chặn mà không chặn. Tôi sẽ rất vui nếu bạn giới thiệu cho tôi một giải pháp mà không bị hack DoEvents.
flq

1
liên kết mới đến bài đăng trên blog cũ: kent-boogaart.com/blog/dispatcher-frames
CAD bloke

11

Nếu bạn chỉ cần cập nhật đồ họa cửa sổ, tốt hơn hãy sử dụng như thế này

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

Sử dụng DispatcherPinent.Render hoạt động nhanh hơn sau đó DispatcherPinent.Background. Đã kiểm tra hôm nay với StopWatcher
Александр Пекшев

Tôi vừa mới sử dụng thủ thuật này nhưng sử dụng DispatcherPosystem.Send (ưu tiên cao nhất) để có kết quả tốt nhất; giao diện người dùng phản hồi nhanh hơn.
Bent Rasmussen

6
myCanvas.UpdateLayout();

dường như hoạt động tốt.


Tôi sẽ sử dụng cái này vì nó có vẻ an toàn hơn rất nhiều đối với tôi, nhưng tôi sẽ giữ DoEvents cho những trường hợp khác.
Carter Medlin

Không biết tại sao nhưng điều này không hiệu quả với tôi. DoEvents () hoạt động tốt.
newman

Trong trường hợp của tôi, tôi phải làm điều này cũng như DoEvents ()
Jeff

3

Một vấn đề với cả hai cách tiếp cận được đề xuất là chúng dẫn đến việc sử dụng CPU nhàn rỗi (theo kinh nghiệm của tôi lên đến 12%). Điều này là không tối ưu trong một số trường hợp, chẳng hạn như khi hành vi giao diện người dùng phương thức được thực hiện bằng kỹ thuật này.

Biến thể sau đây giới thiệu độ trễ tối thiểu giữa các khung hình sử dụng bộ đếm thời gian (lưu ý rằng nó được viết ở đây bằng Rx nhưng có thể đạt được với bất kỳ bộ đếm thời gian thông thường nào):

 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

Kể từ khi giới thiệu asyncawaitbây giờ có thể từ bỏ phần luồng giao diện người dùng thông qua một khối mã đồng bộ (trước đây) * bằng cách sử dụng Task.Delay, ví dụ:

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

Thành thật mà nói, tôi đã không thử nó với mã chính xác ở trên, nhưng tôi sử dụng nó trong các vòng lặp chặt chẽ khi tôi đặt nhiều mặt hàng vào một ItemsControlmặt hàng có mẫu mặt hàng đắt tiền, đôi khi thêm một độ trễ nhỏ để cung cấp cho mặt hàng kia nhiều thời gian hơn trên giao diện người dùng.

Ví dụ:

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

Trên Windows Store, khi có một chuyển đổi chủ đề đẹp mắt trên bộ sưu tập, hiệu ứng khá đáng mong đợi.

Luke

  • Xem ý kiến. Khi tôi nhanh chóng viết câu trả lời của mình, tôi đã nghĩ về hành động lấy một khối mã đồng bộ và sau đó chuyển chuỗi trở lại trình gọi của nó, tác động của việc này làm cho khối mã không đồng bộ. Tôi không muốn nói lại hoàn toàn câu trả lời của mình bởi vì khi đó người đọc không thể thấy Servy và tôi đã cãi nhau vì điều gì.

"bây giờ có thể từ bỏ phần luồng giao diện người dùng thông qua một khối đồng bộ" Không, không phải vậy. Bạn vừa tạo mã không đồng bộ , thay vì bơm thông báo từ chuỗi giao diện người dùng theo một phương pháp đồng bộ. Giờ đây, một ứng dụng WPF được thiết kế chính xác sẽ là ứng dụng không bao giờ chặn chuỗi giao diện người dùng bằng cách thực hiện đồng bộ các hoạt động chạy dài trong chuỗi giao diện người dùng ngay từ đầu, sử dụng tính không đồng bộ để cho phép máy bơm thông báo hiện có bơm thông báo một cách thích hợp.
Servy

@Servy Dưới vỏ bọc, awaittrình biên dịch sẽ đăng ký phần còn lại của phương thức không đồng bộ như một phần tiếp theo của nhiệm vụ đã chờ đợi. Sự tiếp tục đó sẽ xảy ra trên chuỗi giao diện người dùng (cùng một ngữ cảnh đồng bộ hóa). Sau đó, điều khiển trả về trình gọi của phương thức không đồng bộ, tức là hệ thống con sự kiện WPFs, nơi các sự kiện sẽ chạy cho đến khi tiếp tục theo lịch trình chạy đôi khi sau khi hết thời gian trễ.
Luke Puplett,

vâng, tôi biết rõ về điều đó. Đó là điều làm cho phương thức không đồng bộ (điều khiển nhường quyền điều khiển cho người gọi và chỉ lập lịch tiếp tục). Câu trả lời của bạn nói rằng phương pháp là đồng bộ trong khi thực tế nó đang sử dụng tính không đồng bộ để cập nhật giao diện người dùng.
Servy

Phương thức đầu tiên (mã của OP) là đồng bộ, Servy. Ví dụ thứ hai chỉ là một mẹo để giữ cho giao diện người dùng hoạt động khi ở trong một vòng lặp hoặc phải đổ các mục vào một danh sách dài.
Luke Puplett

Và những gì bạn đã làm là làm cho mã đồng bộ trở nên không đồng bộ. Bạn đã không giữ giao diện người dùng phản hồi trong một phương pháp đồng bộ, như mô tả của bạn nêu hoặc câu trả lời yêu cầu.
Servy

0

Tạo DoEvent () của bạn trong 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();

Làm việc tốt cho tôi!


-2

Trả lời câu hỏi ban đầu: DoEvents ở đâu?

Tôi nghĩ DoEvents là VBA. Và VBA dường như không có chức năng Sleep. Nhưng VBA có một cách để có được hiệu ứng chính xác giống như Sleep hoặc Delay. Có vẻ như với tôi rằng DoEvents tương đương với Sleep (0).

Trong VB và C #, bạn đang kinh doanh .NET. Và câu hỏi ban đầu là một câu hỏi C #. Trong C #, bạn sẽ sử dụng Thread.Sleep (0), trong đó 0 là 0 mili giây.

Bạn cần

using System.Threading.Task;

ở đầu tệp để sử dụng

Sleep(100);

trong mã của bạn.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.