Creating responsive layouts

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
a really important part of any app is responsive layout what that means is let me run the app that we are going to create it is going to look like this right now we have an app with a couple of items stacked on top of each other so far nothing special however if I increase the size of this widget we are at some point getting another kind of layout and if I go even further we have a different kind of layout which means depending on the size of the window we get different kinds of layout which is really important because I want the app to look good on basically any size and some layouts simply don't work on a certain kind of size for example if I make this a bit smaller again this kind of layout here only really works on very small windows while this one here would only really work on a white window so I have to create different kinds of layouts now that being said implementing all of this in tkinter isn't the easiest thing to do the reason for that is that tkinter doesn't have inbuilt tools to create responsive layouts which basically means that we cannot update an existing layout instead what we have to do is we have to create a separate layout for every window size or at least for the layouts that we want to create so for this example I have created three different layouts let me actually show you I think that's going to be useful here's the code that we are going to create over the course of this video all the important stuff is inside of the app class basically what you have to understand is that we have three methods one for create small layout create medium layout and create large layout inside of them we are simply creating a frame we are adding widgets to this Frame along with the layout method and then we are packing the frame also before that we are forgetting the previous frame that way if we go from the small layout to the medium layout we are not creating extra widgets the really important part of logic happens inside of another class this one is called size Notifier what this one is doing is it checks the size of our window order with to be more specific and if we reach a certain threshold like 300 or 600 or 1200 then this class is going to call the functions we are passing into it or the methods in this case which are going to be create small layout create medium layout or create large layout all you really have to understand is that we are creating three separate layouts and we are displaying each layout depending on the minimum size of the window that's all that's happening here as a matter of fact let's talk about it really quick what we are doing in the most basic sense we are setting different breaking points for the minimum width of a layout so for example if the width of the windows 300 we want to have a small layout if the width is 600 we want to have the medium layout and if the width is 1200 we want to have the large layout and whenever the window resizes we are checking the width and if the window is crossing a new threshold if that is the case we are building a new layout and forgetting the previous layout that is literally the entire logic although to implement it we do need a couple of things the major difficulty that we actually have to face is in this step here by default we can use event binding to check when the window is resizing the problem is this event is going to run on every resizing of the window which is a problem for us because we only really want to check if the width crosses a certain threshold so for example we want to check if the width of the app losses 300 we do not care if it goes from 350 to 400 that is completely irrelevant to us we only care if we go from 299 to 300. that is the major difficulty that we have to implement the rest is actually fairly simple but I guess let's jump straight in and let's create all of this to get started all I have are the Imports for tkinder and ttk the reason for that is that all of the app is going to be handled by classes the most important one is the app class this one has to inherit from TK and TK inside of that I want to call the dunder init method with self and I also want to have a parameter for the start size should be fairly obvious what this one is doing inside of the init method I now want to create a super Dunder init method although this one doesn't need any arguments next up I want to create a title for the app which I called responsive layout after that I want to get the geometry of the app this one is going to set the starting size the starting size we are getting from the parameters this one is supposed to be a tuple with the width and the height this I have to convert into a string which I'm doing with an F string I want to get start size and the index number 0 that is going to be the width then I want to do the same thing so start size except now I'm getting the first Index this is going to be the height once I have that I want to run self dot main Loop to actually run the app and this should be all we need to get started I can now create an instance of this app so app is app for the starting size I went with 400 by 300. if a run is now we have a basic window what I now have to figure out is how to get the size of this window when we are resizing it for that we need a certain kind of event before the main loop I want to self dot bind an event and the event we're looking for don't forget the smaller and greater sign what we're looking for is called configure this kind of event triggers every time a widget resizes or changes the position just to illustrate what's happening let me pass in a Lambda function don't forget the event all I want to do is print the event if I run this now you can already see in the bottom we have an event that is called configure this event gives us the X and the Y position of the window along with the width and the height so for example you can see right now we have a width and a height of 400 by 300 the exact same thing we have passed into the app on top of that if I am resizing the window you can see we get a different kind of width and height and if I change the position of the window we get a different X and Y position but now we do have a tiny bit of a problem or at least something to be aware of if I'm resizing the window again you can see for the width we get lots of different numbers let me try to find a Breaking Point what you can see in here we are running a new event on every change of the window and I got a width of 977 988 999 1005. and in just a bit I want to run a function to build a new layout when we are passing a certain kind of threshold for example our threshold could be 1000 pixels which means if we go from 999 to 1005 we want to build a new layout but only at this point if we go from 977 to 988 I do not want to build a new layout which means we have to add some code that we only Built a new layout if we are crossing a certain kind of threshold all of the logic I have put into a separate class this class I called size node TEF fire there's no need for inheritance and in here I want to have a Dunder init method when itself as always then I want to have the window which is going to be the main window or in our case this app here finally I want to have what I called a size dictionary for now don't worry about it I'll explain in just a second first of all I want to get self.window as an attribute which means self.window is going to be window next up I want to turn the size dictionary into an attribute as well size dictionary itself is going to be size dictionary the basic idea is that this size dictionary is going to have key value pairs like any dictionary the key is going to be the minimum size of the layout and then the value is going to be the function that creates this kind of layout let me implement it right away actually we are going to create an instance of this class inside of the app we don't have to start in a variable so we can just create it straight away I want to have a size Notifier the window is going to be self and the size dictionary is going to be a dictionary I want to have for the key a minimum size which I set to 300. the associated value is going to be a method for example if our minimum width is 300 I want to create a small layout this one doesn't exist right now so let me create it right away I want to create a small layout we need self and nothing else for now so I don't get an error I'm going to add pass in here the code we are going to write inside of size Notifier really is just going to check if we are crossing this number and then we are going to call this function although to make this a bit more practical I want to have two different layouts at least for now I want to create one at 300 the small one and then I want to have at 600 pixels a medium layout this one doesn't exist right now as well meaning I have to create medium layout with self and pass that means inside of the size Notifier if I am printing self dot size dictionary I am getting a dictionary I don't need the window right now that has a key of 300 then a method and another key of 600 and then another method this is a good start but I do want to do one more thing and that is I want to order this dictionary right now it is already ordered we are starting from a small number like 300 and going to a large number to 600. however what we could have is a dictionary like this where we are starting with a larger number this would be a massive problem later on so I want to order this dictionary right away this is actually quite simple all I need is dictionary comprehension in here I want to create a new dictionary with key value pairs next up I need 4 and key and Val u in now I want the size dictionary and get the items with this we would essentially copy the dictionary that we already have so if I run this we can't see any difference to sort all of this I have to sort what is being returned from this items here this I can do very easily with this sorted function now if I run this we have a dictionary that is sorted we can get to the next part and that is going to be running this event here or binding it to the window this I want to do inside of the size Notifier I want to cut it out and paste it in here although for this to work I have to add self.window.bind because we want to check the size of the window not the size of this size Notifier which wouldn't work because it's not a widget and let me clean up the white space a tiny bit like so the reason why I want to have this event binding inside of the size Notifier is because I want to connect it to a method of this class the method I called check size or rather self dot check size and this is going to be just another method important here we need self and we need the event just to check if this is working I want to print the event this should already work if I run this now we can already see we have one event printed out if I resize the window we get lots of events so things are still working just fine we could already get started to create different kinds of layout I guess for now what I could do is I could print small layout for the small layout and I could print medium layout for the medium layout so we can see a bit better what's going on these two methods I can already call quite easily for example when I'm checking the size I could get self dot size dictionary with a key of 300 and then don't forget to call it if I run this now I get small layout anytime I am changing the layout this self.size by 300 is going to return this create small layout method the brackets afterwards are calling this method importantly here when we are passing the method into the other class we're not calling it we're just passing the method around which is totally fine to do what we now have to figure out is when to call which method so when do we call the 300 method and when do we call this 600 method for that we need a couple of things first of all I want to get the window width this one is really easy because all we need is event dot with next up I want to have a variable that tracks which sizes I have already checked I call this check size by default the value here is none also let me get rid of this function call here now the logic that I want to implement is for minimum size in self dot size dictionary I want to toggle through all of the minimum sizes inside of this size dictionary I can print right away what we're getting minimum size if I run this now we get 300 and 600. the way I am going to use this is I want to have the Delta size this is going to be the window with minus the minimum size and let me print right away what we're getting we get 100 and minus 200. what these numbers mean is by default this window has a width of 400 and we have two breaking points one at 300 let's say this is roughly here at 300 and we have another point at 600. let's say this one is broadly here 600 this Delta is going to give us either this distance here or this distance here which is going to be 100 and minus 200. this information is really useful because all I really have to know is what the smallest positive number is once a number goes negative I know my window size this point here doesn't reach the next minimum size but if the number is positive I am greater than the smaller minimum size so in this case we have two options 100 and negative 200. this negative 200 tells me that I haven't reached the next minimum size but since this number here is positive I know I have exceeded this minimum size which is this 300 here and since my window is 400 right now we're on this point we know we have reached this minimum size but not the next one all I really want to check is if Delta is greater or equal than zero if that is the case I want to get my checked size and set it to the minimum size since Delta is becoming negative once we don't reach the next minimum size this checked size here is going to give us the proper minimum size there's one thing we still have to do and that is I want to check if the checked size is different from self dot current minimum size this is an attribute that doesn't exist right now but I do want to create it let me copy it and inside of the init method I want to create a current minimum size by default this can be done it doesn't really matter what it is also this should be size inside of this if statement I want to set self dot current Min size to the checked size checked size like so this statement here is really important because this one actually tracks if we have a change in our minimum size which means in here we can now run self dot size dictionary and I can pass in self dot current minimum size and this I want to run now if I run this we get small layout and nothing happens if I just move it a tiny bit although if I move it a bit further we get medium layout and if I go back to small layout this is working really well however there's one problem already and that is if I go too far to the left we get none the reason for that is if our window is smaller than 300 pixels we are well trying to get the index on this dictionary of a number that doesn't exist as a consequence this one here is going to throw an error the best way around that that I found is to set a minimum size that is as large as the smallest size here so in our case I want to set the minimum size of the window to 300. that way we can never have a smaller layout than the smallest layout that is quite easily added here I'm going to do this inside of the init method I want to get a minimum height and I want to get a minimum width the minimum width is actually the easier bit for the simple reason that I already have myself dot size dictionary and I only want to get the first key from it for that I have to turn the entire dictionary into a list on this list I want to get the first item that is literally all we need so next up we have to work on the height for the height I simply want to copy what we are getting when we are creating the app in my case that is going to be 300. one way to get that is first of all I want to get the window or to be a bit more consistent self.window although in this case window and sales.window refer to the same object it doesn't matter which one you choose this one has a method called W info underscore with this is a method so don't forget to call it although let me print it right away minimum height and well let's see what we get we are getting one and that is going to be a bit weird we know that the minimum height of the app when we are starting it is going to be 300. so why is this minimum height going to give us one that is very strange the reason for that is that tickinder can be a tiny bit weird so what it creates and when it places a layout there's a tiny bit of delay at this point here when we are creating the size Notifier we have created the widgets but we haven't placed them properly yet as a consequence the size of the app is going to be 1. to account for that we have to call Self dot update this is going to put everything in place and afterwards you can call the W info.wiff or hide and then you get the proper numbers so if I run this now we are getting an error because size Notifier doesn't have an update method instead what we need is self dot window dot update now if I run this we are getting 400. and I just realized that is the width not the height I typed in with this should be 8 like so now if I run this we get 300 that is much better ah sorry about that with that I can get rid of the print statement here and once I have these two numbers all I have to do is get self.window and set a minimum size the minimum size here is going to be the minimum width and the minimum height let's try this one now I have the app and I can move it to be wider and taller but this is the smallest it's going to get all we have to do now is create different kinds of layouts we're already calling them so inside of these two methods we have to create all of the layouts that we are going to use or while the two layouts that we want to use for this small layout I want to have self dot frame which is going to be ttk and frame the parent here or the master is going to be self inside of that I want to create four labels and to save you watching me type I'm going to copy paste things into here like so we have ttk labels four times they all have self.frame as the parent then I'm setting the text to be label one they all have a different kind of background and afterwards right away I'm calling the pack method Auto Pack Method All I'm really doing is I'm setting expand to True fill to both and then I'm giving them some padding there's literally nothing complicated going on right now the last thing that we have to do in here is self.frame and pack this entire frame for that I want to set expand to true and I want to set fill to both and now if I run this we can't see any kind of layout and this is a little bit weird because we are creating a layout here and we're also placing it on the window so this should be working now if I scroll down a tiny bit we are calling this method inside of this line the problem is if I add a print statement in here let me call it test I run this again nothing happens which means that this method here isn't being called and that's the problem right now the reason why it is not being called is because of this self.window update if I comment this one out run this again now we can see our app basically this self.window update causes this self.configure not to run I'm not exactly sure about the reason why but we can overcome it quite easily by moving this configure before the Windows update now if I run this there we go this configure is run before the update as a consequence it runs when we are starting the app and with that we can see our layout with that I can minimize size Notifier and now we can start working on the other layout for the medium layout I do need quite a few lines of code I think the best way for this is to Simply copy paste everything like so and then let me explain it really quick we are once again creating a frame although this frame has a grid layout which means we need a column configure and a row configure in this case we have two columns and two rows after that we are packing the frame on the main layout so we can see it and after that I am creating four different labels these labels have the same arguments that I used up here for the minimum layout and here you can see a minor problem in tkinter and that is well we have to create all of these widgets again there's no efficient way to copy widgets from one master to another right now we have a very simple layout so this isn't too much of a problem but if you had a more complex layout this would be quite a bit of work so well it's quite unfortunate but we have to work with it other than that we are using the grid layout method four times to place each label inside of one cell I'm also adding some padding but well that isn't very much and with that let's try the entire thing I have the small layout and if I increase the size of the app we get something very strange also a key error and well we have to cover a couple of things here the first one let me close this and get rid of the error message the first problem we have is that once we create the medium layout we are not discarding the small layout that is quite an easy thing to fix all I want to do for that is create self.frame as an attribute in the init method this is going to be ttk dot frame as well with self as the master this self.frame I want to pack right away self.frame dot pack we can set it to expand being true and fill being both as well although this one doesn't matter very much because as soon as I create I have a small or the medium layout I want self.frame dot pack underscore forget this I want to do for both the small and the medium layout essentially what is going to happen when we are starting the app we are creating a frame by default this Frame is going to be empty however once we are creating this small layout we are removing this Frame and then creating a new frame for the same variable or while attribute this is important because if we now create a medium layout we can use the same attribute to forget the current layout and then create a new one that way if we go back to a small layout like this one here we can once again forget the layout and create a new one with that we should be making some progress although it's still not ideal so now if I increase the size we get something very briefly like so but we're getting a key error none this problem happens down here in the size Notifier more specifically all the way down here there's some kind of problem with self.current size let's print it actually I want to print self.current minimum size now if I run this by default we get 300 that's a good start but if I resize the app we get something really strange we basically have an infinite Loop let me close all of this we had some kind of infinite Loop where we kept on getting 300 and 600 and the issue here is let me find it really quick this window.bind is being run whenever any kind of widget changes size or position as a consequence this is also going to be run when we are placing tdk.label also this is going to be run on self.frame the reason why this is a problem is because all of these labels here all of these labels and this Frame as well they also get the configure event and since they can change the size we basically end up with the problem that these widgets here or more specifically the grid layout method changes the size as a consequence of this change in size we are calling check size which in turn once again calls this function here which once again calls the grid method the result is we are creating an infinite Loop where nothing is going to work anymore and tick enter gets really confused fortunately this is really easy to fix inside of check size I want to add one more if statement this if statement is going to be the event.widget should be self dot window I only care about this event and only if that is the case I want to run all of this now if I run the code again I can resize the window and now this is working perfectly fine and we can change between different layouts this is looking good and with that we are done we have different kinds of layout and well we can move between them although I would really recommend you to go over this in your own time especially this kind of logic here can get a bit tricky but well the exercise is going to be I want you guys to create a third layout where all of the widgets are next to each other for that purpose I use grid but you could use whatever you want it doesn't really matter and this layout should appear once the window is wider than 1 200 pixels pause the video now and try to implement this yourself alrighty first of all we are going to need another layout method I'm going to call this one create large layout and win itself as always and here once again this is going to be quite a bit of typing so I'm going to just copy it for my notes it's going to look like this and it's fairly similar compared to create the medium layout we are forgetting the previous frame then we are creating a new frame with a column and a row although now we have four different columns and just one row this Frame we're packing right away and after that we're creating four labels that all once again have the same arguments after that we are using the grid layout method to place all of these labels although now we're using column 0 1 2 and 3 and row is always going to be zero now we have to figure out when to call this and for that I want to minimize all of these methods so things are a bit easier to see when we are calling size Notifier we are passing in a dictionary it's probably better to put this dictionary over multiple lines like so that way it's quite a bit easier to read to this I want to add another entry 1200 that's the minimum width and I want to create large layout and with that we are done I can now run this I have a small layout I have a medium layout and I get a large layout that's basically all we need I guess what you could do if you have your own app you could simply use the size Notifier and use it for your own purposes this one is quite reusable and works really well
Info
Channel: Atlas
Views: 11,829
Rating: undefined out of 5
Keywords:
Id: esGGoxy_JRQ
Channel Id: undefined
Length: 30min 55sec (1855 seconds)
Published: Sat Dec 24 2022
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.