2015-01-09 1 views
1

Я просто пытаюсь изучить материал Windows Forms для справки, используя C++/CLI. Я создал проект в VS 2010 под названием LibraryScan, используя стандартные параметры Windows Forms, и я изменил форму с помощью нескольких элементов управления. Все изменения кода, которые я сделал, находятся в Form1.h (см. Ниже). Я добавил всю форму Form1.h, потому что я предполагаю, что если вы просто создали обычное приложение Windows Forms VS 2010 C++/CLI, вы можете заменить автогенерированный Form1.h на то, что ниже (хотя есть пример для просмотра; [0] - общий значок документа, [1] - закрытая папка, а [2] - открытая папка).Почему мое приложение C++/CLI не отвечает, даже когда я использую BackgroundWorker?

В основном вы используете кнопку «Обзор ...», чтобы выбрать папку, а затем нажмите кнопку «Сканировать», и цель состоит в том, чтобы она перезаписывала корневую папку и ее подпапки, чтобы найти все файлы в ней. Имя каждого файла добавляется в многострочный TextBox, а древовидная структура создается в TreeView.

Проблема у меня в том, что без линии:

System::Threading::Thread::Sleep(1); 

в listFolder() Функция интерфейса является своего рода отвечать на запросы при сканировании папки. TextBox обновляется нормально, но TreeView не отображается до тех пор, пока проверка не будет завершена, и во время сканирования вы не сможете изменить размер или переместить окно приложения. Это нормально с Sleep (1), хотя и немного медленным!

Как я уже сказал, я новичок в Windows Forms, но имею некоторый опыт работы с MFC (хотя большая часть моего опыта разработки программного обеспечения в течение более чем двадцати лет находится во встроенном виде, так что ...), в том числе с использованием аварийных насосов, чтобы попытаться обойти это что-то типа. Однако чтение, которое я сделал до сих пор, похоже, предполагает, что класс BackgroundWorker и RunWorkerAsync()/ReportProgress() и т. Д. - это путь в C++/CLI/Windows Forms, но большинство вопросов и примеров находятся на C# и все мои поиски для безответных guis с BackgroundWorker в конечном итоге с решениями, которые я не вижу, сильно отличаются от того, что я делаю!

Любая помощь с благодарностью оценили.

#pragma once 

ref class ProgressObject; 

namespace LibraryScan { 

    using namespace System; 
    using namespace System::ComponentModel; 
    using namespace System::Collections; 
    using namespace System::Windows::Forms; 
    using namespace System::Data; 
    using namespace System::Drawing; 
    using namespace System::IO; 

