This is part five in our Python tkinter tutorial series, in which we are building a simple GUI app to process and check file hash digests. In the previous two articles (part 3 and part 4), we configured the input and output widgets for our app. In this post, we're going to build out our app's two primary buttons and hook them into the appropriate input and output fields.
Our mockup design calls for two buttons: 1) a button that computes the hash value of the given file and checks to see if it matches the value that can be provided by the user; and 2) a button that clears all the input and output fields. Before we begin, let's consider more precisely what exactly our buttons will do. For the purposes of this tutorial, we're not going to consider all the possible edge cases, but rather try to keep things simple.
When the user clicks the hash button, what needs to happen? The app should first: 1) grab the file path from the file path input field, 2) grab the hash value to check against if the user has supplied one, 3) determine which hash function to run from the user's choice of radio button. Then it has to hash the appropriate file, and when the hash digest value is returned, it should: 1) compare that value against the user supplied hash value if there was one, 2) display the digest value in the appropriate output field, 3) indicate whether there was a match, or no match, or something else, in the result field. The second button is much simpler. When the user clicks the clear button, all input and output fields should be cleared of any values.
You can find the current state of our code at the end of part four in our tkinter series. As usual, we'll begin here by getting the preliminary configuration of our button widget frame out of the way. Here's the relevant code from our View class:
Following our normal work flow, we've configured a new row in our main_frame to hold the new frame for our button widgets, and given it a weight of 1. We've also created a new method where we'll configure the frame for our button widgets, and we've called that method from our setup method. We've embedded the buttons frame into the main frame grid at row 3, column 0. And finally, we configured our button frame to have 2 rows and 1 column, to hold each of our two buttons. Let's now configure our two buttons and create place holders for the functions they will call.
These are the first buttons we've configured for our app, but the code in the snippet above should look fairly familiar to you by now if you've been following along from the beginning. We've attached each button to the buttons_frame object. We've embedded the hash button at row 0, column 0, and the clear button at row 1, column 1. We've given both a 'raised' relief to make them look like buttons, and made them both sticky in all four directions.
The only thing really new that we're encountering in the configuration of the buttons is the 'command' keyword argument. As you might have guessed, the command keyword argument contains a reference to the method that is called when the button is pressed. (Note that there are no parentheses () following the name of a method
or function when it is assigned to the command keyword argument, but
rather just the name of the method itself.) We've also created an initial version of each of these methods, which, for the time being, log a simple message when the method is called.
If you run the app, you'll see that each time you click one of the two buttons, the corresponding log message will appear in your terminal. Make sure you have verified this and that your buttons are working properly before moving on to the next section.
The Home Stretch
We're in the home stretch! All the widgets for our application's GUI are now in place. All that remains to do is to fill out the runHash() and clear() methods with the necessary functionality, and then clean up the code.
So, what do we need in order to run our hash function? To begin with, we need to know the full path of the file to be hashed and the hash function we are going to use. Fortunately, we have access to the appropriate class instance attributes that we created in previous installments!
In the runHash() method, we've now grabbed the file path and hash function values from their respective class instance attributes and logged the results to make sure things are working properly. If you run the app, you can play around with various input states to make sure you are indeed grabbing the correct values. Try choosing a couple different files, using the two different radio button options, and verify that the correct values are logged in your terminal.
We'll now write a top level function to process our file hashes and return the hash digest. I've decided to place this function outside our class to keep its logic separate from our app's main view. We'll name this function processDigest() and call it from the runHash() method and log the result.
If you've never used Python's hashlib module before, it might not be entirely clear what's going on in the function above. If so, don't worry too much about it, the code above should work for you, but you should read up on hashlib in the docs, and elsewhere, such as at Python Module of the Week. Suffice it to say, our processDigest() function takes two parameters: the path of the file, and the type of hash to run. It calls the appropriate hash function from hashlib based on the hash_type parameter, and then runs that function on the file located at the path parameter. If something goes wrong, it logs the error. If all goes well, it returns the hash digest.
Our hash button now returns the input file's hash value and logs it in the terminal. We still have to display the returned value in the digest output field, check this returned value against the one that may have been supplied by the user, and provide an appropriate message in the result field depending on the outcome of the match check. This might sound like a lot, but it can easily be accomplished without ever leaving the runHash method. Let's add the necessary functionality.
This seems to do the trick. First we set the hash value attribute with the returned digest string (we then get that value and log it to make sure it's there!). Then we get the the contents of the user supplied hash value input field in the same way, using the get() method that complements the object's set() method. If there's nothing there, we display a simple message saying we're done. If, on the other hand, the user supplied value matches our digest, we display a success message. And finally, on the third hand, if the supplied value does not match, we display a failure message.
Let's fill out our clear method now. It is pretty simple. All it needs to do is set our string variables to the empty string and delete the contents of our entry fields.
Set and delete are all we need here. We've seen the set method before. Here we clear the string variables by setting them to the empty string. For the entry fields, we use the delete method. We want to delete everything in the entry field, so we pass in two parameters: 0, which is where we want to begin deleting from, and 'end', which is where we want to delete to. Here's what the app currently looks like on my system, with all the debug and testing colors still in place, after having successfully processed a hash a confirmed a match:
final article in tkinter tutorial series, we'll clean up the code a bit, and then consider some final thoughts. Here's the current state of our code:
Thanks for following along!