Introduction
Welcome to the first article in a series devoted to a WPF application I wrote, called Podder. Podder allows you to listen to podcasts streamed over the Web. The aim of this article is to introduce Podder and let people around the world start enjoying it. The goal of this entire series, however, is to review interesting aspects of Podder�s design and implementation.
At the time of this writing, the application is incomplete. There are still many features, both big and small, that I intend to add to Podder. However, it is far enough along at this point to get this series off the ground, so I decided that sooner is better than later. The file downloads, located at the top of this article, will most likely change every time there is a new article in the series. Podder is a living project.
Background
A podcast is like a radio show; it is a series of episodes recorded by the same person or people about some particular topic. It can contain any audio content, since it is just a regular audio file, such as MP3. An episode can contain interviews, monologues, music, the sound of one hand clapping, etc. Almost every podcast has an RSS feed associated with it, so that you can easily find all episodes in the podcast, and discover newly released episodes.
A podcatcher is a program that allows you to play podcast episodes. Most podcatchers allow you to subscribe to various podcast RSS feeds, and will automatically notify you when a new episode is available. Podder allows you to maintain a list of podcast RSS feeds, but does not yet provide any notifications when new episodes exist.
The Podder application was created after I had built two simple podcatcher prototypes; the first was in Silverlight and the other in WPF. Podder is much more sophisticated and feature-rich than those two prototypes, but they were excellent learning experiences necessary for me to figure out how to design Podder correctly.
Technologies Used
- VS2008 & .NET 3.5
- WPF
- XLinq
- C# 3.0
Podder Features
The application has the core features one might expect in a podcatcher, and a few extra bells and whistles that I enjoy using. Here is what you will find in Podder:
- Store a list of podcast RSS feeds and view all available episodes
- Stream episodes for immediate playback
- Standard play, pause, and stop functionality
- Seek through a podcast, like fast-forward and rewind
- Indicate which episodes are your favorite, and view them in a �My Favorites� list. That list is persisted between runs of the application.
- Remember which episodes you have already listened to because they are marked as �finished� when they complete. The list of finished episodes is persisted between runs.
- Optionally play every episode in a podcast in an endless loop
- Refresh the list of episodes in a podcast, which is useful for frequently updated podcast feeds
- Open an episode in an external program (either a Web browser or an MP3 player, depending on the podcast)
- Tooltip over episode item in
ListView displays extra information such as publication date and file size
- Window sizes, states, and positions persist between runs of the application
- Various user interface settings persist between runs of the application
- Pre-loaded with some podcasts, so new users can immediately start listening to episodes
- Podder will eventually allow you to apply completely different user interfaces at runtime (a.k.a. �structural skinning�)
Picture Gallery
Here are some screenshots of Podder, using the default skin (at this point, it is the only skin). The first image shows the list of favorite episodes and a tooltip over an episode, displaying extra information about it.

The next image shows the list of podcasts I have entered into Podder. Notice that the first item in the dropdown allows you to view the list of favorite episodes.
The screenshot below shows the context menu that appears when you right-click on an item in the ListView. The menu item allows you to open an episode in a Web browser or MP3 player, based on the URL provided in the podcast RSS feed�s <link> element. If it is a Web page URL, the page will open in your default browser. If it points directly to an audio file, it will open in your default audio player (such as Windows Media Player).
The last image shows the dialog box used to add and remove podcasts to the application�s list of podcast RSS feeds. At the time of this writing, this dialog box does not provide any validation, but it will in a subsequent release.
Points of Interest
There are many interesting things to explore in Podder. This section lists some of them, and the next articles in the series explore some of them in detail.
Structural Skinning
Podder supports what I call �structural skinning�. Structural skinning allows you to apply a completely new user interface to the application at runtime. You can replace all of the controls in the UI, as well as the color schemes, fonts, etc. It uses the Model-View-Controller (MVC) pattern, routed commands, collection views, and dynamic resource references to make this possible. A future article in this series will review this topic in depth. As of this writing, Podder does not have any additional skins, but it will soon.
EpisodePlayer
The application uses WPF�s MediaPlayer class to play episode MP3 files. I subclassed MediaPlayer to create EpisodePlayer, which encapsulates MediaPlayer manipulation and workaround for its quirks. A disproportionately large amount of my development effort was spent on working around strange behavior of MediaPlayer. I have ironed out all the issues, as best as I can.
Reading an RSS Feed with XLinq
A podcast�s RSS feed is just like any RSS feed, it is just an XML document. Since Podder was written against the .NET 3.5 Framework, I used XLinq to transform an RSS feed into instances of my Episode class. Using XLinq made the XML processing much easier.
Serializable Data Model
Podder does not use a database or XML file to save its data. When the window closes, it serializes the data model using BinaryFormatter and saves that binary stream to disk. When the application opens again, it deserializes the data in that file and the application then uses those objects.
The application�s domain classes are in the PodderLib project. Most of those classes derive from my BindableObject base class, which provides an implementation of INotifyPropertyChanged, amongst other things.
Persisted Window Settings
Both the main window and the �Manage Podcasts� dialog box remember their size, state, and location between runs of the app. They both derive from my ConfigurableWindow base class to gain that functionality for free. That class was built specifically for Podder, but can be used in any WPF program.
So Much More�
It is difficult to create a list of �interesting� aspects of an application�s code base, because everyone sees things differently. If you are interested in seeing what else Podder has to offer, I suggest you download the source code and explore it on your own. If you have any questions, feel free to leave a question or comment on this article�s message board.
Not Yet Implemented
- Ability to dynamically discover and apply external skins
- Awareness of an active Internet connection, or lack thereof
- An improved Podcast management dialog
- User-friendly error handling
- An aesthetically pleasing UI
- Notifications of when a new episode exists in one of your feeds
- A whole lot more!
Special Thanks
Craig Shoemaker, the host of Polymorphic Podcast, provided a lot of helpful feedback, insightful suggestions, and encouragement.
Karl Shifflett helped a lot with testing and submitting bug reports. He also gave some tasteful feature requests.
Sacha Barber indirectly assisted because his article about XLinq helped me get up to speed with XLinq enough so that I could use it to parse RSS feeds.
Revision History
- January 6, 2008 - Created the article
|
|
 |
 | [Message Removed] nompel | 15:57 20 Sep '08 |
