Манипулиране на UI елементи от друга нишка

Опитвам се отделна нишка в приложение на WinForms C# да стартира фонов работник, който контролира ProgressBar (шатър). Проблемът е, че когато се опитам да настроя лентата на видима, тя просто не прави нищо и опитах много форми на Invoke, но те не изглежда да помагат.

Следният метод progressBarCycle се извиква от отделна нишка.

    BackgroundWorker backgroundWorker = new BackgroundWorker();

    public void progressBarCycle(int duration)
    {
        backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);
        backgroundWorker.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker_ProgressChanged);
        backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker_RunWorkerCompleted);
        backgroundWorker.WorkerReportsProgress = true;
        backgroundWorker.WorkerSupportsCancellation = true;
        backgroundWorker.RunWorkerAsync(duration);
    }

    private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
    {
        BackgroundWorker worker = sender as BackgroundWorker;

        worker.ReportProgress(0);

        DateTime end = DateTime.Now.AddMilliseconds((int)e.Argument);
        while (DateTime.Now <= end)
        {
            System.Threading.Thread.Sleep(1000);
        }
    }

    private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        if (!this.IsHandleCreated)
            this.CreateHandle();
        statusStrip1.Invoke((MethodInvoker)delegate
        {
            progressBar1.Visible = false;
        });
        //    if (!this.IsHandleCreated)
        //    {
        //        this.CreateHandle();
        //        if (InvokeRequired) this.Invoke((MethodInvoker)(() => progressBar1.Visible = false));
        //        else progressBar1.Visible = false;
        //    }
        //    else
        //        if (InvokeRequired) this.Invoke((MethodInvoker)(() => progressBar1.Visible = false));
        //        else progressBar1.Visible = false;
    }

    private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        if (!this.IsHandleCreated)
            this.CreateHandle();
        statusStrip1.Invoke((MethodInvoker)delegate
        {
            progressBar1.Visible = true;
        });
        //    if (!this.IsHandleCreated)
        //    {
        //        this.CreateHandle();
        //        if (InvokeRequired) this.Invoke((MethodInvoker)(() => progressBar1.Visible = true));
        //        else progressBar1.Visible = true;
        //    }
        //    else
        //        if (InvokeRequired) this.Invoke((MethodInvoker)(() => progressBar1.Visible = true));
        //        else progressBar1.Visible = true;
    }

Пропускам ли нещо очевидно тук? Секциите за коментари са други неща, които съм пробвал.


person UncleDave    schedule 31.12.2012    source източник


Отговори (3)


ProgressChanged е вече повдигнат в нишката на потребителския интерфейс (чрез синхронизиращия контекст); вашият ProgressChanged не трябва да прави това Invoke - той може да манипулира потребителския интерфейс директно (за разлика от това, DoWork абсолютно не може да прави това). Може би истинският проблем е, че не правите нищо worker.ReportProgress(...) вътре в цикъла - така че това се случва само веднъж в началото.

Ето пълен пример:

