A Simple C# Application : Part1

I get sidetracked from time to time and enjoy play around with new things.

Or in this case, old things I just never played with before.

My “daily driver” is a gamer laptop that runs Windows 8.1. It’s primary purpose is to run Linux in VMware, as my employer does not support 100% Linux systems. I still do use the Windows “half” for a few things though, and I do like the looks of Windows 10. (Hopefully VMware will announce support for Windows 10 as a host operating system soon…)

I am also an avid comic book fan. I read most of my comics on an iPad, some with Comixology’s great reader and some in Comic Book Archive (CBR) format. You can get a lot of DRM free comics in CBR format. For example, there’s a bunch here.

As the Wikipedia article on CBR spells out in detail, comic book archive files are generally ZIP or RAR (ugh) archives of PNG or JPG images.

While I read these files on my tablet most often (I recommend Comic Zeal, which has a great web server mode for uploading comics to your device instead of the ghastly iTunes file sharing interface.) I occasionally want to manipulate a file on my laptop, usually to extract an image and share with someone.

So why not write an application for that? Visual Studio is free for personal use now, and I’ve heard from a few people that C# is a decent language to work in.

So here’s what I want to do:

  • Open a CBZ or CBR file
  • Display each image in order
  • Allow the user to save individual images to file

The code for the final application, as well as the intermediate steps for each blog post, will be available in Github. (That’s a link to the first version, covered in this post.)

Displaying a Window

For this project I used C# and Windows Presentation Foundation (WPF.) I was able to display a window and open files very quickly, in a language I never used before.

Open a new project in Visual Studio 2013. Select Visual C# in the New Project Window and then WPF Application. Give your project a name and then click OK.

The project should open in the “Visual Designer,” but if it does not open it from the View menu or with Shift-F7. You add controls to your window from the Toolbox, which should be available on the left or by pressing CTRL-ALT-X. As you add controls, the XAML in the pane below the visual editor will update.

After some playing around I ended up with this XAML:

[code language=”xml”]
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" x:Class="ComicViewer.MainWindow"
Title="ComicViewer" Width="Auto" Height="Auto" SizeToContent="WidthAndHeight">
<Grid>
<Menu DockPanel.Dock="Top">
<MenuItem Header="_File">
<MenuItem Header="_Open" Click="MenuItem_Click_1"/>
<MenuItem Header="_Close" Click="MenuItem_Click_2"/>
<MenuItem Header="_Save" Click="MenuItem_Click_3"/>
</MenuItem>
</Menu>
<StackPanel Orientation="Vertical">
<StackPanel >
<Image x:Name="ImageViewer1" />
</StackPanel>
</StackPanel>
</Grid>
</Window>
[/code]

This defines a single window named MainWindow that belongs to a class named ComicViewer. It has a menu with three options, and an ImageViewercontrol. Note that the main window’s size settings are “Auto” for both height and Width and that we have an extra SizeToContent attribute. These are not the defaults.

If I try to build and run without modifying the code associated with this window the build will fail. The app needs event handlers for the menu items. Each of the three menu items (Open, Close, and Save) have a Click attribute with a method name defined.

Press F7 to bring up the code window and add the methods defined at the bottom of this image:

[code lang=”csharp”]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace ComicViewer
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}

private void MenuItem_Click_1(object sender, RoutedEventArgs e)
{
Console.WriteLine("File open.");
}

private void MenuItem_Click_2(object sender, RoutedEventArgs e)
{
Console.WriteLine("File close.");
}

private void MenuItem_Click_3(object sender, RoutedEventArgs e)
{
Console.WriteLine("File save.");
}

}
}
[/code]

Now press start to build and run the app. There’s a very small window with the standard File menu. Select a menu option and you’ll a message in Studio’s Output window.  (You may have to open it on the bottom while the application is running.)

These console messages tell us where the action will be in this app.

GUI (Graphical User Interface) programs are event driven. They respond to events generated by the user clicking on the screen and/or pressing keys, as opposed to following a set path defined by the developer. Our only user control so far is the menu, so it is where all of our events will come from.

Opening a File

So let’s wire up opening a file:
[code lang=”csharp”]
private void MenuItem_Click_1(object sender, RoutedEventArgs e)
{

Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog();

// Set filter for comic book archives
dlg.Filter = "CBR files (*.cbr)|*.cbr|CBZ files (*.cbz)|*.cbz|All Files (*.*)|*.*";

// Display OpenFileDialog by calling ShowDialog method
Nullable<bool> result = dlg.ShowDialog();

// Get the selected file name and log to console (for now)
if (result == true)
{
// Open document
string filename = dlg.FileName;
Console.WriteLine("Filename:" + filename);
}
}
[/code]

No surprises here. When the event handler MenuItem_Click_1 is called, the app instantiates a standard file open dialog.
[code lang=”csharp”]
dlg.Filter = "CBR files (*.cbr)|*.cbr|CBZ files (*.cbz)|*.cbz|All Files (*.*)|*.*";
[/code]
The file dialog’s Filter property allows us to offer sensible options for filtering the files shown in the open window.

If the user selects a file ShowDialog() returns true and we print the filename. If not, the event handler quietly returns.

Examining An Archive File

If all comics archives were zipped, we could use C#’s built-in ZipStream class. Many use RAR, which is a proprietary format and lacks native support in most environments.

Fortunately there is an easily accessible native C# library that we can add to our project that at least support RAR up to version 4. SharpCompress makes this project very simple to implement.

In Solution Explorer right-click on your project name and select Manage Nuget Packages.

Select Online in the left hand side of the Manage Nuget Packages window.

Type “SharpCompress” in the search box. Click Install on SharpCompress version .11. (The latest at the time this was published.

Until now we’ve only used classes that Visual Studio added to our declarations for us. (With the exception of the OpenFileDialog, which we declared using its fully qualified name.) Add SharpCompress to your declarations.
[code lang=”csharp”]
using SharpCompress.Archive;
[/code]
We need to add it to our packages because we’ll eventually use a few different classes from the library. In the case of OpenFileDialog we only need it from its package, so it wasn’t worth importing all of Microsoft.Win32 which is a large package.

Now let’s use SharpCompress to read the archive’s table of contents:

[code lang=”csharp”]
private void openArchive(String filename)
{
var archive = ArchiveFactory.Open(filename);
foreach (IArchiveEntry entry in archive.Entries)
{
if (!entry.IsDirectory)
{
Console.WriteLine(entry.Key);
}
}
}
[/code]

As I said, SharpCompress makes this project very simple.

Before you can test this, you need an archive. There’s a couple in the Github archive for this project, or you can download one here.

When we select a comic archive we see a list of files in the archive printed to the output window.

So what happens if a user bypasses our file select filters and tries to open a file that is not an archive? Try it.

ArchiveFactory throws an exception and our program screeches to a halt. We need to protect ourselves from this.
[code lang=”csharp”]
private void openArchive(String filename)
{
try
{
var archive = ArchiveFactory.Open(filename);
foreach (IArchiveEntry entry in archive.Entries)
{
if (!entry.IsDirectory)
{
Console.WriteLine(entry.Key);
}
}
}
catch (InvalidOperationException ioe)
{
Console.WriteLine("Error opening file " + ioe.Message);
MessageBox.Show("Error opening file. Please try another.");

}
}
[/code]

If we catch the exception we can return from the file open event handler and give the user another chance. We also display a message telling them what happened.

We covered a lot this time. Next time we’ll display some images. See you then!

One Reply to “A Simple C# Application : Part1”

Leave a Reply