RSS

Category Archives: Software Development

Announcing Masonry for node.js


I’m happy to announce that node.js version of ‘Masonry’ framework (branded as collab.js) has landed it’s GitHub repository.

Masonry is a starter kit for social-enabled web applications. To get more details about Masonry starter kit for ASP.NET MVC4 please refer to the following article.

Supported environments

Masonry.js supports both OSX and Windows development environments. In addition it contains support for Microsoft WebMatrix 2 for development and deployment/publishing, and uses only those NPM modules that do not require manual compilation steps in order to run on either OSX x64 or Windows 7 x64 systems.

Supported databases

Masonry.js features extensible provider-based model for data layer. It is extremely easy writing custom data providers or switching between them. At the moment of writing this article the following database engines are supported out-of-box:

  • Microsoft SQL Server
  • MySQL Server

Providers for Azure SQL, MongoDb and SQLite are in progress and will appear pretty soon.

Simple code maintenance

The Node.js version of Masonry provides a better code partitioning with the benefit of keeping the core implementation and modules up-to-date with less effort. Masonry views, routes and controllers are kept separately to avoid interference with your development process.

Great level of extensibility

With node.js and JavaScript you get extensibility for nearly everything out of box. With node.js you don’t need binary compilation of the project and so don’t need complex plugin layers with always limited APIs for discovery and initialization of extensions at run time.

In order to reduce the need modifying core Masonry files there is a set of lightweight contracts (via configuration files) to modify or extend key parts of the UI like brand/copyright information, sidebar/header links, etc. That means less time can be spent to keep Masonry up-to-date and more time for your own features.

Separate presentation and web api layers

The frontend is separated with a plain presentation layer and web api’s - set of RESTful services with JSON output by default. It becomes extremely easy switching between presentation layers and rendering engines, or building  completely different clients on the top of existing RESTful endpoints.

And many more…

You should expect more features and details soon. Meanwhile feel free raising issues and/or suggestions here.

 
 

Tags:

Make a really dynamic plugin using multiple Application Domains


Note: This article assumes that you have basic knowledge of creating and using plugins. It won’t cover the whole process of plugin development. There are too many articles in the internet towards this topic to make another copy of sources ;) I want to dwell on some specific sugar that could possibly help solving problems described below.

Yesterday I was playing with a windows service based application that was capable of dynamic loading and execution of .net assemblies. The loading and execution process was perfect except one ugly though common thing that can be found in many plugin based applications. After the assembly was loaded and all the required static methods executed successfully I couldn’t update it or delete. This is obvious behavior when loading assemblies in .net by means of "type loaders", usage of "Assembly.Load()" etc. and shouldn’t be regarded as bug.

The "Assembly.Load" function loads assembly to current application domain so you won’t be able to do a lot untill your plugin host application is unloaded. Event don’t think of hot changes of assemblies with your host running. To override this you should move from kid’s type loaders to multiple application domains :) I’ll show how to do that.

Additionally you will see how the multiple AppDomains approach helps solving problems with single threaded applications like XNA games. XNA Framework based game can be easily converted to assembly, but it cannot be simply executed by the plugin host. It doesn’t assume being loaded from another thread, you cannot apply all the stuff from your favorite own plugin framework. It also needs the power of multiple AppDomains plus some minor changes in your game code.

So let’s start implementation part…

First we define some dummy IPlugin interface for external assemblies. It will be placed to a PluginLib.dll assembly common either for host application and external plugins.

IPlugin.cs

using System;

namespace PluginLib
{    
    public interface IPlugin : IDisposable
    {
        string Name { get; }
        void Run(object param);
    }
}

 

Here we have a name of the plugin for future UI integration purposes and execution call with some parameter. You can use your own existing IPlugin interface only don’t forget to inherit IDisposable interface.

Additionally we implement our own custom exception for future purposes. It can also be modified to suit your needs.

PluginException.cs

using System;

namespace PluginLib
{
    [Serializable]
    public class PluginException : Exception
    {
        public PluginException() { }
        public PluginException(Exception ex) : base("Plugin Exception", ex) { }
    }
}

The most important thing I want to dwell is the plugin host. The significant part of the projects will be placed in it. For the details for using "AppDomainManager" and "AppDomainSetup" you should refer to MSDN.

Note that your hosting logic class MUST inherit MarshalByRefObject so the host application can create an object of the PluginHost inside other AppDomains, also it should implement IDisposable to cleanup any resources.

