 |
|
 |
Even after seven years of publication there are some weirdo's (=me) using your suggested component extension, Paul! Isn't that wonderfull?
I encountered a shortcomming when the tab sizes of the drag tab and the hover tab are different. Especially when the drag tab is smaller then the hover tab.
But with some small adaptation I managed to overcome this issue.
Hereby the code pieces which I modified: (also most of the other enhancements from the previous posts to this article are already included in this code part)
The OnDragOver event:
protected override void OnDragOver(System.Windows.Forms.DragEventArgs e) { base.OnDragOver(e); Point pt = new Point(e.X, e.Y); pt = PointToClient(pt);
if (e.Data.GetDataPresent(typeof(TabPage))) { TabPage drag_tab = (TabPage)e.Data.GetData(typeof(TabPage));
TabPage hover_tab = GetTabPageByTab(pt, drag_tab); if (hover_tab != null) { e.Effect = DragDropEffects.Move;
int item_drag_index = FindIndex(drag_tab); int drop_location_index = FindIndex(hover_tab);
if (item_drag_index != drop_location_index) { this.SuspendLayout();
TabPages[drop_location_index] = drag_tab; TabPages[item_drag_index] = hover_tab;
SelectedTab = drag_tab; this.ResumeLayout(); } } } else { e.Effect = DragDropEffects.None; } }
The new (additional) GetTabPageByTab method:
private TabPage GetTabPageByTab(Point pt, TabPage dragTab) { TabPage tp = null;
Rectangle dragTabRect = GetTabRect(FindIndex(dragTab));
for (int i = 0; i < TabPages.Count; i++) { Rectangle hoverTabRect = GetTabRect(i);
if (dragTabRect.Width < hoverTabRect.Width) { if (dragTabRect.X < hoverTabRect.X) hoverTabRect.X += hoverTabRect.Width - dragTabRect.Width; else hoverTabRect.Width -= hoverTabRect.Width - dragTabRect.Width; }
if (hoverTabRect.Contains(pt)) { tp = TabPages[i]; break; } }
return tp; }
The only side effect of this implementation is that when hovering over a tab the switch of tabs is only done when the cursor comes out of the blind zone (blind zone equals the difference between tab sizes).
Happy Coding!
|
| Sign In·View Thread·PermaLink | 5.00/5 (1 vote) |
|
|
|
 |
|
 |
Hello Mr. Auger,
Could you please advise me on the following question. What's the story with its reuse in commercial products, is it allowed? Are there any restrictions, copyright statements to be inserted, credit given etc?
Regards, 2twotango
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
 |
This took me about an hour to figure out. Otherwise the code works great.
Just remember that when you create a new instance of the "DraggableTabControl" that you need to set it's AllowDrop attribute to true.
Dim MyCT As New DraggableTabControl MyCT.Location = New Point(390, 80) MyCT.Name = "mtcSections" MyCT.AllowDrop = True Me.Controls.Add(MyCT)
Hope this wasn't obivious and I just made myself look stupid. 
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Tanx for the work, it does a good job. I've added some code in case you have tablabels with different widths. Having a small beside a big one gives increadeble flickering so I added some lines:
Just put it under:
// No need to replace the tab by itself if (item_drag_index == drop_location_index) return;
----------new begin
Rectangle selRect = GetTabRect(item_drag_index); Rectangle dropRect = GetTabRect(drop_location_index); if (selRect.Top == dropRect.Top ) if (item_drag_index < drop_location_index) { if (pt.X < (selRect.Left + dropRect.Width)) return; } else if (pt.X > (dropRect.Left + dropRect.Width)) return;
----------new end
and:
this.SuspendLayout();
Regards, Henk
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
 |
|
 |
