Feedback (Loading, Validation, Error Messages) - WPF MVVM TUTORIAL #8

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
there's still more improvements we can make in our reservement wpf application for example what if a database interaction takes a little bit longer than expected we would probably want to show some kind of loading indication there what if the user enters data that is invalid would probably want to notify them about that data so that they could fix it and lastly what if some kind of error happens whenever we save data to the database or read data we would probably want to report that error to the user so that being said we are going to focus on setting up loading indicators implementing data validation for user input and reporting error messages on our ui so we're going to start off by setting up a loading indicator whenever we load data from the database so what i'm going to do is i'm going to hard code my database interaction to take a little bit longer than expected so we will just do a task delay and delay for two seconds so 2 000 milliseconds and now if we've run this we will see on the ui here we go empty and then two seconds later then it populates but how was i supposed to know that the data was loading there was no indicator but we are going to add one so that is going to go on our reservation listing view model we're going to need some kind of property for is loading so let's define that this will be a prop change so gotta raise one property change whenever this changes and it's just going to be a boolean for is loading and now we just need to toggle this boolean whenever we load so that loading is done in the load reservations command and we have a reference to our reservation listing view model in this command so before we start loading we can set the viewmodels is loading that we just added to true and then when we're done loading we'll set it to false so we'll put this into a finally and actually we don't have to because this will not throw exceptions so we can just set is loading the false whenever we're done loading and let's make sure that worked out okay so there we go we sit is loading the true because we are loading now and now still loading still loading and then we're done so we set it to false so now we just need to update our ui to show some kind of loading indicator rather than our empty grid of reservations so that is in the reservation listing view and we have our header here so that's always going to be visible but then we have our list view and we want this to be not visible when we are loading so you might think we would take our visibility and have that bind to is loading and then we can use the built-in wpf boolean to visibility converter but then what that would do is it would convert is loading when it's true to visible but that's not what we want we want when is loading is true we want this to be invisible so we would want the opposite of what the built-in converter would do so rather than defining a custom converter for something like inverse boolean divisibility converter i usually set up a style and use a data trigger so that when is loading is true i'll set visibility to collapse but we haven't done anything with converters yet in the series and using a converter here rather than a style will make the xaml a lot more concise so we are going to create a custom converter it's good to demonstrate converters because they are pretty fundamental wpf concepts so we have a converters folder and we are going to have an inverse boolean to visibility converter so all converters need to implement i value converter so implement that interface and for this we are not going to be converting back so we just need to implement convert and what we're going to do is first type check our value that gets passed in and in our case it's going to be an is loading boolean so we are going to try and cast this to a boolean so if it is a bool and we'll call this the bool value and that bull value is true then we're going to return visibility so import that dot collapsed and then if the value isn't a boolean or the boolean is false so we aren't loading then in this case we want to show visible so maybe not the best idea to make this a one-liner kind of hard to understand but pretty simple converter i guess it works in this case now let's use that converter so we can have user control resources and we're going to define an inverse boolean to visibility converter so we're gonna have to import that from our converter's namespace and give it a key which in our case is just gonna be the inverse boolean to visibility converter and now we can use that down here as our converter by specifying a static resource and pointing to our converter resource so now what this is going to do is if we are loading then the visibility will be collapsed and if we're not loading then the visibility will be visible but before we test this out i do want to set up our loading indicator so what i'm going to do actually is wrap this list view into a grid so we can do a little surround with and let's move our grid row and margin onto that grid and now in this grid so it's going to be in the same spot as the list view we are going to have some kind of loading indicator so we could just have a text block for loading but what i'm going to do is add a new package that includes a loading spinner control so this is called dot loadingspinner.wpf by singleton sean so i have a video where i go through and create this control definitely a great video for learning some fundamental wpf concepts but we don't have to worry about that for now we can just install this it's pretty easy to use as we will see and now in our grid we are gonna have a loading spinner so import that from our namespace i'll just rename this to custom and now we're gonna have to toggle the visibility on this as well so this is also gonna bind to is loading but in this case our converter is not going to be the inverse boolean to visibility converter we just want to use the regular boolean to visibility converter which is built into wpf so a boolean to visibility converter give that a key and what this converter does let's use that down here is if is loading is true then the visibility will be visible so we will show the loading spinner so we got these two controls hand in hand that's why i wrapped them in this grid so that we could reuse the margin and the grid rail and before we test this there is more that we can configure with this loading spinner so we can increase the thickness a little bit we'll go with five we can set the color how about a gray i think we also need a diameter so the size will do a hundred that should be good enough let's see how this looks all right so we load up and the loading spinner was not there but our grid was invisible so that's good we just need our loading spinner to appear and oh i know the issue so this is pretty dumb so we actually have an is loading dependency property on here which toggles whether or not the spinner is visible so we don't even need this converter we can just bind this is loading dependency property to the is loading property on our view model and get rid of this visibility converter so we don't need that at all anymore and this is loading property will toggle its visibility automatically there we go we see the spinner and then the reservations appear perfect and the other issue i noticed is that we always have the scroll viewer over here so to get rid of that head to the main window we added this last time but what we want to do is set the vertical scroll bar visibility to auto so that it's not always visible so there is other places we could add loading for example whenever we make a reservation we might want some kind of loading indicator while we create that in the database but adding that would pretty much just be doing the same exact thing we just did so we are now going to move on to data validation and the perfect place to demonstrate this is on our make reservation view so currently the start date can be after the end date so what i want to do for this data validation is display some kind of error on these date pickers if the end date is not after the start date because otherwise that just wouldn't make any sense so we're going to do all of that validation in the view model so the make reservation view model and the reason we're doing it here is because we're going to use i notify data error info so this is a pretty standard way to do validation in wpf and it allows us to do our validation in the viewmodel which is good because then our ui doesn't have to know about our business logic that should really stay in the viewmodel and above which on that note this validation should also occur inside of our model and we should throw some kind of exception if the end date is not after the start date so ideally we would have that validation in both places but for now we're just going to focus on getting that set up on the view model so implement i notify data error info and what has that created for us so we have an errors changed event that is just generated right in the middle of our field and property thank you very much let's move that down here we also have a has errors property and then we have this get errors function that returns a list of errors for a given property that we're binding to so based on this we see that each property can have many errors so the best way to represent this based on past experiences is to use a dictionary so we're gonna have a dictionary here usually i have fields at the top of my class but i'm going to keep all my errors stuff together so this can be a dictionary with the key being a string for our property name and the value being a list of the error messages and we'll call this the property name to errors dictionary and we'll initialize that in the constructor so now it has errors that'll be pretty easy to implement we can just take our dictionary and check if it has any values in it for getting errors this is pretty easy as well all i have to do is take our dictionary and do a git value or default with the property name as our key and the default value can just be an empty list so now we just need to add errors and whenever we add an error we need to raise this errors changed event so that our ui knows that there's errors and it can re-grab those so let's just do this in our end date setter for now the first thing we want to do is clear any existing errors before we set this new value so for that we can take our dictionary and remove the entry for our end date property and then if the end date is before the start date then we want to add an error here so in our case we only have one possible error so what we can do is just create a list for our errors right here and pass in our error message so in this case we're going to say that end date cannot be before the start date and then we'll take our dictionary and add an entry for our end date property and that'll be for these errors but what happens here the errors changed so we need to raise our areas changed event so we'll just do that right here we'll take errors change and invoke it with this is the sender and some data error changed event args which just takes our property name we'll pass that in again and now let's head over to the make reservation page and let's put some kind of dumb date in here let's do a whole year back and nothing quite happens so why is nothing happening let's put a break point here and try this again all right so we hit the break point let's step through this so we go through our validation we add it to the dictionary and then errors changed all good let's continue okay so that time we had it so it was only the first time what is going on here let's try this again let's do same thing all right so we hit the setter again step over okay so what is our start date and here we go here is the issue so we do not set the end date to the new value before we do our validation so we're using the old value we're validating the old value that is not going to cut it we got to set the value first let's just do that right here in fact we can move the one property changed up here as well we'll just tack on the validation afterwards and now everything should work so let's make a reservation let's change this and there we go now we get our error so the only downside of validation in wpf is that we don't get our error message displayed by default now i have another video where i go deeper into i notified data error info and actually override a template so that we see the error messages because that would definitely be helpful but in this case we're going to bypass that anyways if we choose a date afterwards there we go error message is gone so now we just need to clean this up a little bit so this could be a much more general function let's extract that and that can be called clear errors and clear errors is going to take our property name that we want to clear errors for this is the end date so we'll get that property name passed in there we go let's move this down here with the other error methods this could also be and when error is changed so one error is changed and that's going to take a property name as well so we'll pass in end date and generate that parameter and go ahead and use it and then lastly we could have an add error function so add an error except this is going to be a little bit more unique so this is going to take the error message that we want to add and also the property name that we want to add the error for so let's update that up here pass in the error message and the end date property get the name of that so we're much cleaner up here but we still got to implement add air and we want add error to support multiple error messages so what we can do is first check if our dictionary does not contain an entry for our property because if it doesn't then we want to add an entry for that property and in this case it can just be an empty list so now we know this property is in the dictionary so now all we have to do is take our dictionary get the value for our property name and add the error message into that list and then actually we can call it when errors changed here instead so we don't have to do it everywhere and update our property name we could also call on errors change in our clear errors that might be helpful we don't really need to return here this can just be a void and there we go much cleaner and let's make sure this all worked so here we go let's take our end date and put it before the start date there we go we get our error but if we take the start date and put that back before the end date then we still have our error so what we're going to do to fix that is clear errors in our start date setter for the end date but then what if we're clearing errors and the validation is still invalid well we can check if this new start date value is actually after the end date so we're going to do validation again so let's just copy this except now our error is actually just going to go on the start date and we'll say the start date cannot be after the end date so if we're adding an error for start date we're also going to clear errors for start date so we get a fresh set of errors and now we're also going to want to clear errors for start date and our end date setter because otherwise we could face the same issue where this error message on the start date could be stale when we set the end date but now let's send our end date back so we get the error let's fix our start date that removes the error on the end date and now let's move our start date after the end date and there we go now we get an error on the start date so maybe that's a little bit weird you could also have errors for both of these appearing at once by just calling add air here for the end date as well as the start date so for example something like this and then move the start date error into the end date setter and there we go now we get errors for both if you wanted that but i'm just going to leave it as it is so the last thing we're going to cover is error messages on the ui and i think for this we're going to go back to the reservation listing view have an error message on here now there's really three approaches to error messages we can have the message box which we have now but it's kind of ugly which is why i'm going to show a different way you could have a global message handler so you'd have a banner in a centralized location that would show all of your error messages i have a video where i discuss that but in this case what we're going to do is just display the error message on the individual page in this case the reservation listing view so in our load reservations command let's just hard code an exception here and we're going to catch that exception but right now we just show a message box we don't want that instead we want to have some kind of error message on our ui so that means on our reservation listing view model we can have a prop change for an error message just a string maybe you'd want it to be an observable collection of strings if you had multiple error messages but in this case we're just going to have one and now in our load reservations commands what we're going to do is set that error message property on our viewmodel to our fail to load reservations and then before we load we're going to want to clear that stale error message so we can set it to string.empty and now all we do is in our ui we want to show the error message so that's going to be done after loading so our loading indicator is going to have a higher priority than the error message so what we're going to do is wrap our list view in another grid so it's around with a grid and we're going to move our visibility for is loading onto that grid and now we have this new grid that's only going to be shown if we are not loading so if we are not loading then we want to display our error message if we have one so we only want to display our list view if we do not have an error message so we can have a binding here and what are we going to bind to well we can have a property on our view model so this can be a boolean has error message and that'll check if our error message is not null or empty but this is a property that we're eventually going to be binding to so we're gonna have to raise one property changed for this property but like where there's no setter here well this property depends on error message so whenever we set error message we can raise one property changed for has error message and then our ui will update and re-grab this value so that means we can bind to it so we're going to bind the visibility for our list view to has error message and if has error message is true then we want our list view to be collapsed so we're going to use our inverse boolean to visibility converter here and finally we're going to have a text block that displays our error message we'll make it red and this will need a visibility as well so we're going to bind to has error message again but this time the converter is going to be a boolean to visibility converter which we actually deleted a little while ago so we're going to need that again let's just add that give it a key and now let's reference that down here in our static resource a boolean to visibility converter and now we should see our error message because we are hard coding that exception and there we go we get failed-loaded reservations but i didn't see the spinner there and that's because we just throw the exception immediately let's get our spinner back so we're going to load first and then throw so just to show off we get the spinner first and then we fail to load so one thing i want to point out real quick that we should have addressed in the stores video is that if our loading actually throws an exception so we load and then we throw and fail and then we go to make a reservation and come back to this page we don't reload the reservations so ideally we would try and re-initialize again if we failed and to fix that what we can do is wrap this lazy in a try catch and then if it fails we can assign the lazy to a new lazy that hasn't been initialized yet and one sec we actually do still want to throw here have that exception propagate up to our command so that we can handle it in the view model and lastly the lazy is read only it doesn't have to be we can make that a non-read-only field so let's get the exception back in here we load and then we fail but then we go back to that page and we load again so we are actually in here again trying to reload let's make sure that is true so we do hit that breakpoint so we do try and load again that's good so let's get rid of that and lastly i don't like the gray spinner i think i like black better so i went ahead and made that black so we got a loading indicator to give user feedback we have data validation for validating bad user input such as the start date being after the end date which doesn't make sense and then we showed off how to add an error message in case our loading fails or something so we did make all these changes in just one place but they could definitely be applied to many places in the application for example the make reservation page could have an error message as well and a loading indicator but i just wanted to introduce these concepts rather than doing the same thing over and over again and being more boring than i have to be anyways hopefully you can apply these concepts to your own application i recommend doing so to give ultimate user feedback but anyways if you have any questions criticisms or concerns be sure to leave them below in the comments section if you enjoyed the video or enjoying the channel consider becoming a member other than that leave a like or subscribe for more thank you
Info
Channel: SingletonSean
Views: 2,081
Rating: undefined out of 5
Keywords: wpf, programming, visual, studio, xaml, custom, control, generic, system, line, display, timer, template, binding, c#, how, to, series, tutorial, easy, time, maintain, package, design, part, event, code, framework, register, static, state, default, view, style, wrap, panel, stack, scroll, viewer, first, width, action, void, model, layout, user, mvvm, data, error, icon, class, clean, simple, sub, file, host, grid, scope, align, margin, deploy, release, download, essential, validation, rule, logic, storyboard, story, navigation, store, feedback, ui, message, handling
Id: XW88Aa12mx8
Channel Id: undefined
Length: 19min 48sec (1188 seconds)
Published: Sat Nov 06 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.