In the creation of some apps, a list-box with multiple lined content is preferred. Since listboxes have no such function, the creation of a custom control is needed. For this case, I'm working on a compiler app that the user can load an import and export C# prefab into the program to manipulate data. To see this compiler in action, you can check out my previous post here. For this instance, I want a debug log of any errors to be outputted into the listbox. Since some errors contain multiple lines, some of which are rather long, I read up and generated a Listbox of Textbox items.
The most current copy of this can be found on pastebin.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace DataStripper
{
public partial class MultiLineListView : System.Windows.Forms.ListBox
{
public MultiLineListView()
{
//InitializeComponent();
this.DrawMode = DrawMode.OwnerDrawVariable;
this.ScrollAlwaysVisible = true;
tbox.Hide();
tbox.mllb = this;
Controls.Add(tbox);
}
protected override void OnMeasureItem(MeasureItemEventArgs e)
{
if (Site != null)
return;
if (e.Index > -1)
{
string s = Items[e.Index].ToString();
float best = 0;
foreach (string line in s.Split(new string[] { Environment.NewLine }, StringSplitOptions.None))
{
float chk = e.Graphics.MeasureString(line, Font, Width).Width;
if (chk > best)
best = chk;
}
SizeF sf = e.Graphics.MeasureString(s, Font, Width);
int htex = 1;//(e.Index == 0) ? 15 : 10;
e.ItemHeight = (int)(sf.Height*Items.Count) + htex;
e.ItemWidth = (int)best;
/*NTextBox i = (NTextBox)Items[e.Index];
e.ItemHeight = i.Height;
e.ItemWidth = i.Width;*/
}
}
protected override void OnDrawItem(DrawItemEventArgs e)
{
if (Site != null)
return;
if (e.Index > -1)
{
string s = Items[e.Index].ToString();
if ((e.State & DrawItemState.Focus) == 0)
{
e.Graphics.FillRectangle(new SolidBrush(SystemColors.Window), e.Bounds);
e.Graphics.DrawString(s, Font, new SolidBrush(SystemColors.WindowText),
e.Bounds);
e.Graphics.DrawRectangle(new Pen(SystemColors.Highlight), e.Bounds);
}
else
{
e.Graphics.FillRectangle(new SolidBrush(SystemColors.Highlight), e.Bounds);
e.Graphics.DrawString(s, Font, new SolidBrush(SystemColors.HighlightText),
e.Bounds);
}
}
}
protected override void OnMouseUp(System.Windows.Forms.MouseEventArgs e)
{
int index = IndexFromPoint(e.X, e.Y);
if (index != ListBox.NoMatches &&
index != 65535)
{
/*if (e.Button == MouseButtons.Right)
{
SelectedIndex = index;
Focus();
//tbox.index = index;
}*/
/*if (e.Button == MouseButtons.Right)
{
string s = Items[index].ToString();
Rectangle rect = GetItemRectangle(index);
tbox.Location = new Point(rect.X, rect.Y);
tbox.Size = new Size(rect.Width, rect.Height);
tbox.Text = s;
tbox.index = index;
tbox.SelectAll();
tbox.Show();
tbox.Focus();
}*/
}
base.OnMouseUp(e);
}
NTextBox tbox = new NTextBox();
class NTextBox : TextBox
{
public MultiLineListView mllb;
public int index = -1;
bool errshown = false;
bool brementer = false;
public NTextBox()
{
Multiline = true;
MaxLength = 2147483647;
MaximumSize = new System.Drawing.Size(0, 0);
WordWrap = false;
ScrollBars = ScrollBars.Both;
AcceptsReturn = true;
AcceptsTab = true;
}
protected override void OnKeyUp(KeyEventArgs e)
{
if (brementer)
{
Text = "";
brementer = false;
}
base.OnKeyUp(e);
}
protected override void OnKeyPress(KeyPressEventArgs e)
{
base.OnKeyPress(e);
}
protected override void OnLostFocus(System.EventArgs e)
{
if (Text.Trim() == "")
{
if (!errshown)
{
MessageBox.Show(
"Cannot enter NULL string as item!",
"Fatal error!", MessageBoxButtons.OK,
MessageBoxIcon.Error);
}
errshown = false;
}
else
{
errshown = false;
mllb.Items[index] = Text;
Hide();
}
base.OnLostFocus(e);
}
}
protected override void OnKeyDown(KeyEventArgs e)
{
if (e.KeyData == Keys.F2)
{
int index = SelectedIndex;
if (index == ListBox.NoMatches ||
index == 65535)
{
if (Items.Count > 0)
index = 0;
}
if (index != ListBox.NoMatches &&
index != 65535)
{
string s = Items[index].ToString();
Rectangle rect = GetItemRectangle(index);
tbox.Location = new Point(rect.X, rect.Y);
tbox.Size = new Size(rect.Width, rect.Height);
tbox.Text = s;
tbox.index = index;
tbox.SelectAll();
tbox.Show();
tbox.Focus();
}
}
base.OnKeyDown(e);
}
}
}
The difficulty I'm having, is that, even though I've set the textbox as it should be, the list view item still seems to be limiting content to TextWrap, and a maximum of 7.5 lines.
Image Reference http://imageshack.us/a/img819/9345/5nh4.png
On line 32 foreach (string line in s.Split(new string[] { Environment.NewLine }, StringSplitOptions.None)) I try to find the length of the longest line of text in the string to return in the OnMeasureItem override, but it refuses to go in excess of the assumed limit. Any help would be greatly appreciated.
You should take a look at a control called GlacialList. you can do such thing easily. Latest versions are not free but i used to use version 1.3. If you get to it there is a property that allow you to put ANY control inside a list cell. just add multiline textbox and your good to go
It may not be an option for you but if you want more Advanced UI features why not just switch to WPF where you get this built-in?
Related
In the designer in the listBox1 properties i set the DrawMode to OwnerDrawFixed
Then added this class for the coloring :
public class MyListBoxItem
{
public MyListBoxItem(Color c, string m)
{
ItemColor = c;
Message = m;
}
public Color ItemColor { get; set; }
public string Message { get; set; }
}
and added event of the DrawItem :
private void listBox1_DrawItem(object sender, DrawItemEventArgs e)
{
MyListBoxItem item = listBox1.Items[e.Index] as MyListBoxItem; // Get the current item and cast it to MyListBoxItem
if (item != null)
{
e.Graphics.DrawString( // Draw the appropriate text in the ListBox
item.Message, // The message linked to the item
listBox1.Font, // Take the font from the listbox
new SolidBrush(item.ItemColor), // Set the color
0, // X pixel coordinate
e.Index * listBox1.ItemHeight // Y pixel coordinate. Multiply the index by the ItemHeight defined in the listbox.
);
}
else
{
// The item isn't a MyListBoxItem, do something about it
}
}
then i'm trying to add some items just for testing in the constructor :
each item should be in another color :
listBox1.Items.Add(new MyListBoxItem(Colors.Green, "Validated data successfully"));
listBox1.Items.Add(new MyListBoxItem(Colors.Red, "Failed to validate data"));
but getting exception on the first listBox1.Items.Add line :
System.ArgumentException: 'Items collection cannot be modified when the DataSource property is set.'
this is the full code :
using Newtonsoft.Json;
using Ookii.Dialogs.WinForms;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using static System.Windows.Forms.VisualStyles.VisualStyleElement;
using static System.Windows.Forms.VisualStyles.VisualStyleElement.ProgressBar;
using static System.Windows.Forms.VisualStyles.VisualStyleElement.Window;
namespace Image_Crop
{
public partial class Form1 : Form
{
Rectangle rect;
int pixelsCounter = 0;
Color SelectedColor = Color.LightGreen;
List<DrawingRectangle> DrawingRects = new List<DrawingRectangle>();
Bitmap rectImage;
int saveRectanglesCounter = 1;
bool drawBorder = true;
bool clearRectangles = true;
bool saveRectangles = true;
string rectangleName;
Dictionary<string, string> FileList = new Dictionary<string, string>();
string selectedPath;
int x, y;
private bool crop = false;
public Form1()
{
InitializeComponent();
textBox1.Text = Properties.Settings.Default.ImageToCropFolder;
textBox2.Text = Properties.Settings.Default.CroppedImagesFolder;
selectedPath = textBox2.Text;
if (textBox1.Text != "")
{
Bitmap bmp = new Bitmap(Image.FromFile(textBox1.Text),
pictureBox2.Width, pictureBox2.Height);
pictureBox2.Image = bmp;
}
checkBoxDrawBorder.Checked = true;
checkBoxClearRectangles.Checked = true;
checkBoxSaveRectangles.Checked = true;
if (selectedPath != "" && selectedPath != null)
{
if (System.IO.File.Exists(Path.Combine(selectedPath, "rectangles.txt")))
{
string g = System.IO.File.ReadAllText(Path.Combine(selectedPath, "rectangles.txt"));
g = g.Remove(0, 32);
FileList = JsonConvert.DeserializeObject<Dictionary<string, string>>(g);
listBox1.DataSource = FileList.Keys.ToList();
label2.Text = listBox1.Items.Count.ToString();
listBox1.SelectedIndex = listBox1.Items.Count - 1;
}
else
{
label2.Text = "0";
}
}
else
{
label2.Text = "0";
}
if ((selectedPath != "" && selectedPath != null) && textBox1.Text != "")
{
crop = true;
}
else
{
crop = false;
}
listBox1.Items.Add(new MyListBoxItem(Color.Green, "Validated data successfully"));
listBox1.Items.Add(new MyListBoxItem(Color.Red, "Failed to validate data"));
}
public class DrawingRectangle
{
public Rectangle Rect => new Rectangle(Location, Size);
public Size Size { get; set; }
public Point Location { get; set; }
public Control Owner { get; set; }
public Point StartPosition { get; set; }
public Color DrawingcColor { get; set; } = Color.LightGreen;
public float PenSize { get; set; } = 3f;
}
private void Form1_Load(object sender, EventArgs e)
{
}
private void pictureBox2_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button != MouseButtons.Left || crop == false) return;
x = 0;
y = 0;
if (pictureBox2.Image != null && selectedPath != null)
{
if ((x >= 0 && x <= pictureBox2.Image.Size.Width) && (y >= 0 && y <= pictureBox2.Image.Size.Height))
{
DrawingRects.Add(new DrawingRectangle()
{
Location = e.Location,
Size = Size.Empty,
StartPosition = e.Location,
Owner = (Control)sender,
DrawingcColor = SelectedColor
});
}
}
}
private void pictureBox2_MouseMove(object sender, MouseEventArgs e)
{
int X = e.X;
int Y = e.Y;
if (e.Button != MouseButtons.Left || crop == false) return;
if ((X >= 0 && X <= pictureBox2.Width) && (Y >= 0 && Y <= pictureBox2.Height))
{
if (pictureBox2.Image != null && selectedPath != null && DrawingRects.Count > 0)
{
if ((x >= 0 && x <= pictureBox2.Image.Size.Width) && (y >= 0 && y <= pictureBox2.Image.Size.Height))
{
x = e.X;
y = e.Y;
var dr = DrawingRects[DrawingRects.Count - 1];
if (e.Y < dr.StartPosition.Y) { dr.Location = new Point(dr.Rect.Location.X, e.Y); }
if (e.X < dr.StartPosition.X) { dr.Location = new Point(e.X, dr.Rect.Location.Y); }
dr.Size = new Size(Math.Abs(dr.StartPosition.X - e.X), Math.Abs(dr.StartPosition.Y - e.Y));
pictureBox2.Invalidate();
}
}
}
}
int count = 0;
private void pictureBox2_MouseUp(object sender, MouseEventArgs e)
{
if (e.Button != MouseButtons.Left || crop == false) return;
if (DrawingRects.Count > 0 && pictureBox2.Image != null && selectedPath != "")
{
if ((x >= 0 && x <= pictureBox2.Image.Size.Width) && (y >= 0 && y <= pictureBox2.Image.Size.Height))
{
var dr = DrawingRects.Last();
if (dr.Rect.Width > 0 && dr.Rect.Height > 0)
{
rectImage = cropAtRect((Bitmap)pictureBox2.Image, dr.Rect);
if (saveRectangles)
{
count++;
rectangleName = GetNextName(Path.Combine(selectedPath, "Rectangle"), ".bmp");
FileList.Add($"{dr.Location}, {dr.Size}", rectangleName);
string json = JsonConvert.SerializeObject(
FileList,
Formatting.Indented
);
using (StreamWriter sw = new StreamWriter(Path.Combine(selectedPath, "rectangles.txt"), false))
{
sw.WriteLine("Total number of rectangles: " + count + Environment.NewLine);
sw.Write(json);
sw.Close();
}
rectImage.Save(rectangleName);
saveRectanglesCounter++;
}
else
{
var stream = ToMemoryStream(rectImage);
var image = System.Drawing.Image.FromStream(stream);
pictureBox1.Image = image;
}
pixelsCounter = rect.Width * rect.Height;
pictureBox1.Invalidate();
listBox1.DataSource = FileList.Keys.ToList();
listBox1.SelectedIndex = listBox1.Items.Count - 1;
pictureBox2.Focus();
Graphics g = Graphics.FromImage(this.pictureBox1.Image);
g.Clear(this.pictureBox1.BackColor);
}
}
else
{
if (clearRectangles)
{
DrawingRects.Clear();
pictureBox2.Invalidate();
}
x = 0;
y = 0;
}
}
}
public class MyListBoxItem
{
public MyListBoxItem(Color c, string m)
{
ItemColor = c;
Message = m;
}
public Color ItemColor { get; set; }
public string Message { get; set; }
}
public MemoryStream ToMemoryStream(Bitmap b)
{
MemoryStream ms = new MemoryStream();
b.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
return ms;
}
string GetNextName(string baseName, string extension)
{
int counter = 1;
string nextName = baseName + counter + extension;
while (System.IO.File.Exists(nextName))
{
counter++;
nextName = baseName + counter + extension;
}
return nextName;
}
private void pictureBox2_Paint(object sender, PaintEventArgs e)
{
if (drawBorder)
{
ControlPaint.DrawBorder(e.Graphics, pictureBox2.ClientRectangle, Color.Red, ButtonBorderStyle.Solid);
}
if (pictureBox2.Image != null && selectedPath != null && DrawingRects.Count > 0)
{
DrawShapes(e.Graphics);
}
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
if (drawBorder)
{
ControlPaint.DrawBorder(e.Graphics, pictureBox1.ClientRectangle, Color.Red, ButtonBorderStyle.Solid);
}
if (rectImage != null && DrawingRects.Count > 0)
{
var dr = DrawingRects.Last();
e.Graphics.DrawImage(rectImage, dr.Rect);
if (clearRectangles)
{
DrawingRects.Clear();
pictureBox2.Invalidate();
}
}
}
private void DrawShapes(Graphics g)
{
if (DrawingRects.Count == 0) return;
g.SmoothingMode = SmoothingMode.AntiAlias;
foreach (var dr in DrawingRects)
{
if (dr.Rect.Width > 0 && dr.Rect.Height > 0)
{
using (Pen pen = new Pen(dr.DrawingcColor, dr.PenSize))
{
g.DrawRectangle(pen, dr.Rect);
};
}
}
}
public Bitmap cropAtRect(Bitmap b, Rectangle r)
{
Bitmap nb = new Bitmap(r.Width, r.Height);
using (Graphics g = Graphics.FromImage(nb))
{
g.DrawImage(b, -r.X, -r.Y);
return nb;
}
}
private void checkBoxDrawBorder_CheckedChanged(object sender, EventArgs e)
{
if (checkBoxDrawBorder.Checked)
{
drawBorder = true;
}
else
{
drawBorder = false;
}
pictureBox1.Invalidate();
pictureBox2.Invalidate();
}
private void checkBoxClearRectangles_CheckedChanged(object sender, EventArgs e)
{
if (checkBoxClearRectangles.Checked)
{
clearRectangles = true;
}
else
{
clearRectangles = false;
}
pictureBox2.Invalidate();
}
private void checkBoxSaveRectangles_CheckedChanged(object sender, EventArgs e)
{
if(checkBoxSaveRectangles.Checked)
{
saveRectangles = true;
}
else
{
saveRectangles = false;
}
}
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
var item = ((ListBox)sender).SelectedItem;
var val = FileList[(string)item];
if (File.Exists(val))
{
pictureBox1.Image = System.Drawing.Image.FromFile(val);
}
}
private void button1_Click(object sender, EventArgs e)
{
VistaOpenFileDialog dialog = new VistaOpenFileDialog();
{
dialog.Filter = "Images (*.jpg, *.bmp, *.gif)|*.jpg;*.bmp;*.gif";
};
if (dialog.ShowDialog() == DialogResult.OK)
{
textBox1.Text = dialog.FileName;
Properties.Settings.Default.ImageToCropFolder = dialog.FileName;
Properties.Settings.Default.Save();
Bitmap bmp = new Bitmap(Image.FromFile(dialog.FileName),
pictureBox2.Width, pictureBox2.Height);
pictureBox2.Image = bmp;
if(textBox1.Text != "" && textBox2.Text != "")
{
crop = true;
}
}
}
private void listBox1_DrawItem(object sender, DrawItemEventArgs e)
{
MyListBoxItem item = listBox1.Items[e.Index] as MyListBoxItem; // Get the current item and cast it to MyListBoxItem
if (item != null)
{
e.Graphics.DrawString( // Draw the appropriate text in the ListBox
item.Message, // The message linked to the item
listBox1.Font, // Take the font from the listbox
new SolidBrush(item.ItemColor), // Set the color
0, // X pixel coordinate
e.Index * listBox1.ItemHeight // Y pixel coordinate. Multiply the index by the ItemHeight defined in the listbox.
);
}
else
{
// The item isn't a MyListBoxItem, do something about it
}
}
private void button2_Click(object sender, EventArgs e)
{
VistaFolderBrowserDialog dialog = new VistaFolderBrowserDialog();
if (dialog.ShowDialog() == DialogResult.OK)
{
textBox2.Text = dialog.SelectedPath;
selectedPath = dialog.SelectedPath;
Properties.Settings.Default.CroppedImagesFolder = selectedPath;
Properties.Settings.Default.Save();
if (textBox1.Text != "" && textBox2.Text != "")
{
crop = true;
}
}
}
}
}
Your question is How to color items in listBox in different colors? and the "Y" of what might be considered an X-Y Problem is that you get an exception when you try to Add an item inline. After carefully reading your code, something that would make a big difference would be using the MyListBoxItem type consistently in both in your DataSource and your Json serialization and deserialization, and then appending the data source when you wish to Add an item inline.
Datasource
BindingList<MyListBoxItem> MyItems { get; } = new BindingList<MyListBoxItem>();
Example of MyItems in Json-Serialized form in the disk file
[
{
"ItemColor": "Blue",
"Message": "Blue Item"
},
{
"ItemColor": "Green",
"Message": "Green Item"
},
{
"ItemColor": "Red",
"Message": "Red Item"
}
]
Where:
public class MyListBoxItem
{
public Color ItemColor { get; set; }
public string Message { get; set; }
}
Main Form initialization for listBox1 drawing code
public partial class MainForm : Form
{
public MainForm() => InitializeComponent();
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
listBox1.DisplayMember = nameof(MyListBoxItem.Message);
listBox1.DataSource = MyItems;
listBox1.DrawItem += onDrawItem;
listBox1.DrawMode = DrawMode.OwnerDrawFixed;
listBox1.SelectedIndexChanged += (sender, e) => listBox1.Refresh();
// Tests
buttonTest.Click += onButtonTest;
buttonReadJson.Click += onButtonReadJson;
}
private void onDrawItem(object sender, DrawItemEventArgs e)
{
if((e.Index == -1) || (e.Index >= MyItems.Count))
{
e.DrawBackground();
}
else
{
var myItem = MyItems[e.Index];
if (listBox1.SelectedItems.Contains(myItem))
{
using (var backgroundBrush = new SolidBrush(myItem.ItemColor))
{
e.Graphics.FillRectangle(backgroundBrush, e.Bounds);
}
using (var textBrush = new SolidBrush(Color.White))
{
e.Graphics.DrawString(myItem.Message, listBox1.Font, textBrush, e.Bounds);
}
}
else
{
using (var backgroundBrush = new SolidBrush(SystemColors.Window))
{
e.Graphics.FillRectangle(backgroundBrush, e.Bounds);
}
using (var textBrush = new SolidBrush(myItem.ItemColor))
{
e.Graphics.DrawString(myItem.Message, listBox1.Font, textBrush, e.Bounds);
}
}
}
}
BindingList<MyListBoxItem> MyItems { get; } = new BindingList<MyListBoxItem>();
.
.
.
}
Example of adding items
private void onButtonTest(object sender, EventArgs e)
{
MyItems.Clear();
MyItems.Add(new MyListBoxItem
{
Message = "Validated data successfully",
ItemColor = Color.Green,
});
MyItems.Add(new MyListBoxItem
{
Message = "Failed to validate data",
ItemColor = Color.Red,
});
}
Example of deserializing file
private void onButtonReadJson(object sender, EventArgs e)
{
MyItems.Clear();
foreach (
var myItem
in JsonConvert.DeserializeObject<List<MyListBoxItem>>(mockFileContents))
{
MyItems.Add(myItem);
}
}
const string mockFileContents =
#"[
{
""ItemColor"": ""Blue"",
""Message"": ""Blue Item""
},
{
""ItemColor"": ""Green"",
""Message"": ""Green Item""
},
{
""ItemColor"": ""Red"",
""Message"": ""Red Item""
}
]";
The treeview control code :
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
public class AdvancedTreeView : TreeView
{
private const int TVM_SETEXTENDEDSTYLE = 0x1100 + 44;
private const int TVM_GETEXTENDEDSTYLE = 0x1100 + 45;
private const int TVS_EX_DOUBLEBUFFER = 0x0004;
[DllImport("user32.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);
private Bitmap openedIcon, closedIcon;
private List<TreeNode> rootNodes = new List<TreeNode>();
public AdvancedTreeView()
{
DrawMode = TreeViewDrawMode.OwnerDrawText;
ShowLines = false;
AlternateBackColor = BackColor;
ArrowColor = SystemColors.WindowText;
this.AllowDrop = true;
}
public Color AlternateBackColor { get; set; }
public Color ArrowColor { get; set; }
protected override void OnHandleCreated(EventArgs e)
{
SendMessage(this.Handle, TVM_SETEXTENDEDSTYLE, (IntPtr)TVS_EX_DOUBLEBUFFER, (IntPtr)TVS_EX_DOUBLEBUFFER);
base.OnHandleCreated(e);
}
protected override void OnDrawNode(DrawTreeNodeEventArgs e)
{
Color backColor = (GetTopNodeIndex(e.Node) & 1) == 0 ? BackColor : AlternateBackColor;
using (Brush b = new SolidBrush(backColor))
{
e.Graphics.FillRectangle(b, new Rectangle(0, e.Bounds.Top, ClientSize.Width, e.Bounds.Height));
}
if ((e.State & TreeNodeStates.Selected) != 0)
{
e.Graphics.FillRectangle(Brushes.Green, e.Bounds);
}
// icon
if (e.Node.Nodes.Count > 0)
{
Image icon = GetIcon(e.Node.IsExpanded);
e.Graphics.DrawImage(icon, e.Bounds.Left - icon.Width - 3, e.Bounds.Top);
}
// text (due to OwnerDrawText mode, indenting of e.Bounds will be correct)
TextRenderer.DrawText(e.Graphics, e.Node.Text, Font, e.Bounds, ForeColor);
// indicate selection (if not by backColor):
if ((e.State & TreeNodeStates.Selected) != 0)
ControlPaint.DrawFocusRectangle(e.Graphics, e.Bounds);
}
protected override void OnItemDrag(ItemDragEventArgs e)
{
// Move the dragged node when the left mouse button is used.
if (e.Button == MouseButtons.Left)
{
DoDragDrop(e.Item, DragDropEffects.Move);
}
// Copy the dragged node when the right mouse button is used.
else if (e.Button == MouseButtons.Right)
{
DoDragDrop(e.Item, DragDropEffects.Copy);
}
}
protected override void OnDragOver(DragEventArgs e)
{
// Retrieve the client coordinates of the mouse position.
Point targetPoint = this.PointToClient(new Point(e.X, e.Y));
// Select the node at the mouse position.
this.SelectedNode = this.GetNodeAt(targetPoint);
}
protected override void OnDragDrop(DragEventArgs e)
{
Point targetPoint = PointToClient(new Point(e.X, e.Y));
TreeNode targetNode = GetNodeAt(targetPoint);
TreeNode draggedNode = (TreeNode)e.Data.GetData(typeof(TreeNode));
if (draggedNode == null || targetNode == null || draggedNode.Level != targetNode.Level)
{
return;
}
else
{
TreeNode parentNode = targetNode;
if (!draggedNode.Equals(targetNode) && targetNode != null)
{
bool canDrop = true;
while (canDrop && (parentNode != null))
{
canDrop = !Object.ReferenceEquals(draggedNode, parentNode);
parentNode = parentNode.Parent;
}
if (canDrop)
{
TreeNode treeNode = draggedNode.Parent;
if (treeNode != null)
{
int index = draggedNode.Index;
draggedNode.Remove();
treeNode.Nodes.Insert(targetNode.Index, draggedNode);
targetNode.Remove();
treeNode.Nodes.Insert(index, targetNode);
}
else
{
int draggedindex = draggedNode.Index;
int targetindex = targetNode.Index;
draggedNode.Remove();
targetNode.Remove();
this.Nodes.Insert(targetindex, draggedNode);
this.Nodes.Insert(draggedindex, targetNode);
}
}
}
}
SelectedNode = draggedNode;
}
private int GetTopNodeIndex(TreeNode node)
{
while (node.Parent != null)
node = node.Parent;
return Nodes.IndexOf(node);
}
// Determine whether one node is a parent
// or ancestor of a second node.
private bool ContainsNode(TreeNode node1, TreeNode node2)
{
// Check the parent node of the second node.
if (node2.Parent == null) return false;
if (node2.Parent.Equals(node1)) return true;
// If the parent node is not null or equal to the first node,
// call the ContainsNode method recursively using the parent of
// the second node.
return ContainsNode(node1, node2.Parent);
}
private Image GetIcon(bool nodeIsExpanded)
{
if (openedIcon == null)
InitIcons();
return nodeIsExpanded ? openedIcon : closedIcon;
}
private void InitIcons()
{
openedIcon = new Bitmap(16, 16);
closedIcon = new Bitmap(16, 16);
using (Brush b = new SolidBrush(ArrowColor))
{
using (Graphics g = Graphics.FromImage(openedIcon))
g.FillPolygon(b, new[] { new Point(0, 0), new Point(15, 0), new Point(8, 15), });
using (Graphics g = Graphics.FromImage(closedIcon))
g.FillPolygon(b, new[] { new Point(0, 0), new Point(15, 8), new Point(0, 15), });
}
}
}
And form1 where I'm adding the nodes and using the trackbar :
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Treeview_Test
{
public partial class Form1 : Form
{
int numberofroots = 1000;
int childsnum = 50;
int childsnumperlevel = 5;
int leveldepth = 6;
public Form1()
{
InitializeComponent();
AddNodes();
for (int i = 0; i < 100; i++)
{
ComboboxItem boxitem = new ComboboxItem();
boxitem.Text = i.ToString();
comboBox1.Items.Add(boxitem);
}
comboBox1.SelectedIndex = 0;
}
private void AddNodes()
{
advancedTreeView1.Nodes.Clear();
for (int i = 0; i < numberofroots; i++)
{
TreeNode subnode = advancedTreeView1.Nodes.Add("New Node " + i);
if (allRootsWithChilds.Checked || i < childsnum)
{
for (int j = 0; j < leveldepth; j++)
{
// Add a new child node and set subnode to the new node we just added
subnode = subnode.Nodes.Add(subnode.Text);
for (int x = 0; x < childsnumperlevel; x++)
{
subnode.Nodes.Add("New Node " + x);
}
}
}
}
}
private void allRootsWithChilds_CheckedChanged(object sender, EventArgs e)
{
AddNodes();
}
private void advancedTreeView1_AfterSelect(object sender, TreeViewEventArgs e)
{
textBox2.Text = e.Node.Text;
}
private void button1_Click(object sender, EventArgs e)
{
}
public class ComboboxItem
{
public string Text { get; set; }
public object Value { get; set; }
public override string ToString()
{
return Text;
}
}
private void trackBar1_Scroll(object sender, EventArgs e)
{
advancedTreeView1.ItemHeight = 5;
advancedTreeView1.SelectedNode.NodeFont = new Font("Arial", 5);
// Get the font size from combobox.
string selectedString = trackBar1.Value.ToString();//comboBox1.SelectedItem.ToString();
int myNodeFontSize = Int32.Parse(selectedString);
// Set the font of root node.
advancedTreeView1.SelectedNode.NodeFont = new Font("Arial", myNodeFontSize);
for (int i = 0; i < advancedTreeView1.Nodes[0].Nodes.Count; i++)
{
// Set the font of child nodes.
advancedTreeView1.Nodes[0].Nodes[i].NodeFont =
new Font("Arial", myNodeFontSize);
}
// Get the bounds of the tree node.
Rectangle myRectangle = advancedTreeView1.SelectedNode.Bounds;
int myNodeHeight = myRectangle.Height;
if (myNodeHeight < myNodeFontSize)
{
myNodeHeight = myNodeFontSize;
}
advancedTreeView1.ItemHeight = myNodeHeight + 4;
}
}
}
The first problem is each time I'm changing the trackBar value from 1 to 2 it's taking time the treeview to redraw. I'm not sure if it's drawing the whole treeview over again or only the nodes. Maybe the problem is the OnDrawNode override method in the AdvancedTreeView control code since this method is being called all the time.
The second problem is that when the nodes are resizing getting bigger each time when moving the trackBar to a higher value the text of the nodes is keep staying the same size and not resizing with the nodes. How can I make that the text of the nodes will be resized automatic fitting the size of the nodes?
And the red arrow the icon in the AdvancedTreeView code is also not fitting the nodes sizes the arrow is getting in each node higher and higher.
Here is a screenshot of the treeview when the trackbar value is 1: Now it's all fitting :
And this screenshot is after I moved the trackBar to the right to about the middle about value 50 :
Now the text is in the middle of each node but the text is still too small and not being resized with the nodes.
And the red arrow the icon in each node seems to be on the top left corner instead like in the first screenshot.
The icon/red arrow should also resize with the nodes and stay in the centre pointing the text of the nodes.
Edit :
Tried to reduce the code in the trackBar1_Scroll event to test and it sitll drawing slow when changing the trackBar values :
private void trackBar1_Scroll(object sender, EventArgs e)
{
// Get the font size from combobox.
string selectedString = trackBar1.Value.ToString();
int myNodeFontSize = Int32.Parse(selectedString);
// Get the bounds of the tree node.
Rectangle myRectangle = advancedTreeView1.SelectedNode.Bounds;
int myNodeHeight = myRectangle.Height;
if (myNodeHeight < myNodeFontSize)
{
myNodeHeight = myNodeFontSize;
}
advancedTreeView1.ItemHeight = myNodeHeight + 4;
}
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;
}
}
I made small application(winforms) to show the cricket score (the world has just started , yay).
It works fine in xp, but in win 7 the label shows a few pixels down in position as compared to its position in xp, which totally ruins everything. ( i hope that was clear )
here is the exe: [REDACTED]
how it looks in xp; http://imgur.com/emcKG.jpg
how it looks in 7(approx): http://imgur.com/sdqry.jpg
also can someone confirm which .net my app requires ? I think its .net 2.0, since the target framework is set to .Net 2.0 .
Thanks
Edit: won't post the exe next time. sorry!
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Net;
using System.Xml;
using System.Runtime.InteropServices;
using Microsoft.Win32;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
int numberOfMatches = 0;
int selectedmatch = 0;
string[,] data;
string fileToParse = Path.GetTempPath() + "cricketfile.xml";
int matchToShow = 0;
public const int WM_NCLBUTTONDOWN = 0xA1;
public const int HT_CAPTION = 0x2;
[DllImportAttribute("user32.dll")]
public static extern int SendMessage(IntPtr hWnd,
int Msg, int wParam, int lParam);
[DllImportAttribute("user32.dll")]
public static extern bool ReleaseCapture();
RegistryKey rkApp = Registry.CurrentUser.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", true);
public Form1()
{
InitializeComponent();
int X = System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width / 2 - this.Width / 2;
this.Location = new System.Drawing.Point(X, -5);
if (rkApp.GetValue("cricketscore") == null)
{
startWithWindowsToolStripMenuItem.Checked = false ;
}
else
{
startWithWindowsToolStripMenuItem.Checked = true ;
}
this.Region = new Region(new Rectangle(10, 10, 197, 17));
ToolTip tooltip = new ToolTip();
tooltip.SetToolTip(pictureBox1, " right click for settings, left click to move");
tooltip.SetToolTip(label1, " right click for settings, left click to move");
fetchData();
addContextEntries();
chooseIndia();
updateScore();
// MessageBox.Show(data.GetLength(0).ToString());
//foreach (string s in data)
//{
// Console.WriteLine(s);
//}
timer1.Interval = 10 * 1000;
timer1.Enabled = true;
}
public void addContextEntries()
{
// MessageBox.Show("num- " + numberOfMatches);
List<ToolStripMenuItem> Mylist1 = new List<ToolStripMenuItem>();
for (int i = 0; i < numberOfMatches; i++)
{
Mylist1.Add( new ToolStripMenuItem() );
this.contextMenuStrip1.Items.Add(Mylist1[i]);
Mylist1[i].Text = "See Match " + (i + 1) + "'s score";
switch(i)
{
case 0:
Mylist1[i].Click += new System.EventHandler(this.match1);
break;
case 1:
Mylist1[i].Click += new System.EventHandler(this.match2);
break;
case 2:
Mylist1[i].Click += new System.EventHandler(this.match3);
break;
case 3:
Mylist1[i].Click += new System.EventHandler(this.match4);
break;
case 4:
Mylist1[i].Click += new System.EventHandler(this.match5);
break;
}
}
}
public void match1(object sender, EventArgs e)
{
// MessageBox.Show("match 1");
matchToShow = 0;
label1.Text = data[0, 0] + " " + data[0, 1];
}
public void match2(object sender, EventArgs e)
{
// MessageBox.Show("match 2");
matchToShow = 1;
label1.Text = data[1, 0] + " " + data[1, 1];
}
public void match3(object sender, EventArgs e)
{
matchToShow = 2;
label1.Text = data[2, 0] + " " + data[2, 1];
}
public void match4(object sender, EventArgs e)
{
matchToShow = 3;
label1.Text = data[3, 0] + " " + data[3, 1];
}
public void match5(object sender, EventArgs e)
{
matchToShow = 4;
label1.Text = data[4, 0] + " " + data[4, 1];
}
public void chooseIndia()
{
for (int i = 0; i < data.GetLength(0); i++)
{
// MessageBox.Show("i - " + i);
if (data[i, 3].ToLower().Contains("australia"))
{
matchToShow = i;
// MessageBox.Show("i - " + i);
break;
}
}
}
public void updateScore()
{
fetchData();
//foreach (string s in data)
//{
// Console.WriteLine(s);
//}
// MessageBox.Show("matchToShow- " + matchToShow);
label1.Text = data[matchToShow,0] + " " + data[matchToShow ,1];
}
public void fetchData()
{
int matchnumber = -1;
numberOfMatches = 0;
WebClient Client = new WebClient();
try
{
Client.DownloadFile("http://synd.cricbuzz.com/score-gadget/gadget-scores-feed.xml", fileToParse);
}
catch ( WebException we)
{
if (we.ToString().ToLower().Contains("connect to"))
;//MessageBox.Show("unable to connect to server") ;
}
XmlTextReader xmlreader0 = new XmlTextReader(fileToParse);
while (xmlreader0.Read())
{
if (xmlreader0.Name.ToString() == "match" && xmlreader0.NodeType == XmlNodeType.Element)
{
++numberOfMatches;
}
}
data = new string[numberOfMatches, 4];
// numberOfMatches = 0;
// MessageBox.Show("matchnumbers - " + numberOfMatches);
XmlTextReader xmlreader = new XmlTextReader(fileToParse);
while (xmlreader.Read())
{
if (xmlreader.Name.ToString() == "header" && xmlreader.NodeType == XmlNodeType.Element)
{
xmlreader.Read();
data[matchnumber, 0] = xmlreader.Value;
// MessageBox.Show(xmlreader.Value);
}
if (xmlreader.Name == "description" && xmlreader.NodeType == XmlNodeType.Element)
{
// MessageBox.Show(xmlreader.Value);
xmlreader.Read();
// MessageBox.Show("matched - " + xmlreader.Value);
data[matchnumber, 1] = xmlreader.Value;
}
if (xmlreader.Name == "url-text" && xmlreader.NodeType == XmlNodeType.Element)
{
xmlreader.Read();
// MessageBox.Show(xmlreader.Value);
data[matchnumber, 2] = xmlreader.Value;
}
if (xmlreader.Name == "url-link" && xmlreader.NodeType == XmlNodeType.Element)
{
xmlreader.Read();
data[matchnumber, 3] = xmlreader.Value;
}
if (xmlreader.Name.ToString() == "match" && xmlreader.NodeType == XmlNodeType.Element)
{
matchnumber++;
}
}
xmlreader.Close();
xmlreader0.Close();
}
private void timer1_Tick(object sender, EventArgs e)
{
updateScore();
}
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
ReleaseCapture();
SendMessage(Handle, WM_NCLBUTTONDOWN, HT_CAPTION, 0);
}
}
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
ReleaseCapture();
SendMessage(Handle, WM_NCLBUTTONDOWN, HT_CAPTION, 0);
}
}
private void exitToolStripMenuItem_Click(object sender, EventArgs e)
{
Application.Exit();
}
private void switchColrosToolStripMenuItem_Click(object sender, EventArgs e)
{
if ( label1.ForeColor == System.Drawing.Color.Black)
label1.ForeColor = System.Drawing.Color.White ;
else
label1.ForeColor = System.Drawing.Color.Black;
}
private void startWithWindowsToolStripMenuItem_Click(object sender, EventArgs e)
{
}
private void startWithWindowsToolStripMenuItem_CheckStateChanged(object sender, EventArgs e)
{
if (startWithWindowsToolStripMenuItem.Checked == true)
{
rkApp.SetValue("cricketscore", Application.ExecutablePath.ToString());
}
else
{
rkApp.DeleteValue("cricketscore", false);
}
}
private void showFullScorecardToolStripMenuItem_Click(object sender, EventArgs e)
{
System.Diagnostics.Process.Start(data[matchToShow, 3]);
}
private void label1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
ReleaseCapture();
SendMessage(Handle, WM_NCLBUTTONDOWN, HT_CAPTION, 0);
}
}
//public void findNumberOfMatches()
//{
// if (xmlreader.Name.ToString() == "match" && xmlreader.NodeType == XmlNodeType.Element)
// {
// matchnumber++;
// }
//}
}
}
btw how do I get by exe verified , so that normal people can use it without fear ? virustotal.com ?
Edit: oops , I was wrong ,tis not just the label. the picturebox to the left of the label has also been shifted down a few pixels.
It appears, from your screenshots, that the entire box that holds the text shrinks, and there's a blue bar that covers part of the label.
Perhaps it is a resolution issue or dpi issue. also they change color from one OS to the other??
You might need to set the locations and other properties in the code with a static ,hard coded values, rather than letting windows place it from the designer view or using some variable number based on the size of the screen
this is in c#.net should be similar
label1.Left = 10;
label1.Top = 10;
this.Region = new Region(new Rectangle(10, 10, 197, 17));
Delete that. It makes your window too small on a machine with a higher video dots-per-inch setting. Quite common on Win7. A higher DPI makes the fonts taller in pixels. The Form.AutoScaleMode property automatically adjusts for that by making the controls larger to fit that bigger font. Your Region doesn't grow though, cutting off the bottom of the controls. No idea why you use a Region in the first place since its a plain rectangle, I suppose you are looking for FormBorderStyle = None. A form won't allow you to make it too small but you can override that in the OnLoad method. Set the ClientSize large enough to fit the rescaled controls.
This has to do with a difference in the DPI settings on Windows XP and Windows 7.
How to change DPI on Windows 7.
How to change DPI on Windows XP.
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;
}
}
}