IntroductionWelcome| 00:04 | Hello, and welcome to Code Clinic for C#. | | 00:08 | My name is David Gassner, and I'm excited to show you | | 00:11 | my solutions for the challenges proposed to the Code Clinic authors. | | 00:16 | In this monthly series, we'll explore creative and efficient | | 00:20 | ways to use C# to solve a range of problems. | | 00:24 | I'll describe some of the unique aspects of working | | 00:26 | with this language, and show you some tips on how | | 00:29 | to make your code run faster and be more | | 00:31 | reliable, as well as pointers on making it more readable. | | 00:36 | If you search the Lynda.com library for Code Clinic, | | 00:40 | you'll find additional courses by different authors using other languages. | | 00:45 | Each Code Clinic author solves the exact same set of problems. | | 00:49 | So you can compare different solutions, as well | | 00:51 | as the pros and cons of each language. | | 00:54 | If you're looking for tips on better coding, or trying to decide | | 00:57 | on your next new programming language, you're going to love Code Clinic. | | 01:01 | So let's get started with Code Clinic for C#. | | Collapse this transcript |
| What you should know before starting this course| 00:00 | This course is designed for software developers who either already use the C# | | 00:05 | programming language, or are curious about it | | 00:08 | and want to compare it to other languages. | | 00:12 | If you're brand new to C# and Visual | | 00:14 | Studio, you might be interested in these Lynda.com courses. | | 00:19 | For your first introduction to the language, | | 00:22 | check out Up and Running with C#. | | 00:24 | And to get into more details about | | 00:26 | programming with C#, look at C# Essential Training. | | 00:32 | To learn your way around Visual Studio, you can look at these courses. | | 00:37 | Visual Studio 2012 Essential Training, which is a complete tour of the product. | | 00:43 | And Visual Studio 2013 for Web Developers, which focuses on | | 00:48 | aspects of the product that are unique to web development. | | 00:53 | If you'd like to open the solutions I provide | | 00:55 | as part of the course, you'll need some software. | | 01:00 | You can either use one of the pay for licence versions of Visual Studio, for | | 01:04 | example Visual Studio Professional 2013, or you can use free software. | | 01:11 | To work with console applications, or | | 01:13 | desktop applications built with the wind forms | | 01:16 | or WPF frameworks, you can use Visual Studio Express 2013 for Windows Desktop. | | 01:24 | The sample code that I provide as part of the course should | | 01:27 | open fine in either the free or the paid versions of the product. | | 01:32 | So, let's get started with Code Clinic for C#. | | Collapse this transcript |
| Using the exercise files| 00:00 | Each of the problems in the Code Clinic Series is accompanied by exercise files. | | 00:06 | The exercise files are the finished source code for my solution to each problem. | | 00:12 | I've copied the exercise files to my desktop, but | | 00:15 | you can place them anywhere on your hard disk. | | 00:18 | For this challenge, I have two different applications. | | 00:22 | One named CollectLakeStats and one named LakeStatsWebService. | | 00:28 | If you have one of the pay for license versions of Visual Studio | | 00:33 | 2013, you can open these applications in that one version of Visual Studio. | | 00:38 | If you're using the free Visual Studio | | 00:40 | Express, you'll need Visual Studio 2013 for | | 00:44 | Windows Desktop to open the CollectLakeStates project | | 00:48 | and you'll need the web version for LakeStatsWebService. | | 00:52 | I'm using Visual Studio Professional, and I'll show | | 00:56 | you how to open each of these solutions. | | 00:59 | You can either open the solutions from Windows Explorer or | | 01:02 | file explorer, or you can open them from within Visual Studio. | | 01:08 | In file explorer, I'll go to | | 01:10 | the CollectLakeStats folder and double-click collectlakestats.sln. | | 01:16 | And that opens a new instance of Visual Studio. | | 01:20 | And there's all the files and directories in my solution. | | 01:28 | You can do the same thing with the web service. | | 01:31 | Open the folder, and look for the solution file. | | 01:35 | Double-click, and it should open in a compatible version of Visual Studio. | | 01:42 | If you prefer, you can open these files from within Visual Studio. | | 01:47 | I'll select File > Open > Project or | | 01:50 | Solution, go to the folder, and double-click the solution. | | 01:56 | Once again if you're using the express version of Visual Studio. | | 02:01 | Be sure you're using the right addition for each application. | | 02:05 | Once you have the solutions open, you can explore the code. | | 02:09 | And I'll show you how I've configured | | 02:11 | each application when I get into those demonstrations. | | Collapse this transcript |
| Getting the most from Code Clinic| 00:00 | If you really want to get the most out of Code Clinic, | | 00:03 | create your own solution to the problem before you watch my solution. | | 00:07 | If you spent prior time working on the question, you'll have a better idea of | | 00:12 | some of the difficult parts and be better | | 00:14 | able to understand why I've made certain choices. | | 00:18 | Finally, I've included the source code for my solution in the exercise files. | | 00:23 | I'm assuming you already have the necessary | | 00:25 | tools installed to be able to edit | | 00:27 | and compile the code, and I'll describe what you need in the next movie. | | 00:33 | Code Clinic is a unique opportunity to look | | 00:36 | over my shoulder as I actually solve coding problems. | | 00:40 | Keep in mind that my solution isn't the only correct solution. | | 00:44 | There are always a multitude of ways to solve a problem, but by watching | | 00:49 | how I approach the task, you can learn how to become a better programmer yourself. | | Collapse this transcript |
|
|
Problem One: Statistical AnalysisIntroducing Lake Pend Oreille| 00:06 | Hello and welcome to Code Clinic. | | 00:08 | My name is David Gasner. | | 00:11 | Code Clinic is a monthly course, where a unique | | 00:13 | problem is introduced to a collection of lynda.com authors. | | 00:17 | In response, each author will create a | | 00:19 | solution using their programing language of choice. | | 00:23 | You can learn several things from Code Clinic. | | 00:25 | Different approaches to solving a problem, the pros and cons of different | | 00:29 | languages, and some tips and tricks, | | 00:32 | to incorporate into your own coding practices. | | 00:36 | This month, we'll work on a problem in | | 00:38 | statistical analysis, and to some extent, handling big data. | | 00:43 | Its common to use a computer, to | | 00:44 | manipulate and summarize large amounts of information. | | 00:48 | Providing important insights on how to improve or handle a situation. | | 00:54 | In this problem, we'll use weather data collected by | | 00:57 | the U.S. Navy, from Lake Pend Oreille in Northern Idaho. | | 01:02 | Lake Pend Oreille is the fifth deepest freshwater lake in the United States. | | 01:07 | So deep in fact, that the U.S. Navy uses it to test submarines. | | 01:12 | As part of that testing, the U.S. | | 01:14 | Navy compiles an exhaustive list of weather statistics. | | 01:18 | Wind speed, air temperature, barometric pressure and so on. | | 01:24 | You can browse this data, by pointing your web browser at http://lpo.dt.navy.mil. | | 01:29 | You'll find several weather summaries, a webcam, and the raw | | 01:35 | data they collect every five minutes, archived as, standard text files. | | 01:41 | For anyone living or working on Lake Pend Oreille. | | 01:44 | Weather statistics are an important part of everyday life. | | 01:48 | Average wind speed can be very different than median wind speed. | | 01:52 | Especially if you are on a small boat in the middle of the lake. | | 01:56 | In this challenge, each of our authors will use their favorite language, to | | 02:00 | calculate the mean and median of the | | 02:03 | wind speed, air temperature, and barometric pressure. | | 02:07 | Recorded at the Deep Moore station for a given range of dates. | | 02:11 | First, let's briefly review mean and median. | | 02:15 | These are both statistics. | | 02:17 | To explain statistics, we need a set of numbers. | | 02:21 | How about 14 readings for wind gust at | | 02:25 | Deep More weather station on January 1st 2014. | | 02:30 | You can see the data at this web page. | | 02:33 | The first column in the data, is the day the wind gust was recorded. | | 02:37 | The second column is the time it was recorded, and | | 02:40 | the third column, is the wind gust in miles per hour. | | 02:45 | The mean, is also known as the average. | | 02:49 | To calculate the mean of a range of numbers, simply add | | 02:52 | the values in the set, then divide by the number of values. | | 02:56 | In this example, we add 14, plus 14, plus 11 and so on. | | 03:02 | Then divide the sum by 14. | | 03:05 | The count, of the numbers in the set. | | 03:07 | In this case, the mean is equal to nine. | | 03:11 | The median, is the number halfway between all | | 03:14 | the values in a sorted range of values. | | 03:17 | Think of the median as in the median strip of the road. | | 03:21 | It always marks the center of the road. | | 03:24 | To calculate the median, first sort the numbers from lowest to highest. | | 03:29 | If there is an odd number of values, then just take the middle number. | | 03:34 | If there's an even number of values, then | | 03:36 | calculate the average of the central two numbers. | | 03:40 | In this case, there's an even number of values so we sort, | | 03:45 | then take the average of the middle two values, eight and 11. | | 03:49 | The median is 9.5. | | 03:52 | Now, some authors have chosen to implement a web service, allowing other websites or | | 03:58 | other web service consumers, to access the | | 04:01 | new weather statistics created by the author's program. | | 04:05 | This is typically done by creating a RESTful API, which, | | 04:09 | when called, returns information in a format known as JSON. | | 04:14 | For a detailed description of JSON and its uses, please look | | 04:18 | at JavaScript and JSON by Ray Villalobos, in the lynda.com library. | | 04:24 | In our case, I've hosted the finished web service, on a Microsoft Azure website. | | 04:31 | I've seeded the service with cached data | | 04:33 | from January 1st through February 28th 2014. | | 04:38 | If I make this HTTP call, I get back the data in JSON format. | | 04:45 | Notice that my start date and my end date are a part of the URL. | | 04:50 | I'll describe later on how you can call this web service in a variety of ways. | | 04:56 | When I make the web service call, I expect to receive the | | 04:58 | mean and median for windSpeed, airTemperature | | 05:00 | and barometricPressure for the range of dates. | | 05:06 | If I wanted the range of dates from January 1st to | | 05:09 | January 7th, I would simply change that value in the URL. | | 05:14 | And I would get back a different calculation. | | 05:17 | And when formatted in JSON, it looks like this. | | 05:22 | So there's our first challenge. | | 05:24 | Pulls statistics from a data set available online. | | 05:28 | And then calculate them, creating aggregate values. | | 05:32 | Perhaps you want to pause and create a solution of your own. | | 05:35 | How would you solve the problem? | | 05:38 | In the following movies, I'll show you how I solved the challenge. | | Collapse this transcript |
| Overview of my solution| 00:00 | Before I describe the details of how I solved this programming problem | | 00:05 | I'm going to describe some of the thinking that went into it. | | 00:08 | First lets start with the specifications. | | 00:11 | What my application and web service need to do. | | 00:15 | First I'm going to be collecting data from a remote server over its web service. | | 00:20 | The web service will return data one page at a | | 00:24 | time for each combination of a date and a data type. | | 00:28 | That can't be changed. | | 00:30 | That's the nature of the remote web service. | | 00:34 | Then I'm going to aggregate that data. | | 00:36 | I'm going to calculate the median and averages | | 00:39 | I've described and publish that information through my own | | 00:42 | web service and the request from the client | | 00:46 | is that the data be published in JSON format. | | 00:50 | Now there are a couple of ways to solve this. | | 00:53 | One approach might be described as Synchronous Processing. | | 00:58 | There are three components in this system. | | 01:02 | There is a Web Service Client, it could be a web browser, a mobile application | | 01:07 | or any other application that can consume a JSON formatted web service. | | 01:14 | Then there is My Web Service that is providing that JSON formatted data. | | 01:19 | And then there's the real data source | | 01:21 | which here I'm calling the Lake Web Service. | | 01:25 | In synchronous processing, the Web Service Client would make a request to My | | 01:29 | Web Service, and it would then make a request to the Lake Web Service. | | 01:34 | The data would come back and then it would | | 01:37 | be aggregated and return to the Web Service Client. | | 01:42 | And this strategy might work fine as long as we | | 01:45 | limit the amount of data that's going to be processed | | 01:48 | and as long as all components are working correctly at | | 01:51 | all times, but here's where we can get into trouble. | | 01:56 | The client has told us that they want to be able to | | 01:58 | request any range of dates from the web service that we're creating. | | 02:04 | And the problem is in the structure of the data on the actual web service. | | 02:08 | And that can create a problem of scale. | | 02:11 | The problem is in this step. | | 02:14 | Let's say for example that the client request array of seven days. | | 02:20 | The data is organize in the link web service, one file per day per data type. | | 02:27 | And a little bit of math with three data types tells us | | 02:30 | that that results in 21 separate requests for just one week of data. | | 02:37 | If the client request a larger range of | | 02:39 | days, that creates a larger number of requests. | | 02:43 | Each new day adds three new requests, and if they ask for a year of data, the | | 02:49 | web service might be sitting there for a long time, using up threads on the server. | | 02:55 | And hammering that back end web service and it's a web | | 02:59 | service that we don't really have permission to treat in that way. | | 03:02 | So another approach is to put together a system that I'm calling asynchronous | | 03:07 | processing and separate the task into | | 03:11 | two steps, Data Collection and Data Publishing. | | 03:16 | In this architecture, I'm going to start with a desktop app. | | 03:20 | It'll be a console app with no real user interface. | | 03:24 | It could just as easily be built with | | 03:26 | WPF or Windows Presentation Foundation or Windows Forms. | | 03:31 | But I'm going to keep it simple. | | 03:33 | And the job of this application will be | | 03:35 | to collect the data from the Lake Web Service. | | 03:39 | For each combination of date and data type it'll make a request and get the | | 03:44 | data back, and then it'll save the data locally in a cache of some kind. | | 03:50 | The cache could be a relational database, but all I'm going to do is | | 03:54 | take those pages from the lake web service and save them locally on disk. | | 03:59 | So that they're available instantly to my web service. | | 04:03 | Now here are the other two components, the web client and my web service. | | 04:09 | And when the web client makes a request to | | 04:11 | my web service, I'll get the data from the cache. | | 04:15 | And aggregate it and return it to the client. | | 04:19 | Now because the data is stored locally, responses should be nearly instantaneous. | | 04:25 | And I won't be depending on the lake web service to be up and running. | | 04:28 | And I went constantly hammering looking for data. | | 04:33 | Here are some assumptions about my system. | | 04:36 | The web service will only be able to provide pre-cached data. | | 04:41 | When request comes in from the web client. | | 04:43 | It'll look for those files in the cached directory. | | 04:47 | If they're all available it'll do the | | 04:49 | aggregation and return the result in JSON format. | | 04:54 | But if anything is missing, it'll just return an error. | | 04:58 | The data collection process, which will be a console application built | | 05:03 | with .net will run in a scheduled task once a day. | | 05:07 | So each day, say at two in the morning, it'll go | | 05:11 | out to that Lake Web service and get the most recent data. | | 05:15 | And you could also set it up, so that it looks for | | 05:18 | any holes in it's current data and sees if it can fill them. | | 05:22 | The data will be cached as simple text files on disk. | | 05:26 | As I described earlier, I could put the | | 05:29 | data into a relational database such as SQL | | 05:32 | server, but by using simple text files, I | | 05:35 | eliminate a whole bunch of labors of complexity. | | 05:39 | And just like the original web service does, I'll store the | | 05:42 | data in one file for each combination of date and data type. | | 05:49 | The applications will be built as a desktop | | 05:52 | console application that will do the data collection. | | 05:55 | And if I want I can set it up as a scheduled task. | | 05:59 | And for publication I'll use an ASP.NET web service. | | 06:04 | Alternatively, I could also use Windows Communication Foundation, or WCF, but the | | 06:10 | ASP.NET web service will be simpler, take less code, and be easier to update. | | 06:17 | And finally, of course, both applications will be coded in C#. | | 06:23 | They will share a certain amount of code in common, | | 06:26 | but for this demonstration I'm keeping each application completely separate. | | 06:31 | As I tune and re-factor my applications, I might find areas where they | | 06:35 | can share libraries and where I can make my application more refined and elegant. | | 06:41 | But for these steps, each application will be in it's own | | 06:44 | Visual Studio Project, one, a website and the other a complete solution. | | 06:51 | So let's take a look at the code that I used | | 06:53 | to build the desktop console application and the ASP.NET web service. | | Collapse this transcript |
| Retrieving data from the lake's web service| 00:00 | I'll begin my review of my solution to this problem | | 00:03 | by opening up one of the applications in Visual Studio. | | 00:07 | In the Exercise Files for this chapter, | | 00:10 | I'll go to the CollectLakeStates folder, and | | 00:13 | double-click to open the Solution file, and | | 00:16 | that opens the project in Visual Studio 2013. | | 00:20 | Now, before I run the application, I'll show you one important configuration. | | 00:26 | I'll go to the menu and choose Debug > Lake Stats Properties. | | 00:31 | In the Debug category, I've set the working directory | | 00:35 | to a directory named c colon backslash Lake Stats. | | 00:39 | This is where I'll be creating my executable, and | | 00:42 | it is also where I will be caching data locally. | | 00:46 | Before you run this application on your machine if you want | | 00:49 | to place this somewhere else on your hard disk, change it here. | | 00:54 | Then build the solution by choosing Build > Build Solution. | | 01:00 | And you should see a message in the | | 01:01 | output window indicating that they're weren't any failures. | | 01:06 | Now I'll go to a command window. | | 01:12 | I'll change to that \Lakestats folder and look at it's contents | | 01:18 | and I see the generated executable, LakeStats.exe and a bunch of other files. | | 01:25 | I'll run the application by typing in | | 01:28 | LakeStats.exe, and when I run the application without | | 01:32 | any arguments, I get back usage hints indicating | | 01:36 | how the application is supposed to be run. | | 01:39 | So I'll go ahead and run it that way. | | 01:42 | I'll call LakeStats. | | 01:44 | Then I'll pass in two dates, a start date and an end date. | | 01:50 | I'll set my start date as 2014-01-01, | | 01:55 | that's January 1st, and 2014-01-07. | | 02:02 | So I'm asking for a range of seven days. | | 02:06 | For each of the days, I get a | | 02:08 | message indicating that it's downloading from the remote website. | | 02:12 | And then it's calculating the mean and median for that day. | | 02:16 | Notice that the output isn't in JSON format. | | 02:19 | It's in object notation. | | 02:22 | This output is purely for debugging. | | 02:25 | It's verifying that it got the data from | | 02:27 | the remote website, and it's doing an initial calculation. | | 02:32 | Now, if I run the application a second time, you'll see that it goes much faster. | | 02:38 | And instead of the message downloading, I get a message from cache. | | 02:44 | And that's because the first time I ran the | | 02:46 | application, I created a subdirectory named cachedir, for cache directory. | | 02:53 | And in that directory, there are now | | 02:55 | three subdirectories named Air_Temp, Barometric_Press, and Wind-Speed. | | 03:03 | And if I go into those folders and list | | 03:05 | their contents I see one text file for each date. | | 03:10 | Notice the format of this date here, it uses underscores instead of dashes. | | 03:15 | And that mimics the way it's named in the remote web service. | | 03:20 | Finally, I'll list the contents of one of these text files and show | | 03:25 | that it has exactly the same structure as the original web service result. | | 03:31 | In face, all this application is doing is grabbing the results | | 03:35 | from the HTTP request and saving it locally in a text file. | | 03:41 | Once it's been cached locally on the hard disk, the web service will be able to | | 03:46 | get to this content and aggregate and display the results almost instantly. | | 03:52 | So that's how this application works. | | 03:54 | In the following movies I'll show you the code. | | Collapse this transcript |
| Running the console application| 00:00 | The console application is structured as four C# classes. | | 00:05 | The entry point is this class, program.cs. | | 00:10 | It has a main method, which marks it as the beginning point of the program. | | 00:14 | As the application starts up, it examines the | | 00:17 | arguments that are passed into the command line. | | 00:20 | It looks for either one or two arguments. | | 00:23 | If there's one argument, that's treated as both a starting and an ending date. | | 00:27 | And if there are two arguments, then those | | 00:29 | are treated as separate starting and ending dates. | | 00:32 | Either way, the values that are passed in are parsed as date time values. | | 00:38 | If no arguments or more than two arguments are passed in, then we hit the default | | 00:43 | case and we display the usage hints on | | 00:45 | the command line and return, exiting the program. | | 00:50 | Now, notice that all that code is wrapped in | | 00:52 | a try catch block, and if we run into an | | 00:56 | exception, then we output the type of the exception and | | 00:59 | the exception message, display the usage hints again, and return. | | 01:05 | I'll go to my command line. | | 01:07 | And type, LakeStats. | | 01:10 | And I'll pass in values that can't be parsed as daytime values. | | 01:15 | I get back the exception type. | | 01:17 | The message, I see the usage hints and the program stops. | | 01:24 | If we get past that point in the | | 01:25 | code, then we can start looking for other conditions. | | 01:30 | Here's the next condition starting at line 42. | | 01:32 | I'm comparing the starting and ending dates, making sure that they | | 01:37 | either match, or that the start date is before the end date. | | 01:41 | If the end date is before the start date, then I output an error and I return. | | 01:47 | If I get to line 49, then I'm | | 01:50 | ready to start getting data from the remote website. | | 01:55 | Here, I am going to use a class called FeedManager. | | 01:58 | This is another custom class that's a part of my application. | | 02:02 | I'm then looping through the days that have been requested. | | 02:06 | Using a while loop, each time through the while | | 02:10 | loop I'm adding one to the value of the date. | | 02:13 | Using the ad days method down at the bottom of the loop. | | 02:17 | Within the loop, I once again have a tried catch block. | | 02:20 | Notice that I'm outputting the information about the | | 02:23 | exception, but I'm not stopping the program now. | | 02:26 | I'm letting this be forgiving. | | 02:28 | So if I run into an exception, I'll still | | 02:31 | be able to process the rest of the request. | | 02:34 | For each date, I'm gathering the wind speed, the | | 02:38 | barometric pressure, and the air temperature from the remote website. | | 02:44 | Let's take a look at this method, GetData. | | 02:46 | That's a part of the FeedManager class. | | 02:50 | I'll select the method name and press F12, and | | 02:53 | that jumps to that class and to that method. | | 02:58 | Notice at the top of the class that there are | | 03:00 | three public constants, for wind speed, barometric pressure and air temperature. | | 03:06 | The strings for these constants match the | | 03:09 | names of the directories on the remote website. | | 03:12 | I'll be using those to build the URLs and make the requests to the website. | | 03:19 | Notice also that I've created an instance of | | 03:21 | a class called cash manager, that has all | | 03:24 | the code to manage where the data will be saved locally, I'll get to that later. | | 03:30 | Let's look at the GetData method. | | 03:33 | Within the method, I'm declaring a list of double values. | | 03:37 | This is where I'll store the raw numbers for the | | 03:40 | current date and the current data type, such as barometric pressure. | | 03:46 | I'm also creating an instance of a class called feed result. | | 03:50 | This class has three fields, named, mean, median and count. | | 03:54 | And a two string method, that outputs the values with appropriate labels. | | 04:02 | For each of the three data types, I'm creating an instance of that FeedResult | | 04:07 | class, populating it with the data from the remote website, and returning it here. | | 04:14 | Going back once again to the GetData method. | | 04:17 | This is where I create the instance of the FeedResult class, | | 04:22 | and then I try to get the data from the local cache. | | 04:26 | Don't worry about that method call, I'll come back to it later. | | 04:30 | But here, on line 36, if I get back null from get data from cache, then I decide I | | 04:37 | need to go get the data from the remote | | 04:39 | website, and I call this method, get data from URL. | | 04:45 | Notice, this is where I'm outputting the label downloading. | | 04:50 | The GetData from URL method does the following work. | | 04:55 | It constructs the URL for the current request. | | 04:58 | Starting with the base URL, that's the location of all the web services. | | 05:03 | And then, appending the year as a directory. | | 05:06 | The complete date as a directory and the data type. | | 05:11 | Now, I have a URL from which I can request the data. | | 05:15 | Within the try catch block, I'm using a number of classes from | | 05:18 | the .Net framework including web request, stream, stream writer, and stream reading. | | 05:26 | I'm then looping through the response, and I'm saving that data as a list of strings. | | 05:34 | Then at line 115, I'm writing that data to the cache. | | 05:38 | Again, I'll come back to the cache manager later. | | 05:42 | If I get past the catch block, then I return the lines to the calling scope. | | 05:48 | Notice in the finally block, I'm closing the response object. | | 05:52 | That's very important to prevent any memory loss. | | 05:56 | I'll go back to the program class, and show that | | 06:00 | this is where I'm outputting the results to the console. | | 06:04 | The final method in this class is called | | 06:06 | DisplayUsageHints, and this is the method I'm using | | 06:09 | to output information to the console, if the | | 06:12 | user doesn't pass in the correct of arguments. | | 06:16 | So that's the outer structure of the application. | | 06:20 | There are two more critical parts. | | 06:22 | Doing the calculation of the aggregate values, and managing the local data cache. | | 06:27 | And I'll describe those steps in detail in the following movies | | Collapse this transcript |
| Saving the data to a local cache| 00:00 | The Cache Manager class is a critical part of my application. | | 00:04 | Versions of this class will be used in both the console application that collects | | 00:08 | the data from the lake's web site, and the web service that uses the data. | | 00:15 | Here is the version of the class that I'm using in the console application. | | 00:19 | It’s called CacheManager.CS. | | 00:22 | First of all, there’s a constant here that points to the | | 00:26 | location of the directory where I’m going to save the data. | | 00:30 | Right now it’s hard coded. | | 00:32 | If you wanted the data to go somewhere | | 00:33 | else on your system, you could change this string. | | 00:39 | First, there is a method called setDataDirectory, | | 00:41 | which accepts a single argument called data type. | | 00:45 | It verifies that the cache directory and if it doesn't, it creates it. | | 00:50 | Then, it creates a string called dataDirectory | | 00:53 | that appends the data type and a | | 00:55 | trailing backslash and it guarantees that, that | | 00:58 | directory exists, creating it if it doesn't. | | 01:02 | And finally, a call to the method, SetCurrentDirectory from the dot | | 01:06 | net frame works directory class sets that as the current working directory. | | 01:12 | Now this method, setDataDirectory, is called from | | 01:14 | one place in this class called getFileName. | | 01:18 | When I call the get file name method, I'm | | 01:20 | passing into current date and a data type, a string. | | 01:24 | And then I'm calling this method setDataDirectory. | | 01:28 | Then I'm building the location and name of the file. | | 01:31 | Starting with the current directory, and then | | 01:33 | appending a backslash, and then the current | | 01:36 | date in this specific format, that matches the format used on the remote website. | | 01:43 | And finally upending the .txt file extension. | | 01:47 | Because, that's how the data is stored by the remote web service. | | 01:51 | This getFileName method is called a couple of times. | | 01:54 | First, in right file to cache. | | 01:57 | This method receives three arguments. | | 02:00 | The content, which is the content retrieved from the remote site, the | | 02:04 | date time, which is the current date and the data type, a string. | | 02:09 | It gets the file name and then uses the stream | | 02:12 | writer class to write that file to the local hard disk. | | 02:17 | The getDataFromCache method does something similar. | | 02:20 | Once again it gets the fileName based on Date and | | 02:23 | dataType and if the file doesn't exist it returns null. | | 02:28 | But then if the file does exist it reads the file | | 02:32 | into memory using the file stream, file, and stream reader classes. | | 02:37 | And returns the list of strings. | | 02:42 | The feed manager class is where this cacheManager class is being used. | | 02:46 | Here's the call to writefileToCache, and here's the call to getDataFromCache. | | 02:54 | So that's how the cacheManager is working. | | 02:58 | Now if you preferred, you could change your caching strategy to use | | 03:02 | a relational database, such as SQL server or a no SQL database. | | 03:07 | All the code to manage the cache is in the cacheManager class. | | 03:12 | So theoretically, you could make those changes in a | | 03:15 | local class without changing the rest of your API. | | 03:18 | So that`s how I`m managing the local data. | | 03:22 | And now, in the next movie, I`ll show you | | 03:24 | how I`m aggregating the values and showing the results. | | Collapse this transcript |
| Calculating the aggregated statistics| 00:00 | So far I've described two major components of the Console | | 00:03 | application, the entry point where it gets the arguments or requests | | 00:08 | from the user, and the local caching strategy which is currently | | 00:12 | saving the data as simple files on the local hard disk. | | 00:17 | Now I'll describe how the aggregate calculations are being done. | | 00:21 | Which in the Console application is calculating all the values for | | 00:25 | a single date and a single data type at a time. | | 00:29 | This code is in the FeedManager class. | | 00:32 | If scroll down to the get data method down to about | | 00:34 | line 57, you'll see that we're working with an object called values. | | 00:40 | The values object is declared up here. | | 00:44 | It's a list of double values. | | 00:46 | It's the raw numbers that come from the text files, regardless of whether we | | 00:51 | get them in this call from the remote website or from the local data cache. | | 00:58 | Here's where the values list is being populated, starting at line 47. | | 01:03 | We're looping through lines the strings in the list, and then I"m parsing each line. | | 01:09 | Getting the third item in the list and adding | | 01:12 | that value to the list as a double value. | | 01:15 | The conversion from string to double is happening right here on line 52. | | 01:21 | Notice this option in the call to the strings split class. | | 01:26 | I'm passing it an option called | | 01:28 | StringSplitOption.RemoveEmptyEntries, and that's because some of the | | 01:33 | files have values in them that are just commas separated from each other. | | 01:38 | To get consistency in parsing, I had to make this little adjustment. | | 01:43 | Once I get past this code, I know how many items I have in the list. | | 01:47 | And I save that to my instance of the feed result class. | | 01:52 | Then, I get the mean value. | | 01:55 | The list class makes this incredibly easy because it has a method named Average. | | 02:01 | it does all the rounding for me, and right now I'm returning the raw value. | | 02:07 | If I want to round it to two values, I could do this. | | 02:10 | I could wrap values.average in math.round, and then pass in a value of two. | | 02:20 | And that would mean round it to a value of two decimals. | | 02:25 | Next, I'm going to calculate the median value. | | 02:28 | This takes a little bit more work, because there isn't | | 02:31 | a method in the list interface that does it for me. | | 02:34 | As I described in the introduction, here's how you do a median calculation. | | 02:40 | First sort the values in ascending order and I am doing | | 02:43 | that with a simple call to the list interfaces sort method. | | 02:48 | Then, find the median index. | | 02:51 | The point that's midway between the highest and the lowest values. | | 02:55 | Notice I am casting this return value as a double. | | 02:58 | That guarantees that I won't lose any fractional values as I do the calculation. | | 03:04 | Then I'm checking to see whether I have a fractional or whole value. | | 03:08 | This expression at line 70, truncates the index | | 03:12 | and then compares it to the untruncated index. | | 03:16 | If they match each other, then I know I had an odd number | | 03:19 | of entries and I got back an index that was a whole number. | | 03:23 | And I can simply take the value from that index, notice that to | | 03:26 | deal with it as an index, I have to convert it to an integer. | | 03:30 | And then take the value from the list at that position and save it as the median. | | 03:36 | If I have an even number of values, then my index will be fractional. | | 03:41 | And here's how I'm doing that calculation. | | 03:44 | Once again I'm the indexes integer, and I'm saving it as the low index. | | 03:49 | Then I'm adding one to get the high index. | | 03:52 | And then taking the average of those two values and saving that as the median. | | 03:57 | And then returning the result. | | 04:00 | The result is an instance of the feed result class. | | 04:05 | And the program receives those values and simply outputs them to the console. | | 04:11 | And that's the complete Console application. | | 04:14 | Now at this point, we're not | | 04:15 | quite matching to specifications because first of | | 04:18 | all we're only doing these aggregate calculations for one day at a time. | | 04:23 | And the spec said we needed to handle a range of dates. | | 04:26 | But I'm going to do that in the context of the web service. | | 04:30 | In the web service, the user will be able to pass in a range | | 04:33 | of dates and get the aggregate for all of those dates at the same time. | | 04:38 | The job of the console application, is simply to get the data from the remote | | 04:42 | website, save it locally and then report to the console what happened. | | Collapse this transcript |
| Configuring the ASP.NET web service| 00:00 | The second critical component of my solution is the web service. | | 00:04 | Which I've built as an ASP dot web net service. | | 00:08 | Alternatively I could have used | | 00:10 | the windows communications foundations frame work. | | 00:13 | But that takes a good bit more meta data and for my purposes a | | 00:17 | simple ASP dot net web service was easier to create and easier to maintain. | | 00:24 | I'll open the code from Visual Studio by selecting File > Open | | 00:29 | > Project or Solution and then from the Exercise Files folder I'll | | 00:34 | go down to the LakeStatsWebService folder and I'll open the solution file. | | 00:40 | Depending on where you've placed your exercise files you | | 00:43 | might see an error occur when you open the project. | | 00:46 | If you do, close the project and then try opening it this way. | | 00:50 | Select File > Open > Website, navigate to the project folder and click Open. | | 01:00 | You'll see that the properties window in the bottom right looks a bit different. | | 01:04 | It shows website properties instead of project | | 01:07 | properties but it's the same visual studio project. | | 01:12 | Just like the Console application, this application has four different classes. | | 01:18 | But there is also a critical entry point and ASMX file, and this is the file that | | 01:24 | the web service consumer will actually refer to when they make an HTTP request. | | 01:30 | It has a web service annotation and a code behind reference pointing to this file. | | 01:36 | LakeStatsService.cs. | | 01:40 | It also has a class reference, pointing to | | 01:43 | a Class LakeStatsService, that's defined in this cs file. | | 01:50 | Now, before I get in to the actual | | 01:52 | code, I'll describe some critical configurations that I've made. | | 01:57 | First, I've added a web service annotation with my own custom namespace. | | 02:03 | Then, I've added a web service binding and the method that | | 02:07 | encapsulates my web service, which is | | 02:09 | named GetLakeStats, has two critical annotations. | | 02:14 | Web method, which means that it can be called over the web. | | 02:17 | And script method which says that it can be called as | | 02:20 | a get request and that the response will be in Json format. | | 02:26 | But there's one other critical configuration that I | | 02:28 | had to add and that's in the web.config file. | | 02:33 | In this file, I added protocol definitions. | | 02:37 | One for HttpPost, and one for HttpGet. | | 02:41 | And this means that my web service can be | | 02:43 | called either with a Get request, or a Post request. | | 02:48 | Now as I indicated in the introduction to this chapter, I've | | 02:52 | posted my web service on a website that's hosted at Microsoft Azure. | | 02:58 | And if I call the web service, I'll see that I'm | | 03:01 | referring to the Asmx file here, as part of the URL. | | 03:05 | And the name of the method, getLakeStats here. | | 03:08 | And then everything else is the arguments, the values that I'm passing in. | | 03:15 | Now, because I'm using an Asp.net web | | 03:17 | service, I get some automatic free testing capabilities. | | 03:22 | I'm going to trim off everything after the | | 03:24 | .Asmx and then press enter to make that request. | | 03:29 | And now I get a webpage that shows the available operations or methods. | | 03:35 | I'll click into GetLakeStats. | | 03:37 | This screen lets me make requests using a Post request. | | 03:43 | I'll pass in dates that I know have been locally cast at the web service. | | 03:46 | And | | 03:49 | now invoke the web service. | | 03:52 | And I get back exactly the sort of response I did with a Get request. | | 03:58 | I can also scroll down on this page and see documentation | | 04:02 | for calling the web service with Soap and with a GET request. | | 04:08 | But my focus is on the GET request and so I'm going to stick with this sort of call | | 04:13 | where I'm simply passing in the starting and ending values as a part of the URL. | | 04:20 | So, that's the basic architecture of the web service. | | 04:23 | I've configured it to respond to both POST and GET requests. | | 04:28 | I've added the appropriate annotations to the method | | 04:31 | that I'm exposing as a web service operation. | | 04:34 | And now I'm ready to add the actual | | 04:36 | code, that's receiving and responding to the request. | | 04:40 | And I'll describe that code in the following movies. | | Collapse this transcript |
| Returning data in JSON format| 00:00 | For testing purposes, I created my GetLakeStats method so that if | | 00:05 | blank values were passed in, then I would cede the service with default values. | | 00:11 | And that's the code you see in the beginning of the method GetLakeStats. | | 00:13 | Because I'm going to be calling this directly from a browser, this | | 00:19 | bit of code won't actually be used, but it was useful during testing. | | 00:25 | The next bit of code declares a couple | | 00:27 | of date time values named startDate and endDate. | | 00:31 | And at lines 39 and 40, I'm taking the arguments that | | 00:34 | are passed into this method and parsing them into DateTime values. | | 00:39 | This code and the following code is all wrapped in tricatch blocks. | | 00:44 | If I get into the catch block, notice I'm taking | | 00:47 | the exception and passing it into this method, called GetExceptionAsJson. | | 00:54 | And this method creates a dictionary object. | | 00:57 | It passes in a single item into the dictionary with | | 01:00 | a name of error and a value of the exceptions message. | | 01:05 | And then serializes that using a class called JavaScriptSerialize. | | 01:11 | Now, let's watch what happens if I pass in values that can't be parsed. | | 01:16 | I'll set my start value to XYZ. | | 01:20 | And there's the result. | | 01:21 | I get back a valid Java Script object, which can be parsed by a JSON parser. | | 01:28 | The name of the object is Error. | | 01:30 | And the message is the message that's exposed by the exception. | | 01:35 | So that's some trivial error handling that I was able to | | 01:38 | add to the web service to deal with the most common issue. | | 01:42 | Here is another common issue, a value that can't be parsed as a valid date. | | 01:53 | Instead of 131, our passing 2-31 and we all know that February only has | | 02:00 | 28 days and I get back the error, string was not recognized as a valid date time. | | 02:07 | if I get past that initial condition. | | 02:09 | And I have valid date time values. | | 02:12 | The next step, is to setup a place to store my results. | | 02:16 | I'm creating another dictionary. | | 02:19 | And for this dictionary, the names will be strings. | | 02:22 | But the items values will be instances, of the FeedResult class. | | 02:28 | Let's take a look at feed result for the web service. | | 02:31 | It's a little different than the version I created for the console application. | | 02:36 | It has two fields named Mean and Median. | | 02:40 | And then the two string method returns it's value as JSON. | | 02:45 | Now, once again, I could have used a whole dictionary and a serializer, but | | 02:50 | this bit of code was so simple that I decided to hand code it. | | 02:54 | And part of that is to show | | 02:55 | you various approaches to creating JSON formatted data. | | 03:01 | Going back to my class, I'm getting those | | 03:04 | feed result objects by calling the method, GetData. | | 03:08 | Which is a member of the class, FeedManager. | | 03:12 | Just as I did with the console | | 03:13 | application, I'm calling the GetData method three times. | | 03:17 | Once for the Wind Speed, once for the | | 03:20 | Air Temperature, and once for the Barometric Pressure. | | 03:24 | And I’m taking those objects and adding them to my dictionary. | | 03:28 | Then, in the coded lines 52 and 53, I’m once again using the | | 03:33 | Java Script Serializer class, and serializing | | 03:36 | the data as a Java Script object. | | 03:39 | And then I’m returning that data to the requester using | | 03:43 | Context.Response.Write, a standard method of the asp.net framework. | | 03:50 | And that's how the data is coming back. | | 03:53 | I'll once again call the web service with valid dates, | | 03:56 | and each of these objects represents an item in the dictionary. | | 04:01 | The three items taken together are passed back and serialized. | | 04:06 | And that's how i'm getting my JSON formatted content. | | 04:09 | There's one other thing to explain here and | | 04:12 | that's how I'm aggregating a range of dates, | | 04:14 | rather than one date at a time and I'll describe that code in the following movie. | | Collapse this transcript |
| Calculating statistics for a range of dates| 00:00 | So far I've described how I configured my web service, and how I wrote the code | | 00:05 | to get the data and then format it as JSON to send back to the requester. | | 00:11 | Now I'll describe how I'm handling the aggregation of | | 00:14 | values for more than one date at a time. | | 00:17 | And for this, I'll go to the GetData method of the feed manager class. | | 00:23 | Just as with the Console application, I have these | | 00:25 | three constants, Wind Speed, Barometric Pressure and Air Temperature. | | 00:32 | And each time the GetData method is called, I'm using the start date, the | | 00:36 | end date, and the data type to go get the data from the cache. | | 00:41 | Now this is a different condition than the console application. | | 00:45 | Remember in my introduction I said I didn't want to do all | | 00:48 | these operations synchronously in the web service, so in this code I've established | | 00:53 | a rule that says if any particular combination of date and data | | 00:58 | type isn't represented in my local data cache, I won't try to respond. | | 01:03 | Instead, I'll just throw an exception. | | 01:07 | And because the code that you see starting at line | | 01:09 | 33 isn't wrapped in a try catch block, the exception | | 01:13 | will bubble up to the get lake stats method of | | 01:16 | the primary service class, and it'll be exposed to the requester. | | 01:21 | Let's see what happens in that condition. | | 01:24 | I'll go back to the browser. | | 01:26 | And this time, I'll pass in a value of 4-1-2014. | | 01:34 | And this time, I get an exception. | | 01:36 | File not found: Wind_Speed, 3/22/2014. | | 01:41 | After a bit of investigation. | | 01:43 | I figured out, that that data wasn't | | 01:46 | actually even available, on the remote web service. | | 01:49 | And so that's a condition, that my application has to be able to deal with. | | 01:54 | My rules say, that if the user requests a particular range of dates, I either have | | 02:00 | to aggregate the values from all those dates, | | 02:03 | or tell the requester, I can't do it. | | 02:05 | And tell them, why. | | 02:07 | And so that's why this exception is appearing. | | 02:10 | I don't have the data for that date, so | | 02:12 | I'm not going to try to aggregate the values. | | 02:15 | If I tried, it would be inaccurate, | | 02:18 | because I don't have all the available data. | | 02:21 | So back to the code. | | 02:23 | In this code, I'm starting with a range of dates, a starting and an ending | | 02:28 | date, and I'm getting the lines of text from the method get data from cache. | | 02:33 | This is in the cache manager class for the web service. | | 02:37 | In this version of the cache manager I'm looping through all the dates. | | 02:42 | For each date I'm getting the file name | | 02:44 | for the current date and the current data type. | | 02:47 | If the file doesn't exist, this is where that exception is being thrown. | | 02:53 | I'm creating the error message file not found, telling the requestor | | 02:57 | what data type and date weren't available, and throwing the exception. | | 03:02 | But if I do have the file available in the local data cache, I'm using a file | | 03:07 | stream and a stream reader to read the | | 03:09 | file and add those lines to the overall collection. | | 03:14 | Once I finish the loop I have all the data | | 03:17 | for the current data type for the entire range of dates. | | 03:21 | And the cache manager can then return the file lines. | | 03:27 | Going back to the feed manager class, which is where I called that from. | | 03:31 | Once I have all the lines, I can then actually do the aggregation. | | 03:36 | And this code is pretty much exactly the same as in the console lab. | | 03:40 | I'm looping through the lines and parsing them and | | 03:43 | grabbing the numeric values and adding them to a list. | | 03:46 | I'm calculating the mean using the average method, and calculating the | | 03:51 | median using the same code as before and returning the result. | | 03:56 | And then in the service class, I'm taking that result, | | 03:59 | packaging it up into the dictionary as I described before. | | 04:03 | And after I've collected all of the data, I'm | | 04:06 | serializing that into JSON, and returning it to the requester. | | 04:11 | And that's all the code I need to create the web service. | | 04:15 | Now currently, my console application that's gathering the data | | 04:19 | from the remote web service and saving it locally | | 04:22 | is living on my local development computer, and my | | 04:25 | web service is being hosted in a Microsoft Azure website. | | 04:29 | So the question becomes, how does that cache | | 04:32 | data get from one location to the other? | | 04:35 | I haven't solved that in this particular example because my goal was to show | | 04:39 | you the C# code, but in a complete deployment, I would host both the | | 04:45 | Console application and the web service on a single machine, or I would set | | 04:49 | up the Console application to post the data to the web service, storage area. | | 04:55 | For that purpose I might decide at that point | | 04:57 | to use a relational database instead of a file-based cache. | | 05:02 | But the code that I've provided has solved all of the important parts of this | | 05:05 | challenge, using C# and the .NET framework to gather data from a remote website. | | 05:13 | Aggregate it, and then return the results as a | | 05:16 | web service to a requester using the JSON format. | | Collapse this transcript |
|
|
Problem Two: Image AnalysisIdentify the image subset| 00:06 | Hello and welcome to Code Clinic. | | 00:08 | My name is David Gassner. | | 00:11 | Code Clinic is a monthly course where a unique problem | | 00:14 | is introduced to a collection of Lynda.com authors. | | 00:17 | In response, each author will create a solution | | 00:20 | using their programming language of choice. | | 00:23 | You can learn several things from Code Clinic: | | 00:26 | Different approaches to solving a problem, | | 00:28 | the pros and cons of different languages, | | 00:31 | and some tips and tricks to incorporate | | 00:33 | into your own coding practices. | | 00:37 | This is a problem centered around image analysis. | | 00:40 | In one sense, this is simply data analysis. | | 00:44 | Images are really nothing more than | | 00:46 | specialized and well-defined sets of data. | | 00:50 | An image consists of pixels. | | 00:53 | Pixels consist of data, representing a color, | | 00:57 | and in some cases, transparency. | | 01:01 | The pixels are arranged in rows and columns. | | 01:04 | When assembled correctly they represent an image. | | 01:09 | Our brains are very good at recognizing | | 01:11 | patterns, but computers are not. | | 01:15 | Think about CAPTCHA security devices, | | 01:18 | those puzzles you sometimes see when logging into a website. | | 01:23 | The CAPTCHA asks what letters and numbers are in the image. | | 01:27 | Information is obscured by random lines, | | 01:30 | or sometimes overlapping transparent blocks of color. | | 01:35 | All of those intersecting shapes make it | | 01:37 | difficult for a computer program to separate | | 01:40 | the background noise from the actual data. | | 01:44 | Another example is the test to determine color blindness. | | 01:48 | Letters and numbers are hidden in | | 01:50 | a circle filled with different color dots. | | 01:53 | If you're color blind, you won't be able to see the numbers. | | 01:57 | For a computer program this can be incredibly difficult, | | 02:01 | as it requires detecting an edge, | | 02:04 | as well as recognizing the overall shape. | | 02:07 | It's difficult even for the most advanced programmer. | | 02:11 | In this problem we're trying to solve | | 02:14 | a common issue for many photographers: Plagiarism. | | 02:18 | A photographer will take a picture and post it on the | | 02:20 | Internet, only to discover someone has stolen their image | | 02:24 | and placed a subset of that image on their website. | | 02:29 | For example, here is an image, | | 02:32 | and then a cropped version of the same image. | | 02:36 | It would be extremely handy to have a program searching | | 02:39 | the Internet for cropped versions of an original image, | | 02:43 | so that a photographer could protect their rights. | | 02:46 | In fact, Google Image Search will do just that, | | 02:50 | but we're curious how it works | | 02:52 | and what the required code might look like. | | 02:56 | Here's the challenge: Given two images, | | 03:00 | determine if one image is a subset of the other image. | | 03:05 | We'll assume that they're both JPEG files, | | 03:07 | that the resolution is the same, as well as the bit depth. | | 03:12 | We've provided a set of images. | | 03:14 | The program should determine which images | | 03:16 | are cropped versions of other images. | | 03:20 | Perhaps you'll want to pause | | 03:21 | and create a solution of your own. | | 03:24 | How would you solve the problem? | | 03:26 | In the next videos I'll show you how I solved this challenge. | | Collapse this transcript |
| Overview of my first solution| 00:00 | - [Voiceover] Working through this challenge | | 00:01 | required two different solutions. | | 00:04 | I'll start by describing the scenarios I'm trying to cover | | 00:07 | and then show you first solution | | 00:09 | that only handled one scenario. | | 00:12 | We're working with two photos that we want to compare. | | 00:16 | One larger and one smaller. | | 00:18 | In one scenario, the simplest of them, | | 00:21 | the smaller photo is simply cropped from the larger one | | 00:25 | and no other changes are made to the smaller photo. | | 00:28 | So essentially, the pixels in the smaller photo | | 00:31 | exactly match some pixels in the larger photo. | | 00:36 | The more complex scenarios though, are these. | | 00:39 | Perhaps, the smaller photo was both cropped | | 00:42 | and then scaled or resized. | | 00:45 | In this event, | | 00:47 | the series of pixels that started off in the larger photo | | 00:50 | will have changed in the smaller photo in some way | | 00:54 | because the photo will be stretched or compressed. | | 00:57 | Either way, | | 00:58 | the pixels will occur in a slightly different order | | 01:02 | or in a slightly different number than in the original photo | | 01:06 | and in a more extreme circumstance | | 01:09 | the smaller photo might be cropped, scaled, and rotated | | 01:13 | making an exact match of the pixels between the two photos | | 01:16 | much more complex. | | 01:18 | My first programming solution | | 01:20 | covered only the first scenario, | | 01:23 | where the smaller photo is cropped from the larger photo | | 01:26 | but no other changes are made | | 01:28 | and this was my strategy. | | 01:31 | First, I converted the images to sets of strings. | | 01:35 | Let's take a simple image. | | 01:37 | Each image consists of rows and columns of pixels. | | 01:42 | Each pixel is defined by a red, a green, a blue, | | 01:46 | and an alpha channel. | | 01:48 | Alpha means transparency. | | 01:51 | Each of these channels is assigned a numeric value. | | 01:55 | In C Sharp you can convert an image to a bitmap object | | 01:59 | and then get a pixel at a particular position in the image. | | 02:04 | This code, for example, gets a reference to the pixel | | 02:07 | in the top left corner of the image | | 02:10 | at an X position and a Y position of zero | | 02:13 | and in this image this is the result. | | 02:15 | I'm getting an alpha, a red, a green, and a blue color. | | 02:20 | Now that's a long string | | 02:21 | and that's not what I want to work with in my comparisons. | | 02:24 | Instead, I added a little bit of code | | 02:27 | to get the pixels hash code. | | 02:30 | The hash code is a pure numeric value. | | 02:33 | It contains the same values as that longer string | | 02:36 | but it's a smaller string | | 02:38 | and so it will be faster and easier to work with. | | 02:43 | So let's take a large image. | | 02:45 | I'll take this larger image | | 02:47 | and convert it completely to strings. | | 02:50 | It's sort of like in the "Matrix" | | 02:53 | now instead of seeing actual colors, | | 02:55 | I'm seeing values that the computer can understand. | | 02:59 | In this case, numbers converted to strings. | | 03:02 | Then I did the same thing to the smaller image. | | 03:05 | Once again, converting it to a set of strings | | 03:09 | and then when I compare the two, I can find the match. | | 03:13 | Now this will only work again | | 03:15 | where the smaller image doesn't have any other changes | | 03:18 | other than the cropping | | 03:21 | and here's what ended up happening. | | 03:23 | It worked for simple cropped photos | | 03:26 | but it doesn't work | | 03:27 | if a cropped photo is also scaled or rotated. | | 03:31 | So let's take a look at my code for the first solution. | | Collapse this transcript |
| Selecting image files to compare| 00:00 | The exercise files folder for this challenge | | 00:03 | includes two folders that have Visual Studio solutions | | 00:06 | and one folder named "Images." | | 00:10 | The Image folder contains three sub-folders. | | 00:14 | "Original" contains original images with their full sizes. | | 00:20 | There's a "Scaled" folder that contains the same images | | 00:23 | scaled down to ten percent of their original size. | | 00:26 | I'll explain later how I'm using those. | | 00:30 | And finally, there's a folder named "Web." | | 00:33 | This folder contains two very small images. | | 00:37 | This one has a resolution of 267x400 and a DPI of 72. | | 00:44 | It's a very small image. | | 00:47 | And this image is cropped from the original image. | | 00:51 | It has a resolution of 267x265 | | 00:55 | and in fact, it comes from the very top left corner | | 00:58 | of the original image. | | 01:00 | These are the images I'll use | | 01:02 | to demonstrate this first solution. | | 01:06 | To open the Visual Studio projects, you can either use | | 01:10 | one of the pay-for-license versions of Visual Studio | | 01:13 | or you can use the free Visual Studio Express 2013 | | 01:17 | for Windows desktop. | | 01:20 | To open the project, just double-click the "Solution" file. | | 01:25 | This application is built with the WinForms framework. | | 01:29 | I could have also used WPF | | 01:31 | or Windows Presentation Foundation | | 01:33 | or built this as a Windows 8 Modern app. | | 01:37 | I've chosen to use WinForms because it gives me | | 01:40 | the broadest access to the classes I'm going to use. | | 01:44 | I'll begin by showing how I'm selecting images to compare. | | 01:49 | I've included a file dialog component | | 01:51 | as an invisible component in this application. | | 01:56 | And when the user clicks one of these links, | | 01:58 | they can select an image. | | 02:00 | The application asks them to select a large image | | 02:02 | and a small image. | | 02:06 | There are two field declarations called "largeFileName" | | 02:08 | and "smallFileName." | | 02:11 | And when the form loads, | | 02:12 | I'm initializing the appearance of some of the components. | | 02:16 | There are two methods that are called, | | 02:19 | one for the large link and one for the small link. | | 02:22 | In each case, I use the file dialog component, open it | | 02:27 | and then wait for the user to respond. | | 02:30 | After the code continues, at line 39 in this method, | | 02:34 | I then evaluate what the user selected | | 02:37 | by looking at the dialog boxes FileName property | | 02:41 | and its ToString method. | | 02:43 | And, if the ToString value is not a blank string, | | 02:47 | that means the user selected a file. | | 02:50 | I save the filename, use a label to display | | 02:54 | the location and name of the file that was selected | | 02:57 | and then, use a picture-box component | | 03:00 | to display the image that's been selected. | | 03:04 | After I've done all that, I call a method | | 03:05 | called "validate selections." | | 03:08 | And here, I examine the two filenames. | | 03:11 | If the user has selected both files, | | 03:14 | then I enable a button that they can click | | 03:16 | to launch the comparison operation. | | 03:20 | So, I'll demonstrate this application. | | 03:24 | When it opens, I click the "large image" link | | 03:27 | and I can choose one of the large images. | | 03:31 | I'll go to my "original" folder under Images | | 03:34 | and choose this image. | | 03:37 | And then, I'll click the other link | | 03:39 | and choose a small image. | | 03:42 | Now that I've chosen both images, | | 03:44 | the "Compare Images" button is ready to use. | | 03:48 | And I'll show you what happens | | 03:49 | when the user clicks that button, in the next movie. | | Collapse this transcript |
| Comparing images with strict pixel matching| 00:00 | -So far I've described the basic structure | | 00:02 | of this application, how it selects images | | 00:06 | and gets ready to compare them. | | 00:08 | Now I'll describe the code that's extracting | | 00:11 | strings from the images, and then comparing | | 00:13 | the images to each other. | | 00:16 | When the user clicks the compare button, | | 00:19 | they're running this method, | | 00:20 | named compare button click, and here | | 00:23 | are the steps it follows. | | 00:25 | At lines 76 and 77, I'm turning the files | | 00:29 | that are stored on disk into bitmap objects, | | 00:33 | and I'm using the bitmap classes constructor | | 00:35 | to do that. | | 00:38 | Then before I start operating on those objects, | | 00:40 | I'm initializing a progress bar component. | | 00:44 | Notice at line 81 I'm setting the maximum value | | 00:47 | for the progress bar to the sum of the heights | | 00:51 | of the two bitmaps. | | 00:53 | I'm going to be scanning the bitmaps one line | | 00:56 | at a time, so by adding the two height values | | 00:59 | together, I'm telling the progress bar that I'll | | 01:02 | be incrementing it once for each line of pixels. | | 01:07 | Then I set the initial value to zero, | | 01:09 | and make the progress bar visible. | | 01:12 | Next I'm calling a method called get rows. | | 01:16 | The get rows method accepts a bitmap object, | | 01:19 | and I'll place my cursor on the name of the method, | | 01:22 | and press F12 to jump to it. | | 01:25 | The get rows method creates a list object | | 01:28 | that will contain string values. | | 01:31 | Essentially, I'm going to turn each image | | 01:33 | into a list of strings, and each of those strings | | 01:37 | will consist of the hash-codes for the pixels. | | 01:40 | One string is for one row of pixels. | | 01:45 | I'll create one string builder object | | 01:47 | and then use it for the lifetime of this method. | | 01:50 | And now, I'm ready to loop | | 01:52 | through the bitmap's rows. | | 01:55 | There are two for loops here, an outer loop, | | 01:57 | and an inner loop. | | 01:59 | The outer loop is for the rows. | | 02:02 | I'll execute that row once for each row of pixels. | | 02:06 | And the inner loop is for individual pixels | | 02:08 | within a row. | | 02:10 | So in the outer loop I'm starting by clearing | | 02:12 | the string builder. | | 02:14 | It doesn't have any string now. | | 02:16 | Then at line 114, I'm looping through the pixels | | 02:20 | for the current row. | | 02:22 | Within the loop, I'm getting the hash-code | | 02:25 | for the current pixel. | | 02:26 | Using the get pixel method and the get hash-code | | 02:30 | method that I described in an earlier movie. | | 02:34 | The hash-code is an integer, but I'm appending | | 02:37 | it to a string builder object, and that turns it | | 02:40 | into a string. | | 02:42 | Because I'm looping through all the pixels | | 02:44 | for the current row, by the time I'm finished | | 02:46 | with the inner loop I now have a single string | | 02:49 | consisting of string representations | | 02:52 | of all the pixels in the row. | | 02:55 | Once I've finished creating that string, I add it | | 02:58 | to the rows list using rows dot add, and then | | 03:02 | I increment the progress bar by one. | | 03:05 | So each time through the row of pixels, | | 03:07 | the progress bar will increment. | | 03:10 | Once I'm done with the outer loop, I'm finished | | 03:12 | turning the image into a list of strings, | | 03:15 | and I return that list. | | 03:17 | So now let's go back to where I called that method | | 03:20 | at lines 86 and 87. | | 03:23 | I now have two lists of strings, and at line 91, | | 03:28 | I'm calling a method named is subset, and I'm | | 03:31 | passing in the two lists. | | 03:34 | So I'll jump to that method. | | 03:37 | In is subset, I'm starting by creating two integer | | 03:40 | values named matches and misses, and then I'm | | 03:44 | going to loop through the rows of the small image, | | 03:47 | and match them against rows of the large image. | | 03:51 | Once again, there are two loops here, | | 03:53 | this time for each loops. | | 03:55 | There's an outer loop to loop through the small | | 03:58 | images rows, and an inner loop | | 04:00 | for the large images rows. | | 04:03 | For each row in the small image, I start | | 04:05 | by assuming that I won't make a match, | | 04:08 | and then I loop through the rows of the large | | 04:10 | image and I see if each row contains | | 04:13 | the small image row. | | 04:15 | If I get a match on line 136, then I say | | 04:19 | that the match is true, meaning I got a match, | | 04:22 | and I break out of the inner loop. | | 04:25 | Then at lines 142 to 145, I evaluate what happened, | | 04:30 | and if the match is true, I increment the matches | | 04:33 | value, and if it's false, I increment the misses value. | | 04:37 | Once the outer loop is done, | | 04:39 | I've finished my comparison. | | 04:41 | I've compared all of the rows of pixels | | 04:43 | in the small image to all the rows of pixels | | 04:46 | in the large image, and notice, I'm not looking | | 04:49 | for an exact match. | | 04:51 | I'm asking whether the large image row | | 04:53 | contains the small image row. | | 04:56 | Finally, at the end of this method, I do a | | 04:58 | calculation, looking at the matches and the misses, | | 05:02 | and finding out what percentage of matches I got. | | 05:05 | That's the calculation at line 149. | | 05:09 | There's a bit of de-bugging code | | 05:10 | to see what happened, and then I return | | 05:13 | a bullion value determined by matching | | 05:15 | the percentage of matches that I got | | 05:17 | to the threshold that I set at the top | | 05:20 | of the code. | | 05:22 | The threshold is set way at the top to point 9, | | 05:26 | meaning if I got a 90% match, I'm going to say, | | 05:29 | yes, those two images match each other. | | 05:33 | So now let's see what happens. | | 05:37 | For this, I'm going to use the web images, | | 05:40 | and I'll warn you that if you try to use | | 05:42 | any of the other images in my image set, | | 05:45 | it won't work. | | 05:46 | I'll describe why in a moment. | | 05:49 | I'll go to my web folder and choose | | 05:50 | yellow pansy dot jpg for my large image, | | 05:55 | and I'll choose yellow pansy cropped | | 05:56 | for my small image, and I prepared these images | | 05:59 | exactly for this test. | | 06:01 | I know for sure that this is almost an exact match | | 06:04 | to the large image. | | 06:07 | I'll click compare images, and I get the message | | 06:09 | that the small image is a subset of the large image. | | 06:14 | But now let's take a look at some images | | 06:15 | that won't work this way. | | 06:18 | I'll go to my scaled images, and I'll choose | | 06:22 | the big picture of the parrot. | | 06:25 | Then for the small image, I'll choose this one | | 06:28 | that has the letter A at the end of the name. | | 06:30 | And that, a cropped and scaled image, it's | | 06:34 | not an exact match, because it hasn't just been | | 06:37 | cropped, it's also been re-sized. | | 06:40 | I'll try to compare the images, and I get | | 06:43 | a negative result. | | 06:45 | So think about why this is happening. | | 06:49 | When I cropped the small image, and then, | | 06:51 | before I save it, I scale it, make it either | | 06:55 | larger or smaller, the software that I use | | 06:58 | has to fill in what's changed. | | 07:00 | Especially if I make it larger, the software | | 07:03 | has to decide whether to duplicate pixels, | | 07:06 | or, through some sort of logic, add pixels | | 07:09 | of completely different colors that let the | | 07:12 | human eye see a blended image. | | 07:15 | Either way, the two strings just aren't going | | 07:17 | to match anymore, and this can get even | | 07:20 | tougher when you're dealing with images that | | 07:22 | have been rotated or otherwise changed. | | 07:26 | So this is a good solution, but only for a | | 07:28 | very small set of images, only those images | | 07:32 | that have been cropped, but nothing else | | 07:34 | has been done to them. | | 07:36 | To handle more complex situations, | | 07:39 | I'll need a completely different solution. | | 07:41 | I'll show you what I came up with | | 07:43 | in the following movies. | | Collapse this transcript |
| Overview of my second solution| 00:00 | As I've previously described, the process | | 00:02 | of comparing photos gets much more complex | | 00:06 | when a photo has both been cropped | | 00:08 | and then resized, rotated or both. | | 00:12 | Here are two photos where one is a subset of the other. | | 00:16 | The photo on the right was cropped from | | 00:18 | the photo on the left, but then it was scaled. | | 00:21 | And for any particular row of pixels, | | 00:24 | there won't be an exact match, either in | | 00:26 | the number of pixels or their composition. | | 00:29 | So doing a simple string matching, like I did | | 00:31 | in my first solution, just isn't going to work. | | 00:37 | The problem gets even more complex when you're | | 00:40 | dealing with images that might have been rotated. | | 00:43 | For example, here's a large image of the Statue of Liberty. | | 00:47 | And here's a cropped image. | | 00:50 | But you can see right away that the image | | 00:51 | has been flipped, not just rotated, | | 00:55 | and there definitely won't be an exact match. | | 00:58 | Or the color-blind test where the pixels | | 01:01 | might match exactly, where there might | | 01:04 | be an exact match in the colored sections, | | 01:06 | but if any changes have been made to the tinting | | 01:09 | of the colors, once again, it'll be hard to find. | | 01:13 | This is a problem that people | | 01:14 | have written dissertations about. | | 01:17 | In fact, in my searches I found complete dissertations. | | 01:22 | This is an example of an article that was written | | 01:24 | by a number of computer scientists, describing | | 01:27 | one approach to sub-image detection and retrieval. | | 01:32 | If you read through some of this article, | | 01:34 | you'll find that it's a big challenge. | | 01:37 | It's computationally intensive, it takes a lot | | 01:41 | of computer power to go through an image, | | 01:44 | identify what are called "key points," | | 01:47 | and then to match the images, even when | | 01:50 | a sub-image has been changed in some way. | | 01:53 | This article describes a process where the images are turned | | 01:57 | into data sets, which are stored in very large databases, | | 02:01 | and even then, the matching takes quite a bit of time. | | 02:06 | Or this article, which defines another algorithm. | | 02:11 | Reading through these articles, it became clear | | 02:14 | that creating my own algorithm to solve | | 02:16 | this problem was going to be very time intensive. | | 02:19 | And I couldn't be sure that I'd be able | | 02:22 | to solve it without building an entire database. | | 02:26 | So I went looking for existing | | 02:28 | solutions for the .NET Framework. | | 02:31 | Here's one solution that some of the authors | | 02:33 | who are working in Code Clinic will use. | | 02:36 | It's a library called ImageMagick. | | 02:39 | It's available for multiple operating systems, and it does | | 02:43 | have tools for finding sub-images in main images. | | 02:48 | The challenge for me, because I'm | | 02:49 | working in C Sharp, is figuring out how | | 02:52 | to integrate this into a .NET-based program. | | 02:56 | I found some .NET wrappers for the ImageMagick | | 02:59 | library, but none of them included the right components. | | 03:05 | So after looking a little more, I found AForge. | | 03:09 | AForge is a framework for .NET that includes | | 03:13 | many libraries for managing images | | 03:16 | and handling other advanced techniques. | | 03:19 | The library is already built for .NET, so it's | | 03:22 | easy to integrate into a .NET application | | 03:25 | where you're programming with C Sharp or Visual Basic. | | 03:30 | To integrate the library, I created a new | | 03:33 | version of my solution, and I've opened it up here | | 03:36 | by opening the CompareImagesAForge solution. | | 03:40 | Then I went to the Tools menu, to the | | 03:44 | Library Package Manager, and I selected | | 03:47 | Manage NuGet Packages for Solution. | | 03:51 | I clicked on Nuget.org, and then | | 03:54 | I searched online for AForge. | | 03:59 | I found all the libraries that are | | 04:01 | available for the AForge framework. | | 04:04 | I chose this one: AForge.Imaging. | | 04:09 | I imported the imaging library, then, | | 04:12 | because it was dependent on other libraries, | | 04:15 | I also automatically got the core | | 04:17 | library and AForge.Math. | | 04:21 | Then I was able to use tools from the AForge | | 04:24 | library to compare images to each other. | | 04:28 | I'll show you how I used those tools in the next movie. | | Collapse this transcript |
| Comparing images with AForge.Imaging| 00:00 | - As I described in the previous movie, | | 00:02 | the key to my second solution | | 00:04 | is the AForge library. | | 00:07 | An open source framework | | 00:09 | that lets me do image comparisons, | | 00:11 | and has a lot of other capabilities. | | 00:14 | So now, I'm working in the Compare Images AForge solution | | 00:18 | that I've opened up in Visual Studio | | 00:20 | and as with the last solution, | | 00:22 | you can either open it in Visual Studio Professional | | 00:25 | or one of the other advanced versions, | | 00:28 | or you can open it in the free version, | | 00:30 | Visual Studio Express 2013 for Windows Desktop. | | 00:35 | I'm going to start by running the application | | 00:37 | and showing you a successful match. | | 00:41 | I'll choose a large image | | 00:43 | and for this experiment, I'm going to choose this one, | | 00:46 | a picture of the wedding. | | 00:49 | Then I'll choose a small image. | | 00:51 | And this is a cropped image | | 00:53 | that's also been scaled significantly. | | 00:56 | It's been resized to be much larger | | 00:58 | than the version it was cropped from. | | 01:01 | If you look very closely though, | | 01:03 | this is definitely a crop of that first image. | | 01:06 | It's coming from this doorway right here. | | 01:10 | Now I'll click the button labeled Compare Images | | 01:13 | and I'll wait a few moments. | | 01:15 | Notice that I'm using the scaled versions | | 01:17 | of the images. | | 01:19 | The images that have been reduced in size. | | 01:22 | And the reason I'm doing that | | 01:23 | is because the AForge library, | | 01:26 | while it worked with the very large images, | | 01:28 | took a long time. | | 01:31 | And I see that I got back a correct response. | | 01:34 | A match was found between the sub-image | | 01:38 | and the original image. | | 01:40 | So now let's take a look at the code that's doing this. | | 01:44 | I'll go back to Visual Studio and look at my code | | 01:48 | and the first part of the code in Form 1.CS | | 01:51 | is pretty much the same as in the first solution. | | 01:55 | It's letting me choose the images I want | | 01:57 | to compare and displaying them on the screen. | | 02:01 | But when I compare the two images | | 02:03 | by clicking that button, | | 02:05 | here's the code that I'm actually running. | | 02:07 | And it turns out to be a tiny amount of code. | | 02:11 | On line 75 and 76 I'm once again | | 02:14 | turning my images into bitmap objects. | | 02:18 | And then, I'm using a couple of classes | | 02:21 | that come from the AForge.imaging library. | | 02:25 | The exhaustive template matching class | | 02:28 | is doing the hard work. | | 02:30 | When I instantiate that class, | | 02:32 | I'm passing in a float value, .9 | | 02:36 | and that means I'm asking for a match | | 02:39 | to a degree of certainty of 90 percent. | | 02:43 | That's a pretty high level. | | 02:45 | Now, when I clicked the Compare button | | 02:47 | I was showing this processing string, | | 02:50 | but notice that I took away the progress bar | | 02:52 | from the earlier solution. | | 02:55 | And that's because | | 02:56 | the method that I'm about to call, Process Image, | | 02:59 | from the exhaustive template matching object, | | 03:02 | is a black box. | | 03:04 | I haven't yet found a way | | 03:05 | for it to report to me | | 03:07 | how far along it is in its process. | | 03:10 | I'm simply calling it and getting back a result. | | 03:14 | And so, because I can't make the progress bar increment | | 03:18 | I won't use it at all. | | 03:21 | But now, when I call this method, | | 03:23 | I'm passing in the large bitmap and the small bitmap. | | 03:27 | And I get back an array. | | 03:29 | The array contains instances of the template match class. | | 03:33 | Once again, this is a class from AForge. | | 03:37 | And here's the rule, | | 03:39 | this array will contain one object | | 03:41 | for each match it finds. | | 03:44 | When you're comparing two images to each other | | 03:47 | you're really looking at two possibilities. | | 03:49 | Either no matches or one match. | | 03:53 | If you were dealing with vector images, | | 03:55 | where let's say, the small image was a single shape, | | 03:58 | and that image might recur multiple times | | 04:01 | in the original image, | | 04:03 | then your array could contain more than one object. | | 04:06 | But for our application, | | 04:08 | we can expect either zero or one. | | 04:11 | So after I've run the process image method, | | 04:14 | I then examine the array, | | 04:15 | which I've named Matches, | | 04:17 | and I look at its length. | | 04:19 | If the length is greater than zero, | | 04:22 | that means I found a match. | | 04:24 | And if the length is zero, | | 04:26 | then I didn't find a match. | | 04:28 | And then I'm finding out | | 04:29 | where the two images matched | | 04:31 | by looking at the match objects rectangle property | | 04:35 | which has a location property, | | 04:37 | which has x and y values. | | 04:40 | And I'm using that information | | 04:42 | to tell the user what I found. | | 04:45 | And that's all the code. | | 04:47 | Once again, this is a very complex problem, | | 04:50 | but this AForge library has solved it for me. | | 04:54 | So now that we know how the code works, | | 04:57 | let's run it on a few more images. | | 05:00 | This time, for the large image I'll choose the parrot image, | | 05:03 | and then I'll choose the sub-image. | | 05:07 | Notice the A at the end of the file name. | | 05:10 | That means that this image is a crop | | 05:12 | of the original image. | | 05:14 | But like that doorway in the wedding shot, | | 05:16 | it was scaled after it was cropped. | | 05:19 | And this was the set of image | | 05:21 | that my original solution failed to find a match in. | | 05:25 | But now I'll compare the two images | | 05:28 | and because of the amount of color and detail | | 05:30 | in these images, | | 05:32 | this process takes a little bit longer | | 05:33 | than the first one. | | 05:35 | But when the comparison is complete, | | 05:38 | I'm told that the images match. | | 05:41 | I'm told that the sub-image occurs in the original image | | 05:45 | at an x position of 145, and a y position of 11. | | 05:51 | But now let's compare an image that doesn't match. | | 05:54 | This image might look, at first glance, | | 05:57 | like it's a sub-image of the first, | | 05:59 | but there are some significant differences. | | 06:02 | Take a look at how far open the bird's beak is, | | 06:05 | and compared to the original image | | 06:07 | you'll see that that's different, | | 06:09 | and you'll see on the smaller image | | 06:11 | that the feathers on the back of the bird's neck | | 06:13 | are standing up a little bit, | | 06:15 | and that's not the case in the original image. | | 06:19 | And this time, when I compare the two images, | | 06:22 | I'm told instantly that a match wasn't found. | | 06:25 | And I know that that's correct. | | 06:28 | This is library also turns out to be pretty good | | 06:30 | at not getting fooled when it just sees similar colors. | | 06:35 | Here's a smaller image that might look, | | 06:37 | at first glance, like it's a sub-image, | | 06:39 | but if you look carefully at the clouds, | | 06:42 | you'll see that it's actually a very different image. | | 06:45 | And when I compare the images, | | 06:47 | it takes a little bit longer, | | 06:49 | but it pretty quickly comes back | | 06:51 | and tells me these images don't match. | | 06:55 | But that takes us to the real problem images. | | 06:58 | I'll start with the Statue of Liberty. | | 07:00 | And this is an example where the image | | 07:02 | has been cropped, but then flipped. | | 07:05 | And this is where we run past the capabilities | | 07:08 | of the AForge library. | | 07:10 | At least, in the way that I'm currently using it. | | 07:13 | It takes a little bit longer | | 07:14 | to do the processing than it did in the previous examples, | | 07:18 | but then it says, "Couldn't find a match." | | 07:22 | And it also fails to find a match | | 07:24 | when it looks at images | | 07:26 | where either the tinting has changed | | 07:28 | or where it has to find some kind of edge. | | 07:31 | When I compare these two images, | | 07:33 | I'm told instantly that the match wasn't found. | | 07:37 | But the AForge library actually handles | | 07:40 | a lot of the problems that I was looking at, | | 07:43 | especially where I'm looking at images | | 07:45 | that have been cropped and rescaled. | | 07:48 | If you're running this code on your own computer | | 07:51 | try it on your own images | | 07:52 | and see what results you get. | | 07:55 | Just as I did, I recommend taking any images | | 07:58 | that are in their original raw size, | | 08:01 | and significantly scaling them down, | | 08:04 | reducing their size to reduce the amount | | 08:07 | of computational fire power that you need. | | 08:10 | You don't need the images to be large | | 08:12 | to get a good match, | | 08:14 | but you do need a great algorithm, | | 08:16 | and the AForge library provided that for my solution. | | Collapse this transcript |
|
|
Problem Three: Eight QueensA classic CS interview question| 00:00 | (Intro music) | | 00:07 | Hello, and welcome to Code Clinic. | | 00:09 | My name is David Gassner. | | 00:12 | Code Clinic is a monthly course where a | | 00:14 | unique problem is introduced to a collection | | 00:17 | of Lynda.com authors. | | 00:19 | In response, each author will create a solution | | 00:22 | using their programming language of choice. | | 00:25 | You can learn several things from Code Clinic. | | 00:27 | Different approaches to solving a problem, | | 00:30 | the pros and cons of different languages, | | 00:32 | and some tips and tricks to incorporate into | | 00:35 | your own coding practices. | | 00:38 | This month, we're working on a classic computer | | 00:40 | programming problem called "The Eight Queens." | | 00:44 | This famous problem is often used during interviews | | 00:48 | or to demonstrate the utility of a computer language. | | 00:52 | It requires an understanding of recursion | | 00:54 | and algorithm design, | | 00:56 | and can be quite useful as an exercise | | 00:58 | in learning to program solutions for complex problems. | | 01:03 | This problem was proposed by Max Bezzel in 1848 | | 01:07 | and solved by Franz Nauck in 1850. | | 01:11 | The problem is simple. | | 01:13 | Start with a chess board and eight queens. | | 01:15 | Then, set up the board so that no two queens | | 01:18 | can attack each other. | | 01:20 | There's more than one solution. | | 01:22 | Our challenge is to find them all. | | 01:25 | We already know there are 92 possible solutions | | 01:28 | and we already have examples of the solutions | | 01:30 | in several computer languages. | | 01:34 | If you've never played chess, | | 01:35 | you'll need to understand that a queen can | | 01:37 | attack by moving an unlimited number of spaces | | 01:41 | in three directions, | | 01:43 | horizontally, vertically, and diagonally. | | 01:47 | This means that no two queens can share a row | | 01:49 | or a column. | | 01:51 | Nor can they be located diagonally from each other. | | 01:55 | In the following movies, I'll show you my solution | | 01:58 | to the eight queens problem. | | 01:59 | I'd encourage you to also look at the solutions | | 02:02 | from other authors in the Lynda.com library. | | 02:06 | You will be able to compare different author styles | | 02:08 | and different languages. | | Collapse this transcript |
| Overview of my solution| 00:00 | - This programming challenge does not specify | | 00:03 | the framework or application style. | | 00:06 | It's a challenge of logic, | | 00:08 | and so it's up to the programmer | | 00:10 | to decide what kind of application | | 00:11 | they want to build. | | 00:13 | My sense of the application requirements | | 00:16 | are that I'll need very fast calculation | | 00:18 | and sorting capabilities, | | 00:20 | and I'll need a rich desktop interface | | 00:22 | to display the results to the user. | | 00:25 | There's no particular reason to build | | 00:27 | this as, say, a mobile or a web application | | 00:30 | and so I'll default to the desktop | | 00:32 | environment where I have the most tools. | | 00:35 | On Windows, you can choose from | | 00:37 | either Windows Forms or WinForms | | 00:40 | or WPF, Windows Presentation Foundation. | | 00:44 | For this solution, I'm working with Windows Forms. | | 00:49 | In deciding how to build this application, | | 00:52 | I had to decide what sort of logic to use. | | 00:55 | The logic you use is called the algorithm, | | 00:58 | and there's been a lot of research done | | 01:00 | on the Eight Queens problem. | | 01:02 | There are a number of approaches you can take. | | 01:05 | One is called the exhaustive search | | 01:07 | algorithm where you basically create | | 01:09 | every combination of different placements | | 01:12 | and then evaluate whether each one works. | | 01:16 | The problem with this approach is that there | | 01:18 | are a ridiculous number of possibilities, | | 01:21 | over 200 trillion of them. | | 01:23 | So it would be very computationally | | 01:26 | expensive to try to do this. | | 01:29 | Another approach is called iterative repair. | | 01:33 | With this logic, you start by placing | | 01:35 | queens in all locations on the board | | 01:38 | and count the available conflicts, and start | | 01:41 | removing pieces until you find solutions. | | 01:45 | Once again, it's computationally expensive | | 01:47 | and would take a lot of code. | | 01:50 | I'm using an algorithm or a logic | | 01:52 | pattern known as backtracking. | | 01:55 | With this approach, you place the queen | | 01:57 | in a particular location. | | 01:59 | You would start, for example, | | 02:00 | by placing a queen in the top left corner | | 02:03 | of the chessboard and then you begin | | 02:06 | to test other possible locations. | | 02:09 | You might place a queen next to the first one, | | 02:12 | below it, or to the lower right, | | 02:15 | and then for each placement, | | 02:16 | you test whether it's going to work. | | 02:19 | You're checking for conflicts | | 02:21 | and there are three kinds of conflicts: | | 02:23 | horizontal -- if the new queen is on the same row; | | 02:27 | vertical -- if it's in the same column; | | 02:29 | and diagonal. | | 02:31 | When we get to the actual solution, | | 02:33 | I'll show you an equation you can use | | 02:36 | to test for diagonal placement. | | 02:39 | Once you've found an acceptable | | 02:41 | location for the next queen, | | 02:43 | you move to the next level. | | 02:45 | You have eight queens to deal with | | 02:47 | and therefore eight levels. | | 02:49 | By doing a recursive search, | | 02:52 | you can find all of the 92 possible distinct solutions. | | 02:57 | If you're dealing with a visual | | 02:58 | environment such as I am with Windows Forms, | | 03:01 | you can then display the result to the user. | | 03:04 | In my application, I'm doing all of | | 03:07 | the calculations up front | | 03:09 | and storing all of my 92 solutions, | | 03:12 | and then the user can trace through | | 03:14 | the solutions by clicking a button | | 03:16 | in the application, | | 03:18 | showing the first solution, | | 03:20 | the second, the third, and so on. | | 03:24 | Now, I've built my solution in C#, | | 03:27 | but I need to give acknowledgments | | 03:29 | where they're due. | | 03:31 | I learned from a solution that was | | 03:32 | provided on the Code Project website. | | 03:36 | This solution was built for .NET | | 03:38 | but with a different programming language, | | 03:40 | VB.NET, and it was built by a | | 03:43 | programmer named Ali Tarhini. | | 03:46 | As I said, the solution is available | | 03:47 | on the Code Project site, | | 03:50 | an open source code-sharing site | | 03:52 | and the code is openly licensed | | 03:55 | so you can download it, | | 03:56 | experiment with it, | | 03:58 | make changes in it, | | 03:59 | and share it as you see fit | | 04:01 | as long as you give proper acknowledgment. | | 04:04 | The VB.NET version of the solution | | 04:06 | is available at this URL: | | 04:08 | http://www.codeproject.com/Articles/32235/ | | 04:10 | This solution does more than my solution does. | | 04:13 | In addition to presenting the | | 04:14 | 92 possible solutions, | | 04:16 | it lets the user experiment | | 04:19 | by placing items on the chessboard | | 04:21 | and then seeing whether they can | | 04:22 | find a solution manually. | | 04:25 | The point here is that rather than try | | 04:28 | to build the code from scratch myself, | | 04:30 | I found some solutions that worked. | | 04:33 | I looked at a number of others | | 04:34 | before I came upon this one, | | 04:36 | and I learned from the successes of | | 04:38 | other developers and then adapted | | 04:41 | that code to create something that | | 04:43 | worked for my specifications. | | 04:45 | This is an important lesson | | 04:47 | in software development. | | 04:49 | You don't have to re-invent every wheel yourself. | | 04:52 | In many cases, the problems you're | | 04:54 | trying to solve have already been | | 04:56 | solved by somebody else, | | 04:58 | and you can learn from those solutions | | 05:00 | and adapt them for your own needs. | | Collapse this transcript |
| Opening the solution in Visual Studio| 00:00 | - As with all of my solutions for the Code Clinic series, | | 00:04 | I've provided my solution, as exercise files, | | 00:08 | which you can download from the lynda dot com website | | 00:10 | and extract anywhere on your hard disk. | | 00:13 | I've placed them on my desktop in an exercise files folder. | | 00:18 | within that folder, | | 00:19 | you'll find a folder named eight queens problem, | | 00:22 | and within that, the visual studio project. | | 00:26 | To open the project, you'll need either a pay for license | | 00:29 | version of Visual Studio, I'm using Professional, | | 00:33 | or you can use the free version, | | 00:35 | Visual Studio Express 2013 for Windows desktop. | | 00:41 | From the menu select file, open, project or solution. | | 00:46 | Navigate to the exercise files folder, | | 00:49 | then to the eight queens problem sub folder, | | 00:52 | and then the solution file. | | 00:55 | And that will open the project | | 00:57 | in your copy of Visual Studio. | | 01:00 | Then to test the solution, | | 01:02 | click the start button on the tool bar. | | 01:05 | You'll be running the application in debug mode, | | 01:08 | so you can add break points, | | 01:10 | step through the code | | 01:12 | and inspect variables, | | 01:14 | to help you understand how the solution is put together. | | 01:17 | As the application starts up, | | 01:19 | it displays a chess board. | | 01:22 | Then, it calculates all of the solutions. | | 01:26 | Down at the bottom of the interface there's a message, | | 01:28 | click to show one of 92 solutions. | | 01:31 | And a button. | | 01:32 | And each time I click a button, a new solution is displayed. | | 01:38 | There are only two graphic files involved in the solution. | | 01:42 | The chess board itself, and the chess piece image. | | 01:45 | And I'll show you how I've implemented those | | 01:47 | a little bit later. | | 01:50 | I'll click again, and there's solution number two, | | 01:52 | three, four, five and so on. | | 01:56 | And if you were to click 92 times, | | 01:59 | you would then cycle back to the first solution. | | 02:02 | So that's how you can open the project from the | | 02:04 | exercise files in your copy of Visual Studio | | 02:08 | and test out the solution. | | 02:11 | Next, I'll show you how the user interface is put together. | | Collapse this transcript |
| Building the user interface| 00:00 | - When I run my application, a chessboard | | 00:03 | is displayed and then each time I click the next | | 00:06 | solution button, I'm seeing the graphic of the | | 00:09 | queen image being displayed in eight positions. | | 00:12 | I'm going to start my walkthrough of the code | | 00:15 | by showing how I'm handling those images. | | 00:18 | In a .NET application, you can deal | | 00:20 | with image files in a couple of different ways, | | 00:22 | but the most efficient runtime approach is to | | 00:25 | compile the images into the project as resources. | | 00:30 | I've done this in my project and I'll show you | | 00:32 | where it's done by going to the menu choice | | 00:35 | Debug and then EightQueensProblem properties. | | 00:40 | Within the properties interface, I'll click | | 00:43 | on resources, and here are my two | | 00:45 | graphic files, the chessboard and the queen. | | 00:50 | If you want to use graphics in this way, | | 00:53 | you can go to this resources panel, | | 00:56 | then choose add resource, add existing file. | | 01:00 | Then you can choose the graphic | | 01:02 | file and add it to the project, and then you can | | 01:06 | address the image as a .NET object. | | 01:10 | I'll show you where this is done in the chessboard.cs file. | | 01:16 | I'll open up the partial class chessboard, | | 01:18 | and I'm looking at it as C# code, and I'll scroll down | | 01:23 | a bit to a method named DrawBoard. | | 01:26 | This method is called each time | | 01:28 | I need to repaint the screen. | | 01:31 | And right here on line 71, there's a | | 01:34 | reference to resources.chessboard. | | 01:37 | That's the compiled graphic. | | 01:40 | So, to display that image I'm creating a .bmp object | | 01:44 | wrapped around the chessboard resource, | | 01:46 | and then drawing it on the screen, | | 01:48 | and scaling it to the size of the rectangle. | | 01:52 | That's the size of the application window itself. | | 01:56 | Then, I move down here a little further and show that | | 02:00 | I'm using the QueenImage as a resource here. | | 02:03 | This is the code that's deciding | | 02:05 | where to place the queen image. | | 02:07 | It's going to place eight of these queens for each solution. | | 02:10 | And I'll go through some of this other logic later. | | 02:13 | But the important thing here is that | | 02:15 | I have a reference to the resource | | 02:18 | and once again, I'm using the DrawImage | | 02:19 | method to draw it on the screen. | | 02:22 | This all happens incredibly fast, and again, | | 02:26 | it happens each time I'm displaying | | 02:28 | a new solution to the problem. | | 02:31 | So, now I'll run the application again, | | 02:33 | and I'll click the button, and I see both | | 02:36 | the chessboard image and the eight | | 02:38 | instances of the QueenImage resource. | | 02:42 | So, that's how the user interface is being displayed. | | 02:45 | Next, I'll get to the code that's | | 02:47 | calculating the 92 distinct solutions. | | Collapse this transcript |
| Calculating the 92 distinct solutions| 00:00 | - As my application starts up, it automatically loads | | 00:03 | form one, the automatically generated form that's created | | 00:08 | when you create a new Windows form project in Visual Studio. | | 00:12 | The form one load method starting at line 22 | | 00:14 | is executed automatically. | | 00:18 | There's a private field up here, | | 00:20 | an instance of my chess board class. | | 00:23 | It's instantiated at line 19. | | 00:26 | Then, a method of that chess board class | | 00:29 | called "GetSolutions" is called. | | 00:31 | I'll dig down into that method in a moment | | 00:33 | because it's critical to understanding this solution, | | 00:36 | but I'll also point out that there's a method here | | 00:39 | that adds the chess board to the current form. | | 00:43 | And that's how the chess board is being displayed. | | 00:47 | Now let's look at the GetSolutions method. | | 00:50 | This is where the guts of the solution resides. | | 00:53 | I'll go do the definition of that method, | | 00:56 | which is in my chess board class. | | 01:00 | In this class, there's a list named Queens. | | 01:04 | The purpose of this list is to contain | | 01:07 | eight instances of another class called Queen. | | 01:11 | I'll go to the definition of the list, | | 01:14 | and then go to the definition of the class Queen. | | 01:19 | The Queen class has two private fields | | 01:21 | called mrow and mcolumn, both integers, | | 01:24 | a constructor method that initializes those values to zero, | | 01:29 | another constructor that lets you create an instance | | 01:32 | of the Queen class and set those values, | | 01:35 | and then public getters and setters for both fields. | | 01:39 | This is a pretty straightforward data class. | | 01:43 | Each instance of the class will represent one queen | | 01:46 | on the chess board. | | 01:48 | I'll close that and I'll go back to my chess board class. | | 01:52 | Then, from there, I'll find the GetSolutions method again. | | 01:58 | When the GetSolutions method is called, | | 02:00 | I first clear that list. | | 02:02 | It's empty. | | 02:04 | And then there's another method named "ResetCells." | | 02:08 | That method is emptying an array | | 02:10 | of Boolean values called "cells." | | 02:14 | It's declared with this syntax: | | 02:16 | bool, meaning Boolean, then a pair of brackets and a comma. | | 02:21 | In C Sharp, that means, "Create a two-dimensional array." | | 02:27 | The underlying private field named mcells | | 02:30 | is being instantiated here. | | 02:33 | It's being declared with eight positions in each dimension, | | 02:37 | one for the horizontal and one for the vertical. | | 02:40 | So an item with index of zero and zero would be the top left, | | 02:45 | and an item with an index of seven and seven | | 02:48 | would be the lower right. | | 02:50 | Those two values together would then equate | | 02:52 | to a true or false value. | | 02:55 | If it's true, that means a queen is in that position. | | 02:58 | And if it's false, that means | | 03:00 | that square on the chess board is blank. | | 03:03 | That's how we're going to track our solutions. | | 03:06 | Now I'll go back to the GetSolutions method again. | | 03:12 | After calling ResetCells to reset that array, | | 03:15 | I call a method named DrawBoard. | | 03:18 | This is the method where I'm displaying the chess board. | | 03:23 | When I start up the application, | | 03:24 | the mcells's multidimensional array has 64 items. | | 03:30 | Eight times eight. | | 03:32 | Initially, they're all false. | | 03:34 | And so, when I draw the board, I get to this code, | | 03:38 | which is looking for items in the mcells's array | | 03:40 | that are true. | | 03:42 | It doesn't find any, and so no queens | | 03:44 | are drawn on the board. | | 03:46 | This explains why, as the application starts up, | | 03:49 | it can display the chess board but no chess pieces. | | 03:56 | Once again, let's go back to the GetSolutions method. | | 04:00 | We've cleared to the queens list. | | 04:02 | We've reset the cells, and we've drawn the board. | | 04:05 | Next, we're going to do some looping. | | 04:09 | First, there's a for loop that's looping eight times, | | 04:12 | from zero through seven. | | 04:14 | So the list will contain eight queen objects. | | 04:19 | Then there's a bit of code that's setting | | 04:20 | the row and column of each queen object. | | 04:24 | At this top level of the code, the row's being set to zero, | | 04:28 | meaning the first row, | | 04:29 | and the column is being set to the next available column. | | 04:34 | Then there's a call to a method named PlaceQueen. | | 04:37 | That's where we'll go next. | | 04:40 | The PlaceQueen method is designed to be called recursively. | | 04:43 | That is, the first time you call it, it'll call itself | | 04:46 | again and again and again, so that it's called eight times, | | 04:51 | once for each row or column in the set. | | 04:54 | This first bit of code will only be called | | 04:56 | on the eighth iteration, | | 04:58 | and it's only going to decide that a cell is valid | | 05:02 | if it passes some tests. | | 05:04 | But the important bit of code that's called | | 05:06 | every single time is down here. | | 05:09 | Each time this bit of code is called, | | 05:11 | it's going to place a queen in a particular position. | | 05:15 | Then it's going to check that position | | 05:17 | and make sure that it hasn't created a conflict. | | 05:21 | The logic for that is in the method CheckAll, | | 05:24 | and we'll jump to that. | | 05:27 | Here's the code that's checking for an existing conflict. | | 05:31 | It's looking for three possible conflicts: | | 05:34 | horizontal, vertical, and diagonal. | | 05:38 | The horizontal check is pretty easy. | | 05:41 | You look at the row properties for each queen, | | 05:43 | and if they match, that means it won't work. | | 05:47 | Then you do the same thing for the columns. | | 05:50 | For the diagonal check, there's a calculation here. | | 05:53 | It's saying, if the sum of the row and the column | | 05:57 | for the first queen match the sum for the second queen, | | 06:01 | or the row minus the row of the first and second queen, | | 06:05 | and the same calculation for the columns, | | 06:08 | if any of those come out true, then we say, | | 06:11 | this position isn't going to work and we return false. | | 06:15 | But if we pass all of those tests, we return true. | | 06:19 | That means that we found a queen that can be put | | 06:22 | in that position. | | 06:24 | Going back to the PlaceQueen method | | 06:25 | from which this was called, | | 06:27 | we say that, if the CheckAll method worked, | | 06:30 | then we decide we placed a queen and we're ready | | 06:33 | to go on to the next one. | | 06:35 | Finally, when we get to the last recursion, | | 06:38 | that is, the eighth call, or level number seven, | | 06:41 | taking into account the zero-based offset, | | 06:44 | then we can decide whether a queen can be placed | | 06:47 | in a particular position. | | 06:49 | Here we're back to the mcells's two-dimensional array. | | 06:54 | If the row and the column worked out well, | | 06:56 | then we set the value in that position to true, | | 06:59 | and otherwise, we set it to false. | | 07:02 | After all the calls to the PlaceQueen method, | | 07:05 | we'll end up with 92 arrays in this solutions list, | | 07:10 | and that's the list that we're looping through | | 07:12 | when we're displaying them on the chess board. | | 07:17 | So, if I start the application, | | 07:22 | as the application starts up, all the looping | | 07:25 | from calling PlaceQueen and the checking method and so on | | 07:28 | is being done, and I have an array with my solutions. | | 07:32 | And I can loop through and display them. | | 07:35 | So, finally, in the last movie of this chapter, I'll show you | | 07:39 | how I'm managing that solutions list at run time | | 07:42 | and letting the user see each of the possible solutions. | | Collapse this transcript |
| Displaying the solutions| 00:00 | -The chessboard class in my solution | | 00:02 | contains all of the logic, both to solve the | | 00:05 | problem, and to contain the solutions. | | 00:08 | And the solutions are stored in | | 00:10 | this list object, called msolutions. | | 00:13 | It's a list, and each item in the list | | 00:16 | is a two dimensional array. | | 00:18 | Within the array, just like M cells, | | 00:21 | there are 64 items, indexed by row and column. | | 00:26 | And each item has a true or a false value. | | 00:30 | Access to the msolutions field is | | 00:32 | provided by the solutions list, | | 00:35 | which has a getter and a setter. | | 00:39 | Now, as the application starts up, | | 00:42 | the code in form one is executed, | | 00:45 | and it calls the get solutions method | | 00:47 | to find all of the solutions. | | 00:50 | So by the time the chessboard is | | 00:52 | displayed, all of the solutions are | | 00:53 | already found, and they're available through | | 00:56 | the solutions property of the chessboard object. | | 01:00 | Now, as the user clicks the button, they'll | | 01:03 | execute an event handler method, and this is | | 01:06 | where we're seeing each solution in turn. | | 01:10 | So here's the logic that's doing that. | | 01:13 | It's in the method button one click. | | 01:17 | At the top of this class, there's a field | | 01:18 | called solution number, initialized to zero. | | 01:23 | Each time the user clicks the button, | | 01:25 | we go get the solution from that list, | | 01:28 | using solution number as an index. | | 01:32 | And that's the two dimensional array | | 01:33 | that we're going to represent visually. | | 01:37 | We're taking that two dimensional array | | 01:39 | and saving it as the cells field. | | 01:41 | Then we're calling the method draw board. | | 01:44 | Which is a member of the chessboard class. | | 01:48 | And once again, this code was executed | | 01:50 | as the application started up. | | 01:52 | But this time, it's dealing with a particular | | 01:55 | array, one of the 92 solutions. | | 01:59 | And here's the code that's deciding | | 02:01 | where to place each queen. | | 02:05 | First of all, there's a bit of conditional | | 02:06 | code, making sure that the chessboard | | 02:09 | isn't completely invisible. | | 02:12 | Then, there's code to draw the board | | 02:13 | itself, that I've previously described. | | 02:16 | Here's where we're using the chessboard resource. | | 02:19 | Then, there's two four-loops, one inside the other. | | 02:23 | One representing rows, the other columns. | | 02:27 | Within the inner four-loop, we look at | | 02:29 | the item in the m-cells array. | | 02:33 | Then I'm checking whether the item | | 02:35 | at that position of the array | | 02:36 | has a value of true or false. | | 02:39 | If it's true, then the following code will be executed. | | 02:44 | First, we create a rectangle object. | | 02:48 | And there's a calculation here to place the | | 02:50 | rectangle on the chessboard in the position | | 02:53 | that equates to this row and column. | | 02:57 | Then I'm creating a bit map, | | 03:00 | and I'm using the queen image resource | | 03:03 | and drawing the image to fill that rectangle. | | 03:07 | In a full solution, this block of code | | 03:10 | will be executed eight times, | | 03:12 | once for each of the eight queens | | 03:14 | that's being placed on the chessboard. | | 03:18 | And then, finally, at the end of the code, | | 03:20 | there's a call to the invalidate method, | | 03:22 | which means repaint the user interface. | | 03:27 | This bit of code is executed each time | | 03:29 | the user clicks that button. | | 03:32 | This time, and this time, and this time, and so on. | | 03:38 | The queen graphics are being placed | | 03:39 | using that logic, that's deciding where | | 03:42 | the rectangle will be created, and then | | 03:44 | filling the rectangle with the queen image resource. | | 03:49 | So there are three major steps to | | 03:51 | understanding this application. | | 03:53 | And I've described them each in turn. | | 03:56 | First, generating and managing the user interface. | | 04:00 | Then, the logic that finds all the 92 distinct | | 04:03 | solutions to the eight queens problem. | | 04:07 | And then more user interface control | | 04:10 | that allows the user to cycle through | | 04:11 | the solutions and see them one at a | | 04:14 | time in the Windows forms application. | | 04:18 | I encourage you to explore this code further, | | 04:21 | and experiment with it, trying different things. | | 04:24 | And as I mentioned earlier in this chapter, | | 04:27 | this application was based on an existing | | 04:29 | application that was built with VB.net. | | 04:32 | If you're a Visual Basic programmer, | | 04:35 | try that version of the application out, | | 04:37 | and you can learn a little bit about | | 04:39 | solving complex algorithm problems | | 04:42 | using any .net based language. | | Collapse this transcript |
|
|
Problem Four: Accessing PeripheralsBuilding a musicial instrument using mouse movements| 00:06 | - Hello and welcome to Code Clinic. | | 00:08 | My name is David Gassner. | | 00:11 | Code Clinic is a monthly course | | 00:13 | where a unique problem is introduced | | 00:15 | to a collection of lynda.com authors. | | 00:18 | In response, each author will create a solution | | 00:20 | using their programming language of choice. | | 00:23 | You can learn several things from Code Clinic. | | 00:26 | Different approaches to solving a problem. | | 00:28 | The pros and cons of different languages. | | 00:31 | And some tips and tricks to incorporate | | 00:33 | into your own coding practices. | | 00:35 | This month, the problem is to create | | 00:37 | a musical instrument controlled by a mouse. | | 00:41 | Move the mouse up and down to change the pitch. | | 00:44 | Move it side to side to change the volume. | | 00:47 | The instrument is silent until we | | 00:49 | click and hold one of the mouse buttons. | | 00:52 | Letting go of the button turns off the musical note. | | 00:55 | It's a simple request, but because it requires | | 00:58 | access to the mouse, it may be difficult | | 01:01 | or impossible in some languages. | | 01:03 | In all cases, we'll explore some ways | | 01:05 | to solve the problem and learn why they might not work. | | 01:10 | Thomas Edison observed. "Negative results are just what I | | 01:13 | "want, they're just as valuable to me as positive results. | | 01:17 | "I can never find the thing that does the job | | 01:19 | "best until I find the ones that don't." | | 01:23 | You may want to take some time | | 01:25 | and solve the problem yourself. | | 01:27 | In the next videos, I'll explain | | 01:29 | how I answered this challenge. | | Collapse this transcript |
| Overview of my solution| 00:00 | - In this month's challenge, | | 00:02 | I'm creating a simple musical instrument, | | 00:04 | a Windows application that responds to mouse clicks | | 00:08 | and moves, and plays an audio sound in response. | | 00:12 | This application has some specific requirements. | | 00:17 | It needs access to all the audio capabilities of a computer, | | 00:20 | and it has to know how to handle | | 00:22 | media files and other sounds. | | 00:25 | So I need complete access | | 00:27 | to the computer's operating system. | | 00:30 | That points me to desktop environments. | | 00:32 | So for this application, I'll be working in | | 00:35 | Windows Presentation Foundation, or WPF. | | 00:40 | I did three different versions of this application, | | 00:43 | starting from a very simple strategy | | 00:46 | to a more complex and powerful one. | | 00:49 | In the first application, I took advantage of | | 00:52 | Windows Systems Sounds, things like beep. | | 00:56 | These are exposed in the .NET API as console methods. | | 01:00 | This bit of code, System.Console.Beep, | | 01:05 | passing in values of 440 and 3,000, | | 01:08 | means make a beeping sound at 440 hertz. | | 01:12 | That's the pitch, or frequency, of the sound. | | 01:16 | It will have a duration of | | 01:18 | 3,000 milliseconds, or three seconds. | | 01:22 | Here's what the finished application looks like. | | 01:25 | This application just has a single action. | | 01:28 | It plays a beep, and it does it | | 01:31 | when I click this large button. | | 01:33 | (beep) | | 01:37 | The advantages of using System Sounds | | 01:39 | are that they're very simple and easy to code. | | 01:42 | That action took exactly one line of code, | | 01:45 | but it's very static. | | 01:48 | With System Sounds, you can't control the volume | | 01:50 | or the pitch, or frequency. | | 01:53 | This technique, while it's easy, | | 01:56 | doesn't satisfy the application requirements. | | 02:00 | The second strategy I tried was using an audio file. | | 02:04 | I started with a WAV file and I fed it | | 02:06 | into a class called MediaPlayer. | | 02:09 | Then I played the WAV file, | | 02:12 | and allowed the user to adjust the volume. | | 02:14 | Here's what it looks and sounds like... | | 02:19 | Now, when I click the mouse the sound will start, | | 02:23 | and when I release the mouse the sound will end. | | 02:25 | (beep) | | 02:27 | (beep) | | 02:29 | When I move the cursor from side to side, | | 02:32 | it'll affect the volume. | | 02:34 | (beep fading in and out) | | 02:42 | The advantage of this strategy, is that I'm using | | 02:45 | a class that's built into the .NET framework | | 02:48 | and I have control over the sound's volume. | | 02:51 | But I still haven't satisfied the application requirements | | 02:54 | because with the MediaPlayer, I can't control the frequency, | | 02:58 | also known as the pitch. | | 03:01 | So I tried a third strategy. | | 03:04 | In this example, I'll generate my own custom sound | | 03:08 | instead of starting with a WAV file. | | 03:11 | I'll be using a library called NAudio. | | 03:15 | This library is completely free and open source. | | 03:18 | So you can incorporate it into your applications | | 03:21 | without paying any royalties. | | 03:23 | The NAudio library is able to generate sounds from scratch, | | 03:27 | and then you can affect both volume and pitch as needed. | | 03:31 | Here's what it looks and sounds like... | | 03:36 | Once again, in this application, when I hold down | | 03:39 | the mouse button I will play a tone, | | 03:41 | and when I release the mouse, it'll stop. | | 03:44 | (beep) | | 03:46 | But now I have control over both volume and pitch. | | 03:49 | I'll control the pitch by moving the cursor up and down, | | 03:52 | and the volume by moving side to side. | | 03:55 | (beep fluctuating in pitch) | | 04:00 | (beep fading in and out) | | 04:05 | So the NAudio library satisfies | | 04:07 | the application requirements. | | 04:09 | It gives me control over the volume | | 04:11 | and frequency of the sound. | | 04:13 | There's still one drawback. | | 04:15 | As you play with this application, you'll see that | | 04:18 | it doesn't handle frequency changes quite perfectly. | | 04:21 | You sometimes hear little clicks and pops in the sound. | | 04:25 | When I get to that code, I'll describe where those clicks | | 04:28 | are coming from, and what you might have to do | | 04:30 | to solve this problem completely. | | 04:33 | Before we get to the code, I want to | | 04:35 | acknowledge a few different sources. | | 04:37 | As is so commonly the case in programming, | | 04:40 | I was only able to solve this problem | | 04:43 | with the help of existing code. | | 04:45 | First, there's the NAudio library. | | 04:48 | I was able to download it into Visual Studio | | 04:51 | using the NuGet framework that's built into | | 04:53 | the Visual Studio IDE. | | 04:56 | But you can also download NAudio from its website | | 04:59 | at naudio.codeplex.com. | | 05:02 | At that website, you'll find the downloadable binaries | | 05:06 | plus documentation and examples of all sorts of things | | 05:09 | that you can do with this incredibly valuable library. | | 05:13 | Also, my application depends highly on code | | 05:16 | that was created by a programmer named Mark Heath. | | 05:19 | In fact, you'll see that some of the code is taken | | 05:22 | verbatim from a blog post that he put up in 2010. | | 05:27 | You can find that blog post at this URL. | | 05:30 | So let's get started looking at the code | | 05:33 | that I used to solve this month's challenge. | | Collapse this transcript |
| Playing system sounds| 00:00 | - The Exercise Files for this chapter | | 00:02 | are arranged in a single Solution File | | 00:05 | that you can use in Visual Studio. | | 00:07 | You can either open them up in a Visual Studio Edition | | 00:10 | that's paid for license, | | 00:12 | I'll be using Visual Studio Professional, | | 00:15 | or you can use the Free Edition, | | 00:17 | Visual Studio Express 2013 for Windows Desktop. | | 00:23 | To open the Solution, go to the Audio Players Folder. | | 00:27 | You'll find an Audio Players Solution File, | | 00:30 | and then one chapter for each project. | | 00:33 | I'm going to start describing the Beep project, | | 00:36 | but there are also Folders for PlayWav | | 00:39 | and for the project that uses the NAudio library. | | 00:43 | To open the Solution, just double-click the Solution File. | | 00:47 | If you have a correct version of Visual Studio installed, | | 00:50 | it should open automatically in that version. | | 00:53 | Notice that the Beep project | | 00:55 | is automatically set as the StartUp Project. | | 00:58 | You can change that by right-clicking | | 01:01 | and choosing Set as StartUp Project, | | 01:04 | and so, when I click the Start button, | | 01:06 | I'll fire up that application. | | 01:09 | As I described previously, this is a very simple application | | 01:13 | that plays a single System Sound. | | 01:16 | When I click the large Beep button, | | 01:18 | that will play a System Sound, | | 01:21 | a beep sound that's set at a frequency or a pitch | | 01:24 | of 440 Hz and will last for three seconds. | | 01:29 | (beep plays) | | 01:33 | Let's take a look at the code that plays that sound. | | 01:36 | I'll open the project here in the Solution Explorer, | | 01:40 | and then double-click on MainWindow.xaml | | 01:44 | Windows Presentation Foundation | | 01:46 | defines its user interface in xaml, | | 01:49 | the xml-based language that affects the marker. | | 01:53 | Here's the definition of the button in xaml. | | 01:55 | It has a name of PlayBtn and, when I click it, | | 01:59 | it executes this method, PlayBtn_Click. | | 02:05 | I'll press the F12 key and that will take me | | 02:08 | to the xaml files code behind file, | | 02:11 | named Windows.xaml.cs | | 02:15 | You can also reach that file through the Solution Explorer | | 02:19 | by opening up the tree control next to MainWindow.xaml | | 02:22 | and you'll find the code file there. | | 02:25 | Here's the implementation of the method, | | 02:28 | and this is all the code you need | | 02:29 | to play a simple System Sound. | | 02:31 | I have two integer variables. | | 02:34 | The frequency is set at 440, | | 02:37 | and the duration at 3,000. | | 02:39 | That's a frequency or pitch of 440 Hz | | 02:43 | and a duration of 3,000 milliseconds, or three seconds. | | 02:48 | Then, to make the sound play, | | 02:50 | I call the method System.Console.Beep | | 02:54 | and pass in those two values. | | 02:56 | If I wanted to make this sound a little bit shorter, | | 02:59 | I could change the duration here. | | 03:01 | I'll make it one second, and run the application again, | | 03:06 | and now, when I click the button, it'll be shorter, | | 03:10 | (beep plays) | | 03:13 | and I'll return it back to 3,000, | | 03:16 | and that's all there is to it. | | 03:18 | It takes, really, just a single line of code | | 03:21 | to play a System Sound. | | 03:23 | That's great, it's easy to do, easy to debug, | | 03:27 | and almost foolproof, but it doesn't do | | 03:30 | what we need the application to do. | | 03:32 | We need to play a tone that we can affect | | 03:34 | after it's started, and we need to be able to change | | 03:38 | both the volume and the frequency, | | 03:40 | and this coding technique doesn't allow that. | | 03:43 | So next, I'll take a look at a slightly more complex, | | 03:47 | but much more powerful, application | | 03:50 | that uses a pre-existing sound file, | | 03:52 | in this example a wav file, | | 03:55 | and a .net class called The Media Player. | | Collapse this transcript |
| Playing an existing audio file| 00:00 | - Going from simple to complex and from weak to powerful, | | 00:04 | my next step is to work with preexisting sound files. | | 00:08 | And here I'll use the project named Play Wave File. | | 00:13 | I'll right click on it and choose Set a Startup Project. | | 00:17 | Then I'll click the start button | | 00:19 | and that runs that application. | | 00:23 | As I described previously, this application | | 00:26 | uses a preexisting sound file and lets the user | | 00:29 | affect the volume by moving the cursor from side to side. | | 00:33 | When I hold down the cursor, it plays the sound | | 00:36 | and when I release it, it stops. | | 00:38 | (Beeps) | | 00:41 | And now I'll show the volume control. | | 00:43 | (Beeps louder then softer) | | 00:51 | So here's how this application is put together. | | 00:54 | First, here's the wave file. | | 00:58 | You can create this wave file | | 00:59 | with any audio system you like | | 01:01 | and all you need to do to use it | | 01:04 | in your application is to add it | | 01:06 | to your Visual Studio project. | | 01:09 | And here's the code that I'm using. | | 01:12 | I'll open the file MainWindow.xaml.cs | | 01:17 | and then I'll look at it in full screen. | | 01:21 | At the top of the class, I'm creating an instance | | 01:24 | of the MediaPlayer class. | | 01:27 | This class is a part of the .net framework. | | 01:31 | You don't need to import any libraries | | 01:32 | but you do need to include a using statement | | 01:36 | for System.Windows.Media. | | 01:39 | Then I've declared a couple of other fields, | | 01:41 | a boolean value named isMouseDown | | 01:44 | and a point named lastPoint. | | 01:48 | I'll be using these values to track the mouse gestures. | | 01:52 | In the constructor method for the MainWindow, | | 01:55 | I'm initializing my media player, that's on line 23. | | 02:00 | I call the player object Open method | | 02:03 | and I pass in a Uri wrapped around the name of the file. | | 02:07 | Notice that when I construct the URI I'm passing | | 02:10 | in an argument of UriKind.Relative. | | 02:13 | That means look for the file in this project, | | 02:16 | not in an explicit place on the hard disk. | | 02:20 | That makes this project portable from computer to computer. | | 02:25 | Then I'm initializing the volume of the player at 0.5. | | 02:28 | The volume can be set in a range from zero to one, | | 02:32 | and it supports, obviously, fractional values. | | 02:35 | Then I'm calling a method named DisplayValues. | | 02:40 | That's at the bottom of the code. | | 02:42 | It gets the current volume from the player object, | | 02:45 | does a little bit of math on it to make it presentable | | 02:48 | and then displays that value | | 02:50 | followed by a percentage character. | | 02:53 | And that's how I'm displaying | | 02:55 | the current volume on the screen. | | 02:57 | Let's see that in action. | | 02:59 | I'll run the application again. | | 03:01 | And there's the value that was calculated | | 03:03 | by the DisplayValues method | | 03:06 | and here's how it behaves | | 03:08 | as I change the volume at runtime. | | 03:10 | (Beeps) | | 03:13 | Now we'll go back to the code. | | 03:17 | Next I have a couple of event handler methods | | 03:20 | named MouseLeftButtonDown and MouseLeftButtonUp. | | 03:24 | In each of these, I changed the value | | 03:26 | of isMouseDown so I can track | | 03:28 | whether the mouse is currently down at any moment, | | 03:31 | and then I either play or stop the player. | | 03:35 | Notice I don't have to reload the wave file every time. | | 03:38 | That was already done in the constructor method. | | 03:41 | So when I play it, I'm playing the preselected tone | | 03:44 | and when I stop it I'm just stopping it. | | 03:48 | Then finally the code for managing the volume | | 03:50 | is in the method WindowMouseMove. | | 03:54 | Once again, this is an event handler and it's reacting | | 03:57 | whenever the mouse moves up or down or from side to side. | | 04:02 | First I'm getting the current mouse position. | | 04:06 | That's returned as a point object. | | 04:09 | A point has X and Y values to indicate the horizontal | | 04:12 | and vertical position of the mouse cursor. | | 04:17 | Then I have a bit of conditional code. | | 04:20 | Notice that I'm only processing the mouse move | | 04:22 | if the mouse button is currently held down. | | 04:26 | If the mouse button is up, I'm ignoring it. | | 04:30 | The next step is to get the delta, | | 04:32 | the amount of change that happened | | 04:34 | since the last mouse move. | | 04:37 | And I'm getting both a vertical | | 04:38 | and a horizontal Delta because I want | | 04:41 | to find out whether the mouse moved mostly up | | 04:44 | and down, or mostly side to side. | | 04:48 | Then I have another conditional clause starting at line 55 | | 04:51 | and I'm asking the question, if the value | | 04:54 | of the horizontal Delta is greater than the value | | 04:58 | of the vertical Delta, that means it was a side to side move. | | 05:03 | I'm actually going to ignore mouse moves | | 05:05 | that are mostly vertical at this point. | | 05:07 | Then I take the Delta value, the amount | | 05:10 | that the mouse moved from the last position | | 05:13 | and I divide it by 200 and then I add | | 05:17 | that value to the current value. | | 05:20 | Now here's why I'm doing that division operation. | | 05:23 | The Delta comes to me as an integer value, | | 05:26 | but I need to express the volume as a fractional value | | 05:31 | and I chose the value 200 because that allows me | | 05:34 | to affect the volume relative to the amount | | 05:36 | of space I have from side to side of the window. | | 05:41 | So I'm adding the Delta to the volume. | | 05:44 | If the mouse moves to the right, | | 05:46 | I have a positive Delta that will increase the volume | | 05:49 | and if it moves to the left, I'll have a negative delta | | 05:53 | and that will decrease the volume. | | 05:56 | Then once again, I call the DisplayValues method | | 05:58 | to update the label at the top of the screen | | 06:01 | and show the user what the value is of the current volume. | | 06:05 | Finally, I save the current point using | | 06:09 | that field lastPoint, so the next time I execute | | 06:12 | this method I'll be able to get new Deltas, | | 06:15 | new calculations of the change in the mouse position. | | 06:20 | And so let's once again run the application | | 06:23 | and see how all that code is working. | | 06:26 | I'll click the start button, | | 06:28 | and once again I'll hold down the mouse | | 06:31 | and move the cursor from side to side. | | 06:33 | Notice when I move it up and down, | | 06:36 | nothing will change. | | 06:38 | (Beeps) | | 06:44 | So this application is solving part of the problem. | | 06:47 | I'm allowing the user to control the volume of a tone, | | 06:51 | but I still haven't satisfied the entire specification | | 06:54 | because I also need to allow the user | | 06:57 | to control the pitch and I'll show you | | 06:59 | how I did that in the next project. | | Collapse this transcript |
| Creating sounds with the NAudio library| 00:00 | - The last I'll describe is named NAudioWaveProvider. | | 00:05 | I'll set it as the Startup Project | | 00:08 | and then I'll run it. | | 00:10 | And in this project the user can control | | 00:13 | both the volume and the pitch. | | 00:15 | The initial pitch is 440 Hertz | | 00:18 | and the volume is showing "Off". | | 00:21 | But when I hold the mouse down | | 00:23 | the tone starts and I can control the volume | | 00:26 | moving the mouse from side to side, | | 00:28 | just as I did before, | | 00:30 | but now I can also control the pitch. | | 00:32 | (digital tone) | | 00:42 | So let's see how this project is put together. | | 00:45 | First I'll show you that I've added a library | | 00:48 | using the NuGet Framework. | | 00:51 | I went to the NuGet Package Manager | | 00:54 | and I installed the NAudio library. | | 00:58 | NAudio is an open-source library | | 01:01 | for controlling audio on Windows-based computers. | | 01:04 | It can be used with .NET applications | | 01:06 | using a variety of languages. | | 01:09 | Most of the documentation and examples | | 01:11 | you find are built with C# | | 01:14 | but you also see examples that are built with C++. | | 01:18 | So, once you've added the library to your project | | 01:22 | you can use its classes, | | 01:24 | and here's what I've done. | | 01:27 | First, I have a class named WaveProvider32. | | 01:32 | As I described previously, | | 01:34 | this code is right from a blog post | | 01:36 | by the programmer Mark Heath | | 01:38 | and you'll see the link to that blog post | | 01:40 | here in the code. | | 01:43 | This is an abstract class | | 01:44 | that's implementing an interface named IWaveProvider. | | 01:49 | In the constructor method it's setting default values | | 01:53 | and setting the WaveFormat, | | 01:55 | and it implements a Read method. | | 01:58 | It executes some calculations | | 02:01 | and then returns a simple WaveProvider. | | 02:05 | Then there's a subclass named SineWaveProvider, | | 02:09 | and here's where the default values | | 02:11 | for the frequency and the amplitude are set. | | 02:15 | Notice that the default frequency is set at 440 | | 02:19 | and this would explain why the tone sounds | | 02:21 | like it does when you first play it. | | 02:24 | Then there's an override of the Read method, | | 02:27 | and once again it's doing some basic math | | 02:30 | and returning the sampleCount. | | 02:33 | Those two classes are pretty much the same | | 02:35 | as what Mark Heath provided in his example, | | 02:38 | and here's how I've used these classes in my application. | | 02:43 | I'll go to my main window XAML file. | | 02:46 | The user interface is similar to the WaveFile example, | | 02:50 | I once again have labels to show | | 02:52 | the user what's going on, | | 02:54 | and I'll be affecting these labels at runtime. | | 02:57 | I'll go directly to the C# code behind file | | 03:01 | and show that I've declared an instance | | 03:03 | of the SineWaveProvider32 class. | | 03:06 | That's this custom class that I already showed. | | 03:10 | Then just as I did in the previous project, | | 03:13 | and then just as I did in the previous project, | | 03:16 | at lines 19 and 20, | | 03:18 | I have fields that allow me | | 03:19 | to manage the mouse gestures. | | 03:22 | In the main window's constructor method | | 03:25 | I'm initialing my SineWaveProvider object | | 03:28 | and setting its WaveFormat. | | 03:31 | And then I'm creating an instance | | 03:32 | of a class named WaveOut, | | 03:35 | this is essentially the sound player | | 03:37 | in the NAudio framework. | | 03:39 | It does the same sort of work as the media player | | 03:43 | but it can work with custom sounds, | | 03:45 | whereas the media player has to work | | 03:47 | with pre-built sound files. | | 03:50 | I'm initializing the player by providing the WaveProvider, | | 03:54 | that's how my defined tone is getting to the player, | | 03:57 | and then I'm setting the volume. | | 03:59 | Notice that in this example I have the suffix "f", | | 04:03 | meaning that this is a float value, | | 04:06 | and that's because the volume property | | 04:08 | of the WaveOut object is also a float. | | 04:12 | Then I have a call to my DisplayValues method. | | 04:15 | Here's the implementation | | 04:16 | of DisplayValues for this application. | | 04:19 | First, I have slightly different logic here. | | 04:22 | If the mouse isn't currently down | | 04:25 | I set the label that's showing the volume | | 04:27 | to a string of "Off", | | 04:29 | but if the mouse is down | | 04:31 | then I detect the volume, | | 04:33 | do a little bit of math, | | 04:35 | and then display the volume | | 04:36 | followed by the percentage sign. | | 04:40 | Next I determine the current frequency, | | 04:42 | and notice I'm going right back to the WaveProvider | | 04:45 | to find out what the current frequency is, | | 04:48 | and once again I'm displaying it. | | 04:51 | So now let's go back | | 04:52 | and analyze the MouseMove method, | | 04:55 | that's where the bulk of the work is happening. | | 04:58 | Just as with the WaveFile project | | 05:00 | I'm determining whether the mouse is currently down | | 05:03 | and I'm only processing the mouse gestures if it is. | | 05:08 | I'm once again getting the horizontal and vertical deltas, | | 05:11 | and I once again have some conditional code. | | 05:14 | But this time I'm handling both | | 05:16 | horizontal and vertical changes. | | 05:19 | For the horizontal changes I'll affect the volume. | | 05:23 | Notice there's some more complex code here. | | 05:27 | I discovered in my experimentation | | 05:29 | that if I tried to set the volume of the player object, | | 05:33 | that's the WaveOut object, | | 05:35 | to a value greater than one | | 05:37 | it would throw an exception, | | 05:39 | so I'm saving the current volume before I try to change it | | 05:43 | and then I change the volume by the value of the delta, | | 05:47 | but if this throws an exception | | 05:50 | I restore the old volume | | 05:51 | and ignore everything else. | | 05:54 | Now, if the user moves the cursor up and down, | | 05:57 | that means they wanna change the pitch, | | 06:00 | and here's the code that's doing that. | | 06:02 | The pitch is known as the frequency, | | 06:05 | so just as I did with the volume | | 06:07 | I'm first saving the current frequency | | 06:10 | and then I have a little bit of code | | 06:12 | that's making sure that the frequency | | 06:14 | hasn't gone below zero. | | 06:17 | I discovered, again in testing, | | 06:19 | that when I allow the frequency to go into a negative value | | 06:23 | it did some very unexpected things. | | 06:25 | You'll see, if you experiment with it, | | 06:28 | that it loops back to a higher value | | 06:30 | instead of stopping at zero or throwing an exception, | | 06:33 | so I have an explicit check in my code. | | 06:36 | Now, if the vertical delta is greater than zero, | | 06:40 | and if the frequency isn't already set at zero, | | 06:44 | then I set the increment value to a value of 10, | | 06:48 | and notice once again that's a float, not an integer, | | 06:51 | otherwise I set it to a negative 10, | | 06:55 | and then finally I affect the frequency. | | 06:58 | Notice I'm using the "-=" operator. | | 07:01 | Again, experiment with that to see | | 07:03 | what would happen with "+=". | | 07:05 | Finally, if I hit that ArgumentOutOfRangeException | | 07:09 | I bail and I reset the frequency | | 07:11 | to whatever the last frequency was. | | 07:15 | And after all that code is executed | | 07:17 | I once again update the screen | | 07:19 | by calling my DisplayValues method. | | 07:22 | And then, just as I did in the WaveFile project, | | 07:26 | I remember the current mouse position | | 07:28 | so I can calculate the deltas again | | 07:31 | the next time this event happens. | | 07:34 | I'll click the Start button | | 07:36 | and test the application again. | | 07:38 | (digital tone) | | 07:45 | Now you might be wondering | | 07:46 | why I'm allowing the frequency to jump like that | | 07:49 | rather than creating a smooth change | | 07:51 | from one frequency to the next. | | 07:54 | Let's what happens when I change | | 07:55 | the increment values from 10 to one. | | 08:00 | I'll change it and start the application again, | | 08:03 | and then play the sound. | | 08:05 | (digital tone) | | 08:12 | And you hear those clicks happen. | | 08:14 | That's happening because of a problem | | 08:16 | that's common in dealing with audio output. | | 08:20 | Changes in the frequency | | 08:21 | that are very close together | | 08:23 | will frequently emit those sort of clicks, | | 08:26 | and to solve this problem I discovered | | 08:28 | I would need to move to another language, | | 08:30 | I would need to get into C++ | | 08:33 | where I have lower level access to system audio properties. | | 08:38 | So, I decided that this had to be good enough. | | 08:43 | I went back and re-read my specifications. | | 08:46 | The specification said that the frequency had to change | | 08:49 | in reaction to mouse moves, | | 08:52 | it didn't say it had to be smooth | | 08:54 | and it didn't say how much it had to change, | | 08:57 | and so I decided that this | | 08:58 | was an acceptable solution. | | 09:01 | As with many programming projects, | | 09:03 | I found that solving the last 10 percent of the problem | | 09:07 | could take as much as 70 or 80 percent of the time. | | 09:11 | And so I offer this to the client | | 09:13 | and the client says "that's good enough" | | 09:15 | and we call it a day. | | 09:17 | And now we have a finished application | | 09:20 | that creates a simple musical instrument | | 09:22 | that reacts to user gestures with the mouse | | 09:25 | and affects the volume and the pitch of a musical tone. | | 09:29 | (digital tone) | | Collapse this transcript |
|
|
Problem Five: Recursion and DirectoriesRecursing directories and gathering image data| 00:07 | - Hello, and welcome to Code Clinic. | | 00:09 | My name is David Gassner. | | 00:12 | Code Clinic is a monthly course where a unique problem | | 00:14 | is introduced to a collection of lynda.com authors. | | 00:18 | In response, each author will create a solution | | 00:21 | using their programming language of choice. | | 00:24 | You can learn several things from Code Clinic. | | 00:27 | Different approaches to solving a problem, | | 00:29 | the pros and cons of different languages, | | 00:32 | and some tips and tricks to incorporate | | 00:34 | into your own coding practices. | | 00:38 | This month, the problem combines two concepts: | | 00:41 | Recursion and accessing image data. | | 00:45 | Recursion means to repeat something in a similar way. | | 00:49 | In programming, recursion means | | 00:51 | a function that calls itself, | | 00:53 | nesting a call to a subroutine within a call | | 00:56 | to the same subroutine. | | 00:59 | You'll see this sort of code in the samples | | 01:01 | you're about to see from some authors. | | 01:04 | Sometimes, however, recursion is handled for you | | 01:07 | by the language or application framework. | | 01:11 | It's happening in the background, | | 01:12 | but your programming job is greatly simplified. | | 01:17 | The second part of this month's challenge | | 01:19 | is to extract image data from picture files. | | 01:23 | JPEG files can contain additional image data | | 01:26 | stored as standards called EXIF or ITPC. | | 01:31 | EXIF stands for Exchangeable Image File Format, | | 01:35 | and is a well-documented standard. | | 01:38 | If you have a digital camera or have taken photos | | 01:40 | with a newer cell phone camera, | | 01:43 | the image probably had EXIF data available. | | 01:47 | Using a Mac, you can see this metadata information | | 01:51 | by opening the image in Preview. | | 01:54 | Opening Tools and the Show Inspector | | 01:57 | and selecting the EXIF or IPTC tab. | | 02:01 | On windows, you can see metadata by right-clicking an image, | | 02:05 | selecting properties and then the Details tab. | | 02:09 | You'll see things like caption, dimensions, camera type, | | 02:13 | color space, exposure information and other details. | | 02:18 | Cell phones will also embed geographic location data, | | 02:22 | identifying the longitude and latitude. | | 02:26 | This month's challenge is to look through | | 02:28 | the example files included with Code Clinic. | | 02:31 | Find images, extract the caption from the metadata, | | 02:35 | and then reorganize those photos | | 02:38 | into an alphabetical folder structure | | 02:40 | based on the caption. | | 02:43 | As always, you may want to take some | | 02:45 | time and solve the problem yourself. | | 02:48 | In the following videos, I'll show you how I solved this. | | Collapse this transcript |
| Overview of my solution| 00:00 | - The primary focus of this month's challenge | | 00:03 | is reading metadata from image files. | | 00:06 | But we also have to handle folder recursion, | | 00:09 | that is starting at a top-level folder | | 00:11 | and recursing through its subfolder structure. | | 00:15 | My solution in C# requires access | | 00:18 | to the operating-system resources. | | 00:21 | I have to be able to find and read image files | | 00:24 | and support other operating-system features. | | 00:28 | I also will need to incorporate some third-party libraries | | 00:31 | such as Adobe's XMP Toolkit to get | | 00:34 | all the data I'm looking for. | | 00:37 | The solution for this month's challenge | | 00:39 | has been built with Windows Presentation Foundation, | | 00:42 | or WPF. | | 00:44 | It could have also been built with Windows Forms, | | 00:47 | and resulted in pretty much the same output. | | 00:51 | The first challenge turns out to be the easiest in .NET. | | 00:55 | We have to start from a top-level folder | | 00:58 | and then recurse through the subfolders, | | 01:01 | and find all of the files in all of those folders. | | 01:05 | In some languages, you would create a function | | 01:08 | and you would call that function for the top-level folder. | | 01:11 | It would then look for all the subfolders | | 01:14 | and call itself for each of them. | | 01:17 | But it's a lot easier in .NET. | | 01:19 | The .NET framework has a method | | 01:22 | that handles folder recursion automatically for you, | | 01:25 | as long as you configure it correctly. | | 01:29 | It's a method called EnumerateFiles, | | 01:32 | which is a member of the Directory Class. | | 01:35 | It's a static method. | | 01:37 | So you call it from the Directory Class directly, | | 01:40 | and you pass in the location of the directory | | 01:42 | you want to explore, a file pattern if you like, | | 01:46 | and then an option called SearchOption.AllDirectories. | | 01:51 | It returns an implementation of an interface | | 01:54 | called IEnumerable, which in this case is called files. | | 01:58 | And then you can iterate through the files | | 02:01 | and deal with them one at a time. | | 02:03 | It turns out that this solution | | 02:05 | is perfect for this challenge. | | 02:07 | The list you get back is a flat list. | | 02:10 | It isn't hierarchical like the original directory structure, | | 02:14 | and that's what you want for this challenge. | | 02:17 | So the real work for this month's challenge | | 02:19 | went into solving the second part, | | 02:22 | extracting image data. | | 02:25 | And part of that challenge was pretty easy. | | 02:28 | Most of the files that I'll be working with | | 02:30 | are in JPEG format, and these files | | 02:34 | support the Exif standard. | | 02:36 | Exif stands for Exchangeable Image File format, | | 02:40 | and it's an international standard | | 02:42 | that's implemented across a lot of software platforms. | | 02:46 | The .NET API, as accessed from WPF, | | 02:50 | lets you easily extract metadata from JPEG files. | | 02:54 | You'll use the following classes. | | 02:57 | FileStream to open the file, | | 03:00 | and then two classes named BitmapSource and BitmapData. | | 03:04 | And from there, I'll easily be able | | 03:06 | to get the caption from each file. | | 03:09 | It's known as the title in this API, | | 03:12 | but it's the same information. | | 03:15 | The other type of file, however, | | 03:17 | took a little bit more work. | | 03:19 | PNG files don't support the standard for Exif. | | 03:24 | And in fact, there isn't a single universal standard | | 03:26 | for metadata in PNG files. | | 03:29 | But it turns out that the one PNG file | | 03:32 | that I'll be working with uses a format called XMP | | 03:36 | that was created and is managed by Adobe Systems. | | 03:41 | Adobe offers a free toolkit for developers | | 03:44 | called the XMP Toolkit. | | 03:47 | It's built for programming in C++, | | 03:50 | and Adobe also provides a Java version. | | 03:53 | But for C# developers, you'll need something more. | | 03:57 | And it turns out there is a publicly available solution | | 04:01 | for C# developers to read XMP data from image files. | | 04:06 | It's called the C# XMP Toolkit. | | 04:09 | It isn't managed directly by Adobe. | | 04:12 | Instead, it's an open-source free library, | | 04:15 | and you can download it and add it | | 04:18 | to your Visual Studio solution, | | 04:21 | and then call its methods from your C# code. | | 04:25 | It's essentially a C# wrapper for Adobe's XMP Toolkit. | | 04:30 | And as you'll see when I review the code for the solution, | | 04:33 | you'll actually need the XMP Toolkit DLL from Adobe. | | 04:38 | The C# Toolkit doesn't do the work by itself, | | 04:41 | it depends on Adobe's code. | | 04:43 | And I'll show you how to set it all up in Visual Studio. | | 04:47 | To download the latest version of the C# XMP Toolkit, | | 04:51 | go to this website at sourceforge.net/projects/csxmptk. | | 05:00 | Finally, I actually had to do a little bit | | 05:02 | of special work to let the user browse for a folder | | 05:06 | in Windows Presentation Foundation. | | 05:08 | In an earlier chapter of the Code Clinic series, | | 05:11 | I built an application using Windows Forms. | | 05:14 | And with just a few lines of code, | | 05:16 | I was able to popup a directory browser. | | 05:20 | Windows Presentation Foundation | | 05:22 | doesn't have that same built-in class. | | 05:25 | Now you could go back to the WinForms classes, | | 05:28 | but alternatively you can use classes | | 05:31 | that are members of a library called | | 05:32 | the Windows 7 API Code Pack. | | 05:35 | Don't worry that it says Windows 7. | | 05:38 | This code works on Windows Vista, | | 05:40 | Windows 7, and Windows 8. | | 05:43 | It does not however work on Windows XP, | | 05:46 | and that would be something to take into account | | 05:48 | when you're planning your version of the application. | | 05:52 | If you need to support Windows XP, | | 05:54 | you would think about changing your application structure | | 05:57 | to Windows Forms instead of WPF. | | 06:01 | But we're now at a point in the Windows evolution | | 06:04 | where three versions of Windows is enough for me. | | 06:08 | Windows 8, Windows 7, and Windows Vista. | | 06:12 | And now, let's take a look at the code I created | | 06:14 | to solve this month's challenge. | | Collapse this transcript |
| Exploring the solution's dependencies| 00:00 | - The exercise files for this solution | | 00:03 | contain two sub-folders. | | 00:05 | The Images folder has two sub-folders | | 00:08 | named problem2 and problem3. | | 00:11 | The problem2 folder contains images | | 00:13 | from the chapter in Code Clinic on image comparison. | | 00:18 | The problem3 folder has the images | | 00:20 | from the eight queens problem. | | 00:23 | Our goal is to search this entire folder, | | 00:27 | including all of its sub-folders, | | 00:29 | and make a flat list of all the images | | 00:32 | and then list all of their titles. | | 00:35 | The other folder within Exercise Files is called Imageinfo, | | 00:39 | and this contains the Visual Studio Solution. | | 00:43 | There are three sub-folders. | | 00:45 | The primary sub-folder is the ImageInfo project. | | 00:49 | This is the c# project that I created, | | 00:52 | and it's where I put all of my code. | | 00:55 | There's also a folder named CsXmpToolkit. | | 00:59 | This is the c# Xmp toolkit that I downloaded | | 01:02 | from the open source site. | | 01:05 | And there's a Packages folder that contains references | | 01:08 | to the Windows API code pack library. | | 01:11 | I'll be using that to present a user dialogue | | 01:14 | to select directories. | | 01:18 | To open the solution double click | | 01:20 | the Imageinfo solution file. | | 01:24 | You should be able to open this solution in any | | 01:26 | pay-for-license version of Visual Studio, | | 01:29 | or in the free edition Visual Studio Express 2013 | | 01:34 | for Windows Desktop. | | 01:37 | Let's start with looking at the structure of this solution. | | 01:42 | The Imageinfo project is set as the start-up project, | | 01:45 | so when you click the Start button | | 01:47 | you'll be running that application. | | 01:50 | When you click Choose Directory you'll see a prompt | | 01:53 | and you'll be asked to select a directory. | | 01:56 | I'll go back to the Exercise files folder, | | 01:59 | and double click the Images folder, | | 02:02 | and click Select Folder, | | 02:05 | and there's the result. | | 02:07 | The application reads all of the files | | 02:09 | in the top-level folder and all of its sub-folders, | | 02:13 | and then lists them alphabetically, | | 02:15 | grouping them by their initial character. | | 02:19 | Down toward the bottom you'll see | | 02:20 | that there are two images in the T category. | | 02:26 | That's all the application does. | | 02:28 | I'll close it. | | 02:30 | This application depends on CsXmptoolkit, | | 02:35 | the open source project. | | 02:37 | I'll go back to the Imageinfo project, | | 02:39 | then go to Project, add reference. | | 02:45 | I'll the Solution category, and see that | | 02:48 | the CsXmptoolkit has been added here. | | 02:54 | There's one other very important dependency. | | 02:57 | This dll Xmptoolkit.dll comes from Adobe. | | 03:02 | If you're a c++ developer you could download | | 03:06 | the Xmp toolkit SDK from Adobe | | 03:09 | and build this dll yourself. | | 03:11 | But our goal is to do as much as we can just with c#. | | 03:15 | So I downloaded the finished, or pre-compiled dll, | | 03:19 | and just copied it into my project, | | 03:22 | and then very importantly, I sent a copy to | | 03:26 | Output Directory property to copy always. | | 03:29 | So when I build the application this dll will be copied | | 03:33 | into either the dBug or the release directory, | | 03:37 | and it will be available to my application. | | 03:40 | If the dll weren't available | | 03:42 | the application just wouldn't work. | | 03:44 | It's completely dependent on it. | | 03:47 | Within the Imageinfo project you'll see the common starting | | 03:51 | window named Mainwindow.xaml. | | 03:55 | As always you can look at this in design or in xaml view. | | 04:00 | The design view doesn't tell you much. | | 04:02 | It just shows a button, | | 04:04 | but there's also a scroll viewer here, | | 04:07 | and within the scroll viewer a text block. | | 04:10 | You'll see that information down here in the xaml window. | | 04:16 | Finally, all of my custom logic is in this file: | | 04:19 | Mainwindow.xaml.cs, the code behind file | | 04:24 | for the main xaml file. | | 04:27 | Before you continue watching the videos in this chapter | | 04:30 | I recommend taking a look at the code | | 04:32 | and analyzing it yourself. | | 04:34 | See how much you understand about how the code is working | | 04:37 | and what it's doing, and then watch the rest of the videos, | | 04:41 | where I'll review all of the code | | 04:43 | and explain what it does. | | Collapse this transcript |
| Browsing folders with Windows API Code Pack| 00:00 | - In my finished solution, when I click the Start button, | | 00:04 | I click a button labeled "Choose directory" | | 00:07 | and that pops up this browser window. | | 00:10 | It'll always default to the last directory | | 00:12 | that was selected by the user, if you | | 00:15 | start from the exercise files folder | | 00:18 | and choose just the images folder | | 00:20 | and then select that folder you'll see a listing | | 00:23 | of all of the images but if you repeat the process | | 00:28 | and just choose one of the sub-folders | | 00:30 | you'll only see the images listed in that sub-folder. | | 00:34 | I'll go back to the images folder and down to problem3 | | 00:38 | and click Select Folder and now | | 00:41 | I only see the titles for two of the images. | | 00:45 | If I make the window a bit wider | | 00:48 | I can see the entire directory name. | | 00:51 | So let's start with the code that's popping up | | 00:53 | that browsing window that lets the user choose a directory. | | 00:58 | As I mentioned previously, when you work | | 01:01 | in Windows Presentation Foundation | | 01:03 | you don't always have all the tools | | 01:05 | that you might of had in Windows Forms. | | 01:08 | But it's pretty easy to find replacements | | 01:10 | for those common components. | | 01:14 | When the user clicks the button | | 01:15 | that executes this method named Button_Click | | 01:19 | and here's the code that's being executed. | | 01:22 | I'm creating an instance of a class | | 01:24 | called CommonOpenFileDIalog, when I move the cursor | | 01:28 | over the name of the class in the declaration | | 01:31 | I see that the class is a member | | 01:33 | of Microsoft.WindowsAPICodePack.Dialogs. | | 01:39 | Now, how did I get this code? | | 01:41 | This came from NuGet, the architecture that lets you | | 01:45 | easily find, download and integrate libraries | | 01:49 | into your Visual Studio solutions. | | 01:52 | I went into PROJECT and selected "Manage NuGet" folders. | | 01:59 | And in this dialog box, if you click on "Installed packages" | | 02:03 | you'll see the code pack library's there. | | 02:06 | Notice that they're labeled with Windows 7 | | 02:09 | but they work perfectly well with Windows 8 | | 02:11 | and all the way back to Windows Vista. | | 02:15 | So that's where that project came from. | | 02:18 | The rest of the code is pretty straightforward. | | 02:20 | I create an instance of the dialog, | | 02:23 | I set its Title, I set a boolean property | | 02:26 | called IsFolderPicker = true, if I don't do that | | 02:30 | then I'll be letting the user choose files. | | 02:33 | And then I call this ShowDialog method. | | 02:36 | If the user successfully selects a folder or directory, | | 02:41 | this method will return a value | | 02:43 | of CommonFileDialogResult.Ok. | | 02:47 | So if I get that value back I know I | | 02:49 | have a valid directory name to work with. | | 02:53 | Here I'm clearing a collection named data | | 02:57 | which is an instance of a Dictionary class. | | 03:01 | The Dictionary class contains name value pairs. | | 03:05 | And I've defined this Dictionary to contain | | 03:07 | items where the key is a string | | 03:10 | and the referenced item is a List of strings. | | 03:14 | This is how I'll be categorizing my results. | | 03:18 | All photos where the first character is A | | 03:20 | will go into one list, all those with B | | 03:23 | will go into another list and so on. | | 03:26 | I'm both declaring and instantiating the directory | | 03:29 | as a private field of the class. | | 03:32 | So it'll be available globally to all the code in this class | | 03:36 | but won't be available anywhere else. | | 03:39 | Going back to me Button_Click method, | | 03:42 | I clear that Dictionary and then I set another field | | 03:46 | value named imageDir, that was a string, | | 03:49 | to the value returned by the dialog box. | | 03:53 | And then I execute two methods. | | 03:56 | AnalyzeImages and DisplayResults. | | 04:00 | I'll go over the details of those methods in a moment | | 04:03 | but their basic purposes are these, | | 04:06 | the AnalyzeImages method finds all of the image files | | 04:10 | in the folder structure and then one file at a time | | 04:14 | gets its Title and stores that | | 04:16 | information in the Dictionary. | | 04:18 | Then the DisplayResults method processes the Dictionary, | | 04:22 | displaying the data one category at a time. | | 04:25 | So in the next couple of videos | | 04:27 | I'll show you how that code works. | | Collapse this transcript |
| Retrieving image data from JPG and PNG files| 00:00 | - Once the user has selected a folder to work | | 00:02 | with, the next step is to go get all | | 00:05 | of the files and analyze them. | | 00:08 | That's all done in the AnalyzeImages. | | 00:12 | As I mentioned in the introduction, | | 00:14 | this is where recursion takes place. | | 00:17 | In some languages you would write a method | | 00:20 | that analyzed all the files in the current folder and | | 00:23 | then called the method again for each of the subfolders. | | 00:28 | The .NET APIs though make this very easy. | | 00:31 | The EnumerateFiles method has an argument | | 00:34 | which allows you to set an option | | 00:36 | of AllDirectories and that means go get | | 00:39 | all the files for the selected directory | | 00:41 | plus its entire subdirectory structure. | | 00:45 | You get back a list which is an implementation | | 00:48 | of an interface named IEnumerable. | | 00:52 | It doesn't matter to you what the actual | | 00:54 | concrete class is of that collection. | | 00:56 | All you need to know is that you're getting | | 00:58 | back files and you can iterate through them, | | 01:01 | looping, and handling one file at a time. | | 01:06 | So after the call at line 54, I now have | | 01:09 | a flat list of all the files, all of | | 01:12 | the images that are in all of the directories. | | 01:16 | At line 57, I start my iteration. | | 01:20 | I'm using a foreach loop, which was | | 01:23 | enabled by the IEnumerable interface. | | 01:26 | For each file, I get its information using the FileInfo | | 01:30 | class and then I examine the file extension. | | 01:35 | I'm only going to process files | | 01:37 | that have a .jpg or a .png extension. | | 01:42 | Any other extension will be ignored. | | 01:44 | That's important here because in | | 01:46 | an image folder where you might think you | | 01:48 | only have image files, you might also have | | 01:51 | an invisible database file that Microsoft Windows | | 01:54 | frequently generates and you don't want | | 01:57 | to try to process that file, it's not going to work. | | 02:01 | The continue; keyword means start back | | 02:03 | at the beginning of the loop and go to | | 02:05 | the next file, we're not interested in this one. | | 02:10 | At line 69, I now process an image file. | | 02:15 | The first step is to use a FileStream. | | 02:18 | I'm instantiating the FileStream inside | | 02:21 | a using clause, that will cause it | | 02:23 | to close automatically when we're done | | 02:25 | with this section of the code. | | 02:28 | I'm opening it with read access only. | | 02:32 | The next step is to set a variable name title, | | 02:34 | which I'm initializing to an empty string. | | 02:38 | We'll set the actual title in just a little bit. | | 02:41 | Then I'm creating two objects, a BitmapSource | | 02:44 | and a BitmapMetadata object. | | 02:48 | The first one takes the stream and looks at it | | 02:51 | as a graphic and then the second statement gets | | 02:54 | the Metadata, the information about the graphic. | | 02:59 | The Metadata has a property called Format | | 03:02 | which will tell us what kind of file we're working with. | | 03:05 | And next we have a Switch statement. | | 03:08 | For JPG files, this is very easy. | | 03:11 | Because JPG files support the E x i f, | | 03:15 | or Exif standard, you'll get a title back | | 03:18 | and you can simply save that value. | | 03:21 | The work is done. | | 03:23 | For PNG files, it's a bit harder but | | 03:26 | the CSXmpToolkit makes it actually pretty easy. | | 03:31 | First, I'm creating something called an Xmp object. | | 03:34 | You can see by moving the cursor over | | 03:37 | the declaration that this class is | | 03:39 | a member of the CSXmpToolkit. | | 03:43 | As is the next one, XmpFileMode. | | 03:46 | Once again, I'm placing this code inside | | 03:49 | a using statement to make sure that | | 03:51 | the Xmp is closed when I'm done with it. | | 03:55 | Next, I'm taking the Xmp object | | 03:57 | and wrapping it in a class named DublinCore. | | 04:01 | The structure of this class is dictated | | 04:03 | by Adobe's XmpToolkit, but when you wrap | | 04:07 | that around the Xmp, you're actually getting | | 04:10 | the data out of the Xmp structure | | 04:12 | and now we can get the title of the image. | | 04:16 | And you get that with the expression dc.Title.DefaultValue. | | 04:21 | Now you might be wondering why | | 04:23 | DefaultValue, why not just value? | | 04:25 | And the answer is Xmp allows you | | 04:28 | to store multiple versions of a title, | | 04:30 | one per human language. | | 04:33 | So you could have an English version, | | 04:34 | a French version, a German version, and so on. | | 04:39 | I know that the images I'm working | | 04:41 | with only have English versions so I'll use | | 04:44 | the DefaultValue property and I'll be | | 04:47 | pretty sure I'm getting what I'm looking for. | | 04:51 | Finally, I'll store the information. | | 04:55 | Working on the current title, I'll extract | | 04:57 | the initial character using a substring extension | | 05:00 | and then make sure it's uppercase with a call to ToUpper. | | 05:05 | Then I'll look at my data collection and ask, | | 05:07 | is there already a list that's | | 05:09 | indexed by that first character? | | 05:12 | And if there isn't, I add the list, that's on line 99. | | 05:17 | And finally, on line 101, I add | | 05:20 | the current title to that list. | | 05:23 | So this method is doing all that work. | | 05:25 | Getting the information out of the file, | | 05:28 | that's the title, using either | | 05:30 | the Exif Metadata or the Xmp Metadata, | | 05:34 | depending on the file format, and | | 05:37 | then storing the title in the dictionary. | | 05:40 | Once the loop is complete, my dictionary will be done. | | 05:44 | It'll contain multiple items. | | 05:47 | Each item will be indexed by a character | | 05:49 | of the alphabet and within that item, | | 05:51 | there will be a list of image titles. | | 05:55 | My next step is to display the results | | 05:58 | and I'll show you how I did that in the next video. | | Collapse this transcript |
| Displaying the results| 00:00 | - So far, we've asked the user to select a directory, | | 00:03 | and then we've analyzed all the files in | | 00:06 | that directory and its sub directories. | | 00:09 | We're ready to display the results. | | 00:12 | That code is here in the display results method. | | 00:16 | The first step is to get a list of all the key values. | | 00:21 | We're getting that from this expression, data.Keys, | | 00:25 | and we're extracting it as a list of strings. | | 00:29 | Then a simple call on line 112 to the list sort method, | | 00:34 | will place the list of keys in alphabetical order. | | 00:38 | Then, we're displaying the directory name by setting | | 00:41 | the text property of the text block, | | 00:44 | which has a name of txtOutput. | | 00:47 | By setting that value, we're clearing | | 00:49 | any previously displayed results. | | 00:52 | All the rest of the code will use the plus equals operator | | 00:56 | to concatenate values to that text property. | | 00:59 | Next, there's a for each loop, looping | | 01:02 | through the list of key values. | | 01:05 | For each of those values, we're displaying | | 01:07 | the value and then adding a line feed. | | 01:11 | Then, extracting the list of titles that's referenced | | 01:15 | with that key, and on line 123 sorting that list. | | 01:20 | This makes sure that the image | | 01:21 | titles are also in alphabetical order. | | 01:25 | Finally, there's a nested loop, | | 01:26 | this time operating on the titles list. | | 01:29 | On line 126, we're outputting a bullet | | 01:32 | character, the title, and another line feed. | | 01:37 | Finally, at the end of the for each loop, one more | | 01:39 | line feed for good measure to separate | | 01:42 | the categories from each other. | | 01:44 | That's the entire code for the display results method. | | 01:48 | It's operating on a dictionary where we know that the keys | | 01:52 | of each item are individual alphabetical characters, | | 01:56 | and the lists that are referenced | | 01:57 | by those keys contain image titles. | | 02:01 | Finally, here's the result once again. | | 02:05 | I run the application, I choose a directory, | | 02:09 | I select it, and I see the results. | | 02:12 | I can scroll through and see all the information | | 02:15 | that came out of those image files. | | 02:18 | So that's my solution to this month's challenge. | | 02:21 | Using C# to find all the files within a particular | | 02:24 | directory structure, and then extract and display | | 02:28 | the metadata, the titles, for each of those images. | | Collapse this transcript |
|
|
Problem Six: Building the WebConverting static web pages into ASP.NET web forms| 00:06 | - Hello, and welcome to Code Clinic. | | 00:09 | My name is David Gassner. | | 00:11 | Code Clinic is a monthly course where a unique problem | | 00:15 | is introduced to a collection of lynda.com authors. | | 00:18 | In response, each author will create a solution | | 00:22 | using their programming language of choice. | | 00:25 | You can learn several things from Code Clinic: | | 00:28 | different approaches to solving a problem, | | 00:30 | the pros and cons of different languages, | | 00:33 | and some tips and tricks to incorporate | | 00:35 | into your own coding practices. | | 00:39 | This month, we're working on a problem | | 00:41 | common to anyone building a website or a report. | | 00:44 | Specifically, merging live data with static template. | | 00:49 | Data is only useful when we can effectively | | 00:52 | communicate it to someone else, and to do that, | | 00:55 | we need to format it into some sort of | | 00:57 | meaningful visual representation. | | 01:00 | Sometimes this takes the form of a printed report. | | 01:03 | Most likely, it means publishing data to a web page. | | 01:08 | In this problem, we have the preformatted HTML | | 01:11 | web page and some data files in CSV format. | | 01:16 | Our job is to write code that will insert the data files | | 01:19 | into the web page and apply the proper formatting. | | 01:24 | We're going to use the files created by | | 01:25 | James Williamson in Chapter 9 of his course, | | 01:28 | titled "Dreamweaver CC Essential Training." | | 01:32 | James provides all of the material | | 01:34 | we need for this challenge. | | 01:36 | I invite you to download the example files for that course | | 01:40 | if you'd like to attempt your own solution. | | 01:44 | In the next videos, I'll show you | | 01:46 | how I solved this challenge. | | Collapse this transcript |
| Overview of my solution| 00:00 | - In this month's challenge, I'm going to show you | | 00:03 | how I added data from CSV or comma delimited files | | 00:07 | into what starts as a static webpage. | | 00:12 | C# is used for all of the Microsoft platforms | | 00:15 | including desktop applications built with Windows forms | | 00:19 | and Windows Presentation Foundation | | 00:21 | and modern applications built for Windows 8. | | 00:25 | But one of the most common uses of C# | | 00:28 | is to work with ASP.NET, the server-side engine | | 00:32 | that generates webpages dynamically. | | 00:35 | And so this is the natural approach | | 00:37 | to applying C# to this challenge. | | 00:40 | The first thing I did was to break down the HTML page | | 00:44 | into reusable components that I can put together | | 00:47 | at runtime with ASP.NET. | | 00:50 | This is a normal process in ASP.NET. | | 00:53 | You take the parts of the page that will be reused a lot | | 00:57 | and you turn them into something called user controls. | | 01:00 | And I'll show you how you do that in code | | 01:02 | when I get into the code review. | | 01:05 | In this case, I'm starting from a static webpage | | 01:08 | and I decided to break it down into four parts | | 01:12 | that look like this. | | 01:14 | First the header, this component | | 01:17 | will be used on every page in the entire site | | 01:20 | and so will the bottom of the page | | 01:22 | which I called the footer. | | 01:24 | Then you have the content. | | 01:26 | This is the material that's unique to this page | | 01:29 | and it's where my data tables are going to go. | | 01:32 | And finally, the sidebar. | | 01:34 | In the webpage I'm working on, | | 01:36 | this is also static content but it's easy | | 01:39 | to foresee the possibility that it would become dynamic too. | | 01:44 | So, I break the webpage down into these four components | | 01:48 | and then I put them together again at runtime | | 01:51 | to generate and output a single page. | | 01:55 | This allows me to see and work on | | 01:56 | just a small part of the page at any given time | | 02:00 | and as you'll see when I get into the code, | | 02:02 | it allows me to focus just on the task at hand. | | 02:06 | Reading the data from the CSV files | | 02:08 | and injecting it into the webpage. | | 02:12 | The task of reading the CSV files | | 02:14 | and getting the data into memory | | 02:16 | was accomplished with some common.NET classes. | | 02:20 | Here's what the code looks like. | | 02:23 | I'm using a class called the StreamReader | | 02:25 | to open and read the file into memory | | 02:28 | and then I'm storing the data inside a list | | 02:31 | and each item in the list is an instance | | 02:33 | of a custom class that I define named Course. | | 02:38 | The Course has fields or properties | | 02:40 | that allow me to store each bit of data, | | 02:43 | the name of the course and the number of credits. | | 02:47 | Then the while loop is examining the stream | | 02:50 | and grabbing data one line at a time. | | 02:53 | For each line, I'm splitting the line by the comma | | 02:56 | and then storing the values in the data object. | | 02:59 | Once I have the data in memory, | | 03:02 | the next step is to model the data tables | | 03:04 | in the HTML output and I'm doing that | | 03:07 | with server-side web controls. | | 03:10 | In ASP.NET, a component that starts | | 03:13 | with an ASP prefix and has a runat attribute of server | | 03:18 | is processed at the server before being sent to the browser. | | 03:22 | And here I'm using tags named Table, | | 03:24 | TableRow, and TableHeaderCell, all being run at the server. | | 03:31 | These are all static values but by using | | 03:34 | a server-side table component, | | 03:36 | it allows me to add data at runtime dynamically | | 03:40 | and I'm doing that with pure C# code. | | 03:43 | I already have the data in memory | | 03:45 | so for each item in a list, I'm creating | | 03:49 | a couple of cells, adding the values from the Course | | 03:53 | into those cells, adding the cells into a row | | 03:57 | and adding the row to the table. | | 04:00 | This, it turns out, is the only C# code that I really needed | | 04:04 | in addition to code I used to parse the CSV files. | | 04:09 | So this is a natural way to use C# | | 04:12 | to take something that starts off as a static webpage | | 04:16 | and to turn it into something that's generated dynamically | | 04:18 | at the server and then delivered to the browser. | | Collapse this transcript |
| Exploring the solution's files| 00:00 | - The Exercise Files for this challenge | | 00:02 | are organized in two folders. | | 00:05 | This folder, named "StaticSite," | | 00:08 | contains the original web page from the course: | | 00:11 | Dreamweaver CC Essential Training. | | 00:14 | The "programs" subfolder has an html page, | | 00:17 | and you should be able to open that html page | | 00:20 | in Internet Explorer, or in any other modern browser. | | 00:25 | The page is organized with a header at the top, | | 00:28 | a footer at the bottom with a bunch of links, | | 00:31 | a sidebar to the right, | | 00:34 | and the page's primary content on the left. | | 00:38 | The primary content | | 00:39 | displays the curriculum for this program. | | 00:43 | In this version of the web page, | | 00:45 | the data is embedded in the html file. | | 00:49 | You can right-click and choose 'View Source' | | 00:52 | to look at the html structure and data. | | 00:56 | And if you scroll down to the tables | | 00:58 | you'll see the data's right there. | | 01:01 | Each course number is wrapped in "" tags | | 01:04 | and we'll have to solve that in our C# code | | 01:07 | when we inject the data into the web page. | | 01:12 | The "StaticSite" folder also contains | | 01:14 | subfolders containing various assets. | | 01:17 | Including: style sheet files, | | 01:19 | fonts, | | 01:21 | images, and more. | | 01:25 | The other primary folder | | 01:27 | in the Exercise Files for this challenge | | 01:29 | is named "DynamicSite." | | 01:32 | And this is a web page that's been built | | 01:34 | in asp.net. | | 01:37 | The first folders you see, | | 01:38 | with the underscore prefix, | | 01:40 | are exactly the same as with the default site. | | 01:44 | There's still a cascading style sheets file, | | 01:47 | and there are still fonts, | | 01:48 | images, and javascript files. | | 01:51 | But there are two new subfolders. | | 01:54 | The first is named "App_Code," | | 01:56 | and it contains a couple of C# classes. | | 02:00 | And the "App_Data" folder | | 02:02 | now contains the csv files. | | 02:05 | The original two csv files from the course | | 02:08 | are both here. | | 02:10 | They're called "first_semester" and "second_semester" .csv. | | 02:14 | I've also created | | 02:15 | a "third_semester" and a "fourth_semester" file | | 02:19 | that represent the data that was in the original web page. | | 02:22 | Because my goal is for the asp.net version | | 02:25 | to look exactly the same as the original version. | | 02:29 | You can open this asp.net site | | 02:32 | in either one of the pay for license versions | | 02:34 | of Visual Studio, | | 02:36 | or in one of the free editions. | | 02:38 | Visual Studio Express 2013 for web. | | 02:43 | To open the site, | | 02:44 | just double-click the "DynamicSite" solution file | | 02:48 | and if you have the right version of Visual Studio | | 02:50 | it should open automatically. | | 02:53 | Now, to test this version of the page | | 02:55 | click the start button up at the top. | | 02:58 | As with the static version, | | 03:00 | you can run the asp.net version | | 03:02 | in Internet Explorer, or in any other modern browser. | | 03:06 | When you click the button, | | 03:08 | you'll rebuild the site | | 03:10 | and then launch it. | | 03:12 | Notice in the web address the url. | | 03:15 | That we're no longer accessing the file | | 03:17 | directly from the file system, | | 03:19 | but instead we're going through a web server, | | 03:22 | indicated by the local host address. | | 03:25 | That special port might be different | | 03:27 | on your computer than on mine, | | 03:29 | and that's because it's set dynamically | | 03:31 | when you launch the page | | 03:32 | and when you launch the developed web server. | | 03:36 | I'll scroll down a bit, | | 03:38 | and show that the page looks exactly the same. | | 03:41 | And then, just to prove it again, | | 03:44 | I'll take this version and move it over to the right... | | 03:48 | I'll minimize Visual Studio, | | 03:50 | and close all these other files. | | 03:53 | I'll go to my "Exercise Files" folder, | | 03:56 | to the static site, | | 03:58 | to the "programs" subfolder, | | 04:01 | and I'll open up the static site. | | 04:04 | Then I'll drag that version | | 04:06 | and put it in its own window over here. | | 04:09 | And here are the two versions of the page. | | 04:12 | The one on the left is being opened | | 04:13 | from the file system, | | 04:15 | that's the static page, | | 04:16 | and the one on the right is being opened | | 04:18 | from the web server. | | 04:20 | I'll scroll down to the curriculum section | | 04:23 | and I'll do that in this version as well. | | 04:26 | And as you can see, they look exactly the same. | | 04:30 | So next, | | 04:31 | I'll take a look at the code that I used | | 04:33 | to transform this static web page | | 04:36 | into something that's generated dynamically | | 04:38 | on a web server upon request. | | Collapse this transcript |
| Breaking down a web page into ASP.NET user controls| 00:00 | - My first step in transforming this static web page | | 00:03 | into an ASP.NET page was to break down the HTML | | 00:08 | into parts. | | 00:09 | I chose to break down the original web page into four parts. | | 00:13 | A header and the footer, a sidebar and the main content. | | 00:18 | In the original web page as shown here in TextPad, | | 00:22 | all of the markup and all of the data | | 00:25 | was in a single file. | | 00:26 | That made the file long and fairly complex, | | 00:29 | not impossible to deal with | | 00:31 | but also not reusable. | | 00:34 | If the user wanted for example to reuse the header content | | 00:37 | in another page, they would have had to copy it. | | 00:40 | One of the great advantages | | 00:42 | of working in a dynamic web server environment | | 00:45 | such as ASP.NET, is that you can take this | | 00:48 | commonly used content and put it in its own reusable files | | 00:53 | and then call that content whenever you need it, | | 00:56 | so that was my first step, | | 00:58 | breaking everything down into pieces. | | 01:00 | I chose to create each piece as something called | | 01:03 | a user control. | | 01:05 | In ASP.NET, a user control | | 01:08 | has a file extension of .ascx | | 01:11 | and this is the user control for the header content. | | 01:15 | At the top of the file there's a control meta tag, | | 01:19 | it declares the language I'm programming in, C#, | | 01:22 | and then indicates the code file | | 01:26 | also known as the code behind file, | | 01:28 | which is in this case header.ascx.cs. | | 01:33 | In the Solution Explorer pane over on the right, | | 01:37 | you can locate the associated code file | | 01:40 | by opening up the three control for that file | | 01:43 | and then you can double-click and open the file. | | 01:47 | Notice in this user control | | 01:50 | and also in a couple of the others, | | 01:52 | the code behind file isn't doing anything. | | 01:55 | There's a page load event handler but nothing else. | | 01:59 | That's the header file | | 02:01 | and it has exactly the same content | | 02:03 | as the original static HTML, | | 02:06 | just with a little bit better indentation. | | 02:10 | There is a little bit of code here that bears explaining. | | 02:13 | If you simply copy and paste your HTML code | | 02:16 | into Visual Studio | | 02:18 | and then look at it in design view in a user control file, | | 02:22 | your cascading style sheets won't work. | | 02:25 | These codes at lines three to five has an if clause. | | 02:29 | It's asking if and then looking at the condition false | | 02:34 | which means that this code will never execute. | | 02:37 | The code within the if clause is a client side link | | 02:40 | to the website's CSS or cascading style sheet file. | | 02:45 | The reason I put this in this file | | 02:48 | and in all of the other user controls | | 02:50 | is so that when I go into design mode | | 02:52 | the CSS files will be recognized and used | | 02:56 | and I'll be able to see more clearly | | 02:58 | what this file will look like at runtime. | | 03:00 | The design will never be perfect in Visual Studio, | | 03:04 | you have to really look at it in the web browser | | 03:06 | to see what it really looks like, | | 03:08 | but it will be closer than if I didn't have that code. | | 03:13 | Here's another example, | | 03:14 | the footer user control. | | 03:16 | Once again I have exactly the same code | | 03:19 | as in the original static footer | | 03:21 | and once again I have the code at lines three to five | | 03:24 | that incorporate the CSS file. | | 03:27 | The footer for this web page is nothing more | | 03:30 | than a bunch of links. | | 03:31 | When I look at it in design mode, | | 03:34 | I see pretty much exactly what it's going to look like | | 03:37 | when I open it in the web browser. | | 03:40 | I'll close that user control and then open up the sidebar. | | 03:46 | The sidebar user control has the stacked elements | | 03:49 | over on the right side of the page | | 03:51 | which incorporate a bunch of links | | 03:54 | and then some dynamic content that's being fed | | 03:57 | by some social websites. | | 03:59 | I'll once again look at it in design mode | | 04:02 | and see that it's pretty close. | | 04:06 | Finally, there's the curriculum user control. | | 04:09 | This is my main content for the page. | | 04:12 | I could have chosen to put this in the main page | | 04:14 | but I decided to put it in its own user control | | 04:17 | just to make it isolated. | | 04:19 | So I could work on it without worrying about the whole page | | 04:22 | at the same time. | | 04:24 | The text at the top is the text the user sees | | 04:27 | at the top of that section | | 04:29 | and then there's a series of four HTML tables. | | 04:32 | I'll talk about this table tags a little later | | 04:36 | but just notice for the moment | | 04:37 | that they have the ASP prefix | | 04:40 | and they each have a runat attribute | | 04:43 | set to a value of server, | | 04:45 | that means these tables are being processed on the server | | 04:49 | before they're being sent to the browser. | | 04:51 | Here's how I'm putting everything together. | | 04:54 | I'll close the user controls | | 04:56 | and open the file default.aspx. | | 05:00 | In an ASP.NET web form file, | | 05:03 | you have a page meta data tag at the top. | | 05:06 | Once again it declares the language | | 05:08 | and the code file | | 05:11 | then if you're going to use user controls | | 05:13 | you need to register them. | | 05:16 | You can either register them the way I have here | | 05:19 | within each page that uses them | | 05:21 | or you can also register them globally | | 05:24 | in the web.config file. | | 05:26 | Because this is only one page, I've kept it simple | | 05:29 | and kept all my code in one place. | | 05:32 | Notice that for each register tag, | | 05:35 | there's a TagPrefix, always set to controls | | 05:38 | and then a TagName. | | 05:41 | The source is then set to the actual filename. | | 05:45 | The TagName is in the alias, | | 05:47 | a name by which I can refer to this user control. | | 05:51 | For example, this is the register tag for the header | | 05:54 | and at line 11, | | 05:56 | this is where I'm actually calling the header | | 05:59 | and outputting it to the browser. | | 06:02 | Then within the body tag, I'm outputting the curriculum, | | 06:05 | the sidebar, and the footer. | | 06:08 | Now, I've isolated each of those bits of code | | 06:11 | in their own file and I can work on them one at a time. | | 06:16 | My main focus in this challenge | | 06:18 | was to work on the curriculum. | | 06:21 | In the next video, I'll describe in more detail | | 06:24 | how I built this user control | | 06:26 | and how I'm getting data out of the CSV files | | 06:29 | and injecting the data at runtime into the HTML. | | Collapse this transcript |
| Modelling HTML markup with web form controls| 00:00 | - Before I can add the data to an HTML table, | | 00:03 | I have to describe how the HTML table is going to look. | | 00:08 | And in this solution, I did this in file cirriculum.ascx. | | 00:12 | As I previously described, this is a user control file. | | 00:17 | It has basic HTML markup and simple text in it, | | 00:21 | and most of the file is exactly the same | | 00:24 | as in the original static site. | | 00:26 | But when we get to the HTML tables, everything changes. | | 00:31 | Instead of simple HTML tables, | | 00:33 | I'm using server side web form controls. | | 00:38 | In this solution, I'm using three controls in my markup | | 00:41 | named Table, TableRow and TableHeaderCell. | | 00:46 | Each of these has an XML prefix of asp, | | 00:50 | and a runat attribute of server. | | 00:53 | The tables each have an ID. | | 00:55 | There's a Table1, Table2, Table3 and Table4, | | 01:00 | and the captions for each of these tables | | 01:02 | are set with literal strings. | | 01:04 | If I preferred, I could have set those | | 01:06 | dynamically at runtime. | | 01:08 | And then the TableHeaderCells also have literal strings. | | 01:13 | I could have done everything at runtime if I preferred, | | 01:16 | but the stated goal of this challenge is to | | 01:18 | inject data into a template that's already set up, | | 01:22 | and this comes closest. | | 01:25 | Notice that there's nothing in the table markup | | 01:27 | that would determine fonts, colors, | | 01:30 | or other aspects of the table's appearance. | | 01:34 | Just as in the static tables, | | 01:37 | those are being determined by the cascading | | 01:39 | style sheet files. | | 01:41 | And, if you understand how that's working in the | | 01:43 | static version, you already understand | | 01:46 | how it's working in ASP.NET. | | 01:49 | James Williamson, the original author of these files, | | 01:52 | gives a great explanation of how the CSS is used at runtime | | 01:56 | by the browser to render the tables and the data. | | 02:01 | And so that's really all there is to it. | | 02:04 | There are four tables, they each have one row. | | 02:08 | Each of those rows has two TableHeaderCells, | | 02:11 | and the literal text for those cells is set here | | 02:15 | in the HTML markup. | | 02:17 | So in the next movie, I'll show you how I use | | 02:20 | C# code to extract the data at runtime | | 02:24 | from the CSV files that are stored here, | | 02:27 | in the App Data subfolder. | | Collapse this transcript |
| Parsing data from CSV files| 00:00 | - My solution contains four CSV files, | | 00:03 | all stored in the App_Data folder. | | 00:07 | In ASP.NET this folder has a special name, | | 00:10 | it's where you typically put CSV files, XML, | | 00:14 | local database files and other data oriented content. | | 00:20 | In theory, you could put this content anywhere in the side | | 00:23 | but in a moment I'll show you an advantage | | 00:26 | of using this specially named subfolder. | | 00:30 | There are four CSV files, | | 00:32 | one for each semester that I'm going to be displaying. | | 00:35 | The first two CSV files came from | | 00:38 | James Williamson sample files. | | 00:40 | I created the third and fourth semester files | | 00:43 | using the data that he was displaying | | 00:46 | in his finished website. | | 00:49 | I'll close these files | | 00:50 | and show you how I'm opening this data at runtime | | 00:54 | using C# code. | | 00:57 | I'll go to the solution explorer | | 00:59 | and I'll open up curriculum.ascx, | | 01:03 | clicking on its table icon here. | | 01:06 | I'll open the user controls code behind file | | 01:09 | curriculum.ascx.cs. | | 01:14 | As the page loads, I execute a bit of code. | | 01:19 | First, I'm declaring a list of course objects. | | 01:24 | Let's take a look at the files in the app code folder. | | 01:27 | There are two. | | 01:28 | This one is a class name simply course. | | 01:32 | It's a data entity. | | 01:34 | It declares two properties called course name and hours. | | 01:39 | Course name is a string and hours is an integer. | | 01:45 | Then there's a class named DataManager, | | 01:47 | the DataManager class has a method named GetData. | | 01:52 | It receives the name of a file | | 01:54 | and then it executes a bunch of code | | 01:57 | to open the file, extract its data, | | 02:00 | and return that data as a list of data objects. | | 02:03 | Let's go through that code a couple lines at a time. | | 02:08 | The code at lines 18 through 20 | | 02:10 | is determining the location of the file | | 02:14 | that we're about to open. | | 02:15 | When the GetData method is called | | 02:17 | only the name of the file is passed in. | | 02:21 | The location is set here | | 02:23 | because it's always the same for every file. | | 02:26 | First, I'm using an expression to get the location | | 02:30 | of the data directory, | | 02:31 | that's the specially named App_Data directory, | | 02:35 | which is available in all ASP.NET applications. | | 02:39 | The expression AppDomain.CurrentDomain.GetData | | 02:43 | then receives a string of DataDirectory, | | 02:47 | it returns the directory object | | 02:49 | and then the ToString method returns it as a string. | | 02:53 | I get back the complete address of that folder. | | 02:58 | Then I append to that an extra backslash | | 03:01 | and the name of the file that was passed in to the method. | | 03:04 | Now, the fullname variable has the complete location | | 03:08 | and name of the file that I'm about to open and parse. | | 03:12 | Next, I open the file at line 22. | | 03:14 | I'm using a StreamReader object | | 03:17 | wrapped around a file object, | | 03:19 | that lets me read the file into memory. | | 03:23 | Then I create that list of courses, | | 03:26 | this is where the list is instantiated. | | 03:29 | A list is a resizable set of objects, | | 03:32 | it uses generic notation so you can indicate | | 03:36 | what type of object it's going to contain. | | 03:38 | I'm going to say my list contains course objects, | | 03:42 | that is instances of the course class. | | 03:46 | Next, I read the file using a while loop. | | 03:48 | The expression !reader.EndOfStream | | 03:52 | means keep reading the file until I get to the end. | | 03:56 | Within the while loop I call the reader objects | | 03:59 | read line method, | | 04:01 | that reads everything up to a line feed | | 04:03 | and returns just the line. | | 04:06 | Then I'm using the strings split method, | | 04:10 | passing in the comma as the delimiter | | 04:12 | and I get back the values. | | 04:15 | In our CSV files, each line has two values. | | 04:18 | The name of the course and the number. | | 04:22 | Notice, that the first line of each of these files though | | 04:25 | has a set of strings that don't represent a course. | | 04:28 | Here's how I'm telling my code to ignore that line. | | 04:33 | I'll go back here | | 04:34 | and you'll see that I'm parsing the data | | 04:37 | and grabbing each item at lines 32 and 33, | | 04:41 | but that code is wrapped in a try-catch block. | | 04:44 | In the catch block when I hit a format exception, | | 04:48 | I output a bit of code to my debug console | | 04:51 | and then call the continue keyword | | 04:54 | and that means don't finish this loop, | | 04:56 | go back to the beginning of the loop and try the next line. | | 05:01 | If I get past that catch block though | | 05:03 | that means I have valid values | | 05:06 | and I'll take that course object | | 05:08 | and I'll add it to my courses list. | | 05:12 | Finally, when I'm done with the loop | | 05:14 | I return that data. | | 05:16 | Going back to the curriculum code file, | | 05:20 | I'm calling that GetData method four times, | | 05:23 | once for each of the CSV files, | | 05:26 | and then I'm calling my own custom method | | 05:28 | called AddDataToTable and I'm passing in the courses list | | 05:33 | and then passing in a reference to the table | | 05:35 | I want to inject the data into. | | 05:38 | Notice that I'm injecting data into tables one, | | 05:41 | two, three, and four. | | 05:44 | I'm matching that with the CSV files | | 05:47 | and that's how the correct data is getting | | 05:49 | into the correct table. | | 05:52 | That's how I'm getting the data from the CSV files | | 05:55 | and then passing it to the tables. | | 05:58 | In the next movie, I'll show you the code | | 06:00 | that's manipulating those tables | | 06:02 | and displaying the data in the correct places | | 06:05 | with the correct formatting. | | Collapse this transcript |
| Adding data to HTML tables at runtime| 00:00 | - So far, I've described how I've modeled | | 00:02 | the HTML using web form controls, | | 00:06 | and then how I extracted the data | | 00:08 | from the csv file using parsing code, | | 00:11 | which you'll find in the data manager class | | 00:13 | of the solution. | | 00:15 | Finally, I'll show you how I'm injecting | | 00:17 | that data into the HTML tables at run time. | | 00:22 | That code is in the curriculum code file | | 00:25 | curicculum.ascx.cs, and it's in the method | | 00:29 | AddDataToTable. | | 00:31 | This method receives two arguments. | | 00:34 | A list of courses and a reference | | 00:36 | to the table I want to inject the data into. | | 00:39 | I'm starting with a foreach loop, | | 00:42 | looping through the courses in the list | | 00:44 | and then adding each course as a row | | 00:47 | to the selected HTML table. | | 00:49 | For each course, I'm creating an instance | | 00:51 | of the table row class. | | 00:53 | That's exactly the same table row | | 00:56 | that I'm adding to the tables | | 00:58 | in the HTML markup. | | 01:01 | But now I'm referring to it using C# code | | 01:04 | instead of HTML. | | 01:06 | So I've created the row, but right now | | 01:09 | it's not attached to the table. | | 01:12 | If you go back to the static version of the website, | | 01:16 | you'll see that in the HTML tables | | 01:19 | the course names have bold around the course IDs. | | 01:23 | This is done with a pair of strong tags. | | 01:26 | So here's how I'm making that happen | | 01:28 | in my C# code. | | 01:31 | First I'm splitting up the course name into words. | | 01:35 | Parsing using a space character as the delimiter. | | 01:39 | I'll get back different numbers of words | | 01:40 | depending on the course, but I always know | | 01:43 | that the first word is the course ID. | | 01:46 | So then I'm rebuilding the course name | | 01:48 | one word at a time. | | 01:50 | At line 37 I'm wrapping the course number | | 01:53 | or ID in a pair of strong tags. | | 01:57 | Then on lines 38 to 41 I'm looping | | 02:00 | through the remaining course words | | 02:03 | and for each word re-appending a space character | | 02:06 | and that word. | | 02:08 | So now the variable course name has the same text, | | 02:12 | but the strong tags are where they need to be. | | 02:16 | So I have a table row and I have my course name. | | 02:20 | Next I'll create my table cells. | | 02:23 | Once again, these are web form controls. | | 02:27 | I'm instantiating them using pure C# code, | | 02:30 | instead of markup, but they're the same thing. | | 02:33 | To add pure text to a cell, use the cell's ID | | 02:38 | then the controls property, | | 02:40 | and from there call the add method. | | 02:43 | To set the text of the cell, wrap the text | | 02:46 | in an instance of the class LiteralControl. | | 02:49 | So that's how I'm setting the text of the cell, | | 02:52 | and then I'm adding the cell to the row. | | 02:56 | Then I repeat that code at lines 48 to 50 | | 02:59 | for the hours, the numeric value. | | 03:02 | Notice that the expression at line 49 | | 03:05 | has course.hours.ToString. | | 03:08 | When I parsed the csv files, I took that numeric value | | 03:12 | and stored it as a number just in case I need | | 03:15 | to do any mathematical operations | | 03:17 | or numeric formatting later on. | | 03:20 | But here, I need to re-enter it as a simple string, | | 03:24 | and that's how I'm doing it. | | 03:27 | So now I have my two cells | | 03:28 | and I've added them to the row. | | 03:31 | At line 53, I'm adding the row to the table, | | 03:34 | once again, using the controls property | | 03:37 | of the object I'm working with | | 03:39 | with the syntax table.controls.add. | | 03:43 | And that's it. | | 03:45 | That's all the work that I'm doing. | | 03:47 | Just as with the tables themselves, | | 03:49 | I'm not adding any information about colors or fonts. | | 03:53 | I'm letting the existing cascading style sheet file | | 03:57 | do all that work for me. | | 03:59 | Once again, here's the result. | | 04:02 | The finished HTML page looking exactly the same | | 04:06 | as it did when it was a static webpage. | | 04:09 | But now the page is being generated | | 04:11 | dynamically at run time. | | 04:13 | If I modify the csv files and re-run the page, | | 04:17 | the new or edited data will | | 04:19 | show up here automatically. | | 04:22 | I won't have to go back to the core page | | 04:24 | or the user controls if the only thing | | 04:27 | that's changed is the data. | | 04:30 | So that's the rest of the solution. | | 04:33 | I've described how I re-factored a static webpage | | 04:36 | and turned it into a dynamic asp.net web form. | | 04:40 | And then how I wrote just a little bit | | 04:42 | of C# code to extract the data | | 04:45 | from the csv files at run time | | 04:47 | and inject that data dynamically into the web form | | 04:51 | before sending it back to the browser. |
|
|
No comments:
Post a Comment