2013-12-16 2 views
2

Случай использования
Я разрабатываю небольшое приложение на C#, которое вызывается другим приложением для извлечения данных из Интернета. Он работает как процесс сам по себе, но почти все взаимодействие с ним управляется вызывающим приложением. Поэтому он не имеет графического интерфейса. Однако я хотел бы добавить индикатор выполнения, используя WPF, который отображается во время определенных данных, которые могут занять до минуты. Достаточно легко оценить, сколько работы сделано и сколько осталось, и поэтому я считаю, что индикатор прогресса подходит.Вызов методов в окне WPF из основной темы

Исследования, проведенные
У меня есть справедливое понимание многопоточности после прочтения больших частей PDF Albahari на резьб (http://www.albahari.info/threading/threading.pdf). Я также прочитал много сообщений о SO и MSDN в этом вопросе. В большинстве сообщений предлагается использовать фонового работника для долговременного извлечения данных, сохраняя графический интерфейс в основном потоке и предлагая решения с использованием рабочего фона. Это неудобно в этом сценарии, хотя главная задача - поиск данных, а не взаимодействие с графическим интерфейсом.

Я потратил кучу часов, пытаясь понять разные учебники и форумы, пытаясь приспособить их к моей проблеме, но мне это не удалось, и теперь я почти полностью вернусь к квадрату. В основном я хотел бы, чтобы в конечном итоге со следующими двумя классами, описанных ниже:

ProgressBarWindow

public partial class ProgressBarWindow : Window 
{ 
    public ProgressBarWindow() 
    { 
     InitializeComponent(); 
    } 

    public void setValue(int value) 
    { 
     // This function should be available from the main thread 
    } 
} 

Querier

Public class Querier 
{ 



    public List<Item> getItems() 
    { 
     // call ProgressBarWindow.setValue(0); 
     ... 
     // call ProgressBarWindow.setValue(100); 
     // call ProgressBarWindow.Close(); 
    } 
} 

Это мое понимание того, что интерфейс должен работать под единым резьб и поэтому мой объект ProgressBarWindow не может быть создан в новом потоке и в то же время доступен для основного потока (типа).

Dispatcher.BeginInvoke кажется моим спасителем здесь, но до сих пор мне не удалось выяснить, что должно пойти в класс Querier и что нужно делать в классе ProgressBarWindow. Как я могу заставить эти два потока взаимодействовать с одним и тем же экземпляром ProgressBarWindow?

Пожалуйста, спросите, нужна ли вам дополнительная информация, и я попробую уточнить.

+0

Пожалуйста избежать 'SetXXX()' и 'getXXX()' синтаксис. он выглядит так много, как я, я собираюсь бросить. Вместо этого используйте C# -подобный код и создайте правильные свойства. –

+0

Point принят! Я обычно стараюсь. – Anders

ответ

1

Вы можете использовать класс Progress для обновления пользовательского интерфейса с текущим ходом работы в течение длительного времени.

Сначала нужно создать экземпляр Progress в вашем пользовательском интерфейсе:

Progress<int> progress = new Progress<int>(currentProgress => 
{ 
    progressBar.Value = currentProgress; 
    //todo do other stuff 
}); 

Затем передать его к длинному запущенному процессу:

public List<Item> getItems(IProgress<int> progress) 
{ 
    progress.Report(0); 
    //todo do something 
    progress.Report(100); 
} 
+0

Я еще не пробовал, но я стараюсь кратко разобраться в моей голове. Я не вижу, где будет создан новый поток. Будет ли это реализовано в «продолжительном процессе»? – Anders

+0

@ Anders У меня создалось впечатление, что у вас не было проблемы с запуском какого-то кода в фоновом потоке, а скорее с возвратом к потоку пользовательского интерфейса. Вы можете просто использовать 'Task.Run (() => whatever.GetItems (progress))' для запуска 'getItems' в потоке, отличном от UI, хотя есть много других методов, которые будут работать так же хорошо. Это главное преимущество использования «Прогресс», все равно, как вы заканчиваете запуск кода в другом потоке. – Servy

+0

Хотелось бы наоборот. Строка «Прогресс» должна работать в фоновом потоке, который оставляет мне что-то вроде: «Task.Run (() => new ProgressBarDialog(). ShowDialog()». Но, я думаю, можно было бы делегировать делегатов внутри анонимной функции как хорошо, чтобы дать возможность передавать изменения статуса через них? – Anders

0

Вот обобщенную функцию, которую я обычно используют:

public static void Invoke(this UIElement element,Action action) 
    { 
     element.Dispatcher.Invoke(action, null); 
    } 

И использовать его, просто звоните:

this.Invoke(() => ProgressBarWindow.SetValue(0)); 

Таким образом, в GetItems() функцию, вы бы что-то вместе линии:

public List<Item> getItems() 
{ 
    ProgressBarWindow wnd; 
    MainWindow.Invoke(() => wnd = new ProgressBarWindow()) 
    MainWindow.Invoke(() => wnd.SetValue(0)) 
    ... 
    MainWindow.Invoke(() => wnd.SetValue(100)) 
    MainWindow.Invoke(() => wnd.Close()) 
} 

Удостоверьтесь, что у вас всегда есть способ добраться до главного окна - это что угодно (один r unning из App.xml или App.Run (...). После этого вы можете выполнить какие-либо действия с графическим интерфейсом (даже если вам нужно создать новое окно Loader, если оно выполнено в основном потоке)

+0

Спасибо за ваш ответ. Однако я не уверен, что делать с первым формальным параметром метода Invoke. В вашем примере не указано, что отправить в качестве фактического параметра, и Visual Studio жалуется на количество аргументов. – Anders

0

Приложение.XAML

public partial class App : Application 
{ 
    private void Application_Startup_1(object sender, StartupEventArgs e) 
    { 
     Task.Factory.StartNew<List<int>>(() => Querier.GetItems()); 
    } 
} 

ProgressBarWindow.xaml.cs

public partial class ProgressWindow : Window 
{ 
    public ProgressWindow() 
    { 
     InitializeComponent(); 
     Querier.Start +=()=> Visibility = Visibility.Visible; 
     Querier.Stop +=() => Visibility = Visibility.Collapsed; 
     Querier.ReportProgress +=OnReportProgress; 
    } 

    public void OnReportProgress(int value) 
    { 
     txtBox.Text = value.ToString(); 
    } 
} 

ProgressBarWindow.xaml

<Grid> 
    <TextBox x:Name="txtBox"></TextBox> 
</Grid> 

Querier

public class Querier 
{ 
    public static event Action Start; 

    public static event Action Stop; 

    public static event Action<int> ReportProgress; 

    public static List<int> GetItems() 
    { 
     if (Start != null) 
      App.Current.Dispatcher.BeginInvoke(Start,null); 

     for (int index = 0; index <= 10; index++) 
     { 
      Thread.Sleep(200); 
      if (ReportProgress != null) 
       App.Current.Dispatcher.BeginInvoke(ReportProgress, index*10); 
     } 


     if (Stop != null) 
      App.Current.Dispatcher.BeginInvoke(Stop, null); 

     return Enumerable.Range(1, 100).ToList(); 

    } 

} 

Я просто пытаюсь дать идею надеюсь, что это поможет.

+0

Мой проект на самом деле не проект WPF, поэтому мне не хватает app.xaml. Я только что включил окно WPF. Из-за этого у меня есть метод Main как точка входа. Кажется, это оставляет меня без возможности называть «App.Current ....» или равно. – Anders

+0

Итак, где атрибут [STAThread]? на вашем MainMethod ... – ethicallogics

+0

Да, это правильно. В настоящее время у меня есть класс под названием «ThreadTest» с основным, а также объявленный атрибут [STAThread]. Visual Studio не распознает 'Current' на' ThreadTest' – Anders

Смежные вопросы