Thursday, March 15, 2007

Writing a Simple Image Editor in CAB - Part 4

In today’s installment we’ll continue where we left off and add the functionality to apply image filters.

In order to do this, let’s take a step back and think about how we want to accomplish this.  I’m imagining two use cases:

  • Load filters from dynamic assemblies
  • Apply a filter to the image

To load the filters, I’ll have a separate assembly implement a specific interface, then they will be loaded from a directory and placed in the menu.

First, our core interface:

using System;

using System.Drawing;

 

namespace ImageFiltering

{

    public interface IImageFilter

    {

        string Name { get; }

        Bitmap Filter(Bitmap source);

    }

}

The name property determines what will be displayed on the menu.  The Filter method is, as they say on MTV Cribs, “where the magic happens.”

I’ll implement some basic image filters in a bit.  Since both of these use cases are related, I can package them up in the same module.  I create a module called ImageFiltering.

The first workitem is going to be the LoadFiltersWorkItem.  It will run at startup and read up the assemblies and obtain a list of IImageFilter implementations:

private UIExtensionSite GetMainMenu()

{

    //return the well known ui extension site for the main menu

    return RootWorkItem.UIExtensionSites["MainMenuStrip"];

}

 

protected override void OnRunStarted()

{

    base.OnRunStarted();

 

    //get the menu strip

    UIExtensionSite site = GetMainMenu();

 

    //create the new menu item

    ToolStripMenuItem filterMenu = new ToolStripMenuItem();

    filterMenu.Text = "Filters";

 

    //load the filters

    LoadFiltersFromDirectory(filterMenu);

 

    //add the filters menu to the site

    site.Add<ToolStripMenuItem>(filterMenu);

}

The LoadFiltesrFromDirectory(filterMenu) method basically uses reflection to pull out all of the assemblies in the /filters directory of our application.  The details of this aren’t important here, but if you want to see it you can download the project at the end of today’s article.

Since this needs to run when the application starts up, we code that up in the Module Init class:

public class ImageFilteringModuleInit : ModuleInit

{

    private WorkItem _rootWorkItem;

    [ServiceDependency]

    public WorkItem RootWorkItem

    {

        get { return _rootWorkItem; }

        set { _rootWorkItem = value; }

    }

 

    private LoadFiltersWorkItem _loadFiltersWorkItem;

 

    public override void Load()

    {

        base.Load();

 

        //load up our work items

        _loadFiltersWorkItem = this.RootWorkItem.WorkItems.AddNew<LoadFiltersWorkItem>("LoadFiltersWorkItem");

        _loadFiltersWorkItem.Run();

    } 

}

This is pretty basic Module Init stuff.  Create the work item and run it.  Assuming we’ve already created some filters and placed the dll inside of the filters directory, we should be able to run the application and see the filters loaded in the menu.

Our filters have been loaded

Notice how I initially set the menu items to be disabled.  This is because no image has been loaded yet!  We already have an event being raise when the image is loaded, so I handle that in the LoadFiltersWorkItem and enable the menu items:

[EventSubscription("topic://ImageEditor/ImageDisplayModule/ImageLoaded", ThreadOption.UserInterface)]

public void OnImageSelected(object sender, DataEventArgs<Bitmap> e)

{

    _image = e.Data;

    foreach (ToolStripMenuItem filterItem in _filterItems)

    {

        filterItem.Enabled = true;               

    }

}

Attached to the Tag property of each of these menu items is the concrete filter that we dynamically loaded.  The next step is to raise an event when the filter is clicked, so that the other work item (that we’re about to create) can apply the filter.

As I create each of the menu items, I wire up the click event to call this handler:

void filterItem_Click(object sender, EventArgs e)

{

    ToolStripMenuItem menuItem = sender as ToolStripMenuItem;

    IImageFilter filter = menuItem.Tag as IImageFilter;

 

    if (FilterSelected != null)

    {

        DataEventArgs<IImageFilter> args = new DataEventArgs<IImageFilter>(filter);

        FilterSelected(this, args);

    }

}

The event is raised with the concrete filter passed along as data event args.

The next work item doesn’t get created at start up.  It begins when the user clicks the menu item.  So when/where do we kick off the work item?  We handle the FilterSelected event (that is being published as a CAB event) in our module, and create the workitem there:

 [EventSubscription(ModuleConstants.Events.FilterSelected, ThreadOption.UserInterface)]

 public void OnFilterSelected(object sender, DataEventArgs<IImageFilter> e)

 {

    //get the current image

    Bitmap image = _loadFiltersWorkItem.Image;

 

    //create a new workitem to apply the filter           

    WorkItem wi = new FilterImageWorkItem(e.Data, image);

    this.RootWorkItem.WorkItems.Add(wi);

 

    wi.Run();

 }

