2010-06-14 22 views
19

Tôi có một chuỗi nền tạo một chuỗi các đối tượng BitmapImage. Mỗi khi chuỗi nền kết thúc tạo bitmap, tôi muốn hiển thị bitmap này cho người dùng. Vấn đề là tìm ra cách để vượt qua BitmapImage từ chuỗi nền tới chuỗi giao diện người dùng.Làm thế nào để bạn vượt qua một BitmapImage từ một chuỗi nền đến chuỗi giao diện người dùng trong WPF?

Đây là một dự án MVVM, vì vậy quan điểm của tôi có một Image yếu tố:

<Image Source="{Binding GeneratedImage}" /> 

xem mô hình của tôi có một tài sản GeneratedImage:

private BitmapImage _generatedImage; 

public BitmapImage GeneratedImage 
{ 
    get { return _generatedImage; } 
    set 
    { 
     if (value == _generatedImage) return; 
     _generatedImage= value; 
     RaisePropertyChanged("GeneratedImage"); 
    } 
} 

xem mô hình của tôi cũng có mã mà tạo chủ đề nền:

public void InitiateGenerateImages(List<Coordinate> coordinates) 
{ 
    ThreadStart generatorThreadStarter = delegate { GenerateImages(coordinates); }; 
    var generatorThread = new Thread(generatorThreadStarter); 
    generatorThread.ApartmentState = ApartmentState.STA; 
    generatorThread.IsBackground = true; 
    generatorThread.Start(); 
} 

private void GenerateImages(List<Coordinate> coordinates) 
{ 
    foreach (var coordinate in coordinates) 
    { 
     var backgroundThreadImage = GenerateImage(coordinate); 
     // I'm stuck here...how do I pass this to the UI thread? 
    } 
} 

Tôi muốn bằng cách nào đó vượt qua backgroundThreadImage vào chuỗi giao diện người dùng, nơi nó sẽ trở thành uiThreadImage, sau đó đặt GeneratedImage = uiThreadImage để chế độ xem có thể cập nhật. Tôi đã xem xét một số ví dụ đối phó với WPF Dispatcher, nhưng tôi dường như không thể đưa ra một ví dụ giải quyết vấn đề này. Xin cho biết.

Trả lời

12

Sau đây sử dụng bộ điều phối để thực thi một đại biểu Hành động trên chuỗi giao diện người dùng. Điều này sử dụng một mô hình đồng bộ, thay thế Dispatcher.BeginInvoke sẽ thực hiện ủy nhiệm không đồng bộ.

var backgroundThreadImage = GenerateImage(coordinate); 

GeneratedImage.Dispatcher.Invoke(
     DispatcherPriority.Normal, 
     new Action(() => 
      { 
       GeneratedImage = backgroundThreadImage; 
      })); 

CẬP NHẬT Như đã thảo luận trong các ý kiến, trên một mình sẽ không làm việc như các BitmapImage không được tạo ra trên thread UI. Nếu bạn không có ý định sửa đổi hình ảnh một khi bạn đã tạo nó, bạn có thể đóng băng nó bằng cách sử dụng Freezable.Freeze và sau đó gán cho GeneratedImage trong ủy nhiệm của người điều phối (BitmapImage trở thành chỉ đọc và do đó luồng an toàn là kết quả của Freeze). Tùy chọn khác sẽ là tải hình ảnh vào một MemoryStream trên luồng nền và sau đó tạo BitmapImage trên chuỗi giao diện người dùng trong bộ điều phối ủy nhiệm với luồng đó và thuộc tính StreamSource của BitmapImage.

+0

này là hữu ích, Simon, cảm ơn. Nhưng làm thế nào để có được xung quanh thực tế là, khi quá trình này bắt đầu, 'GeneratedImage' không được đặt thành một thể hiện của một đối tượng? – devuxer

+1

@ DANM tất nhiên tôi đã không nghĩ về điều đó :) Bạn cũng có thể nắm giữ một Dispatcher thông qua 'Application.Current.Dispatcher' –

+0

Tôi sợ điều này không làm điều đó. Tôi nhận được "Các chủ đề gọi không thể truy cập đối tượng này bởi vì một chủ đề khác nhau sở hữu nó." – devuxer

6

Bạn cần phải làm hai việc:

  1. Freeze BitmapImage của bạn để nó có thể được chuyển đến thread UI, sau đó
  2. Sử dụng Dispatcher để chuyển đổi sang thread UI để thiết lập GeneratedImage

