2010-08-18 1 views
3

전 WPF에 완전히 익숙하지 않습니다.이 프로세스는 스레드를 사용하려고 시도하는 동안 TreeView에 전체 드라이브 구조 (폴더, 파일)를 나열하는 간단한 WPF 응용 프로그램을 만들었습니다. GetFolderTree() 메서드를 실행하고 UI가 응답하지 않게되는 것을 방지하기 위해 몇 가지 문제가 있습니다. FolderBrowser라는 클래스를 만들었습니다.이 클래스는 모든 클래스 구조에서 TreeViewItem의 새 인스턴스를 만듭니다.간단한 WPF 응용 프로그램에서 멀티 스레딩을 사용하는 경우의 문제

using System.IO; 
using System.Windows.Controls; 

namespace WpfApplication 
{ 
    public class FolderBrowser 
    { 
    private TreeViewItem folderTree; 
    private string rootFolder; 

    public FolderBrowser(string path) 
    { 
     rootFolder = path; 
     folderTree = new TreeViewItem(); 
    } 

    private void GetFolders(DirectoryInfo di, TreeViewItem tvi) 
    { 
     foreach (DirectoryInfo dir in di.GetDirectories()) 
     { 
      TreeViewItem tviDir = new TreeViewItem() { Header = dir.Name };   

      try 
      { 
       if (dir.GetDirectories().Length > 0) 
        GetFolders(dir, tviDir); 

       tvi.Items.Add(tviDir); 
       GetFiles(dir, tviDir); 
      } 
      //catch code here 
     } 

     if (rootFolder == di.FullName) 
     { 
      folderTree.Header = di.Name; 
      GetFiles(di, folderTree); 
     } 
    } 

    private void GetFiles(DirectoryInfo di, TreeViewItem tvi) 
    { 
     foreach (FileInfo file in di.GetFiles()) 
     { 
      tvi.Items.Add(file.Name); 
     } 
    } 

    public TreeViewItem GetFolderTree() 
    { 
     DirectoryInfo di = new DirectoryInfo(rootFolder); 
     if (di.Exists) 
     {     
      GetFolders(di, folderTree);         
     } 

     return folderTree; 
    } 
    } 
} 

가 어떻게이 새 스레드 내부의 새로운 컨트롤 인스턴스를 만들 수 있습니다 :이 코드입니다, 트 리뷰를 채우는 반환 값으로 사용되는 말에 드라이브 구조를 보유하고? 사전에

감사

+0

은 ... 몇 년 새 응답으로 게시 할 예정입니다 min –

답변

1

당신은 어떤 스레드에서 UI하지만 UI 스레드와 상호 작용할 수 없습니다,하지만 당신은 UI 스레드 내에서 콜백을 실행하기 위해 UI 디스패처 객체를 사용할 수 있습니다

System.Windows.Application.Current.Dispatcher.Invoke(new System.Action(() => { /* your UI code here */ })); 

Dispatcher를 얻는 데 "깨끗한"방법은 UI 객체에서 스레드를 생성 할 때 해당 스레드를 생성하는 스레드/클래스로 전달하는 것입니다.

편집 :

난 내 이상 HCL의 솔루션을 추천합니다. 그러나 코드에서이 큰 불쾌한 코드 블록을 어디서나 복사하지 않고도이 작업을 수행하는 방법에 대한 의견을 물었습니다.

생성자에서 Dispatcher 개체에 대한 참조를 클래스에 저장합니다. 이 방법

RunOnUIThread(() => { /* UI code */ }); 

당신은 코드의 큰 블록을 포장 할 수 있습니다 :

RunOnUIThread(() => 
{ 
    Console.WriteLine("One statement"); 
    Console.WriteLine("Another statement"); 
}); 

당신이 만약

는 다음과 같은 방법합니다

private void RunOnUIThread(Action action) 
{ 
    this.dispatcher.Invoke(action); 
} 

을 그리고 다음과 같이 호출 이 코드를 UI로 너무 많이 밀어 넣으려고 시도해도 UI에서 모든 코드를 실행 한 경우와 다를 것은 없습니다. ead, 그리고 여전히 UI가 걸릴 것입니다.

그러나, 사용자 정의 트리 구조를 채우는 대신 그 코드를 갖는 HCL의 제안은 UI 컨트롤에 대해 아무것도 알고, 훨씬 더 :)

+0

내가 아는 한, 그는 UI-Thread에서 각 TreeViewItem을 만들어야합니다. 따라서이 솔루션은 어려울 수도 있지만 가능합니다. 그러나 내가 생각하는 것처럼 나는 확실하지 않다. – HCL

+0

GetFolders 메서드에서 코드를 사용했습니다. Application.Current.Dispatcher.Invoke (새 System.Action() => {tviDir = 새 TreeViewItem() {헤더 = dir.Name};}))); 그리고 그것은 작동하지만 그 tviDir가 수정 된 다른 장소는 어떨까요? 각 행/코드 블록에 대해 동일한 Application.Current.Dispatcher.Invoke (...)를 사용해야한다면 모든 메소드를 수용 할 수있는 구문이 있습니다. 대신에? – Albert

+0

오, 알았어, 내가 직접 UI 컨트롤과 그것을하는 방법이 있었는지 알고 싶었지만, 나는 이것을 더 잘 피하는 것 같다. 감사합니다 – Albert

2

Merkyn 모건 - 그레이엄의 솔루션 (일 내 댓글이 표시되지 않는 경우입니다 , 나는 잘 모르겠다.), 나는 당신의 디렉토리 객체를 포함하는 독립적 인 객체 구조를 만들 것을 제안한다.

