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!

Comments are closed.
Remortgage - Renegade motorhomes - Credit Counseling - Credit Cards