Bạn sẽ cần truy cập Bộ điều phối của chuỗi giao diện người dùng từ chuỗi trình tạo. Cách linh hoạt nhất để làm điều này là để nắm bắt được các Dispatcher.CurrentDispatcher giá trị trong các chủ đề chính và đi qua nó vào thread máy phát điện:

public void InitiateGenerateImages(List<Coordinate> coordinates) 
{ 
    var dispatcher = Dispatcher.CurrentDispatcher; 

    var generatorThreadStarter = new ThreadStart(() => 
    GenerateImages(coordinates, dispatcher)); 

    ... 

Nếu bạn biết bạn sẽ chỉ sử dụng này trong một ứng dụng đang chạy và rằng ứng dụng sẽ chỉ có một chuỗi giao diện người dùng, bạn chỉ có thể gọi Application.Current.Dispatcher để nhận điều phối hiện tại. Nhược điểm là:

  1. Bạn mất khả năng sử dụng mô hình khung độc lập với đối tượng Ứng dụng được xây dựng.
  2. Bạn chỉ có thể có một chuỗi giao diện người dùng trong ứng dụng của mình.

Trong thread máy phát điện, thêm một cuộc gọi đến Freeze sau khi hình ảnh được tạo ra, sau đó sử dụng Dispatcher để chuyển đổi sang thread UI để thiết lập hình ảnh:

var backgroundThreadImage = GenerateImage(coordinate); 

backgroundThreadImage.Freeze(); 

dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => 
{ 
    GeneratedImage = backgroundThreadImage; 
})); 

Làm rõ

Trong đoạn mã trên, điều quan trọng là Dispatcher.CurrentDispatcher được truy cập từ chuỗi giao diện người dùng chứ không phải từ luồng máy phát. Mỗi luồng có Trình điều phối riêng. Nếu bạn gọi Dispatcher.CurrentDispatcher từ thread máy phát, bạn sẽ nhận được Dispatcher của nó thay vì cái mà bạn muốn.

Nói cách khác, bạn phải làm điều này:

var dispatcher = Dispatcher.CurrentDispatcher; 

    var generatorThreadStarter = new ThreadStart(() => 
    GenerateImages(coordinates, dispatcher)); 

và không này:

var generatorThreadStarter = new ThreadStart(() => 
    GenerateImages(coordinates, Dispatcher.CurrentDispatcher)); 
+0

+1. Cảm ơn, Ray. Tôi thích mẹo của bạn để vượt qua trong điều phối viên. Khái niệm Freeze() cũng rất quan trọng, điều mà tôi biết tôi sẽ cần trong tương lai. (Như tôi đã nhận xét với Simon, việc giải phóng chủ đề UI không thực sự là mục tiêu của tôi trong trường hợp này - tôi chỉ cố gắng cập nhật (render) từng hình ảnh thay vì đợi cho đến khi tất cả hình ảnh được xử lý để cập nhật.) – devuxer

+0

Ray, tôi vừa thử ý tưởng của bạn về việc chuyển giao trong điều phối viên, nhưng vì một lý do nào đó, 'Dispatcher.CurrentDispatcher' không tương đương với' App.Current.Dispatcher'. Nếu tôi sử dụng 'App.Current.Dispatcher', mã của tôi hoạt động hoàn hảo, nhưng nếu tôi chuyển vào' Dispatcher.CurrentDispatcher', tôi nhận được "Chuỗi cuộc gọi không thể truy cập đối tượng này bởi vì một chuỗi khác sở hữu nó." Bất kỳ ý tưởng tại sao điều này có thể? – devuxer

+0

Có, bạn đang gọi Dispatcher.CurrentDispatcher từ luồng máy phát không phải là chuỗi giao diện người dùng. Tôi đã thêm một giải thích cho câu trả lời để giải thích cách khắc phục. –

0

Trong bối cảnh chủ đề làm việc với Streams.

Ví dụ, trong thread nền:

var artUri = new Uri("MyProject;component/../Images/artwork.placeholder.png", UriKind.Relative); 

StreamResourceInfo albumArtPlaceholder = Application.GetResourceStream(artUri); 

var _defaultArtPlaceholderStream = albumArtPlaceholder.Stream; 

SendStreamToDispatcher(_defaultArtPlaceholderStream); 

Trong thread UI:

void SendStreamToDispatcher(Stream imgStream) 
{ 
    dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => 
    { 
     var imageToDisplay = new BitmapImage(); 
     imageToDisplay.SetSource(imgStream); 

     //Use you bitmap image obtained from a background thread as you wish! 
    })); 
} 
Các vấn đề liên quan