Как обновить индикатор выполнения на один шаг, каждый цикл цикла? С#

Создание .net-приложения на C#, формы Windows. Как обновить индикатор выполнения на 1 шаг в каждом цикле цикла из 100 циклов? (Я обрабатываю лист Excel в цикле.) Элементы управления индикатором выполнения находятся в классе пользовательского интерфейса, который подключается к классу контроллера, который подключается к пользовательскому классу (шаблон MVC). Цикл находится в пользовательском классе. Нужно ли мне отправлять экземпляр класса пользовательского интерфейса в каждом методе или есть лучший способ?

Прямо сейчас индикатор выполнения обновляется после завершения цикла. Application.doevents и .update или .refresh не работают.


person ReinForce    schedule 16.09.2010    source источник
comment
Что плохого в событии на нижних уровнях, рекламирующем прогресс?   -  person Joey    schedule 16.09.2010
comment
Извините, я не понимаю, что вы только что сказали. Индикатор выполнения определен в более высоком классе, если я обновлю значение в классе более низкого уровня, как об этом узнает высший класс? Мне пришлось бы либо передать экземпляр класса пользовательского интерфейса, либо экземпляр индикатора выполнения на более низкие уровни.   -  person ReinForce    schedule 16.09.2010
comment
Я не уверен, но если обновление задерживается, это почти похоже на проблему с потоками. Используете ли вы вызов (если необходимо)?   -  person Richard J Foster    schedule 16.09.2010


Ответы (3)


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

sealed class Looper
{
    public event EventHandler ProgressUpdated;

    private int _progress;
    public int Progress
    {
        get { return _progress; }
        private set
        {
            _progress = value;
            OnProgressUpdated();
        }
    }

    public void DoLoop()
    {
        _progress = 0;
        for (int i = 0; i < 100; ++i)
        {
            Thread.Sleep(100);
            Progress = i;
        }
    }

    private void OnProgressUpdated()
    {
        EventHandler handler = ProgressUpdated;
        if (handler != null)
        {
            handler(this, EventArgs.Empty);
        }
    }
}

Вы можете реализовать это, используя BackgroundWorker как часть вашего пользовательского интерфейса, где в событии backgroundWorker.DoWork вы вызываете looper.DoLoop(). Затем в вашем обработчике события looper.ProgressUpdated вы можете вызвать backgroundWorker.ReportProgress, чтобы увеличить индикатор выполнения из потока пользовательского интерфейса.

Обратите внимание, что, вероятно, было бы более разумно включить сам прогресс в информацию, переносимую вашим событием ProgressUpdated (мне просто не хотелось писать новый класс, производный от EventArgs, чтобы проиллюстрировать это; вы, вероятно, и так поняли картину).

Также обратите внимание, что приведенное выше действительно не имеет смысла, если вы не выполняете свой код с циклом в потоке, отдельном от потока пользовательского интерфейса. В противном случае вся ваша работа в любом случае будет выполнена до следующего обновления пользовательского интерфейса, поэтому ваш индикатор выполнения не будет давать никакого значения (он просто будет меняться от 0 до 100, когда цикл завершится).

Просто пример того, как такого рода вещи могут быть достигнуты.

