How can I perform a DragDrop/swap of a ChartControl? - c#

I'm trying to allow my users to swap two DevExpress chart controls (although I believe pretty much any control should work...), by dragging one over the top of the other. I have done this for my TabControl (to allow swapping/moving of tabs), but for some reason I appear to be missing something here which is stopping me doing the same with my ChartControl.
It "should" draw a grey-ish box over the chartcontrol and allow the user to drag it to wherever they like, but I just get a black circle with a stripe through it.
Here is the code I have written so far, hopefully one of you will be able to spot the mistake and I can stop pulling my hair out! :)
private void ChartControlMouseMove(object sender, MouseEventArgs e)
{
// Handle Mouse move only if left button is pressed.
if (e.Button == MouseButtons.Left)
{
var chartControl = (ChartControl)sender;
// If the mouse moves outside the rectangle, start the drag.
if (!rectDragBoxFromMouseDown.Equals(Rectangle.Empty)
& !rectDragBoxFromMouseDown.Contains(e.X, e.Y))
{
Invalidate();
DoDragDrop(chartControl, DragDropEffects.Move);
CalcRectDragBox(e.X, e.Y);
Invalidate();
}
}
}
private void ChartControlMouseDown(object sender, MouseEventArgs e)
{
CalcRectDragBox(e.X, e.Y);
}
private 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.
var dragSize = SystemInformation.DragSize;
// Create a rectangle using the DragSize, with the mouse position being
// at the center of the rectangle.
rectDragBoxFromMouseDown = new Rectangle(
new Point(x - (dragSize.Width/2), y - (dragSize.Height/2)), dragSize);
}
private void ChartControlDragOver(object sender, DragEventArgs e)
{
var chartControl = (ChartControl)sender;
// get the control we are hovering over.
var hitInformation = chartControl.CalcHitInfo(chartControl.PointToClient(new Point(e.X, e.Y)));
if ((hitInformation != null))
{
//ChartHitInfo hoverTab = hitInformation;
if (e.Data.GetDataPresent(typeof(ChartControl)))
{
e.Effect = DragDropEffects.Move;
var dragTab = (ChartControl)e.Data.GetData(typeof(ChartControl));
if (dragTab != chartControl)
{
for (int i = 0; i < layoutControlGroupDashboard.Items.Count; i++)
{
var layoutControlItem = layoutControlGroupDashboard.Items[i] as LayoutControlItem;
if (layoutControlItem != null && layoutControlItem.Control == chartControl)
{
for (int j = 0; j < layoutControlGroupDashboard.Items.Count; j++)
{
var controlItem = layoutControlGroupDashboard.Items[j] as LayoutControlItem;
if (controlItem != null && controlItem.Control == dragTab)
{
if (!_ignoreNextDrag)
{
_ignoreNextDrag = true;
layoutControlGroupDashboard.BeginInit();
var layoutControlItemi = layoutControlGroupDashboard.Items[i] as LayoutControlItem;
if (layoutControlItemi != null)
{
Control tempControlI =
layoutControlItemi.Control;
var layoutControlItemj = layoutControlGroupDashboard.Items[j] as LayoutControlItem;
if (layoutControlItemj != null)
{
layoutControlItemi.BeginInit();
layoutControlItemj.BeginInit();
Control tempControlJ =
layoutControlItemj.Control;
layoutControlItemi.Control =
null;
layoutControlItemj.Control =
null;
layoutControlItemi.Control =
tempControlJ;
layoutControlItemj.Control =
tempControlI;
layoutControlItemi.EndInit();
layoutControlItemj.EndInit();
}
}
layoutControlGroupDashboard.EndInit();
break;
}
else
{
_ignoreNextDrag = false;
break;
}
}
}
}
}
}
}
}
else
{
e.Effect = DragDropEffects.None;
}
}
Again, the idea is to allow the user to swap the controls around just click click-dragging things around... Hopefully it's just something simple I'm missing, but I can't see it for the life of me!
Edit: This is something I tried (adding my chart to a panel first...)
Panel panel = new Panel();
panel.Name = Guid.NewGuid().ToString();
panel.Controls.Add(chartControl);
var dashboardItem = new LayoutControlItem(layoutControlDashboard, panel)
{
Padding = new DevExpress.XtraLayout.Utils.Padding(0),
Spacing = new DevExpress.XtraLayout.Utils.Padding(0),
SizeConstraintsType = SizeConstraintsType.Custom
};

Here is the modified ChartControlDragOver method which work in case the ChartControl is placed in the LayoutControl:
private void ChartControlDragOver(object sender, DragEventArgs e) {
var chartControl = (ChartControl)sender;
// get the control we are hovering over.
var hitInformation = chartControl.CalcHitInfo(chartControl.PointToClient(new Point(e.X, e.Y)));
if ((hitInformation != null)) {
//ChartHitInfo hoverTab = hitInformation;
if (e.Data.GetDataPresent(typeof(ChartControl))) {
e.Effect = DragDropEffects.Move;
var dragTab = (ChartControl)e.Data.GetData(typeof(ChartControl));
if (dragTab == chartControl) return;
InsertType insertType = InsertType.Left;
Point hitPoint = chartControl.Parent.PointToClient(new Point(e.X, e.Y));
if (dragTab.Bounds.Left < hitPoint.X && dragTab.Bounds.Right > hitPoint.X) {
if (dragTab.Bounds.Top > hitPoint.Y)
insertType = InsertType.Top;
else if (dragTab.Bounds.Bottom < hitPoint.Y)
insertType = InsertType.Bottom;
} else if (dragTab.Bounds.Right < hitPoint.X)
insertType = InsertType.Right;
else if (dragTab.Bounds.Left > hitPoint.X)
insertType = InsertType.Left;
LayoutControl layout = (LayoutControl)chartControl.Parent;
layout.GetItemByControl(dragTab).Move(layout.GetItemByControl(chartControl), insertType);
}
} else {
e.Effect = DragDropEffects.None;
}
}

Related

Dragging and dropping between two wpf controls - I need the mouse icon to change depending on location inside 2nd control

I have two WPF controls. One is a TreeView and the other is a graph control.
I have dragging and dropping working between them. When I drag from the TreeView control to the graph control and drop something it works as I want it to. The mouse cursor has the dragging drop look to it during this. However I want to change the mouse cursor (to something that points up) if the user points the mouse to the top half of the graph control. If the user goes to the bottom of the graph control then I want the cursor to go back to the original dragging drop look.
I thought I could use the GiveFeedback event with the 1st control but that doesn't return the graph object to me.
I can provide code if needed but I don't think it would be helpful. I do have a method called MouseNearTop(Graph g, DragEventArgs e) that returns a bool true if the mouse is in the top half of the grid ad false if on the bottom half.
UPDATE:
I tried using the Mouse.OverrideCursor property but that seems to change the mouse after you release the button. I tried again using the static class DragDrop but that throws exceptions and still doesn't work.
This is for the code for my second attempt:
namespace WpfApplication11
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private bool pointingUp = true;
private void Rectangle_DragOver(object sender, DragEventArgs e)
{
currentPoint = e.GetPosition(MyRectangle);
if ((currentPoint.X > 0) && (currentPoint.X < MyRectangle.ActualWidth) && (currentPoint.Y > 0) && (currentPoint.Y < (MyRectangle.ActualHeight / 2)))
{
if (!pointingUp)
{
//Mouse.OverrideCursor = Cursors.UpArrow;'
try
{
DragDrop.DoDragDrop(MyRectangle1, MyRectangle1, DragDropEffects.Copy);
}
catch
{
}
pointingUp = true;
}
}
else
{
if (pointingUp)
{
//Mouse.OverrideCursor = null;
try
{
DragDrop.DoDragDrop(MyRectangle1, MyRectangle1, DragDropEffects.Move);
}
catch
{
}
pointingUp = false;
}
}
}
Point currentPoint = new Point();
private void MyRectangle1_MouseMove(object sender, MouseEventArgs e)
{
System.Media.SystemSounds.Beep.Play();
if (e.LeftButton == MouseButtonState.Pressed)
{
//if (FileTree.SelectedItem == null)
//{
// return;
//}
//var node = FileTree.SelectedItem as TreeViewNode;
// && (node.TableName.Equals("Ttmp", StringComparison.InvariantCultureIgnoreCase))
//if ((node.Items.Count == 0) && !(node.TableName == "Temp" || node.NodeDisplayName.EqualsAtLeastOne(StringComparison.InvariantCultureIgnoreCase, "DiFR", "DSFR")))
//{
var mousePos = e.GetPosition(null);
var diff = _startPoint - mousePos;
if (Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance
|| Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance)
{
DragDrop.DoDragDrop(MyRectangle1, MyRectangle1, DragDropEffects.Move);
}
//}
}
}
private Point _startPoint;
private void MyRectangle1_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
_startPoint = e.GetPosition(null);
}
private void MyRectangle_GiveFeedback(object sender, GiveFeedbackEventArgs e)
{
//Point p = e.GetPosition(MyRectangle);
}
private void MyRectangle_Drop(object sender, DragEventArgs e)
{
e.Effects = DragDropEffects.None;
}
}
}
I've used a Rectangle for demo purposes, but I think this should work OK with whatever graph you're using.
private void MyRectangle_DragOver(object sender, DragEventArgs e)
{
Point p = e.GetPosition(MyRectangle);
if ((p.X > 0) && (p.X < MyRectangle.ActualWidth) && (p.Y > 0) && (p.Y < (MyRectangle.ActualHeight / 2)))
{
Mouse.OverrideCursor = Cursors.UpArrow;
}
else
{
Mouse.OverrideCursor = null;
}
}
I figured a solution.
In the object I am dragging the data into I have this method:
private void ObjectDraggingInto_DragOver(object sender, DragEventArgs e)
{
if (ObjectDraggingFrom.DragDroppingOn)
{
ObjectDraggingFrom.MoveUpCursor = MouseNearTop(sender, e)
? true
: false;
}
}
Then here is the code from the object that I started dragging from:
public static bool MoveUpCursor = false;
public static bool DragDroppingOn = false;
private void ObjectDraggingFrom_GiveFeedback(object sender, GiveFeedbackEventArgs e)
{
if (MoveUpCursor)
{
e.UseDefaultCursors = false;
Mouse.SetCursor(Cursors.UpArrow);
}
else
{
e.UseDefaultCursors = true;
}
e.Handled = true;
}
private void ObjectDragging_Drop(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
((DataExploreViewModel)DataContext).ImportDraggedAndDroppedFiles((string[])e.Data.GetData(DataFormats.FileDrop));
}
DragDroppingOn = false;
}
private void ObjectDraggingFrom_MouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
if (myData.SelectedItem == null)
{
return;
}
var mousePos = e.GetPosition(null);
var diff = _startPoint - mousePos;
if (Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance
|| Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance)
{
DragDroppingOn = true;
DragDrop.DoDragDrop(FileTree, data, DragDropEffects.Move | DragDropEffects.Copy);
}
}
}
So this will change the mouse drag cursor to a up arrow when you are in the upper half of the object you are dragging to. Otherwise the cursor will look like the normal drag cursor.

C# drag controls around a panel

i am developing a system which allow user to drag objects around within a same panel, i went through some research and founds that i should use mouse events like mouse_up, mouse_down and mouse_move.
The the program will generate 3 picturebox and allow the user to drag around the every picturebox within the panel, but the program i code did not work perfectly as when i drag over a picturebox, the picturebox will move, but not according to my mouse cursor location, it is somewhere else, besides, when dragging, there is picturebox shadows in the panel, i've tried those update(),refresh(), and invalidate() but it seems not useful for me. Below are my codes, thanks for helping
public partial class Form1 : Form
{
List<PictureBox> pictureBoxList = new List<PictureBox>();
private bool isDragging = false;
public Form1()
{
InitializeComponent();
for (int i = 0; i < 3; i++)
{
PictureBox picture = new PictureBox
{
Name = "pictureBox" + i,
Size = new Size(20, 20),
Location = new Point(i * 40, i * 40),
BorderStyle = BorderStyle.FixedSingle,
SizeMode = PictureBoxSizeMode.Zoom,
ImageLocation = "A.jpg"
};
pictureBoxList.Add(picture);
foreach (PictureBox p in pictureBoxList)
{
p.MouseDown += new MouseEventHandler(c_MouseDown);
p.MouseMove += new MouseEventHandler(c_MouseMove);
p.MouseUp += new MouseEventHandler(c_MouseUp);
pnlDisplayImage.Controls.Add(p);
pnlDisplayImage.Refresh();
}
}
}
void c_MouseDown(object sender, MouseEventArgs e)
{
isDragging = true;
}
void c_MouseMove(object sender, MouseEventArgs e)
{
if (isDragging == true) {
Control c = sender as Control;
for (int i = 0; i < pictureBoxList.Count(); i++)
{
if (c.Equals(pictureBoxList[i]))
{
pictureBoxList[i].Location = new Point(e.X, e.Y);
}
}
}
}
void c_MouseUp(object sender, MouseEventArgs e)
{
PictureBox c = sender as PictureBox;
isDragging = false;
for (int i = 0; i < pictureBoxList.Count(); i++) {
if (c.Equals(pictureBoxList[i])){
pictureBoxList[i].Location = new Point(e.X, e.Y);
}
}
}
private void pnlDisplayImage_Paint(object sender, PaintEventArgs e)
{
foreach (PictureBox p in pictureBoxList)
{
pnlDisplayImage.Controls.Add(p);
}
}
}
Finally I've found what are the problems that caused my program not running as my expectations. The main problem is that I accidentally put the foreach loop inside the for loop which I used to create pictureBox, this problem caused the pictureBox comes out some shadows effect while dragging during run time due to there are few same pictureBox. Also, I have change a little bit of the codes and it now run as what I expected. Below are the code that I want for answer.
public partial class Form1 : Form
{
List<PictureBox> pictureBoxList = new List<PictureBox>();
private bool isDragging = false;
Point move;
public Form1()
{
InitializeComponent();
for (int i = 0; i < 3; i++)
{
PictureBox picture = new PictureBox
{
Name = "pictureBox" + i,
Size = new Size(20, 20),
Location = new Point(i * 40, i * 40),
BorderStyle = BorderStyle.FixedSingle,
SizeMode = PictureBoxSizeMode.Zoom,
ImageLocation = "A.jpg"
};
pictureBoxList.Add(picture);
}
foreach (PictureBox p in pictureBoxList)
{
p.MouseDown += new MouseEventHandler(c_MouseDown);
p.MouseMove += new MouseEventHandler(c_MouseMove);
p.MouseUp += new MouseEventHandler(c_MouseUp);
pnlDisplayImage.Controls.Add(p);
pnlDisplayImage.Refresh();
}
}
void c_MouseDown(object sender, MouseEventArgs e)
{
Control c = sender as Control;
isDragging = true;
move = e.Location;
}
void c_MouseMove(object sender, MouseEventArgs e)
{
if (isDragging == true) {
Control c = sender as Control;
for (int i = 0; i < pictureBoxList.Count(); i++)
{
if (c.Equals(pictureBoxList[i]))
{
pictureBoxList[i].Left += e.X - move.X;
pictureBoxList[i].Top += e.Y - move.Y;
}
}
}
}
void c_MouseUp(object sender, MouseEventArgs e)
{
isDragging = false;
}
}
Try something like (it's custom control with overrides, but should be easy to convert to events):
private bool _isMoved = false; // true if move mode on
private Point _pointMove = new Point(0); // for moving
protected override void OnMouseDown(MouseEventArgs e)
{
// if left button pressed
if(e.Button == MouseButtons.Left)
{
_pointMove.X = e.X;
_pointMove.Y = e.Y;
_isMoved = true;
Cursor = Cursors.SizeAll;
Capture = true;
}
base.OnMouseDown (e);
}
protected override void OnMouseUp(MouseEventArgs e)
{
// if move mode on
if(_isMoved)
{
_isMoved = false;
Cursor = Cursors.Default;
Capture = false;
}
base.OnMouseUp (e);
}
protected override void OnMouseMove(MouseEventArgs e)
{
// if move mode on
if (_isMoved)
{
Left += e.X - _pointMove.X;
Top += e.Y - _pointMove.Y;
}
base.OnMouseMove (e);
}

Drag and Drop between xtragrid and Scheduler

someone can tell me how to drag and drop data between gridcontrol and schedulercontrol from devexpress? I want to drag the data from the grid and drop it on the scheduler. The devexpress example in the demo doesnt work for me. I just get a block-symbol.
regards
I got it.
private void grdGrid_MouseDown(object sender, MouseEventArgs e)
{
posImGrid = null;
GridHitInfo hitInfo = grvView.CalcHitInfo(new Point(e.X, e.Y));
if (Control.ModifierKeys != Keys.None)
{
return;
}
if ((e.Button == System.Windows.Forms.MouseButtons.Left) &&
(hitInfo.InRow) &&
(hitInfo.HitTest != GridHitTest.RowIndicator))
{
posImGrid = hitInfo;
}
}
private void grdGrid_MouseMove(object sender, MouseEventArgs e)
{
if ((e.Button == System.Windows.Forms.MouseButtons.Left) &&
(posImGrid != null))
{
Size dragSize = SystemInformation.DragSize;
Rectangle dragRect = new Rectangle(new Point(posImGrid.HitPoint.X - dragSize.Width / 2,
posImGrid.HitPoint.Y - dragSize.Height / 2), dragSize);
if (!dragRect.Contains(new Point(e.X, e.Y)))
{
grvView.GridControl.DoDragDrop(GetDragData(grvView), DragDropEffects.All);
posImGrid = null;
}
}
}
private SchedulerDragData GetDragData(GridView view)
{
Appointment termin = Storage.CreateAppointment(AppointmentType.Normal);
clsMeineKlasse tempObjekt = (clsMeineKlasse)grvView.GetFocusedRow();
termin.Description = tempObjekt.Beschreibung;
termin.Subject = tempObjekt.Bezeichnung;
termin.Duration = TimeSpan.FromHours(8);
SchedulerDragData sdd = new SchedulerDragData(termin);
return sdd;
}

see values of chart points when the mouse is on points

I have a chart and I want the user to see the values when the pointer is on the points.
By using digEmAll's help in the page finding the value of the points in a chart ,I could write the following code:
Point? prevPosition = null;
ToolTip tooltip = new ToolTip();
void chart1_MouseMove(object sender, MouseEventArgs e)
{
var pos = e.Location;
if (prevPosition.HasValue && pos == prevPosition.Value)
return;
tooltip.RemoveAll();
prevPosition = pos;
var results = chart1.HitTest(pos.X, pos.Y, false, ChartElementType.PlottingArea);
foreach (var result in results)
{
if (result.ChartElementType == ChartElementType.PlottingArea)
{
chart1.Series[0].ToolTip = "X=#VALX, Y=#VALY";
}
}
}
by the above code,the user can see the values when the pointer is near to a series.But now How can I let the user to see the values only when the pointer is on the points?
I replaced
int k = result.PointIndex;
if (k >= 0)
{
chart1.Series[0].Points[k].ToolTip = "X=#VALX, Y=#VALY";
}
instead of
chart1.Series[0].ToolTip = "X=#VALX, Y=#VALY";
to solve my problem.But It wasn't usefull.
You should modify the code in this way:
Point? prevPosition = null;
ToolTip tooltip = new ToolTip();
void chart1_MouseMove(object sender, MouseEventArgs e)
{
var pos = e.Location;
if (prevPosition.HasValue && pos == prevPosition.Value)
return;
tooltip.RemoveAll();
prevPosition = pos;
var results = chart1.HitTest(pos.X, pos.Y, false,
ChartElementType.DataPoint);
foreach (var result in results)
{
if (result.ChartElementType == ChartElementType.DataPoint)
{
var prop = result.Object as DataPoint;
if (prop != null)
{
var pointXPixel = result.ChartArea.AxisX.ValueToPixelPosition(prop.XValue);
var pointYPixel = result.ChartArea.AxisY.ValueToPixelPosition(prop.YValues[0]);
// check if the cursor is really close to the point (2 pixels around the point)
if (Math.Abs(pos.X - pointXPixel) < 2 &&
Math.Abs(pos.Y - pointYPixel) < 2)
{
tooltip.Show("X=" + prop.XValue + ", Y=" + prop.YValues[0], this.chart1,
pos.X, pos.Y - 15);
}
}
}
}
}
The idea is to check if the mouse is very close to the point e.g. 2 pixels around it (because is really unlikely to be exactly on the point) and show the tooltip in that case.
Here's a complete working example.
I would take this solution:
Add custom tooltip event handler:
this.chart1.GetToolTipText += this.chart1_GetToolTipText;
Implement event handler:
private void chart1_GetToolTipText(object sender, ToolTipEventArgs e)
{
// Check selected chart element and set tooltip text for it
switch (e.HitTestResult.ChartElementType)
{
case ChartElementType.DataPoint:
var dataPoint = e.HitTestResult.Series.Points[e.HitTestResult.PointIndex];
e.Text = string.Format("X:\t{0}\nY:\t{1}", dataPoint.XValue, dataPoint.YValues[0]);
break;
}
}
Consider the following as a possible better option than tooltips...use the label feature of the chart control.
DataPoint _prevPoint;
void chart1_MouseMove(object sender, MouseEventArgs e)
{
// this if statement clears the values from the previously activated point.
if (_prevPoint) {
_prevPoint.MarkerStyle = MarkerStyle.None;
_prevPoint.IsValueShownAsLabel = false;
}
var result = chart1.HitTest(e.X, e.Y, ChartElementType.DataPoint);
if (result.ChartElementType == ChartElementType.DataPoint)
{
var prop = result.Object as DataPoint;
if (prop != null)
{
prop.IsValueShownAsLabel = true;
prop.MarkerStyle = MarkerStyle.Star4;
}
}
}
I've tested this and i'm using it currently. It's very nice on charts with a lot of points since it shows the marker on the chart as well.

Is it possible to make the WinForms Tab Control be able to do tab reordering like IE or Firefox?

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;
}
}
}

Categories

Resources