WynApse Home Page
Home    Blog    About    Contact       
Latest Article:


My Tags:
My Sponsors:






Lifetime Member:

Silverlight 1.0 OutlookBar Control







Later Changes

Two weeks to the day after I started the Ajax-enabled version of this code in my MasterPage, Michael Washington wrote to me that the code was broken in this article. All it took was him saying that and I knew what was wrong, and can't believe nobody else reported it!

The JavaScript from the MasterPage was interfering with the JavaScript for the article ... amazing that the MasterPage one works without problems...

So, I've not made any changes to the JavaScript or XAML displayed in the boxes below, but what I have done is modified all the XAML and JavaScript that's running this article to make it function alongside the other. The only changes I made were naming changes to avoid interference.

The code is different between this article and what you see working in the right-hand sidebar for one basic reason, and that is extensibility. This article is written with a limited set of buttons, known at design time and unchanging, so I can set that number as a tag in the main canvas. The MasterPage code is populated from the database and has no design-time dependencies.

Because the code in the MasterPage is tighter, I will make that code available in the Silverlight 1.0 Meets SQL Via ASP.NET Ajax and Web Services article hopefully later today.

Interesting adventure, and thanks Michael! -- visit Michael's DNN Silverlight Site here.


Day One Changes

Matt Casto posted to my blog about a problem with the original code for this application. He was able to give me positive directions on how to cause a problem when the mouse was pressed and moved away from the button artwork. As it turns out, I had seen this happen once, but didn't know what I had done, and couldn't repeat it. Apparently a QA team of 1 is less than desirable :)

After some messing around, the solution ended up being very simple, and was caused by a feature addition when I went from rectangles to artwork. I knew I wanted to to at least do Rollover images, and since I had no prior need for MouseLeave and MouseEnter (that I was aware of), I put those two messages on the Image not the container canvas.

When I then tried to use the MouseEnter and MouseLeave to release the mouse capture, the MouseMove on the container was interfering.

The Solution - Three Parts

The solution to this problem was in three very simple parts:
  1. Move the MouseEnter and MouseLeave messages from the Image object to the container canvas
  2. Modify the MouseEnter and MouseLeave JavaScript to drill down to the Image to set the Source
  3. Make a determination that if the cursor leaves the mouse while the mouse is pressed, it is considered off-canvas and release mouse capture. This was a very easy decision to make because moving the mouse up and down is already handled. Left and right motion can be considered off-button.
The JavaScript and XAML shown below have been updated to reflect these changes.

Thanks Matt!


Desert Code Camp III Presentation

On September 15, 2007 I presented the application above during an introductory talk on Silverlight 1.0. I posted the code that evening on my blog, and that same zip file is available with the "Download" link just below.

The purpose of this article is to showcase the control on my site and to expand a bit on the code for those that were not in attendance.

Download the source code

Preliminary Discussion

At Code Camp, I began the talk by finding out that between 1/4 and 1/2 of the people attending had either never tried Silverlight, or had poor results when they tried. The first part of my presentation was directly geared to them. In the download file, you will find 5 sub-folders named "one", "two", "three", "four", and "five". I discussed those in numerical sequence.

Folder "one" contains only an html file, nothing more. It is a place holder for what follows.

Folder "two" adds Silverlight to the placeholder html file by displaying a TextBlock on the page.

Folder "three" is where the real fun begins as I displayed a series of 'buttons' built from a canvas, a Rectangle, and a Textblock. The buttons have the ability of being moved up and down displacing the one(s) above or below.

Folder "four" extends the button paradigm into the 'outlook bar' paradigm with a drop-down menu.

Folder "five" is what I'm displaying above, and discussing below.


Design Requirements

Once I had decided I was going to do an Outlook-style Navigation bar, I 'test drove' a couple similar bars, such as the toolbar in Visual Studio. My idea was not to try to exactly copy anything, but gather ideas and build my own. Since I began with simply buttons, some of my 'design' was a holdover from then, and my goals were modified as I went forward leaving me with the following:

  • Clicking a button will open a menu, and close any (other) existing menu
  • Clicking the button on an open menu will close the menu
  • Buttons with or without attached menus can be moved up or down displacing those above or below
  • As Buttons are moved up and down they should move over the top of the staionary ones
  • The whole 'control' should be easily extensible, so no names should be coded into the JavaScript
  • Rollover images will be used, but no 'down' images to avoid nasty interaction with the button slide code