We can definitely split the process of plugin execution in the folowing steps:

1. Create and setup new Application Domain for the required assembly binding a .config file if found

2. Add domain to the internal collection of loaded domains.

3. Load the required assembly from dedicated appdomain created

4. Initialize a plugin from assembly

5. Do whatever you want, in my sample I will call "Run(object param)" method to start doing something

 

1-2. Creating and setting up new Application Domain, loading domain to internal collection

Assume that we already defined a global collection variable for storing all the application domains loaded

// The list of loaded application domains
List<AppDomain> _appDomains = new List<AppDomain>(); 

Our host will contain only one public method called RunPlugin accepting one string argument as the physical path of the plugin assembly to load

 

/// <summary>
/// This function is what the Host Application is going to call.
/// </summary>
/// <param name="path">The plugin (*.dll) file</param>
public void RunPlugin(string path)
{
    //Creating the appdomain manager
    AppDomainManager manager = new AppDomainManager();
    //Check if there is any *.dll.config file
    string configFileName = string.Format("{0}.config", path);
    AppDomainSetup setup = new AppDomainSetup();
    //Enable shadow copying, so the Plugin files are not locked
    setup.ShadowCopyFiles = "true";
    setup.LoaderOptimization = LoaderOptimization.MultiDomain;
    //if the config file exists, load it into the appdomain
    if (File.Exists(configFileName))
        setup.ConfigurationFile = configFileName;
    //Creating the AppDomain & adding a reference to it in the _appDomains collection.
    AppDomain domain = manager.CreateDomain(String.Format("AD-{0}", _appDomains.Count), null, setup);
    _appDomains.Add(domain);
    /*
     * This important.
     * Here we are initiating an instance of PluginHost inside the new AppDomain.
     * this instance will give us control from the host appdomain to load & run the plugins inside the new appdomain
     */
    PluginHost remoteHost = domain.CreateInstanceAndUnwrap(Assembly.GetAssembly(typeof(PluginHost)).FullName, typeof(PluginHost).ToString()) as PluginHost;

    // Here we run every plugin in a separate thread
    ThreadPool.QueueUserWorkItem(
        delegate(object state)
        {
            try
            {
                //calling the PluginHost object created in the other appdomain.
                remoteHost.Launch(path, domain);
            }
            catch (Exception ex)
            {
                _appDomains.Remove(domain);
                AppDomain.Unload(domain);
            }
        });
} 

The code above is well commented, so I’d like to dwell on "Launch" method of our PluginHost class

 

3-5. Plugin execution from dedicated AppDomain

/// <summary>
/// this function will load the specified assembly file in the specified AppDomain, then it is going to run any class that implements IPlugin inside.
/// </summary>
/// <param name="assemblyPath">The assembly (*.dll) file which contains the plugin(s)</param>
/// <param name="domain">The appdomain in which the assembly is going to be loaded</param>
void Launch(string assemblyPath, AppDomain domain)
{
    //Loading the assembly file into the appdomain
    Assembly pluginAssembly = domain.Load(AssemblyName.GetAssemblyName(assemblyPath));
    domain.AssemblyResolve += delegate(object sender, ResolveEventArgs args)
    {
        AppDomain d = sender as AppDomain;
        string path = Path.Combine(@"C:PluginsReferences", args.Name.Split(',')[0] + ".dll");
        return d.Load(path);
    };

    RaiseEvent(String.Format("Hello from domain {0}", domain.FriendlyName));
    //Searching for Plugin types inside the loaded assembly
    foreach (Type type in pluginAssembly.GetTypes())
    {
        if (!type.IsClass) continue;

        if (type.FindInterfaces(delegate(Type t, object filter) { return t == filter as Type; }, typeof(IPlugin)).Length > 0)
        {
            //Using block will make sure that the plugins will run cleanup code after execution
            using (IPlugin plugin = pluginAssembly.CreateInstance(type.ToString()) as IPlugin)
            {
                plugin.Run("Here we place some params for the external plugin");
            }
        }
    }
}

This part should be definitly configured to suit you needs. For quick implementation purposes I’ve hardcoded some parts.

Upon loading our plugin assembly we might encounter references to third party libraries not known to our host application. This must be resolved somehow. We attach to AssemblyResolve event of our dedicated AppDomain to reference a folder where all the needed .dll files would be present. Assume that this is a separate folder for all the third party stuff. Here I’ve defined "C:PluginsReferences" for that purposes.

