Column Drag and Swap

Feb 1, 2011 at 1:04 AM

Hi to all,

I've done a little class that inherits SourceGrid.DataGrid and add a column reordering functionality by dragging and swapping columns; my first attempt was to write a Controller, and it seems ok if from there I cannot find how to invoke Swap method and other things to bypass mousePosition locked after MouseDown without break to much of your code.

If you are interested, my code is very simple. I wrote a post on my blog, with some info and all source code.

See you.

Coordinator
Feb 1, 2011 at 6:00 AM

Hi gammato!

Thanks for the blog post, i liked it. It rather clearly conveys the idea of how you have implemented it.

It would be better though to have a Controller, that way we could include your code into SourceGrid.   Maybe by any chance you still have the code that you have written for Controller?

Feb 1, 2011 at 11:26 AM

Here controller code and link on HeaderCell, but it doesn't work for these things:

* OnMouseMove event is not called by parent, becouse when a MouseDown operation starts no more OnMouseMove are called from GridVirtual.OnMouseMove, I don't know the meaning of this behaviuor

* From a Cell Controller I cannot reach Swap methods because I see Cell Controllers belongs to GridVirtual, not Grid (owner of Swap method)

* I don't know where to overload OnPaint

However, there are some Debug.WriteLine that shows OnMouseDown and OnMouseUp works properly.

Maybe, I have to put something like this Controller on  a different level, but I see for the first time this grid tow days ago and I don't have yet a global vision of grid architecture.

I try to keep current code conventions.

Basically, I wrote the same code adding a ColumnSwapper.cs in SourceGrid/Cells/Controllers

using System;
using System.Drawing;
using System.Windows.Forms;

namespace SourceGrid.Cells.Controllers
{
    public class ColumnSwapper : ControllerBase
    {
        /// <summary>
        /// Movable behavior
        /// </summary>
        public readonly static ColumnSwapper ColumnSwapperable = new ColumnSwapper(true);

        /// <summary>
        /// Border used to calculate the region where the resize is enabled.
        /// </summary>
        public DevAge.Drawing.RectangleBorder LogicalBorder = new DevAge.Drawing.RectangleBorder(new DevAge.Drawing.BorderLine(System.Drawing.Color.Black, 4), new DevAge.Drawing.BorderLine(System.Drawing.Color.Black, 4));

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="movable"></param>
        public ColumnSwapper(bool movable)
        {
            m_Movable = movable;
        }

        private MouseCursor mMovingCursor = new MouseCursor(System.Windows.Forms.Cursors.Hand, false);
        private int m_DragStartingColumn = -1;

        #region IBehaviorModel Members

        public override void OnMouseDown(CellContext sender, MouseEventArgs e)
        {
            base.OnMouseDown(sender, e);

            if (!m_Movable) return;

            m_IsMoveing = false;

            Rectangle l_CellRect = sender.Grid.PositionToRectangle(sender.Position);
            Point mousePoint = new Point(e.X, e.Y);

            DevAge.Drawing.RectanglePartType partType = LogicalBorder.GetPointPartType(l_CellRect, mousePoint, out mDistanceFromBorder);

            if (partType == DevAge.Drawing.RectanglePartType.ContentArea)
                if (sender.Grid.PositionToCellRange(sender.Position).GetCellsPositions()[0].Row == 0)
                {
                    m_DragStartingColumn = sender.Grid.PositionToCellRange(sender.Position).GetCellsPositions()[0].Column;
                    System.Diagnostics.Debug.WriteLine("Start Move col " + m_DragStartingColumn.ToString());
                }
        }

        public override void OnMouseUp(CellContext sender, MouseEventArgs e)
        {
            
            base.OnMouseUp(sender, e);

            int dragEndingColumn = sender.Grid.PositionToCellRange(sender.Grid.PositionAtPoint(new Point(e.X, e.Y))).GetCellsPositions()[0].Column;
            if (dragEndingColumn != -1 && dragEndingColumn != m_DragStartingColumn)
            {
                //todo: column swap
                System.Diagnostics.Debug.WriteLine("Moved col " + m_DragStartingColumn.ToString() + " to " + dragEndingColumn.ToString());
            }
            m_DragStartingColumn = -1;
            m_IsMoveing = false;
        }