i've been trying to do something similar,it tried to use GetChildAtPoint() to get the tab but it didn't work finally i used ur code and it works nicely so nice work man
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Nice work Paul. Below is the code in VB including Jeff Key's Non-flicker Code and Big Man's Start in MouseMove.
Thanks guys.
Public Class DraggableTabControl
Private mRectDragBoxFromMouseDown As Rectangle Private mIsDragging As Boolean = False Private mDragOffset As Point
''' ''' Gets the TabPage Rectangle under the mouse pointer. ''' ''' ''' Rectangle of the relevant TabPage. ''' Public ReadOnly Property MouseDownRectangle() As Rectangle Get Return mRectDragBoxFromMouseDown End Get End Property
Protected Overrides Sub OnDragOver(ByVal e As System.Windows.Forms.DragEventArgs) MyBase.OnDragOver(e) Dim pt As Point = New Point(e.X, e.Y) 'We need client coordinates. pt = PointToClient(pt) 'Get the tab we are hovering over. Dim hoverTab As TabPage = GetTabPageByTab(pt) 'Make sure we are on a tab. If hoverTab IsNot Nothing Then 'Make sure there is a TabPage being dragged. If e.Data.GetDataPresent(GetType(TabPage)) Then e.Effect = DragDropEffects.Move Dim dragTab As TabPage = CType(e.Data.GetData(GetType(TabPage)), TabPage) Dim itemDragIndex As Integer = FindIndex(dragTab) Dim dropLocationIndex As Integer = FindIndex(hoverTab) 'Don't do anything if we are hovering over ourself. If itemDragIndex <> dropLocationIndex Then Dim selTabPage As TabPage = TabPages(itemDragIndex) Dim repTabPage As TabPage = TabPages(dropLocationIndex) TabPages(dropLocationIndex) = selTabPage TabPages(itemDragIndex) = repTabPage SelectedTab = selTabPage End If End If Else e.Effect = DragDropEffects.None End If End Sub
''' ''' Sets a member variable mRectDragBoxFromMouseDown to store the point where the mouse down event occured. ''' ''' Mouse X coordinate. ''' Mouse Y coordinate. ''' Protected Sub CalcRectDragBox(ByVal x As Integer, ByVal y As Integer) ' Remember the point where the mouse down occurred. The DragSize indicates ' the size that the mouse can move before a drag event should be started. Dim dragSize As Size = SystemInformation.DragSize ' Create a rectangle using the DragSize, with the mouse position being ' at the center of the rectangle. mRectDragBoxFromMouseDown = New Rectangle(New _ Point(CInt(x - (dragSize.Width / 2)), CInt(y - (dragSize.Height / 2))), dragSize) End Sub
Protected Overrides Sub OnMouseDown(ByVal e As MouseEventArgs) MyBase.OnMouseDown(e) 'Store the rectangle of the TabPage under the mouse. CalcRectDragBox(e.X, e.Y) End Sub
Protected Overrides Sub OnMouseMove(ByVal e As MouseEventArgs) MyBase.OnMouseMove(e) ' Handle Mouse move only if left button is pressed. If e.Button = Windows.Forms.MouseButtons.Left Then ' If the mouse moves outside the rectangle, start the drag. If mRectDragBoxFromMouseDown <> Rectangle.Empty And Not mRectDragBoxFromMouseDown.Contains(e.X, e.Y) Then mIsDragging = True mDragOffset = New Point(e.X, e.Y) Invalidate() ' Proceed with the drag and drop. Dim dropEffect As DragDropEffects = DoDragDrop(Me.SelectedTab, DragDropEffects.Move) ' Reset the drag box to avoid reentry of drag. CalcRectDragBox(e.X, e.Y) mIsDragging = False mDragOffset = Point.Empty Invalidate() End If End If End Sub
' ' Finds the TabPage which contains the passed point. ' ' The point (given in client coordinates) to look for a TabPage. ' The TabPage whose tab is at the given point (null if there isn't one). Friend Function GetTabPageByTab(ByVal pt As Point) As TabPage Dim tp As TabPage = Nothing Dim i As Integer = 0 Do While i < TabPages.Count If GetTabRect(i).Contains(pt) Then tp = TabPages(i) End If i += 1 Loop Return tp End Function
' ' Find the index of the given TabPage. ' ' The TabPage we want the index for. ' The index of the given TabPage(-1 if it isn't found.) Private Function FindIndex(ByVal page As TabPage) As Integer Dim i As Integer = 0 Do While i < TabPages.Count If TabPages(i).Equals(page) Then Return i End If i += 1 Loop Return -1 End Function End Class
Grant.
|
| Sign In·View Thread·PermaLink | 5.00/5 (1 vote) |
|
|
|
 |