using System;
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;
static class Program
{
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        using (var worker = new BackgroundWorker {
            WorkerReportsProgress = true })
        using (var progBar = new ProgressBar {
            Visible = false, Step = 1, Maximum = 100,
            Dock = DockStyle.Bottom })
        using (var btn = new Button { Dock = DockStyle.Top, Text = "Start" })
        using (var form = new Form { Controls = { btn, progBar } })
        {
            worker.ProgressChanged += (s,a) => {
                progBar.Visible = true;
                progBar.Value = a.ProgressPercentage;
            };
            worker.RunWorkerCompleted += delegate
            {
                progBar.Visible = false;
            };
            worker.DoWork += delegate
            {
                for (int i = 0; i < 100; i++)
                {
                    worker.ReportProgress(i);
                    Thread.Sleep(100);
                }
            };
            btn.Click += delegate
            {
                worker.RunWorkerAsync();
            };
            Application.Run(form);
        }
    }
}
person Marc Gravell    schedule 31.12.2012
comment
Сложих ProgressChanged и RunWorkerCompleted само за да видя дали това ще реши проблема ми. Когато вместо това бях включил целия този код в DoWork, той щеше да направи абсолютно същото. Състоянието на Visible просто никога няма да се промени, със или без извиквания. - О, и вероятно си струва да се спомене, че това е Marquee, така че не възнамерявам да използвам отчети за напредъка. - person UncleDave; 31.12.2012
comment
@UncleDave тогава звучи сякаш лентата за напредъка е вътре в друг контрол (панел може би), който не се вижда. - person Marc Gravell; 31.12.2012
comment
Лентата за напредъка е във формуляра, изпълняващ този код, който е и стартовият формуляр на приложението. - person UncleDave; 31.12.2012
comment
Не само, че не извиквате ReportProgress вътре в DoWork цикъла, в събитието ProgressChanged няма увеличение на стойността на лентата за прогрес. - person Steve; 31.12.2012
comment
ReportProgress се извиква веднъж в DoWork, което е всичко, от което се нуждае - лентата е маркировка, така че не се нуждае от актуализиране с напредъка. Проблемът е, че не мога да променя видимото свойство. - person UncleDave; 31.12.2012
comment
@UncleDave трябва да провериш панелите догоре. Проверете .Visible на всеки .Parent, докато стигнете до самия формуляр. Задаването му на видимост работи добре: вижте примера в редакцията - person Marc Gravell; 31.12.2012
comment
Всеки родител е видим, може ли това да има нещо общо с извикването на progressBarCycle от друга нишка? И тогава тази нишка (не основната нишка) извиква фоновия работник. - person UncleDave; 31.12.2012

  1. Стартирайте progressBarCycle от нишката на потребителския интерфейс. RunWorkerAsync ще създаде новата тема за вас.
  2. В backgroundWorker_ProgressChanged просто извикайте progressBar1.Visible = true;. Няма нужда от Invoke.
  3. По-добре добавете и progressBar1.Refresh();.
person ispiro    schedule 31.12.2012

Друга възможност, за която трябва да сте наясно, е, че лентата за напредъка работи във вашата нишка на потребителския интерфейс. За да може лентата за напредъка да се показва и да се преначертава, за да покаже нови количества напредък, нишката на потребителския интерфейс трябва да работи свободно, обработвайки съобщенията на Windows в главния цикъл на приложението.

Така че, ако стартирате вашата фонова работна нишка, но след това вашата UI нишка седи в натоварен цикъл на изчакване, чакайки да завърши, или се изключи и върши много друга работа, тогава тя няма да обработва съобщения на Windows и лентата ви за напредък ще бъде "неотговарящ". Трябва да освободите нишката на потребителския интерфейс, така че това актуализиране все още да се случва (т.е. да се върнете от вашия манипулатор на събития и да позволите на потребителския интерфейс да продължи да работи както обикновено).

Опасността от това е, че ако потребителският интерфейс е активен, тогава потребителят все още може да взаимодейства с него. Следователно трябва да напишете потребителския интерфейс, за да сте наясно, когато фоновият работник е активен, и да се справите правилно със ситуацията (проблемите могат да включват: Позволяване на потребителя да стартира фоновия работник отново, докато той вече работи, потребителският интерфейс се опитва да покаже информация, докато работникът нишката натоварено го актуализира, потребителят решава да зареди нов документ или да излезе, докато фоновият работник е зает и т.н.). Двете основни решения за това са да обвиете всяка част от потребителския интерфейс в защитен щит, който спира да се инициира нещо опасно, докато работи във фонов режим (това може да бъде много работа, ако имате много контроли за обвиване по този начин и лесно е да направите грешка, която да позволи на грешката да се промъкне) или да оставите потребителския интерфейс „незащитен“, но добавете IMessageFilter, който спира всички „опасни“ потребителски взаимодействия (щраквания и натискания на клавиши), като потиска техните входящи съобщения в Windows (WM_KEYPRESS и т.н.), докато фоновата обработка е активна.

person Jason Williams    schedule 31.12.2012