You can also implement some event raising functionality to inform your application about something. Here you can see the call of "RaiseEvent" method. It is as simple as possible:

event PluginEventHandler _pluginEvent;
public event PluginEventHandler PluginChange
{
    add { _pluginEvent += value; }
    remove { _pluginEvent -= value; }
}

private void RaiseEvent(string message)
{
    if (_pluginEvent != null)
        _pluginEvent(message);
}

And at last we use the old fashioned type loader as it is always advised in the internet ;) creating instances and executing methods.

 

Disposing Plugin Host

We should also take care of releasing our AppDomain collection. The disposing is too boring to comment

#region IDisposable Members

public void Dispose()
{
    _appDomains.ForEach(
        delegate(AppDomain domain)
        {
            if (!domain.IsFinalizingForUnload())
                AppDomain.Unload(domain);
        });
}

#endregion

 

How it is used in your application

1. Preparing plugin. Turning application to plugin for testing purposes.

1. Prepare any simple Windows Forms Application, put some controls on it and so on…

2. Add reference to our PluginLib assembly

3. Open project properties and change the output type to "Class Library"

 

In the default Program.cs file you might see the following common picture

static class Program
{
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Form1());
    }
}

Let’s change it implementing some king of plugin loader

//static class Program
//{
//    /// <summary>
//    /// The main entry point for the application.
//    /// </summary>
//    [STAThread]
//    static void Main()
//    {
//        Application.EnableVisualStyles();
//        Application.SetCompatibleTextRenderingDefault(false);
//        Application.Run(new Form1());
//    }
//}

public class SimpleForm : IPlugin
{
    #region IPlugin Members

    public string Name
    {
        get { return "SimpleForm"; }
    }

    public void Run(object param)
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Form1());
    }

    #endregion

    #region IDisposable Members

    public void Dispose()
    {
    }

    #endregion
}

It’s very simple, isn’t it? :)

2. Implementing simple Plugin Host Application

Create folder "C:Plugins" and put there your WindowsApplication1.dll (see code above) and PluginLib.dll

I’ll take the basic console application to dwell only on PluginHost implementation if you don’t mind ;)

Don’t forget to add reference to our common PluginLib assembly

public static void Main(string[] args)
{
    //Using block will make sure that the cleanup code of PluginHost is executed
    using (PluginHost host = new PluginHost())
    {
        host.PluginChange += new PluginEventHandler(host_PluginChange);
        host.RunPlugin(@"C:PluginsWindowsApplication1.dll");
        Console.ReadKey();
    }
}

static void host_PluginChange(string message)
{
    Console.WriteLine(message);
}
It's up you what the logic of your hosting application will be. 
Here you create a new host, link to the simple host event and finally execute the plugin. 
In this case you will see the application designed.
Note: When you plugin is executed and you see windows form you can freely delete the plugin, change it, update and so on.
Source code for the article

 
5 Comments

Posted by on October 17, 2007 in Software Development

 

WPF. Simple adorner usage with drag and resize operations


After posting my previous post I realized that it would be much better to provide the working sample instead of pure idea of class changed to support canvas. So I created a dummy sample for resizing adorner. Additionally I’ve added support for dragging the elements on the canvas, element selection providing adorner for selected element.

Sample window contains two buttons and a textbox. Feel free to add something else. Upon clicking the element you see the selection adorner giving you ther possibility of resizing the element. I used the changed version of ResizingAdorner (see my previous post) to provide more "Visual Studio" look of resizing.

adorners_1 adorners_2

Unlike MSDN library Resizing Adorner sample, you each corner thumb resizes only it’s part of the element without affecting the position.

Note: This sample was prepared using Visual Studio Team System 2008 beta 2

Here’s the xaml for the window. As I’ve mentioned there are two buttons and a textbox.

Window1.xaml

<Window x:Class="adorners.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300" Loaded="Window_Loaded">
    
    <Canvas Name="myCanvas">
        <Button Canvas.Left="49" Canvas.Top="21" Height="23" Name="button1" Width="75">Button1</Button>
        <Button Height="21" Canvas.Left="147" Canvas.Top="23" Width="75">Button2</Button>
        <TextBox Canvas.Left="64" Canvas.Top="60" Height="24" Name="textBox1" Width="128" />
    </Canvas>
    
</Window>

 

