Pages

Tuesday, December 25, 2012

Drag & Drop Tab Control

Recently I had to implement drag and drop tab control, so users would be able to reorder tab pages inside control by simple drag-n-drop. As any other developer I started to google and found this solution: CodeProject. It seemed quite nice to me, so I used it.
In our application we are using DevExpress controls and XtraTabControl as well, so my TabControl was inherited from DevExpress.XtraTab.XtraTabControl. It worked as expected, but little later we have found that on Windows 7 there is a bug. If someone would try to drag second tab into the first position there will be error:

Object reference not set to an instance of an object. System.NullReferenceException at DevExpress.XtraTab.Drawing.SkinTabPageObjectInfo.CalcColor(BaseTabPageViewInfo pInfo) at DevExpress.XtraTab.Drawing.SkinTabPageObjectInfo..ctor(SkinElement element, BaseTabPageViewInfo pInfo) at DevExpress.XtraTab.ViewInfo.SkinTabHeaderViewInfo.UpdatePageInfo(BaseTabPageViewInfo pInfo) at DevExpress.XtraTab.Drawing.SkinTabPainter.DrawHeaderPage(TabDrawArgs e, BaseTabRowViewInfo row, BaseTabPageViewInfo pInfo) at DevExpress.XtraTab.Drawing.BaseTabPainter.DrawPage(TabDrawArgs e, BaseTabRowViewInfo rowInfo, BaseTabPageViewInfo pInfo) at DevExpress.XtraTab.Drawing.BaseTabPainter.DrawHeaderRow(TabDrawArgs e, BaseTabRowViewInfo rowInfo) at DevExpress.XtraTab.Drawing.BaseTabPainter.DrawHeader(TabDrawArgs e) at DevExpress.XtraTab.Drawing.BaseTabPainter.DrawForeground(TabDrawArgs e) at DevExpress.XtraTab.Drawing.BaseTabPainter.Draw(TabDrawArgs e) at DevExpress.XtraTab.XtraTabControl.OnPaint(PaintEventArgs e) at System.Windows.Forms.Control.PaintWithErrorHandling(PaintEventArgs e, Int16 layer) at System.Windows.Forms.Control.WmPaint(Message& m) at System.Windows.Forms.Control.WndProc(Message& m) at DevExpress.Utils.Controls.ControlBase.WndProc(Message& m) at DevExpress.XtraTab.XtraTabControl.WndProc(Message& m) at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m) at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m) at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)

And the entire tab control becomes crossed out with the two diagonal red lines. Terrible. But much more interesting, that I was not able to reproduce this error on Windows XP!
However, I found the solution. This implementation has too complex way to reorder tabs, so here is my version of OnDragOver:

protected override void OnDragOver(System.Windows.Forms.DragEventArgs e)
{
    base.OnDragOver(e);

    Point pt = new Point(e.X, e.Y);
    //We need client coordinates.
    pt = PointToClient(pt);

    //Get the tab we are hovering over.
    XtraTabPage hover_tab = null;
    for (int i = 0; i < TabPages.Count; i++)
    {
        DevExpress.XtraTab.ViewInfo.XtraTabHitInfo info = this.CalcHitInfo(pt);
        if (info.HitTest == DevExpress.XtraTab.ViewInfo.XtraTabHitTest.PageHeader)
        {
            hover_tab = info.Page as XtraTabPage;
            break;
        }
    }

    //Make sure we are on a tab.
    if (hover_tab != null)
    {
        //Make sure there is a XtraTabPage being dragged.
        if (e.Data.GetDataPresent(typeof(XtraTabPage)))
        {
            e.Effect = DragDropEffects.Move;
            XtraTabPage drag_tab = (XtraTabPage)e.Data.GetData(typeof(XtraTabPage));

            if (drag_tab.Parent != this) //if we are trying to drag "aliens" tab - then stop!
            {
                e.Effect = DragDropEffects.None;
                return;
            }

            int item_drag_index = FindIndex(drag_tab);
            int drop_location_index = FindIndex(hover_tab);

            //Don't do anything if we are hovering over ourself.
            if (item_drag_index != drop_location_index)
            {
                //simply set child index!
                TabPages.SetChildIndex(hover_tab, item_drag_index);
                TabPages.SetChildIndex(drag_tab, drop_location_index);

                if (AfterDragNDrop != null) //there can be delegates for after Drag-N-Drop
                    AfterDragNDrop();

                //Make sure the drag tab is selected.
                SelectedTab = drag_tab;
            }
        }
    }
    else
    {
        e.Effect = DragDropEffects.None;
    }
}

No comments:

Post a Comment