0
votes

I have a listview that contains file names. I have another listview that contains possible actions to rename these files. Finally I have a label that displays a preview of the result. When an object is selected in each of the lists I want to display the preview. You can select only one file but one or more actions. I use WPF/Xaml for my UI. I chose to perform my preview with a thread.

Here is a part of my code :

    private Thread _thread;

    public MainWindow()
    {
        InitializeComponent();
        _thread = new Thread(DoWork);
    }

    public void DoWork()
    {
        while (true)
        {
            FileData fileData = listViewFiles.SelectedItem as FileData; // ERROR HERE
            if (fileData != null)
            {
                string name = fileData.FileName;
                foreach (var action in _actionCollection)
                {
                    name = action.Rename(name);
                }
                previewLabel.Content = name;
            }
            Thread.Sleep(1000);
        }
    }

    private void listViewFiles_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        _thread.Start();
    }

At run time I get the error "The calling thread cannot access this object because a different thread owns it." on the FileData fileData = listViewFiles.SelectedItem as FileData; line. Do you know what should I do ?

2

2 Answers

5
votes

You can't modify or access UI from nonUI thread. So if you still want to use different thread first thing you need to do is to add some kind of model (for more info about binding and model try search for "wpf mvvm"), then bind you listViewFiles.SelectedItem to some property of this model this will allow you to access SelectedValue across threads. Second you need to separate all logic that changes UI to method or use lambda so in the end it can look like this:

public void DoWork() 
{ 
    while (true) 
    { 
        FileData fileData = Model.SelectedValue;
        if (fileData != null) 
        { 
            string name = fileData.FileName; 
            foreach (var action in _actionCollection) 
            { 
                name = action.Rename(name); 
            } 
            this.Dispatcher.Invoke((Action)()=>  //use Window.Dispatcher
            {
              label3.Content = fileData.FileName; 
              label4.Content = name;
            }); 
        } 
        Thread.Sleep(1000); 
    } 
} 

UPD. Some additional words about synchronizing with UI: in WPF every UI object inherits from DispatcherObject class. Thus all access to object of this type can be made only from thread in which this object was created, if you want to access DispatcherObject(DO) from another thread you need to use DO.Dispatcher.Invoke(Delegate) method, this will queue your code to DO thread. So in conclusion to run code in UI thread you need to use Dipatcher of any UI element in this case we use Dispatcher of Window (assume that code in window code behind).

-1
votes

Simple answer is that you can't do that: thread A cannot directly access winforms objects (controls) that thread B created.

In practice, you can use a delegate to run this safely on the other thread ala:

form.Invoke(new MethodInvoker(() => {
       FileData fileData = listViewFiles.SelectedItem as FileData; // ERROR HERE
        if (fileData != null)
        {
            string name = fileData.FileName;
            foreach (var action in _actionCollection)
            {
                name = action.Rename(name);
            }
            previewLabel.Content = name;
        }
   }));

However, you may want to just use a Background worker: http://msdn.microsoft.com/en-us/library/8xs8549b.aspx

In more detail at: http://weblogs.asp.net/justin_rogers/pages/126345.aspx