Again the changed resizing adorner class from my previous post

ResizingAdorner.cs

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;

namespace adorners
{
    public class ResizingAdorner : Adorner
    {
        // Resizing adorner uses Thumbs for visual elements.  
        // The Thumbs have built-in mouse input handling.
        Thumb topLeft, topRight, bottomLeft, bottomRight;

        // To store and manage the adorner's visual children.
        VisualCollection visualChildren;

        // Initialize the ResizingAdorner.
        public ResizingAdorner(UIElement adornedElement)
            : base(adornedElement)
        {                
            visualChildren = new VisualCollection(this);

            // Call a helper method to initialize the Thumbs
            // with a customized cursors.
            BuildAdornerCorner(ref topLeft, Cursors.SizeNWSE);
            BuildAdornerCorner(ref topRight, Cursors.SizeNESW);
            BuildAdornerCorner(ref bottomLeft, Cursors.SizeNESW);
            BuildAdornerCorner(ref bottomRight, Cursors.SizeNWSE);

            // Add handlers for resizing.
            bottomLeft.DragDelta += new DragDeltaEventHandler(HandleBottomLeft);
            bottomRight.DragDelta += new DragDeltaEventHandler(HandleBottomRight);
            topLeft.DragDelta += new DragDeltaEventHandler(HandleTopLeft);
            topRight.DragDelta += new DragDeltaEventHandler(HandleTopRight);
        }

        // Handler for resizing from the bottom-right.
        void HandleBottomRight(object sender, DragDeltaEventArgs args)
        {
            FrameworkElement adornedElement = this.AdornedElement as FrameworkElement;
            Thumb hitThumb = sender as Thumb;

            if (adornedElement == null || hitThumb == null) return;
            FrameworkElement parentElement = adornedElement.Parent as FrameworkElement;

            // Ensure that the Width and Height are properly initialized after the resize.
            EnforceSize(adornedElement);

            // Change the size by the amount the user drags the mouse, as long as it's larger 
            // than the width or height of an adorner, respectively.
            adornedElement.Width = Math.Max(adornedElement.Width + args.HorizontalChange, hitThumb.DesiredSize.Width);
            adornedElement.Height = Math.Max(args.VerticalChange + adornedElement.Height, hitThumb.DesiredSize.Height);
        }

        // Handler for resizing from the top-right.
        void HandleTopRight(object sender, DragDeltaEventArgs args)
        {
            FrameworkElement adornedElement = this.AdornedElement as FrameworkElement;
            Thumb hitThumb = sender as Thumb;

            if (adornedElement == null || hitThumb == null) return;
            FrameworkElement parentElement = adornedElement.Parent as FrameworkElement;

            // Ensure that the Width and Height are properly initialized after the resize.
            EnforceSize(adornedElement);

            // Change the size by the amount the user drags the mouse, as long as it's larger 
            // than the width or height of an adorner, respectively.
            adornedElement.Width = Math.Max(adornedElement.Width + args.HorizontalChange, hitThumb.DesiredSize.Width);
            //adornedElement.Height = Math.Max(adornedElement.Height - args.VerticalChange, hitThumb.DesiredSize.Height);

            double height_old = adornedElement.Height;
            double height_new = Math.Max(adornedElement.Height - args.VerticalChange, hitThumb.DesiredSize.Height);
            double top_old = Canvas.GetTop(adornedElement);
            adornedElement.Height = height_new;
            Canvas.SetTop(adornedElement, top_old - (height_new - height_old));
        }

        // Handler for resizing from the top-left.
        void HandleTopLeft(object sender, DragDeltaEventArgs args)
        {
            FrameworkElement adornedElement = AdornedElement as FrameworkElement;
            Thumb hitThumb = sender as Thumb;

            if (adornedElement == null || hitThumb == null) return;

            // Ensure that the Width and Height are properly initialized after the resize.
            EnforceSize(adornedElement);

            // Change the size by the amount the user drags the mouse, as long as it's larger 
            // than the width or height of an adorner, respectively.
            //adornedElement.Width = Math.Max(adornedElement.Width - args.HorizontalChange, hitThumb.DesiredSize.Width);
            //adornedElement.Height = Math.Max(adornedElement.Height - args.VerticalChange, hitThumb.DesiredSize.Height);

            double width_old = adornedElement.Width;
            double width_new = Math.Max(adornedElement.Width - args.HorizontalChange, hitThumb.DesiredSize.Width);
            double left_old = Canvas.GetLeft(adornedElement);
            adornedElement.Width = width_new;
            Canvas.SetLeft(adornedElement, left_old - (width_new - width_old));
            
            double height_old = adornedElement.Height;
            double height_new = Math.Max(adornedElement.Height - args.VerticalChange, hitThumb.DesiredSize.Height);
            double top_old = Canvas.GetTop(adornedElement);
            adornedElement.Height = height_new;
            Canvas.SetTop(adornedElement, top_old - (height_new - height_old));
        }