We have to get a handle on the current image as well as the filter, and this proved to be a bit difficult.  I’d like to have some sort of global state (and maybe I’m missing something) but the State property is WorkItem specific.  I was already handling the ImageLoaded event in the LoadFiltersWorkItem, so I snag the image from that event and hang on to it.  The module reads this and passes it to the FilterImageWorkItem.  Until I can figure out a cleaner way to do this, it will have to stay for now.

Here’s the FilterImageWorkItem:

public class FilterImageWorkItem : WorkItem

{

    private IImageFilter _filter;

    private Bitmap _image;

 

    [EventPublication(ModuleConstants.Events.ImageLoaded, PublicationScope.Global)]

    public event EventHandler<DataEventArgs<Bitmap>> ImageLoaded;

 

    public FilterImageWorkItem(IImageFilter filter, Bitmap image)

    {

        _filter = filter;

        _image = image;

    }   

 

    protected override void OnRunStarted()

    {

        base.OnRunStarted();

 

        Bitmap newImage = _filter.Filter(_image);

        if (ImageLoaded != null)

        {

            ImageLoaded(this, new DataEventArgs<Bitmap>(newImage));

        }

    }

}

Pretty plain and simple.  It calls the filter on the image when it runs.  We need to notify the canvas to redraw the new image, so we raise the already implemented event ImageLoaded.  Once this fires, the DisplayImageWorkItem picks it up and displays the filtered image.

Let’s check out the app in action.

The original image
The original image

The Black & White filter applied
The Black & White filter applied

The inversion filter applied
The inversion filter applied

The image filters I originally wrote were painfully slow, so I borrowed some excellent filters from the code project article here by Christian Graus.  I’m glad I could leverage somebody else’s hard work, because it’s been a few years since I did complex image processing .

I hope you enjoyed this short series of articles on building an application using CAB.  I only touched on some of the topics.  For example, ObjectBuilder was barely used in this application.  I also didn’t use WorkItem State or the StatePersistenceService.  This would be a quick and easy way to save the users work, or you could provide a standard FileSaveDialog box.

You can download the source here:
File Attachment: CAB-ImageEditor.zip (86 KB)
(you’ll need to have CAB already installed and you need to point the reference to the CAB dlls)

I’d appreciate any comments or criticisms you may have for me.

Tuesday, March 13, 2007

Writing a Simple Image Editor in CAB - Part 3

Last time I left off with a working shell, and we loaded a single module into the shell.  In this article we’ll cover:

  • Raising and handling events
  • Invoking and handling commands
  • Adding dynamic items to a UIExtensionSite (our menu)

We have the ability to display the image, but we need to be able to load the image from somewhere. 

I created another WorkItem called ImageSelectionWorkItem.  This workitem will be responsible for loading an image and raising an event that it occurred.

using System;

using Microsoft.Practices.CompositeUI;

 

namespace ImageDisplayModule.WorkItems.Selection

{

    public class ImageSelectionWorkItem : WorkItem

    {

 

        private ImageSelectionView _view;

        private ImageSelectionPresenter _presenter;

 

        protected override void OnRunStarted()

        {

            base.OnRunStarted();

 

            //create & wire up presenter

            _presenter = this.Items.AddNew<ImageSelectionPresenter>();

 

            //create the view

            _view = new ImageSelectionView(_presenter);

 

            //run the view

            _view.Show();         

        }           

 

 

    }

}

This code here is just wiring up the view and presenter so that they can communicate.  Take note on the line where we call this.Items.AddNew<>.  This will instantiate the type provided and add it to the items collection of the WorkItem.  It is important to belong to this collection if you are going to participate in any handle or raise any CAB events or commands.  Our presenter is going to raise an event, so it needs to be added to the Items collection of the WorkItem.

Here is the presenter:

public class ImageSelectionPresenter

{

    [EventPublication(ModuleConstants.Events.ImageLoaded, PublicationScope.Global)]

    public event EventHandler<DataEventArgs<Bitmap>> ImageLoaded;

 

    public void OnFileSelected(string filename)

    {

        if (ImageLoaded != null)

        {

            Image img = Image.FromFile(filename);

            Bitmap bmp = new Bitmap(img);

 

            ImageLoaded(this, new DataEventArgs<Bitmap>(bmp));

        }

    }

}