        public override void OnMouseMove(CellContext sender, MouseEventArgs e)
        {
            base.OnMouseMove(sender, e);

            if (m_IsMoveing)
            {
                mMovingCursor.ApplyCursor(sender, e);

                int dragEndingColumn = sender.Grid.PositionToCellRange(sender.Grid.PositionAtPoint(new Point(e.X, e.Y))).GetCellsPositions()[0].Column;
                if (dragEndingColumn != -1 && dragEndingColumn != m_DragStartingColumn)
                {
                    System.Diagnostics.Debug.WriteLine("Moveing col " + m_DragStartingColumn.ToString() + " to " + dragEndingColumn.ToString());                    
                }
            }
            else
                mMovingCursor.ResetCursor(sender, e);

        }

        public override void OnMouseLeave(CellContext sender, EventArgs e)
        {
            base.OnMouseLeave(sender, e);

            mMovingCursor.ResetCursor(sender, e);
            m_IsMoveing = false;
        }

        #endregion

        private bool m_Movable = false;

 
        //Queste variabili indicano lo stato del resize (essendo usate per� in un contesto di MouseEnter e MouseLeave possono essere tranquillamente condivise tra pi� cello o griglie, visto che il mouse in un dato momento sar� solo in una cella particolare, di un thread particolare, ...). Questo � un motivo in pi� per non poter usare questo controllo in multi thread (cosa che nessun controllo windows forms pu� fare ...)
        private bool m_IsMoveing = false;
        private float mDistanceFromBorder = 0;

        /// <summary>
        /// Indicates if the behavior is currently moveing a column
        /// </summary>
        public bool IsMoveing
        {
            get { return m_IsMoveing; }
        }
    }
}

and in ColumnHeader.cs in SourceGrid/Cells I've added a controller reference

using System;
using System.Drawing;

namespace SourceGrid.Cells.Virtual
{
	/// <summary>
	/// A cell that rappresent a header of a table, with 3D effect. This cell override IsSelectable to false. Default use VisualModels.VisualModelHeader.Style1
	/// </summary>
	public class ColumnHeader : CellVirtual
	{
		/// <summary>
		/// Constructor
		/// </summary>
		public ColumnHeader()
		{
			View = Views.ColumnHeader.Default;
			Model.AddModel(new Models.SortableHeader());
			AddController(Controllers.Unselectable.Default);
			AddController(Controllers.MouseInvalidate.Default);
			ResizeEnabled = true;
			AutomaticSortEnabled = true;
		}

		/// <summary>
		/// Gets or sets if enable the resize of the width of the column. This property internally use the Controllers.Resizable.ResizeWidth.
		/// </summary>
		public bool ResizeEnabled
		{
			get{return FindController(typeof(Controllers.Resizable)) == Controllers.Resizable.ResizeWidth;}
			set
			{
                if (value == ResizeEnabled)
                    return;

				if (value)
					AddController(Controllers.Resizable.ResizeWidth);
				else
					RemoveController(Controllers.Resizable.ResizeWidth);
			}
		}

		/// <summary>
		/// Gets or sets if enable the automatic sort features of the column. This property internally use the Controllers.SortableHeader.Default.
		/// </summary>
		public bool AutomaticSortEnabled
		{
			get{return FindController(typeof(Controllers.SortableHeader)) == Controllers.SortableHeader.Default;}
			set
			{
                if (value == AutomaticSortEnabled)
                    return;

				if (value)
					AddController(Controllers.SortableHeader.Default);
				else
					RemoveController(Controllers.SortableHeader.Default);
			}
		}
	}
}