    /// <summary> 
    /// Summary for Form1 
    /// </summary> 
    public ref class Form1 : public System::Windows::Forms::Form 
    { 
    public: 
     Form1(void) 
     { 
      InitializeComponent(); 
      // 
      //TODO: Add the constructor code here 
      // 
     } 

    protected: 
     /// <summary> 
     /// Clean up any resources being used. 
     /// </summary> 
     ~Form1() 
     { 
      if (components) 
      { 
       delete components; 
      } 
     } 
    private: System::Windows::Forms::Label^ label1; 
    protected: 
    private: System::Windows::Forms::TextBox^ textBox1; 
    private: System::Windows::Forms::Button^ button1; 
    private: System::Windows::Forms::FolderBrowserDialog^ folderBrowserDialog1; 
    private: System::Windows::Forms::OpenFileDialog^ openFileDialog1; 
    private: System::Windows::Forms::TextBox^ textBoxFiles; 
    private: System::Windows::Forms::Label^ label2; 
    private: System::Windows::Forms::Button^ buttonScan; 
    private: System::Windows::Forms::TreeView^ treeViewFiles; 
    private: System::Windows::Forms::ImageList^ imageList1; 
    private: System::ComponentModel::BackgroundWorker^ fileLister; 
    private: System::ComponentModel::IContainer^ components; 



    private: 
     /// <summary> 
     /// Required designer variable. 
     /// </summary> 


#pragma region Windows Form Designer generated code 
     /// <summary> 
     /// Required method for Designer support - do not modify 
     /// the contents of this method with the code editor. 
     /// </summary> 
     void InitializeComponent(void) 
     { 
      this->components = (gcnew System::ComponentModel::Container()); 
      System::ComponentModel::ComponentResourceManager^ resources = (gcnew System::ComponentModel::ComponentResourceManager(Form1::typeid)); 
      this->label1 = (gcnew System::Windows::Forms::Label()); 
      this->textBox1 = (gcnew System::Windows::Forms::TextBox()); 
      this->button1 = (gcnew System::Windows::Forms::Button()); 
      this->folderBrowserDialog1 = (gcnew System::Windows::Forms::FolderBrowserDialog()); 
      this->openFileDialog1 = (gcnew System::Windows::Forms::OpenFileDialog()); 
      this->textBoxFiles = (gcnew System::Windows::Forms::TextBox()); 
      this->label2 = (gcnew System::Windows::Forms::Label()); 
      this->buttonScan = (gcnew System::Windows::Forms::Button()); 
      this->treeViewFiles = (gcnew System::Windows::Forms::TreeView()); 
      this->imageList1 = (gcnew System::Windows::Forms::ImageList(this->components)); 
      this->fileLister = (gcnew System::ComponentModel::BackgroundWorker()); 
      this->SuspendLayout(); 
      // 
      // label1 
      // 
      this->label1->AutoSize = true; 
      this->label1->Location = System::Drawing::Point(28, 13); 
      this->label1->Name = L"label1"; 
      this->label1->Size = System::Drawing::Size(39, 13); 
      this->label1->TabIndex = 0; 
      this->label1->Text = L"Folder:"; 
      // 
      // textBox1 
      // 
      this->textBox1->Location = System::Drawing::Point(74, 13); 
      this->textBox1->Name = L"textBox1"; 
      this->textBox1->Size = System::Drawing::Size(409, 20); 
      this->textBox1->TabIndex = 1; 
      // 
      // button1 
      // 
      this->button1->Location = System::Drawing::Point(489, 13); 
      this->button1->Name = L"button1"; 
      this->button1->Size = System::Drawing::Size(75, 23); 
      this->button1->TabIndex = 2; 
      this->button1->Text = L"Browse..."; 
      this->button1->UseVisualStyleBackColor = true; 
      this->button1->Click += gcnew System::EventHandler(this, &Form1::button1_Click); 
      // 
      // folderBrowserDialog1 
      // 
      this->folderBrowserDialog1->Description = L"Select the directory that you want to scan."; 
      this->folderBrowserDialog1->ShowNewFolderButton = false; 
      // 
      // openFileDialog1 
      // 
      this->openFileDialog1->FileName = L"openFileDialog1"; 
      // 
      // textBoxFiles 
      // 
      this->textBoxFiles->Anchor = static_cast<System::Windows::Forms::AnchorStyles>(((System::Windows::Forms::AnchorStyles::Top | System::Windows::Forms::AnchorStyles::Bottom) 
       | System::Windows::Forms::AnchorStyles::Right)); 
      this->textBoxFiles->Location = System::Drawing::Point(34, 62); 
      this->textBoxFiles->Multiline = true; 
      this->textBoxFiles->Name = L"textBoxFiles"; 
      this->textBoxFiles->ScrollBars = System::Windows::Forms::ScrollBars::Both; 
      this->textBoxFiles->Size = System::Drawing::Size(596, 419); 
      this->textBoxFiles->TabIndex = 3; 
      this->textBoxFiles->WordWrap = false; 
      // 
      // label2 
      // 
      this->label2->AutoSize = true; 
      this->label2->Location = System::Drawing::Point(31, 43); 
      this->label2->Name = L"label2"; 
      this->label2->Size = System::Drawing::Size(31, 13); 
      this->label2->TabIndex = 4; 
      this->label2->Text = L"Files:"; 
      // 
      // buttonScan 
      // 
      this->buttonScan->Enabled = false; 
      this->buttonScan->Location = System::Drawing::Point(570, 13); 
      this->buttonScan->Name = L"buttonScan"; 
      this->buttonScan->Size = System::Drawing::Size(75, 23); 
      this->buttonScan->TabIndex = 5; 
      this->buttonScan->TabStop = false; 
      this->buttonScan->Text = L"Scan"; 
      this->buttonScan->UseVisualStyleBackColor = true; 
      this->buttonScan->Click += gcnew System::EventHandler(this, &Form1::buttonScan_Click); 
      // 
      // treeViewFiles 
      // 
      this->treeViewFiles->Anchor = static_cast<System::Windows::Forms::AnchorStyles>(((System::Windows::Forms::AnchorStyles::Top | System::Windows::Forms::AnchorStyles::Bottom) 
       | System::Windows::Forms::AnchorStyles::Right)); 
      this->treeViewFiles->ImageIndex = 0; 
      this->treeViewFiles->ImageList = this->imageList1; 
      this->treeViewFiles->Location = System::Drawing::Point(636, 62); 
      this->treeViewFiles->Name = L"treeViewFiles"; 
      this->treeViewFiles->SelectedImageIndex = 0; 
      this->treeViewFiles->Size = System::Drawing::Size(392, 419); 
      this->treeViewFiles->TabIndex = 6; 
      this->treeViewFiles->AfterCollapse += gcnew System::Windows::Forms::TreeViewEventHandler(this, &Form1::treeViewFiles_AfterCollapse); 
      this->treeViewFiles->AfterExpand += gcnew System::Windows::Forms::TreeViewEventHandler(this, &Form1::treeViewFiles_AfterExpand); 
      // 
      // imageList1 
      // 
      this->imageList1->ImageStream = (cli::safe_cast<System::Windows::Forms::ImageListStreamer^ >(resources->GetObject(L"imageList1.ImageStream"))); 
      this->imageList1->TransparentColor = System::Drawing::Color::Transparent; 
      this->imageList1->Images->SetKeyName(0, L"Generic_Document.png"); 
      this->imageList1->Images->SetKeyName(1, L"Folder_16x16.png"); 
      this->imageList1->Images->SetKeyName(2, L"FolderOpen_16x16_72.png"); 
      // 
      // fileLister 
      // 
      this->fileLister->WorkerReportsProgress = true; 
      this->fileLister->DoWork += gcnew System::ComponentModel::DoWorkEventHandler(this, &Form1::fileLister_DoWork); 
      this->fileLister->ProgressChanged += gcnew System::ComponentModel::ProgressChangedEventHandler(this, &Form1::fileLister_ProgressChanged); 
      this->fileLister->RunWorkerCompleted += gcnew System::ComponentModel::RunWorkerCompletedEventHandler(this, &Form1::fileLister_RunWorkerCompleted); 
      // 
      // Form1 
      // 
      this->AutoScaleDimensions = System::Drawing::SizeF(6, 13); 
      this->AutoScaleMode = System::Windows::Forms::AutoScaleMode::Font; 
      this->ClientSize = System::Drawing::Size(1040, 493); 
      this->Controls->Add(this->treeViewFiles); 
      this->Controls->Add(this->buttonScan); 
      this->Controls->Add(this->label2); 
      this->Controls->Add(this->textBoxFiles); 
      this->Controls->Add(this->button1); 
      this->Controls->Add(this->textBox1); 
      this->Controls->Add(this->label1); 
      this->Name = L"Form1"; 
      this->Text = L"Form1"; 
      this->ResumeLayout(false); 
      this->PerformLayout(); 

     } 
#pragma endregion 
    private: System::Void button1_Click(System::Object^ sender, System::EventArgs^ e) 
      { 
       // Show the FolderBrowserDialog. 
       System::Windows::Forms::DialogResult result = folderBrowserDialog1->ShowDialog(); 
       if (result == System::Windows::Forms::DialogResult::OK) 
       { 
        String^ folderName = folderBrowserDialog1->SelectedPath; 
        textBox1->Text = folderName; 
        buttonScan->Enabled = true; 
       } 
      } 

    private: System::Void buttonScan_Click(System::Object^ sender, System::EventArgs^ e) 
      { 
       // Scan the folder listed in textBox1 and add the files to textBoxFiles 
       String^ folder = textBox1->Text; 
       fileLister->RunWorkerAsync(folder); 
      } 

    private: long listFolder(String^ folderName, TreeNode^ rootNode, BackgroundWorker^ worker) 
      { 
       // Scan the folder passed in and add the files to textBoxFiles 
       TreeNode^ newNode = gcnew TreeNode(folderName); 
       newNode->ImageIndex = 1; 
       newNode->SelectedImageIndex = 1; 
       worker->ReportProgress(0, gcnew ProgressObject(rootNode, newNode)); 

       array<String^>^ file = Directory::GetFiles(folderName); 
       Array::Sort(file); 
       for (int i = 0; i < file->Length; i++) 
       { 
        TreeNode^ fileNode = gcnew TreeNode(file[i]); 
        worker->ReportProgress(0, gcnew ProgressObject(newNode, fileNode)); 
        System::Threading::Thread::Sleep(1); 
       } 

       // Now scan the directories under this one 
       array<String^>^ dir = Directory::GetDirectories(folderName); 
       Array::Sort(dir); 
       for (int i = 0; i < dir->Length; i++) 
       { 
        listFolder(dir[i], newNode, worker); 
       } 

       return 0L; 
      } 

private: System::Void treeViewFiles_AfterCollapse(System::Object^ sender, System::Windows::Forms::TreeViewEventArgs^ e) 
     { 
      e->Node->ImageIndex = 1; 
      e->Node->SelectedImageIndex = 1; 
     } 

private: System::Void treeViewFiles_AfterExpand(System::Object^ sender, System::Windows::Forms::TreeViewEventArgs^ e) 
     { 
      e->Node->ImageIndex = 2; 
      e->Node->SelectedImageIndex = 2; 
     } 
private: System::Void fileLister_DoWork(System::Object^ sender, System::ComponentModel::DoWorkEventArgs^ e) 
     { 
      BackgroundWorker^ worker = dynamic_cast<BackgroundWorker^>(sender); 
      e->Result = listFolder(safe_cast<String^>(e->Argument), nullptr, worker); 
     } 
private: System::Void fileLister_ProgressChanged(System::Object^ sender, System::ComponentModel::ProgressChangedEventArgs^ e) 
     { 
      ProgressObject^ prog = safe_cast<ProgressObject^>(e->UserState); 
      if (prog->currentNode != nullptr) 
      { 
       if (prog->rootNode == nullptr) 
       { 
        treeViewFiles->Nodes->Add(prog->currentNode); 
       } 
       else 
       { 
        prog->rootNode->Nodes->Add(prog->currentNode); 
       } 
       textBoxFiles->AppendText(String::Concat(prog->currentNode->Text, "\n")); 
      } 
     } 

private: System::Void fileLister_RunWorkerCompleted(System::Object^ sender, System::ComponentModel::RunWorkerCompletedEventArgs^ e) 
     { 
      this->textBoxFiles->AppendText(L"Scan Complete"); 
     } 

    ref class ProgressObject : public Object 
    { 
    public: 
     TreeNode^ rootNode; 
     TreeNode^ currentNode; 

     ProgressObject(TreeNode^ theRootNode, TreeNode^ theCurrentNode) 
      : rootNode(theRootNode), 
       currentNode(theCurrentNode) 
     { 
     } 
    }; 
}; 
} 