person Dan Tao    schedule 16.09.2010
comment
Как это называется, когда у вас есть публичная структура, контролирующая приватную переменную? Это инкапсуляция? - person ReinForce; 16.09.2010
comment
@hi tech credo: Ты имеешь в виду Progress? Это просто свойство .NET, а не struct. Да, это важная часть инкапсуляции... похоже, в этом ответе есть много нового для вас. Дайте мне знать, если у вас возникнут проблемы или вы запутаетесь, и я сделаю все возможное, чтобы помочь. - person Dan Tao; 16.09.2010
comment
Итак, мой основной поток будет запускать пользовательский интерфейс, и я использую фоновый рабочий процесс для вызова моего метода контроллера, который, в свою очередь, вызывает метод в пользовательском классе с циклом. Нужно ли запускать цикл в классе пользовательского интерфейса для повторного вызова backgroundWorker.ReportProgress? или он автоматически обновляется? Спасибо за ваш терпеливый ответ, я никогда раньше не работал с потоками. - person ReinForce; 16.09.2010
comment
ваш класс петлителя ничего не должен знать о пользовательском интерфейсе. Ваш код пользовательского интерфейса должен добавить обработчик события OnProgressUpdated цикла. @ Дэн - спасибо за код, я был слишком ленив, я имею в виду, слишком занят, чтобы кодировать самому. :) - person Robaticus; 16.09.2010
comment
@hi tech credo: Нет, вам не нужен цикл в потоке пользовательского интерфейса. Идея состоит в том, чтобы обработать событие ProgressUpdated и отреагировать на него. Чтобы узнать больше об обработке событий, поищите хорошую статью на эту тему, например эту: msdn.microsoft.com/en-us/library/aa645739(VS.71).aspx. - person Dan Tao; 16.09.2010
comment
Использование BGW для вызова методов Excel не является хорошей идеей. Excel является многопоточным (STA), вызов которого из рабочего потока на самом деле делает его намного медленнее. - person Hans Passant; 16.09.2010
comment
@Hans: Плохо - я полностью упустил из виду, что ОП специально пытается что-то сделать в Excel (не то чтобы я все равно знал об этом). - person Dan Tao; 16.09.2010
comment
@Hans: Вау, это тяжело. Как максимизировать скорость без ущерба для интерактивности пользовательского интерфейса? - person ReinForce; 16.09.2010
comment
@hi tech credo: обратите внимание, что вам не нужно создавать экземпляр Excel и идти по маршруту VSTO только для обработки файла XLS. Вы можете извлечь пользу из такой библиотеки, как Koogra, которая позволяет вам манипулировать книгами и электронными таблицами в Файлы Excel напрямую, минуя сам Excel. Если вы используете этот подход, я считаю, что комментарий Ганса не будет применяться. - person Dan Tao; 16.09.2010
comment
Я использовал ace.olebd и PIA, чтобы прочитать все данные и скопировать их в набор данных. После манипуляции я просто записываю весь результат в новый файл xls. Написание занимает больше всего времени, так как я должен делать это клетка за клеткой. Обновление: у меня запущен фоновый процесс :) на обработчики событий! - person ReinForce; 16.09.2010
comment
Я поместил свой changeEventHandler в класс пользовательского интерфейса, а методы делегата и OnChanged — в класс контроллера. В классе пользовательского интерфейса измененный метод запускает метод отчета о ходе выполнения фонового рабочего процесса для обновления индикатора выполнения. Теперь метод onChanged был размещен на один уровень вглубь класса контроллера. Как разместить метод onChanged на 2 уровня? У меня есть один основной индикатор выполнения, который обновляется по мере завершения основных методов контроллера, и вспомогательный индикатор выполнения, который должен обновляться на основе классов, вызываемых контроллером. - person ReinForce; 17.09.2010

Обычно у меня есть один класс, который выполняет проверку вызовов пользовательского интерфейса. Пользовательский интерфейс -> «Класс обновления» -> другой класс.

Класс Updater имеет предопределенные методы и ссылки на элементы управления пользовательского интерфейса. Итак, Updater.StepProgressBar() — это то, что я вызываю, чтобы пошагово отображать индикатор выполнения пользовательского интерфейса. Я передаю ссылку на класс Updater любому классу, которому потребуется напрямую обновить пользовательский интерфейс.

Таким образом, все обновления пользовательского интерфейса из разных потоков обрабатываются одним классом. Не самый общий способ его реализации, но он никогда не дает сбоев.

Пример псевдокода:

class Updater()
{

  public ProgressBar pb;

  delegate void UpdateProgressBar();

  public StepProgressBar()
  {
     if(pb.InvokeRequired)
     {
          BeginInvoke(new UpdateProgressBar(this.StepProgressBar);
     }
     else
     {
          pb.Step();
      }
   }

}

Что-то такое.

person Ryan Bennett    schedule 16.09.2010
comment
Это не будет практично для меня, так как у меня есть несколько классов контроллеров, которые имеют доступ к более чем одному пользовательскому интерфейсу, и каждый контроллер имеет несколько библиотек под ним. - person ReinForce; 16.09.2010

Вы можете использовать делегата. Когда ваш фоновый процесс создает пользовательский класс, привяжите делегата, который вызывается из пользовательского класса, чтобы сообщить об обновлении. Затем вы можете отреагировать на этот вызов на уровне пользовательского интерфейса и оттуда обновить индикатор выполнения.

например (предупреждение, псевдокод):

MyCustomClass class = new MyCustomClass();
class.ProgressUpdated += (s,e)=>{ /* update UI */};
class.StartLoop();
person Robaticus    schedule 16.09.2010
comment
э-э, извините, никогда раньше не использовал делегатов, мне нужно сначала прочитать о них. - person ReinForce; 16.09.2010
comment
@Dan Tao дал отличный пример того, как это сделать. Я предлагаю вам немного почитать о событиях и делегатах, прежде чем вы попытаетесь это сделать. Фоновая обработка имеет свои собственные наборы ловушек, которые вам нужно знать, как избежать, прежде чем приступать к чему-то слишком сложному. - person Robaticus; 16.09.2010