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