The view is going to call this function when the user selects a file, and the presenter will raise a global CAB event.  I defined the event name as a constant so that it can be safely referred to throughout the module.

The view isn’t actually a User Control.  It’s just a class that opens a windows forms dialog for opening the file:

public class ImageSelectionView

{

 

    private ImageSelectionPresenter _presenter;

 

    public ImageSelectionView(ImageSelectionPresenter presenter)

    {

        _presenter = presenter;

    }

 

    public void Show()

    {

        using (OpenFileDialog fileDlg = new OpenFileDialog())

        {

            fileDlg.Filter = "Picture files (*.gif; *.jpg; *.bmp; *.png)|*.gif;*.png;*.bmp;*.jpg";

            DialogResult result = fileDlg.ShowDialog();

 

            if (result == DialogResult.OK)

            {

                OnFileSelected(fileDlg.FileName);

            }

        }

    }

 

    public void OnFileSelected(string filename)

    {

        _presenter.OnFileSelected(filename);

 

    }

 

}

This class focuses on the UI only, the decision on what to do is provided through the presenter.

So now that we have this WorkItem completed, we need a way to call it!  To facilitate this, we’ll load up an “Open image” menu option and load our work item when this is clicked.

Our application (the shell) has to expose the menu as a UI Extension site.  In order to do this, we need to register it.  So, in our ImageEditorApplication class:

protected override void AfterShellCreated()

{

    base.AfterShellCreated();

 

    this.RootWorkItem.UIExtensionSites.RegisterSite("MainMenuStrip", this.Shell.MainMenuStrip.Items);

    this.RootWorkItem.UIExtensionSites.RegisterSite("FileMenu", this.Shell.FileMenu.DropDownItems);

}

I registered the top-level menu as well as the File menu so we can append to it.  (Notice how I added the DropDownItems collection, rather than the item itself.  If I don’t do this then Items will be added as top level items, not children.  This took me a while to figure out…)

The next step is to find the well-known UI Extension Site for the menu (I called it “FileMenu”) and add our menu item to it (from the Module).  The reason this looks indirect is because CAB applications are, by nature, loosely-coupled.  This is a good thing.

Our module init class now looks like this:

public class ImageDisplayModuleInit : ModuleInit

{

    private WorkItem _rootWorkItem;

    [ServiceDependency]

    public WorkItem RootWorkItem

    {

        get { return _rootWorkItem; }

        set { _rootWorkItem = value; }

    }

 

    public override void Load()

    {

        base.Load();                       

 

        //create workitem(s)

        DisplayImageWorkItem wi = RootWorkItem.WorkItems.AddNew<DisplayImageWorkItem>();

 

        //modify the ui extension sites

        CreateMenuItems();

 

        //run the workitem

        wi.Run();

    }

 

    private void CreateMenuItems()

    {           

        //creat file->open menu item & register click command invoker

        ToolStripMenuItem fileOpen = new ToolStripMenuItem("Open Image");

        RootWorkItem.Commands[ModuleConstants.Commands.LoadImage].AddInvoker(fileOpen, "Click");

 

        //add it to the file menu

        UIExtensionSite site = RootWorkItem.UIExtensionSites["FileMenu"];                       

        site.Add<ToolStripMenuItem>(fileOpen);

    }

 

    [CommandHandler(ModuleConstants.Commands.LoadImage)]

    public void LoadImage(object sender, EventArgs e)

    {

        string id = "LoadImageWorkItem";

        ImageSelectionWorkItem wi;

        if (RootWorkItem.WorkItems.Contains(id))

        {

            wi = RootWorkItem.WorkItems[id] as ImageSelectionWorkItem;

        }

        else

        {

            wi = RootWorkItem.WorkItems.AddNew<ImageSelectionWorkItem>(id);

        }

 

        wi.Run();

    }

}

There is a lot going on here, so I’ll take it in pieces.  First off, we added a call to CreateMenuItems().  This method will be responsible for loading any menu items required by its work items.   We create our menu item for “Open Image” and add it’s click event as a Command Invoker.  This is very similar to wiring up events.

We also have a method decorated with the CommandHandler attribute.  This is the handler for when the menu item is clicked.  It’s purpose is to create and launch the new image selection work item.

