Adorners

Adorners in Silverlight

Adorners in Silverlight

Download source

Licensed under:
NNCL and SNCL

 

For the project I am currently doing for my employ I needed adorners, they do exist in WPF but not in Silverlight. Because of this I did some googling and found this site very quickly: Adorners in Silverlight – Move, Size, Rotate, Custom Mouse Cursor. This was exactly what I needed but I also needed changing the order of the layers and be able to delete the layers. I could do this in a sidebar with a list of layers in it or something like that,, But why not use the adorner? I create a new event arg called OrderAdornedObjectArgs. Those args contain the kind of change (forward, backward or delete) and the IAdornedObject that is changed. In the adorner class I added an mouseEvent to the circles at the top of the adorner and ofcourse changed the tooltips of those. I created the following event in de adorner class:

public delegate void OrderChangeHandler(object sender, OrderAdornedObjectArgs e);
public event OrderChangeHandler OrderChanged;
 
private void OnOrderChange(OrderAdornedObjectArgs e)
{
      if (OrderChanged != null)
          OrderChanged(this, e);
}

The mouseEvents of the circles go to the following functions:

private void MoveBackClick(object sender, MouseButtonEventArgs e)
{
    OrderAdornedObjectArgs args = new OrderAdornedObjectArgs(OrderAdornedObjectArgs.OrderChange.Back, _adornedObject);
    OnOrderChange(args);
}
 
private void MoveFrontClick(object sender, MouseButtonEventArgs e)
{
    OrderAdornedObjectArgs args = new OrderAdornedObjectArgs(OrderAdornedObjectArgs.OrderChange.Front, _adornedObject);
    OnOrderChange(args);
}
 
private void DeleteClick(object sender, MouseButtonEventArgs e)
{
    OrderAdornedObjectArgs args = new OrderAdornedObjectArgs(OrderAdornedObjectArgs.OrderChange.Delete, _adornedObject);
    OnOrderChange(args);
}

Then in my main where the canvas resides it listens to those events:

public MainPage()
{
    // code ommitted
    _adorner.OrderChanged += new Adorner.OrderChangeHandler(_adorner_OrderChanged);
    _adorner.Visibility = Visibility.Collapsed;
    // code ommitted
}
 
private void _adorner_OrderChanged(object sender, OrderAdornedObjectArgs e)
{
    int currentIndex = panelDisplay.Children.IndexOf(e.ObjectToOrder.Object);
 
    if (e.Change.Equals(OrderAdornedObjectArgs.OrderChange.Back))
    {
        if (currentIndex <= 0)         
        {             
            return;         
        }         
        panelDisplay.Children.RemoveAt(currentIndex);         
        panelDisplay.Children.Insert(currentIndex - 1, e.ObjectToOrder.Object);     
    }     
    else if (e.Change.Equals(OrderAdornedObjectArgs.OrderChange.Front))     
    {         
    if (currentIndex == -1 || currentIndex >= panelDisplay.Children.Count - 2)
        {
            return;
        }
 
        panelDisplay.Children.RemoveAt(currentIndex);
        panelDisplay.Children.Insert(currentIndex + 1, e.ObjectToOrder.Object);
    }
    else if (e.Change.Equals(OrderAdornedObjectArgs.OrderChange.Delete))
    {
        panelDisplay.Children.RemoveAt(currentIndex);
        _adorner.Visibility = Visibility.Collapsed;
    }
}

As you can read above, the function _adorner_OrderChanged is called when the event is raised. Then it handles the changing of layers accordingly. If you want to push a layer back there is a small fail safe so you can’t push the layer further back then the last position. The same with pushing forward, you can’t put it infront of the adorner (which is position Children.Count – 1 ). If we have for example 3 images and the adorner in the canvas the count is 4. But if you check the index of the adorner it is index 3 because the index starts at position 0:

Children[0] = image;
Children[1] = image;
Children[2] = image; //topmost element
Children[3] = adorner;
 
Children.Count = 4;

So the index of the topmost element is Children.Count – 2.

Deleting the layer is the most simple by just saying RemoveAt(currentIndex).

I also implemented better support for a TextBlock. I added a eventListener to the Loaded event of the MainPage:

public MainPage()
{
    // code ommitted
    Loaded += new RoutedEventHandler(MainPage_Loaded);
}
 
private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
    foreach (FrameworkElement element in panelDisplay.Children)
    {
        //make an exact fit around the textblock.
        if (element.GetType().Equals(typeof(TextBlock)))
        {
            TextBlock tempItem = (TextBlock)element;
            tempItem.Width = tempItem.ActualWidth;
            tempItem.Height = tempItem.ActualHeight;
        }
 
        //so you cant resize the element so small you cant see it anymore.
        element.MinWidth = 10;
        element.MinHeight = 10;
    }
}

If you added an Textblock in the original adorner It would not wrap nicely around the text. Instead it had the original adorner size. By setting the width and height of the textblock explicitly to the actualWidth and height it fits the adorner around the text nice. I also added MinWidth and MinHeight support to the IAdornedObject so that now you cant resize the item smaller then restrictions of the element. When I had set MinWidth and MinHeight to a TextBlock in the original code and put the adorner on it I could resize the textblock to even smaller then what the MinWidth and MinHeight was. To prevent this I added MinWidth and MinHeight support to the Adorner and to the IAdornedObject. Just for completion I also added MaxWidth and MaxHeight support.

This sums up all of my changes, if you have any question feel free to ask them. And again thanks to Nokola for the original code.

Leave a Reply

You must be logged in to post a comment.