0

I have a FileSystemWatcher that checks a folder for changes, of course. So when you add a file to the folder I need to add a button to a wrappanel. I have tried:

    public void CheckDir()
{
    string[] args = System.Environment.GetCommandLineArgs();
    var folderName = $"{AppDomain.CurrentDomain.BaseDirectory}games";

    FileSystemWatcher watcher = new FileSystemWatcher
    {
        Path = folderName,

        NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite
       | NotifyFilters.FileName | NotifyFilters.DirectoryName
    };

    // Add event handlers.
    watcher.Changed += new FileSystemEventHandler(OnChanged);
    watcher.Created += new FileSystemEventHandler(OnChanged);
    watcher.Deleted += new FileSystemEventHandler(OnChanged);
    watcher.Renamed += new RenamedEventHandler(OnRenamed);

    // Begin watching.
    watcher.EnableRaisingEvents = true;

}

//When button is changed, created, or deleted
private void OnChanged(object source, FileSystemEventArgs e)
{
    Console.WriteLine("File: " + e.Name + " " + e.ChangeType);

    var buttonName = "Button_" + Path.GetFileNameWithoutExtension(e.FullPath).Replace(" ", "");

    if (e.ChangeType == WatcherChangeTypes.Created)
    {
        var buttonContent = Path.GetFileNameWithoutExtension(e.FullPath);

        CreateButton(buttonContent, buttonName);
    }
    else if (e.ChangeType == WatcherChangeTypes.Deleted)
    {

        buttonHolder.Children.Remove(btnFile);
    }
}

And the CreateButton void:

private void CreateButton(string buttonContent, string buttonName)
{
    Button newBtn = new Button
    {
        Content = buttonContent,
        Name = buttonName,
        BorderThickness = new Thickness(1),
        Width = 260,
        Height = 100
    };

    newBtn.Click += OpenFile;

    buttonHolder.Children.Add(newBtn);
}

But I get an error when trying to add the button:

The calling thread must be STA, because many UI components require this.

And I have no idea what to change or what the error means, and yes, I've searched for it, but could not find a solution or good explanation to what it is.

William Jelgren
  • 47
  • 1
  • 1
  • 9
  • Does [this](https://stackoverflow.com/q/2329978/9145461) help? – alxnull Sep 16 '18 at 12:52
  • Oh, thank you! I didn't understand it, but that link helped me. Thanks again! – William Jelgren Sep 16 '18 at 13:14
  • I guess someone else could explain the backgrounds far better than I could. – alxnull Sep 16 '18 at 13:18
  • Quanik helped me out with this [link](https://stackoverflow.com/questions/2329978/the-calling-thread-must-be-sta-because-many-ui-components-require-this) – William Jelgren Sep 16 '18 at 13:19
  • Just for fun, try this: place a breakpoint on the line `var folderName = $"[snip]"` and another on a line inside of the event handler `OnChanged` - when the breakpoints are hit are you inside the same thread? You won't be - so make sure you marshal your update back on to the UI thread (which is likely to be the thread your first breakpoint was set on). Better still, think about using a ListControl bound to the same observable list of filenames, and the item template in the ListControl will be a Button. – slugster Sep 17 '18 at 09:50

1 Answers1

1

Let me try to explain this exception to you.

First, I try to explain what a background thread and a UI thread is and their difference, and then my 2 simple rules to follow when dealing with this exception.

My explanation on STA and MTA, UI thread and background threads

STA stands for Single Thread Apartment, and it has a counterpart called Multi Thread Apartment (MTA). And their differences are discussed in this answer, and you can use STA vs. MTA as keywords for your Google searching if you are interested. You might be confused when you see terms like "STAThreadAttribute"/ "COM" / "CoInitializeEx" in your search, don't worry, because you are not alone, they are some crazy old technologies that few people need to work with.

And a thread uses one of these models, STA or MTA. You might have noticed that some code like

[STAThread]
static void Main()

This means that the thread is using STA. In WPF, the main thread is using STA, the controls are created on this thread, and events handlers like Button_Click are also executed on this thread, let's called this the UI thread.

And the background threads you started by using classes like Thread/ThreadPool/Task are using MTA by default, this is also the case for event handler of System.Timers.Timer.Elapsed, and, as in this case, the event handler of FileSystemWatcher events.

By now you have the idea that your code either runs on UI thread (STA) or one of the background threads (MTA).

My Simple Rules

  1. Normally, you need to create controls on the UI thread. (I don't discuss alternative solution of "creating on background thread and making it freezable" here, just to keep the rules simple to follow. And as pointed out in the comment by @slugster, you can create a control on any thread, but you can only access it from the same thread where it was created.)

  2. From a background thread, if you want to access controls, you need to marshal you code to the UI thread by using Dispatcher.Invoke/BeginInvoke - so the code will be executed on the UI thread. Otherwise, you get this exception (The calling thread must be STA...).

kennyzx
  • 12,845
  • 6
  • 39
  • 83
  • All due respect but this isn't so much a STA vs MTA argument, the error message is a little misleading. In WPF you can create controls on any thread, and you can have more than one UI thread - the trick is to only update the control from the thread it was created on (so your rule #2 is the key one). – slugster Sep 17 '18 at 09:46
  • 1
    Welcome the clarification. Yes, that is more accurate. I use the term “UI thread” loosely, and heard that there are more than one UI thread (for rendering in parallel?) but haven’t found myself in a situation to deal with that so far. – kennyzx Sep 17 '18 at 10:00