|
 |
Hi, I noticed that when you drag a tab onto somewhere outside the form (in my case, the windows taskbar), there will be a COM exception exposed indicating wrong handle.
Is there anyone have idea about this?
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
I think this may have something to do with the way you are using the control. There is no COM used at all. When I compile it and run it and drag/drop a tab to the task bar - or to anywhere for that matter - I don't have any exceptions at all. I will be happy to help you track it down if you could get me a sample app that has the problem.
Sorry I wasn't more help.
PJA
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Hi Paul,
Thanks for your quick response.
Actually, the problem I mentioned only occur in debug mode. And I have found the solution to that.
My way to do this is put DoDragDrop in event handler OnMouseMove instead of OnMouseDown. And override OnQueryContinueDrag method to judge whether the mouse pointer has gone to some invalid area. This is what MSDN told me and I'm using VS2005.
But anyway, your work provided a very good framework and starting point for me to study. Thanks again.
Eliot
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
//Make sure we are on a tab. if(hover_tab != null) { //Make sure there is a TabPage being dragged. if(e.Data.GetDataPresent(typeof(TabPage))) { e.Effect = DragDropEffects.Move; TabPage drag_tab = (TabPage)e.Data.GetData(typeof(TabPage));
int item_drag_index = FindIndex(drag_tab); int drop_location_index = FindIndex(hover_tab); // No need to replace the tab by itself if (item_drag_index == drop_location_index) return;
this.SuspendLayout (); TabPage selTabPage = TabPages[item_drag_index]; TabPage repTabPage = TabPages[drop_location_index]; TabPages[drop_location_index] = selTabPage; TabPages[item_drag_index] = repTabPage; SelectedTab = selTabPage; this.ResumeLayout (); } } else { e.Effect = DragDropEffects.None; }
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
SUper better ! the non-flickering solution is more logic and works better.
hundreds thanks.:->
[Others should opt for that alternative solution.]
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
It doesn't work if the mouse wonders off of the tab control. However I made a little adjustment to the code and it seems to work great. Probably could be optimized a little but here it is anyways.
Replace:
TabPage selTabPage = TabPages[item_drag_index]; TabPage repTabPage = TabPages[drop_location_index]; TabPages[drop_location_index] = selTabPage; TabPages[item_drag_index] = repTabPage; SelectedTab = selTabPage;
With:
bool isleft;
if (drop_location_index < item_drag_index) isleft = true; else isleft = false;
this.SuspendLayout();
if (isleft) { Queue pages = new Queue(); pages.Enqueue(page);
for (int i = drop_location_index; i < TabPages.Count; i++) { if (TabPages[i] != page) pages.Enqueue(TabPages[i]); }
for (int i = drop_location_index; i < TabPages.Count; i++) { TabPages[i] = pages.Dequeue(); }
SelectedTab = page; } else { Queue pages = new Queue(); pages.Enqueue(page);
for (int i = drop_location_index; i > -1; i--) { if (TabPages[i] != page) pages.Enqueue(TabPages[i]); }
for (int i = drop_location_index; i > -1; i--) { TabPages[i] = pages.Dequeue(); }
SelectedTab = page; }
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Here is the new code:
using System; using System.Collections; using System.ComponentModel; using System.Drawing; using System.Data; using System.Windows.Forms;
namespace DraggableTabControl { [ToolboxBitmap(typeof(DraggableTabControl))] /// <summary> /// Summary description for DraggableTabPage. /// </summary> public class DraggableTabControl : System.Windows.Forms.TabControl { /// <summary> /// Required designer variable. /// </summary> private System.ComponentModel.Container components = null;
public DraggableTabControl() { // This call is required by the Windows.Forms Form Designer. InitializeComponent(); AllowDrop = true; // TODO: Add any initialization after the InitForm call
}
/// <summary> /// Clean up any resources being used. /// </summary> protected override void Dispose( bool disposing ) { if( disposing ) { if(components != null) { components.Dispose(); } } base.Dispose( disposing ); }
#region Component Designer generated code /// <summary> /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// </summary> private void InitializeComponent() { } #endregion private Rectangle m_rectDragBoxFromMouseDown; private Point m_dragOffset; 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. TabPage hover_tab = GetTabPageByTab(pt);
//Make sure we are on a tab. if(hover_tab != null) { //Make sure there is a TabPage being dragged. if(e.Data.GetDataPresent(typeof(TabPage))) { e.Effect = DragDropEffects.Move; TabPage drag_tab = (TabPage)e.Data.GetData(typeof(TabPage)); int item_drag_index = FindIndex(drag_tab); int drop_location_index = FindIndex(hover_tab); TabPage selTabPage = TabPages[item_drag_index]; TabPage repTabPage = TabPages[drop_location_index]; TabPages[drop_location_index] = selTabPage; TabPages[item_drag_index] = repTabPage; SelectedTab = selTabPage; } } else { e.Effect = DragDropEffects.None; } }
protected override void OnMouseMove(MouseEventArgs e) { base.OnMouseMove (e);
// Handle Mouse move only if left button is pressed if ( ( e.Button & MouseButtons.Left ) == MouseButtons.Left ) {
// If the mouse moves outside the rectangle, start the drag. if ( m_rectDragBoxFromMouseDown != Rectangle.Empty && !m_rectDragBoxFromMouseDown.Contains( e.X, e.Y ) ) { m_dragOffset = new Point( e.X, e.Y ) ; Invalidate() ;
Point pt = new Point(e.X, e.Y); TabPage tp = GetTabPageByTab(pt); // Proceed with the drag and drop DragDropEffects dropEffect = DoDragDrop( tp, DragDropEffects.Move ) ;
// Reset the drag box to avoid reentry of drag CalcRectDragBox( e.X, e.Y ) ;
m_dragOffset = Point.Empty ; Invalidate() ; } } } protected void CalcRectDragBox( int x, int y ) { // Remember the point where the mouse down occurred. The DragSize indicates // the size that the mouse can move before a drag event should be started. Size dragSize = SystemInformation.DragSize ; // Create a rectangle using the DragSize, with the mouse position being // at the center of the rectangle. m_rectDragBoxFromMouseDown = new Rectangle( new Point( x - ( dragSize.Width /2 ), y - ( dragSize.Height /2 ) ), dragSize ) ; }
protected override void OnMouseDown( MouseEventArgs e ) { base.OnMouseDown( e ) ;
CalcRectDragBox( e.X, e.Y ) ; }
/// <summary> /// Finds the TabPage whose tab is contains the given point. /// </summary> /// <param name="pt">The point (given in client coordinates) to look for a TabPage.</param> /// <returns>The TabPage whose tab is at the given point (null if there isn't one).</returns> private TabPage GetTabPageByTab(Point pt) { TabPage tp = null;
for(int i = 0; i < TabPages.Count; i++) { if(GetTabRect(i).Contains(pt)) { tp = TabPages[i]; break; } } return tp; }
/// <summary> /// Loops over all the TabPages to find the index of the given TabPage. /// </summary> /// <param name="page">The TabPage we want the index for.</param> /// <returns>The index of the given TabPage(-1 if it isn't found.)</returns> private int FindIndex(TabPage page) { for(int i = 0; i < TabPages.Count; i++) { if(TabPages[i] == page) return i; }
return -1; } } }
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Hello,
I want to copy tabpage and all the controls of tabpage to another tabcontrol. Is it possible?
If yes, can you please guide us how do i do that?
thank you in advance, DNN
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
I am not sure whether you mean in the designer or programatically, so I will address both questions...
If you are in the designer, just right-click on the tab page to be copied and click "Copy" from the menu. Right click on the new target tab control and click "Paste" from the menu. Note that to select a page, you must first click the tab, then the designer area.
If you want to do it programmatically, this will work...
TabPage tp = tabControl1.TabPages[0]; //Pick the real tab page here tabControl1.TabPages.Remove(tp); tabControl2.TabPages.Add(tp);
Hope this helps.
Paul
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
I guess the example is rather more moving then copying...
I still haven't found out how to copy a TabPage into the SAME TabControl. I had hoped that there would be some kind of copy constructor, but I guess that is old fashioned C++.
Any suggestions would be great.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Removing all of the tab pages and re-adding them causes my pages to flicker like crazy. You don't need to do all that. Simply replace the following:
TabPages.Remove(selTabPage); TabPages[drop_location_index] = selTabPage;
ArrayList pages = new ArrayList();
for(int i = 0; i < TabPages.Count; i++) { if(i != item_drag_index) pages.Add(TabPages[i]); }
pages.Insert(drop_location_index, drag_tab);
TabPages.Clear();
TabPages.AddRange((TabPage[])pages.ToArray(typeof(TabPage)));
SelectedTab = drag_tab;
With:
TabPage selTabPage = TabPages[item_drag_index]; TabPage repTabPage = TabPages[drop_location_index]; TabPages[drop_location_index] = selTabPage; TabPages[item_drag_index] = repTabPage; SelectedTab = selTabPage;
-jk
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Thank you. I experience flicker often as well. I guess the simplest solution usually is the correct one. I appreciate your input and I will incorporate this change quickly.
-Paul
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Nice job, however the Drag & Drop should start from the MouseMove handler only if the mouse is out of a rectangle defined in the MouseDown handler. Currently Drag & Drop starts every time a tab page is clicked...
Big Man
For example:
protected void CalcRectDragBox( int x, int y ) { // Remember the point where the mouse down occurred. The DragSize indicates // the size that the mouse can move before a drag event should be started. Size dragSize = SystemInformation.DragSize ; // Create a rectangle using the DragSize, with the mouse position being // at the center of the rectangle. m_rectDragBoxFromMouseDown = new Rectangle( new Point( x - ( dragSize.Width /2 ), y - ( dragSize.Height /2 ) ), dragSize ) ; }
protected override void OnMouseDown( MouseEventArgs e ) { base.OnMouseDown( e ) ;
CalcRectDragBox( e.X, e.Y ) ; }
protected override void OnMouseMove( MouseEventArgs e ) { base.OnMouseMove( e ) ;
// Handle Mouse move only if left button is pressed if ( ( e.Button & MouseButtons.Left ) == MouseButtons.Left ) { // If the mouse moves outside the rectangle, start the drag. if ( m_rectDragBoxFromMouseDown != Rectangle.Empty && !m_rectDragBoxFromMouseDown.Contains( e.X, e.Y ) ) { m_isDragging = true ; m_dragOffset = new Point( e.X, e.Y ) ; Invalidate() ;
// Proceed with the drag and drop DragDropEffects dropEffect = DoDragDrop( <YOUR OBJECT>, DragDropEffects.Move ) ;
// Reset the drag box to avoid reentry of drag CalcRectDragBox( e.X, e.Y ) ;
m_isDragging = false ; m_dragOffset = Point.Empty ; Invalidate() ; } } }
|
| Sign In·View Thread·PermaLink | 5.00/5 (2 votes) |
|
|
|
 |
|
 |
Note that this change also fixes the problem of the DraggableTabControl swallowing the DoubleClick event.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Well..It is good to know that after 5 years that code fix is still relevant..
"The true sign of intelligence is not knowledge but imagination." - Albert Einstein
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Good job.
The only thing I would suggest is supporting the escape key to cancel the dragging operation and restore the control to its state before the last dragging op started.
If you get what I mean.
Pete
Insert Sig. Here!
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |