I have a custom control containing a button with a label underneath. I want to make these controls to be draggable over another one to arrange them as I want in a flowlayoutpanel.
Now is working only if I drag the control from it's background (marked with yellow in the picture bellow) to the other control's yellow marked area, but not if i drag from the button or label area..
How can I make it so I can move the custom control no matter from where I grab and drop it on the other control. Basically to be only one control not like a container for the button and label..
This is my code so far:
private void flowLayoutPanel1_DragEnter(object sender, DragEventArgs e)
{
e.Effect = DragDropEffects.Move;
}
private void flowLayoutPanel1_DragDrop(object sender, DragEventArgs e)
{
CustomControl target = sender as CustomControl;
if (target != null)
{
int targetIndex = FindCSTIndex(target);
if (targetIndex != -1)
{
string pictureBoxFormat = typeof(CustomControl).FullName;
if (e.Data.GetDataPresent(pictureBoxFormat))
{
CustomControl source = e.Data.GetData(pictureBoxFormat) as CustomControl;
int sourceIndex = this.FindCSTIndex(source);
if (targetIndex != -1)
this.flowLayoutPanel1.Controls.SetChildIndex(source, targetIndex);
}
}
}
}
private int FindCSTIndex(CustomControl cst_ctr)
{
for (int i = 0; i < this.flowLayoutPanel1.Controls.Count; i++)
{
CustomControl target = this.flowLayoutPanel1.Controls[i] as CustomControl;
if (cst_ctr == target)
return i;
}
return -1;
}
private void OnCstMouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
CustomControl cst = sender as CustomControl;
cst.DoDragDrop(cst, DragDropEffects.Move);
}
}
And the custom control class:
public class CustomControl : Control
{
private Button _button;
private Label _label;
public CustomControl(Button button, Label label)
{
_button = button;
_label = label;
button.Width = 50;
button.Height = 50;
label.Width = 65;
button.BackgroundImageLayout = ImageLayout.Stretch;
Height = button.Height + label.Height;
Width = 68;
// Width = Math.Max(button.Width, label.Width);
Controls.Add(_button);
_button.Location = new Point(0, 0);
Controls.Add(_label);
_label.Location = new Point(0, button.Height);
}
}
Use MouseDown instead of MouseMove to initiate drag-and-drop (MSDN). You can initiate drag-and-drop in the control code itself (assuming what all CustomControls will be drag-and-drop-able), otherwise you may want to create public method to sign childs (exposing childs is bad idea, unless you already use them outside).
public class CustomControl : Control
{
...
public CustomControl(Button button, Label label)
{
...
_button.MouseDown += OnMouseDown;
_label.MouseDown += OnMouseDown;
}
override void OnMouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
(sender as Control).DoDragDrop(this, DragDropEffects.Move);
}
}
Untested, but should give you idea.
Managed to solve it:
private void flowLayoutPanel1_DragDrop(object sender, DragEventArgs e)
{
Control target = new Control();
target.Parent = sender as Control;
if (target != null)
{
int targetIndex = FindCSTIndex(target.Parent);
if (targetIndex != -1)
{
string cst_ctrl = typeof(CustomControl).FullName;
if (e.Data.GetDataPresent(cst_ctrl))
{
Button source = new Button();
source.Parent = e.Data.GetData(cst_ctrl) as CustomControl;
if (targetIndex != -1)
this.flowLayoutPanel1.Controls.SetChildIndex(source.Parent, targetIndex);
}
}
}
}
private int FindCSTIndex(Control cst_ctr)
{
for (int i = 0; i < this.flowLayoutPanel1.Controls.Count; i++)
{
CustomControl target = this.flowLayoutPanel1.Controls[i] as CustomControl;
if (cst_ctr.Parent == target)
return i;
}
return -1;
}
private void OnCstMouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
Control cst = sender as Control;
cst.DoDragDrop(cst.Parent, DragDropEffects.Move);
}
}
Related
I have a ListBox that slides its ScrollViewer horizontally by dragging with the left mouse button pressed.
private ScrollViewer scrollViewer;
private Point scrollMousePoint = new Point();
private double horizontalOffset = 1;
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
Border border = (Border)VisualTreeHelper.GetChild(myListBox, 0);
scrollViewer = (ScrollViewer)VisualTreeHelper.GetChild(border, 0);
// This works
}
private void myListBox_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
scrollMousePoint = e.GetPosition(scrollViewer);
horizontalOffset = scrollViewer.HorizontalOffset;
scrollViewer.CaptureMouse();
// This works
}
private void myListBox_PreviewMouseMove(object sender, MouseEventArgs e)
{
if (scrollViewer.IsMouseCaptured)
scrollViewer.ScrollToHorizontalOffset(horizontalOffset + (scrollMousePoint.X - e.GetPosition(scrollViewer).X));
// This works
}
private void myListBox_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
scrollViewer.ReleaseMouseCapture();
if (scrollMousePoint == Mouse.GetPosition(scrollViewer))
{
// Click
// Here I want to get and select the ListBoxItem that was pressed.
}
}
I want to get and select the ListBoxItem that was clicked.
To determine if it is a click without movement, I compare if the position saved in the PreviewMouseLeftButtonDown and in PreviewMouseLeftButtonUp events are the same.
Here you go:
private void myListBox_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
scrollViewer.ReleaseMouseCapture();
if (scrollMousePoint == Mouse.GetPosition(scrollViewer))
{
ListBox listBox = sender as ListBox;
if (listBox != null)
{
var element = VisualTreeHelper.HitTest(listBox, scrollMousePoint).VisualHit;
if (element.GetType() != typeof(ScrollViewer))
{
while (element.GetType() != typeof(ListBoxItem))
element = VisualTreeHelper.GetParent(element);
(element as ListBoxItem).IsSelected = true;
}
}
}
}
I managed to drag and drop items from my ListView onto a canvas and show an image on it. I used this question as a template:
https://social.msdn.microsoft.com/Forums/en-US/cef5c42c-87a0-4e19-afc8-935284607488/drag-and-drop-controls-issue-from-listbox-into-canvas-wpf?forum=wpf
I also added the suggestion in that thread, so that the Item gets rendered where I drop it. These are the code behinds for my "Drawing Plate" (The canvas) and the ListView:
Canvas:
public partial class DrawingPlateUC : UserControl
{
IMessenger messenger = Messenger.Instance;
public DrawingPlateUC()
{
InitializeComponent();
}
void Canvas_Drop(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent("MyFormat"))
{
var module = e.Data.GetData("MyFormat") as Module;
Canvas CanvasView = sender as Canvas;
Image image = new Image();
image.Source = module.ModuleImage;
CanvasView.Children.Add(image);
}
}
private void Canvas_DragOver(object sender, DragEventArgs e)
{
// write down this point to a private member
Point enterPoint = e.GetPosition(this.moduleCanvas);
messenger.Send<Point>(enterPoint, MessengerTopics.MousePoint);
}
void Canvas_DragEnter(object sender, DragEventArgs e)
{
if (!(e.Data.GetDataPresent("contact")) || (sender == e.Source))
{
e.Effects = DragDropEffects.Copy;
}
}
}
ListView:
public partial class ItemListViewUC : UserControl
{
IMessenger messenger = Messenger.Instance;
Point startPoint;
Point enterPoint;
public ItemListViewUC()
{
messenger.Register<Point>(this, MessengerTopics.MousePoint, GetEnterPoint);
InitializeComponent();
}
private void GetEnterPoint(Point point)
{
enterPoint = point;
}
void StackPanel_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
startPoint = e.GetPosition(null);
}
void StackPanel_PreviewMouseMove(object sender, MouseEventArgs e)
{
Point mousPos = e.GetPosition(null);
Vector diff = startPoint - mousPos;
if ((e.LeftButton == MouseButtonState.Pressed) && (Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance) && (Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance))
{
ListView listView = sender as ListView;
ListViewItem listViewItem = FindAnchestor<ListViewItem>((DependencyObject)e.OriginalSource);
if (listViewItem == null) { return; }
var contact = (Module)listView.ItemContainerGenerator.ItemFromContainer(listViewItem);
DataObject dataObject = new DataObject("MyFormat", contact);
try
{
DragDrop.DoDragDrop(listViewItem, dataObject, DragDropEffects.Copy);
}
catch { }
// Set the Margin property to place the drag item on the canvas.
listViewItem.Margin = new Thickness(enterPoint.X, enterPoint.Y, 0, 0);
}
}
static T FindAnchestor<T>(DependencyObject current) where T : DependencyObject
{
do
{
if (current is T)
{
return (T)current;
}
current = VisualTreeHelper.GetParent(current);
}
while (current != null);
return null;
}
}
This here is supposed to draw the Image on the spot where I dropped it:
listViewItem.Margin = new Thickness(enterPoint.X, enterPoint.Y, 0, 0);
But it only renders on the top left corner, coordinates 0, 0 of the canvas. This is for all Items I drop, they overlay on that position. I already checked the coordinates, they are not 0, 0, but the ones where my mouse is when I drop the item.
This is my Window:
when you drop an item, you don't set any coordinate for Image:
void Canvas_Drop(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent("MyFormat"))
{
var module = e.Data.GetData("MyFormat") as Module;
Canvas CanvasView = sender as Canvas;
Image image = new Image();
image.Source = module.ModuleImage;
image.SetValue(Canvas.LeftProperty, _enterPoint.X);
image.SetValue(Canvas.TopProperty, _enterPoint.Y);
CanvasView.Children.Add(image);
}
}
private Point _enterPoint;
private void Canvas_DragOver(object sender, DragEventArgs e)
{
_enterPoint = e.GetPosition(this.moduleCanvas);
messenger.Send<Point>(_enterPoint, MessengerTopics.MousePoint);
}
If one of my buttons is selected and the mouse wheel is activated then its size should change according to the mouse wheel action.
If the mouse wheel goes up the size of my button should increase by 2.
If the mouse wheel goes down the size of my button should decrease by 2.
I'm trying something like this:
private void Form1_Load(object sender, EventArgs e)
{
foreach (Control c in this.Controls)
{
btn = c as Button;
{
if (btn == null)
continue;
c.MouseWheel += c_MouseWheel;
}
}
}
private void c_MouseWheel(object sender, MouseEventArgs e)
{
TabControl tabControl = sender as TabControl;
if (tabControl != null)
{
if (e.Delta < 0)
{
tabControl.Size = new Size(-2, -2);
}
else
{
tabControl.Size = new Size(+2, +2);
}
Unfortunately my code does not work.
This is now solved:
private void Form1_Load(object sender, EventArgs e)
{
foreach (Control c in this.Controls)
{
btn = c as Button;
{
if (btn == null)
continue;
c.MouseWheel += c_MouseWheel;
}
}
}
private void c_MouseWheel(object sender, MouseEventArgs e)
{
ss = sender as Button;
TabControl tabControl = sender as TabControl;
int y = ss.Size.Width;
int x = ss.Size.Height;
if (e.Delta < 0)
{
ss.Size = new Size(y+2, x+2);
}
else
{
ss.Size = new Size(y-2, x-2);
}
What I am attempting to accomplish is to move by drag and drop method a bunch of composite controls as outlined by the following image:
Currently I am able to do this by only the edge of the composite control. Is it even possible to be able to click on a child/daughter control in the composite control to be able to move the entire composite control?
Here is my code for the addition of the control to panel3 of the main form:
private void newPictureBox_Click(object sender, EventArgs e)
{
UserControl1 _UserControl = new UserControl1();
PictureBox _PictureBox = (PictureBox)sender;
string _NewControlClusterName = "_New" + _PictureBox.Name;
_UserControl.Name = _NewControlClusterName;
_UserControl.ThreadCount = 16;
_UserControl.ImageBackground = _PictureBox.BackColor;
_UserControl.Dock = DockStyle.Top;
_UserControl.AllowDrop = true;
_UserControl.Cursor = Cursors.SizeAll;
_UserControl.MouseMove += _UserControl_MouseMove;
_UserControl.DragDrop += _UserControl_DragDrop;
_UserControl.DragEnter += _UserControl_DragEnter;
string ColorName = toolTip1.GetToolTip(_PictureBox);
string ColorCode = toolTip2.GetToolTip(_PictureBox);
toolTip1.SetToolTip(_UserControl.pictureBox1, ColorName);
toolTip2.SetToolTip(_UserControl.pictureBox1, ColorCode);
toolTip2.Active = false;
_UserControl.PictureClick += new EventHandler(ClusterControl_Click);
_UserControl.TrackBarScroll += new EventHandler(GetTartanCode);
panel3.Controls.Add(_UserControl);
panel3.Controls.SetChildIndex(_UserControl, 0);
}
private void _UserControl_DragEnter(object sender, DragEventArgs e)
{
e.Effect = DragDropEffects.Move;
}
private void _UserControl_DragDrop(object sender, DragEventArgs e)
{
UserControl1 target = sender as UserControl1;
if (target != null)
{
int targetIndex = FindUserControlIndex(target);
if (targetIndex != -1)
{
string _UserControlFormat = typeof(UserControl1).FullName;
if (e.Data.GetDataPresent(_UserControlFormat))
{
UserControl1 source = e.Data.GetData(_UserControlFormat) as UserControl1;
int sourceIndex = this.FindUserControlIndex(source);
if (targetIndex != -1)
this.panel3.Controls.SetChildIndex(source, targetIndex);
}
}
}
}
private int FindUserControlIndex(UserControl1 _UserControl)
{
for (int i = 0; i < this.panel3.Controls.Count; i++)
{
UserControl1 target = this.panel3.Controls[i] as UserControl1;
if (_UserControl == target)
return i;
}
return -1;
}
private void _UserControl_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
UserControl1 _UserControl1 = sender as UserControl1;
_UserControl1.BorderStyle = BorderStyle.Fixed3D;
_UserControl1.DoDragDrop(_UserControl1, DragDropEffects.All);
}
}
private void ClusterControl_Click(object sender, EventArgs e)
{
PictureBox _PictureBox = (PictureBox)sender;
GroupBox _GroupBox = (GroupBox)_PictureBox.Parent;
UserControl1 _UserControl1 = (UserControl1)_GroupBox.Parent;
panel3.Controls.Remove(_UserControl1);
}
There is no simple way to do this.
You can try to attach your event handler to every child controls recursively.
Something like this:
public class UserControl1 : Control
{
public UserControl1()
{
// ...
ApplyChildEvents(this);
}
private void ApplyChildEvents(Control control)
{
foreach (Control subcontrol in control.Controls)
{
subcontrol.MouseMove += _UserControl_MouseMove;
subcontrol.DragDrop += _UserControl_DragDrop;
subcontrol.DragEnter += _UserControl_DragEnter;
ApplyChildEvents(subcontrol);
}
}
}
So, all controls on your UserControl1 will call this method.
Used the answer above along with the following code adjustment to the _UserControl_MouseMove event handler and it now works perfectly.
private void _UserControl_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
if (sender.GetType() == typeof(GroupBox))
{
GroupBox _GroupBox = sender as GroupBox;
UserControl1 _UserControl1 = _GroupBox.Parent as UserControl1;
_UserControl1.BorderStyle = BorderStyle.Fixed3D;
_UserControl1.DoDragDrop(_UserControl1, DragDropEffects.All);
}
else
{
UserControl1 _UserControl1 = sender as UserControl1;
_UserControl1.BorderStyle = BorderStyle.Fixed3D;
_UserControl1.DoDragDrop(_UserControl1, DragDropEffects.All);
}
}
}
And also had to change the above code to the following (since the area mentioned to follow is generated on the fly):
newPictureBox_Click
foreach (Control subcontrol in _UserControl.Controls)
{
if (subcontrol.GetType() == typeof(GroupBox)){
subcontrol.MouseMove += _UserControl_MouseMove;
subcontrol.DragDrop += _UserControl_DragDrop;
subcontrol.DragEnter += _UserControl_DragEnter;
}
}
Complete code is as follows:
private void newPictureBox_Click(object sender, EventArgs e)
{
UserControl1 _UserControl = new UserControl1();
PictureBox _PictureBox = (PictureBox)sender;
string _NewControlClusterName = "_New" + _PictureBox.Name;
_UserControl.Name = _NewControlClusterName;
_UserControl.ThreadCount = 16;
_UserControl.ImageBackground = _PictureBox.BackColor;
_UserControl.Dock = DockStyle.Top;
_UserControl.AllowDrop = true;
_UserControl.Cursor = Cursors.SizeAll;
_UserControl.MouseMove += _UserControl_MouseMove;
_UserControl.DragDrop += _UserControl_DragDrop;
_UserControl.DragEnter += _UserControl_DragEnter;
string ColorName = toolTip1.GetToolTip(_PictureBox);
string ColorCode = toolTip2.GetToolTip(_PictureBox);
toolTip1.SetToolTip(_UserControl.pictureBox1, ColorName);
toolTip2.SetToolTip(_UserControl.pictureBox1, ColorCode);
toolTip2.Active = false;
_UserControl.PictureClick += new EventHandler(ClusterControl_Click);
_UserControl.TrackBarScroll += new EventHandler(GetTartanCode);
foreach (Control subcontrol in _UserControl.Controls)
{
if (subcontrol.GetType() == typeof(GroupBox)){
subcontrol.MouseMove += _UserControl_MouseMove;
subcontrol.DragDrop += _UserControl_DragDrop;
subcontrol.DragEnter += _UserControl_DragEnter;
}
}
panel3.Controls.Add(_UserControl);
panel3.Controls.SetChildIndex(_UserControl, 0);
}
private void _UserControl_DragEnter(object sender, DragEventArgs e)
{
e.Effect = DragDropEffects.All;
}
private void _UserControl_DragDrop(object sender, DragEventArgs e)
{
UserControl1 target = sender as UserControl1;
if (target != null)
{
int targetIndex = FindUserControlIndex(target);
if (targetIndex != -1)
{
string _UserControlFormat = typeof(UserControl1).FullName;
if (e.Data.GetDataPresent(_UserControlFormat))
{
UserControl1 source = e.Data.GetData(_UserControlFormat) as UserControl1;
int sourceIndex = this.FindUserControlIndex(source);
if (targetIndex != -1)
this.panel3.Controls.SetChildIndex(source, targetIndex);
}
}
}
}
private int FindUserControlIndex(UserControl1 _UserControl)
{
for (int i = 0; i < this.panel3.Controls.Count; i++)
{
UserControl1 target = this.panel3.Controls[i] as UserControl1;
if (_UserControl == target)
return i;
}
return -1;
}
private void _UserControl_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
if (sender.GetType() == typeof(GroupBox))
{
GroupBox _GroupBox = sender as GroupBox;
UserControl1 _UserControl1 = _GroupBox.Parent as UserControl1;
_UserControl1.BorderStyle = BorderStyle.Fixed3D;
_UserControl1.DoDragDrop(_UserControl1, DragDropEffects.All);
}
else
{
UserControl1 _UserControl1 = sender as UserControl1;
_UserControl1.BorderStyle = BorderStyle.Fixed3D;
_UserControl1.DoDragDrop(_UserControl1, DragDropEffects.All);
}
}
}
Is it possible to reorder the tabs in the WinForms TabControl at run-time like IE or Firefox?
Links like this don't give me much hope.
Sure, it's possible! You're most likely trying to overcomplicate the solution. Essentially, all you have to do is subclass the standard TabControl and add some logic to the mouse event handlers. You'll just need to check which form the user is currently dragging and reorder it in the TabPages collection.
There are a couple of complete solutions available online:
Reordering TabPages inside TabControl
Drag and Drop Tab Control
Reposition TabItems at runtime
I found the solution originally posted by #Cody Gray to be mostly what I wanted, but I didn't see the need for it to be so complicated.
This is my simplification, implemented by deriving from TabControl:
public class DraggableTabControl : TabControl
{
private TabPage m_DraggedTab;
public DraggableTabControl()
{
MouseDown += OnMouseDown;
MouseMove += OnMouseMove;
}
private void OnMouseDown(object sender, MouseEventArgs e)
{
m_DraggedTab = TabAt(e.Location);
}
private void OnMouseMove(object sender, MouseEventArgs e)
{
if (e.Button != MouseButtons.Left || m_DraggedTab == null)
{
return;
}
TabPage tab = TabAt(e.Location);
if (tab == null || tab == m_DraggedTab)
{
return;
}
Swap(m_DraggedTab, tab);
SelectedTab = m_DraggedTab;
}
private TabPage TabAt(Point position)
{
int count = TabCount;
for (int i = 0; i < count; i++)
{
if (GetTabRect(i).Contains(position))
{
return TabPages[i];
}
}
return null;
}
private void Swap(TabPage a, TabPage b)
{
int i = TabPages.IndexOf(a);
int j = TabPages.IndexOf(b);
TabPages[i] = b;
TabPages[j] = a;
}
}
The drag and drop APIs are really intended for dragging stuff between separate applications, or at the very least, separate controls. Using them in this case is overkill.
Make sure you upvote Cody's answer too if you upvote mine, as it is based on his.
reordering TabPages with drag and drop - by Ludwig B.
inspired by http://dotnetrix.co.uk/tabcontrol.htm#tip7
private void tc_MouseDown(object sender, MouseEventArgs e)
{
// store clicked tab
TabControl tc = (TabControl)sender;
int hover_index = this.getHoverTabIndex(tc);
if (hover_index >= 0) { tc.Tag = tc.TabPages[hover_index]; }
}
private void tc_MouseUp(object sender, MouseEventArgs e)
{
// clear stored tab
TabControl tc = (TabControl)sender;
tc.Tag = null;
}
private void tc_MouseMove(object sender, MouseEventArgs e)
{
// mouse button down? tab was clicked?
TabControl tc = (TabControl)sender;
if ((e.Button != MouseButtons.Left) || (tc.Tag == null)) return;
TabPage clickedTab = (TabPage)tc.Tag;
int clicked_index = tc.TabPages.IndexOf(clickedTab);
// start drag n drop
tc.DoDragDrop(clickedTab, DragDropEffects.All);
}
private void tc_DragOver(object sender, DragEventArgs e)
{
TabControl tc = (TabControl)sender;
// a tab is draged?
if (e.Data.GetData(typeof(TabPage)) == null) return;
TabPage dragTab = (TabPage)e.Data.GetData(typeof(TabPage));
int dragTab_index = tc.TabPages.IndexOf(dragTab);
// hover over a tab?
int hoverTab_index = this.getHoverTabIndex(tc);
if (hoverTab_index < 0) { e.Effect = DragDropEffects.None; return; }
TabPage hoverTab = tc.TabPages[hoverTab_index];
e.Effect = DragDropEffects.Move;
// start of drag?
if (dragTab == hoverTab) return;
// swap dragTab & hoverTab - avoids toggeling
Rectangle dragTabRect = tc.GetTabRect(dragTab_index);
Rectangle hoverTabRect = tc.GetTabRect(hoverTab_index);
if (dragTabRect.Width < hoverTabRect.Width)
{
Point tcLocation = tc.PointToScreen(tc.Location);
if (dragTab_index < hoverTab_index)
{
if ((e.X - tcLocation.X) > ((hoverTabRect.X + hoverTabRect.Width) - dragTabRect.Width))
this.swapTabPages(tc, dragTab, hoverTab);
}
else if (dragTab_index > hoverTab_index)
{
if ((e.X - tcLocation.X) < (hoverTabRect.X + dragTabRect.Width))
this.swapTabPages(tc, dragTab, hoverTab);
}
}
else this.swapTabPages(tc, dragTab, hoverTab);
// select new pos of dragTab
tc.SelectedIndex = tc.TabPages.IndexOf(dragTab);
}
private int getHoverTabIndex(TabControl tc)
{
for (int i = 0; i < tc.TabPages.Count; i++)
{
if (tc.GetTabRect(i).Contains(tc.PointToClient(Cursor.Position)))
return i;
}
return -1;
}
private void swapTabPages(TabControl tc, TabPage src, TabPage dst)
{
int index_src = tc.TabPages.IndexOf(src);
int index_dst = tc.TabPages.IndexOf(dst);
tc.TabPages[index_dst] = src;
tc.TabPages[index_src] = dst;
tc.Refresh();
}
I extended the answer of Jacob Stanley a bit. This way the swapping won't occur too often. This is especially helpful for tabs of different sizes in which case the previous solution would swap very often while dragging.
The difference in user experience is that you have to drag a bit further to actually move the tab. But this is similar to tab reordering in browsers.
Also I added a hand cursor while dragging and enabled double-buffering.
using System;
using System.Drawing;
using System.Windows.Forms;
namespace Controls
{
public class DraggableTabControl : TabControl
{
private TabPage draggedTab;
public DraggableTabControl()
{
this.MouseDown += OnMouseDown;
this.MouseMove += OnMouseMove;
this.Leave += new System.EventHandler(this.DraggableTabControl_Leave);
this.SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint, true);
}
private void OnMouseDown(object sender, MouseEventArgs e)
{
draggedTab = TabAt(e.Location);
}
private void OnMouseMove(object sender, MouseEventArgs e)
{
if (e.Button != MouseButtons.Left || draggedTab == null)
{
this.Cursor = this.DefaultCursor;
draggedTab = null;
return;
}
int index = TabPages.IndexOf(draggedTab);
int nextIndex = index + 1;
int prevIndex = index - 1;
int minXForNext = int.MaxValue;
int maxXForPrev = int.MinValue;
var tabRect = GetTabRect(index);
if (nextIndex < TabPages.Count)
{
var nextTabRect = GetTabRect(nextIndex);
minXForNext = tabRect.Left + nextTabRect.Width;
}
if (prevIndex >= 0)
{
var prevTabRect = GetTabRect(prevIndex);
maxXForPrev = prevTabRect.Left + tabRect.Width;
}
this.Cursor = Cursors.Hand;
if (e.Location.X > maxXForPrev && e.Location.X < minXForNext)
{
return;
}
TabPage tab = TabAt(e.Location);
if (tab == null || tab == draggedTab)
{
return;
}
Swap(draggedTab, tab);
SelectedTab = draggedTab;
}
private TabPage TabAt(Point position)
{
int count = TabCount;
for (int i = 0; i < count; i++)
{
if (GetTabRect(i).Contains(position))
{
return TabPages[i];
}
}
return null;
}
private void Swap(TabPage a, TabPage b)
{
int i = TabPages.IndexOf(a);
int j = TabPages.IndexOf(b);
TabPages[i] = b;
TabPages[j] = a;
}
private void DraggableTabControl_Leave(object sender, EventArgs e)
{
this.Cursor = this.DefaultCursor;
draggedTab = null;
}
}
}