Building A Splash Screen With Async Loading In C# And WPF
July 10, 2024
• 3,088 views
You know it. I know it. We all know it.
Every single good desktop application has a fancy splash screen.
And now... you can build your very own in C# and WPF! In this tutorial, I'll walk through how to build a WPF splash screen that supports early cancellation as well as asynchronous loading.
Because what's the biggest gotcha with these?
Threading.
So let's ensure we're executing our UI and background work on the right threads!
View Transcript
all right it's no secret that if you want to build a really cool desktop application you need to have one of these things hi my name is Nick centino and I'm a principal software engineering manager at Microsoft what's the number one thing that your cool fancy desktop application needs well a splash screen and okay maybe not the best most important thing that your application needs but it's really cool to be able to have a splash screen at the startup of your app and that way not only can you Captivate the user's attention you can get some loading done at the same time in this video I'm going to walk through a basic WPF tutorial where we can get a splash screen going and we're going to look at some of the most common issues that I see when doing this type of thing and that's
going to be threading if that sounds interesting remember to subscribe to the channel and check out that pin comment for my courses on dome Trin with that said let's go build us a splash screen so to start things off I'm going to jump over to my Splash Window that I've created it's just a big magenta window that's definitely going to get people's attention I obviously don't recommend doing this you probably want to put a picture or something else in place but I do just have this progress bar set to be indeterminate so that we can see that it's not frozen we have a screen up but I didn't have any cool graphics to go drop in place here so we're not going to be focusing on that you can go style this thing up however you'd like but I just wanted to call out that
I do have this window that I've added and a couple other things to note are that I have the startup location to be Center screen and that's going to mean that when we open this window however we decided to do that it's going to be at the center of your screen as it might indicate otherwise you might have some weird Behavior with the startup location you'll notice that the splash screen kind of comes up in different spots I think usually it's where the cursor is but you know something to pay attention to if you want it to feel a little bit more Pro the other thing to mention is that I have the window style set to none so you can see that there is no control bar at the top of this window you can't X out of it to close it it's just
going to be a borderless template that we can work with and that way like I said you can go slap an image onto here and make it look super cool so if you want to see how we end up building this with actual progress stay right to the end of this video and you can see how to hook that all up Okay so we've seen that we have a very basic Splash Window and if you notice in the left solution explorer that I have a main window zaml file as well so what we're going to be doing is chaining our application startup to launching the splash screen and when that's done we're going to go launch the main window right after that now a couple of things that I want to talk about in terms of the goals of our splash screen in this video
are going to be one that I want to make sure that we can run some background work because that's one of the primary purposes that we have Splash screens for we need to do some work up front to get things set up and that way we're not basically blocking the user interface and the user's kind of sitting there like why the heck do I have this ghosted application that you know it's locked up I can't move it around so we can go do some work right so I want to do that the other thing is that we might want to have a Graphic or something maybe it's just to you know like I said catch their attention so you do want to have it there for some amount of time but maybe once the work is done you can let them X out of that
splash screen right so I want to talk about these two concepts of having some maybe some minimum amount of time you want to wait and then also some work that you want to be doing so we have these two things that we have to wait for and once that work is done maybe we can just say okay the users waited long enough let's let them X Out of the splash screen and go to the application so at the very beginning of my application if you've watched my previous videos I am using screw door I am using the I service collection for dependency injection and I'm setting up a service provider but if you want to learn more about that you can go check out these videos and that way you can catch up on this series but it's not super important you can go new
these things up and create them however you'd like this is just my preferred way if we jump over to our startup method and we see how we want to go get this splash screen going what I'm going to be doing is using what I call a presenter and if you're working with different UI Frameworks you'll be familiar with things like MVC model view controller mvvm model view view model and there's all sorts of other things you can do I like to use something that I call presenters they're going to be sort of our Logic for getting our flow and our user interface so I have a presenter that's going to be sort of the entry point that we're working with here and I'm going to ask that Splash presenter to show the splash screen a couple of things that I want to point out here
on the API and of course I'm going to walk through this in more detail are that I have this show splash async method so we're going to await that I have a time that we want to show the splash screen for and after creating this I wonder if maybe this should say maximum splash screen time so this is going to be something we talk about about waiting for this work which is the next part we want to wait for this work to finish and I'll talk about this comment because as I said in the beginning of this video this is going to be one of the trickiest parts that I see that we have to navigate when we're building things like Splash screens but really these are the two main things that we have on our splash screen we have to wait some amount of
time for the splash to be up but we also don't want to let the user close that splash screen if we're still doing important work let's go jump into the implementation of show splash to start off this presenter does take in a few variables so I am going to talk about this dispatcher a little bit later that's going to be a bit of a spoiler alert for some things that are up and coming but I do need a Splash Window reference that's going to be the window that we saw at the very beginning of this video that big magenta one so we need a reference to that this will get PED in through dependency injection in my current setup I also need a main window presenter if you don't want to use this uh presenter pattern you can just go ahead and get a reference
to the main window and new it up or you could new it up inside of your splash screen presenter to show it I am using dependency injection for all this stuff it's my preferred way to do it but this part here is going to be the thing that allows us to go show the main window after like I said I'm going to come back to the dispatcher when we start coming across that throughout this tutorial but again if we go look at the show splash async method we'll see that we have that time we want to wait and we have our call back for doing some important work so from here let's go dive into the details about how we make these things run together and do them successfully the first thing that I want to have and I'm going to come back to this
in a moment is I have to make this cancellation token source and that's because when we want to go have that delay that we're just waiting for the SP FL screen to be up we need to be able to cancel that if the user Clicks in time so say that work finishes we've done all of our heavy background work and that only took you know 3 seconds let's say and we were supposed to wait up to 5 seconds for the splash screen if you want to let the user close the splash screen earlier so sometime between that 3 and 5 Second Mark we do want to make sure that we can cancel that weight period which we're going to look at in just a moment here I am going to have to hook up an event for when that window is closing and that's going
to mean that once that window's closed our Splash Window that way we can cancel the weight period if it's still going right and then at the end we can also make sure that we go and show the main window cuz that was the whole goal for after the splash screen so we hook that up when the Splash Window is closing right after that we're going to show the Splash Window so at this point in time after line 118 runs we have a Splash Window that should get presented to the user before we move on this is just a reminder that I do have courses available on don't train if you want to level up in your C programming if you head over to dome train you can see that I have a course bundle that has my getting started and deep dive courses on C
between the two of these that's 11 hours of programming in the C language taking you from absolutely no programming experience to being able to build basic applications you'll learn everything about variables Loops a bit of async programming and object-oriented programming as well make sure to check it out this is where we have to go run our other work and that's going to be between the minimum weight period that we want to set as well as down here I have the work that we want to go do in the background so these are two different tasks that we have to work with and the way that I've structured this I want to explain because you'll see that I have a task set here but I'm not awaiting it at line 121 I'm actually awaiting it all the way down at line 165 so that's a little
bit weird right why do I have this one created up here but down here is when I'm awaiting it and to explain that I want to talk about these two things competing right because we could think about doing something like await task when all and then we would pass in both of these task so we have the minimum weight task and then we would have the other one that's the um let's call it just the do work task which I don't have right but conceptually do we want to wait for both of these to finish Until the End right and I'm going to write it out like this if the minimum weight task is supposed to be 5 seconds and like I said the naming is probably wrong at this point it might might be like max weight time um so the max weight and
then the time it takes to do work time to do work is only 3 seconds let's say what we want to be able to do is minimally wait for the work to get done so we minimally need to wait 3 seconds and then between the 3 and the 5 Seconds that's when we have this period where the user should be able to close the splash screen and how I've accommodated that you'll notice on line 167 I'm switching the Splash Window style to be from none which I talked about in the beginning to Tool window that's going to give us that little X in the top you could do other things and I I left a comment here talking about bindings and view models and stuff like that you could instead have a button or something on the Splash Window or make it so you can
click uh somewhere on the splash screen or press escape and it handles this the same way that's where you're going to want to configure it here I'm just using the X in the top corner just to keep it simple but the point is that if we were to wait for both of these things we're always going to wait the maximum amount of time and that means that we don't get that option to be able to x out earlier we don't want a when all but we want to wait until the work is done if the work is done we're going to like I said allow the user to interact with this splash screen to close it off however we want to do that and then at the end if the user has not interacted with the splash screen we want to wait that remainder of
the time so again minimum weight task maybe not the best naming maybe it should be maximum weight because it's truly the maximum before we go and do that so I'm going to go ahead and rename this because I think it's going to make it a little bit more obvious as we go through the rest of this so I'm going to call this maximum Splash time as well the point that's important to note here is that if the time it took to do the work was greater than the splash time that your setting it's still not going to be the max right it's actually like the max idle time or something like that the other thing I want to call out right at the end here on line 165 that's kind of weird this is a bit of a cheat I guess I don't want to
throw an exception when this task it's cancelled because that's one of the things that can happen up here right line 115 can cancel that weight task and if you see here we use that cancel token Source we use the token for that I didn't explain this in detail so I'm going to come back to this in just a moment but because this cancellation token can get triggered from here down here this task will throw an exception if it's cancelled so this continu with syntax is a bit of a cheat it's going to swallow that task cancellation exception the dangerous part about this is if that task through an exception for any other reason it's also going to get swallowed it's just because this is super lightweight right now that I'm not super worried about that but a better way to do this would be and
it's a little bit gross to look at but if you wanted to do something like this we know that this task can get cancelled so we don't care it's just that the behavior for tasks in.net when you do a cancellation is that it's going to throw so that's the goal of what this is to do is we just don't care if it was actually cancelled we just want to continue on now to talk about this because this part's important we do want to have a delay and like I said we're going to be using our linked cancellation token so it takes in the cancel token that was passed in but we're going to make a new source and that way they're linked together if someone on the outside cancels this thing will get cancelled but we also want to make sure that we can trigger
cancellation ourself that was back here on line 115 so that's the token we're using here it's important to note but I have this continue with on here as well so if we do reach the end of this you'll see that it's only when it ran to completion so if nothing else cancels this there's no exception whatever it ran successfully to completion we're going to close that Splash Window as a result this event will get triggered and we're going to continue on to put this all together and we're going to go jump into this part in just a moment from line 158 to line line 160 so to put these pieces together we have this delay task and that's going to make sure that we can keep the splash screen up for an amount of time and we also have this task that's going to be
doing background work I already explained the details about how we want to wait for that background work to be done before the user can go close the splash screen but we have to go run these two things in parallel those are some of the nuances that we have to think through for both of these things but now we have to go look at how this call back works so I want to explain this a little bit further this do heavy work async call back if I scroll up to where this was called this logic here is if you wanted to do some loading right so if you need to load resources up connect to different things whatever it happens to be Resto ing state for your application it you have so many different options for things you could do here but if you need some
setup code to run at the beginning of your app this is where you'd want to do it now I'm going to talk about this assertion here and I want to put this back in the code because when we go to run this I want to talk about what's happening the important thing here is that this is background work we want to make sure that whatever loading we're doing is happening on the background thread if not what will happen is that you will be using the user interface thread the main thread to do work and that's not good because as you are doing heavy work on this thread you could be locking up the user interface you get that ghosting Behavior it's kind of choppy if someone's trying to move the window around we want to enforce that this is on the background so I have
this debug assert for us to prove if that's the case a spoiler alert for the rest of this video we're going to be looking at all of the different situations how this stuff is currently broken in everything that I showed you from there we're going to go fix it to start off let's go run this and see where the first issue is what I'm going to do is start with a break point though so when we go show splash screen I'm going to have a break point right here so we can see how things come together let's go try it out all right as we step through things there is no splash screen shown yet which is as expected we haven't hit line 118 so I'm going to go make that linked cancellation token Source we'll hook up our event handler on the closing of
the window and then we're going to go show it so at this point you might have seen very quickly and I can't actually bring it up on my screen because we're stepping through the code but the Splash Window popped up very briefly it is sort of in the background right now that's okay so it did get shown but now we have to go set up this maximum weight task I changed the name from there we're also going to do the background work so I'm going to go ahead and run this I'm going to put a break point here and we'll see what happens and as soon as I pressed F5 it might not have been obvious you might not have heard the click on my keyboard but when I pressed F5 to continue this we hit this assertion the tricky part about what's happening here
is that this assertion is catching an issue for us the assertion says that we need to be on a different thread than the current user interface thread checking the dispatcher which is that thing I talked about earlier I said we're going to come back to that the dispatcher is what allows us to run things on the main thread so all of the user interface in WPF even Wind forms and other user interface Frameworks there's generally a main thread that runs for the UI this dispatcher thread is the main thread it's the UI thread so what I'm doing is saying look we need to be on a different one so when I pressed F5 it ran right to here and it basically said look you're on the main thread stop this isn't right so how do we make sure that we don't get stuck on the
main thread let's go back and see where we came from right here line one 58 something about this is wrong we're basically saying await do this heavy work and we're going to basically invoke this call to go run that method but the problem is that this is running on the main thread so let's go fix it the code on my screen right now is a little bit more of ver Bose I've left in the original code here you'll notice that 156 through 158 is actually the same as 149 through 151 but what I'm doing is wrapping this in a task run I've also copied in assertion right here and that's because you may want to ensure right in your splash screen and not in the calling destination that you are checking for this right it's not the responsibility of the person who's implementing the background
loading to know that you really want to guarantee it for that person so I have this now copied into here as well but what this should afford us to do is go run this work in the background we're guaranteeing it here right but this task run is what's going to be the thing that allows us to do that so I'm going to go ahead and run this now and we'll see if that gets us any further I've entered I hit my break point I'm just going to press F5 and hopefully we can get down to here oh and we get a splash screen which is awesome but now we hit another breakpoint so let's go and we hit another exception so that breakpoint we hit we ended up hitting that because the work finished to explain why it finished or how I know that it
finished successfully is that this does 50 it ations waiting for 50 milliseconds and that means we're going to wait for 2.5 seconds overall so after 2.5 seconds if I scroll back to here we finished this right we finished that we got to this line but when I tried to get this Windows style it actually threw an exception and you'll see now that I have this other exception going on because this part up here is also throwing a similar type of exception it's happening because we're on the wrong thread this one we're going to come back to in just a moment this is a bit of a distraction it's just because this was all asynchronous I'm trying to record the video my editor is going to have a hell of a time trying to get this all put in order but this part down here was
what happened first as soon as I stepped over it it threw an exception so if I stop this why is this on the wrong thread and it might not be obvious to you but it's all because of line 154 so I'm going to talk about this for just a moment we ran this task right this forced the background work to actually be on a background thread instead of the main thread that's good for us but configure a weight this is something that you probably see in a lot of spots when you're using tasks configur weight false means that when this task finishes we don't care which thread it resumes on this task started on the main thread and we said we don't care which one after now if I take this away this will mean that this execution flow will resume back on the main
thread that's where it started and that means that this should be safe for us the other way that you could go do this is that dispatcher that I talked about earlier we could use the dispatcher and I have it as underscore dispatcher we could go do this and then put this code inside here this is one option where you don't need that so that's one thing you could do or we could take this out and we could not use this by not having configure our weight false this will go resume back on the main thread let's go prove it so there's our break point being hit at the beginning I'm going to go put the break point here so after this goes and executes 2 and 1/2 seconds in and we get over this line successfully and that's because we are still on the main
thread so that's what doing this helped us with is everything okay are we still going to have this problem that came up as well it's the same issue we are not on the main thread when we go to finish this maximum weight time right let's go fix this with the dispatcher this is the other thing I said we could do in this particular code unlike the configure away thing that I was changing before I'm going to use the dispatcher to say hey look I want you to go run this body of code inside of this Anonymous delegate I want you to go run this code on the main thread for us let's go try this in combination with what we had before and I'm going to go ahead and take this break point off and as well this one down here because I want to
see if this flows all the way through to the end now after 2 and 1/2 seconds we should get that X in the top right and there we go it closed off automatically after a total of 5 Seconds and we got our main window so far so good right we ended up switching between the state of the window it automatically closed off after 5 seconds so that functionality seems to be working for us but there's still one more thing that we have to Pro prove and that's if if I click that X while it's running so let's go run this again after 2 and 1/2 seconds I should be able to click the X and it works and if I wait the rest of that sort of 5-second period there's no exceptions no crashing nothing weird going on so to recap what we have going
on here is a splash screen that we can go use through what I'm calling a presenter so a splash presenter you can do this in any way that you want this is just my preferred birdway again with some dependency injection I have to hook up an event for when that window's closing because once it's closed that's when I want to launch the main form and the big things that we wanted to look through in this method were that we wanted to wait a period of time and then the other thing was running some background work truly in the background between these two things we wait for them accordingly and then we're going to switch the window style and this is where I said you could do some other things like putting buttons up you could click on the user interface make it enabled instead of
disabled anything you want there but that's line 162 we do that after the background work is done and then finally we wait for that wait time to finish off in between all of these things if the user clicks and tries to close it off it should go work successfully so to go run this one more time we should see this all come together we get this little progress bar that's indeterminant we got the X in the top right and it closed off on its own and that is how you go make a splash screen but I did promise you at the beginning of this video that if you wanted to see how we could go get progress to work on our splash screen that you had to watch to the end so if you wait when this video is uploaded you can see how to
do that thanks and I'll see you next time
Frequently Asked Questions
What is the purpose of a splash screen in a desktop application?
A splash screen is a great way to captivate the user's attention while the application is loading. It allows us to perform background work during the startup process without blocking the user interface, so users don't experience a frozen application.
How can I allow users to close the splash screen before the maximum wait time is reached?
To let users close the splash screen early, I set up a cancellation token that can be triggered when the splash screen is closing. This way, if the background work finishes before the maximum wait time, users can close the splash screen at that point.
What are some common issues when implementing a splash screen in WPF?
One common issue is threading. It's important to ensure that any heavy background work is done on a separate thread to avoid freezing the user interface. Additionally, managing the timing of the splash screen and ensuring it can be closed appropriately can also be tricky.
These FAQs were generated by AI from the video transcript.