|
|
 |
 | Podder v2 Has Been Released! Josh Smith | 10:26 6 Mar '08 |
|
 |
The second article[^] in this series contains Podder v2. It is much more feature-rich and robust than Podder v1. Go get it!
:josh: My WPF Blog[ ^] All of life is just a big rambling blog post.
|
|
|
|
 |
 | Question on Data Binding Usage [modified] aerospaceboy | 10:37 3 Feb '08 |
|
 |
I really appreciate the work you have done here, but just wanted to ask one question. I am curious as to why you store selected item information in the data model? This doesn't seem to be necessary, although since I do not know every implication of the decision I may be overlooking something.
For example, in the MainWindowView_Episodes.xaml under the _podcastListView declaration you set the Path of the DataContext Binding to EpisodePlayerSettings.SelectedPodcast when you could of set it to Podcasts.CurrentItem.
This would utilize the ICollectionView's management of CurrentItem instead of tracking that information in the data model.
I also noticed that with this change, and the removal of the line setting this property in the OnCurrentPodcastChanged method of the MainWindowController class, the property can be removed altogether from the data model.
Thanks again.
modified on Sunday, February 03, 2008 3:58:29 PM
|
|
|
|
 |
|
 |
That's a great question. The truth is, I did not know that binding to Podcasts.CurrentItem was possible. I was not aware that the XAML parser allows you to specify an ICollectionView property on the collection object it wraps. If I had known that, I probably would not have placed SelectedPodcast in the data model. Thanks for teaching me about that!
:josh: My WPF Blog[ ^] All of life is just a big rambling blog post.
|
|
|
|
 |
 | Cool.... marlongrech | 12:33 7 Jan '08 |
|
 |
once again Josh does it.... nice one...
Regards C# Disciple
|
|
|
|
 |
|
 |
Thanks Marlon. In the immortal words of Randy Bachman, "You ain't seen nothing yet." This series is just getting started!!
:josh: My WPF Blog[ ^] All of life is just a big rambling blog post.
|
|
|
|
 |
|
 |
I really like how you implemented the MVC with Commands.... It is really cool... It would be nice to see a futher article on that from you
Great job keep it up
Regards C# Disciple
|
|
|
|
 |
 | Nice but... Rama Krishna Vavilala | 4:47 7 Jan '08 |
|
 |
Overall, I liked the MVC design. Though, I normally prefer to have no code in the view classes (only markup).
Well sorry to be -ve but there are some aspects that I am not really crazy about in the code:
1. The error messages are in the Settings file instead of the resources file.
2. I am not crazy about deriving everything from the BindableObject class. This seems to be derivation just to add some functionality to the sub classes rather than due to a relationship.
Overall a nice article as usual.
|
|
|
|
 |
|
 |