So if we can recap for a moment, our module loads, it stuffs in a new menu item into the File menu.  The user clicks this, which invokes a command called “LoadImage”.  A Command Handler picks this up and launches the ImageSelectionWorkItem.  It will display an open file dialog where the user can select an image.  Once the image is selected, an event is published.  We currently have a subscriber to this event, and it’s job is to load the selected image into the display work item.

If we run the application, we can now browse for an image, select it, and it loads onto our canvas:

Our new menu item, loaded into the UI Extension site

CropperCapture[49]

So at this point we’ve touched on most of the CAB concepts.  Next we’ll introduce another module for Filtering the image, kind of like a mini-photoshop (maybe that’s going too far, but you get the idea…).

Until next time!

Friday, March 09, 2007

Writing a Simple Image Editor in CAB - Part 2

So after part 1, we have a shell ready to go.  We haven’t done anything CABish yet, but we will in this post.

The first thing we need to do is create our layout that modules will be loaded into.  If you remember, CAB Shells basically contain 2 possible things:  Workspaces and UI Extension sites.  It just so happens that we’ll need both!

First, we’ll need a menu for loading the image and applying filters.  That will be our UI Extension Site.

Second we need a workspace in which to load the image display module.

Our first UI Extension Site

Here I added a MenuStrip and created our default File menu.  Give it a name like mainMenuToolStrip so that you can refer to it later.

Next we want to add a workspace so that we can load the image.  To gain access to these CAB UI controls, you need to add them to the toolbox on the left.  Right click on the Toolbox Pane, select Add Tab and name it CAB Controls.  Then right click on the name and select Choose Items.  You’ll have to Browse for the Microsoft.Practices.CompositeUI.WinForms.dll.  Select all of the controls and click OK.

Now you should see a toolbox group like this:

CropperCapture[43]

We will use the DeckWorkspace control.  Drag that onto the form and name it mainWorkspace.  Make sure that you dock it to fill the form.

It should look like this:

CropperCapture[44]

We have our basic form, now it’s time to load the first module into it!

Create a new class library project called ImageDisplayModule.  Rename Class1.cs to ImageDisplayModuleInit.cs.  You’ll need a reference to the dlls mentioned above as well.

If you recall, a module contains work items.  Our first work item is DisplayImageWorkItem.  It’s responsibility is to load an image and display it on the form.

Let’s start out by creating the View.  The view is a user control with a [SmartPart] attribute applied.  The view is going to consist of an ImagePlaceHolder.   Also keep in mind that we are doing this with Model-View-Presenter, so we’ll start by identifying the interface for our view.

Create an interface called IDisplayImageView.  The only real member that this interface ensures is that you can set and retrieve an image.

public interface IDisplayImageView

{

    public Bitmap Image

    {

        get;

        set;

    }

 

}

Next, create a new UserControl called DisplayImageView.  This user control needs to implement the IDisplayImageView interface.  Also notice that I have placed the [SmartPart] attribute on the class as well.

using System;

using System.Drawing;

using System.Windows.Forms;

using Microsoft.Practices.CompositeUI.SmartParts;

 

namespace ImageDisplayModule.cs.WorkItems

{

    [SmartPart]

    public partial class DisplayImageView : UserControl, IDisplayImageView

    {

        private Bitmap _image;

 

        public DisplayImageView()

        {

            InitializeComponent();

        }

 

        #region IDisplayImageView Members

 

        public Bitmap Image

        {

            get { return _image; }

            set { _image = value; }

        }

 

        #endregion

    }

}

 Next, we’ll switch over to design view and drag a PictureBox on the control.  I’ve also added a dummy label so that we can identify this control on the form in a minute.

Here’s what it looks like:

CropperCapture[45]

I’ve also created a presenter that will control all of the UI logic for this view:

public class DisplayImagePresenter

{

    private IDisplayImageView _view;

 

    public DisplayImagePresenter(IDisplayImageView view)

    {

        _view = view;

    }

 

    [EventSubscription("topic://ImageEditor/ImageDisplayModule/ImageLoaded", Thread=ThreadOption.UserInterface)]

    public void OnImageLoaded(object sender, DataEventArgs<Bitmap> e)

    {

        _view.Image = e.Data;

    }

}

Here you can see that the view is accepted in the construtor, and we are responding to an event.  The topic:// is just a hierarchical way of naming events that we might care about.  Basically the presenter is going to listen for an event when a picture is loaded, and set the image to the view once it fires.

In order to load this module into our shell, we need a few more pieces.  The first is a ProfileCatalog.xml file.  This tells the shell what modules to load.