        // Handler for resizing from the bottom-left.
        void HandleBottomLeft(object sender, DragDeltaEventArgs args)
        {
            FrameworkElement adornedElement = AdornedElement as FrameworkElement;
            Thumb hitThumb = sender as Thumb;

            if (adornedElement == null || hitThumb == null) return;

            // Ensure that the Width and Height are properly initialized after the resize.
            EnforceSize(adornedElement);

            // Change the size by the amount the user drags the mouse, as long as it's larger 
            // than the width or height of an adorner, respectively.
            //adornedElement.Width = Math.Max(adornedElement.Width - args.HorizontalChange, hitThumb.DesiredSize.Width);
            adornedElement.Height = Math.Max(args.VerticalChange + adornedElement.Height, hitThumb.DesiredSize.Height);

            double width_old = adornedElement.Width;
            double width_new = Math.Max(adornedElement.Width - args.HorizontalChange, hitThumb.DesiredSize.Width);
            double left_old = Canvas.GetLeft(adornedElement);
            adornedElement.Width = width_new;            
            Canvas.SetLeft(adornedElement, left_old - (width_new - width_old));
        }

        // Arrange the Adorners.
        protected override Size ArrangeOverride(Size finalSize)
        {
            // desiredWidth and desiredHeight are the width and height of the element that's being adorned.  
            // These will be used to place the ResizingAdorner at the corners of the adorned element.  
            double desiredWidth = AdornedElement.DesiredSize.Width;
            double desiredHeight = AdornedElement.DesiredSize.Height;
            // adornerWidth & adornerHeight are used for placement as well.
            double adornerWidth = this.DesiredSize.Width;
            double adornerHeight = this.DesiredSize.Height;

            topLeft.Arrange(new Rect(-adornerWidth / 2, -adornerHeight / 2, adornerWidth, adornerHeight));
            topRight.Arrange(new Rect(desiredWidth - adornerWidth / 2, -adornerHeight / 2, adornerWidth, adornerHeight));
            bottomLeft.Arrange(new Rect(-adornerWidth / 2, desiredHeight - adornerHeight / 2, adornerWidth, adornerHeight));
            bottomRight.Arrange(new Rect(desiredWidth - adornerWidth / 2, desiredHeight - adornerHeight / 2, adornerWidth, adornerHeight));

            // Return the final size.
            return finalSize;
        }

        // Helper method to instantiate the corner Thumbs, set the Cursor property, 
        // set some appearance properties, and add the elements to the visual tree.
        void BuildAdornerCorner(ref Thumb cornerThumb, Cursor customizedCursor)
        {
            if (cornerThumb != null) return;

            cornerThumb = new Thumb();

            // Set some arbitrary visual characteristics.
            cornerThumb.Cursor = customizedCursor;
            cornerThumb.Height = cornerThumb.Width = 10;
            cornerThumb.Opacity = 0.40;
            cornerThumb.Background = new SolidColorBrush(Colors.MediumBlue);

            visualChildren.Add(cornerThumb);
        }

        // This method ensures that the Widths and Heights are initialized.  Sizing to content produces
        // Width and Height values of Double.NaN.  Because this Adorner explicitly resizes, the Width and Height
        // need to be set first.  It also sets the maximum size of the adorned element.
        void EnforceSize(FrameworkElement adornedElement)
        {
            if (adornedElement.Width.Equals(Double.NaN))
                adornedElement.Width = adornedElement.DesiredSize.Width;
            if (adornedElement.Height.Equals(Double.NaN))
                adornedElement.Height = adornedElement.DesiredSize.Height;

            FrameworkElement parent = adornedElement.Parent as FrameworkElement;
            if (parent != null)
            {
                adornedElement.MaxHeight = parent.ActualHeight;
                adornedElement.MaxWidth = parent.ActualWidth;
            }
        }
        // Override the VisualChildrenCount and GetVisualChild properties to interface with 
        // the adorner's visual collection.
        protected override int VisualChildrenCount { get { return visualChildren.Count; } }
        protected override Visual GetVisualChild(int index) { return visualChildren[index]; }
    }
}

 