Rama,
How's everything?
Got a question about point #2. I'm trying to learn a better way here.
If you have a base class like BindableObject, and want to add its functionality to subclasses as opposed to relationship building, what is the "Rama Approved" method for doing this?
In my WPF projects, my BLL entity objects all inherit from my BindableValidatableBase. There is no Goldfish is a Fish relationship, but the Customer, Vendor, Account entity classes need the built in functionality of this base class.
I looked at Delegation, but this one base class is much simpler and doesn't break polymorphic rules.
Thanks for your thoughts.
modified on Monday, January 07, 2008 12:05:35 PM
|
|
|
|
 |
|
 |
Thanks for the feedback, Rama. Please help me understand your points more clearly.
Rama Krishna Vavilala wrote: 1. The error messages are in the Settings file instead of the resources file.
What's wrong with putting them in the Settings file? What am I missing here?
Rama Krishna Vavilala wrote: 2. I am not crazy about deriving everything from the BindableObject class. This seems to be derivation just to add some functionality to the sub classes rather than due to a relationship.
BindableObject implements the all-important INotifyPropertyChanged interface, which is a must for any objects being bound to the UI. Since most of the classes in my data model are bound to, I need them to implement that interface. So, I put the interface implementation into a common base class. What's wrong with that?
Thanks.
:josh: My WPF Blog[ ^] All of life is just a big rambling blog post.
|
|
|
|
 |
|
 |
To be honest Josh, I like the bindable object idea so much, im going to use right now on my next article.
I think its a good way to be honest. It does all the ground work. I like that
Sacha Barber
- Microsoft Visual C# MVP 2008
- Codeproject MVP 2008
Your best friend is you. I'm my best friend too. We share the same view, and never argue My Blog : sachabarber.net
|
|
|
|
 |
|
 |
Sacha Barber wrote: I think its a good way to be honest.
Me too. It makes perfect sense to me.
Another nice feature of BindableObject is that if you call the RaisePropertyChanged method and pass in a property name that does not match any public property on the object, it will cause an error. This only happens when running a Debug build, not in Release. This feature has saved me hours of debugging time after renaming a property, but forgetting to update the property name string used in the PropertyChangedEventArgs.
:josh: My WPF Blog[ ^] All of life is just a big rambling blog post.
|
|
|
|
 |
|
 |
Yep I like that too.
What I dont like is using 3rd party web services, which I am for current article, and they throw some null value, every so often. Can I find it, no I cant.
Grrr
Sacha Barber
- Microsoft Visual C# MVP 2008
- Codeproject MVP 2008
Your best friend is you. I'm my best friend too. We share the same view, and never argue My Blog : sachabarber.net
|
|
|
|
 |
|
 |
Hey Josh,
Looking forward to the rest of the articles. Regarding the BindableObject's RaisePropertyChanged method, does that work only with typed properties on the object's interface, or can it work on dynamic properties passed in a property bag like structure with name/value pairs like you guys use in Tangerine?
Regards,
Roland Rodriguez
|
|
|
|
 |
|
 |
roland rodriguez wrote: Regarding the BindableObject's RaisePropertyChanged method, does that work only with typed properties on the object's interface, or can it work on dynamic properties passed in a property bag like structure with name/value pairs like you guys use in Tangerine?
The real question is, does WPF's data binding infrastructure support property bags. The answer is no, it only binds to and detects changes on "real" properties of an object. So, using BindableObject for an object whose property's are in a property bag would not work out.
:josh: My WPF Blog[ ^] All of life is just a big rambling blog post.
|
|
|
|
 |
|
 |
According to MS the WPF databinding engine is supposed to recognise ICustomTypeDescriptor though so exposing the property bag via that should circumvent that, no?
Thanks again for another great article.
R
|
|
|
|
 |
|
 |
roland rodriguez wrote: According to MS the WPF databinding engine is supposed to recognise ICustomTypeDescriptor though so exposing the property bag via that should circumvent that, no?
Oh, I see what you're getting at now. I have never tested BindableObject with a child class that uses ICustomTypeDescriptor to expose a property bag, so I don't know if it works. But, once Rama's fix is in place, I don't see why it would not work.
Thanks.
:josh: My WPF Blog[ ^] All of life is just a big rambling blog post.
|
|
|
|
 |
|
 |