ответ

2

Ваша программа страдает от 3-ей наиболее распространенной ошибки в потоке. Числа 1 и 2 - это раскачки и тупик, BackgroundWorker очень помогает вам избежать этих неприятных. Но это ничего не поможет, чтобы вы избежали номера 3, ошибка пожарного шланга. Ментальный образ здесь пытается выпить из работающего пожарного шланга, независимо от того, насколько сильно вы проглотите, вы никогда не сможете избежать проливания воды.

Вода - это TreeNodes, которые производит рабочий. Ваша файловая система работает быстро и может выплюнуть их с очень высокой скоростью. Тем более что второй раз, когда вы запускаете свою программу, и все данные файла поступают из кеша файловой системы. Десятки тысяч в секунду.

Рот - это поток пользовательского интерфейса в вашей программе, ему необходимо добавить эти узлы в TreeView и создать краску, чтобы сделать их видимыми. В лучшем случае он может добавлять сотни в секунду.

Нить пользовательского интерфейса запускает отправку запроса на вызов, как только ваш рабочий сначала вызывает ReportProgress(). Он выполняет запрос, вызывая обработчик события ProgressChanged. Проблема в том, что как только это будет сделано, есть еще один запрос на вызов, ожидающий. Он никогда не сможет догнать и освободить очередь вызовов. Он сжигает 100% ядро, ничего не делая, кроме вызова обработчика события ProgressChanged.

