Reputation: 368
Running an issue with multi-threading and WPF. I don't really know what I'm doing and the usual stackoverflow answers aren't working out.
First off, a bunch of WPF windows are created via:
var thread = new Thread(() =>
{
var bar = new MainWindow(command.Monitor, _workspaceService, _bus);
bar.Show();
System.Windows.Threading.Dispatcher.Run();
});
thread.Name = "Bar";
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
In the ctor of the spawned windows, a viewmodel is created and an event is listened to where the viewmodel should be changed.
this.DataContext = new BarViewModel();
// Listen to an event propagated on main thread.
_bus.Events.Where(@event => @event is WorkspaceAttachedEvent).Subscribe(observer =>
{
// Refresh contents of viewmodel.
(this.DataContext as BarViewModel).SetWorkspaces(monitor.Children);
});
The viewmodel state is modified like this:
public void SetWorkspaces(IEnumerable<Workspace> workspaces)
{
Application.Current.Dispatcher.Invoke((Action)delegate
{
this.Workspaces.Clear(); // this.Workspaces is an `ObservableCollection<Workspace>`
foreach (var workspace in workspaces)
this.Workspaces.Add(workspace);
this.OnPropertyChanged("Workspaces");
});
}
Problem is accessing Application.Current.Dispatcher
results in a NullReferenceException
. Is there something wrong with the way the windows are spawned?
Upvotes: 0
Views: 409
Reputation: 915
The problem is, you wouldn't have an Application.Current
instance. You are starting threads for your windows, every thread/window would have its own dispatcher. So the simplest solution is, to pass in the Dispatcher of your MainWindow to your ViewModel and use it instead of Application.Current.Dispatcher
. A quick and very dirty solution:
static void Main(string[] args)
{
do
{
switch (Console.ReadLine())
{
case "n":
StartNewWindowOnOwnThread();
break;
default:
return;
}
} while (true);
}
private static void StartNewWindowOnOwnThread()
{
var t = new Thread(() =>
{
var w = new MainWindow();
w.Show();
System.Windows.Threading.Dispatcher.Run();
});
t.SetApartmentState(ApartmentState.STA);
t.Start();
}
ViewModel of MainWindow:
class MainWindowModel : INotifyPropertyChanged
{
private readonly Dispatcher dispatcher;
private readonly Timer timer;
private int count;
public event PropertyChangedEventHandler PropertyChanged;
internal MainWindowModel(Dispatcher dispatcher)
{
this.dispatcher = dispatcher;
//Simulate background activity on another thread
timer = new Timer(OnTimerTick, null, 1000, 1000);
}
public string ThreadId => $"Thread {dispatcher.Thread.ManagedThreadId}";
public int Count
{
get { return count; }
set
{
count = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count)));
}
}
private void OnTimerTick(object state)
{
dispatcher.BeginInvoke(new Action(() => Count++));
}
}
MainWindow:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowModel(Dispatcher);
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Dispatcher.InvokeShutdown();
}
}
XAML - fragment:
<Grid>
<StackPanel Orientation="Vertical">
<Button Content="Close" Click="Button_Click"/>
<TextBlock Text="{Binding ThreadId}"/>
<TextBlock Text="{Binding Count}"/>
</StackPanel>
</Grid>
Upvotes: 2
Reputation: 1865
The rest of the app is a standard .NET core console app and I needed a way to spawn WPF windows programmatically
Is there something wrong with the way the windows are spawned?
Yes.
In WPF applications you have one main thread (also called the UI thread) which handles all the UI work. Attempting to do UI work from other threads can lead to exceptions.
Here it sounds like your entry point is not a WPF application, but rather a console application and you're trying to spawn windows from that console application. This is not gonna work.
You need to create a WPF project like normal and have it be your main entry point.
Also, don't use Thread
objects where you should be using Task
s.
Upvotes: 1