namespace SourceGrid.Cells
{
	/// <summary>
	/// A cell that rappresent a header of a table. 
	/// View: Views.ColumnHeader.Default 
	/// Model: Models.SortableHeader 
	/// Controllers: Controllers.Unselectable.Default, Controllers.MouseInvalidate.Default, Controllers.Resizable.ResizeWidth, Controllers.SortableHeader.Default 
	/// </summary>
	public class ColumnHeader : Cell
	{
		/// <summary>
		/// Constructor
		/// </summary>
		public ColumnHeader():this(null)
		{
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="cellValue"></param>
		public ColumnHeader(object cellValue):base(cellValue)
		{
			View = Views.ColumnHeader.Default;
			Model.AddModel(new Models.SortableHeader());
			AddController(Controllers.Unselectable.Default);
			AddController(Controllers.MouseInvalidate.Default);
			ResizeEnabled = true;
			AutomaticSortEnabled = true;
            ColumnSwapperEnabled = true;
		}

		/// <summary>
		/// Gets or sets if enable the resize of the width of the column. This property internally use the Controllers.Resizable.ResizeWidth.
		/// </summary>
		public bool ResizeEnabled
		{
			get{return FindController(typeof(Controllers.Resizable)) == Controllers.Resizable.ResizeWidth;}
			set
			{
                if (value == ResizeEnabled)
                    return;

				if (value)
					AddController(Controllers.Resizable.ResizeWidth);
				else
					RemoveController(Controllers.Resizable.ResizeWidth);
			}
		}

        public bool ColumnSwapperEnabled
        {
            get { return FindController(typeof(Controllers.ColumnSwapper)) == Controllers.ColumnSwapper.ColumnSwapperable; }
            set
            {
                if (value == ColumnSwapperEnabled)
                    return;

                if (value)
                    AddController(Controllers.ColumnSwapper.ColumnSwapperable);
                else
                    RemoveController(Controllers.ColumnSwapper.ColumnSwapperable);
            }
        }

		/// <summary>
		/// Gets or sets if enable the automatic sort features of the column. This property internally use the Controllers.SortableHeader.Default.
		/// If you want to use a custom sort you can add a customized Controller or a customized instance of Controllers.SortableHeader.
		/// </summary>
		public bool AutomaticSortEnabled
		{
			get{return FindController(typeof(Controllers.SortableHeader)) == Controllers.SortableHeader.Default;}
			set
			{
                if (value == AutomaticSortEnabled)
                    return;

				if (value)
					AddController(Controllers.SortableHeader.Default);
				else
					RemoveController(Controllers.SortableHeader.Default);
			}
		}

		/// <summary>
		/// Gets the used SortableHeader model.
		/// </summary>
		private Models.SortableHeader SortableHeaderModel
		{
			get{return (Models.SortableHeader)Model.FindModel(typeof(Models.SortableHeader));}
		}

		/// <summary>
		/// Status of the sort.
		/// </summary>
		public Models.SortStatus SortStatus
		{
			get{return SortableHeaderModel.SortStatus;}
			set{SortableHeaderModel.SortStatus = value;}
		}

		/// <summary>
		/// Comparer used.
		/// </summary>
		public System.Collections.IComparer SortComparer
		{
			get{return SortStatus.Comparer;}
			set{SortStatus = new SourceGrid.Cells.Models.SortStatus(SortStatus.Style, value);}
		}

		/// <summary>
		/// Sort style.
		/// </summary>
		public DevAge.Drawing.HeaderSortStyle SortStyle
		{
			get{return SortStatus.Style;}
			set{SortStatus = new SourceGrid.Cells.Models.SortStatus(value, SortStatus.Comparer);}
		}

		/// <summary>
		/// Sort the column
		/// </summary>
		/// <param name="ascending"></param>
		public void Sort(bool ascending)
		{
			Controllers.SortableHeader sortableHeader = (Controllers.SortableHeader)FindController(typeof(Controllers.SortableHeader));
			if (sortableHeader == null)
				throw new SourceGridException("No SortableHeader controller found");
			sortableHeader.SortColumn(new CellContext(Grid, Range.Start, this), ascending, SortComparer);
		}
	}

}

See you