И прекращает выполнять свои обычные обязанности, отправляя уведомления о операционной системе. Что включает ввод, вы видите, что он больше не реагирует на щелчки мыши и ввод с клавиатуры. И живопись - задача с низким приоритетом, которая выполняется только тогда, когда ничего не нужно делать.

Это не Заблокировано или заморожено, вы увидите, что оно оживает, как только ваша рабочая нить завершается. Обычно через несколько секунд после завершения работы работнику все еще нужно работать с отставанием запросов на вызовы, которые находятся в очереди. И это работает, когда вы отказываетесь от пожарного шланга, замедляя рабочего с помощью Thread.Sleep(), чтобы вы могли быстро проглотить.

Вам нужно больше, чем один обходной путь, чтобы держать нити сбалансированными:

  • Не называйте ReportProgress() так часто, собирать узлы в List<TreeNode^>^ до тех пор, пока есть, скажем, сто из них. Помните о безопасности потоков, вам нужно создать новый список после вызова ReportProgress().
  • Используйте метод TreeView.Items.AddRange() в обработчике событий ProgressChanged, гораздо эффективнее, чем добавлять их по одному.
  • Избегайте TreeView, выполняющего слишком много работы, чтобы получить окрашенные узлы, вызывается метод BeginUpdate() при запуске рабочего объекта EndUpdate() в обработчике событий RunWorkerCompleted. Вы будете пропускать «живое» обновление таким образом, но это малоинтересно, у пользователя нет никаких шансов прочитать их так быстро.
  • Рассмотрите возможность обновления TreeView до тех пор, пока рабочий не будет выполнен, учитывая, что просмотр в реальном времени не очень интересен. Сделайте это в обработчике событий RunWorkerCompleted.
+0

Спасибо за это Ганс; так как я задал вопрос, на который я набросился, там было слишком много обновлений прогресса, поэтому я искал попытку уменьшить их. Мне показалось странным, что TextBox постоянно обновлялся без проблем, а TreeView не был! Я рассмотрю ваши полезные предложения и посмотрю, как это происходит. Это не помогает, что ПК, над которым я работал, не подключен к Интернету, а Intellisense не работает :-) – cosimo193

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