BackgroundWorker으로이 작업을 수행하십시오. 이 작업이 완료되면이 구조를 사용하여 TreeViewItem-nodes를 직접 구축하십시오 (몇 백 개가 있으면 느리지 않기 때문에). 또는 ViewModel (더 좋음)으로 사용하십시오.

BackgroundWorker bgWorker = new BackgroundWorker(); 
bgWorker.DoWork += (s, e) => { 
    // Create here your hierarchy 
    // return it via e.Result     
}; 
bgWorker.RunWorkerCompleted += (s, e) => { 
    // Create here your TreeViewItems with the hierarchy from e.Result     
}; 
bgWorker.RunWorkerAsync(); 
+0

+1,보기 및 구현을 서로 독립적으로하고 데이터 바인딩을 사용하여 함께 묶습니다 (MVVM). –

+0

코드 샘플을 시도했지만 여전히 많은 UI 구성 요소에이 기능이 필요하기 때문에 호출하는 스레드가 STA 여야합니다. " TreeViewItem의 새 인스턴스를 만들려고 할 때 – Albert

+0

@Albert : DoWork 이벤트에서 TreeViewItems를 만들려고합니다. 내가 이미 썼 듯이, 이것은 가능한 한 내가 아는 한도 내다. TreeViewItems가있는 것처럼 시각적 개체 (더 정확하게는 DependencyObjects)가 아닌 구조를 만든 다음 RunWorkerComplete에서이 개체를 원본으로 사용하여 VM으로 만듭니다. – HCL

0

수동으로 트리를 작성하는 대신 계층 적 템플릿을 살펴 보시기 바랍니다. 백그라운드 스레드에서 전체 구조를 빌드 한 다음 결과 데이터 구조를 트리에 바인딩 할 수 있습니다.

0

다음은 MVVM (물론 뷰/뷰 모델 부분 이상) 및 백그라운드 작업자 스레드를 사용한 대답입니다. 이렇게하면 백그라운드 작업자가 뷰 모델을 (재귀 적으로) 채우고 계층 적 데이터 템플릿을 사용하여 뷰를 뷰 모델에 바인딩합니다.

작업자 스레드가 ObservableCollection을 변경할 수 없기 때문에 여전히 동일한 스레드 문제가 있음을 유의하십시오. 따라서 UI 스레드에서 실행되는 RunWorkerCompleted 이벤트 처리기를 사용하여 컬렉션을 채 웁니다.

MainWindow.xaml :

<Window 
    x:Class="WpfApplication.MainWindow" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:local="clr-namespace:WpfApplication"> 
    <StackPanel> 
     <TextBlock Text="Contents:" /> 
     <TreeView ItemsSource="{Binding BaseDirectory.Contents}"> 
      <TreeView.Resources> 
       <HierarchicalDataTemplate 
         DataType="{x:Type local:FileSystemEntry}" 
         ItemsSource="{Binding Contents}"> 
        <TextBlock Text="{Binding Name}" /> 
       </HierarchicalDataTemplate> 
      </TreeView.Resources> 
     </TreeView> 
    </StackPanel> 
</Window> 

MainWindowViewModel.cs : XAML/MVVM을 사용하여 작은 샘플에서 작업

using System; 
using System.Collections.Generic; 
using System.Collections.ObjectModel; 
using System.ComponentModel; 
using System.IO; 

namespace WpfApplication 
{ 
    public class MainWindowViewModel 
    { 
     public MainWindowViewModel() 
     { 
      this.BaseDirectory = new FileSystemEntry("C:\\"); 
      this.BaseDirectory.Populate(); 
     } 

     public FileSystemEntry BaseDirectory { get; private set; } 
    } 

    public class FileSystemEntry 
    { 
     public FileSystemEntry(string path) 
      : this(new DirectoryInfo(path)) 
     { 
     } 

     private FileSystemEntry(DirectoryInfo di) 
      : this() 
     { 
      this.Name = di.Name; 
      this.directoryInfo = di; 
     } 

     private FileSystemEntry(FileInfo fi) 
      : this() 
     { 
      this.Name = fi.Name; 
      this.directoryInfo = null; 
     } 

     private FileSystemEntry() 
     { 
      this.contents = new ObservableCollection<FileSystemEntry>(); 
      this.Contents = new ReadOnlyObservableCollection<FileSystemEntry>(this.contents); 
     } 

     public string Name { get; private set; } 

     public ReadOnlyObservableCollection<FileSystemEntry> Contents { get; private set; } 

     public void Populate() 
     { 
      var bw = new BackgroundWorker(); 

      bw.DoWork += (s, e) => 
      { 
       var result = new List<FileSystemEntry>(); 

       if (directoryInfo != null && directoryInfo.Exists) 
       { 
        try 
        { 
         foreach (FileInfo file in directoryInfo.GetFiles()) 
          result.Add(new FileSystemEntry(file)); 

         foreach (DirectoryInfo subDirectory in 
          directoryInfo.GetDirectories()) 
         { 
          result.Add(new FileSystemEntry(subDirectory)); 
         } 
        } 
        catch (UnauthorizedAccessException) 
        { 
         // Skip 
        } 
       } 

       System.Threading.Thread.Sleep(2000); // Todo: Just for demo purposes 

       e.Result = result; 
      }; 

      bw.RunWorkerCompleted += (s, e) => 
      { 
       var newContents = (IEnumerable<FileSystemEntry>)e.Result; 

       contents.Clear(); 
       foreach (FileSystemEntry item in newContents) 
        contents.Add(item); 

       foreach (FileSystemEntry subItem in newContents) 
        subItem.Populate(); 
      }; 

      bw.RunWorkerAsync(); 
     } 

     private ObservableCollection<FileSystemEntry> contents; 
     private DirectoryInfo directoryInfo; 
    } 
}