IntroductionWelcome00: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 course00: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 files00: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 Clinic00: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 Oreille00: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 solution00: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 service00: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 application00: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 cache00: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 statistics00: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 service00: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 format00: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 dates00: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 subset00: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 solution00: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 compare00: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 matching00: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 solution00: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.Imaging00: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 question00: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 solution00: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 Studio00: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 interface00: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 solutions00: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 solutions00: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 movements00: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 solution00: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 sounds00: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 file00: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 library00: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 data00: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 solution00: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 dependencies00: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 Pack00: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 files00: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 results00: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 forms00: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 solution00: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 files00: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 controls00: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 controls00: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 files00: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 runtime00: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