How the movement is handled

Sliding things around on a Silverlight Canvas is not a big deal. People call it drag and drop, but there's no 'drop' to it, since it's not really a classical 'drop-target' type of concept. Using Canvas.ZIndex as items are 'grabbed' can give a very nice slide-over effect, and that's what I did with this code. Everything is Canvas.ZIndex 0 unless it's being moved, at which point it becomes 1.

One of the things I was concerned about was how to deal with the up/down and keep track of where a button was 'agnostically' (is that a word?). I didn't want to have to deal with names in the JavaScript because that would make extending the NavBar difficult. After drawing 5 buttons on paper, and indexing them 0 through 4, I realized that I could use the Tag property of the object to keep track of the index for me. If I always kept the buttons in order 0 through 4 top-to-bottom, then it would make the code easier to manipulate.

If you look at the XAML below, at start time, the upper-most button is "SelectionOne", and it is Tag "0". The buttons and tags progress downward to "SelectionFive" and Tag "4".

Looking at the button movement from a very granular standpoint, it simply becomes:

  • If I'm moving up, then (Tag - 1) is the button above me, and only allow it if I'm Tag > 0
  • If I'm moving down, then (Tag + 1) is the button below me, and only allow it if I'm Tag < Last Tag
Rather than discuss this in a general case, let's consider SelectionFive is moved up to exchange places with SelectionFour. The sequence of events happens when the top edge of SelectionFive crosses above the top edge of SelectionFour:

  • SelectionFive is set to the position of SelectionFour
  • SelectionFour is pushed down below SelectionFive (and SelectionFive's menu if it is showing)
  • SelectionFive's Tag is decremented
  • SelectionFour's Tag is incremented
Downward movement is identical with the exception of dealing with the lower edge of the buttons crossing each other. It becomes more complex when moving a button across a menu or moving a button with a menu showing, but essentially the sequence remains the same, and once the location and Tag switch is completed, the button can continue to be moved and appears to be seamless in action.

Rollover Effects

The Rollover Effects on the button images are done using Tag values. The Image object has a Tag value set to the base name for the button artwork. For example, "SelectionOne" has an image Source of "One.png". The rollover image for this is "OneRollover.png". The other buttons are named similarly. The Tag for "SelectionOne" is "One". When the button is rolled over, the Source for the Image is changed to "OneRollover.png" using the Tag name to supply the text "One", and likewise the normal button image Source is set to "One.png" using the Tag value.

Silverlight Effects used

There are a few interesting Silverlight effects used in this application that might be interesting to note.

Because of my desire to make the JavaScript be name-agnostic, I wanted needed to name things consistently. The Menu associated with "SelectionOne" is "MenuSelectionOne. The Image Associated with "SelectionOne" is "SelectionOneImage".

Having this naming convention allows constructs such as the following to be done.

In the MouseDown code, as mentioned above, the Button and menu both get Canvas.ZIndex set to one. This code is:

sender["Canvas.ZIndex"] = 1;sender.findName("Menu" + sender.Name)["Canvas.ZIndex"] = 1;


sender.Name is, for instance, "SelectionOne", so "Menu" + sender.Name becomes "MenuSelectionOne".

Similarly, the following is a function that finds the Tag value for a button given the Menu name:

function GetTagForMenua(sender, sMenu){return parseInt(sender.findName(sMenu.substring(4)).Tag);}


There are many such lines of code in the JavaScript for this application, so while I think it is counter productive to detail them here, it may be instructive to investigate the code closely.


JavaScript for producing this page:

<script type="text/javascript">
var beginYa;
var fIsMouseDowna = false;
var fMouseMoveda = false;
var sMenuShowinga = "";
var mainCanvasa = null;
var nTagCounta; // number of buttons in control

function CanvasLoadeda(sender, args)
{
mainCanvasa = sender;
nTagCounta = sender.Tag;
}

// Common MouseDown function used for
// all the buttons
function onMouseDowna(sender, mouseEventArgs)
{
sender.captureMouse();

// just get it over with and set the button and menu
// to ZIndex 1. If the menu's not up, it won't be seen anyway
// need to do it here in case it gets moved, if you wait until
// it gets moved it will be too late and will pop around
// awkwardly
sender["Canvas.ZIndex"] = 1;
sender.findName("Menu" + sender.Name)["Canvas.ZIndex"] = 1;

beginYa = mouseEventArgs.getPosition(null).y;

fIsMouseDowna = true;
fMouseMoveda = false;
}


// Common MouseUp function used for
// all the buttons
function onMouseUpa(sender, mouseEventArgs)
{
// Only deal with it if the mouse is supposed to be down
if (fIsMouseDowna)
{
if (!fMouseMoveda)
{
// Mouse hasn't moved, so this is a click ...
// pop or drop a menu

// This is the menu associated with the button clicked
var Menu = sender.findName("Menu" + sender.Name);

// If we've clicked the showing menu, drop it
if (Menu.Name == sMenuShowinga)
{
Menu.Visibility = "Collapsed";
RollButtonsUpa(sender, parseInt(sender.Tag) + 1);
sMenuShowinga = "";

}
else
{
// If we've clicked a button for a menu other than the one showing
// drop the one showing, and show the one clicked
if (sMenuShowinga != "")
{
sender.findName(sMenuShowinga).Visibility = "Collapsed";
RollButtonsUpa(sender, GetTagForMenua(sender, sMenuShowinga) + 1);
}
sMenuShowinga = Menu.Name;
sender.findName(sMenuShowinga)["Canvas.ZIndex"] = 0;
Menu["Canvas.Top"] = sender["Canvas.Top"] + sender.Height;

// Move anything below it down
PushButtonsdowna(sender, parseInt(sender.Tag)+1, parseInt(Menu["Canvas.Top"])+parseInt(Menu.Height));
Menu.Visibility="Visible";
}
// and reset this button/menu to ZIndex 0
sender["Canvas.ZIndex"] = 0;
sender.findName("Menu" + sender.Name)["Canvas.ZIndex"] = 0;
}
else
{
// You're only in here if you mouse up after a move without rearranging
// so put the button back where it came from (or so help me)
PutButtonBacka(sender);
}

LiftMouseHelpera(sender);
}
}

function PutButtonBacka(sender)
{
if (sMenuShowinga == ("Menu" + GetCanvasForTaga(sender, (parseInt(sender.Tag) - 1))))
{
sender["Canvas.Top"] = (parseInt(sender.Tag))*27 + sender.findName(sMenuShowinga).Height;
}
else
{
if (sender.Tag == 0)
{
sender["Canvas.Top"] = 0;
}
else
{
sender["Canvas.Top"] = sender.findName(GetCanvasForTaga(sender, (parseInt(sender.Tag) - 1)))["Canvas.Top"] + 27;
}
}
// wherever the button ends up, align the menu with it
sender.findName("Menu" + sender.Name)["Canvas.Top"] = sender["Canvas.Top"] + 27;
}


// PushButtonsdowna
// Used to ripple the buttons below the selected one
// down to make room
function PushButtonsdowna(sender, nIndex, nLocation)
{
for (nPBDa = nIndex; nPBDa < nTagCounta; nPBDa++)
{
var sCanvasa = GetCanvasForTaga(sender, nPBDa.toString()).toString();
sender.findName(sCanvasa)["Canvas.Top"] = nLocation;
nLocation += 27;
}
}

// RollButtonsUpa
// Used to ripple the buttons below the selected one
// up to fill in
function RollButtonsUpa(sender, nIndex)
{
for (nRBUa = nIndex; nRBUa < nTagCounta; nRBUa++)
{
var sCanvasa = GetCanvasForTaga(sender, nRBUa.toString()).toString();
sender.findName(sCanvasa)["Canvas.Top"] = nRBUa*27;
}
}

// LiftMouseHelpera
// Helper function does some of the functinoality
// of Mouse Up, but also used one other place
function LiftMouseHelpera(sender)
{
fIsMouseDowna = false;
sender["Canvas.ZIndex"] = 0;
sender.findName("Menu" + sender.name)["Canvas.ZIndex"] = 0;

sender.releaseMouseCapture();
beginYa = 0;
}

// MouseMove
// Main function when any captured mouse is moving
// Deal with up/down direction and all manipulation
function onMouseMovea(sender, mouseEventArgs)
{
fMovingUpa = false;

// If the mouse isn't 'down', just go on
if (fIsMouseDowna)
{
// Set mouse moved because this is used elsewhere
fMouseMoveda = true;

// Get Current Y value from args parameter
var currYa = mouseEventArgs.getPosition(null).y;

// check to see if we're off the canvas
// if so, put the button back to where it belongs
if ((currYa < 1) || (currYa > 398))
{
PutButtonBacka(sender);
LiftMouseHelpera(sender);
return;
}

// Decide if we're movig up or down
if ((currYa - beginYa) < 0)
{
fMovingUpa = true;
}

// Decide if we relocate by what tag and where it is
if (((sender.Tag != "0") || (fMovingUpa == false))
|| ((sender.Tag == "0") && (sender["Canvas.Top"] > 0)))
{
sender["Canvas.Top"] += (currYa - beginYa);
if (sMenuShowinga == "Menu" + sender.Name)
{
sender.findName("Menu" + sender.Name)["Canvas.Top"] += (currYa - beginYa);
}
}

// You've moved the canvas, reset beginYa
// to avoid jumps as you move
beginYa = currYa;

// Moving the mouse up, but don't let them move the top-most one
if ((fMovingUpa) && (sender.Tag != "0"))
{
// Find what Button is above the one being moved
var sCanvasAbovea = GetCanvasForTaga(sender, parseInt(sender.Tag) - 1);

// If the upper edge of the button being moved passes above
// the upper edge of the button above, then swap them, tag and all
if (sender["Canvas.Top"] < sender.findName(GetCanvasForTaga(sender, parseInt(sender.Tag) - 1))["Canvas.Top"])
{
// This sets the one being moved
sender["Canvas.Top"] = sender.findName(sCanvasAbovea)["Canvas.Top"];
sender.Tag = (parseInt(sender.Tag) - 1).toString();

var nMenuIncrementUpa = 0;
if (sMenuShowinga == "Menu" + sender.Name)
{
nMenuIncrementUpa = sender.findName(sMenuShowinga).height;
}

// This sets the one (Above) that was moved over
sender.findName(sCanvasAbovea)["Canvas.Top"] = sender["Canvas.Top"] + 27 + nMenuIncrementUpa;
sender.findName(sCanvasAbovea).Tag = (parseInt(sender.findName(sCanvasAbovea).Tag) + 1).toString();
sender.findName("Menu" + sCanvasAbovea)["Canvas.Top"] = sender.findName(sCanvasAbovea)["Canvas.Top"] + 27;
}
}

// Moving the mouse down, but don't move the bottom one
if ((!fMovingUpa) && (sender.Tag != parseInt(nTagCounta - 1).toString()))
{
// Find what Button is below the one being moved
var sCanvasBelowa = GetCanvasForTaga(sender, parseInt(sender.Tag) + 1);

var nMenuIncrementDowna = 0;
if (sMenuShowinga == "Menu" + sender.Name)
{
nMenuIncrementDowna = sender.findName(sMenuShowinga).height;
}

var nStaticMenuIncrementDowna = 0;
if (sMenuShowinga == ("Menu" + sCanvasBelowa))
{
nStaticMenuIncrementDowna = sender.findName("Menu" + sCanvasBelowa).height;
}

// If the upper edge of the button being moved passes below
// the upper ede of the button below, then swap them, tag and all
if ((sender["Canvas.Top"] + nMenuIncrementDowna) > (sender.findName(sCanvasBelowa)["Canvas.Top"] + nStaticMenuIncrementDowna))
{
// This sets the one being moved
sender["Canvas.Top"] = sender.findName(sCanvasBelowa)["Canvas.Top"] - nMenuIncrementDowna + nStaticMenuIncrementDowna;
sender.Tag = (parseInt(sender.Tag) + 1).toString();

// set global also

// This sets the one (Below) that was moved over
sender.findName(sCanvasBelowa)["Canvas.Top"] = sender["Canvas.Top"] - 27 - nStaticMenuIncrementDowna;
sender.findName("Menu" + sCanvasBelowa)["Canvas.Top"] = sender.findName(sCanvasBelowa)["Canvas.Top"] + 27;
sender.findName(sCanvasBelowa).Tag = (parseInt(sender.findName(sCanvasBelowa).Tag) - 1).toString();
}
}
}
}

// GetCanvasForTaga
// Only works if Canvas Names begin "Selection"
function GetCanvasForTaga(sender, sTag)
{
for (i = 0; i < mainCanvasa.children.count; i++)
{
var child = mainCanvasa.children.getItem(i);

if ((child.toString() == "Canvas") && (child.name.substring(0, 9) == "Selection"))
{
if (child.Tag.toString() == sTag)
{
return child.Name;
}
}
}
return "-1";
}

// GetTagForMenua
// Given a menu name, find the Tag
function GetTagForMenua(sender, sMenu)
{
return parseInt(sender.findName(sMenu.substring(4)).Tag);
}

function onMouseEnter(sender, args)
{
sender.findName("OutputThis").Text="";

// Change to deal with moving MouseEnter MouseLeave into the canvas
sender.findName(sender.Name + "Image").Source = "images/" + sender.findName(sender.Name + "Image").Tag + "Rollover.png";
}

function onMouseLeave(sender, args)
{
// Change to deal with moving MouseEnter MouseLeave into the canvas
sender.findName(sender.Name + "Image").Source = "images/" + sender.findName(sender.Name + "Image").Tag + ".png";

// Addition to effectively assert mouse up when cursor exits button
if (fIsMouseDowna)
{
PutButtonBacka(sender);
LiftMouseHelpera(sender);
}
}

function onMouseEnterMenuItem(sender, args)
{
sender.Foreground="Yellow";
}

function onMouseLeaveMenuItem(sender, args)
{
sender.Foreground="Black";
}

function onMouseUpaOnMenuItem(sender, args)
{
sender.findName("OutputThis").Text=sender.Text;
}

</script>


XAML for producing this page:

<Canvas 
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Loaded="CanvasLoadeda"
Tag="5">

<Rectangle Width="103"
Height="400"
StrokeThickness="1"
Stroke="#3e4f73"
Fill="#bbc1cd"/>

<Canvas x:Name="SelectionOne"
Canvas.Top="0"
Canvas.Left="0"
Height="27"
Canvas.ZIndex="0"
Tag="0"
Cursor="Hand"
MouseLeftButtonDown="onMouseDowna"
MouseLeftButtonUp="onMouseUpa"
MouseMove="onMouseMovea"
MouseEnter="onMouseEnter"
MouseLeave="onMouseLeave">
<Image x:Name="SelectionOneImage"
Source="images/One.png"
Tag="One"/>
</Canvas>
<Canvas x:Name="MenuSelectionOne"
Canvas.Top="0"
Canvas.Left="1"
Height="150"
Visibility="Collapsed">
<Rectangle Width="101"
Height="150"
Fill="#bbc1cd"/>
<TextBlock FontSize="12"
Canvas.Left="4"
Canvas.Top="100"
Text="Menu One"
Foreground="Black"
MouseEnter="onMouseEnterMenuItem"
MouseLeave="onMouseLeaveMenuItem"
MouseLeftButtonUp="onMouseUpaOnMenuItem"
/>
</Canvas>

<Canvas x:Name="SelectionTwo"
Canvas.Top="27"
Canvas.Left="0"
Height="27"
Canvas.ZIndex="0"
Tag="1"
Cursor="Hand"
MouseLeftButtonDown="onMouseDowna"
MouseLeftButtonUp="onMouseUpa"
MouseMove="onMouseMovea"
MouseEnter="onMouseEnter"
MouseLeave="onMouseLeave">
<Image x:Name="SelectionTwoImage"
Source="images/Two.png"
Tag="Two"/>
</Canvas>
<Canvas x:Name="MenuSelectionTwo"
Canvas.Top="0"
Canvas.Left="1"
Height="150"
Visibility="Collapsed">
<Rectangle Width="101"
Height="150"
Fill="#bbc1cd"/>
<TextBlock FontSize="12"
Canvas.Left="4"
Canvas.Top="100"
Text="Menu Two"
Foreground="Black"
MouseEnter="onMouseEnterMenuItem"
MouseLeave="onMouseLeaveMenuItem"
MouseLeftButtonUp="onMouseUpaOnMenuItem"
/>
</Canvas>

<Canvas x:Name="SelectionThree"
Canvas.Top="54"
Canvas.Left="0"
Height="27"
Canvas.ZIndex="0"
Tag="2"
Cursor="Hand"
MouseLeftButtonDown="onMouseDowna"
MouseLeftButtonUp="onMouseUpa"
MouseMove="onMouseMovea"
MouseEnter="onMouseEnter"
MouseLeave="onMouseLeave">
<Image x:Name="SelectionThreeImage"
Source="images/Three.png"
Tag="Three" />
</Canvas>
<Canvas x:Name="MenuSelectionThree"
Canvas.Top="0"
Canvas.Left="1"
Height="150"
Visibility="Collapsed">
<Rectangle Width="101"
Height="150"
Fill="#bbc1cd"/>
<TextBlock FontSize="12"
Canvas.Left="4"
Canvas.Top="100"
Text="Menu Three"
Foreground="Black"
MouseEnter="onMouseEnterMenuItem"
MouseLeave="onMouseLeaveMenuItem"
MouseLeftButtonUp="onMouseUpaOnMenuItem"
/>
</Canvas>

<Canvas x:Name="SelectionFour"
Canvas.Top="81"
Canvas.Left="0"
Height="27"
Canvas.ZIndex="0"
Tag="3"
Cursor="Hand"
MouseLeftButtonDown="onMouseDowna"
MouseLeftButtonUp="onMouseUpa"
MouseMove="onMouseMovea"
MouseEnter="onMouseEnter"
MouseLeave="onMouseLeave">
<Image x:Name="SelectionFourImage"
Source="images/Four.png"
Tag="Four"/>
</Canvas>
<Canvas x:Name="MenuSelectionFour"
Canvas.Top="0"
Canvas.Left="1"
Height="150"
Visibility="Collapsed">
<Rectangle Width="101"
Height="150"
Fill="#bbc1cd"/>
<TextBlock FontSize="12"
Canvas.Left="4"
Canvas.Top="100"
Text="Menu Four"
Foreground="Black"
MouseEnter="onMouseEnterMenuItem"
MouseLeave="onMouseLeaveMenuItem"
MouseLeftButtonUp="onMouseUpaOnMenuItem"
/>
</Canvas>

<Canvas x:Name="SelectionFive"
Canvas.Top="108"
Canvas.Left="0"
Height="27"
Canvas.ZIndex="0"
Tag="4"
Cursor="Hand"
MouseLeftButtonDown="onMouseDowna"
MouseLeftButtonUp="onMouseUpa"
MouseMove="onMouseMovea"
MouseEnter="onMouseEnter"
MouseLeave="onMouseLeave">
<Image x:Name="SelectionFiveImage"
Source="images/Five.png"
Tag="Five" />
</Canvas>
<Canvas x:Name="MenuSelectionFive"
Canvas.Top="0"
Canvas.Left="1"
Height="150"
Visibility="Collapsed">
<Rectangle Width="101"
Height="150"
Fill="#bbc1cd"/>
<TextBlock FontSize="12"
Canvas.Left="4"
Canvas.Top="100"
Text="Menu Five"
Foreground="Black"
MouseEnter="onMouseEnterMenuItem"
MouseLeave="onMouseLeaveMenuItem"
MouseLeftButtonUp="onMouseUpaOnMenuItem"
/>
</Canvas>

<TextBlock x:Name="OutputThis"
FontSize="20"
Canvas.Left="110"
Canvas.Top="200"
Foreground="Blue"
Text=""/>

</Canvas>


Design Enhancements


I wanted the code in this article to match that of the Code Camp presentation, so I have not enhanced or modified anything in that code.

As I was explaining the code, one of the attendees had a suggestion that I think might help ease some of the JavaScript if the code stays as-is. The second enhancement removes the ability to do the first, so read these carefully before modifying.

Menu Canvas with button container canvas

I have code that runs to keep the Menu locked to the bottom of the button. It was pointed out that if I move the Menu Canvas inside the button container canvas that this whole issue essentially disappears, because the menu is always right there. Excelling point, but not without some testing. If the menu canvas is inside the button container, then mouse messages that are now being handled on the button will also be handled by the menu, unless they are blocked. Since I didn't code it this way, I haven't put a lot of thought into it, so there may be an easy way around this.

Removal of container canvas

The logical progression of code from "one" to "five" would normally also include the removal of the container canvas, allowing the Image to handle the mouse events and moving. If the container canvas is removed, then the menu canvas is necessary.

An alternative to this might be an empty container canvas that simply holds the image and menu. The problem I see with this is that to handle the moving, the container should have the location settings. If that's the case then all mouse movement would have to be rippled up from the image to the container canvas, which may make the JavaScript as ugly as it is now. Only testing will tell.

Copyright © 2006-2017, WynApse