Introduction
This is a helpful solution if you want to dive deeper into multithreading windows using WPF. I hope it helped you understand how to develop Windows in a different thread and sort of situation which you need to figure out. You can use it for your own purposes and even bind more complex logic to your window which runs in different threads.
Plan
- Purpose and problem description
- Annoying while trying to find the right solution and meanwhile solution
- Good idea with a custom window and final solution
- Summarize
- Links and greetings
Purpose and Problem Description
I’m an experienced WPF/.NET developer. And everyone who works with that amazing technology knows that there are a lot of backside things which unfortunately don’t have right or good solutions. I’d like to tell you about this kind of exceptional case.
This time, I was developing a desktop application and on the right top of the working window, I have to put the EKG gif (The Graphics Interchange Format, which is by simple words just animated image). This EKG should show you that the application doesn’t stick, that it’s alive, and gives a good feeling to the user who is working with it.
So, before, I had some experience with that kind of apps. Of course, I implemented everything together, don’t mind and sometimes it stuck.
A little step to left. When you work with a big amount of data in WPF that kind things unfortunately happen. Because, when you are sending data to the UI the WPF-renderer at the moment stuck and all your live objects stopped moving, which makes a bad impression for users. Of course, there are existing solutions, that can work for your needs for some point: you can send it partially (old school solution using BackgroundWorker), using virtualization (which is also far from an ideal solution), and so on. Always controlling these things is annoying me, I’d say.
That’s why I started to think on some better solution which could make this EKG animation works totally independent from the rest of app processes. Finally, I found it, but on my way, I met a lot of difficulties and showstopper cases.
Annoying trying to find the right solution and meanwhile solution
Yes, my decision was to make it running on a different thread. You know it is easy with the backend process but isn’t so on UI. In WPF starts with two threads: one for handling rendering and another for managing the UI. The rendering thread effectively runs hidden in the background while the UI thread receives input, handles events, paints the screen, and runs application code. The WPF-applications use a single UI thread, although in some situations it is not the best solution.
First of all, I wasn’t sure if this is a really good way, but when I’m starting something new which I’m not sure I always say to myself that this is programming and there is everything is possible.
So, I started with running a new window, but you can’t write just a new Window() (because it will run in the same thread), you should run it in a different one, specially dedicated thread. The solution didn’t make me wait:
- internal void MakeEkgOnDifferentThread()
- {
- try
- {
- Thread newWindowThread = new Thread(new ThreadStart(ThreadStartingPoint));
- newWindowThread.SetApartmentState(ApartmentState.STA);
- newWindowThread.IsBackground = true;
- newWindowThread.Start();
- }
- catch (Exception ex)
- {
- // logging it
- }
- }
- private void ThreadStartingPoint()
- {
- try
- {
- EkgController = new EkgController();
- EkgController.Show();
- System.Windows.Threading.Dispatcher.Run();
- }
- catch (Exception ex)
- {
- // logging it
- }
- }
I have authentication in my app and I call the method MakeEkgOnDifferentThread() right after a successful login. From this time the Other thread window is running in other thread and it will not be stuck even if you will call something like Thread.Sleep(5000) in your main app. This is exactly what we need, but there are a lot of “underwater stones”, mostly with UI part and user feelings from using the app.
My WPF application designed for adaptive layout, you can maximize or change the size of the window how you want, or moving it from place to place. In the window, by design, I have also place for EKG, the user should see it through all app lifecycle. From the information above you can understand that EKG-window should be glued to the main window. This wasn’t a big problem to resolve. I was subscribed to main window events StateChanged, LocationChanged, SizeChanged.
I made 2 help methods, which will return you real window coefficient for calculating real width and height (also depends on which screen you use (in case of multiple connected screens), which scaling settings you have set in windows settings (very often for HD 125% and for 4k 200 or 150%), etc.),
- internal static double GetWindowsScalingWidth()
- {
- return Screen.PrimaryScreen.Bounds.Width / SystemParameters.PrimaryScreenWidth;
- }
- internal static double GetWindowsScalingHeight()
- {
- return Screen.PrimaryScreen.Bounds.Height / SystemParameters.PrimaryScreenHeight;
- }
FYI: In the main window view I use the Grid to make a layout markup. And I put the Grid with name GridForOtherThreadWindow where I’d like to see the EKG-Window. And I decided to wrap the EKG-window to some controller class EkgManager which will handle my window and keep some settings there, just for comfort.
Below is the method that I will call a lot of times when events StateChanged, LocationChanged, SizeChanged will be invoked. This method defines the position of the EKG-window,
- private void GetEkgSettings()
- {
- if (EkgManager.Current == null) return;
- var content = (FrameworkElement)this.GridForOtherThreadWindow;
- var currentPosition = content.PointToScreen(new Point(0, 0));
- var widthFactor = MainWindow.GetWindowsScalingWidth();
- var heightFactor = MainWindow.GetWindowsScalingHeight();
- EkgManager.Current.Settings["Left"] = currentPosition.X / widthFactor;
- EkgManager.Current.Settings["Top"] = currentPosition.Y / heightFactor;
- EkgManager.Current.Settings["Width"] = content.ActualWidth * widthFactor;
- EkgManager.Current.Settings["Height"] = content.ActualHeight * heightFactor;
- EkgManager.Current.WindowSettingsChanged();
- }
- // EkgController is our target EKG-window managed from EkgManager.
- internal void WindowSettingsChanged()
- {
- try
- {
- if (EkgController == null) return;
- EkgController.Dispatcher.Invoke(() => UpdateWindowSettings());
- }
- catch (Exception ex)
- {
- // log it
- }
- }
- private void UpdateWindowSettings()
- {
- EkgController.Width = Settings["Width"];
- EkgController.Height = Settings["Height"];
- EkgController.Left = Settings["Left"];
- EkgController.Top = Settings["Top"];
- }
Here we are. Ready to go deeper and see what happens when event LocationChanged is invoked?
- private void WindowLocationChangedHandler(object sender, EventArgs e)
- {
- GetEkgSettings();
- }
Go to the next one SizeChanged. But be careful, it will not work properly if you will subscribe to the event of your Main Window SizeChanged.
You should subscribe on the event of your container (Grid, StackPanel, whatever) which holds your content, that’s why I named it as Content SizeChanged.
- private void ContentSizeChanged(object sender, SizeChangedEventArgs e)
- {
- GetEkgSettings();
- }
For now, it looks pretty easy, doesn’t it?
Implementing handlers for LocationChanged and SizeChanged events let us stick our other thread window to the main window. But we also want to see it above our main window. And in this case, TopMost property helps us to implement it. Just set it is true and you will get the needed result. Of course, I will not remind you about it if all has been done without trouble.
The most interesting part is coming here when we implement handler for the StateChanged event. You should make it because, in case of minimizing your Main Window, the Ekg-Window will not be minimized. And this isn’t only one bad thing which happens. Your Main Window will not restore back anymore, because Windows OS somehow thinks that it is already restored because of visible Ekg-Window. So, for this reason, I implemented a handler for StateChanged event and I’d like to minimize my window I’m killing the Ekg-window and after restoring creating it again. Believe me, this works fine solution (Maybe, you will find better, describe it in comments). Also, one more reason to use wrapper for another thread window. Here is the implementation:

Oleksii KucherenkoPosted Oct 20, 2020, 5:38 AM
I found some bugs in my solution. And I have fixed version. Bugs related to the window behavior. Unfortunately, I can't update my attached archive but if you really interesting in it write me.