Place an xml file in the shell project and make sure that the properties are set to Copy Always so that it will end up in your output directory.  The file should look like this:

<?xml version="1.0" encoding="utf-8" ?>

<SolutionProfile xmlns="http://schemas.microsoft.com/pag/cab-profile">

  <Modules>

    <ModuleInfo AssemblyFile="ImageDisplayModule.dll" />

  </Modules>

</SolutionProfile>

The important concept to grasp here is that there is no project level reference to the module project.  The dll is placed directly in the shell’s bin folder, and CAB is going to read this file to know to load it.

When CAB goes to load a module, it will scan the assembly for a class that inherits from ModuleInit.  So, I created an ImageDisplayModuleInit class :

public class ImageDisplayModuleInit : ModuleInit

{

    private WorkItem _rootWorkItem;

    [ServiceDependency]

    public WorkItem RootWorkItem

    {

        get { return _rootWorkItem; }

        set { _rootWorkItem = value; }

    }

 

    public override void Load()

    {

        base.Load();

 

        //create workitem

        DisplayImageWorkItem wi = RootWorkItem.WorkItems.AddNew<DisplayImageWorkItem>();

 

        //run the workitem

        wi.Run();

    }

Here we take a reference to the Root WorkItem, which is provided to us through the [ServiceDependency] attribute from ObjectBuilder.  The Load() method is called which instantiates our work item and runs it.

The work item then needs to load up any smartparts and place them in the required workspaces.

public class DisplayImageWorkItem : WorkItem

{

    private IDisplayImageView _view;

    private DisplayImagePresenter _presenter;

 

    protected override void OnRunStarted()

    {

        base.OnRunStarted();

 

        ShowView();

    }

 

    private void ShowView()

    {

        //create the view

        _view = this.SmartParts.AddNew<DisplayImageView>();

        _presenter = new DisplayImagePresenter(_view);

 

        this.Workspaces["mainWorkspace"].Show(_view);

    }

}

This part is pretty self explanatory.  We take the workspace name from the shell and tell it to show our smart part.

Everything is wired up now, so if you run the application you should see that the module has been loaded into the shell.

the app running with a module loaded

If you have troubles getting everything to work, make sure that your Module project is setup to output the dll inside the Shell’s bin folder, and also verify that the profile catalog xml file is placed there as well.

Next time we’ll figure out how to load the image using a different workitem.

Writing a Simple Image Editor in CAB - Part 1

I promised posts with more “meat,” so here it goes.  I’m going to demonstrate how to start writing a CAB application by building a simple image manipulation program.

Overview:
The application will load an image from some flexible source (file system, flickr, whatever).  The image will be displayed on the canvas.  The user will have various filters to apply to the image.  These filters should be easily added later on, and they should all manipulate the image in some way.  When everything is done, the user will have the option of saving the changes to disk.

So now that we have our high-level description of the app, let’s build it!

Start up Visual Studio 2005, create a blank solution called CAB-ImageEditor.  Add a new C# Windows application called ImageEditorShell.  This will be the basic structure of the application, and we will add things to it.

Add the following references to your project:

  • Microsoft.Practices.CompositeUI
  • Microsoft.Practices.ObjectBuilder
  • Microsoft.Practices.CompositeUI.WinForms

First you need to delete the files that Visual Studio gives you out of the box.  We will start from scratch.

Create a new Form called ImageEditorShell.cs.  Give it a window title if you like.

Our blank shell

Create a new class called ImageEditorApplication.cs.  This will be the entry point for our application.  We need to inherit the class from FormShellApplication<T,K> and give it the type of the root work item and our shell.

using System;

using Microsoft.Practices.CompositeUI;

using Microsoft.Practices.CompositeUI.WinForms;

 

namespace ImageEditorShell

{

    public class ImageEditorApplication : FormShellApplication<WorkItem, ImageEditorShell>

    {

    }

}

We need an entry point to start the application, so add that…

public class ImageEditorApplication : FormShellApplication<WorkItem, ImageEditorShell>

{

    [STAThread]

    public static void Main()

    {

        new ImageEditorApplication().Run();

    }       

}

That’s all we need to get our application running.  Go ahead and push F5 and see what happens.

Man, are you impressed yet?  Hehe, chill out.  There’s more to come…

CAB - Hands on Labs

The very best resource I have found so far about working with CAB are the Hands On Labs.

For some reason they were very difficult to find for me.

If you’re looking to understand CAB from a very beginner’s perspective, then I highly recommend you go through them.  There are 9 in total.