To turn it the other way: Why not deriving from DependencyObject object? I'm currently playing with the MVC pattern and deriving from DependencyObject gives me the advantage of being able to implement Dependency properties (instead of INotifyPropertyChanged) in my "controllers". Another nice thing is that you can implement asynchronous methods without loosing the GUI thread affinity by means of Dispatcher.Invoke. Basically like this:
public bool IsActionRunning { get { return (bool)GetValue(IsActionRunningProperty); } private set { SetValue(IsActionRunningPropertyKey, value); } }
void OnCommandActionExecuted(object sender, ExecutedRoutedEventArgs e) { IsActionRunning=true; Action action=AndAction; action.BeginInvoke(...) }
void AndAction(ActionArgs args) { ... Dispatcher.Invoke( DispatcherPriority.Normal, new Action(ActionCompletedCompleted), qs); }
void ActionCompletedCompleted(ActionResultCollection result) { IsActionRunning=false; }
//eop
"All languages allow you to write crap code, VB just makes it easier (IMO)."
Michael P Butler
|
|
|
|
 |
|
 |
I use BinaryFormatter to save the app state, so I need my domain objects to be serializable. DependencyObject is not serializable.
:josh: My WPF Blog[ ^] All of life is just a big rambling blog post.
|
|
|
|
 |
|
 |
I'm curious why you do not use XamlReader /XamlWriter for this purpose? It should work perfectly with DependencyObject derivates.
cu
"All languages allow you to write crap code, VB just makes it easier (IMO)."
Michael P Butler
|
|
|
|
 |
|
 |
Maximilian Hänel wrote: I'm curious why you do not use XamlReader /XamlWriter for this purpose? It should work perfectly with DependencyObject derivates.
Yes, but it is much slower. XamlWriter is notoriously slow. BinaryFormatter is fast.
:josh: My WPF Blog[ ^] All of life is just a big rambling blog post.
|
|
|
|
 |
|
 |
Thanks for answering!
"All languages allow you to write crap code, VB just makes it easier (IMO)."
Michael P Butler
|
|
|
|
 |
|
 |
Josh Smith wrote: What's wrong with putting them in the Settings file?
Simple reason this is not a setting. Settings are more for user configurable items at runtime: like window height, width (as you have correctly used). However error messages are not user configured. It does not make any sense for the user to modify the error message text. BTW: the app settings file get saved in configuration file and the user settings in a similar xml file in the user profile directory. Also when you think about localization resources make more sense.
Josh Smith wrote: BindableObject implements the all-important INotifyPropertyChanged
There is nothing wrong per se. It's a more nit picky stuff. I will give a detailed reply a little later. Unless Marc jumps in and puts his take on it.
|
|
|
|
 |
|
 |
Rama Krishna Vavilala wrote: Simple reason this is not a setting.
Good point. I guess I used the settings file because VS generates some classes that you can use to easily access those values. I never checked to see if the Resources does that, too, but all my experiences with resources are a pain involving ResourceManager, resource IDs, etc. A real nuisance. Using the Settings file is quick and easy. I'm lazy.
Rama Krishna Vavilala wrote: Also when you think about localization resources make more sense.
Good point.
Rama Krishna Vavilala wrote: There is nothing wrong per se. It's a more nit picky stuff. I will give a detailed reply a little later. Unless Marc jumps in and puts his take on it.
I assume you're going to go into the "IS A" relationship, etc. If so, don't bother. I know all about that, and long ago decided that object orientation IS A tool I use to HAVE AN easier time building software. I'm not into the whole philosophical debate of conceptual purity, etc. It's interesting stuff, but I'm not really interested in adhering to anything besides getting the job done. If I wrongly assumed your objection to my BindableObject class, please feel free to fire away with what you have in mind.
Thanks.
:josh: My WPF Blog[ ^] All of life is just a big rambling blog post.
|
|
|
|
 |
|
 |
Josh Smith wrote: I never checked to see if the Resources does that
Starting with VS 2005, accessing resources extremely easy. You can just do the following:
Resources.ErrorMessage. VS 2005 creates a type safe way to access respources. That's hwta you miss for programming in WPF and not WInForms
Josh Smith wrote: whole philosophical debates of conceptual purity
That's why I called it nit-picky and not a big deal. The issue here is that BindableObject works well for your code. But in complex hierarchies it is really not possible to have a base classes for interface implementation. So I agree it is not a big deal.
But here are some other minor stuff. I normally prefer to do it the standard way
virtual void OnPropertyChanged(PropertyChangedEventArgs e);
..insteda of Raise and After method.
Also I suggest a minor change to the VerifyProperty method(it is used only in debug so not a big deal again):
[Conditional("DEBUG")] private void VerifyProperty(string propertyName) { if (TypeDescriptor.GetProperties(this).Find(propertyName) == null) {
string msg = string.Format( ERROR_MSG, propertyName, type.FullName);
Debug.Fail(msg); } }
That way even the custom properties can be accounted for.
|
|
|
|
 |
|
|
Last Updated 6 Jan 2008 |
Advertise |
Privacy |
Terms of Use |
Copyright ©
CodeProject, 1999-2010