And finally the code behind for the window.

Window1.xaml.cs

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;

namespace adorners
{
    public partial class Window1 : Window
    {
        AdornerLayer aLayer;

        bool _isDown;
        bool _isDragging;
        bool selected = false;
        UIElement selectedElement = null;

        Point _startPoint;
        private double _originalLeft;
        private double _originalTop;

        public Window1()
        {
            InitializeComponent();
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            this.MouseLeftButtonDown += new MouseButtonEventHandler(Window1_MouseLeftButtonDown);
            this.MouseLeftButtonUp += new MouseButtonEventHandler(DragFinishedMouseHandler);
            this.MouseMove += new MouseEventHandler(Window1_MouseMove);
            this.MouseLeave += new MouseEventHandler(Window1_MouseLeave);
            
            myCanvas.PreviewMouseLeftButtonDown += new MouseButtonEventHandler(myCanvas_PreviewMouseLeftButtonDown);
            myCanvas.PreviewMouseLeftButtonUp += new MouseButtonEventHandler(DragFinishedMouseHandler);
        }

        // Handler for drag stopping on leaving the window
        void Window1_MouseLeave(object sender, MouseEventArgs e)
        {
            StopDragging();
            e.Handled = true;
        }

        // Handler for drag stopping on user choise
        void DragFinishedMouseHandler(object sender, MouseButtonEventArgs e)
        {
            StopDragging();
            e.Handled = true;
        }

        // Method for stopping dragging
        private void StopDragging()
        {
            if (_isDown)
            {
                _isDown = false;
                _isDragging = false;
            }
        }
        
        // Hanler for providing drag operation with selected element
        void Window1_MouseMove(object sender, MouseEventArgs e)
        {
            if (_isDown)
            {
                if ((_isDragging == false) &&
                    ((Math.Abs(e.GetPosition(myCanvas).X - _startPoint.X) > SystemParameters.MinimumHorizontalDragDistance) ||
                    (Math.Abs(e.GetPosition(myCanvas).Y - _startPoint.Y) > SystemParameters.MinimumVerticalDragDistance)))
                    _isDragging = true;

                if (_isDragging)
                {
                    Point position = Mouse.GetPosition(myCanvas);
                    Canvas.SetTop(selectedElement, position.Y - (_startPoint.Y - _originalTop));
                    Canvas.SetLeft(selectedElement, position.X - (_startPoint.X - _originalLeft));
                }
            }
        }        
                        
        // Handler for clearing element selection, adorner removal
        void Window1_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {            
            if (selected)
            {
                selected = false;
                if (selectedElement != null)
                {
                    aLayer.Remove(aLayer.GetAdorners(selectedElement)[0]);
                    selectedElement = null;
                }                
            }            
        }

        // Handler for element selection on the canvas providing resizing adorner
        void myCanvas_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            // Remove selection on clicking anywhere the window
            if (selected)
            {
                selected = false;
                if (selectedElement != null)
                {
                    // Remove the adorner from the selected element
                    aLayer.Remove(aLayer.GetAdorners(selectedElement)[0]);                    
                    selectedElement = null;
                }
            }

            // If any element except canvas is clicked, 
            // assign the selected element and add the adorner
            if (e.Source != myCanvas)
            {
                _isDown = true;
                _startPoint = e.GetPosition(myCanvas);

                selectedElement = e.Source as UIElement;

                _originalLeft = Canvas.GetLeft(selectedElement);
                _originalTop = Canvas.GetTop(selectedElement);

                aLayer = AdornerLayer.GetAdornerLayer(selectedElement);
                aLayer.Add(new ResizingAdorner(selectedElement));
                selected = true;
                e.Handled = true;
            }
        }
    }
}

 

The main plot of the post was to give the basic idea of implemeting adorners with such the common tasks as resizing and dragging functionality. Of cource it is obvious that code behind sources are far away from the real life application but it is rather easy play with further.

Thanks for paying attention to this post.

Source code for the article

 
3 Comments

Posted by on October 15, 2007 in Software Development

 
 
Follow

Get every new post delivered to your Inbox.

Join 71 other followers

%d bloggers like this: